From 54c76d6e866f8a23bbcf25887b9dba6851a147ec Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Tue, 6 Jan 2026 14:21:50 +0300 Subject: [PATCH 01/46] feat: header file --- include/countly/configuration_module.hpp | 37 ++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 include/countly/configuration_module.hpp diff --git a/include/countly/configuration_module.hpp b/include/countly/configuration_module.hpp new file mode 100644 index 0000000..12181d1 --- /dev/null +++ b/include/countly/configuration_module.hpp @@ -0,0 +1,37 @@ +#ifndef CONFIGURATION_MODULE_HPP_ +#define CONFIGURATION_MODULE_HPP_ + +#include "countly/countly_configuration.hpp" +#include "countly/logger_module.hpp" +#include "countly/request_builder.hpp" +#include "countly/request_module.hpp" +#include "countly/storage_module_base.hpp" + +namespace cly { +class ConfigurationModule { + +public: + ~ConfigurationModule(); + ConfigurationModule(std::shared_ptr config, std::shared_ptr logger, std::shared_ptr requestBuilder, std::shared_ptr storageModule, std::shared_ptr requestModule, std::shared_ptr mutex); + + void fetchConfigFromServer(nlohmann::json session_params); + bool isTrackingEnabled(); + bool isNetworkingEnabled(); + bool isLoggingEnabled(); + + bool isLocationTrackingEnabled(); + bool isViewTrackingEnabled(); + bool isSessionTrackingEnabled(); + bool isCustomEventTrackingEnabled(); + bool isCrashReportingEnabled(); + + unsigned int getRequestQueueSizeLimit(); + unsigned int getEventQueueSizeLimit(); + unsigned int getSessionUpdateInterval(); + +private: + class ConfigurationModuleImpl; + std::unique_ptr impl; +}; +} // namespace cly +#endif From 7459679fa1a3f8e0c56d1d45fb1825c9f669a4c0 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Tue, 6 Jan 2026 14:22:10 +0300 Subject: [PATCH 02/46] feat: initial impl --- src/configuration_module.cpp | 195 +++++++++++++++++++++++++++++++++++ 1 file changed, 195 insertions(+) create mode 100644 src/configuration_module.cpp diff --git a/src/configuration_module.cpp b/src/configuration_module.cpp new file mode 100644 index 0000000..9eb4db6 --- /dev/null +++ b/src/configuration_module.cpp @@ -0,0 +1,195 @@ +#include "countly/configuration_module.hpp" +#include + +namespace cly { +// some keys do not have feature yet, but reserved for future use. +static constexpr const char *KEY_TIMESTAMP = "t"; // not used yet +static constexpr const char *KEY_CONFIG = "c"; // used +static constexpr const char *KEY_VERSION = "v"; // not used yet + +static constexpr const char *KEY_TRACKING = "tracking"; +static constexpr const char *KEY_NETWORKING = "networking"; + +static constexpr const char *KEY_REQ_QUEUE_SIZE = "rqs"; +static constexpr const char *KEY_EVENT_QUEUE_SIZE = "eqs"; +static constexpr const char *KEY_LOGGING = "log"; +static constexpr const char *KEY_SESSION_UPDATE_INTERVAL = "sui"; +static constexpr const char *KEY_SESSION_TRACKING = "st"; +static constexpr const char *KEY_VIEW_TRACKING = "vt"; +static constexpr const char *KEY_LOCATION_TRACKING = "lt"; +static constexpr const char *KEY_CUSTOM_EVENT_TRACKING = "cet"; +static constexpr const char *KEY_CRASH_REPORTING = "crt"; +static constexpr const char *KEY_SERVER_CONFIG_UPDATE_INTERVAL = "scui"; + +// whitelist / blacklist - not implemented yet +static constexpr const char *KEY_EVENT_BLACKLIST = "eb"; +static constexpr const char *KEY_USER_PROPERTY_BLACKLIST = "upb"; +static constexpr const char *KEY_SEGMENTATION_BLACKLIST = "sb"; +static constexpr const char *KEY_EVENT_SEGMENTATION_BLACKLIST = "esb"; +static constexpr const char *KEY_EVENT_WHITELIST = "ew"; +static constexpr const char *KEY_USER_PROPERTY_WHITELIST = "upw"; +static constexpr const char *KEY_SEGMENTATION_WHITELIST = "sw"; +static constexpr const char *KEY_EVENT_SEGMENTATION_WHITELIST = "esw"; + +// sdk configuration - not implemented yet +static constexpr const char *KEY_CONSENT_REQUIRED = "cr"; +static constexpr const char *KEY_DROP_OLD_REQUEST_TIME = "dort"; + +// sdk internal limits - not implemented yet +static constexpr const char *KEY_LIMIT_KEY_LENGTH = "lkl"; +static constexpr const char *KEY_LIMIT_VALUE_SIZE = "lvs"; +static constexpr const char *KEY_LIMIT_SEG_VALUES = "lsv"; +static constexpr const char *KEY_LIMIT_BREADCRUMB = "lbc"; +static constexpr const char *KEY_LIMIT_TRACE_LINE = "ltlpt"; +static constexpr const char *KEY_LIMIT_TRACE_LENGTH = "ltl"; +// -- This limit is introduced lately and experimental +static constexpr const char *KEY_USER_PROPERTY_CACHE_LIMIT = "upcl"; + +// backoff mechanism - not implemented yet +static constexpr const char *KEY_BACKOFF_MECHANISM = "bom"; +static constexpr const char *KEY_BOM_ACCEPTED_TIMEOUT = "bom_at"; +static constexpr const char *KEY_BOM_RQ_PERCENTAGE = "bom_rqp"; +static constexpr const char *KEY_BOM_REQUEST_AGE = "bom_ra"; +static constexpr const char *KEY_BOM_DURATION = "bom_d"; + +class ConfigurationModule::ConfigurationModuleImpl { +private: +public: + std::shared_ptr _configuration; + std::shared_ptr _logger; + std::shared_ptr _requestBuilder; + std::shared_ptr _storageModule; + std::shared_ptr _requestModule; + std::shared_ptr _mutex; + std::shared_ptr sdk_behavior_settings_mutex = std::make_shared(); + nlohmann::json sdk_behavior_settings; + ConfigurationModuleImpl(std::shared_ptr config, std::shared_ptr logger, std::shared_ptr requestBuilder, std::shared_ptr storageModule, std::shared_ptr requestModule, std::shared_ptr mutex) + : _configuration(config), _logger(logger), _requestBuilder(requestBuilder), _storageModule(storageModule), _requestModule(requestModule), _mutex(mutex) {} + + ~ConfigurationModuleImpl() { _logger.reset(); } + + void _fetchConfigFromServerHTTP(const std::map &data) { + HTTPResponse response = _requestModule->sendHTTP("/o/sdk", _requestBuilder->serializeData(data)); + if (response.success && response.data.is_object() && response.data.contains(KEY_CONFIG)) { + sdk_behavior_settings_mutex->lock(); // why lock here? we already got the new one so lets await other accessors to get the new one + sanitizeConfig(response.data[KEY_CONFIG]); + sdk_behavior_settings = response.data[KEY_CONFIG]; + _logger->log(LogLevel::INFO, "[ConfigurationModule] _fetchConfigFromServerHTTP, SDK config:\n" + sdk_behavior_settings.dump(2)); + sdk_behavior_settings_mutex->unlock(); + } else { + _logger->log(LogLevel::WARNING, cly::utils::format_string("[ConfigurationModule] _fetchConfigFromServerHTTP, failed to fetch response_success: [%s]", response.success ? "true" : "false")); + } + } + + bool getBool(const char *key, bool defaultValue) const { + sdk_behavior_settings_mutex->lock(); + if (!sdk_behavior_settings.is_object()) { + sdk_behavior_settings_mutex->unlock(); + return defaultValue; + } + + auto it = sdk_behavior_settings.find(key); + if (it == sdk_behavior_settings.end() || !it->is_boolean()) { + sdk_behavior_settings_mutex->unlock(); + return defaultValue; + } + + bool value = it->get(); + sdk_behavior_settings_mutex->unlock(); + return value; + } + + unsigned int getUInt(const char *key, unsigned int defaultValue) const { + sdk_behavior_settings_mutex->lock(); + if (!sdk_behavior_settings.is_object()) { + sdk_behavior_settings_mutex->unlock(); + return defaultValue; + } + + auto it = sdk_behavior_settings.find(key); + if (it == sdk_behavior_settings.end() || !it->is_number_unsigned()) { + sdk_behavior_settings_mutex->unlock(); + return defaultValue; + } + + unsigned int value = it->get(); + sdk_behavior_settings_mutex->unlock(); + return value; + } + + void sanitizeConfig(nlohmann::json &c) { + if (!c.is_object()) { + c.clear(); + return; + } + + for (auto it = c.begin(); it != c.end();) { + std::string key = it.key(); + auto value = it.value(); + if (key == KEY_REQ_QUEUE_SIZE || key == KEY_EVENT_QUEUE_SIZE || key == KEY_SESSION_UPDATE_INTERVAL || key == KEY_LIMIT_KEY_LENGTH || key == KEY_LIMIT_VALUE_SIZE || key == KEY_LIMIT_SEG_VALUES || key == KEY_LIMIT_BREADCRUMB || key == KEY_LIMIT_TRACE_LINE || key == KEY_LIMIT_TRACE_LENGTH || + key == KEY_USER_PROPERTY_CACHE_LIMIT || key == KEY_DROP_OLD_REQUEST_TIME || key == KEY_SERVER_CONFIG_UPDATE_INTERVAL) { + if (!value.is_number_unsigned()) { + it = c.erase(it); + continue; + } + } else if (key == KEY_TRACKING || key == KEY_NETWORKING || key == KEY_LOGGING || key == KEY_SESSION_TRACKING || key == KEY_VIEW_TRACKING || key == KEY_LOCATION_TRACKING || key == KEY_CUSTOM_EVENT_TRACKING || key == KEY_CONSENT_REQUIRED || key == KEY_CRASH_REPORTING) { + if (!value.is_boolean()) { + it = c.erase(it); + continue; + } + } else if (key == KEY_EVENT_BLACKLIST || key == KEY_USER_PROPERTY_BLACKLIST || key == KEY_SEGMENTATION_BLACKLIST || key == KEY_EVENT_SEGMENTATION_BLACKLIST || key == KEY_EVENT_WHITELIST || key == KEY_USER_PROPERTY_WHITELIST || key == KEY_SEGMENTATION_WHITELIST || + key == KEY_EVENT_SEGMENTATION_WHITELIST) { + if (!value.is_array()) { + it = c.erase(it); + continue; + } + } else { + _logger->log(LogLevel::DEBUG, "[ConfigurationModule] sanitizeConfig, removing unknown key: " + key); + it = c.erase(it); + continue; + } + ++it; + } + } +}; + +ConfigurationModule::ConfigurationModule(std::shared_ptr config, std::shared_ptr logger, std::shared_ptr requestBuilder, std::shared_ptr storageModule, std::shared_ptr requestModule, + std::shared_ptr mutex) { + impl.reset(new ConfigurationModuleImpl(config, logger, requestBuilder, storageModule, requestModule, mutex)); + impl->_logger->log(LogLevel::DEBUG, "[ConfigurationModule] Initialized"); +} + +ConfigurationModule::~ConfigurationModule() { impl.reset(); } + +void ConfigurationModule::fetchConfigFromServer(nlohmann::json session_params) { + impl->_mutex->lock(); + std::map data = {{"method", "sc"}, {"app_key", session_params["app_key"].get()}, {"device_id", session_params["device_id"].get()}}; + impl->_mutex->unlock(); + // Fetch SBS asynchronously + std::thread _thread(&ConfigurationModule::ConfigurationModuleImpl::_fetchConfigFromServerHTTP, impl.get(), data); + _thread.detach(); +} + +bool ConfigurationModule::isTrackingEnabled() { return impl->getBool(KEY_TRACKING, true); } + +bool ConfigurationModule::isNetworkingEnabled() { return impl->getBool(KEY_NETWORKING, true); } + +bool ConfigurationModule::isLoggingEnabled() { return impl->getBool(KEY_LOGGING, false); } + +bool ConfigurationModule::isLocationTrackingEnabled() { return impl->getBool(KEY_LOCATION_TRACKING, true); } + +bool ConfigurationModule::isViewTrackingEnabled() { return impl->getBool(KEY_VIEW_TRACKING, true); } + +bool ConfigurationModule::isSessionTrackingEnabled() { return impl->getBool(KEY_SESSION_TRACKING, true); } + +bool ConfigurationModule::isCustomEventTrackingEnabled() { return impl->getBool(KEY_CUSTOM_EVENT_TRACKING, true); } + +bool ConfigurationModule::isCrashReportingEnabled() { return impl->getBool(KEY_CRASH_REPORTING, true); } + +unsigned int ConfigurationModule::getRequestQueueSizeLimit() { return impl->getUInt(KEY_REQ_QUEUE_SIZE, 1000); } + +unsigned int ConfigurationModule::getEventQueueSizeLimit() { return impl->getUInt(KEY_EVENT_QUEUE_SIZE, 1000); } + +unsigned int ConfigurationModule::getSessionUpdateInterval() { return impl->getUInt(KEY_SESSION_UPDATE_INTERVAL, 60); } +// namespace cly +} // namespace cly \ No newline at end of file From 7289c7eea9559591d2d18b94335fdd375596bb3f Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Tue, 6 Jan 2026 14:22:32 +0300 Subject: [PATCH 03/46] feat: adding init module --- include/countly.hpp | 2 ++ src/countly.cpp | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/include/countly.hpp b/include/countly.hpp index 6065b9d..2d08d22 100644 --- a/include/countly.hpp +++ b/include/countly.hpp @@ -27,6 +27,7 @@ #include #include #include +#include namespace cly { class Countly : public cly::CountlyDelegates { @@ -345,6 +346,7 @@ class Countly : public cly::CountlyDelegates { std::shared_ptr requestBuilder; std::shared_ptr requestModule; std::shared_ptr storageModule; + std::shared_ptr configurationModule; std::shared_ptr mutex = std::make_shared(); bool is_queue_being_processed = false; diff --git a/src/countly.cpp b/src/countly.cpp index bf01fef..273294e 100644 --- a/src/countly.cpp +++ b/src/countly.cpp @@ -452,6 +452,7 @@ void Countly::start(const std::string &app_key, const std::string &host, int por requestBuilder.reset(new RequestBuilder(configuration, logger)); requestModule.reset(new RequestModule(configuration, logger, requestBuilder, storageModule)); + configurationModule.reset(new cly::ConfigurationModule(configuration, logger, requestBuilder, storageModule, requestModule, mutex)); crash_module.reset(new cly::CrashModule(configuration, logger, requestModule, mutex)); views_module.reset(new cly::ViewsModule(this, logger)); @@ -462,6 +463,12 @@ void Countly::start(const std::string &app_key, const std::string &host, int por is_sdk_initialized = result; // after this point SDK is initialized. + if(is_sdk_initialized){ + mutex->unlock(); + configurationModule->fetchConfigFromServer(session_params); + mutex->lock(); + } + if (!running) { if (configuration->manualSessionControl == false) { From 65b37046a05912173789efa1e2d4a1713eb09be4 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Tue, 6 Jan 2026 14:22:55 +0300 Subject: [PATCH 04/46] feat: adding to module file --- CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1d72104..44eb368 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -48,7 +48,8 @@ add_library(countly ${CMAKE_CURRENT_SOURCE_DIR}/src/request_builder.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/storage_module_db.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/storage_module_memory.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/event.cpp) + ${CMAKE_CURRENT_SOURCE_DIR}/src/event.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/configuration_module.cpp) target_include_directories(countly PUBLIC $ From c89333b53ee568e23d5329d0cc609d563e531c08 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Tue, 6 Jan 2026 14:24:04 +0300 Subject: [PATCH 05/46] feat: changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c1bf66..e59e97c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## XX.XX.XX +- ! Minor breaking change ! SDK Behavior Settings is now enabled by default. Changes made on SDK Manager > SDK Behavior Settings on your server will affect SDK behavior directly. + ## 23.2.4 - Mitigated an issue where cached events were not queued when a user property was recorded. From 7c74605715c0edf433bf631e2523154bfaf8c88f Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Wed, 7 Jan 2026 11:59:00 +0300 Subject: [PATCH 06/46] feat: intoruce config provider contract --- include/countly/configuration_provider.hpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 include/countly/configuration_provider.hpp diff --git a/include/countly/configuration_provider.hpp b/include/countly/configuration_provider.hpp new file mode 100644 index 0000000..25bc406 --- /dev/null +++ b/include/countly/configuration_provider.hpp @@ -0,0 +1,15 @@ +#ifndef CONFIGURATION_PROVIDER_HPP_ +#define CONFIGURATION_PROVIDER_HPP_ +namespace cly { + +class ConfigurationProvider { +public: + virtual ~ConfigurationProvider() = default; + + virtual bool isNetworkingEnabled() const = 0; + virtual bool isTrackingEnabled() const = 0; + virtual bool isLoggingEnabled() const = 0; + virtual unsigned int getRequestQueueSizeLimit() const = 0; +}; +} // namespace cly +#endif \ No newline at end of file From 1f796dcf8b3a28977eac40defdf59a4b39112bbe Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Wed, 7 Jan 2026 12:00:36 +0300 Subject: [PATCH 07/46] feat: impl config contract to request module --- include/countly/request_module.hpp | 3 +++ src/request_module.cpp | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/include/countly/request_module.hpp b/include/countly/request_module.hpp index 7d494a1..3c5087c 100644 --- a/include/countly/request_module.hpp +++ b/include/countly/request_module.hpp @@ -9,6 +9,7 @@ #include "countly/logger_module.hpp" #include "countly/request_builder.hpp" #include "countly/storage_module_base.hpp" +#include "countly/configuration_provider.hpp" namespace cly { class RequestModule { @@ -36,10 +37,12 @@ class RequestModule { void clearRequestQueue(); long long RQSize(); + void setConfigurationProvider(std::weak_ptr provider); // try injecting private: class RequestModuleImpl; std::unique_ptr impl; + std::weak_ptr _configProvider; }; } // namespace cly #endif diff --git a/src/request_module.cpp b/src/request_module.cpp index 1770c86..05fc010 100644 --- a/src/request_module.cpp +++ b/src/request_module.cpp @@ -99,6 +99,14 @@ static size_t countly_curl_write_callback(void *data, size_t byte_size, size_t n } void RequestModule::addRequestToQueue(const std::map &data) { + + if (std::shared_ptr config = _configProvider.lock()) { + if (!config->isTrackingEnabled()) { + impl->_logger->log(LogLevel::DEBUG, "[RequestModule] addRequestToQueue: Tracking is disabled. Not adding request to queue."); + return; + } + } + if (impl->_configuration->requestQueueThreshold <= impl->_storageModule->RQCount()) { impl->_logger->log(LogLevel::WARNING, cly::utils::format_string("[RequestModule] addRequestToQueue: Request Queue is full. Dropping the oldest request.")); impl->_storageModule->RQRemoveFront(); @@ -112,6 +120,14 @@ void RequestModule::clearRequestQueue() { impl->_storageModule->RQClearAll(); } void RequestModule::processQueue(std::shared_ptr mutex) { mutex->lock(); + + if (std::shared_ptr config = _configProvider.lock()) { + if (!config->isNetworkingEnabled()) { + impl->_logger->log(LogLevel::DEBUG, "[RequestModule] processQueue: Networking is disabled. Not processing request queue."); + return; + } + } + // making sure that no other thread is processing the queue if (impl->is_queue_being_processed) { mutex->unlock(); @@ -342,4 +358,7 @@ HTTPResponse RequestModule::sendHTTP(std::string path, std::string data) { #endif } long long RequestModule::RQSize() { return impl->_storageModule->RQCount(); } + +void RequestModule::setConfigurationProvider(std::weak_ptr provider) { _configProvider = std::move(provider); } + } // namespace cly From a01e9ebb786930bb6f4632cee149e113a2eea68c Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Wed, 7 Jan 2026 12:01:06 +0300 Subject: [PATCH 08/46] feat: imp contract to modules --- include/countly.hpp | 1 + src/countly.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/include/countly.hpp b/include/countly.hpp index 2d08d22..df782cb 100644 --- a/include/countly.hpp +++ b/include/countly.hpp @@ -28,6 +28,7 @@ #include #include #include +#include namespace cly { class Countly : public cly::CountlyDelegates { diff --git a/src/countly.cpp b/src/countly.cpp index 273294e..c142e7d 100644 --- a/src/countly.cpp +++ b/src/countly.cpp @@ -453,6 +453,7 @@ void Countly::start(const std::string &app_key, const std::string &host, int por requestBuilder.reset(new RequestBuilder(configuration, logger)); requestModule.reset(new RequestModule(configuration, logger, requestBuilder, storageModule)); configurationModule.reset(new cly::ConfigurationModule(configuration, logger, requestBuilder, storageModule, requestModule, mutex)); + requestModule->setConfigurationProvider(configurationModule); crash_module.reset(new cly::CrashModule(configuration, logger, requestModule, mutex)); views_module.reset(new cly::ViewsModule(this, logger)); From 71a6bd3ea71ccabf2043c4a04c2aaf5bceeba494 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Wed, 7 Jan 2026 12:02:32 +0300 Subject: [PATCH 09/46] feat: impl contract --- include/countly/configuration_module.hpp | 13 +++++++------ src/configuration_module.cpp | 12 ++++++------ 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/include/countly/configuration_module.hpp b/include/countly/configuration_module.hpp index 12181d1..c4ea33c 100644 --- a/include/countly/configuration_module.hpp +++ b/include/countly/configuration_module.hpp @@ -4,20 +4,21 @@ #include "countly/countly_configuration.hpp" #include "countly/logger_module.hpp" #include "countly/request_builder.hpp" -#include "countly/request_module.hpp" #include "countly/storage_module_base.hpp" +#include "countly/configuration_provider.hpp" +#include "countly/request_module.hpp" namespace cly { -class ConfigurationModule { +class ConfigurationModule : public ConfigurationProvider{ public: ~ConfigurationModule(); ConfigurationModule(std::shared_ptr config, std::shared_ptr logger, std::shared_ptr requestBuilder, std::shared_ptr storageModule, std::shared_ptr requestModule, std::shared_ptr mutex); void fetchConfigFromServer(nlohmann::json session_params); - bool isTrackingEnabled(); - bool isNetworkingEnabled(); - bool isLoggingEnabled(); + bool isTrackingEnabled() const override; + bool isNetworkingEnabled() const override; + bool isLoggingEnabled() const override; bool isLocationTrackingEnabled(); bool isViewTrackingEnabled(); @@ -25,7 +26,7 @@ class ConfigurationModule { bool isCustomEventTrackingEnabled(); bool isCrashReportingEnabled(); - unsigned int getRequestQueueSizeLimit(); + unsigned int getRequestQueueSizeLimit() const override; unsigned int getEventQueueSizeLimit(); unsigned int getSessionUpdateInterval(); diff --git a/src/configuration_module.cpp b/src/configuration_module.cpp index 9eb4db6..d6ef0f4 100644 --- a/src/configuration_module.cpp +++ b/src/configuration_module.cpp @@ -170,11 +170,11 @@ void ConfigurationModule::fetchConfigFromServer(nlohmann::json session_params) { _thread.detach(); } -bool ConfigurationModule::isTrackingEnabled() { return impl->getBool(KEY_TRACKING, true); } +bool ConfigurationModule::isTrackingEnabled() const { return impl->getBool(KEY_TRACKING, true); } -bool ConfigurationModule::isNetworkingEnabled() { return impl->getBool(KEY_NETWORKING, true); } +bool ConfigurationModule::isNetworkingEnabled() const { return impl->getBool(KEY_NETWORKING, true); } -bool ConfigurationModule::isLoggingEnabled() { return impl->getBool(KEY_LOGGING, false); } +bool ConfigurationModule::isLoggingEnabled() const { return impl->getBool(KEY_LOGGING, false); } bool ConfigurationModule::isLocationTrackingEnabled() { return impl->getBool(KEY_LOCATION_TRACKING, true); } @@ -186,10 +186,10 @@ bool ConfigurationModule::isCustomEventTrackingEnabled() { return impl->getBool( bool ConfigurationModule::isCrashReportingEnabled() { return impl->getBool(KEY_CRASH_REPORTING, true); } -unsigned int ConfigurationModule::getRequestQueueSizeLimit() { return impl->getUInt(KEY_REQ_QUEUE_SIZE, 1000); } +unsigned int ConfigurationModule::getRequestQueueSizeLimit() const { return impl->getUInt(KEY_REQ_QUEUE_SIZE, impl->_configuration->requestQueueThreshold); } -unsigned int ConfigurationModule::getEventQueueSizeLimit() { return impl->getUInt(KEY_EVENT_QUEUE_SIZE, 1000); } +unsigned int ConfigurationModule::getEventQueueSizeLimit() { return impl->getUInt(KEY_EVENT_QUEUE_SIZE, impl->_configuration->eventQueueThreshold); } -unsigned int ConfigurationModule::getSessionUpdateInterval() { return impl->getUInt(KEY_SESSION_UPDATE_INTERVAL, 60); } +unsigned int ConfigurationModule::getSessionUpdateInterval() { return impl->getUInt(KEY_SESSION_UPDATE_INTERVAL, impl->_configuration->sessionDuration); } // namespace cly } // namespace cly \ No newline at end of file From 61ac50d085fe29a4d6dce2f05f80f694a2360c1c Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Wed, 7 Jan 2026 12:03:48 +0300 Subject: [PATCH 10/46] fix: move stting provider to after module inits --- src/countly.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/countly.cpp b/src/countly.cpp index c142e7d..7c0ebe6 100644 --- a/src/countly.cpp +++ b/src/countly.cpp @@ -453,10 +453,11 @@ void Countly::start(const std::string &app_key, const std::string &host, int por requestBuilder.reset(new RequestBuilder(configuration, logger)); requestModule.reset(new RequestModule(configuration, logger, requestBuilder, storageModule)); configurationModule.reset(new cly::ConfigurationModule(configuration, logger, requestBuilder, storageModule, requestModule, mutex)); - requestModule->setConfigurationProvider(configurationModule); crash_module.reset(new cly::CrashModule(configuration, logger, requestModule, mutex)); views_module.reset(new cly::ViewsModule(this, logger)); + requestModule->setConfigurationProvider(configurationModule); + bool result = true; #ifdef COUNTLY_USE_SQLITE result = createEventTableSchema(); From dda3be2541c65fbd36b615d8aae2ab70b3133ce2 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Wed, 7 Jan 2026 13:43:46 +0300 Subject: [PATCH 11/46] feat: update gitignore --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index 93c1c62..51298a8 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,7 @@ compile_commands.json CTestTestfile.cmake _deps cmake-build-debug/ + +.DS_Store +test_results_*.log +*.db From 5766a523c27cf6e819f87ce96bc7a4dba4be3900 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Wed, 7 Jan 2026 13:45:40 +0300 Subject: [PATCH 12/46] feat: sbs RQ limit --- src/request_module.cpp | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/request_module.cpp b/src/request_module.cpp index 05fc010..0987be4 100644 --- a/src/request_module.cpp +++ b/src/request_module.cpp @@ -12,7 +12,6 @@ #ifndef COUNTLY_USE_CUSTOM_HTTP #ifdef _WIN32 #include "Windows.h" -#include "WinHTTP.h" #undef ERROR #pragma comment(lib, "winhttp.lib") #else @@ -105,15 +104,15 @@ void RequestModule::addRequestToQueue(const std::map & impl->_logger->log(LogLevel::DEBUG, "[RequestModule] addRequestToQueue: Tracking is disabled. Not adding request to queue."); return; } - } - if (impl->_configuration->requestQueueThreshold <= impl->_storageModule->RQCount()) { - impl->_logger->log(LogLevel::WARNING, cly::utils::format_string("[RequestModule] addRequestToQueue: Request Queue is full. Dropping the oldest request.")); - impl->_storageModule->RQRemoveFront(); - } + if (config->getRequestQueueSizeLimit() <= impl->_storageModule->RQCount()) { + impl->_logger->log(LogLevel::WARNING, cly::utils::format_string("[RequestModule] addRequestToQueue: Request Queue is full. Dropping the oldest request.")); + impl->_storageModule->RQRemoveFront(); + } - const std::string request = impl->_requestBuilder->buildRequest(data); - impl->_storageModule->RQInsertAtEnd(request); + const std::string request = impl->_requestBuilder->buildRequest(data); + impl->_storageModule->RQInsertAtEnd(request); + } } void RequestModule::clearRequestQueue() { impl->_storageModule->RQClearAll(); } From ecb436b2eaff5c658b27d32da80f4651f54fc72f Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Wed, 7 Jan 2026 13:46:09 +0300 Subject: [PATCH 13/46] feat: use atomic for accessing sbs values --- src/configuration_module.cpp | 61 ++++++++++++++++++++++-------------- 1 file changed, 38 insertions(+), 23 deletions(-) diff --git a/src/configuration_module.cpp b/src/configuration_module.cpp index d6ef0f4..4eb6313 100644 --- a/src/configuration_module.cpp +++ b/src/configuration_module.cpp @@ -12,7 +12,6 @@ static constexpr const char *KEY_NETWORKING = "networking"; static constexpr const char *KEY_REQ_QUEUE_SIZE = "rqs"; static constexpr const char *KEY_EVENT_QUEUE_SIZE = "eqs"; -static constexpr const char *KEY_LOGGING = "log"; static constexpr const char *KEY_SESSION_UPDATE_INTERVAL = "sui"; static constexpr const char *KEY_SESSION_TRACKING = "st"; static constexpr const char *KEY_VIEW_TRACKING = "vt"; @@ -20,6 +19,7 @@ static constexpr const char *KEY_LOCATION_TRACKING = "lt"; static constexpr const char *KEY_CUSTOM_EVENT_TRACKING = "cet"; static constexpr const char *KEY_CRASH_REPORTING = "crt"; static constexpr const char *KEY_SERVER_CONFIG_UPDATE_INTERVAL = "scui"; +static constexpr const char *KEY_LOGGING = "log"; // not used yet // whitelist / blacklist - not implemented yet static constexpr const char *KEY_EVENT_BLACKLIST = "eb"; @@ -61,8 +61,19 @@ class ConfigurationModule::ConfigurationModuleImpl { std::shared_ptr _storageModule; std::shared_ptr _requestModule; std::shared_ptr _mutex; - std::shared_ptr sdk_behavior_settings_mutex = std::make_shared(); nlohmann::json sdk_behavior_settings; + + // current settings cached for quick access + std::atomic networkingEnabled{true}; + std::atomic trackingEnabled{true}; + std::atomic sessionTrackingEnabled{true}; + std::atomic viewTrackingEnabled{true}; + std::atomic locationTrackingEnabled{true}; + std::atomic customEventTrackingEnabled{true}; + std::atomic crashReportingEnabled{true}; + std::atomic eventQueueThreshold{0}; + std::atomic requestQueueSizeLimit{0}; + std::atomic sessionUpdateInterval{0}; ConfigurationModuleImpl(std::shared_ptr config, std::shared_ptr logger, std::shared_ptr requestBuilder, std::shared_ptr storageModule, std::shared_ptr requestModule, std::shared_ptr mutex) : _configuration(config), _logger(logger), _requestBuilder(requestBuilder), _storageModule(storageModule), _requestModule(requestModule), _mutex(mutex) {} @@ -71,52 +82,56 @@ class ConfigurationModule::ConfigurationModuleImpl { void _fetchConfigFromServerHTTP(const std::map &data) { HTTPResponse response = _requestModule->sendHTTP("/o/sdk", _requestBuilder->serializeData(data)); if (response.success && response.data.is_object() && response.data.contains(KEY_CONFIG)) { - sdk_behavior_settings_mutex->lock(); // why lock here? we already got the new one so lets await other accessors to get the new one sanitizeConfig(response.data[KEY_CONFIG]); sdk_behavior_settings = response.data[KEY_CONFIG]; _logger->log(LogLevel::INFO, "[ConfigurationModule] _fetchConfigFromServerHTTP, SDK config:\n" + sdk_behavior_settings.dump(2)); - sdk_behavior_settings_mutex->unlock(); + populateConfigValues(); } else { _logger->log(LogLevel::WARNING, cly::utils::format_string("[ConfigurationModule] _fetchConfigFromServerHTTP, failed to fetch response_success: [%s]", response.success ? "true" : "false")); } } bool getBool(const char *key, bool defaultValue) const { - sdk_behavior_settings_mutex->lock(); if (!sdk_behavior_settings.is_object()) { - sdk_behavior_settings_mutex->unlock(); return defaultValue; } auto it = sdk_behavior_settings.find(key); if (it == sdk_behavior_settings.end() || !it->is_boolean()) { - sdk_behavior_settings_mutex->unlock(); return defaultValue; } bool value = it->get(); - sdk_behavior_settings_mutex->unlock(); return value; } unsigned int getUInt(const char *key, unsigned int defaultValue) const { - sdk_behavior_settings_mutex->lock(); if (!sdk_behavior_settings.is_object()) { - sdk_behavior_settings_mutex->unlock(); return defaultValue; } auto it = sdk_behavior_settings.find(key); if (it == sdk_behavior_settings.end() || !it->is_number_unsigned()) { - sdk_behavior_settings_mutex->unlock(); return defaultValue; } unsigned int value = it->get(); - sdk_behavior_settings_mutex->unlock(); return value; } + void populateConfigValues() { + trackingEnabled.store(getBool(KEY_TRACKING, true), std::memory_order_release); + networkingEnabled.store(getBool(KEY_NETWORKING, true), std::memory_order_release); + sessionTrackingEnabled.store(getBool(KEY_SESSION_TRACKING, true), std::memory_order_release); + viewTrackingEnabled.store(getBool(KEY_VIEW_TRACKING, true), std::memory_order_release); + locationTrackingEnabled.store(getBool(KEY_LOCATION_TRACKING, true), std::memory_order_release); + customEventTrackingEnabled.store(getBool(KEY_CUSTOM_EVENT_TRACKING, true), std::memory_order_release); + crashReportingEnabled.store(getBool(KEY_CRASH_REPORTING, true), std::memory_order_release); + eventQueueThreshold.store(getUInt(KEY_EVENT_QUEUE_SIZE, _configuration->eventQueueThreshold), std::memory_order_release); + requestQueueSizeLimit.store(getUInt(KEY_REQ_QUEUE_SIZE, _configuration->requestQueueThreshold), std::memory_order_release); + sessionUpdateInterval.store(getUInt(KEY_SESSION_UPDATE_INTERVAL, _configuration->sessionDuration), std::memory_order_release); + } + void sanitizeConfig(nlohmann::json &c) { if (!c.is_object()) { c.clear(); @@ -170,26 +185,26 @@ void ConfigurationModule::fetchConfigFromServer(nlohmann::json session_params) { _thread.detach(); } -bool ConfigurationModule::isTrackingEnabled() const { return impl->getBool(KEY_TRACKING, true); } +bool ConfigurationModule::isTrackingEnabled() const { return impl->trackingEnabled.load(std::memory_order_acquire); } -bool ConfigurationModule::isNetworkingEnabled() const { return impl->getBool(KEY_NETWORKING, true); } +bool ConfigurationModule::isNetworkingEnabled() const { return impl->networkingEnabled.load(std::memory_order_acquire); } -bool ConfigurationModule::isLoggingEnabled() const { return impl->getBool(KEY_LOGGING, false); } +bool ConfigurationModule::isLoggingEnabled() const { return false; } // false for now -bool ConfigurationModule::isLocationTrackingEnabled() { return impl->getBool(KEY_LOCATION_TRACKING, true); } +bool ConfigurationModule::isLocationTrackingEnabled() { return impl->locationTrackingEnabled.load(std::memory_order_acquire); } -bool ConfigurationModule::isViewTrackingEnabled() { return impl->getBool(KEY_VIEW_TRACKING, true); } +bool ConfigurationModule::isViewTrackingEnabled() { return impl->viewTrackingEnabled.load(std::memory_order_acquire); } -bool ConfigurationModule::isSessionTrackingEnabled() { return impl->getBool(KEY_SESSION_TRACKING, true); } +bool ConfigurationModule::isSessionTrackingEnabled() { return impl->sessionTrackingEnabled.load(std::memory_order_acquire); } -bool ConfigurationModule::isCustomEventTrackingEnabled() { return impl->getBool(KEY_CUSTOM_EVENT_TRACKING, true); } +bool ConfigurationModule::isCustomEventTrackingEnabled() { return impl->customEventTrackingEnabled.load(std::memory_order_acquire); } -bool ConfigurationModule::isCrashReportingEnabled() { return impl->getBool(KEY_CRASH_REPORTING, true); } +bool ConfigurationModule::isCrashReportingEnabled() { return impl->crashReportingEnabled.load(std::memory_order_acquire); } -unsigned int ConfigurationModule::getRequestQueueSizeLimit() const { return impl->getUInt(KEY_REQ_QUEUE_SIZE, impl->_configuration->requestQueueThreshold); } +unsigned int ConfigurationModule::getRequestQueueSizeLimit() const { return impl->requestQueueSizeLimit.load(std::memory_order_acquire); } -unsigned int ConfigurationModule::getEventQueueSizeLimit() { return impl->getUInt(KEY_EVENT_QUEUE_SIZE, impl->_configuration->eventQueueThreshold); } +unsigned int ConfigurationModule::getEventQueueSizeLimit() { return impl->eventQueueThreshold.load(std::memory_order_acquire); } -unsigned int ConfigurationModule::getSessionUpdateInterval() { return impl->getUInt(KEY_SESSION_UPDATE_INTERVAL, impl->_configuration->sessionDuration); } +unsigned int ConfigurationModule::getSessionUpdateInterval() { return impl->sessionUpdateInterval.load(std::memory_order_acquire); } // namespace cly } // namespace cly \ No newline at end of file From 533c25fac3eddeb922ac7d14e2d25c5efa9aec09 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Wed, 7 Jan 2026 13:46:28 +0300 Subject: [PATCH 14/46] feat: use EQ size sbs --- src/countly.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/countly.cpp b/src/countly.cpp index 7c0ebe6..879211a 100644 --- a/src/countly.cpp +++ b/src/countly.cpp @@ -545,7 +545,7 @@ void Countly::checkAndSendEventToRQ() { int queueSize = checkEQSize(); mutex->lock(); #ifdef COUNTLY_USE_SQLITE - if (queueSize >= configuration->eventQueueThreshold) { + if (queueSize >= configurationModule->getEventQueueSizeLimit()) { log(LogLevel::DEBUG, "Event queue threshold is reached"); std::string event_ids; @@ -559,7 +559,7 @@ void Countly::checkAndSendEventToRQ() { removeEventWithId(event_ids); } #else - if (queueSize >= configuration->eventQueueThreshold) { + if (queueSize >= configurationModule->getEventQueueSizeLimit()) { log(LogLevel::WARNING, "Event queue is full, dropping the oldest event to insert a new one"); for (const auto &event_json : event_queue) { events.push_back(nlohmann::json::parse(event_json)); From cc14a3b6e7cc5fb68c4495f6ec43607735507bd2 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Wed, 7 Jan 2026 13:48:16 +0300 Subject: [PATCH 15/46] feat: remove unsued sbs --- include/countly/configuration_module.hpp | 1 - include/countly/configuration_provider.hpp | 1 - src/configuration_module.cpp | 4 +--- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/include/countly/configuration_module.hpp b/include/countly/configuration_module.hpp index c4ea33c..a17712d 100644 --- a/include/countly/configuration_module.hpp +++ b/include/countly/configuration_module.hpp @@ -18,7 +18,6 @@ class ConfigurationModule : public ConfigurationProvider{ void fetchConfigFromServer(nlohmann::json session_params); bool isTrackingEnabled() const override; bool isNetworkingEnabled() const override; - bool isLoggingEnabled() const override; bool isLocationTrackingEnabled(); bool isViewTrackingEnabled(); diff --git a/include/countly/configuration_provider.hpp b/include/countly/configuration_provider.hpp index 25bc406..5e8145b 100644 --- a/include/countly/configuration_provider.hpp +++ b/include/countly/configuration_provider.hpp @@ -8,7 +8,6 @@ class ConfigurationProvider { virtual bool isNetworkingEnabled() const = 0; virtual bool isTrackingEnabled() const = 0; - virtual bool isLoggingEnabled() const = 0; virtual unsigned int getRequestQueueSizeLimit() const = 0; }; } // namespace cly diff --git a/src/configuration_module.cpp b/src/configuration_module.cpp index 4eb6313..da92671 100644 --- a/src/configuration_module.cpp +++ b/src/configuration_module.cpp @@ -19,7 +19,7 @@ static constexpr const char *KEY_LOCATION_TRACKING = "lt"; static constexpr const char *KEY_CUSTOM_EVENT_TRACKING = "cet"; static constexpr const char *KEY_CRASH_REPORTING = "crt"; static constexpr const char *KEY_SERVER_CONFIG_UPDATE_INTERVAL = "scui"; -static constexpr const char *KEY_LOGGING = "log"; // not used yet +static constexpr const char *KEY_LOGGING = "log"; // not used and implemented yet // whitelist / blacklist - not implemented yet static constexpr const char *KEY_EVENT_BLACKLIST = "eb"; @@ -189,8 +189,6 @@ bool ConfigurationModule::isTrackingEnabled() const { return impl->trackingEnabl bool ConfigurationModule::isNetworkingEnabled() const { return impl->networkingEnabled.load(std::memory_order_acquire); } -bool ConfigurationModule::isLoggingEnabled() const { return false; } // false for now - bool ConfigurationModule::isLocationTrackingEnabled() { return impl->locationTrackingEnabled.load(std::memory_order_acquire); } bool ConfigurationModule::isViewTrackingEnabled() { return impl->viewTrackingEnabled.load(std::memory_order_acquire); } From 8fc0e6e9ac25ac891b7fe43c920115bd388ff6a2 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Wed, 7 Jan 2026 14:04:37 +0300 Subject: [PATCH 16/46] feat: integrate custom event tracking --- src/countly.cpp | 7 +++++++ src/request_module.cpp | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/countly.cpp b/src/countly.cpp index 879211a..941336a 100644 --- a/src/countly.cpp +++ b/src/countly.cpp @@ -530,6 +530,13 @@ void Countly::setUpdateInterval(size_t milliseconds) { } void Countly::addEvent(const cly::Event &event) { + if (configurationModule->isCustomEventTrackingEnabled() == false) { + std::string eventStr = event.serialize(); + if(eventStr.find("[CLY]_") == std::string::npos){ + log(LogLevel::DEBUG, "[Countly] addEvent, custom event tracking is disabled in server configuration, can not add event with key: " + eventStr); + return; + } + } mutex->lock(); #ifndef COUNTLY_USE_SQLITE event_queue.push_back(event.serialize()); diff --git a/src/request_module.cpp b/src/request_module.cpp index 0987be4..5032265 100644 --- a/src/request_module.cpp +++ b/src/request_module.cpp @@ -100,7 +100,7 @@ static size_t countly_curl_write_callback(void *data, size_t byte_size, size_t n void RequestModule::addRequestToQueue(const std::map &data) { if (std::shared_ptr config = _configProvider.lock()) { - if (!config->isTrackingEnabled()) { + if (config->isTrackingEnabled() == false) { impl->_logger->log(LogLevel::DEBUG, "[RequestModule] addRequestToQueue: Tracking is disabled. Not adding request to queue."); return; } @@ -121,7 +121,7 @@ void RequestModule::processQueue(std::shared_ptr mutex) { mutex->lock(); if (std::shared_ptr config = _configProvider.lock()) { - if (!config->isNetworkingEnabled()) { + if (config->isNetworkingEnabled() == false) { impl->_logger->log(LogLevel::DEBUG, "[RequestModule] processQueue: Networking is disabled. Not processing request queue."); return; } From e00e8fdc35a72e53d36fa6c378b3d6324f63baa1 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Thu, 8 Jan 2026 10:05:10 +0300 Subject: [PATCH 17/46] feat: session update interval --- src/countly.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/countly.cpp b/src/countly.cpp index 941336a..8cf57c4 100644 --- a/src/countly.cpp +++ b/src/countly.cpp @@ -814,7 +814,7 @@ bool Countly::updateSession() { mutex->lock(); // report session duration if it is greater than the configured session duration value - if (duration.count() >= configuration->sessionDuration) { + if (duration.count() >= configurationModule->getSessionUpdateInterval()) { 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); From 2460bfcbc64509b9c917122b8e5f7358ee2a008e Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Thu, 8 Jan 2026 10:30:10 +0300 Subject: [PATCH 18/46] feat: config provider to views module --- include/countly/views_module.hpp | 4 ++++ src/countly.cpp | 2 ++ src/views_module.cpp | 4 ++++ 3 files changed, 10 insertions(+) diff --git a/include/countly/views_module.hpp b/include/countly/views_module.hpp index a7a3d09..1f48006 100644 --- a/include/countly/views_module.hpp +++ b/include/countly/views_module.hpp @@ -4,6 +4,7 @@ #include #include +#include "countly/configuration_provider.hpp" #include "countly/constants.hpp" #include "countly/logger_module.hpp" @@ -36,10 +37,13 @@ class ViewsModule { */ std::string openView(const std::string &name, const std::map &segmentation = {}); + private: + friend class Countly; void _recordView(std::string eventID); class ViewModuleImpl; std::unique_ptr impl; + void setConfigurationProvider(std::weak_ptr provider); // try injecting }; } // namespace cly #endif diff --git a/src/countly.cpp b/src/countly.cpp index 8cf57c4..3139f43 100644 --- a/src/countly.cpp +++ b/src/countly.cpp @@ -457,6 +457,8 @@ void Countly::start(const std::string &app_key, const std::string &host, int por views_module.reset(new cly::ViewsModule(this, logger)); requestModule->setConfigurationProvider(configurationModule); + views_module->setConfigurationProvider(configurationModule); + //crash_module->setConfigurationProvider(configurationModule); bool result = true; #ifdef COUNTLY_USE_SQLITE diff --git a/src/views_module.cpp b/src/views_module.cpp index e1a0f4d..eed92bc 100644 --- a/src/views_module.cpp +++ b/src/views_module.cpp @@ -68,6 +68,7 @@ class ViewsModule::ViewModuleImpl { public: std::shared_ptr _logger; + std::weak_ptr _configProvider; ViewModuleImpl(cly::CountlyDelegates *cly, std::shared_ptr logger) : _cly(cly), _logger(logger) {} ~ViewModuleImpl() { _logger.reset(); } @@ -150,4 +151,7 @@ void ViewsModule::closeViewWithID(const std::string &viewId) { impl->_closeViewWithID(viewId); } + +void ViewsModule::setConfigurationProvider(std::weak_ptr provider) { impl->_configProvider = std::move(provider); } + } // namespace cly \ No newline at end of file From d8749b953d08a47490c44c9d4433c9044a4e00e5 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Thu, 8 Jan 2026 10:50:43 +0300 Subject: [PATCH 19/46] feat: config to crash module --- include/countly/crash_module.hpp | 2 ++ src/crash_module.cpp | 3 +++ 2 files changed, 5 insertions(+) diff --git a/include/countly/crash_module.hpp b/include/countly/crash_module.hpp index 1cabe7e..e2bbadc 100644 --- a/include/countly/crash_module.hpp +++ b/include/countly/crash_module.hpp @@ -35,8 +35,10 @@ class CrashModule { void recordException(const std::string &title, const std::string &stackTrace, const bool fatal, const std::map &crashMetrics, const std::map &segmentation = {}); private: + friend class Countly; class CrashModuleImpl; std::unique_ptr impl; + void setConfigurationProvider(std::weak_ptr provider); // try injecting }; } // namespace cly #endif diff --git a/src/crash_module.cpp b/src/crash_module.cpp index 319db7f..f437111 100644 --- a/src/crash_module.cpp +++ b/src/crash_module.cpp @@ -16,6 +16,7 @@ class CrashModule::CrashModuleImpl { std::shared_ptr _logger; std::shared_ptr _requestModule; std::shared_ptr _mutex; + std::weak_ptr _configProvider; CrashModuleImpl(std::shared_ptr config, std::shared_ptr logger, std::shared_ptr requestModule, std::shared_ptr mutex) : _configuration(config), _logger(logger), _requestModule(requestModule), _mutex(mutex) {} // destructor to reset logger @@ -94,4 +95,6 @@ void CrashModule::recordException(const std::string &title, const std::string &s impl->_mutex->unlock(); } +void CrashModule::setConfigurationProvider(std::weak_ptr provider) { impl->_configProvider = std::move(provider); } + } // namespace cly \ No newline at end of file From 1d20263497bff77cb37d16412837e5c7003b751f Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Thu, 8 Jan 2026 10:54:56 +0300 Subject: [PATCH 20/46] feat: expose crash reporting flag to provider --- include/countly/configuration_module.hpp | 8 ++++---- include/countly/configuration_provider.hpp | 1 + src/configuration_module.cpp | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/include/countly/configuration_module.hpp b/include/countly/configuration_module.hpp index a17712d..8082e32 100644 --- a/include/countly/configuration_module.hpp +++ b/include/countly/configuration_module.hpp @@ -1,15 +1,15 @@ #ifndef CONFIGURATION_MODULE_HPP_ #define CONFIGURATION_MODULE_HPP_ +#include "countly/configuration_provider.hpp" #include "countly/countly_configuration.hpp" #include "countly/logger_module.hpp" #include "countly/request_builder.hpp" -#include "countly/storage_module_base.hpp" -#include "countly/configuration_provider.hpp" #include "countly/request_module.hpp" +#include "countly/storage_module_base.hpp" namespace cly { -class ConfigurationModule : public ConfigurationProvider{ +class ConfigurationModule : public ConfigurationProvider { public: ~ConfigurationModule(); @@ -23,7 +23,7 @@ class ConfigurationModule : public ConfigurationProvider{ bool isViewTrackingEnabled(); bool isSessionTrackingEnabled(); bool isCustomEventTrackingEnabled(); - bool isCrashReportingEnabled(); + bool isCrashReportingEnabled() const override; unsigned int getRequestQueueSizeLimit() const override; unsigned int getEventQueueSizeLimit(); diff --git a/include/countly/configuration_provider.hpp b/include/countly/configuration_provider.hpp index 5e8145b..5a42d78 100644 --- a/include/countly/configuration_provider.hpp +++ b/include/countly/configuration_provider.hpp @@ -8,6 +8,7 @@ class ConfigurationProvider { virtual bool isNetworkingEnabled() const = 0; virtual bool isTrackingEnabled() const = 0; + virtual bool isCrashReportingEnabled() const = 0; virtual unsigned int getRequestQueueSizeLimit() const = 0; }; } // namespace cly diff --git a/src/configuration_module.cpp b/src/configuration_module.cpp index da92671..687121f 100644 --- a/src/configuration_module.cpp +++ b/src/configuration_module.cpp @@ -197,7 +197,7 @@ bool ConfigurationModule::isSessionTrackingEnabled() { return impl->sessionTrack bool ConfigurationModule::isCustomEventTrackingEnabled() { return impl->customEventTrackingEnabled.load(std::memory_order_acquire); } -bool ConfigurationModule::isCrashReportingEnabled() { return impl->crashReportingEnabled.load(std::memory_order_acquire); } +bool ConfigurationModule::isCrashReportingEnabled() const { return impl->crashReportingEnabled.load(std::memory_order_acquire); } unsigned int ConfigurationModule::getRequestQueueSizeLimit() const { return impl->requestQueueSizeLimit.load(std::memory_order_acquire); } From 6f752c538846bd9f0fbfcff1901d30c52572e4f4 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Thu, 8 Jan 2026 10:56:07 +0300 Subject: [PATCH 21/46] feat: expose view reporting flag to provider --- include/countly/configuration_module.hpp | 2 +- include/countly/configuration_provider.hpp | 1 + src/configuration_module.cpp | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/include/countly/configuration_module.hpp b/include/countly/configuration_module.hpp index 8082e32..b516e40 100644 --- a/include/countly/configuration_module.hpp +++ b/include/countly/configuration_module.hpp @@ -20,7 +20,7 @@ class ConfigurationModule : public ConfigurationProvider { bool isNetworkingEnabled() const override; bool isLocationTrackingEnabled(); - bool isViewTrackingEnabled(); + bool isViewTrackingEnabled() const override; bool isSessionTrackingEnabled(); bool isCustomEventTrackingEnabled(); bool isCrashReportingEnabled() const override; diff --git a/include/countly/configuration_provider.hpp b/include/countly/configuration_provider.hpp index 5a42d78..e573de0 100644 --- a/include/countly/configuration_provider.hpp +++ b/include/countly/configuration_provider.hpp @@ -9,6 +9,7 @@ class ConfigurationProvider { virtual bool isNetworkingEnabled() const = 0; virtual bool isTrackingEnabled() const = 0; virtual bool isCrashReportingEnabled() const = 0; + virtual bool isViewTrackingEnabled() const = 0; virtual unsigned int getRequestQueueSizeLimit() const = 0; }; } // namespace cly diff --git a/src/configuration_module.cpp b/src/configuration_module.cpp index 687121f..8d4e714 100644 --- a/src/configuration_module.cpp +++ b/src/configuration_module.cpp @@ -191,7 +191,7 @@ bool ConfigurationModule::isNetworkingEnabled() const { return impl->networkingE bool ConfigurationModule::isLocationTrackingEnabled() { return impl->locationTrackingEnabled.load(std::memory_order_acquire); } -bool ConfigurationModule::isViewTrackingEnabled() { return impl->viewTrackingEnabled.load(std::memory_order_acquire); } +bool ConfigurationModule::isViewTrackingEnabled() const { return impl->viewTrackingEnabled.load(std::memory_order_acquire); } bool ConfigurationModule::isSessionTrackingEnabled() { return impl->sessionTrackingEnabled.load(std::memory_order_acquire); } From 50bf4ea2abb1cf3aaafe39ce31cde52a7ef410ce Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Thu, 8 Jan 2026 11:00:39 +0300 Subject: [PATCH 22/46] feat: integrate sbs to view tracking --- src/views_module.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/views_module.cpp b/src/views_module.cpp index eed92bc..ca3d04b 100644 --- a/src/views_module.cpp +++ b/src/views_module.cpp @@ -74,6 +74,12 @@ class ViewsModule::ViewModuleImpl { ~ViewModuleImpl() { _logger.reset(); } std::string _openView(const std::string &name, const std::map &segmentation) { + if (std::shared_ptr config = _configProvider.lock()) { + if (config->isViewTrackingEnabled() == false) { + _logger->log(LogLevel::DEBUG, "[ViewsModule] _openView: View tracking is disabled. Not opening view."); + return ""; + } + } ViewModuleImpl::ViewInfo *v = new ViewModuleImpl::ViewInfo(); v->name = name; v->viewId = cly::utils::generateEventID(); @@ -88,6 +94,12 @@ class ViewsModule::ViewModuleImpl { } void _closeViewWithName(const std::string &name) { + if (std::shared_ptr config = _configProvider.lock()) { + if (config->isViewTrackingEnabled() == false) { + _logger->log(LogLevel::DEBUG, "[ViewsModule] _closeViewWithName: View tracking is disabled. Not closing view."); + return; + } + } std::shared_ptr v = findViewByName(name); if (v == nullptr) { _logger->log(cly::LogLevel::WARNING, cly::utils::format_string("[ViewModuleImpl] _closeViewWithName: Couldn't found " @@ -99,6 +111,12 @@ class ViewsModule::ViewModuleImpl { } void _closeViewWithID(const std::string &viewId) { + if (std::shared_ptr config = _configProvider.lock()) { + if (config->isViewTrackingEnabled() == false) { + _logger->log(LogLevel::DEBUG, "[ViewsModule] _closeViewWithID: View tracking is disabled. Not closing view."); + return; + } + } if (_viewsStartTime.find(viewId) == _viewsStartTime.end()) { _logger->log(cly::LogLevel::WARNING, cly::utils::format_string("[ViewModuleImpl] _closeViewWithID: Couldn't found " From e182799c3d4f9dad9a5f8e823572a3fa566401a4 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Thu, 8 Jan 2026 11:00:51 +0300 Subject: [PATCH 23/46] feat: integrate sbs to crash reporting --- src/crash_module.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/crash_module.cpp b/src/crash_module.cpp index f437111..cca8a16 100644 --- a/src/crash_module.cpp +++ b/src/crash_module.cpp @@ -51,6 +51,13 @@ void CrashModule::recordException(const std::string &title, const std::string &s impl->_logger->log(LogLevel::INFO, cly::utils::format_string("[CrashModule] recordException: title = %s, stackTrace = %s", title.c_str(), stackTrace.c_str())); + if (std::shared_ptr config = impl->_configProvider.lock()) { + if (config->isCrashReportingEnabled() == false) { + impl->_logger->log(LogLevel::DEBUG, "[CrashModule] recordException: Crash reporting is disabled. Not recording exception."); + return; + } + } + if (title.empty()) { impl->_logger->log(LogLevel::WARNING, "[CrashModule] recordException : The parameter 'title' can't be empty"); } From c21bcaa580944fdb49692480973d4184e0af7af9 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Thu, 8 Jan 2026 11:15:47 +0300 Subject: [PATCH 24/46] feat: integrate session tracking --- src/countly.cpp | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/countly.cpp b/src/countly.cpp index 3139f43..8ed8591 100644 --- a/src/countly.cpp +++ b/src/countly.cpp @@ -458,7 +458,7 @@ void Countly::start(const std::string &app_key, const std::string &host, int por requestModule->setConfigurationProvider(configurationModule); views_module->setConfigurationProvider(configurationModule); - //crash_module->setConfigurationProvider(configurationModule); + crash_module->setConfigurationProvider(configurationModule); bool result = true; #ifdef COUNTLY_USE_SQLITE @@ -467,7 +467,7 @@ void Countly::start(const std::string &app_key, const std::string &host, int por is_sdk_initialized = result; // after this point SDK is initialized. - if(is_sdk_initialized){ + if (is_sdk_initialized) { mutex->unlock(); configurationModule->fetchConfigFromServer(session_params); mutex->lock(); @@ -534,9 +534,9 @@ void Countly::setUpdateInterval(size_t milliseconds) { void Countly::addEvent(const cly::Event &event) { if (configurationModule->isCustomEventTrackingEnabled() == false) { std::string eventStr = event.serialize(); - if(eventStr.find("[CLY]_") == std::string::npos){ - log(LogLevel::DEBUG, "[Countly] addEvent, custom event tracking is disabled in server configuration, can not add event with key: " + eventStr); - return; + if (eventStr.find("[CLY]_") == std::string::npos) { + log(LogLevel::DEBUG, "[Countly] addEvent, custom event tracking is disabled in server configuration, can not add event with key: " + eventStr); + return; } } mutex->lock(); @@ -720,6 +720,11 @@ std::vector Countly::debugReturnStateOfEQ() { bool Countly::beginSession() { mutex->lock(); log(LogLevel::INFO, "[Countly][beginSession]"); + if (configurationModule->isSessionTrackingEnabled() == false) { + log(LogLevel::ERROR, "[Countly][beginSession] Session tracking is disabled in server configuration, can not begin session."); + mutex->unlock(); + return false; + } if (began_session == true) { mutex->unlock(); log(LogLevel::DEBUG, "[Countly][beginSession] Session is already active."); @@ -776,6 +781,11 @@ bool Countly::updateSession() { try { // Check if there was a session, if not try to start one mutex->lock(); + if (configurationModule->isSessionTrackingEnabled() == false) { + log(LogLevel::ERROR, "[Countly][updateSession] Session tracking is disabled in server configuration, can not update session."); + mutex->unlock(); + return false; + } if (began_session == false) { mutex->unlock(); if (configuration->manualSessionControl == true) { @@ -903,6 +913,11 @@ void Countly::sendEventsToRQ(const nlohmann::json &events) { bool Countly::endSession() { log(LogLevel::INFO, "[Countly][endSession]"); + if (configurationModule->isSessionTrackingEnabled() == false) { + log(LogLevel::ERROR, "[Countly][endSession] Session tracking is disabled in server configuration, can not end session."); + mutex->unlock(); + return false; + } if (began_session == false) { log(LogLevel::DEBUG, "[Countly][endSession] There is no active session to end."); return true; From eaeeb7b8efaa7ab79d40a2259062e0fa14d4c5a8 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Thu, 8 Jan 2026 11:47:51 +0300 Subject: [PATCH 25/46] feat: record location expose to cly delegate --- include/countly.hpp | 5 +++++ include/countly/constants.hpp | 2 ++ 2 files changed, 7 insertions(+) diff --git a/include/countly.hpp b/include/countly.hpp index df782cb..919c3c7 100644 --- a/include/countly.hpp +++ b/include/countly.hpp @@ -259,6 +259,11 @@ class Countly : public cly::CountlyDelegates { addEvent(event); } + void RecordLocation(const std::string &countryCode, const std::string &city, const std::string &gpsCoordinates, const std::string &ipAddress) override { + setLocation(countryCode, city, gpsCoordinates, ipAddress); + }; + + /* Provide 'updateInterval' in seconds. */ inline void setAutomaticSessionUpdateInterval(unsigned short updateInterval) { if (is_sdk_initialized) { diff --git a/include/countly/constants.hpp b/include/countly/constants.hpp index 52b436c..69bbc7c 100644 --- a/include/countly/constants.hpp +++ b/include/countly/constants.hpp @@ -93,6 +93,8 @@ class CountlyDelegates { virtual void RecordEvent(const std::string &key, const std::map &segmentation, int count, double sum) = 0; virtual void RecordEvent(const std::string &key, const std::map &segmentation, int count, double sum, double duration) = 0; + + virtual void RecordLocation(const std::string &countryCode, const std::string &city, const std::string &gpsCoordinates, const std::string &ipAddress) = 0; }; } // namespace cly From 210e9ac445be699a7283e4eb4fa5855570206cd6 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Thu, 8 Jan 2026 11:48:50 +0300 Subject: [PATCH 26/46] mixed: location tracking & cly delegate to config module --- include/countly/configuration_module.hpp | 3 ++- src/countly.cpp | 7 ++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/include/countly/configuration_module.hpp b/include/countly/configuration_module.hpp index b516e40..6354227 100644 --- a/include/countly/configuration_module.hpp +++ b/include/countly/configuration_module.hpp @@ -7,13 +7,14 @@ #include "countly/request_builder.hpp" #include "countly/request_module.hpp" #include "countly/storage_module_base.hpp" +#include "countly/constants.hpp" namespace cly { class ConfigurationModule : public ConfigurationProvider { public: ~ConfigurationModule(); - ConfigurationModule(std::shared_ptr config, std::shared_ptr logger, std::shared_ptr requestBuilder, std::shared_ptr storageModule, std::shared_ptr requestModule, std::shared_ptr mutex); + ConfigurationModule(cly::CountlyDelegates *cly, std::shared_ptr config, std::shared_ptr logger, std::shared_ptr requestBuilder, std::shared_ptr storageModule, std::shared_ptr requestModule, std::shared_ptr mutex); void fetchConfigFromServer(nlohmann::json session_params); bool isTrackingEnabled() const override; diff --git a/src/countly.cpp b/src/countly.cpp index 8ed8591..eaba628 100644 --- a/src/countly.cpp +++ b/src/countly.cpp @@ -252,6 +252,11 @@ void Countly::setLocation(double lattitude, double longitude) { void Countly::setLocation(const std::string &countryCode, const std::string &city, const std::string &gpsCoordinates, const std::string &ipAddress) { mutex->lock(); + if (configurationModule->isLocationTrackingEnabled() == false) { + log(LogLevel::ERROR, "[Countly][setLocation] Location tracking is disabled in server configuration, can not set location."); + mutex->unlock(); + return; + } log(LogLevel::INFO, "[Countly][setLocation] SetLocation : countryCode = " + countryCode + ", city = " + city + ", gpsCoordinates = " + gpsCoordinates + ", ipAddress = " + ipAddress); if ((!countryCode.empty() && city.empty()) || (!city.empty() && countryCode.empty())) { @@ -452,7 +457,7 @@ void Countly::start(const std::string &app_key, const std::string &host, int por requestBuilder.reset(new RequestBuilder(configuration, logger)); requestModule.reset(new RequestModule(configuration, logger, requestBuilder, storageModule)); - configurationModule.reset(new cly::ConfigurationModule(configuration, logger, requestBuilder, storageModule, requestModule, mutex)); + configurationModule.reset(new cly::ConfigurationModule(this, configuration, logger, requestBuilder, storageModule, requestModule, mutex)); crash_module.reset(new cly::CrashModule(configuration, logger, requestModule, mutex)); views_module.reset(new cly::ViewsModule(this, logger)); From 98731b4a1f968a5fb50ab6a6e9caa4ccb447b3e8 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Thu, 8 Jan 2026 11:49:25 +0300 Subject: [PATCH 27/46] refactor: more precise param setting --- src/configuration_module.cpp | 49 +++++++++++++++++++++++++----------- 1 file changed, 34 insertions(+), 15 deletions(-) diff --git a/src/configuration_module.cpp b/src/configuration_module.cpp index 8d4e714..92e4439 100644 --- a/src/configuration_module.cpp +++ b/src/configuration_module.cpp @@ -54,15 +54,17 @@ static constexpr const char *KEY_BOM_DURATION = "bom_d"; class ConfigurationModule::ConfigurationModuleImpl { private: -public: std::shared_ptr _configuration; - std::shared_ptr _logger; std::shared_ptr _requestBuilder; std::shared_ptr _storageModule; std::shared_ptr _requestModule; - std::shared_ptr _mutex; + cly::CountlyDelegates *_cly; nlohmann::json sdk_behavior_settings; +public: + std::shared_ptr _logger; + std::shared_ptr _mutex; + // current settings cached for quick access std::atomic networkingEnabled{true}; std::atomic trackingEnabled{true}; @@ -74,8 +76,9 @@ class ConfigurationModule::ConfigurationModuleImpl { std::atomic eventQueueThreshold{0}; std::atomic requestQueueSizeLimit{0}; std::atomic sessionUpdateInterval{0}; - ConfigurationModuleImpl(std::shared_ptr config, std::shared_ptr logger, std::shared_ptr requestBuilder, std::shared_ptr storageModule, std::shared_ptr requestModule, std::shared_ptr mutex) - : _configuration(config), _logger(logger), _requestBuilder(requestBuilder), _storageModule(storageModule), _requestModule(requestModule), _mutex(mutex) {} + ConfigurationModuleImpl(cly::CountlyDelegates *cly, std::shared_ptr config, std::shared_ptr logger, std::shared_ptr requestBuilder, std::shared_ptr storageModule, std::shared_ptr requestModule, + std::shared_ptr mutex) + : _configuration(config), _logger(logger), _requestBuilder(requestBuilder), _storageModule(storageModule), _requestModule(requestModule), _mutex(mutex), _cly(cly) {} ~ConfigurationModuleImpl() { _logger.reset(); } @@ -119,14 +122,30 @@ class ConfigurationModule::ConfigurationModuleImpl { return value; } - void populateConfigValues() { - trackingEnabled.store(getBool(KEY_TRACKING, true), std::memory_order_release); - networkingEnabled.store(getBool(KEY_NETWORKING, true), std::memory_order_release); - sessionTrackingEnabled.store(getBool(KEY_SESSION_TRACKING, true), std::memory_order_release); - viewTrackingEnabled.store(getBool(KEY_VIEW_TRACKING, true), std::memory_order_release); - locationTrackingEnabled.store(getBool(KEY_LOCATION_TRACKING, true), std::memory_order_release); - customEventTrackingEnabled.store(getBool(KEY_CUSTOM_EVENT_TRACKING, true), std::memory_order_release); - crashReportingEnabled.store(getBool(KEY_CRASH_REPORTING, true), std::memory_order_release); + void populateConfigValues(bool fromStorage = false) { + // get values here + bool trackingEnabledVal = trackingEnabled.load(std::memory_order_acquire); + bool networkingEnabledVal = networkingEnabled.load(std::memory_order_acquire); + bool sessionTrackingEnabledVal = sessionTrackingEnabled.load(std::memory_order_acquire); + bool viewTrackingEnabledVal = viewTrackingEnabled.load(std::memory_order_acquire); + bool locationTrackingEnabledVal = locationTrackingEnabled.load(std::memory_order_acquire); + bool customEventTrackingEnabledVal = customEventTrackingEnabled.load(std::memory_order_acquire); + bool crashReportingEnabledVal = crashReportingEnabled.load(std::memory_order_acquire); + bool locationTrackingCurrent = getBool(KEY_LOCATION_TRACKING, locationTrackingEnabledVal); + + // area under is for behavior changes, did not creata new function for them cuz we have only on feature that should behave after + if (fromStorage == false && locationTrackingEnabledVal == true && locationTrackingCurrent == false) { + // disable location + _cly->RecordLocation("", "", "", ""); + } + + trackingEnabled.store(getBool(KEY_TRACKING, trackingEnabledVal), std::memory_order_release); + networkingEnabled.store(getBool(KEY_NETWORKING, networkingEnabledVal), std::memory_order_release); + sessionTrackingEnabled.store(getBool(KEY_SESSION_TRACKING, sessionTrackingEnabledVal), std::memory_order_release); + viewTrackingEnabled.store(getBool(KEY_VIEW_TRACKING, viewTrackingEnabledVal), std::memory_order_release); + locationTrackingEnabled.store(locationTrackingCurrent, std::memory_order_release); + customEventTrackingEnabled.store(getBool(KEY_CUSTOM_EVENT_TRACKING, customEventTrackingEnabledVal), std::memory_order_release); + crashReportingEnabled.store(getBool(KEY_CRASH_REPORTING, crashReportingEnabledVal), std::memory_order_release); eventQueueThreshold.store(getUInt(KEY_EVENT_QUEUE_SIZE, _configuration->eventQueueThreshold), std::memory_order_release); requestQueueSizeLimit.store(getUInt(KEY_REQ_QUEUE_SIZE, _configuration->requestQueueThreshold), std::memory_order_release); sessionUpdateInterval.store(getUInt(KEY_SESSION_UPDATE_INTERVAL, _configuration->sessionDuration), std::memory_order_release); @@ -168,9 +187,9 @@ class ConfigurationModule::ConfigurationModuleImpl { } }; -ConfigurationModule::ConfigurationModule(std::shared_ptr config, std::shared_ptr logger, std::shared_ptr requestBuilder, std::shared_ptr storageModule, std::shared_ptr requestModule, +ConfigurationModule::ConfigurationModule(cly::CountlyDelegates *cly, std::shared_ptr config, std::shared_ptr logger, std::shared_ptr requestBuilder, std::shared_ptr storageModule, std::shared_ptr requestModule, std::shared_ptr mutex) { - impl.reset(new ConfigurationModuleImpl(config, logger, requestBuilder, storageModule, requestModule, mutex)); + impl.reset(new ConfigurationModuleImpl(cly, config, logger, requestBuilder, storageModule, requestModule, mutex)); impl->_logger->log(LogLevel::DEBUG, "[ConfigurationModule] Initialized"); } From 5195333db58fd0d01e030de5a648bfcde9f42e4b Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Thu, 8 Jan 2026 15:47:18 +0300 Subject: [PATCH 28/46] feat: config module update timer --- include/countly/configuration_module.hpp | 7 ++- src/configuration_module.cpp | 73 +++++++++++++++++++++++- src/countly.cpp | 2 + 3 files changed, 79 insertions(+), 3 deletions(-) diff --git a/include/countly/configuration_module.hpp b/include/countly/configuration_module.hpp index 6354227..e29e2dd 100644 --- a/include/countly/configuration_module.hpp +++ b/include/countly/configuration_module.hpp @@ -2,21 +2,24 @@ #define CONFIGURATION_MODULE_HPP_ #include "countly/configuration_provider.hpp" +#include "countly/constants.hpp" #include "countly/countly_configuration.hpp" #include "countly/logger_module.hpp" #include "countly/request_builder.hpp" #include "countly/request_module.hpp" #include "countly/storage_module_base.hpp" -#include "countly/constants.hpp" namespace cly { class ConfigurationModule : public ConfigurationProvider { public: ~ConfigurationModule(); - ConfigurationModule(cly::CountlyDelegates *cly, std::shared_ptr config, std::shared_ptr logger, std::shared_ptr requestBuilder, std::shared_ptr storageModule, std::shared_ptr requestModule, std::shared_ptr mutex); + ConfigurationModule(cly::CountlyDelegates *cly, std::shared_ptr config, std::shared_ptr logger, std::shared_ptr requestBuilder, std::shared_ptr storageModule, std::shared_ptr requestModule, + std::shared_ptr mutex); void fetchConfigFromServer(nlohmann::json session_params); + void startServerConfigUpdateTimer(nlohmann::json session_params); + void stopTimer(); bool isTrackingEnabled() const override; bool isNetworkingEnabled() const override; diff --git a/src/configuration_module.cpp b/src/configuration_module.cpp index 92e4439..53e74a2 100644 --- a/src/configuration_module.cpp +++ b/src/configuration_module.cpp @@ -65,6 +65,11 @@ class ConfigurationModule::ConfigurationModuleImpl { std::shared_ptr _logger; std::shared_ptr _mutex; + std::atomic stopConfigThread{false}; + std::thread configUpdateThread; + std::mutex configUpdateMutex; + std::condition_variable configUpdateCv; + // current settings cached for quick access std::atomic networkingEnabled{true}; std::atomic trackingEnabled{true}; @@ -80,7 +85,27 @@ class ConfigurationModule::ConfigurationModuleImpl { std::shared_ptr mutex) : _configuration(config), _logger(logger), _requestBuilder(requestBuilder), _storageModule(storageModule), _requestModule(requestModule), _mutex(mutex), _cly(cly) {} - ~ConfigurationModuleImpl() { _logger.reset(); } + ~ConfigurationModuleImpl() { + std::lock_guard lock(configUpdateMutex); + stopConfigThread.store(true, std::memory_order_release); + configUpdateCv.notify_all(); + if (configUpdateThread.joinable()) { + configUpdateThread.join(); + } + sdk_behavior_settings.clear(); + + networkingEnabled.store(true, std::memory_order_relaxed); + trackingEnabled.store(true, std::memory_order_relaxed); + sessionTrackingEnabled.store(true, std::memory_order_relaxed); + viewTrackingEnabled.store(true, std::memory_order_relaxed); + locationTrackingEnabled.store(true, std::memory_order_relaxed); + customEventTrackingEnabled.store(true, std::memory_order_relaxed); + crashReportingEnabled.store(true, std::memory_order_relaxed); + eventQueueThreshold.store(0, std::memory_order_relaxed); + requestQueueSizeLimit.store(0, std::memory_order_relaxed); + sessionUpdateInterval.store(0, std::memory_order_relaxed); + _logger.reset(); + } void _fetchConfigFromServerHTTP(const std::map &data) { HTTPResponse response = _requestModule->sendHTTP("/o/sdk", _requestBuilder->serializeData(data)); @@ -94,6 +119,33 @@ class ConfigurationModule::ConfigurationModuleImpl { } } + void _updateConfigPeriodically(const nlohmann::json &session_params) { + std::unique_lock lock(configUpdateMutex); + + while (!stopConfigThread.load(std::memory_order_acquire)) { + + unsigned int interval = getUInt(KEY_SERVER_CONFIG_UPDATE_INTERVAL, 4); + + if (interval < 1) { + interval = 4; + } + + bool stopped = configUpdateCv.wait_for(lock, std::chrono::hours(interval), [&] { return stopConfigThread.load(std::memory_order_acquire); }); + if (stopped) { + return; + } + + lock.unlock(); + + _mutex->lock(); + std::map data = {{"method", "sc"}, {"app_key", session_params["app_key"].get()}, {"device_id", session_params["device_id"].get()}}; + _mutex->unlock(); + _fetchConfigFromServerHTTP(data); + + lock.lock(); + } + } + bool getBool(const char *key, bool defaultValue) const { if (!sdk_behavior_settings.is_object()) { return defaultValue; @@ -204,6 +256,25 @@ void ConfigurationModule::fetchConfigFromServer(nlohmann::json session_params) { _thread.detach(); } +void ConfigurationModule::startServerConfigUpdateTimer(nlohmann::json session_params) { + if (impl->configUpdateThread.joinable()) { + return; + } + + impl->stopConfigThread.store(false, std::memory_order_release); + impl->configUpdateThread = std::thread(&ConfigurationModule::ConfigurationModuleImpl::_updateConfigPeriodically, impl.get(), session_params); +} + +void ConfigurationModule::stopTimer() { + impl->_logger->log(LogLevel::WARNING, "[ConfigurationModule] stopTimer, stopping server config update timer thread."); + impl->stopConfigThread.store(true, std::memory_order_release); + impl->configUpdateCv.notify_all(); + + if (impl->configUpdateThread.joinable()) { + impl->configUpdateThread.join(); + } +} + bool ConfigurationModule::isTrackingEnabled() const { return impl->trackingEnabled.load(std::memory_order_acquire); } bool ConfigurationModule::isNetworkingEnabled() const { return impl->networkingEnabled.load(std::memory_order_acquire); } diff --git a/src/countly.cpp b/src/countly.cpp index eaba628..93448f8 100644 --- a/src/countly.cpp +++ b/src/countly.cpp @@ -475,6 +475,7 @@ void Countly::start(const std::string &app_key, const std::string &host, int por if (is_sdk_initialized) { mutex->unlock(); configurationModule->fetchConfigFromServer(session_params); + configurationModule->startServerConfigUpdateTimer(session_params); mutex->lock(); } @@ -510,6 +511,7 @@ void Countly::startOnCloud(const std::string &app_key) { } void Countly::stop() { + configurationModule->stopTimer(); _deleteThread(); if (configuration->manualSessionControl == false) { endSession(); From 2556c7ba7c0efe9e000091f1d2fcb5d3cd06d686 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Thu, 8 Jan 2026 15:56:22 +0300 Subject: [PATCH 29/46] refactor: reorder functions internal --- src/configuration_module.cpp | 169 +++++++++++++++++------------------ 1 file changed, 83 insertions(+), 86 deletions(-) diff --git a/src/configuration_module.cpp b/src/configuration_module.cpp index 53e74a2..b9e61ec 100644 --- a/src/configuration_module.cpp +++ b/src/configuration_module.cpp @@ -85,28 +85,6 @@ class ConfigurationModule::ConfigurationModuleImpl { std::shared_ptr mutex) : _configuration(config), _logger(logger), _requestBuilder(requestBuilder), _storageModule(storageModule), _requestModule(requestModule), _mutex(mutex), _cly(cly) {} - ~ConfigurationModuleImpl() { - std::lock_guard lock(configUpdateMutex); - stopConfigThread.store(true, std::memory_order_release); - configUpdateCv.notify_all(); - if (configUpdateThread.joinable()) { - configUpdateThread.join(); - } - sdk_behavior_settings.clear(); - - networkingEnabled.store(true, std::memory_order_relaxed); - trackingEnabled.store(true, std::memory_order_relaxed); - sessionTrackingEnabled.store(true, std::memory_order_relaxed); - viewTrackingEnabled.store(true, std::memory_order_relaxed); - locationTrackingEnabled.store(true, std::memory_order_relaxed); - customEventTrackingEnabled.store(true, std::memory_order_relaxed); - crashReportingEnabled.store(true, std::memory_order_relaxed); - eventQueueThreshold.store(0, std::memory_order_relaxed); - requestQueueSizeLimit.store(0, std::memory_order_relaxed); - sessionUpdateInterval.store(0, std::memory_order_relaxed); - _logger.reset(); - } - void _fetchConfigFromServerHTTP(const std::map &data) { HTTPResponse response = _requestModule->sendHTTP("/o/sdk", _requestBuilder->serializeData(data)); if (response.success && response.data.is_object() && response.data.contains(KEY_CONFIG)) { @@ -119,61 +97,6 @@ class ConfigurationModule::ConfigurationModuleImpl { } } - void _updateConfigPeriodically(const nlohmann::json &session_params) { - std::unique_lock lock(configUpdateMutex); - - while (!stopConfigThread.load(std::memory_order_acquire)) { - - unsigned int interval = getUInt(KEY_SERVER_CONFIG_UPDATE_INTERVAL, 4); - - if (interval < 1) { - interval = 4; - } - - bool stopped = configUpdateCv.wait_for(lock, std::chrono::hours(interval), [&] { return stopConfigThread.load(std::memory_order_acquire); }); - if (stopped) { - return; - } - - lock.unlock(); - - _mutex->lock(); - std::map data = {{"method", "sc"}, {"app_key", session_params["app_key"].get()}, {"device_id", session_params["device_id"].get()}}; - _mutex->unlock(); - _fetchConfigFromServerHTTP(data); - - lock.lock(); - } - } - - bool getBool(const char *key, bool defaultValue) const { - if (!sdk_behavior_settings.is_object()) { - return defaultValue; - } - - auto it = sdk_behavior_settings.find(key); - if (it == sdk_behavior_settings.end() || !it->is_boolean()) { - return defaultValue; - } - - bool value = it->get(); - return value; - } - - unsigned int getUInt(const char *key, unsigned int defaultValue) const { - if (!sdk_behavior_settings.is_object()) { - return defaultValue; - } - - auto it = sdk_behavior_settings.find(key); - if (it == sdk_behavior_settings.end() || !it->is_number_unsigned()) { - return defaultValue; - } - - unsigned int value = it->get(); - return value; - } - void populateConfigValues(bool fromStorage = false) { // get values here bool trackingEnabledVal = trackingEnabled.load(std::memory_order_acquire); @@ -237,6 +160,88 @@ class ConfigurationModule::ConfigurationModuleImpl { ++it; } } + + void _updateConfigPeriodically(const nlohmann::json &session_params) { + std::unique_lock lock(configUpdateMutex); + + while (!stopConfigThread.load(std::memory_order_acquire)) { + + unsigned int interval = getUInt(KEY_SERVER_CONFIG_UPDATE_INTERVAL, 4); + + if (interval < 1) { + interval = 4; + } + + bool stopped = configUpdateCv.wait_for(lock, std::chrono::hours(interval), [&] { return stopConfigThread.load(std::memory_order_acquire); }); + if (stopped) { + return; + } + + lock.unlock(); + + _mutex->lock(); + std::map data = {{"method", "sc"}, {"app_key", session_params["app_key"].get()}, {"device_id", session_params["device_id"].get()}}; + _mutex->unlock(); + _fetchConfigFromServerHTTP(data); + + lock.lock(); + } + } + + void _stopTimer() { + _logger->log(LogLevel::WARNING, "[ConfigurationModule] stopTimer, stopping server config update timer thread."); + stopConfigThread.store(true, std::memory_order_release); + configUpdateCv.notify_all(); + + if (configUpdateThread.joinable()) { + configUpdateThread.join(); + } + } + + ~ConfigurationModuleImpl() { + _stopTimer(); + sdk_behavior_settings.clear(); + + networkingEnabled.store(true, std::memory_order_relaxed); + trackingEnabled.store(true, std::memory_order_relaxed); + sessionTrackingEnabled.store(true, std::memory_order_relaxed); + viewTrackingEnabled.store(true, std::memory_order_relaxed); + locationTrackingEnabled.store(true, std::memory_order_relaxed); + customEventTrackingEnabled.store(true, std::memory_order_relaxed); + crashReportingEnabled.store(true, std::memory_order_relaxed); + eventQueueThreshold.store(0, std::memory_order_relaxed); + requestQueueSizeLimit.store(0, std::memory_order_relaxed); + sessionUpdateInterval.store(0, std::memory_order_relaxed); + _logger.reset(); + } + + bool getBool(const char *key, bool defaultValue) const { + if (!sdk_behavior_settings.is_object()) { + return defaultValue; + } + + auto it = sdk_behavior_settings.find(key); + if (it == sdk_behavior_settings.end() || !it->is_boolean()) { + return defaultValue; + } + + bool value = it->get(); + return value; + } + + unsigned int getUInt(const char *key, unsigned int defaultValue) const { + if (!sdk_behavior_settings.is_object()) { + return defaultValue; + } + + auto it = sdk_behavior_settings.find(key); + if (it == sdk_behavior_settings.end() || !it->is_number_unsigned()) { + return defaultValue; + } + + unsigned int value = it->get(); + return value; + } }; ConfigurationModule::ConfigurationModule(cly::CountlyDelegates *cly, std::shared_ptr config, std::shared_ptr logger, std::shared_ptr requestBuilder, std::shared_ptr storageModule, std::shared_ptr requestModule, @@ -265,15 +270,7 @@ void ConfigurationModule::startServerConfigUpdateTimer(nlohmann::json session_pa impl->configUpdateThread = std::thread(&ConfigurationModule::ConfigurationModuleImpl::_updateConfigPeriodically, impl.get(), session_params); } -void ConfigurationModule::stopTimer() { - impl->_logger->log(LogLevel::WARNING, "[ConfigurationModule] stopTimer, stopping server config update timer thread."); - impl->stopConfigThread.store(true, std::memory_order_release); - impl->configUpdateCv.notify_all(); - - if (impl->configUpdateThread.joinable()) { - impl->configUpdateThread.join(); - } -} +void ConfigurationModule::stopTimer() { impl->_stopTimer(); } bool ConfigurationModule::isTrackingEnabled() const { return impl->trackingEnabled.load(std::memory_order_acquire); } From ea27c94c72ef3ad63d0797fe6a167cb79a0780c3 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Thu, 8 Jan 2026 18:27:40 +0300 Subject: [PATCH 30/46] feat: introduce storage for sbs --- include/countly/storage_module_base.hpp | 4 ++++ include/countly/storage_module_db.hpp | 2 ++ include/countly/storage_module_memory.hpp | 2 ++ 3 files changed, 8 insertions(+) diff --git a/include/countly/storage_module_base.hpp b/include/countly/storage_module_base.hpp index 1cda567..507a35e 100644 --- a/include/countly/storage_module_base.hpp +++ b/include/countly/storage_module_base.hpp @@ -94,6 +94,10 @@ class StorageModuleBase { * @param request: content of the request */ virtual void RQInsertAtEnd(const std::string &request) = 0; + + virtual void storeSDKBehaviorSettings(const std::string &sdk_behavior_settings) = 0; + + virtual std::string getSDKBehaviorSettings() = 0; }; } // namespace cly diff --git a/include/countly/storage_module_db.hpp b/include/countly/storage_module_db.hpp index 47bf852..a57022c 100644 --- a/include/countly/storage_module_db.hpp +++ b/include/countly/storage_module_db.hpp @@ -24,6 +24,8 @@ class StorageModuleDB : public StorageModuleBase { std::vector> RQPeekAll() override; void RQRemoveFront(std::shared_ptr request) override; void RQInsertAtEnd(const std::string &request) override; + void storeSDKBehaviorSettings(const std::string &sdk_behavior_settings) override; + std::string getSDKBehaviorSettings() override; }; } // namespace cly #endif \ No newline at end of file diff --git a/include/countly/storage_module_memory.hpp b/include/countly/storage_module_memory.hpp index f1e3468..89fc3e7 100644 --- a/include/countly/storage_module_memory.hpp +++ b/include/countly/storage_module_memory.hpp @@ -26,6 +26,8 @@ class StorageModuleMemory : public StorageModuleBase { std::vector> RQPeekAll() override; void RQRemoveFront(std::shared_ptr request) override; void RQInsertAtEnd(const std::string &request) override; + void storeSDKBehaviorSettings(const std::string &sdk_behavior_settings) override; + std::string getSDKBehaviorSettings() override; }; } // namespace cly #endif \ No newline at end of file From efc88dc109ffab8382b77b571a579769f7309ccb Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Thu, 8 Jan 2026 18:28:30 +0300 Subject: [PATCH 31/46] feat: impls of them --- src/storage_module_db.cpp | 92 +++++++++++++++++++++++++++++++++++ src/storage_module_memory.cpp | 9 ++++ 2 files changed, 101 insertions(+) diff --git a/src/storage_module_db.cpp b/src/storage_module_db.cpp index 60c73e9..c0dc435 100644 --- a/src/storage_module_db.cpp +++ b/src/storage_module_db.cpp @@ -10,6 +10,10 @@ const char REQUESTS_TABLE_NAME[] = "Requests"; const char REQUESTS_TABLE_REQUEST_ID[] = "RequestID"; const char REQUESTS_TABLE_REQUEST_DATA[] = "RequestData"; +#define SDK_BEHAVIOR_SETTINGS_TABLE_NAME "SDKBehaviorSettings" +#define SDK_BEHAVIOR_SETTINGS_KEY_COLUMN_NAME "Key" +#define SDK_BEHAVIOR_SETTINGS_DATA_COLUMN_NAME "SettingsData" +#define SDK_BEHAVIOR_SETTINGS_KEY_VALUE 1 namespace cly { StorageModuleDB::StorageModuleDB(std::shared_ptr config, std::shared_ptr logger) : StorageModuleBase(config, logger) {} @@ -30,6 +34,8 @@ void StorageModuleDB::init() { // Create schema for the requests table _is_initialized = createSchema(REQUESTS_TABLE_NAME, REQUESTS_TABLE_REQUEST_ID, REQUESTS_TABLE_REQUEST_DATA); + // Create schema for the SDK behavior settings table + _is_initialized = createSchema(SDK_BEHAVIOR_SETTINGS_TABLE_NAME, SDK_BEHAVIOR_SETTINGS_KEY_COLUMN_NAME, SDK_BEHAVIOR_SETTINGS_DATA_COLUMN_NAME); if (_is_initialized) { vacuumDatabase(); @@ -454,4 +460,90 @@ const std::shared_ptr StorageModuleDB::RQPeekFront() { } } +void StorageModuleDB::storeSDKBehaviorSettings(const std::string &sdk_behavior_settings) { + if (!_is_initialized) { + _logger->log(LogLevel::ERROR, "[Countly][StorageModuleDB] storeSDKBehaviorSettings: Module is not initialized"); + return; + } + + if (sdk_behavior_settings.empty()) { + _logger->log(LogLevel::WARNING, "[Countly][StorageModuleDB] storeSDKBehaviorSettings: Empty data"); + return; + } + + _logger->log(LogLevel::DEBUG, "[Countly][StorageModuleDB] storeSDKBehaviorSettings"); + +#ifdef COUNTLY_USE_SQLITE + sqlite3 *db = nullptr; + sqlite3_stmt *stmt = nullptr; + + if (sqlite3_open(_configuration->databasePath.c_str(), &db) != SQLITE_OK) { + _logger->log(LogLevel::ERROR, "[Countly][StorageModuleDB] Failed to open database"); + return; + } + + const char *sql = "INSERT OR REPLACE INTO " SDK_BEHAVIOR_SETTINGS_TABLE_NAME " (" SDK_BEHAVIOR_SETTINGS_KEY_COLUMN_NAME ", " SDK_BEHAVIOR_SETTINGS_DATA_COLUMN_NAME ") " + "VALUES (?, ?);"; + + if (sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr) != SQLITE_OK) { + _logger->log(LogLevel::ERROR, "[Countly][StorageModuleDB] Failed to prepare statement"); + sqlite3_close(db); + return; + } + + sqlite3_bind_int(stmt, 1, SDK_BEHAVIOR_SETTINGS_KEY_VALUE); + sqlite3_bind_text(stmt, 2, sdk_behavior_settings.c_str(), -1, SQLITE_TRANSIENT); + + if (sqlite3_step(stmt) != SQLITE_DONE) { + const char* err = sqlite3_errmsg(db); + _logger->log( + LogLevel::ERROR, + std::string("[Countly][StorageModuleDB] storeSDKBehaviorSettings failed: ") + + err); } + + sqlite3_finalize(stmt); + sqlite3_close(db); +#endif +} + +std::string StorageModuleDB::getSDKBehaviorSettings() { + if (!_is_initialized) { + _logger->log(LogLevel::ERROR, "[Countly][StorageModuleDB] getSDKBehaviorSettings: Module is not initialized"); + return ""; + } + +#ifdef COUNTLY_USE_SQLITE + sqlite3 *db = nullptr; + sqlite3_stmt *stmt = nullptr; + std::string result; + + if (sqlite3_open(_configuration->databasePath.c_str(), &db) != SQLITE_OK) { + _logger->log(LogLevel::ERROR, "[Countly][StorageModuleDB] Failed to open database"); + return ""; + } + + const char *sql = "SELECT " SDK_BEHAVIOR_SETTINGS_DATA_COLUMN_NAME " FROM " SDK_BEHAVIOR_SETTINGS_TABLE_NAME " LIMIT 1;"; + + if (sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr) == SQLITE_OK) { + if (sqlite3_step(stmt) == SQLITE_ROW) { + const unsigned char *text = sqlite3_column_text(stmt, 0); + if (text) { + result = reinterpret_cast(text); + } + } + } else { + _logger->log(LogLevel::ERROR, "[Countly][StorageModuleDB] Failed to prepare statement"); + } + + if (stmt) { + sqlite3_finalize(stmt); + } + sqlite3_close(db); + + return result; +#else + return ""; +#endif +} + }; // namespace cly \ No newline at end of file diff --git a/src/storage_module_memory.cpp b/src/storage_module_memory.cpp index eadb4c3..1409653 100644 --- a/src/storage_module_memory.cpp +++ b/src/storage_module_memory.cpp @@ -106,6 +106,15 @@ void StorageModuleMemory::RQClearAll() { request_queue.clear(); } +void StorageModuleMemory::storeSDKBehaviorSettings(const std::string &sdk_behavior_settings) { + // For in-memory storage, it is already stored in memory inside the module. +} + +std::string StorageModuleMemory::getSDKBehaviorSettings() { + // For in-memory storage, it is already stored in memory inside the module. + return ""; +} + const std::shared_ptr StorageModuleMemory::RQPeekFront() { std::shared_ptr front = nullptr; if (!_is_initialized) { From 001f43052013d1821746d291a7292106a0d5b268 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Thu, 8 Jan 2026 18:28:59 +0300 Subject: [PATCH 32/46] feat: fetching from storage --- include/countly/configuration_module.hpp | 1 + src/configuration_module.cpp | 24 ++++++++++++++++++++++++ src/countly.cpp | 1 + 3 files changed, 26 insertions(+) diff --git a/include/countly/configuration_module.hpp b/include/countly/configuration_module.hpp index e29e2dd..3334f1b 100644 --- a/include/countly/configuration_module.hpp +++ b/include/countly/configuration_module.hpp @@ -18,6 +18,7 @@ class ConfigurationModule : public ConfigurationProvider { std::shared_ptr mutex); void fetchConfigFromServer(nlohmann::json session_params); + void fetchConfigFromStorage(); void startServerConfigUpdateTimer(nlohmann::json session_params); void stopTimer(); bool isTrackingEnabled() const override; diff --git a/src/configuration_module.cpp b/src/configuration_module.cpp index b9e61ec..8da4e52 100644 --- a/src/configuration_module.cpp +++ b/src/configuration_module.cpp @@ -90,6 +90,7 @@ class ConfigurationModule::ConfigurationModuleImpl { if (response.success && response.data.is_object() && response.data.contains(KEY_CONFIG)) { sanitizeConfig(response.data[KEY_CONFIG]); sdk_behavior_settings = response.data[KEY_CONFIG]; + _storageModule->storeSDKBehaviorSettings(sdk_behavior_settings.dump()); _logger->log(LogLevel::INFO, "[ConfigurationModule] _fetchConfigFromServerHTTP, SDK config:\n" + sdk_behavior_settings.dump(2)); populateConfigValues(); } else { @@ -97,6 +98,23 @@ class ConfigurationModule::ConfigurationModuleImpl { } } + void _initializeSBSFromStorage() { + std::string sbs_string = _storageModule->getSDKBehaviorSettings(); + if (!sbs_string.empty()) { + try { + nlohmann::json sbs_json = nlohmann::json::parse(sbs_string); + sanitizeConfig(sbs_json); + sdk_behavior_settings = sbs_json; + _logger->log(LogLevel::INFO, "[ConfigurationModule] _initializeSBSFromStorage, SDK config from storage:\n" + sdk_behavior_settings.dump(2)); + populateConfigValues(true); + } catch (const nlohmann::json::parse_error &e) { + _logger->log(LogLevel::ERROR, "[ConfigurationModule] _initializeSBSFromStorage, Failed to parse SDK behavior settings from storage: " + std::string(e.what())); + } + } else { // use provided config _configuration->providedSBS + _logger->log(LogLevel::INFO, "[ConfigurationModule] _initializeSBSFromStorage, No SDK behavior settings found in storage."); + } + } + void populateConfigValues(bool fromStorage = false) { // get values here bool trackingEnabledVal = trackingEnabled.load(std::memory_order_acquire); @@ -261,6 +279,12 @@ void ConfigurationModule::fetchConfigFromServer(nlohmann::json session_params) { _thread.detach(); } +void ConfigurationModule::fetchConfigFromStorage() { + impl->_mutex->lock(); + impl->_initializeSBSFromStorage(); + impl->_mutex->unlock(); +} + void ConfigurationModule::startServerConfigUpdateTimer(nlohmann::json session_params) { if (impl->configUpdateThread.joinable()) { return; diff --git a/src/countly.cpp b/src/countly.cpp index 93448f8..03dc288 100644 --- a/src/countly.cpp +++ b/src/countly.cpp @@ -474,6 +474,7 @@ void Countly::start(const std::string &app_key, const std::string &host, int por if (is_sdk_initialized) { mutex->unlock(); + configurationModule->fetchConfigFromStorage(); configurationModule->fetchConfigFromServer(session_params); configurationModule->startServerConfigUpdateTimer(session_params); mutex->lock(); From 5790876e94840ead108c1f6dd0948b844abbbbe5 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Fri, 9 Jan 2026 09:51:46 +0300 Subject: [PATCH 33/46] feat: SBS configs in configuration --- include/countly.hpp | 38 +++++++++++++++++++---- include/countly/countly_configuration.hpp | 4 +++ 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/include/countly.hpp b/include/countly.hpp index 919c3c7..2bf2ced 100644 --- a/include/countly.hpp +++ b/include/countly.hpp @@ -24,11 +24,11 @@ #include "countly/logger_module.hpp" #include "countly/storage_module_base.hpp" #include "countly/views_module.hpp" +#include +#include #include #include #include -#include -#include namespace cly { class Countly : public cly::CountlyDelegates { @@ -259,10 +259,7 @@ class Countly : public cly::CountlyDelegates { addEvent(event); } - void RecordLocation(const std::string &countryCode, const std::string &city, const std::string &gpsCoordinates, const std::string &ipAddress) override { - setLocation(countryCode, city, gpsCoordinates, ipAddress); - }; - + void RecordLocation(const std::string &countryCode, const std::string &city, const std::string &gpsCoordinates, const std::string &ipAddress) override { setLocation(countryCode, city, gpsCoordinates, ipAddress); }; /* Provide 'updateInterval' in seconds. */ inline void setAutomaticSessionUpdateInterval(unsigned short updateInterval) { @@ -274,6 +271,35 @@ class Countly : public cly::CountlyDelegates { configuration->sessionDuration = updateInterval; } + /** + * Disable SDK behavior settings updates that SDK performs periodically from the server. + */ + void disableSDKBehaviorSettingsUpdates() { + if (is_sdk_initialized) { + log(LogLevel::WARNING, "[Countly] disableSDKBehaviorSettingsUpdates, You can not disable SDK behavior settings updates after SDK initialization."); + return; + } + + configuration->sdkBehaviorSettingsUpdatesDisabled = true; + } + + /** + * Provide SDK behavior settings in JSON format string. + */ + void setSDKBehaviorSettings(std::string &settings_json) { + if (is_sdk_initialized) { + log(LogLevel::WARNING, "[Countly] setSDKBehaviorSettings, You can not provide SDK behavior settings after SDK initialization."); + return; + } + + if(settings_json.empty()) { + log(LogLevel::WARNING, "[Countly] setSDKBehaviorSettings, Provided SDK behavior settings is empty."); + return; + } + + configuration->sdkBehaviorSettings = settings_json; + } + #ifdef COUNTLY_BUILD_TESTS /** * Convert event queue into list. diff --git a/include/countly/countly_configuration.hpp b/include/countly/countly_configuration.hpp index 6540338..633ee90 100644 --- a/include/countly/countly_configuration.hpp +++ b/include/countly/countly_configuration.hpp @@ -76,6 +76,10 @@ struct CountlyConfiguration { nlohmann::json metrics; + bool sdkBehaviorSettingsUpdatesDisabled = false; + + std::string sdkBehaviorSettings; + CountlyConfiguration(const std::string appKey, std::string serverUrl) { this->appKey = appKey; this->serverUrl = serverUrl; From c83af8bac891d8679f1dd729a1f607b0d8813128 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Fri, 9 Jan 2026 10:00:29 +0300 Subject: [PATCH 34/46] feat: using sbs updates config --- src/configuration_module.cpp | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/src/configuration_module.cpp b/src/configuration_module.cpp index 8da4e52..6b4c998 100644 --- a/src/configuration_module.cpp +++ b/src/configuration_module.cpp @@ -207,6 +207,11 @@ class ConfigurationModule::ConfigurationModuleImpl { } void _stopTimer() { + if (_configuration->sdkBehaviorSettingsUpdatesDisabled) { + _logger->log(LogLevel::INFO, "[ConfigurationModule] _stopTimer, SDK behavior settings updates are disabled."); + return; + } + _logger->log(LogLevel::WARNING, "[ConfigurationModule] stopTimer, stopping server config update timer thread."); stopConfigThread.store(true, std::memory_order_release); configUpdateCv.notify_all(); @@ -216,6 +221,20 @@ class ConfigurationModule::ConfigurationModuleImpl { } } + void _startTimer(nlohmann::json session_params) { + if (_configuration->sdkBehaviorSettingsUpdatesDisabled) { + _logger->log(LogLevel::INFO, "[ConfigurationModule] _startTimer, SDK behavior settings updates are disabled."); + return; + } + + if (configUpdateThread.joinable()) { + return; + } + + stopConfigThread.store(false, std::memory_order_release); + configUpdateThread = std::thread(&ConfigurationModule::ConfigurationModuleImpl::_updateConfigPeriodically, this, session_params); + } + ~ConfigurationModuleImpl() { _stopTimer(); sdk_behavior_settings.clear(); @@ -285,14 +304,7 @@ void ConfigurationModule::fetchConfigFromStorage() { impl->_mutex->unlock(); } -void ConfigurationModule::startServerConfigUpdateTimer(nlohmann::json session_params) { - if (impl->configUpdateThread.joinable()) { - return; - } - - impl->stopConfigThread.store(false, std::memory_order_release); - impl->configUpdateThread = std::thread(&ConfigurationModule::ConfigurationModuleImpl::_updateConfigPeriodically, impl.get(), session_params); -} +void ConfigurationModule::startServerConfigUpdateTimer(nlohmann::json session_params) { impl->_startTimer(session_params); } void ConfigurationModule::stopTimer() { impl->_stopTimer(); } From 923d9e11ab1aad6c34da807a247ace61c2b6bb97 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Mon, 12 Jan 2026 13:48:24 +0300 Subject: [PATCH 35/46] feat: impl provided sbs --- src/configuration_module.cpp | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/configuration_module.cpp b/src/configuration_module.cpp index 6b4c998..732ba3a 100644 --- a/src/configuration_module.cpp +++ b/src/configuration_module.cpp @@ -101,21 +101,25 @@ class ConfigurationModule::ConfigurationModuleImpl { void _initializeSBSFromStorage() { std::string sbs_string = _storageModule->getSDKBehaviorSettings(); if (!sbs_string.empty()) { - try { - nlohmann::json sbs_json = nlohmann::json::parse(sbs_string); - sanitizeConfig(sbs_json); - sdk_behavior_settings = sbs_json; - _logger->log(LogLevel::INFO, "[ConfigurationModule] _initializeSBSFromStorage, SDK config from storage:\n" + sdk_behavior_settings.dump(2)); - populateConfigValues(true); - } catch (const nlohmann::json::parse_error &e) { - _logger->log(LogLevel::ERROR, "[ConfigurationModule] _initializeSBSFromStorage, Failed to parse SDK behavior settings from storage: " + std::string(e.what())); - } - } else { // use provided config _configuration->providedSBS - _logger->log(LogLevel::INFO, "[ConfigurationModule] _initializeSBSFromStorage, No SDK behavior settings found in storage."); + _processSDKBehaviorSettings(sbs_string); + } else if (!_configuration->sdkBehaviorSettings.empty()) { + _processSDKBehaviorSettings(_configuration->sdkBehaviorSettings); + } + } + + void _processSDKBehaviorSettings(const std::string &settings) { + try { + nlohmann::json sbs_json = nlohmann::json::parse(settings); + sanitizeConfig(sbs_json); + sdk_behavior_settings = sbs_json; + _logger->log(LogLevel::INFO, "[ConfigurationModule] _processSDKBehaviorSettings, SDK config:\n" + sdk_behavior_settings.dump(2)); + populateConfigValues(); + } catch (const nlohmann::json::parse_error &e) { + _logger->log(LogLevel::ERROR, "[ConfigurationModule] _processSDKBehaviorSettings, Failed to parse SDK behavior settings: " + std::string(e.what())); } } - void populateConfigValues(bool fromStorage = false) { + void populateConfigValues(bool fromStorage = false) { // from storage means, we do not send disable location request, send it after fetching from server // get values here bool trackingEnabledVal = trackingEnabled.load(std::memory_order_acquire); bool networkingEnabledVal = networkingEnabled.load(std::memory_order_acquire); From c3ac4d21ea2968cd145b52ad39446357e4a7afca Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Mon, 12 Jan 2026 16:33:28 +0300 Subject: [PATCH 36/46] feat: on sbs changed for sbs update interval --- src/configuration_module.cpp | 47 +++++++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/src/configuration_module.cpp b/src/configuration_module.cpp index 732ba3a..7de3177 100644 --- a/src/configuration_module.cpp +++ b/src/configuration_module.cpp @@ -81,6 +81,7 @@ class ConfigurationModule::ConfigurationModuleImpl { std::atomic eventQueueThreshold{0}; std::atomic requestQueueSizeLimit{0}; std::atomic sessionUpdateInterval{0}; + std::atomic serverConfigUpdateInterval{4}; ConfigurationModuleImpl(cly::CountlyDelegates *cly, std::shared_ptr config, std::shared_ptr logger, std::shared_ptr requestBuilder, std::shared_ptr storageModule, std::shared_ptr requestModule, std::shared_ptr mutex) : _configuration(config), _logger(logger), _requestBuilder(requestBuilder), _storageModule(storageModule), _requestModule(requestModule), _mutex(mutex), _cly(cly) {} @@ -92,7 +93,7 @@ class ConfigurationModule::ConfigurationModuleImpl { sdk_behavior_settings = response.data[KEY_CONFIG]; _storageModule->storeSDKBehaviorSettings(sdk_behavior_settings.dump()); _logger->log(LogLevel::INFO, "[ConfigurationModule] _fetchConfigFromServerHTTP, SDK config:\n" + sdk_behavior_settings.dump(2)); - populateConfigValues(); + _onSBSChanged(_populateConfigValues()); } else { _logger->log(LogLevel::WARNING, cly::utils::format_string("[ConfigurationModule] _fetchConfigFromServerHTTP, failed to fetch response_success: [%s]", response.success ? "true" : "false")); } @@ -102,25 +103,40 @@ class ConfigurationModule::ConfigurationModuleImpl { std::string sbs_string = _storageModule->getSDKBehaviorSettings(); if (!sbs_string.empty()) { _processSDKBehaviorSettings(sbs_string); + _logger->log(LogLevel::INFO, "[ConfigurationModule] _initializeSBSFromStorage, initialized SDK behavior settings from storage."); } else if (!_configuration->sdkBehaviorSettings.empty()) { - _processSDKBehaviorSettings(_configuration->sdkBehaviorSettings); + _onSBSChanged(_processSDKBehaviorSettings(_configuration->sdkBehaviorSettings)); + _logger->log(LogLevel::INFO, "[ConfigurationModule] _initializeSBSFromStorage, initialized SDK behavior settings from configuration."); } } - void _processSDKBehaviorSettings(const std::string &settings) { + nlohmann::json _processSDKBehaviorSettings(const std::string &settings) { try { nlohmann::json sbs_json = nlohmann::json::parse(settings); sanitizeConfig(sbs_json); sdk_behavior_settings = sbs_json; _logger->log(LogLevel::INFO, "[ConfigurationModule] _processSDKBehaviorSettings, SDK config:\n" + sdk_behavior_settings.dump(2)); - populateConfigValues(); + return _populateConfigValues(); } catch (const nlohmann::json::parse_error &e) { _logger->log(LogLevel::ERROR, "[ConfigurationModule] _processSDKBehaviorSettings, Failed to parse SDK behavior settings: " + std::string(e.what())); + return nlohmann::json{}; } } - void populateConfigValues(bool fromStorage = false) { // from storage means, we do not send disable location request, send it after fetching from server - // get values here + void _onSBSChanged(const nlohmann::json &changedSettings, const nlohmann::json &session_params = nullptr) { + if (_configuration->sdkBehaviorSettingsUpdatesDisabled != true && changedSettings.contains(KEY_SERVER_CONFIG_UPDATE_INTERVAL)) { + // restart timer with new interval + _stopTimer(); + _startTimer(session_params); + } + + if (changedSettings.contains(KEY_LOCATION_TRACKING) && changedSettings[KEY_LOCATION_TRACKING] == false) { + // disable location + _cly->RecordLocation("", "", "", ""); + } + } + + nlohmann::json _populateConfigValues() { bool trackingEnabledVal = trackingEnabled.load(std::memory_order_acquire); bool networkingEnabledVal = networkingEnabled.load(std::memory_order_acquire); bool sessionTrackingEnabledVal = sessionTrackingEnabled.load(std::memory_order_acquire); @@ -128,13 +144,10 @@ class ConfigurationModule::ConfigurationModuleImpl { bool locationTrackingEnabledVal = locationTrackingEnabled.load(std::memory_order_acquire); bool customEventTrackingEnabledVal = customEventTrackingEnabled.load(std::memory_order_acquire); bool crashReportingEnabledVal = crashReportingEnabled.load(std::memory_order_acquire); - bool locationTrackingCurrent = getBool(KEY_LOCATION_TRACKING, locationTrackingEnabledVal); + int unsigned serverConfigUpdateIntervalVal = serverConfigUpdateInterval.load(std::memory_order_acquire); - // area under is for behavior changes, did not creata new function for them cuz we have only on feature that should behave after - if (fromStorage == false && locationTrackingEnabledVal == true && locationTrackingCurrent == false) { - // disable location - _cly->RecordLocation("", "", "", ""); - } + bool locationTrackingCurrent = getBool(KEY_LOCATION_TRACKING, locationTrackingEnabledVal); + int unsigned serverConfigUpdateIntervalCurrent = getUInt(KEY_SERVER_CONFIG_UPDATE_INTERVAL, serverConfigUpdateIntervalVal); trackingEnabled.store(getBool(KEY_TRACKING, trackingEnabledVal), std::memory_order_release); networkingEnabled.store(getBool(KEY_NETWORKING, networkingEnabledVal), std::memory_order_release); @@ -146,6 +159,16 @@ class ConfigurationModule::ConfigurationModuleImpl { eventQueueThreshold.store(getUInt(KEY_EVENT_QUEUE_SIZE, _configuration->eventQueueThreshold), std::memory_order_release); requestQueueSizeLimit.store(getUInt(KEY_REQ_QUEUE_SIZE, _configuration->requestQueueThreshold), std::memory_order_release); sessionUpdateInterval.store(getUInt(KEY_SESSION_UPDATE_INTERVAL, _configuration->sessionDuration), std::memory_order_release); + serverConfigUpdateInterval.store(getUInt(KEY_SERVER_CONFIG_UPDATE_INTERVAL, 4), std::memory_order_release); + + nlohmann::json changedSettings; + if (locationTrackingCurrent != locationTrackingEnabledVal) { + changedSettings[KEY_LOCATION_TRACKING] = locationTrackingCurrent; + } + if (serverConfigUpdateIntervalCurrent != serverConfigUpdateIntervalVal) { + changedSettings[KEY_SERVER_CONFIG_UPDATE_INTERVAL] = serverConfigUpdateIntervalCurrent; + } + return changedSettings; } void sanitizeConfig(nlohmann::json &c) { From 70cecc0eaa64010e09b45abf6d467c0c1e26f6b5 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Mon, 12 Jan 2026 16:38:49 +0300 Subject: [PATCH 37/46] feat: add missing session params for update --- src/configuration_module.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/configuration_module.cpp b/src/configuration_module.cpp index 7de3177..077726e 100644 --- a/src/configuration_module.cpp +++ b/src/configuration_module.cpp @@ -86,14 +86,14 @@ class ConfigurationModule::ConfigurationModuleImpl { std::shared_ptr mutex) : _configuration(config), _logger(logger), _requestBuilder(requestBuilder), _storageModule(storageModule), _requestModule(requestModule), _mutex(mutex), _cly(cly) {} - void _fetchConfigFromServerHTTP(const std::map &data) { + void _fetchConfigFromServerHTTP(const std::map &data, const nlohmann::json &session_params) { HTTPResponse response = _requestModule->sendHTTP("/o/sdk", _requestBuilder->serializeData(data)); if (response.success && response.data.is_object() && response.data.contains(KEY_CONFIG)) { sanitizeConfig(response.data[KEY_CONFIG]); sdk_behavior_settings = response.data[KEY_CONFIG]; _storageModule->storeSDKBehaviorSettings(sdk_behavior_settings.dump()); _logger->log(LogLevel::INFO, "[ConfigurationModule] _fetchConfigFromServerHTTP, SDK config:\n" + sdk_behavior_settings.dump(2)); - _onSBSChanged(_populateConfigValues()); + _onSBSChanged(_populateConfigValues(), session_params); } else { _logger->log(LogLevel::WARNING, cly::utils::format_string("[ConfigurationModule] _fetchConfigFromServerHTTP, failed to fetch response_success: [%s]", response.success ? "true" : "false")); } @@ -227,7 +227,7 @@ class ConfigurationModule::ConfigurationModuleImpl { _mutex->lock(); std::map data = {{"method", "sc"}, {"app_key", session_params["app_key"].get()}, {"device_id", session_params["device_id"].get()}}; _mutex->unlock(); - _fetchConfigFromServerHTTP(data); + _fetchConfigFromServerHTTP(data, session_params); lock.lock(); } @@ -321,7 +321,7 @@ void ConfigurationModule::fetchConfigFromServer(nlohmann::json session_params) { std::map data = {{"method", "sc"}, {"app_key", session_params["app_key"].get()}, {"device_id", session_params["device_id"].get()}}; impl->_mutex->unlock(); // Fetch SBS asynchronously - std::thread _thread(&ConfigurationModule::ConfigurationModuleImpl::_fetchConfigFromServerHTTP, impl.get(), data); + std::thread _thread(&ConfigurationModule::ConfigurationModuleImpl::_fetchConfigFromServerHTTP, impl.get(), data, session_params); _thread.detach(); } From 368b14273dd0022dcb15b7e6113e7a8adb022846 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Mon, 12 Jan 2026 16:43:58 +0300 Subject: [PATCH 38/46] feat: use networking --- src/countly.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/countly.cpp b/src/countly.cpp index 03dc288..3cd1a36 100644 --- a/src/countly.cpp +++ b/src/countly.cpp @@ -1286,6 +1286,11 @@ void Countly::enableRemoteConfig() { } void Countly::_fetchRemoteConfig(const std::map &data) { + if (configurationModule->isNetworkingEnabled() == false) { + log(LogLevel::ERROR, "[Countly] _fetchRemoteConfig, Error fetching remote config, networking is disabled in SBS"); + return; + } + HTTPResponse response = requestModule->sendHTTP("/o/sdk", requestBuilder->serializeData(data)); mutex->lock(); if (response.success) { @@ -1319,6 +1324,11 @@ nlohmann::json Countly::getRemoteConfigValue(const std::string &key) { } void Countly::_updateRemoteConfigWithSpecificValues(const std::map &data) { + if (configurationModule->isNetworkingEnabled() == false) { + log(LogLevel::ERROR, "[Countly] _updateRemoteConfigWithSpecificValues, Error fetching remote config, networking is disabled in SBS"); + return; + } + HTTPResponse response = requestModule->sendHTTP("/o/sdk", requestBuilder->serializeData(data)); mutex->lock(); if (response.success) { From e42921c87075310ac87b38159f9ec66cb8701300 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Wed, 14 Jan 2026 12:00:16 +0300 Subject: [PATCH 39/46] fix: crash tests --- tests/crash.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/tests/crash.cpp b/tests/crash.cpp index 2fd05ea..6efee90 100644 --- a/tests/crash.cpp +++ b/tests/crash.cpp @@ -13,16 +13,15 @@ using namespace test_utils; using namespace cly; -void validateCrashParams(const std::string &title, const std::string &stackTrace, const bool fatal, const std::string &breadCrumbs, const std::map &crashMetrics, const std::map &segmentation) { +void validateCrashParams(const std::string &title, const std::string &stackTrace, const bool fatal, const std::string &breadCrumbs, const std::map &crashMetrics, const std::map &segmentation, int idx = 0) { CHECK(!http_call_queue.empty()); - HTTPCall http_call = http_call_queue.front(); + HTTPCall http_call = http_call_queue.at(1 + idx); // not front anymore because sbs is 0 now long long timestamp = getUnixTimestamp(); long long timestampDiff = timestamp - std::stoll(http_call.data["timestamp"]); CHECK(http_call.data["app_key"] == COUNTLY_TEST_APP_KEY); CHECK(http_call.data["device_id"] == COUNTLY_TEST_DEVICE_ID); CHECK(timestampDiff >= 0); CHECK(timestampDiff <= 1000); - nlohmann::json c = nlohmann::json::parse(http_call.data["crash"]); CHECK(c["_name"].get() == title); CHECK(c["_error"].get() == stackTrace); @@ -41,12 +40,11 @@ void validateCrashParams(const std::string &title, const std::string &stackTrace CHECK(s[segment.first].get() == segment.second); } } - - http_call_queue.pop_front(); } TEST_CASE("crash unit tests") { clearSDK(); + http_call_queue.clear(); Countly &countly = Countly::getInstance(); countly.setHTTPClient(test_utils::fakeSendHTTP); @@ -122,6 +120,6 @@ TEST_CASE("crash unit tests") { countly.processRQDebug(); // validate crash request - validateCrashParams("Divided By Zero", "stackTrack", true, "first\nsecond\n", crashMetrics, segmentation); + validateCrashParams("Divided By Zero", "stackTrack", true, "first\nsecond\n", crashMetrics, segmentation, 1); } } From 07848d57929c9416aec5bcd72db51b2d3a53f6c9 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Wed, 14 Jan 2026 12:00:51 +0300 Subject: [PATCH 40/46] fix: init config params via configuration --- src/configuration_module.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/configuration_module.cpp b/src/configuration_module.cpp index 077726e..23450d8 100644 --- a/src/configuration_module.cpp +++ b/src/configuration_module.cpp @@ -100,6 +100,7 @@ class ConfigurationModule::ConfigurationModuleImpl { } void _initializeSBSFromStorage() { + _initializeConfigParameters(); std::string sbs_string = _storageModule->getSDKBehaviorSettings(); if (!sbs_string.empty()) { _processSDKBehaviorSettings(sbs_string); @@ -110,6 +111,12 @@ class ConfigurationModule::ConfigurationModuleImpl { } } + void _initializeConfigParameters() { + eventQueueThreshold.store(_configuration->eventQueueThreshold, std::memory_order_release); + requestQueueSizeLimit.store(_configuration->requestQueueThreshold, std::memory_order_release); + sessionUpdateInterval.store(_configuration->sessionDuration, std::memory_order_release); + } + nlohmann::json _processSDKBehaviorSettings(const std::string &settings) { try { nlohmann::json sbs_json = nlohmann::json::parse(settings); @@ -234,11 +241,6 @@ class ConfigurationModule::ConfigurationModuleImpl { } void _stopTimer() { - if (_configuration->sdkBehaviorSettingsUpdatesDisabled) { - _logger->log(LogLevel::INFO, "[ConfigurationModule] _stopTimer, SDK behavior settings updates are disabled."); - return; - } - _logger->log(LogLevel::WARNING, "[ConfigurationModule] stopTimer, stopping server config update timer thread."); stopConfigThread.store(true, std::memory_order_release); configUpdateCv.notify_all(); @@ -333,8 +335,6 @@ void ConfigurationModule::fetchConfigFromStorage() { void ConfigurationModule::startServerConfigUpdateTimer(nlohmann::json session_params) { impl->_startTimer(session_params); } -void ConfigurationModule::stopTimer() { impl->_stopTimer(); } - bool ConfigurationModule::isTrackingEnabled() const { return impl->trackingEnabled.load(std::memory_order_acquire); } bool ConfigurationModule::isNetworkingEnabled() const { return impl->networkingEnabled.load(std::memory_order_acquire); } From 585fa29eeaa3c111aeeca159a6a4021976d1ea9a Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Wed, 14 Jan 2026 12:01:50 +0300 Subject: [PATCH 41/46] fix: prevent double reset of conf module --- src/countly.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/countly.cpp b/src/countly.cpp index 3cd1a36..94b74b4 100644 --- a/src/countly.cpp +++ b/src/countly.cpp @@ -31,6 +31,7 @@ Countly::~Countly() { stop(); crash_module.reset(); views_module.reset(); + configurationModule.reset(); logger.reset(); } @@ -512,7 +513,6 @@ void Countly::startOnCloud(const std::string &app_key) { } void Countly::stop() { - configurationModule->stopTimer(); _deleteThread(); if (configuration->manualSessionControl == false) { endSession(); From 126b5d2a615381defe84ecd1b0fc76f7b550b579 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Wed, 14 Jan 2026 12:02:13 +0300 Subject: [PATCH 42/46] fix: prevent eq size getter to continue before init --- src/countly.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/countly.cpp b/src/countly.cpp index 94b74b4..02e815c 100644 --- a/src/countly.cpp +++ b/src/countly.cpp @@ -560,6 +560,10 @@ void Countly::addEvent(const cly::Event &event) { void Countly::checkAndSendEventToRQ() { nlohmann::json events = nlohmann::json::array(); int queueSize = checkEQSize(); + // if queue size could not be get return early + if (queueSize < 0) { + return; + } mutex->lock(); #ifdef COUNTLY_USE_SQLITE if (queueSize >= configurationModule->getEventQueueSizeLimit()) { From d61368f63c4ddcacc446f29376fe5670d3d726e9 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Wed, 14 Jan 2026 12:03:18 +0300 Subject: [PATCH 43/46] fix: if is being disposed let endSession continue even if session tracking disabled --- src/countly.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/countly.cpp b/src/countly.cpp index 02e815c..458a234 100644 --- a/src/countly.cpp +++ b/src/countly.cpp @@ -925,7 +925,7 @@ void Countly::sendEventsToRQ(const nlohmann::json &events) { bool Countly::endSession() { log(LogLevel::INFO, "[Countly][endSession]"); - if (configurationModule->isSessionTrackingEnabled() == false) { + if (is_being_disposed == false && configurationModule->isSessionTrackingEnabled() == false) { log(LogLevel::ERROR, "[Countly][endSession] Session tracking is disabled in server configuration, can not end session."); mutex->unlock(); return false; From 85d54f9969d690ef2e03dd4edd17d3fed51744c2 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Wed, 14 Jan 2026 13:44:33 +0300 Subject: [PATCH 44/46] fix: eqsize getter of conf module, omit post-init change --- src/configuration_module.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/configuration_module.cpp b/src/configuration_module.cpp index 23450d8..7c76b86 100644 --- a/src/configuration_module.cpp +++ b/src/configuration_module.cpp @@ -54,7 +54,6 @@ static constexpr const char *KEY_BOM_DURATION = "bom_d"; class ConfigurationModule::ConfigurationModuleImpl { private: - std::shared_ptr _configuration; std::shared_ptr _requestBuilder; std::shared_ptr _storageModule; std::shared_ptr _requestModule; @@ -64,6 +63,7 @@ class ConfigurationModule::ConfigurationModuleImpl { public: std::shared_ptr _logger; std::shared_ptr _mutex; + std::shared_ptr _configuration; std::atomic stopConfigThread{false}; std::thread configUpdateThread; @@ -112,7 +112,6 @@ class ConfigurationModule::ConfigurationModuleImpl { } void _initializeConfigParameters() { - eventQueueThreshold.store(_configuration->eventQueueThreshold, std::memory_order_release); requestQueueSizeLimit.store(_configuration->requestQueueThreshold, std::memory_order_release); sessionUpdateInterval.store(_configuration->sessionDuration, std::memory_order_release); } @@ -163,7 +162,7 @@ class ConfigurationModule::ConfigurationModuleImpl { locationTrackingEnabled.store(locationTrackingCurrent, std::memory_order_release); customEventTrackingEnabled.store(getBool(KEY_CUSTOM_EVENT_TRACKING, customEventTrackingEnabledVal), std::memory_order_release); crashReportingEnabled.store(getBool(KEY_CRASH_REPORTING, crashReportingEnabledVal), std::memory_order_release); - eventQueueThreshold.store(getUInt(KEY_EVENT_QUEUE_SIZE, _configuration->eventQueueThreshold), std::memory_order_release); + eventQueueThreshold.store(getUInt(KEY_EVENT_QUEUE_SIZE, 0), std::memory_order_release); requestQueueSizeLimit.store(getUInt(KEY_REQ_QUEUE_SIZE, _configuration->requestQueueThreshold), std::memory_order_release); sessionUpdateInterval.store(getUInt(KEY_SESSION_UPDATE_INTERVAL, _configuration->sessionDuration), std::memory_order_release); serverConfigUpdateInterval.store(getUInt(KEY_SERVER_CONFIG_UPDATE_INTERVAL, 4), std::memory_order_release); @@ -351,7 +350,12 @@ bool ConfigurationModule::isCrashReportingEnabled() const { return impl->crashRe unsigned int ConfigurationModule::getRequestQueueSizeLimit() const { return impl->requestQueueSizeLimit.load(std::memory_order_acquire); } -unsigned int ConfigurationModule::getEventQueueSizeLimit() { return impl->eventQueueThreshold.load(std::memory_order_acquire); } +unsigned int ConfigurationModule::getEventQueueSizeLimit() { + // this is because we permit EQ size to change after initialization + unsigned int value = impl->eventQueueThreshold.load(std::memory_order_acquire); + return value == 0 ? impl->_configuration->eventQueueThreshold : value; + +} unsigned int ConfigurationModule::getSessionUpdateInterval() { return impl->sessionUpdateInterval.load(std::memory_order_acquire); } // namespace cly From e37f20043534e028df5b4db3c28699c27e6beef2 Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Wed, 14 Jan 2026 13:44:56 +0300 Subject: [PATCH 45/46] fix: tests --- tests/event_queue.cpp | 1 + tests/request.cpp | 6 ++++++ tests/session.cpp | 3 ++- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/event_queue.cpp b/tests/event_queue.cpp index 44b7d7e..014701c 100644 --- a/tests/event_queue.cpp +++ b/tests/event_queue.cpp @@ -104,6 +104,7 @@ TEST_CASE("Tests setting 'setEventsToRQThreshold' before we start the SDK") { TEST_CASE("Tests setting 'setEventsToRQThreshold' after we start the SDK") { clearSDK(); + http_call_queue.clear(); Countly &countly = Countly::getInstance(); SUBCASE("Custom threshold size should be used instead of the default one") { diff --git a/tests/request.cpp b/tests/request.cpp index d88c745..23e153f 100644 --- a/tests/request.cpp +++ b/tests/request.cpp @@ -59,8 +59,11 @@ TEST_CASE("Test Request Module with Memory Storage") { std::shared_ptr storageModule = std::make_shared(configuration, logger); std::shared_ptr requestBuilder = std::make_shared(configuration, logger); std::shared_ptr requestModule = std::make_shared(configuration, logger, requestBuilder, storageModule); + std::shared_ptr configurationModule = std::make_shared(nullptr, configuration, logger, requestBuilder, storageModule, requestModule, std::make_shared()); + requestModule->setConfigurationProvider(configurationModule); storageModule->init(); + configurationModule->fetchConfigFromStorage(); SUBCASE("Validate request queue threshold") { ValidateRequestSizeOnReachingThresholdLimit(storageModule, requestModule); } } @@ -78,8 +81,11 @@ TEST_CASE("Test Request Module with SQLite Storage") { std::shared_ptr storageModule = std::make_shared(configuration, logger); std::shared_ptr requestBuilder = std::make_shared(configuration, logger); std::shared_ptr requestModule = std::make_shared(configuration, logger, requestBuilder, storageModule); + std::shared_ptr configurationModule = std::make_shared(nullptr, configuration, logger, requestBuilder, storageModule, requestModule, std::make_shared()); + requestModule->setConfigurationProvider(configurationModule); storageModule->init(); + configurationModule->fetchConfigFromStorage(); SUBCASE("Validate request queue threshold") { ValidateRequestSizeOnReachingThresholdLimit(storageModule, requestModule); } } diff --git a/tests/session.cpp b/tests/session.cpp index b1275bc..92862c9 100644 --- a/tests/session.cpp +++ b/tests/session.cpp @@ -33,7 +33,8 @@ TEST_CASE("sessions unit tests") { SUBCASE("init sdk - session begin ") { countly.processRQDebug(); - HTTPCall http_call = popHTTPCall(); + HTTPCall http_call = popHTTPCall(); // first request is SBS + http_call = popHTTPCall(); // second request is session begin long long timestamp = getUnixTimestamp(); long long timestampDiff = timestamp - stoll(http_call.data["timestamp"]); CHECK(http_call.data["app_key"] == COUNTLY_TEST_APP_KEY); From 81bb314ca5a6001a924c27e6887e98e908a5b3af Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray Date: Wed, 14 Jan 2026 13:46:00 +0300 Subject: [PATCH 46/46] fix: ASAN bug --- src/storage_module_db.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/storage_module_db.cpp b/src/storage_module_db.cpp index c0dc435..ee927e6 100644 --- a/src/storage_module_db.cpp +++ b/src/storage_module_db.cpp @@ -178,7 +178,7 @@ void StorageModuleDB::RQRemoveFront(std::shared_ptr request) { } // Log the request ID being removed - _logger->log(LogLevel::DEBUG, "[Countly][StorageModuleDB] RQRemoveFront RequestID = " + request->getId()); + _logger->log(LogLevel::DEBUG, "[Countly][StorageModuleDB] RQRemoveFront RequestID = " + std::to_string(request->getId())); #ifdef COUNTLY_USE_SQLITE sqlite3 *database;