From 4e23c463208a3d96c94e73b06b3cbe295d98f8a7 Mon Sep 17 00:00:00 2001 From: Dorin Date: Fri, 13 Mar 2026 12:50:35 +0200 Subject: [PATCH 01/26] add NoContent code --- source/gameanalytics/GAHTTPApi.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/gameanalytics/GAHTTPApi.h b/source/gameanalytics/GAHTTPApi.h index 3798bf00..f4f898da 100644 --- a/source/gameanalytics/GAHTTPApi.h +++ b/source/gameanalytics/GAHTTPApi.h @@ -32,8 +32,9 @@ namespace gameanalytics BadRequest = 6, // 400 Unauthorized = 7, // 401 UnknownResponseCode = 8, - Ok = 9, + Ok = 9, // 200 Created = 10, + NoContent = 11, // 204 InternalError }; From 4079ca9b685282179931b64d4b642c49db0d9e50 Mon Sep 17 00:00:00 2001 From: Dorin Date: Fri, 13 Mar 2026 12:51:04 +0200 Subject: [PATCH 02/26] handle code 204 as success --- source/gameanalytics/GAHTTPApi.cpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/source/gameanalytics/GAHTTPApi.cpp b/source/gameanalytics/GAHTTPApi.cpp index 16e5bbe3..a40c2b29 100644 --- a/source/gameanalytics/GAHTTPApi.cpp +++ b/source/gameanalytics/GAHTTPApi.cpp @@ -199,7 +199,7 @@ namespace gameanalytics res = curl_easy_perform(curl); if (res != CURLE_OK) { - logging::GALogger::d(curl_easy_strerror(res)); + logging::GALogger::d("%s", curl_easy_strerror(res)); return NoResponse; } @@ -211,13 +211,21 @@ namespace gameanalytics EGAHTTPApiResponse requestResponseEnum = processRequestResponse(response_code, s.packet.data(), "Events"); + const bool isValidResponse = + requestResponseEnum == Ok || requestResponseEnum == Created || requestResponseEnum == NoContent; + // if not 200 result - if (requestResponseEnum != Ok && requestResponseEnum != Created && requestResponseEnum != BadRequest) + if (!isValidResponse && requestResponseEnum != BadRequest) { logging::GALogger::d("Failed Events Call. URL: %s, JSONString: %s, Authorization: %s", url.c_str(), jsonString.c_str(), authorization.data()); return requestResponseEnum; } + if(requestResponseEnum == NoContent) + { + return requestResponseEnum; + } + // decode JSON json requestJsonDict = json::parse(s.toString()); if (requestJsonDict.is_null()) @@ -438,6 +446,10 @@ namespace gameanalytics { return Created; } + if(statusCode == 204) + { + return NoContent; + } // 401 can return 0 status if (statusCode == 0 || statusCode == 401) From f2aa42d9c4fe2690ba3bec025dec6c0ef9f1c006 Mon Sep 17 00:00:00 2001 From: Dorin Date: Fri, 13 Mar 2026 12:51:29 +0200 Subject: [PATCH 03/26] treat code 204 as valid response --- source/gameanalytics/GAEvents.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/gameanalytics/GAEvents.cpp b/source/gameanalytics/GAEvents.cpp index 64d58ecd..e40616ad 100644 --- a/source/gameanalytics/GAEvents.cpp +++ b/source/gameanalytics/GAEvents.cpp @@ -621,7 +621,7 @@ namespace gameanalytics responseEnum = http.sendEventsInArray(dataDict, payloadArray); #endif - if (responseEnum == http::Ok) + if (responseEnum == http::Ok || responseEnum == http::NoContent) { // Delete events store::GAStore::executeQuerySync(deleteSql); From 1849945599140489701ecc1b3c315011ea56f7d7 Mon Sep 17 00:00:00 2001 From: Dorin Date: Mon, 16 Mar 2026 12:58:01 +0200 Subject: [PATCH 04/26] allow 204 for sdk error event --- source/gameanalytics/GAHTTPApi.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/gameanalytics/GAHTTPApi.cpp b/source/gameanalytics/GAHTTPApi.cpp index a40c2b29..f3aa1977 100644 --- a/source/gameanalytics/GAHTTPApi.cpp +++ b/source/gameanalytics/GAHTTPApi.cpp @@ -364,9 +364,9 @@ namespace gameanalytics logging::GALogger::d("sdk error content : %s", s.toString().c_str());; // if not 200 result - if (statusCode != 200) + if (statusCode != 200 && statusCode != 204) { - logging::GALogger::d("sdk error failed. response code not 200. status code: %u", CURLE_OK); + logging::GALogger::d("sdk error failed. response code not 200 or 204. status code: %u", CURLE_OK); return; } From 7911d53c241b11ed2ef0c6a38f016f516ea84dfd Mon Sep 17 00:00:00 2001 From: Dorin Date: Mon, 16 Mar 2026 13:02:57 +0200 Subject: [PATCH 05/26] add response code constants --- source/gameanalytics/GAHTTPApi.cpp | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/source/gameanalytics/GAHTTPApi.cpp b/source/gameanalytics/GAHTTPApi.cpp index f3aa1977..6f3e3bf4 100644 --- a/source/gameanalytics/GAHTTPApi.cpp +++ b/source/gameanalytics/GAHTTPApi.cpp @@ -14,6 +14,13 @@ namespace gameanalytics { namespace http { + constexpr int HTTP_RESPONSE_OK = 200; + constexpr int HTTP_RESPONSE_CREATED = 201; + constexpr int HTTP_RESPONSE_NO_CONTENT = 204; + constexpr int HTTP_RESPONSE_BAD_REQUEST = 400; + constexpr int HTTP_RESPONSE_UNAUTHORIZED = 401; + constexpr int HTTP_RESPONSE_INTERNAL_ERROR = 500; + size_t writefunc(void *ptr, size_t size, size_t nmemb, ResponseData *s) { const size_t new_len = s->packet.size() + size * nmemb + 1; @@ -364,7 +371,7 @@ namespace gameanalytics logging::GALogger::d("sdk error content : %s", s.toString().c_str());; // if not 200 result - if (statusCode != 200 && statusCode != 204) + if (statusCode != HTTP_RESPONSE_OK && statusCode != HTTP_RESPONSE_NO_CONTENT) { logging::GALogger::d("sdk error failed. response code not 200 or 204. status code: %u", CURLE_OK); return; @@ -438,21 +445,21 @@ namespace gameanalytics } // ok - if (statusCode == 200) + if (statusCode == HTTP_RESPONSE_OK) { return Ok; } - if (statusCode == 201) + if (statusCode == HTTP_RESPONSE_CREATED) { return Created; } - if(statusCode == 204) + if(statusCode == HTTP_RESPONSE_NO_CONTENT) { return NoContent; } // 401 can return 0 status - if (statusCode == 0 || statusCode == 401) + if (statusCode == 0 || statusCode == HTTP_RESPONSE_UNAUTHORIZED) { logging::GALogger::d("%s request. 401 - Unauthorized.", requestId); return Unauthorized; @@ -464,7 +471,7 @@ namespace gameanalytics return BadRequest; } - if (statusCode == 500) + if (statusCode == HTTP_RESPONSE_INTERNAL_ERROR) { logging::GALogger::d("%s request. 500 - Internal Server Error.", requestId); return InternalServerError; From 58c5ed83cb70f27ba7f97fa15d3466391c701fa4 Mon Sep 17 00:00:00 2001 From: Dorin Date: Mon, 16 Mar 2026 13:12:17 +0200 Subject: [PATCH 06/26] print correct status code --- source/gameanalytics/GAHTTPApi.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/gameanalytics/GAHTTPApi.cpp b/source/gameanalytics/GAHTTPApi.cpp index 6f3e3bf4..d5e92ac7 100644 --- a/source/gameanalytics/GAHTTPApi.cpp +++ b/source/gameanalytics/GAHTTPApi.cpp @@ -373,7 +373,7 @@ namespace gameanalytics // if not 200 result if (statusCode != HTTP_RESPONSE_OK && statusCode != HTTP_RESPONSE_NO_CONTENT) { - logging::GALogger::d("sdk error failed. response code not 200 or 204. status code: %u", CURLE_OK); + logging::GALogger::d("sdk error failed. response code not 200 or 204. status code: %u", statusCode); return; } @@ -465,7 +465,7 @@ namespace gameanalytics return Unauthorized; } - if (statusCode == 400) + if (statusCode == HTTP_RESPONSE_BAD_REQUEST) { logging::GALogger::d("%s request. 400 - Bad Request.", requestId); return BadRequest; From 2b0847707f0f61f0e4968e50a01073a4f3082530 Mon Sep 17 00:00:00 2001 From: Dorin Date: Wed, 18 Mar 2026 14:59:24 +0200 Subject: [PATCH 07/26] http interface --- source/gameanalytics/Http/GAHttpWrapper.h | 34 +++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 source/gameanalytics/Http/GAHttpWrapper.h diff --git a/source/gameanalytics/Http/GAHttpWrapper.h b/source/gameanalytics/Http/GAHttpWrapper.h new file mode 100644 index 00000000..2b36dd43 --- /dev/null +++ b/source/gameanalytics/Http/GAHttpWrapper.h @@ -0,0 +1,34 @@ +#pragma once + +#include "GACommon.h" + +namespace gameanalytics +{ + class GAHttpWrapper + { + public: + + struct Response + { + long code = -1; + std::vector packet; + + inline std::string_view toString() const + { + return std::string_view((const char*)packet.data(), packet.size()); + } + }; + + virtual void initialize() = 0; + + virtual void cleanup() = 0; + + virtual Response sendRequest( + std::string const& url, + std::string const& auth, + std::vector const& payloadData, + bool useGzip, + void* userData) = 0; + }; + +} // namespace gameanalytics From 913e16c0d33aae5bd19fcf45c65b132eb7b70095 Mon Sep 17 00:00:00 2001 From: Dorin Date: Wed, 18 Mar 2026 14:59:43 +0200 Subject: [PATCH 08/26] default http implementation --- source/gameanalytics/Http/GAHttpCurl.cpp | 91 ++++++++++++++++++++++++ source/gameanalytics/Http/GAHttpCurl.h | 25 +++++++ 2 files changed, 116 insertions(+) create mode 100644 source/gameanalytics/Http/GAHttpCurl.cpp create mode 100644 source/gameanalytics/Http/GAHttpCurl.h diff --git a/source/gameanalytics/Http/GAHttpCurl.cpp b/source/gameanalytics/Http/GAHttpCurl.cpp new file mode 100644 index 00000000..97706e7d --- /dev/null +++ b/source/gameanalytics/Http/GAHttpCurl.cpp @@ -0,0 +1,91 @@ +#include "Http/GAHttpCurl.h" +#include "GAHTTPApi.h" +#include "GALogger.h" + +namespace gameanalytics +{ + size_t writefunc(void *ptr, size_t size, size_t nmemb, GAHttpWrapper::Response *s) + { + if(!s || !ptr) + { + return 0; + } + + const size_t new_len = s->packet.size() + size * nmemb + 1; + s->packet.reserve(new_len); + + s->packet.insert(s->packet.end(), reinterpret_cast(ptr), reinterpret_cast(ptr) + size * nmemb); + s->packet.push_back('\0'); + + return size*nmemb; + } + + void GAHttpCurl::initialize() + { + curl_global_init(CURL_GLOBAL_DEFAULT); + } + + void GAHttpCurl::cleanup() + { + curl_global_cleanup(); + } + + GAHttpWrapper::Response GAHttpCurl::sendRequest(std::string const& url, std::string const& auth, std::vector const& payloadData, bool useGzip, void* userData) + { + CURL* curl = nullptr; + CURLcode res{}; + curl = curl_easy_init(); + if (!curl) + { + return {}; + } + + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); + + GAHttpWrapper::Response s = {}; + + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + + createRequest(curl, url, auth, payloadData, useGzip); + + res = curl_easy_perform(curl); + if (res != CURLE_OK) + { + logging::GALogger::d("%s", curl_easy_strerror(res)); + return {}; + } + + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &s.code); + curl_easy_cleanup(curl); + + return s; + } + + void GAHttpCurl::createRequest(CURL *curl, std::string const& url, std::string const& auth, const std::vector& payloadData, bool gzip) + { + if(!curl) + { + return; + } + + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_POST, 1L); + struct curl_slist *header = NULL; + + if (gzip) + { + header = curl_slist_append(header, "Content-Encoding: gzip"); + } + + header = curl_slist_append(header, auth.c_str()); + + // always JSON + header = curl_slist_append(header, "Content-Type: application/json"); + + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, header); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payloadData.data()); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); + curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, payloadData.size()); + } +} \ No newline at end of file diff --git a/source/gameanalytics/Http/GAHttpCurl.h b/source/gameanalytics/Http/GAHttpCurl.h new file mode 100644 index 00000000..3137fe32 --- /dev/null +++ b/source/gameanalytics/Http/GAHttpCurl.h @@ -0,0 +1,25 @@ +#pragma once + +#include "Http/GAHttpWrapper.h" +#include + +namespace gameanalytics +{ + class GAHttpCurl: public GAHttpWrapper + { + virtual void initialize() override; + + virtual void cleanup() override; + + virtual Response sendRequest( + std::string const& url, + std::string const& auth, + std::vector const& payloadData, + bool useGzip, + void* userData) override; + + private: + + void createRequest(CURL *curl, std::string const& url, std::string const& auth, const std::vector& payloadData, bool gzip); + }; +} \ No newline at end of file From d59213085bb975af9a9f2c41d05d17e20a4b2c65 Mon Sep 17 00:00:00 2001 From: Dorin Date: Wed, 18 Mar 2026 15:00:02 +0200 Subject: [PATCH 09/26] use http impl --- source/gameanalytics/GAHTTPApi.cpp | 183 ++++++----------------------- source/gameanalytics/GAHTTPApi.h | 32 +---- 2 files changed, 45 insertions(+), 170 deletions(-) diff --git a/source/gameanalytics/GAHTTPApi.cpp b/source/gameanalytics/GAHTTPApi.cpp index d5e92ac7..ef281bd7 100644 --- a/source/gameanalytics/GAHTTPApi.cpp +++ b/source/gameanalytics/GAHTTPApi.cpp @@ -10,6 +10,8 @@ #include "GAUtilities.h" #include "GAValidator.h" +#include "Http/GAHttpCurl.h" + namespace gameanalytics { namespace http @@ -21,21 +23,11 @@ namespace gameanalytics constexpr int HTTP_RESPONSE_UNAUTHORIZED = 401; constexpr int HTTP_RESPONSE_INTERNAL_ERROR = 500; - size_t writefunc(void *ptr, size_t size, size_t nmemb, ResponseData *s) - { - const size_t new_len = s->packet.size() + size * nmemb + 1; - s->packet.reserve(new_len); - - s->packet.insert(s->packet.end(), reinterpret_cast(ptr), reinterpret_cast(ptr) + size * nmemb); - s->packet.push_back('\0'); - - return size*nmemb; - } - // Constructor - setup the basic information for HTTP - GAHTTPApi::GAHTTPApi() + GAHTTPApi::GAHTTPApi(): + impl(std::make_unique()) { - curl_global_init(CURL_GLOBAL_DEFAULT); + impl->initialize(); baseUrl = protocol + "://" + hostName + "/" + version; remoteConfigsBaseUrl = protocol + "://" + hostName + "/remote_configs/" + remoteConfigsVersion; @@ -50,7 +42,7 @@ namespace gameanalytics GAHTTPApi::~GAHTTPApi() { - curl_global_cleanup(); + impl->cleanup(); } GAHTTPApi& GAHTTPApi::getInstance() @@ -80,45 +72,22 @@ namespace gameanalytics std::vector payloadData = createPayloadData(jsonString, useGzip); - CURL* curl = nullptr; - CURLcode res; - curl = curl_easy_init(); - if (!curl) - { - return NoResponse; - } - - curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); + std::string const auth = createAuth(payloadData); + GAHttpWrapper::Response response = impl->sendRequest(url, auth, payloadData, useGzip, nullptr); - ResponseData s; - - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); - - std::vector authorization = createRequest(curl, url, payloadData, useGzip); - - res = curl_easy_perform(curl); - if (res != CURLE_OK) - { - logging::GALogger::d(curl_easy_strerror(res)); - return NoResponse; - } - - long response_code{}; - curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code); - curl_easy_cleanup(curl); + std::string_view content = response.toString(); // process the response - logging::GALogger::d("init request content: %s, json: %s", s.toString().c_str(), jsonString.c_str()); + logging::GALogger::d("init request content: %.*s, json: %s", (int)content.size(), content.data(), jsonString.c_str()); - json requestJsonDict = json::parse(s.toString()); + json requestJsonDict = json::parse(content); - EGAHTTPApiResponse requestResponseEnum = processRequestResponse(response_code, s.packet.data(), "Init"); + EGAHTTPApiResponse requestResponseEnum = processRequestResponse(response.code, (const char*)response.packet.data(), "Init"); // if not 200 result if (requestResponseEnum != Ok && requestResponseEnum != Created && requestResponseEnum != BadRequest) { - logging::GALogger::d("Failed Init Call. URL: %s, JSONString: %s, Authorization: %s", url.c_str(), jsonString.c_str(), authorization.data()); + logging::GALogger::d("Failed Init Call. URL: %s, JSONString: %s, Authorization: %s", url.c_str(), jsonString.c_str(), auth.c_str()); return requestResponseEnum; } @@ -161,6 +130,17 @@ namespace gameanalytics } } + std::string GAHTTPApi::createAuth(std::vector const& payloadData) + { + const std::string key = state::GAState::getGameSecret(); + + std::vector authorization; + utilities::GAUtilities::hmacWithKey(key.c_str(), payloadData, authorization); + std::string auth = "Authorization: " + std::string(reinterpret_cast(authorization.data()), authorization.size()); + + return auth; + } + EGAHTTPApiResponse GAHTTPApi::sendEventsInArray(json& json_out, const json& eventArray) { if (eventArray.empty()) @@ -169,10 +149,10 @@ namespace gameanalytics return JsonEncodeFailed; } - const std::string gameKey = state::GAState::getGameKey(); - try { + const std::string gameKey = state::GAState::getGameKey(); + // Generate URL const std::string url = baseUrl + '/' + gameKey + '/' + eventsUrlPath; logging::GALogger::d("Sending 'events' URL: %s", url.c_str()); @@ -186,37 +166,13 @@ namespace gameanalytics std::vector payloadData = createPayloadData(jsonString, useGzip); - CURL* curl = nullptr; - CURLcode res{}; - curl = curl_easy_init(); - if (!curl) - { - return NoResponse; - } - - curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); - - ResponseData s = {}; - - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); - - std::vector authorization = createRequest(curl, url, payloadData, useGzip); - - res = curl_easy_perform(curl); - if (res != CURLE_OK) - { - logging::GALogger::d("%s", curl_easy_strerror(res)); - return NoResponse; - } + std::string const auth = createAuth(payloadData); + GAHttpWrapper::Response response = impl->sendRequest(url, auth, payloadData, useGzip, nullptr); - long response_code{}; - curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code); - curl_easy_cleanup(curl); + std::string_view content = response.toString(); + logging::GALogger::d("body: %.*s", (int)content.size(), content.data()); - logging::GALogger::d("body: %s", s.toString().c_str()); - - EGAHTTPApiResponse requestResponseEnum = processRequestResponse(response_code, s.packet.data(), "Events"); + EGAHTTPApiResponse requestResponseEnum = processRequestResponse(response.code, (const char*)response.packet.data(), "Events"); const bool isValidResponse = requestResponseEnum == Ok || requestResponseEnum == Created || requestResponseEnum == NoContent; @@ -224,7 +180,7 @@ namespace gameanalytics // if not 200 result if (!isValidResponse && requestResponseEnum != BadRequest) { - logging::GALogger::d("Failed Events Call. URL: %s, JSONString: %s, Authorization: %s", url.c_str(), jsonString.c_str(), authorization.data()); + logging::GALogger::d("Failed Events Call. URL: %s, JSONString: %s, Authorization: %s", url.c_str(), jsonString.c_str(), auth.c_str()); return requestResponseEnum; } @@ -234,7 +190,7 @@ namespace gameanalytics } // decode JSON - json requestJsonDict = json::parse(s.toString()); + json requestJsonDict = json::parse(response.toString()); if (requestJsonDict.is_null()) { return JsonDecodeFailed; @@ -306,7 +262,6 @@ namespace gameanalytics logging::GALogger::d("sendSdkErrorEvent json: %s", payloadJSONString.c_str()); -#if !NO_ASYNC ErrorType errorType = std::make_tuple(category, area); bool useGzip = this->useGzip; @@ -339,47 +294,23 @@ namespace gameanalytics std::vector payloadData = getInstance().createPayloadData(payloadJSONString, useGzip); - CURL *curl = nullptr; - CURLcode res; - curl = curl_easy_init(); - if(!curl) - { - return; - } - - curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); - - ResponseData s = {}; - - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); - - getInstance().createRequest(curl, url.data(), payloadData, useGzip); + std::string auth = createAuth(payloadData); + GAHttpWrapper::Response response = impl->sendRequest(url, auth, payloadData, useGzip, nullptr); - res = curl_easy_perform(curl); - if(res != CURLE_OK) - { - logging::GALogger::d(curl_easy_strerror(res)); - return; - } - - long statusCode{}; - curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &statusCode); - curl_easy_cleanup(curl); + std::string_view content = response.toString(); // process the response - logging::GALogger::d("sdk error content : %s", s.toString().c_str());; + logging::GALogger::d("sdk error content : %.*s", (int)content.size(), content.data()); // if not 200 result - if (statusCode != HTTP_RESPONSE_OK && statusCode != HTTP_RESPONSE_NO_CONTENT) + if (response.code != HTTP_RESPONSE_OK && response.code != HTTP_RESPONSE_NO_CONTENT) { - logging::GALogger::d("sdk error failed. response code not 200 or 204. status code: %u", statusCode); + logging::GALogger::d("sdk error failed. response code not 200 or 204. status code: %l", response.code); return; } countMap[errorType] = countMap[errorType] + 1; }); -#endif } std::vector GAHTTPApi::createPayloadData(std::string const& payload, bool gzip) @@ -404,37 +335,6 @@ namespace gameanalytics return payloadData; } - std::vector GAHTTPApi::createRequest(CURL *curl, std::string const& url, const std::vector& payloadData, bool gzip) - { - curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); - curl_easy_setopt(curl, CURLOPT_POST, 1L); - struct curl_slist *header = NULL; - - if (gzip) - { - header = curl_slist_append(header, "Content-Encoding: gzip"); - } - - // create authorization hash - std::string const key = state::GAState::getGameSecret(); - - std::vector authorization; - utilities::GAUtilities::hmacWithKey(key.c_str(), payloadData, authorization); - std::string auth = "Authorization: " + std::string(reinterpret_cast(authorization.data()), authorization.size()); - - header = curl_slist_append(header, auth.c_str()); - - // always JSON - header = curl_slist_append(header, "Content-Type: application/json"); - - curl_easy_setopt(curl, CURLOPT_HTTPHEADER, header); - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payloadData.data()); - curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); - curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, payloadData.size()); - - return authorization; - } - EGAHTTPApiResponse GAHTTPApi::processRequestResponse(long statusCode, const char* body, const char* requestId) { // if no result - often no connection @@ -478,10 +378,5 @@ namespace gameanalytics } return UnknownResponseCode; } - - std::string ResponseData::toString() const - { - return std::string(packet.begin(), packet.end()); - } } } diff --git a/source/gameanalytics/GAHTTPApi.h b/source/gameanalytics/GAHTTPApi.h index f4f898da..5dcafdeb 100644 --- a/source/gameanalytics/GAHTTPApi.h +++ b/source/gameanalytics/GAHTTPApi.h @@ -6,7 +6,7 @@ #pragma once #include "GACommon.h" -#include +#include "Http/GAHttpWrapper.h" #include #include @@ -18,7 +18,6 @@ namespace gameanalytics { namespace http { - enum EGAHTTPApiResponse { // client @@ -28,12 +27,12 @@ namespace gameanalytics JsonEncodeFailed = 3, JsonDecodeFailed = 4, // server - InternalServerError = 5, + InternalServerError = 5, // 500 BadRequest = 6, // 400 Unauthorized = 7, // 401 UnknownResponseCode = 8, Ok = 9, // 200 - Created = 10, + Created = 10, // 201 NoContent = 11, // 204 InternalError }; @@ -103,12 +102,6 @@ namespace gameanalytics Message = 14 }; - struct ResponseData - { - std::vector packet; - std::string toString() const; - }; - typedef std::tuple ErrorType; class GAHTTPApi @@ -142,10 +135,11 @@ namespace gameanalytics GAHTTPApi(const GAHTTPApi&) = delete; GAHTTPApi& operator=(const GAHTTPApi&) = delete; std::vector createPayloadData(std::string const& payload, bool gzip); - - std::vector createRequest(CURL *curl, std::string const& url, const std::vector& payloadData, bool gzip); + std::string createAuth(std::vector const& payload); EGAHTTPApiResponse processRequestResponse(long statusCode, const char* body, const char* requestId); + std::unique_ptr impl; + std::string protocol = PROTOCOL; std::string hostName = HOST_NAME; std::string version = VERSION; @@ -162,21 +156,7 @@ namespace gameanalytics static constexpr int MaxCount = 10; std::map countMap; std::map timestampMap; - -#if USE_UWP && defined(USE_UWP_HTTP) - Windows::Web::Http::HttpClient^ httpClient; -#endif - }; - -#if USE_UWP && defined(USE_UWP_HTTP) - ref class GANetworkStatus sealed - { - internal: - static void NetworkInformationOnNetworkStatusChanged(Platform::Object^ sender); - static void CheckInternetAccess(); - static bool hasInternetAccess; }; -#endif constexpr const char* GAHTTPApi::sdkErrorCategoryString(EGASdkErrorCategory value) { From ec57ac4ff5d3e261120c6d12aa98c05b3cce6acd Mon Sep 17 00:00:00 2001 From: Dorin Date: Thu, 19 Mar 2026 11:53:12 +0200 Subject: [PATCH 10/26] changes to processRequestResponse --- source/gameanalytics/GAHTTPApi.cpp | 23 ++++++++++++----------- source/gameanalytics/GAHTTPApi.h | 2 +- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/source/gameanalytics/GAHTTPApi.cpp b/source/gameanalytics/GAHTTPApi.cpp index ef281bd7..dd4497f9 100644 --- a/source/gameanalytics/GAHTTPApi.cpp +++ b/source/gameanalytics/GAHTTPApi.cpp @@ -82,7 +82,7 @@ namespace gameanalytics json requestJsonDict = json::parse(content); - EGAHTTPApiResponse requestResponseEnum = processRequestResponse(response.code, (const char*)response.packet.data(), "Init"); + EGAHTTPApiResponse requestResponseEnum = processRequestResponse(response, "Init"); // if not 200 result if (requestResponseEnum != Ok && requestResponseEnum != Created && requestResponseEnum != BadRequest) @@ -172,7 +172,7 @@ namespace gameanalytics std::string_view content = response.toString(); logging::GALogger::d("body: %.*s", (int)content.size(), content.data()); - EGAHTTPApiResponse requestResponseEnum = processRequestResponse(response.code, (const char*)response.packet.data(), "Events"); + EGAHTTPApiResponse requestResponseEnum = processRequestResponse(response, "Events"); const bool isValidResponse = requestResponseEnum == Ok || requestResponseEnum == Created || requestResponseEnum == NoContent; @@ -335,47 +335,48 @@ namespace gameanalytics return payloadData; } - EGAHTTPApiResponse GAHTTPApi::processRequestResponse(long statusCode, const char* body, const char* requestId) + EGAHTTPApiResponse GAHTTPApi::processRequestResponse(GAHttpWrapper::Response const& response, std::string const& requestId) { // if no result - often no connection - if (utilities::GAUtilities::isStringNullOrEmpty(body)) + if (response.packet.empty() && response.code != HTTP_RESPONSE_NO_CONTENT) { - logging::GALogger::d("%s request. failed. Might be no connection. Status code: %ld", requestId, statusCode); + logging::GALogger::d("%s request. failed. Might be no connection. Status code: %ld", requestId.c_str(), response.code); return NoResponse; } // ok - if (statusCode == HTTP_RESPONSE_OK) + if (response.code == HTTP_RESPONSE_OK) { return Ok; } - if (statusCode == HTTP_RESPONSE_CREATED) + if (response.code == HTTP_RESPONSE_CREATED) { return Created; } - if(statusCode == HTTP_RESPONSE_NO_CONTENT) + if(response.code == HTTP_RESPONSE_NO_CONTENT) { return NoContent; } // 401 can return 0 status - if (statusCode == 0 || statusCode == HTTP_RESPONSE_UNAUTHORIZED) + if (response.code == 0 || response.code == HTTP_RESPONSE_UNAUTHORIZED) { logging::GALogger::d("%s request. 401 - Unauthorized.", requestId); return Unauthorized; } - if (statusCode == HTTP_RESPONSE_BAD_REQUEST) + if (response.code == HTTP_RESPONSE_BAD_REQUEST) { logging::GALogger::d("%s request. 400 - Bad Request.", requestId); return BadRequest; } - if (statusCode == HTTP_RESPONSE_INTERNAL_ERROR) + if (response.code == HTTP_RESPONSE_INTERNAL_ERROR) { logging::GALogger::d("%s request. 500 - Internal Server Error.", requestId); return InternalServerError; } + return UnknownResponseCode; } } diff --git a/source/gameanalytics/GAHTTPApi.h b/source/gameanalytics/GAHTTPApi.h index 5dcafdeb..13f1bdeb 100644 --- a/source/gameanalytics/GAHTTPApi.h +++ b/source/gameanalytics/GAHTTPApi.h @@ -136,7 +136,7 @@ namespace gameanalytics GAHTTPApi& operator=(const GAHTTPApi&) = delete; std::vector createPayloadData(std::string const& payload, bool gzip); std::string createAuth(std::vector const& payload); - EGAHTTPApiResponse processRequestResponse(long statusCode, const char* body, const char* requestId); + EGAHTTPApiResponse processRequestResponse(GAHttpWrapper::Response const& response, std::string const& requestId); std::unique_ptr impl; From ae729b321a9ed6957bff933c08187f67a6eba683 Mon Sep 17 00:00:00 2001 From: Dorin Date: Thu, 19 Mar 2026 11:58:23 +0200 Subject: [PATCH 11/26] fix debug logs --- source/gameanalytics/GAHTTPApi.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/gameanalytics/GAHTTPApi.cpp b/source/gameanalytics/GAHTTPApi.cpp index dd4497f9..56c4370f 100644 --- a/source/gameanalytics/GAHTTPApi.cpp +++ b/source/gameanalytics/GAHTTPApi.cpp @@ -361,19 +361,19 @@ namespace gameanalytics // 401 can return 0 status if (response.code == 0 || response.code == HTTP_RESPONSE_UNAUTHORIZED) { - logging::GALogger::d("%s request. 401 - Unauthorized.", requestId); + logging::GALogger::d("%s request. 401 - Unauthorized.", requestId.c_str()); return Unauthorized; } if (response.code == HTTP_RESPONSE_BAD_REQUEST) { - logging::GALogger::d("%s request. 400 - Bad Request.", requestId); + logging::GALogger::d("%s request. 400 - Bad Request.", requestId.c_str()); return BadRequest; } if (response.code == HTTP_RESPONSE_INTERNAL_ERROR) { - logging::GALogger::d("%s request. 500 - Internal Server Error.", requestId); + logging::GALogger::d("%s request. 500 - Internal Server Error.", requestId.c_str()); return InternalServerError; } From bf5af415457a908a2081084f880376a092e7103e Mon Sep 17 00:00:00 2001 From: Dorin Date: Mon, 23 Mar 2026 15:01:37 +0200 Subject: [PATCH 12/26] add virtual dtor for http api --- source/gameanalytics/Http/GAHttpWrapper.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/source/gameanalytics/Http/GAHttpWrapper.h b/source/gameanalytics/Http/GAHttpWrapper.h index 2b36dd43..bcbafa9b 100644 --- a/source/gameanalytics/Http/GAHttpWrapper.h +++ b/source/gameanalytics/Http/GAHttpWrapper.h @@ -19,6 +19,8 @@ namespace gameanalytics } }; + virtual ~GAHttpWrapper() {}; + virtual void initialize() = 0; virtual void cleanup() = 0; From c26dcb18c657d2b90c4b5d7eb321537a39a49ff7 Mon Sep 17 00:00:00 2001 From: Dorin Date: Mon, 23 Mar 2026 15:02:39 +0200 Subject: [PATCH 13/26] add enum for sdk error --- source/gameanalytics/GAHTTPApi.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/source/gameanalytics/GAHTTPApi.h b/source/gameanalytics/GAHTTPApi.h index 13f1bdeb..858cc6fb 100644 --- a/source/gameanalytics/GAHTTPApi.h +++ b/source/gameanalytics/GAHTTPApi.h @@ -20,6 +20,9 @@ namespace gameanalytics { enum EGAHTTPApiResponse { + // sdk is misconfigured + SdkError = -1, + // client NoResponse = 0, BadResponse = 1, From 763df979205898bb1c96ef447c825b041230375c Mon Sep 17 00:00:00 2001 From: Dorin Date: Mon, 23 Mar 2026 15:05:04 +0200 Subject: [PATCH 14/26] simplify code --- source/gameanalytics/Http/GAHttpCurl.cpp | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/source/gameanalytics/Http/GAHttpCurl.cpp b/source/gameanalytics/Http/GAHttpCurl.cpp index 97706e7d..91922dbd 100644 --- a/source/gameanalytics/Http/GAHttpCurl.cpp +++ b/source/gameanalytics/Http/GAHttpCurl.cpp @@ -13,9 +13,7 @@ namespace gameanalytics const size_t new_len = s->packet.size() + size * nmemb + 1; s->packet.reserve(new_len); - s->packet.insert(s->packet.end(), reinterpret_cast(ptr), reinterpret_cast(ptr) + size * nmemb); - s->packet.push_back('\0'); return size*nmemb; } @@ -32,9 +30,7 @@ namespace gameanalytics GAHttpWrapper::Response GAHttpCurl::sendRequest(std::string const& url, std::string const& auth, std::vector const& payloadData, bool useGzip, void* userData) { - CURL* curl = nullptr; - CURLcode res{}; - curl = curl_easy_init(); + CURL* curl = curl_easy_init(); if (!curl) { return {}; @@ -42,24 +38,24 @@ namespace gameanalytics curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); - GAHttpWrapper::Response s = {}; + GAHttpWrapper::Response response = {}; curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); createRequest(curl, url, auth, payloadData, useGzip); - res = curl_easy_perform(curl); + CURLcode res = curl_easy_perform(curl); if (res != CURLE_OK) { logging::GALogger::d("%s", curl_easy_strerror(res)); return {}; } - curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &s.code); + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response.code); curl_easy_cleanup(curl); - return s; + return response; } void GAHttpCurl::createRequest(CURL *curl, std::string const& url, std::string const& auth, const std::vector& payloadData, bool gzip) From 018545115c62231c129a375938a30832f582eb82 Mon Sep 17 00:00:00 2001 From: Dorin Date: Mon, 23 Mar 2026 15:07:06 +0200 Subject: [PATCH 15/26] sanity check for impl --- source/gameanalytics/GAHTTPApi.cpp | 48 ++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/source/gameanalytics/GAHTTPApi.cpp b/source/gameanalytics/GAHTTPApi.cpp index 56c4370f..6288c0fc 100644 --- a/source/gameanalytics/GAHTTPApi.cpp +++ b/source/gameanalytics/GAHTTPApi.cpp @@ -27,7 +27,10 @@ namespace gameanalytics GAHTTPApi::GAHTTPApi(): impl(std::make_unique()) { - impl->initialize(); + if(impl) + { + impl->initialize(); + } baseUrl = protocol + "://" + hostName + "/" + version; remoteConfigsBaseUrl = protocol + "://" + hostName + "/remote_configs/" + remoteConfigsVersion; @@ -42,7 +45,10 @@ namespace gameanalytics GAHTTPApi::~GAHTTPApi() { - impl->cleanup(); + if(impl) + { + impl->cleanup(); + } } GAHTTPApi& GAHTTPApi::getInstance() @@ -52,6 +58,12 @@ namespace gameanalytics EGAHTTPApiResponse GAHTTPApi::requestInitReturningDict(json& json_out, std::string const& configsHash) { + if(!impl) + { + logging::GALogger::e("Invalid http implmentation"); + return SdkError; + } + std::string gameKey = state::GAState::getGameKey(); // Generate URL @@ -75,6 +87,12 @@ namespace gameanalytics std::string const auth = createAuth(payloadData); GAHttpWrapper::Response response = impl->sendRequest(url, auth, payloadData, useGzip, nullptr); + if(response.code < 0) + { + logging::GALogger::e("Request failed: %s", url.c_str()); + return; + } + std::string_view content = response.toString(); // process the response @@ -143,6 +161,12 @@ namespace gameanalytics EGAHTTPApiResponse GAHTTPApi::sendEventsInArray(json& json_out, const json& eventArray) { + if(!impl) + { + logging::GALogger::e("Invalid http implmentation"); + return SdkError; + } + if (eventArray.empty()) { logging::GALogger::d("sendEventsInArray called with missing eventArray"); @@ -169,6 +193,12 @@ namespace gameanalytics std::string const auth = createAuth(payloadData); GAHttpWrapper::Response response = impl->sendRequest(url, auth, payloadData, useGzip, nullptr); + if(response.code < 0) + { + logging::GALogger::e("Request failed: %s", url.c_str()); + return; + } + std::string_view content = response.toString(); logging::GALogger::d("body: %.*s", (int)content.size(), content.data()); @@ -224,6 +254,12 @@ namespace gameanalytics void GAHTTPApi::sendSdkErrorEvent(EGASdkErrorCategory category, EGASdkErrorArea area, EGASdkErrorAction action, EGASdkErrorParameter parameter, std::string const& reason, std::string const& gameKey, const std::string& secretKey) { + if(!impl) + { + logging::GALogger::e("Invalid http implmentation"); + return; + } + if(!state::GAState::isEventSubmissionEnabled()) { return; @@ -250,7 +286,7 @@ namespace gameanalytics utilities::addIfNotEmpty(jsonObject, "error_parameter", sdkErrorParameterString(parameter)); utilities::addIfNotEmpty(jsonObject, "reason", reason); - json eventArray; + json eventArray = json::array(); eventArray.push_back(jsonObject); std::string payloadJSONString = eventArray.dump(); @@ -297,6 +333,12 @@ namespace gameanalytics std::string auth = createAuth(payloadData); GAHttpWrapper::Response response = impl->sendRequest(url, auth, payloadData, useGzip, nullptr); + if(response.code < 0) + { + logging::GALogger::e("Request failed: %s", url.c_str()); + return; + } + std::string_view content = response.toString(); // process the response From d095251f8e3d23991af3ab54a267cab46bcde137 Mon Sep 17 00:00:00 2001 From: Dorin Date: Wed, 1 Apr 2026 14:40:47 +0300 Subject: [PATCH 16/26] return error is response code is invalid --- source/gameanalytics/GAHTTPApi.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/gameanalytics/GAHTTPApi.cpp b/source/gameanalytics/GAHTTPApi.cpp index 6288c0fc..4acee3e8 100644 --- a/source/gameanalytics/GAHTTPApi.cpp +++ b/source/gameanalytics/GAHTTPApi.cpp @@ -90,7 +90,7 @@ namespace gameanalytics if(response.code < 0) { logging::GALogger::e("Request failed: %s", url.c_str()); - return; + return EGAHTTPApiResponse::SdkError; } std::string_view content = response.toString(); From e08608245fc898acc6908275da24ac721e43ab1a Mon Sep 17 00:00:00 2001 From: Dorin Date: Wed, 1 Apr 2026 14:49:53 +0300 Subject: [PATCH 17/26] missing return code --- source/gameanalytics/GAHTTPApi.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/gameanalytics/GAHTTPApi.cpp b/source/gameanalytics/GAHTTPApi.cpp index 4acee3e8..0c7813da 100644 --- a/source/gameanalytics/GAHTTPApi.cpp +++ b/source/gameanalytics/GAHTTPApi.cpp @@ -196,7 +196,7 @@ namespace gameanalytics if(response.code < 0) { logging::GALogger::e("Request failed: %s", url.c_str()); - return; + return EGAHTTPApiResponse::SdkError; } std::string_view content = response.toString(); From 24090ca451f03662d99b804a65642e784b3d8c31 Mon Sep 17 00:00:00 2001 From: Andrei Dabija Date: Thu, 2 Apr 2026 10:20:00 +0300 Subject: [PATCH 18/26] fix: check response status before parsing JSON in init request --- source/gameanalytics/GAHTTPApi.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/source/gameanalytics/GAHTTPApi.cpp b/source/gameanalytics/GAHTTPApi.cpp index 0c7813da..23080567 100644 --- a/source/gameanalytics/GAHTTPApi.cpp +++ b/source/gameanalytics/GAHTTPApi.cpp @@ -98,8 +98,6 @@ namespace gameanalytics // process the response logging::GALogger::d("init request content: %.*s, json: %s", (int)content.size(), content.data(), jsonString.c_str()); - json requestJsonDict = json::parse(content); - EGAHTTPApiResponse requestResponseEnum = processRequestResponse(response, "Init"); // if not 200 result @@ -110,6 +108,7 @@ namespace gameanalytics return requestResponseEnum; } + json requestJsonDict = json::parse(content); if (requestJsonDict.is_null()) { logging::GALogger::d("Failed Init Call. Json decoding failed"); From 997bb0a2dde59c370b1494ac01e637a96d3810a3 Mon Sep 17 00:00:00 2001 From: Andrei Dabija Date: Thu, 2 Apr 2026 10:22:41 +0300 Subject: [PATCH 19/26] Fix: Early-return an empty string_view when packet is empty. --- source/gameanalytics/Http/GAHttpWrapper.h | 1 + 1 file changed, 1 insertion(+) diff --git a/source/gameanalytics/Http/GAHttpWrapper.h b/source/gameanalytics/Http/GAHttpWrapper.h index bcbafa9b..ba222e31 100644 --- a/source/gameanalytics/Http/GAHttpWrapper.h +++ b/source/gameanalytics/Http/GAHttpWrapper.h @@ -15,6 +15,7 @@ namespace gameanalytics inline std::string_view toString() const { + if(packet.empty()) return {}; return std::string_view((const char*)packet.data(), packet.size()); } }; From d544623e53d7512d520a0408909a3d71b15a874a Mon Sep 17 00:00:00 2001 From: Andrei Dabija Date: Thu, 2 Apr 2026 10:24:29 +0300 Subject: [PATCH 20/26] fix: use correct format specifier %ld for status code log --- source/gameanalytics/GAHTTPApi.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/gameanalytics/GAHTTPApi.cpp b/source/gameanalytics/GAHTTPApi.cpp index 23080567..2e4aa9b2 100644 --- a/source/gameanalytics/GAHTTPApi.cpp +++ b/source/gameanalytics/GAHTTPApi.cpp @@ -346,7 +346,7 @@ namespace gameanalytics // if not 200 result if (response.code != HTTP_RESPONSE_OK && response.code != HTTP_RESPONSE_NO_CONTENT) { - logging::GALogger::d("sdk error failed. response code not 200 or 204. status code: %l", response.code); + logging::GALogger::d("sdk error failed. response code not 200 or 204. status code: %ld", response.code); return; } From b932426b2def6c6c9a8454a7482d6c583d0f749d Mon Sep 17 00:00:00 2001 From: Andrei Dabija Date: Thu, 2 Apr 2026 11:03:49 +0300 Subject: [PATCH 21/26] move GAHttpWrapper to public include and allow registering a custom HTTP implementation --- .../GameAnalytics}/GAHttpWrapper.h | 5 ++++- include/GameAnalytics/GameAnalytics.h | 6 ++++++ source/gameanalytics/GAHTTPApi.cpp | 9 ++++++++- source/gameanalytics/GAHTTPApi.h | 6 +++++- source/gameanalytics/GameAnalytics.cpp | 16 ++++++++++++++++ source/gameanalytics/Http/GAHttpCurl.h | 2 +- 6 files changed, 40 insertions(+), 4 deletions(-) rename {source/gameanalytics/Http => include/GameAnalytics}/GAHttpWrapper.h (91%) diff --git a/source/gameanalytics/Http/GAHttpWrapper.h b/include/GameAnalytics/GAHttpWrapper.h similarity index 91% rename from source/gameanalytics/Http/GAHttpWrapper.h rename to include/GameAnalytics/GAHttpWrapper.h index ba222e31..0b0b68ef 100644 --- a/source/gameanalytics/Http/GAHttpWrapper.h +++ b/include/GameAnalytics/GAHttpWrapper.h @@ -1,6 +1,9 @@ #pragma once -#include "GACommon.h" +#include +#include +#include +#include namespace gameanalytics { diff --git a/include/GameAnalytics/GameAnalytics.h b/include/GameAnalytics/GameAnalytics.h index 31bbcb49..ae652c63 100644 --- a/include/GameAnalytics/GameAnalytics.h +++ b/include/GameAnalytics/GameAnalytics.h @@ -6,9 +6,11 @@ #pragma once #include "GameAnalytics/GATypes.h" +#include "GameAnalytics/GAHttpWrapper.h" namespace gameanalytics { + class GameAnalytics { public: @@ -61,6 +63,10 @@ namespace gameanalytics static void configureExternalUserId(std::string const& extId); + // Set a custom HTTP implementation. Must be called before initialize(). + // If not called, the default cURL implementation is used. + static void configureHttpClient(std::unique_ptr httpClient); + // initialize - starting SDK (need configuration before starting) static void initialize(std::string const& gameKey, std::string const& gameSecret); diff --git a/source/gameanalytics/GAHTTPApi.cpp b/source/gameanalytics/GAHTTPApi.cpp index 2e4aa9b2..3e96e572 100644 --- a/source/gameanalytics/GAHTTPApi.cpp +++ b/source/gameanalytics/GAHTTPApi.cpp @@ -23,9 +23,11 @@ namespace gameanalytics constexpr int HTTP_RESPONSE_UNAUTHORIZED = 401; constexpr int HTTP_RESPONSE_INTERNAL_ERROR = 500; + std::unique_ptr GAHTTPApi::pendingCustomImpl = nullptr; + // Constructor - setup the basic information for HTTP GAHTTPApi::GAHTTPApi(): - impl(std::make_unique()) + impl(pendingCustomImpl ? std::move(pendingCustomImpl) : std::make_unique()) { if(impl) { @@ -56,6 +58,11 @@ namespace gameanalytics return state::GAState::getInstance()._gaHttp; } + void GAHTTPApi::setCustomHttpImpl(std::unique_ptr customImpl) + { + pendingCustomImpl = std::move(customImpl); + } + EGAHTTPApiResponse GAHTTPApi::requestInitReturningDict(json& json_out, std::string const& configsHash) { if(!impl) diff --git a/source/gameanalytics/GAHTTPApi.h b/source/gameanalytics/GAHTTPApi.h index 858cc6fb..3f4103ae 100644 --- a/source/gameanalytics/GAHTTPApi.h +++ b/source/gameanalytics/GAHTTPApi.h @@ -6,7 +6,7 @@ #pragma once #include "GACommon.h" -#include "Http/GAHttpWrapper.h" +#include "GameAnalytics/GAHttpWrapper.h" #include #include @@ -127,6 +127,8 @@ namespace gameanalytics static GAHTTPApi& getInstance(); + static void setCustomHttpImpl(std::unique_ptr customImpl); + EGAHTTPApiResponse requestInitReturningDict(json& json_out, std::string const& configsHash); EGAHTTPApiResponse sendEventsInArray(json& json_out, const json& eventArray); void sendSdkErrorEvent(EGASdkErrorCategory category, EGASdkErrorArea area, EGASdkErrorAction action, EGASdkErrorParameter parameter, std::string const& reason, std::string const& gameKey, std::string const& secretKey); @@ -159,6 +161,8 @@ namespace gameanalytics static constexpr int MaxCount = 10; std::map countMap; std::map timestampMap; + + static std::unique_ptr pendingCustomImpl; }; constexpr const char* GAHTTPApi::sdkErrorCategoryString(EGASdkErrorCategory value) diff --git a/source/gameanalytics/GameAnalytics.cpp b/source/gameanalytics/GameAnalytics.cpp index 8d524620..35bec186 100644 --- a/source/gameanalytics/GameAnalytics.cpp +++ b/source/gameanalytics/GameAnalytics.cpp @@ -350,6 +350,22 @@ namespace gameanalytics // ----------------------- INITIALIZE ---------------------- // + void GameAnalytics::configureHttpClient(std::unique_ptr httpClient) + { + if(_endThread) + { + return; + } + + if (isSdkReady(true, false)) + { + logging::GALogger::w("HTTP client must be set before SDK is initialized."); + return; + } + + http::GAHTTPApi::setCustomHttpImpl(std::move(httpClient)); + } + void GameAnalytics::initialize(std::string const& gameKey, std::string const& gameSecret) { if(_endThread) diff --git a/source/gameanalytics/Http/GAHttpCurl.h b/source/gameanalytics/Http/GAHttpCurl.h index 3137fe32..7bfa4172 100644 --- a/source/gameanalytics/Http/GAHttpCurl.h +++ b/source/gameanalytics/Http/GAHttpCurl.h @@ -1,6 +1,6 @@ #pragma once -#include "Http/GAHttpWrapper.h" +#include "GameAnalytics/GAHttpWrapper.h" #include namespace gameanalytics From 029ce4741f2d458b0c2e70ae284283df4f52d57a Mon Sep 17 00:00:00 2001 From: Andrei Dabija Date: Thu, 2 Apr 2026 11:06:44 +0300 Subject: [PATCH 22/26] add HTTP interface tests and README documentation --- README.md | 52 +++++++++++ test/GAHttpInterfaceTests.cpp | 170 ++++++++++++++++++++++++++++++++++ 2 files changed, 222 insertions(+) create mode 100644 test/GAHttpInterfaceTests.cpp diff --git a/README.md b/README.md index 747447e2..7c8ea90a 100644 --- a/README.md +++ b/README.md @@ -124,6 +124,58 @@ void myLogHandler(const char* message, GALoggerMessageType type) gameAnalytics_configureCustomLogHandler(myLogHandler); ``` +### Custom HTTP client + +By default, the SDK uses cURL for HTTP requests. If you need to use a different HTTP library (e.g. on consoles or custom platforms), you can provide your own implementation by subclassing `GAHttpWrapper`: + +``` c++ +#include "GameAnalytics/GAHttpWrapper.h" + +class MyHttpClient : public gameanalytics::GAHttpWrapper +{ +public: + void initialize() override + { + // Set up your HTTP library + } + + void cleanup() override + { + // Tear down your HTTP library + } + + Response sendRequest( + std::string const& url, + std::string const& auth, + std::vector const& payloadData, + bool useGzip, + void* userData) override + { + Response response; + + // Use your HTTP library to POST payloadData to url. + // Set the following headers: + // - auth (e.g. "Authorization: ...") + // - "Content-Type: application/json" + // - "Content-Encoding: gzip" (if useGzip is true) + // + // Fill in response.code with the HTTP status code. + // Fill in response.packet with the response body bytes. + + return response; + } +}; +``` + +Register it **before** calling `initialize()`: + +``` c++ +gameanalytics::GameAnalytics::configureHttpClient(std::make_unique()); +gameanalytics::GameAnalytics::initialize("", ""); +``` + +If `configureHttpClient` is not called, the built-in cURL implementation is used. + ### Configuration Example: diff --git a/test/GAHttpInterfaceTests.cpp b/test/GAHttpInterfaceTests.cpp new file mode 100644 index 00000000..0bee5aa2 --- /dev/null +++ b/test/GAHttpInterfaceTests.cpp @@ -0,0 +1,170 @@ +// +// GA-SDK-CPP +// Tests for the HTTP interface abstraction and custom implementation registration +// + +#include +#include + +#include "GameAnalytics/GameAnalytics.h" +#include "GameAnalytics/GAHttpWrapper.h" +#include "GAHTTPApi.h" + +namespace +{ + +// A mock HTTP implementation for testing +class MockHttpClient : public gameanalytics::GAHttpWrapper +{ +public: + void initialize() override + { + initialized = true; + } + + void cleanup() override + { + cleanedUp = true; + } + + Response sendRequest( + std::string const& url, + std::string const& auth, + std::vector const& payloadData, + bool useGzip, + void* userData) override + { + lastUrl = url; + lastAuth = auth; + lastPayload = payloadData; + lastUseGzip = useGzip; + requestCount++; + + return configuredResponse; + } + + // Test inspection + bool initialized = false; + bool cleanedUp = false; + int requestCount = 0; + std::string lastUrl; + std::string lastAuth; + std::vector lastPayload; + bool lastUseGzip = false; + + // Configurable response + Response configuredResponse = {}; +}; + +// -------- Response struct tests -------- + +TEST(GAHttpWrapperResponse, DefaultResponseHasNegativeCode) +{ + gameanalytics::GAHttpWrapper::Response response; + EXPECT_EQ(response.code, -1); + EXPECT_TRUE(response.packet.empty()); +} + +TEST(GAHttpWrapperResponse, ToStringReturnsEmptyForEmptyPacket) +{ + gameanalytics::GAHttpWrapper::Response response; + EXPECT_TRUE(response.toString().empty()); +} + +TEST(GAHttpWrapperResponse, ToStringReturnsPacketContent) +{ + gameanalytics::GAHttpWrapper::Response response; + std::string body = R"({"status":"ok"})"; + response.packet.assign(body.begin(), body.end()); + response.code = 200; + + std::string_view result = response.toString(); + EXPECT_EQ(result, body); + EXPECT_EQ(result.size(), body.size()); +} + +TEST(GAHttpWrapperResponse, ToStringHandlesBinaryData) +{ + gameanalytics::GAHttpWrapper::Response response; + response.packet = {0x00, 0x01, 0x02, 0xFF}; + response.code = 200; + + std::string_view result = response.toString(); + EXPECT_EQ(result.size(), 4u); +} + +// -------- Mock implementation tests -------- + +TEST(GAHttpInterface, MockImplementsInterface) +{ + auto mock = std::make_unique(); + EXPECT_FALSE(mock->initialized); + EXPECT_FALSE(mock->cleanedUp); + + mock->initialize(); + EXPECT_TRUE(mock->initialized); + + mock->cleanup(); + EXPECT_TRUE(mock->cleanedUp); +} + +TEST(GAHttpInterface, MockSendRequestRecordsParameters) +{ + MockHttpClient mock; + mock.configuredResponse.code = 200; + std::string responseBody = R"({"ok":true})"; + mock.configuredResponse.packet.assign(responseBody.begin(), responseBody.end()); + + std::string url = "https://api.gameanalytics.com/v2/test/events"; + std::string auth = "Authorization: abc123"; + std::vector payload = {'[', '{', '}', ']'}; + + auto response = mock.sendRequest(url, auth, payload, true, nullptr); + + EXPECT_EQ(mock.requestCount, 1); + EXPECT_EQ(mock.lastUrl, url); + EXPECT_EQ(mock.lastAuth, auth); + EXPECT_EQ(mock.lastPayload, payload); + EXPECT_TRUE(mock.lastUseGzip); + EXPECT_EQ(response.code, 200); + EXPECT_EQ(response.toString(), responseBody); +} + +TEST(GAHttpInterface, MockCanReturnErrorResponse) +{ + MockHttpClient mock; + mock.configuredResponse.code = 500; + std::string body = "Internal Server Error"; + mock.configuredResponse.packet.assign(body.begin(), body.end()); + + auto response = mock.sendRequest("http://test.com", "auth", {}, false, nullptr); + + EXPECT_EQ(response.code, 500); + EXPECT_EQ(response.toString(), body); +} + +TEST(GAHttpInterface, MockCanReturnNoContentResponse) +{ + MockHttpClient mock; + mock.configuredResponse.code = 204; + // 204 has no body + + auto response = mock.sendRequest("http://test.com", "auth", {}, false, nullptr); + + EXPECT_EQ(response.code, 204); + EXPECT_TRUE(response.packet.empty()); +} + +// -------- Registration tests -------- + +TEST(GAHttpInterface, SetCustomHttpImplAcceptsUniquePtr) +{ + // Verify the static method compiles and runs without crashing + auto mock = std::make_unique(); + gameanalytics::http::GAHTTPApi::setCustomHttpImpl(std::move(mock)); + + // Clean up: reset to nullptr so it doesn't affect other tests + gameanalytics::http::GAHTTPApi::setCustomHttpImpl(nullptr); +} + +} // namespace From b7af98589c9a45466276899c607fb4874a96b838 Mon Sep 17 00:00:00 2001 From: Andrei Dabija Date: Thu, 2 Apr 2026 11:31:49 +0300 Subject: [PATCH 23/26] fix MSVC min macro conflict in GAUtilities --- source/gameanalytics/GAUtilities.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/gameanalytics/GAUtilities.h b/source/gameanalytics/GAUtilities.h index 6ad3294f..99b02d24 100644 --- a/source/gameanalytics/GAUtilities.h +++ b/source/gameanalytics/GAUtilities.h @@ -74,7 +74,7 @@ namespace gameanalytics inline std::string trimString(std::string const& str, std::size_t size) { - return str.substr(0, std::min(size, str.size())); + return str.substr(0, (std::min)(size, str.size())); } inline json parseFields(std::string const& fields) From 17612694c8c48a39f7871251e89abdc3bd70abc1 Mon Sep 17 00:00:00 2001 From: Andrei Dabija Date: Thu, 2 Apr 2026 11:38:27 +0300 Subject: [PATCH 24/26] update github actions to prevent node.js warnings --- .github/workflows/cmake.yml | 4 ++-- .github/workflows/coverage.yml | 2 +- .github/workflows/create_release.yml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 197f2bb8..b32abd5c 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -59,7 +59,7 @@ jobs: c_compiler: gcc steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: submodules: true @@ -79,7 +79,7 @@ jobs: fi - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: '3.x' diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 3f848dc9..e9a1a9da 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: submodules: true diff --git a/.github/workflows/create_release.yml b/.github/workflows/create_release.yml index 188f7a50..b5c40b22 100644 --- a/.github/workflows/create_release.yml +++ b/.github/workflows/create_release.yml @@ -23,7 +23,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v6 with: fetch-depth: 0 # Fetch all history for tags and commits From ab2563b628ae5ec8994df000786a414ba84bb350 Mon Sep 17 00:00:00 2001 From: Andrei Dabija Date: Thu, 2 Apr 2026 11:45:26 +0300 Subject: [PATCH 25/26] rename GAHttpWrapper to GAHttpClient --- README.md | 6 +++--- .../{GAHttpWrapper.h => GAHttpClient.h} | 4 ++-- include/GameAnalytics/GameAnalytics.h | 4 ++-- source/gameanalytics/GAHTTPApi.cpp | 12 +++++------ source/gameanalytics/GAHTTPApi.h | 10 +++++----- source/gameanalytics/GameAnalytics.cpp | 2 +- source/gameanalytics/Http/GAHttpCurl.cpp | 6 +++--- source/gameanalytics/Http/GAHttpCurl.h | 4 ++-- test/GAHttpInterfaceTests.cpp | 20 +++++++++---------- 9 files changed, 34 insertions(+), 34 deletions(-) rename include/GameAnalytics/{GAHttpWrapper.h => GAHttpClient.h} (93%) diff --git a/README.md b/README.md index 7c8ea90a..26602df9 100644 --- a/README.md +++ b/README.md @@ -126,12 +126,12 @@ gameAnalytics_configureCustomLogHandler(myLogHandler); ### Custom HTTP client -By default, the SDK uses cURL for HTTP requests. If you need to use a different HTTP library (e.g. on consoles or custom platforms), you can provide your own implementation by subclassing `GAHttpWrapper`: +By default, the SDK uses cURL for HTTP requests. If you need to use a different HTTP library (e.g. on consoles or custom platforms), you can provide your own implementation by subclassing `GAHttpClient`: ``` c++ -#include "GameAnalytics/GAHttpWrapper.h" +#include "GameAnalytics/GAHttpClient.h" -class MyHttpClient : public gameanalytics::GAHttpWrapper +class MyHttpClient : public gameanalytics::GAHttpClient { public: void initialize() override diff --git a/include/GameAnalytics/GAHttpWrapper.h b/include/GameAnalytics/GAHttpClient.h similarity index 93% rename from include/GameAnalytics/GAHttpWrapper.h rename to include/GameAnalytics/GAHttpClient.h index 0b0b68ef..6ddd375c 100644 --- a/include/GameAnalytics/GAHttpWrapper.h +++ b/include/GameAnalytics/GAHttpClient.h @@ -7,7 +7,7 @@ namespace gameanalytics { - class GAHttpWrapper + class GAHttpClient { public: @@ -23,7 +23,7 @@ namespace gameanalytics } }; - virtual ~GAHttpWrapper() {}; + virtual ~GAHttpClient() {}; virtual void initialize() = 0; diff --git a/include/GameAnalytics/GameAnalytics.h b/include/GameAnalytics/GameAnalytics.h index ae652c63..03ea6fb1 100644 --- a/include/GameAnalytics/GameAnalytics.h +++ b/include/GameAnalytics/GameAnalytics.h @@ -6,7 +6,7 @@ #pragma once #include "GameAnalytics/GATypes.h" -#include "GameAnalytics/GAHttpWrapper.h" +#include "GameAnalytics/GAHttpClient.h" namespace gameanalytics { @@ -65,7 +65,7 @@ namespace gameanalytics // Set a custom HTTP implementation. Must be called before initialize(). // If not called, the default cURL implementation is used. - static void configureHttpClient(std::unique_ptr httpClient); + static void configureHttpClient(std::unique_ptr httpClient); // initialize - starting SDK (need configuration before starting) static void initialize(std::string const& gameKey, std::string const& gameSecret); diff --git a/source/gameanalytics/GAHTTPApi.cpp b/source/gameanalytics/GAHTTPApi.cpp index 3e96e572..bffb2fff 100644 --- a/source/gameanalytics/GAHTTPApi.cpp +++ b/source/gameanalytics/GAHTTPApi.cpp @@ -23,7 +23,7 @@ namespace gameanalytics constexpr int HTTP_RESPONSE_UNAUTHORIZED = 401; constexpr int HTTP_RESPONSE_INTERNAL_ERROR = 500; - std::unique_ptr GAHTTPApi::pendingCustomImpl = nullptr; + std::unique_ptr GAHTTPApi::pendingCustomImpl = nullptr; // Constructor - setup the basic information for HTTP GAHTTPApi::GAHTTPApi(): @@ -58,7 +58,7 @@ namespace gameanalytics return state::GAState::getInstance()._gaHttp; } - void GAHTTPApi::setCustomHttpImpl(std::unique_ptr customImpl) + void GAHTTPApi::setCustomHttpImpl(std::unique_ptr customImpl) { pendingCustomImpl = std::move(customImpl); } @@ -92,7 +92,7 @@ namespace gameanalytics std::vector payloadData = createPayloadData(jsonString, useGzip); std::string const auth = createAuth(payloadData); - GAHttpWrapper::Response response = impl->sendRequest(url, auth, payloadData, useGzip, nullptr); + GAHttpClient::Response response = impl->sendRequest(url, auth, payloadData, useGzip, nullptr); if(response.code < 0) { @@ -197,7 +197,7 @@ namespace gameanalytics std::vector payloadData = createPayloadData(jsonString, useGzip); std::string const auth = createAuth(payloadData); - GAHttpWrapper::Response response = impl->sendRequest(url, auth, payloadData, useGzip, nullptr); + GAHttpClient::Response response = impl->sendRequest(url, auth, payloadData, useGzip, nullptr); if(response.code < 0) { @@ -337,7 +337,7 @@ namespace gameanalytics std::vector payloadData = getInstance().createPayloadData(payloadJSONString, useGzip); std::string auth = createAuth(payloadData); - GAHttpWrapper::Response response = impl->sendRequest(url, auth, payloadData, useGzip, nullptr); + GAHttpClient::Response response = impl->sendRequest(url, auth, payloadData, useGzip, nullptr); if(response.code < 0) { @@ -383,7 +383,7 @@ namespace gameanalytics return payloadData; } - EGAHTTPApiResponse GAHTTPApi::processRequestResponse(GAHttpWrapper::Response const& response, std::string const& requestId) + EGAHTTPApiResponse GAHTTPApi::processRequestResponse(GAHttpClient::Response const& response, std::string const& requestId) { // if no result - often no connection if (response.packet.empty() && response.code != HTTP_RESPONSE_NO_CONTENT) diff --git a/source/gameanalytics/GAHTTPApi.h b/source/gameanalytics/GAHTTPApi.h index 3f4103ae..065a15dc 100644 --- a/source/gameanalytics/GAHTTPApi.h +++ b/source/gameanalytics/GAHTTPApi.h @@ -6,7 +6,7 @@ #pragma once #include "GACommon.h" -#include "GameAnalytics/GAHttpWrapper.h" +#include "GameAnalytics/GAHttpClient.h" #include #include @@ -127,7 +127,7 @@ namespace gameanalytics static GAHTTPApi& getInstance(); - static void setCustomHttpImpl(std::unique_ptr customImpl); + static void setCustomHttpImpl(std::unique_ptr customImpl); EGAHTTPApiResponse requestInitReturningDict(json& json_out, std::string const& configsHash); EGAHTTPApiResponse sendEventsInArray(json& json_out, const json& eventArray); @@ -141,9 +141,9 @@ namespace gameanalytics GAHTTPApi& operator=(const GAHTTPApi&) = delete; std::vector createPayloadData(std::string const& payload, bool gzip); std::string createAuth(std::vector const& payload); - EGAHTTPApiResponse processRequestResponse(GAHttpWrapper::Response const& response, std::string const& requestId); + EGAHTTPApiResponse processRequestResponse(GAHttpClient::Response const& response, std::string const& requestId); - std::unique_ptr impl; + std::unique_ptr impl; std::string protocol = PROTOCOL; std::string hostName = HOST_NAME; @@ -162,7 +162,7 @@ namespace gameanalytics std::map countMap; std::map timestampMap; - static std::unique_ptr pendingCustomImpl; + static std::unique_ptr pendingCustomImpl; }; constexpr const char* GAHTTPApi::sdkErrorCategoryString(EGASdkErrorCategory value) diff --git a/source/gameanalytics/GameAnalytics.cpp b/source/gameanalytics/GameAnalytics.cpp index 35bec186..f8c68404 100644 --- a/source/gameanalytics/GameAnalytics.cpp +++ b/source/gameanalytics/GameAnalytics.cpp @@ -350,7 +350,7 @@ namespace gameanalytics // ----------------------- INITIALIZE ---------------------- // - void GameAnalytics::configureHttpClient(std::unique_ptr httpClient) + void GameAnalytics::configureHttpClient(std::unique_ptr httpClient) { if(_endThread) { diff --git a/source/gameanalytics/Http/GAHttpCurl.cpp b/source/gameanalytics/Http/GAHttpCurl.cpp index 91922dbd..d70e0d62 100644 --- a/source/gameanalytics/Http/GAHttpCurl.cpp +++ b/source/gameanalytics/Http/GAHttpCurl.cpp @@ -4,7 +4,7 @@ namespace gameanalytics { - size_t writefunc(void *ptr, size_t size, size_t nmemb, GAHttpWrapper::Response *s) + size_t writefunc(void *ptr, size_t size, size_t nmemb, GAHttpClient::Response *s) { if(!s || !ptr) { @@ -28,7 +28,7 @@ namespace gameanalytics curl_global_cleanup(); } - GAHttpWrapper::Response GAHttpCurl::sendRequest(std::string const& url, std::string const& auth, std::vector const& payloadData, bool useGzip, void* userData) + GAHttpClient::Response GAHttpCurl::sendRequest(std::string const& url, std::string const& auth, std::vector const& payloadData, bool useGzip, void* userData) { CURL* curl = curl_easy_init(); if (!curl) @@ -38,7 +38,7 @@ namespace gameanalytics curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); - GAHttpWrapper::Response response = {}; + GAHttpClient::Response response = {}; curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); diff --git a/source/gameanalytics/Http/GAHttpCurl.h b/source/gameanalytics/Http/GAHttpCurl.h index 7bfa4172..0f5e80a3 100644 --- a/source/gameanalytics/Http/GAHttpCurl.h +++ b/source/gameanalytics/Http/GAHttpCurl.h @@ -1,11 +1,11 @@ #pragma once -#include "GameAnalytics/GAHttpWrapper.h" +#include "GameAnalytics/GAHttpClient.h" #include namespace gameanalytics { - class GAHttpCurl: public GAHttpWrapper + class GAHttpCurl: public GAHttpClient { virtual void initialize() override; diff --git a/test/GAHttpInterfaceTests.cpp b/test/GAHttpInterfaceTests.cpp index 0bee5aa2..bf83c195 100644 --- a/test/GAHttpInterfaceTests.cpp +++ b/test/GAHttpInterfaceTests.cpp @@ -7,14 +7,14 @@ #include #include "GameAnalytics/GameAnalytics.h" -#include "GameAnalytics/GAHttpWrapper.h" +#include "GameAnalytics/GAHttpClient.h" #include "GAHTTPApi.h" namespace { // A mock HTTP implementation for testing -class MockHttpClient : public gameanalytics::GAHttpWrapper +class MockHttpClient : public gameanalytics::GAHttpClient { public: void initialize() override @@ -58,22 +58,22 @@ class MockHttpClient : public gameanalytics::GAHttpWrapper // -------- Response struct tests -------- -TEST(GAHttpWrapperResponse, DefaultResponseHasNegativeCode) +TEST(GAHttpClientResponse, DefaultResponseHasNegativeCode) { - gameanalytics::GAHttpWrapper::Response response; + gameanalytics::GAHttpClient::Response response; EXPECT_EQ(response.code, -1); EXPECT_TRUE(response.packet.empty()); } -TEST(GAHttpWrapperResponse, ToStringReturnsEmptyForEmptyPacket) +TEST(GAHttpClientResponse, ToStringReturnsEmptyForEmptyPacket) { - gameanalytics::GAHttpWrapper::Response response; + gameanalytics::GAHttpClient::Response response; EXPECT_TRUE(response.toString().empty()); } -TEST(GAHttpWrapperResponse, ToStringReturnsPacketContent) +TEST(GAHttpClientResponse, ToStringReturnsPacketContent) { - gameanalytics::GAHttpWrapper::Response response; + gameanalytics::GAHttpClient::Response response; std::string body = R"({"status":"ok"})"; response.packet.assign(body.begin(), body.end()); response.code = 200; @@ -83,9 +83,9 @@ TEST(GAHttpWrapperResponse, ToStringReturnsPacketContent) EXPECT_EQ(result.size(), body.size()); } -TEST(GAHttpWrapperResponse, ToStringHandlesBinaryData) +TEST(GAHttpClientResponse, ToStringHandlesBinaryData) { - gameanalytics::GAHttpWrapper::Response response; + gameanalytics::GAHttpClient::Response response; response.packet = {0x00, 0x01, 0x02, 0xFF}; response.code = 200; From a3dc12879a7364cf49339b5fc8035369958b0f79 Mon Sep 17 00:00:00 2001 From: Andrei Dabija Date: Thu, 2 Apr 2026 12:03:07 +0300 Subject: [PATCH 26/26] fix NOMINMAX for windows --- source/gameanalytics/GAUtilities.h | 2 +- source/gameanalytics/Http/GAHttpCurl.h | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/source/gameanalytics/GAUtilities.h b/source/gameanalytics/GAUtilities.h index 99b02d24..6ad3294f 100644 --- a/source/gameanalytics/GAUtilities.h +++ b/source/gameanalytics/GAUtilities.h @@ -74,7 +74,7 @@ namespace gameanalytics inline std::string trimString(std::string const& str, std::size_t size) { - return str.substr(0, (std::min)(size, str.size())); + return str.substr(0, std::min(size, str.size())); } inline json parseFields(std::string const& fields) diff --git a/source/gameanalytics/Http/GAHttpCurl.h b/source/gameanalytics/Http/GAHttpCurl.h index 0f5e80a3..a894ada6 100644 --- a/source/gameanalytics/Http/GAHttpCurl.h +++ b/source/gameanalytics/Http/GAHttpCurl.h @@ -1,5 +1,19 @@ #pragma once +#ifdef _WIN32 + + #ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN + #endif + + #ifndef NOMINMAX + #define NOMINMAX + #endif + + #include + +#endif + #include "GameAnalytics/GAHttpClient.h" #include