diff --git a/CHANGELOG.md b/CHANGELOG.md index 028e8da..87f54c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ -## XX.XX.XX +## XX.XX.XX +- Added manual session control, which can be enabled via "Countly::enableManualSessionControl". - Added "checkRQSize" function to return the current number of requests in the queue. ## 23.2.0 diff --git a/include/countly.hpp b/include/countly.hpp index 610940b..fa985e3 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); @@ -324,6 +326,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; 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 fabbbe2..4e55887 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."); @@ -332,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 @@ -387,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) { @@ -422,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; @@ -451,7 +469,7 @@ void Countly::startOnCloud(const std::string &app_key) { void Countly::stop() { _deleteThread(); - if (began_session) { + if (!configuration->manualSessionControl) { endSession(); } } @@ -593,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() { @@ -656,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; } @@ -711,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; @@ -718,8 +749,31 @@ bool Countly::updateSession() { mutex->lock(); began_session = true; } + + 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; + } + } 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; +} - // events array +void Countly::packEvents() { + try { + // events array nlohmann::json events = nlohmann::json::array(); std::string event_ids; mutex->unlock(); @@ -740,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); } @@ -774,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()}}; @@ -785,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)); @@ -1125,6 +1170,7 @@ void Countly::updateLoop() { if (enable_automatic_session) { updateSession(); } + packEvents(); requestModule->processQueue(mutex); } mutex->lock();