From 4d4ff54c44105c79d4ce8386a7e7c10606a883f0 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Tue, 30 Sep 2025 13:26:34 +0300 Subject: [PATCH 1/4] feat: add manual session control --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d51c0f2..50e36d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## XX.XX.XX +- Added manual session control, which can be enabled via "Countly::enableManualSessionControl". + ## 23.2.0 - Request queue processing now is limited to 100 requests at a time - Added 'setEventsToRQThreshold' method that sets the number of events after which all events will be sent to the RQ. Default value is set to 100. From 1a212201f19688f34d2aaafea5f828ca00199275 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Tue, 30 Sep 2025 13:26:54 +0300 Subject: [PATCH 2/4] feat: add manual session control impl --- include/countly.hpp | 2 ++ include/countly/countly_configuration.hpp | 2 ++ src/countly.cpp | 14 ++++++++++++++ 3 files changed, 18 insertions(+) diff --git a/include/countly.hpp b/include/countly.hpp index 9c95583..029a35b 100644 --- a/include/countly.hpp +++ b/include/countly.hpp @@ -56,6 +56,8 @@ class Countly : public cly::CountlyDelegates { void setSha256(cly::SHA256Function fun); + void enableManualSessionControl(); + void setHTTPClient(HTTPClientFunction fun); void setMetrics(const std::string &os, const std::string &os_version, const std::string &device, const std::string &resolution, const std::string &carrier, const std::string &app_version); diff --git a/include/countly/countly_configuration.hpp b/include/countly/countly_configuration.hpp index 0b4aa4f..c98d500 100644 --- a/include/countly/countly_configuration.hpp +++ b/include/countly/countly_configuration.hpp @@ -68,6 +68,8 @@ struct CountlyConfiguration { SHA256Function sha256_function = nullptr; + bool manualSessionControl = false; + HTTPClientFunction http_client_function = nullptr; nlohmann::json metrics; diff --git a/src/countly.cpp b/src/countly.cpp index bb2188f..a669273 100644 --- a/src/countly.cpp +++ b/src/countly.cpp @@ -128,6 +128,20 @@ void Countly::setSha256(SHA256Function fun) { mutex->unlock(); } +/** + * Enable manual session handling. + */ +void Countly::enableManualSessionControl() { + if (is_sdk_initialized) { + log(LogLevel::WARNING, "[Countly][enableManualSessionControl] You can not enable manual session control after SDK initialization."); + return; + } + + mutex->lock(); + configuration->manualSessionControl = true; + mutex->unlock(); +} + void Countly::setMetrics(const std::string &os, const std::string &os_version, const std::string &device, const std::string &resolution, const std::string &carrier, const std::string &app_version) { if (is_sdk_initialized) { log(LogLevel::WARNING, "[Countly][setMetrics] You can not set metrics after SDK initialization."); From 82f10eb281a46e0be1ccedb18ab4e4c6d3d3e99d Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Tue, 30 Sep 2025 14:25:26 +0300 Subject: [PATCH 3/4] feat: introduce pack events --- include/countly.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/countly.hpp b/include/countly.hpp index 029a35b..b0bc6a1 100644 --- a/include/countly.hpp +++ b/include/countly.hpp @@ -321,6 +321,7 @@ class Countly : public cly::CountlyDelegates { std::chrono::system_clock::duration getSessionDuration(); void updateLoop(); + void packEvents(); bool began_session = false; bool is_being_disposed = false; bool is_sdk_initialized = false; From afdcd28db2f0484d99bc1c3e55919fc6b48a79aa Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Tue, 30 Sep 2025 14:26:25 +0300 Subject: [PATCH 4/4] feat: manual sessions impl --- src/countly.cpp | 94 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 63 insertions(+), 31 deletions(-) diff --git a/src/countly.cpp b/src/countly.cpp index a669273..204fa10 100644 --- a/src/countly.cpp +++ b/src/countly.cpp @@ -346,17 +346,19 @@ void Countly::_changeDeviceIdWithoutMerge(const std::string &value) { // send all event to server and end current session of old user flushEvents(); - if (began_session) { + if(!configuration->manualSessionControl){ endSession(); - mutex->lock(); - session_params["device_id"] = value; - mutex->unlock(); + } + + mutex->lock(); + session_params["device_id"] = value; + mutex->unlock(); + + // start a new session for new user + if(!configuration->manualSessionControl){ beginSession(); - } else { - mutex->lock(); - session_params["device_id"] = value; - mutex->unlock(); } + } #pragma endregion Device Id @@ -401,7 +403,7 @@ void Countly::start(const std::string &app_key, const std::string &host, int por log(LogLevel::INFO, "[Countly][start] '_WIN32' is not defined"); #endif - enable_automatic_session = start_thread; + enable_automatic_session = start_thread && !configuration->manualSessionControl; start_thread = true; if (port < 0 || port > 65535) { @@ -436,9 +438,11 @@ void Countly::start(const std::string &app_key, const std::string &host, int por if (!running) { - mutex->unlock(); - beginSession(); - mutex->lock(); + if(!configuration->manualSessionControl){ + mutex->unlock(); + beginSession(); + mutex->lock(); + } if (start_thread) { stop_thread = false; @@ -465,7 +469,7 @@ void Countly::startOnCloud(const std::string &app_key) { void Countly::stop() { _deleteThread(); - if (began_session) { + if (!configuration->manualSessionControl) { endSession(); } } @@ -607,8 +611,16 @@ bool Countly::attemptSessionUpdateEQ() { return false; } #endif - - return !updateSession(); + bool result; + if(!configuration->manualSessionControl){ + result = !updateSession(); + } else { + log(LogLevel::WARNING, "[Countly][attemptSessionUpdateEQ] SDK is in manual session control mode. Please start a session first."); + result = false; + } + + packEvents(); + return result; } void Countly::clearEQInternal() { @@ -670,6 +682,7 @@ bool Countly::beginSession() { log(LogLevel::INFO, "[Countly][beginSession]"); if (began_session) { mutex->unlock(); + log(LogLevel::DEBUG, "[Countly][beginSession] Session is already active."); return true; } @@ -725,6 +738,10 @@ bool Countly::updateSession() { mutex->lock(); if (!began_session) { mutex->unlock(); + if(configuration->manualSessionControl){ + log(LogLevel::WARNING, "[Countly][updateSession] SDK is in manual session control mode and there is no active session. Please start a session first."); + return false; + } if (!beginSession()) { // if beginSession fails, we should not try to update session return false; @@ -732,8 +749,31 @@ bool Countly::updateSession() { mutex->lock(); began_session = true; } + + mutex->unlock(); + auto duration = std::chrono::duration_cast(getSessionDuration()); + mutex->lock(); - // events array + // report session duration if it is greater than the configured session duration value + if (duration.count() >= configuration->sessionDuration) { + log(LogLevel::DEBUG, "[Countly][updateSession] sending session update."); + std::map data = {{"app_key", session_params["app_key"].get()}, {"device_id", session_params["device_id"].get()}, {"session_duration", std::to_string(duration.count())}}; + requestModule->addRequestToQueue(data); + + last_sent_session_request += duration; + } + } catch (const std::system_error &e) { + std::ostringstream log_message; + log_message << "update session, error: " << e.what(); + log(LogLevel::FATAL, log_message.str()); + } + mutex->unlock(); + return true; +} + +void Countly::packEvents() { + try { + // events array nlohmann::json events = nlohmann::json::array(); std::string event_ids; mutex->unlock(); @@ -754,20 +794,7 @@ bool Countly::updateSession() { } else { log(LogLevel::DEBUG, "[Countly][updateSession] EQ empty."); } - mutex->unlock(); - auto duration = std::chrono::duration_cast(getSessionDuration()); - mutex->lock(); - - // report session duration if it is greater than the configured session duration value - if (duration.count() >= configuration->sessionDuration) { - log(LogLevel::DEBUG, "[Countly][updateSession] sending session update."); - std::map data = {{"app_key", session_params["app_key"].get()}, {"device_id", session_params["device_id"].get()}, {"session_duration", std::to_string(duration.count())}}; - requestModule->addRequestToQueue(data); - - last_sent_session_request += duration; - } - - // report events if there are any to request queue + // report events if there are any to request queue if (!no_events) { sendEventsToRQ(events); } @@ -788,9 +815,9 @@ bool Countly::updateSession() { log(LogLevel::FATAL, log_message.str()); } mutex->unlock(); - return true; } + void Countly::sendEventsToRQ(const nlohmann::json &events) { log(LogLevel::DEBUG, "[Countly][sendEventsToRQ] Sending events to RQ."); std::map data = {{"app_key", session_params["app_key"].get()}, {"device_id", session_params["device_id"].get()}, {"events", events.dump()}}; @@ -799,6 +826,10 @@ void Countly::sendEventsToRQ(const nlohmann::json &events) { bool Countly::endSession() { log(LogLevel::INFO, "[Countly][endSession]"); + if(!began_session) { + log(LogLevel::DEBUG, "[Countly][endSession] There is no active session to end."); + return true; + } const std::chrono::system_clock::time_point now = Countly::getTimestamp(); const auto timestamp = std::chrono::duration_cast(now.time_since_epoch()); const auto duration = std::chrono::duration_cast(getSessionDuration(now)); @@ -1127,6 +1158,7 @@ void Countly::updateLoop() { if (enable_automatic_session) { updateSession(); } + packEvents(); requestModule->processQueue(mutex); } mutex->lock();