From 0911ad6827d1e8e033359980c2b69e7abeb5e554 Mon Sep 17 00:00:00 2001 From: Rodrigo Delduca Date: Mon, 2 Dec 2024 15:41:59 -0300 Subject: [PATCH] Work in progress --- CMakeLists.txt | 2 +- src/common.hpp | 7 +++ src/main.cpp | 131 +++++++++++++++++++++++++++++++++++------ src/networkmanager.cpp | 111 ---------------------------------- src/networkmanager.hpp | 21 ------- src/querybuilder.cpp | 36 +++++++++++ src/querybuilder.hpp | 15 +++++ 7 files changed, 171 insertions(+), 152 deletions(-) delete mode 100644 src/networkmanager.cpp delete mode 100644 src/networkmanager.hpp create mode 100644 src/querybuilder.cpp create mode 100644 src/querybuilder.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index df0ec79..20d8f4b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -52,7 +52,7 @@ if (${CMAKE_SYSTEM_NAME} MATCHES "Emscripten") "-s ALLOW_MEMORY_GROWTH=1" "-s INITIAL_MEMORY=134217728" "-s EXPORTED_RUNTIME_METHODS=['callMain']" - "-s FETCH=1" + "-l websocket.js" # Debugging # "-s RUNTIME_DEBUG" # "-s ASSERTIONS=2" diff --git a/src/common.hpp b/src/common.hpp index d1c16e2..8e74ee1 100644 --- a/src/common.hpp +++ b/src/common.hpp @@ -12,6 +12,7 @@ #ifdef EMSCRIPTEN #include +#include #endif #include @@ -44,9 +45,11 @@ #include #include #include +#include #include #include #include +#include #include #include @@ -108,4 +111,8 @@ namespace math { class vector2d; } +namespace network { +class querybuilder; +} + using namespace std::literals; diff --git a/src/main.cpp b/src/main.cpp index 8f2e353..7262516 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,28 +1,121 @@ #include "application.hpp" -#include "networkmanager.hpp" +#include "common.hpp" +#include "querybuilder.hpp" -int main(int argc, char **argv) { -#if 1 - try { - network::networkmanager manager; - - network::networkrequest request; - request.url = "http://ifconfig.me/"; - request.method = "GET"; - request.callback = [](const std::string &response, long status_code) { - if (status_code == 200) { - std::cout << "Public IP: " << response << std::endl; - } else { - std::cerr << "Failed to fetch IP. Status code: " << status_code << std::endl; - } +class socketio { +public: + explicit socketio(const std::string &url) { + if (!emscripten_websocket_is_supported()) { + throw std::runtime_error("WebSocket is not supported in this environment."); + } + + std::cout << "url " << url << std::endl; + + EmscriptenWebSocketCreateAttributes attributes = { + url.c_str(), nullptr, true }; + socket = emscripten_websocket_new(&attributes); + if (socket <= 0) { + throw std::runtime_error("Failed to create WebSocket."); + } + + emscripten_websocket_set_onopen_callback(socket, this, [](int, const EmscriptenWebSocketOpenEvent *event, void *user_data) -> EM_BOOL { + UNUSED(event); + const auto self = static_cast(user_data); + + std::string connectMessage = R"({"type":"connect"})"; // Exemplo de formato JSON + emscripten_websocket_send_utf8_text(event->socket, connectMessage.c_str()); + + if (self->on_open) self->on_open(); + return true; + }); + + emscripten_websocket_set_onmessage_callback(socket, this, [](int, const EmscriptenWebSocketMessageEvent *event, void *user_data) -> EM_BOOL { + auto self = static_cast(user_data); + if (event->isText && self->on_message) { + std::string message(event->data, event->data + event->numBytes); + self->on_message(message); + } + return true; + }); + + emscripten_websocket_set_onclose_callback(socket, this, [](int, const EmscriptenWebSocketCloseEvent *event, void *user_data) -> EM_BOOL { + auto self = static_cast(user_data); + if (self->on_close) self->on_close(event->reason); + return true; + }); + + emscripten_websocket_set_onerror_callback(socket, this, [](int, const EmscriptenWebSocketErrorEvent *, void *user_data) -> EM_BOOL { + auto self = static_cast(user_data); + if (self->on_error) self->on_error(); + return true; + }); + } + + ~socketio() { + if (socket > 0) { + emscripten_websocket_close(socket, 1000, "Normal closure"); + emscripten_websocket_delete(socket); + } + } + + void emit(const std::string &event, const std::string &message) { + if (socket > 0) { + const auto packet = fmt::format("42[\"{}\",\"{}\"]", event, message); + emscripten_websocket_send_utf8_text(socket, packet.data()); + } + } - manager.send(request); - } catch (const std::exception &e) { - std::cerr << "Error: " << e.what() << std::endl; + void set_on_open(const std::function &callback) { + on_open = callback; } -#endif + + void set_on_message(const std::function &callback) { + on_message = callback; + } + + void set_on_close(const std::function &callback) { + on_close = callback; + } + + void set_on_error(const std::function &callback) { + on_error = callback; + } + +private: + EMSCRIPTEN_WEBSOCKET_T socket; + std::function on_open; + std::function on_message; + std::function on_close; + std::function on_error; +}; + +int main(int argc, char **argv) { + network::querybuilder qb; + const auto query = qb.add("EIO", "4") + .add("transport", "websocket") + .build(); + + socketio io(fmt::format("{}/{}/?{}", "ws://localhost:9000", "socket.io", query)); + + io.set_on_open([]() { + std::cout << "Connected to the server." << std::endl; + }); + + io.set_on_message([](const std::string &message) { + std::cout << "Message received: " << message << std::endl; + }); + + io.set_on_close([](const std::string &reason) { + std::cout << "Connection closed. Reason: " << reason << std::endl; + }); + + io.set_on_error([]() { + std::cerr << "An error occurred." << std::endl; + }); + + // io.emit("echo", "Hello, Server!"); framework::application app(argc, std::move(argv)); diff --git a/src/networkmanager.cpp b/src/networkmanager.cpp deleted file mode 100644 index 80fbb41..0000000 --- a/src/networkmanager.cpp +++ /dev/null @@ -1,111 +0,0 @@ -#include "networkmanager.hpp" - -#ifdef EMSCRIPTEN -#include -#else -#include -#endif - -#include - -using namespace network; - -networkmanager::networkmanager() { -#ifndef EMSCRIPTEN - const auto result = curl_global_init(CURL_GLOBAL_DEFAULT); - if (result != CURLE_OK) { - throw std::runtime_error(fmt::format("Failed to initialize cURL: {}", curl_easy_strerror(result))); - } -#endif -} - -networkmanager::~networkmanager() { -#ifndef EMSCRIPTEN - curl_global_cleanup(); -#endif -} - -void networkmanager::send(const networkrequest &request) { -#ifdef EMSCRIPTEN - emscripten_fetch_attr_t attr; - emscripten_fetch_attr_init(&attr); - - strncpy(attr.requestMethod, request.method.c_str(), sizeof(attr.requestMethod) - 1); - attr.requestMethod[sizeof(attr.requestMethod) - 1] = '\0'; - - auto user_request = std::make_unique(request); - attr.userData = user_request.release(); - - attr.onsuccess = [](emscripten_fetch_t *fetch) { - std::unique_ptr request(static_cast(fetch->userData)); - request->callback(std::string(fetch->data, fetch->numBytes), fetch->status); - emscripten_fetch_close(fetch); - }; - - attr.onerror = [](emscripten_fetch_t *fetch) { - std::unique_ptr request(static_cast(fetch->userData)); - request->callback("", fetch->status); - emscripten_fetch_close(fetch); - }; - - if (!request.body.empty()) { - attr.requestData = request.body.c_str(); - attr.requestDataSize = request.body.size(); - } - - if (!request.headers.empty()) { - std::vector header_strings; - std::vector header_pointers; - - for (const auto &[key, value] : request.headers) { - header_strings.emplace_back(key + ": " + value); - } - - for (const auto &header : header_strings) { - header_pointers.push_back(header.c_str()); - } - - header_pointers.push_back(nullptr); - - attr.requestHeaders = header_pointers.data(); - } - - emscripten_fetch(&attr, request.url.c_str()); -#else - auto curl = std::unique_ptr(curl_easy_init(), &curl_easy_cleanup); - - curl_easy_setopt(curl.get(), CURLOPT_URL, request.url.c_str()); - curl_easy_setopt(curl.get(), CURLOPT_CUSTOMREQUEST, request.method.c_str()); - - auto headers = std::unique_ptr(nullptr, &curl_slist_free_all); - for (const auto &[key, value] : request.headers) { - const auto header = key + ": " + value; - headers.reset(curl_slist_append(headers.release(), header.c_str())); - } - if (headers) { - curl_easy_setopt(curl.get(), CURLOPT_HTTPHEADER, headers.get()); - } - - if (!request.body.empty()) { - curl_easy_setopt(curl.get(), CURLOPT_POSTFIELDS, request.body.c_str()); - } - - auto data = std::string{}; - curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, [](void *ptr, size_t size, size_t nmemb, void *userdata) { - const auto real_size = size * nmemb; - auto *response = static_cast(userdata); - response->append(static_cast(ptr), real_size); - return real_size; - }); - curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, &data); - - const auto result = curl_easy_perform(curl.get()); - if (result != CURLE_OK) { - throw std::runtime_error(fmt::format("cURL request failed: {}", curl_easy_strerror(result))); - } - - long code = 0; - curl_easy_getinfo(curl.get(), CURLINFO_RESPONSE_CODE, &code); - request.callback(data, code); -#endif -} diff --git a/src/networkmanager.hpp b/src/networkmanager.hpp deleted file mode 100644 index e8b79ea..0000000 --- a/src/networkmanager.hpp +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - -#include "common.hpp" - -namespace network { -struct networkrequest { - std::string url; - std::string method; - std::string body; - std::unordered_map headers; - std::function callback; -}; - -class networkmanager { -public: - networkmanager(); - ~networkmanager(); - - void send(const networkrequest &request); -}; -} diff --git a/src/querybuilder.cpp b/src/querybuilder.cpp new file mode 100644 index 0000000..6cae78a --- /dev/null +++ b/src/querybuilder.cpp @@ -0,0 +1,36 @@ +#include "querybuilder.hpp" + +using namespace network; + +static std::string encode(std::string_view value) { + std::string encoded; + encoded.reserve(value.size()); + for (char c : value) { + if (std::isalnum(static_cast(c)) || c == '-' || c == '_' || c == '.' || c == '~') { + encoded += c; + } else { + encoded += '%'; + encoded += static_cast((c >> 4) < 10 ? '0' + ((c >> 4) & 0xF) : 'A' + (((c >> 4) & 0xF) - 10)); + encoded += static_cast((c & 0xF) < 10 ? '0' + (c & 0xF) : 'A' + ((c & 0xF) - 10)); + } + } + return encoded; +} + +querybuilder &querybuilder::add(const std::string &key, const std::string &value) noexcept { + _parameters.emplace(std::string(key), std::string(value)); + return *this; +} + +std::string querybuilder::build() const noexcept { + if (_parameters.empty()) { + return {}; + } + + std::ostringstream query; + for (auto it = _parameters.begin(); it != _parameters.end(); ++it) { + query << it->first << '=' << encode(it->second); + if (std::next(it) != _parameters.end()) query << '&'; + } + return query.str(); +} diff --git a/src/querybuilder.hpp b/src/querybuilder.hpp new file mode 100644 index 0000000..2b115d9 --- /dev/null +++ b/src/querybuilder.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include "common.hpp" + +namespace network { +class querybuilder { +public: + querybuilder &add(const std::string &key, const std::string &value) noexcept; + + [[nodiscard]] std::string build() const noexcept; + +private: + std::unordered_map _parameters; +}; +}