From 79442fcbf8d366c8ebeb4f2ae975661c9d485bf0 Mon Sep 17 00:00:00 2001 From: Nikola Garabandic Date: Wed, 7 Aug 2024 16:51:13 +0200 Subject: [PATCH 1/2] Fixing countly not sending events properly, causing them sometimes to pile up and cause the big stagnation in initializing the service. --- .../Countly SDK/Plugins/CountlySDK/Countly.cs | 2 +- .../Helpers/CountlyMainThreadHandler.cs | 64 ++++++ .../Helpers/CountlyMainThreadHandler.cs.meta | 11 + .../Helpers/RequestCountlyHelper.cs | 207 +++++++++--------- .../Services/SessionCountlyService.cs | 164 ++++++++------ 5 files changed, 274 insertions(+), 174 deletions(-) create mode 100644 Packages/io.chainsafe.web3-unity/Runtime/Plugins/Countly SDK/Plugins/CountlySDK/Helpers/CountlyMainThreadHandler.cs create mode 100644 Packages/io.chainsafe.web3-unity/Runtime/Plugins/Countly SDK/Plugins/CountlySDK/Helpers/CountlyMainThreadHandler.cs.meta diff --git a/Packages/io.chainsafe.web3-unity/Runtime/Plugins/Countly SDK/Plugins/CountlySDK/Countly.cs b/Packages/io.chainsafe.web3-unity/Runtime/Plugins/Countly SDK/Plugins/CountlySDK/Countly.cs index d26495fe5..27f34c1e0 100644 --- a/Packages/io.chainsafe.web3-unity/Runtime/Plugins/Countly SDK/Plugins/CountlySDK/Countly.cs +++ b/Packages/io.chainsafe.web3-unity/Runtime/Plugins/Countly SDK/Plugins/CountlySDK/Countly.cs @@ -343,7 +343,7 @@ private void Init(RequestBuilder requestBuilder, RequestRepository requestRepo, Notifications = new NotificationsCallbackService(Configuration, _logHelper); ProxyNotificationsService notificationsService = new ProxyNotificationsService(transform, Configuration, _logHelper, InternalStartCoroutine, Events); _push = new PushCountlyService(Configuration, _logHelper, RequestHelper, notificationsService, Notifications, Consents); - Session = new SessionCountlyService(Configuration, _logHelper, Events, RequestHelper, Location, Consents); + Session = new SessionCountlyService(Configuration, _logHelper, Events, RequestHelper, Location, Consents, this); CrashReports = new CrashReportsCountlyService(Configuration, _logHelper, RequestHelper, Consents); Initialization = new InitializationCountlyService(Configuration, _logHelper, Location, Session, Consents); diff --git a/Packages/io.chainsafe.web3-unity/Runtime/Plugins/Countly SDK/Plugins/CountlySDK/Helpers/CountlyMainThreadHandler.cs b/Packages/io.chainsafe.web3-unity/Runtime/Plugins/Countly SDK/Plugins/CountlySDK/Helpers/CountlyMainThreadHandler.cs new file mode 100644 index 000000000..649209880 --- /dev/null +++ b/Packages/io.chainsafe.web3-unity/Runtime/Plugins/Countly SDK/Plugins/CountlySDK/Helpers/CountlyMainThreadHandler.cs @@ -0,0 +1,64 @@ +using System; +using System.Threading; +using Plugins.CountlySDK; +using UnityEngine; + +public class CountlyMainThreadHandler : MonoBehaviour +{ + private static CountlyMainThreadHandler _instance; + private Thread mainThread; + private Action _queuedAction; + private readonly object lockObject = new object(); // For thread safety + + public static CountlyMainThreadHandler Instance + { + get { + if (_instance == null) { + // If instance is null, add this script to the created Countly object + GameObject gameObject = Countly.Instance.gameObject; + _instance = gameObject.AddComponent(); + } + return _instance; + } + internal set { + // Allow internal setting of the instance (used during cleanup) + _instance = value; + } + } + + private void Awake() + { + // Record the main thread when the script is first initialized + mainThread = Thread.CurrentThread; + } + + public bool IsMainThread() + { + return Thread.CurrentThread.ManagedThreadId == mainThread.ManagedThreadId; + } + + public void RunOnMainThread(Action action) + { + // Check if we are on the main thread + if (IsMainThread()) { + // If on the main thread, invoke the action immediately + action.Invoke(); + } else { + // If on a different thread, queue the action to be executed on the main thread + lock (lockObject) { + _queuedAction = action; + } + } + } + + private void Update() + { + // Execute any queued action on the main thread during the Unity Update phase + if (_queuedAction != null) { + lock (lockObject) { + _queuedAction.Invoke(); + _queuedAction = null; + } + } + } +} \ No newline at end of file diff --git a/Packages/io.chainsafe.web3-unity/Runtime/Plugins/Countly SDK/Plugins/CountlySDK/Helpers/CountlyMainThreadHandler.cs.meta b/Packages/io.chainsafe.web3-unity/Runtime/Plugins/Countly SDK/Plugins/CountlySDK/Helpers/CountlyMainThreadHandler.cs.meta new file mode 100644 index 000000000..715f8837e --- /dev/null +++ b/Packages/io.chainsafe.web3-unity/Runtime/Plugins/Countly SDK/Plugins/CountlySDK/Helpers/CountlyMainThreadHandler.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 65c548d701c912641b9790aa9face003 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/io.chainsafe.web3-unity/Runtime/Plugins/Countly SDK/Plugins/CountlySDK/Helpers/RequestCountlyHelper.cs b/Packages/io.chainsafe.web3-unity/Runtime/Plugins/Countly SDK/Plugins/CountlySDK/Helpers/RequestCountlyHelper.cs index 89f1cf2da..e88c34b0c 100644 --- a/Packages/io.chainsafe.web3-unity/Runtime/Plugins/Countly SDK/Plugins/CountlySDK/Helpers/RequestCountlyHelper.cs +++ b/Packages/io.chainsafe.web3-unity/Runtime/Plugins/Countly SDK/Plugins/CountlySDK/Helpers/RequestCountlyHelper.cs @@ -19,14 +19,13 @@ namespace Plugins.CountlySDK.Helpers public class RequestCountlyHelper { private bool _isQueueBeingProcess; - private readonly CountlyLogHelper Log; private readonly CountlyUtils _countlyUtils; private readonly CountlyConfiguration _config; private readonly RequestBuilder _requestBuilder; internal readonly RequestRepository _requestRepo; - private readonly MonoBehaviour _monoBehaviour; + private readonly int countlyRequestRemoveLimit = 100; internal RequestCountlyHelper(CountlyConfiguration config, CountlyLogHelper log, CountlyUtils countlyUtils, RequestBuilder requestBuilder, RequestRepository requestRepo, MonoBehaviour monoBehaviour) { @@ -38,21 +37,30 @@ internal RequestCountlyHelper(CountlyConfiguration config, CountlyLogHelper log, _monoBehaviour = monoBehaviour; } + /// + /// Adds a Countly request to the queue, ensuring that the queue size does not exceed the maximum limit. + /// If the queue size exceeds the limit, it removes the oldest requests to make space for the new one. + /// + /// The Countly request model to be added to the queue. internal void AddRequestToQueue(CountlyRequestModel request) { - Log.Verbose("[RequestCountlyHelper] AddRequestToQueue: " + request.ToString()); + Log.Verbose($"[RequestCountlyHelper] AddRequestToQueue, Request: [{request.ToString()}]"); - if (_config.EnableTestMode) - { + if (_config.EnableTestMode) { return; } - if (_requestRepo.Count == _config.StoredRequestLimit) - { - + if (_requestRepo.Count >= _config.StoredRequestLimit) { + // Calculate how many items need to be removed from the queue to accommodate the new request. + int exceedAmount = _requestRepo.Count - _config.StoredRequestLimit; + int removeAmount = Mathf.Min(exceedAmount, countlyRequestRemoveLimit) + 1; Log.Warning("[RequestCountlyHelper] Request Queue is full. Dropping the oldest request."); - _requestRepo.Dequeue(); + // Remove the calculated amount of oldest requests from the queue. + while (removeAmount > 0) { + _requestRepo.Dequeue(); + removeAmount--; + } } _requestRepo.Enqueue(request); @@ -64,26 +72,39 @@ internal void AddRequestToQueue(CountlyRequestModel request) /// internal async Task ProcessQueue() { - if (_isQueueBeingProcess) - { + if (_isQueueBeingProcess) { + Log.Verbose("[RequestCountlyHelper] ProcessQueue, queue is being processed."); return; } _isQueueBeingProcess = true; - CountlyRequestModel[] requests = _requestRepo.Models.ToArray(); + CountlyRequestModel[] requests = null; + + try { + requests = _requestRepo.Models.ToArray(); + Log.Verbose($"[RequestCountlyHelper] ProcessQueue, request count: [{requests.Length}]"); + } catch (Exception ex) { + Log.Warning($"[RequestCountlyHelper] ProcessQueue, exception occurred while converting Models to array. Exception: {ex}"); + _isQueueBeingProcess = false; + return; + } - Log.Verbose("[RequestCountlyHelper] Process queue, requests: " + requests.Length); + // make sure that flag is corrected and exit + if (requests.Length == 0) { + Log.Verbose("[RequestCountlyHelper] ProcessQueue, Request Queue is empty. Returning."); + _isQueueBeingProcess = false; + return; + } - foreach (CountlyRequestModel reqModel in requests) - { - //add the remaining request count in RequestData + foreach (CountlyRequestModel reqModel in requests) { + // Add the remaining request count in RequestData reqModel.RequestData += "&rr=" + (requests.Length - 1); CountlyResponse response = await ProcessRequest(reqModel); - if (!response.IsSuccess) - { - Log.Verbose("[RequestCountlyHelper] ProcessQueue: Request fail, " + response.ToString()); + if (!response.IsSuccess) { + Log.Verbose($"[RequestCountlyHelper] ProcessQueue: Request failed. Response: [{response.ToString()}]"); + _isQueueBeingProcess = false; break; } @@ -91,6 +112,7 @@ internal async Task ProcessQueue() } _isQueueBeingProcess = false; + Log.Verbose("[RequestCountlyHelper] ProcessQueue, _isQueueBeingProcess is false"); } /// @@ -98,22 +120,25 @@ internal async Task ProcessQueue() /// private async Task ProcessRequest(CountlyRequestModel model) { - Log.Verbose("[RequestCountlyHelper] Process request, request: " + model); + Log.Verbose($"[RequestCountlyHelper] Process request, request: [{model}]"); bool shouldPost = _config.EnablePost || model.RequestData.Length > 2000; #if UNITY_WEBGL + // There is not HTTP GET for WebGL. We always do a HTTP POST + Log.Debug($"[RequestCountlyHelper] ProcessRequest, Calling StartProcessRequestRoutine for WebGL"); return await StartProcessRequestRoutine(_countlyUtils.ServerInputUrl, model.RequestData); #else - if (shouldPost) - { + if (shouldPost) { + Log.Debug($"[RequestCountlyHelper] ProcessRequest, Using HTTP POST"); return await Task.Run(() => PostAsync(_countlyUtils.ServerInputUrl, model.RequestData)); } + Log.Debug($"[RequestCountlyHelper] ProcessRequest, Using HTTP GET"); return await Task.Run(() => GetAsync(_countlyUtils.ServerInputUrl, model.RequestData)); #endif } /// - /// An internal function to add a request to request queue. + /// An internal function to add a request to request queue. /// internal void AddToRequestQueue(Dictionary queryParams) { @@ -124,16 +149,14 @@ internal void AddToRequestQueue(Dictionary queryParams) private string AddChecksum(string query) { - if (!string.IsNullOrEmpty(_config.Salt)) - { + if (!string.IsNullOrEmpty(_config.Salt)) { // Create a SHA256 - using (SHA256 sha256Hash = SHA256.Create()) - { + using (SHA256 sha256Hash = SHA256.Create()) { byte[] bytes = sha256Hash.ComputeHash(Encoding.UTF8.GetBytes(query + _config.Salt)); string hex = _countlyUtils.GetStringFromBytes(bytes); query += "&checksum256=" + hex; - Log.Debug("BuildGetRequest: query = " + query); + Log.Debug($"[RequestCountlyHelper] AddChecksum, Checksum added query = [{query}]"); } } @@ -141,77 +164,62 @@ private string AddChecksum(string query) } /// - /// Makes an Asynchronous GET request to the Countly server. + /// Makes an Asynchronous GET request to the Countly server. /// - /// - /// + /// + /// /// internal async Task GetAsync(string uri, string data) { - Log.Verbose("[RequestCountlyHelper] GetAsync request: " + uri + " params: " + data); + Log.Verbose($"[RequestCountlyHelper] GetAsync, calling with request: [{uri}], params: [{data}]"); CountlyResponse countlyResponse = new CountlyResponse(); string query = AddChecksum(data); string url = uri + query; - try - { + try { HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url); - using (HttpWebResponse response = (HttpWebResponse)await request.GetResponseAsync()) - { + using (HttpWebResponse response = (HttpWebResponse)await request.GetResponseAsync()) { int code = (int)response.StatusCode; using (Stream stream = response.GetResponseStream()) - using (StreamReader reader = new StreamReader(stream)) - { + using (StreamReader reader = new StreamReader(stream)) { string res = await reader.ReadToEndAsync(); - JObject body = JObject.Parse(res); - countlyResponse.Data = res; countlyResponse.StatusCode = code; countlyResponse.IsSuccess = IsSuccess(countlyResponse); } - } - } - catch (WebException ex) - { + } catch (WebException ex) { countlyResponse.ErrorMessage = ex.Message; - if (ex.Response != null) - { + if (ex.Response != null) { HttpWebResponse response = (HttpWebResponse)ex.Response; int code = (int)response.StatusCode; using (Stream stream = ex.Response.GetResponseStream()) - using (StreamReader reader = new StreamReader(stream)) - { + using (StreamReader reader = new StreamReader(stream)) { string res = await reader.ReadToEndAsync(); countlyResponse.StatusCode = code; countlyResponse.Data = res; countlyResponse.IsSuccess = IsSuccess(countlyResponse); } } - } - Log.Verbose("[RequestCountlyHelper] GetAsync request: " + url + " params: " + query + " response: " + countlyResponse.ToString()); - + Log.Verbose($"[RequestCountlyHelper] GetAsync, Request URL: [{url}], Params: [{query}], Response: [{countlyResponse.ToString()}]"); return countlyResponse; } /// - /// Makes an Asynchronous POST request to the Countly server. + /// Makes an Asynchronous POST request to the Countly server. /// /// /// - /// - /// /// internal async Task PostAsync(string uri, string data) { CountlyResponse countlyResponse = new CountlyResponse(); - try - { + try { string query = AddChecksum(data); byte[] dataBytes = Encoding.ASCII.GetBytes(query); @@ -220,67 +228,68 @@ internal async Task PostAsync(string uri, string data) request.ContentType = "application/x-www-form-urlencoded"; request.Method = "POST"; - - using (Stream requestBody = request.GetRequestStream()) - { + using (Stream requestBody = request.GetRequestStream()) { await requestBody.WriteAsync(dataBytes, 0, dataBytes.Length); } - using (HttpWebResponse response = (HttpWebResponse)await request.GetResponseAsync()) - { + using (HttpWebResponse response = (HttpWebResponse)await request.GetResponseAsync()) { int code = (int)response.StatusCode; using (Stream stream = response.GetResponseStream()) - using (StreamReader reader = new StreamReader(stream)) - { + using (StreamReader reader = new StreamReader(stream)) { string res = await reader.ReadToEndAsync(); - JObject body = JObject.Parse(res); - countlyResponse.Data = res; countlyResponse.StatusCode = code; countlyResponse.IsSuccess = IsSuccess(countlyResponse); } } - } - catch (WebException ex) - { + } catch (WebException ex) { countlyResponse.ErrorMessage = ex.Message; - if (ex.Response != null) - { + if (ex.Response != null) { HttpWebResponse response = (HttpWebResponse)ex.Response; int code = (int)response.StatusCode; using (Stream stream = ex.Response.GetResponseStream()) - using (StreamReader reader = new StreamReader(stream)) - { + using (StreamReader reader = new StreamReader(stream)) { string res = await reader.ReadToEndAsync(); countlyResponse.StatusCode = code; countlyResponse.Data = res; countlyResponse.IsSuccess = IsSuccess(countlyResponse); } } - } - Log.Verbose("[RequestCountlyHelper] PostAsync request: " + uri + " body: " + data + " response: " + countlyResponse.ToString()); - + Log.Verbose($"[RequestCountlyHelper] PostAsync, Request URL: [{uri}], Body: [{data}], Response: [{countlyResponse.ToString()}]"); return countlyResponse; } /// /// Asynchronously initiates a request routine, combining URL and data, and starts a coroutine to process the request with checksum validation. /// - /// /// /// /// private Task StartProcessRequestRoutine(string uri, string data) { + Log.Debug($"[RequestCountlyHelper] StartProcessRequestRoutine, Start"); TaskCompletionSource tcs = new TaskCompletionSource(); - _monoBehaviour.StartCoroutine(ProcessRequestCoroutine(uri, data, (response) => - { - tcs.SetResult(response); - })); + try { + // 'IsObjectMonoBehaviour can only be called from the main thread' + // That's why we have to move it to main thread. + CountlyMainThreadHandler.Instance.RunOnMainThread(() => { + try { + _monoBehaviour.StartCoroutine(ProcessRequestCoroutine(uri, data, (response) => { + tcs.SetResult(response); + })); + } catch (Exception ex) { + Log.Error($"[RequestCountlyHelper] StartProcessRequestRoutine, Exception occurred while starting coroutine. Exception: [{ex}]"); + tcs.SetException(ex); + } + }); + } catch (Exception ex) { + Log.Error($"[RequestCountlyHelper] StartProcessRequestRoutine, Exception occurred while queueing to main thread. Exception: [{ex}]"); + tcs.SetException(ex); + } return tcs.Task; } @@ -288,25 +297,21 @@ private Task StartProcessRequestRoutine(string uri, string data /// /// Processes a UnityWebRequest coroutine, handling response and errors, and invoking a callback with the CountlyResponse. /// - /// - /// + /// /// /// /// private IEnumerator ProcessRequestCoroutine(string uri, string data, Action callback) { + Log.Debug($"[RequestCountlyHelper] ProcessRequestCoroutine, Start"); CountlyResponse countlyResponse = new CountlyResponse(); string query = AddChecksum(data); string url = uri + query; - using (UnityWebRequest webRequest = UnityWebRequest.Put(url, data)) - { - + using (UnityWebRequest webRequest = UnityWebRequest.Put(url, data)) { webRequest.method = UnityWebRequest.kHttpVerbPOST; - yield return webRequest.SendWebRequest(); - string[] pages = url.Split('?'); int page = pages.Length - 1; int code = (int)webRequest.responseCode; @@ -315,33 +320,33 @@ private IEnumerator ProcessRequestCoroutine(string uri, string data, Action + /// Determines if a CountlyResponse indicates a successful request based on the HTTP status code and the presence of a "result" key in the JSON response data. + /// + /// The CountlyResponse object to evaluate. + /// True if the status code is between 200 and 299 and the response data contains a "result" key; otherwise, false. private bool IsSuccess(CountlyResponse countlyResponse) { - if (countlyResponse.StatusCode >= 200 && countlyResponse.StatusCode < 300) - { - try - { + if (countlyResponse.StatusCode >= 200 && countlyResponse.StatusCode < 300) { + try { JObject json = JObject.Parse(countlyResponse.Data); - if (json.ContainsKey("result")) - { + if (json.ContainsKey("result")) { return true; } - } - catch (JsonException) - { - Log.Debug("[RequestCountlyHelper] IsSuccess : Returned request is not a JSON object"); + } catch (JsonException ex) { + Log.Debug($"[RequestCountlyHelper] IsSuccess : Returned request is not a JSON object. Exception: [{ex}]"); return false; } + } else { + Log.Debug($"[RequestCountlyHelper] IsSuccess, Status Code: [{countlyResponse.StatusCode}] is not in between 200-299. Returning false."); } - return false; } } diff --git a/Packages/io.chainsafe.web3-unity/Runtime/Plugins/Countly SDK/Plugins/CountlySDK/Services/SessionCountlyService.cs b/Packages/io.chainsafe.web3-unity/Runtime/Plugins/Countly SDK/Plugins/CountlySDK/Services/SessionCountlyService.cs index 36572cf4b..d9fdadd93 100644 --- a/Packages/io.chainsafe.web3-unity/Runtime/Plugins/Countly SDK/Plugins/CountlySDK/Services/SessionCountlyService.cs +++ b/Packages/io.chainsafe.web3-unity/Runtime/Plugins/Countly SDK/Plugins/CountlySDK/Services/SessionCountlyService.cs @@ -1,11 +1,12 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Threading.Tasks; using System.Timers; -using Newtonsoft.Json; using Plugins.CountlySDK.Enums; using Plugins.CountlySDK.Helpers; using Plugins.CountlySDK.Models; +using UnityEngine; namespace Plugins.CountlySDK.Services { @@ -19,26 +20,26 @@ public class SessionCountlyService : AbstractBaseService /// /// bool internal bool IsSessionInitiated { get; private set; } - private readonly LocationService _locationService; private readonly EventCountlyService _eventService; internal readonly RequestCountlyHelper _requestCountlyHelper; + private readonly MonoBehaviour _monoBehaviour; + bool isInternalTimerStopped; internal SessionCountlyService(CountlyConfiguration configuration, CountlyLogHelper logHelper, EventCountlyService eventService, - RequestCountlyHelper requestCountlyHelper, LocationService locationService, ConsentCountlyService consentService) : base(configuration, logHelper, consentService) + RequestCountlyHelper requestCountlyHelper, LocationService locationService, ConsentCountlyService consentService, MonoBehaviour monoBehaviour) : base(configuration, logHelper, consentService) { Log.Debug("[SessionCountlyService] Initializing."); - if (configuration.IsAutomaticSessionTrackingDisabled) - { + if (configuration.IsAutomaticSessionTrackingDisabled) { Log.Debug("[SessionCountlyService] Disabling automatic session tracking"); } _eventService = eventService; _locationService = locationService; _requestCountlyHelper = requestCountlyHelper; + _monoBehaviour = monoBehaviour; - if (_configuration.IsAutomaticSessionTrackingDisabled) - { + if (_configuration.IsAutomaticSessionTrackingDisabled) { Log.Verbose("[Countly][CountlyConfiguration] Automatic session tracking disabled!"); } } @@ -48,25 +49,19 @@ internal SessionCountlyService(CountlyConfiguration configuration, CountlyLogHel /// internal async Task StartSessionService() { - if (_configuration.IsAutomaticSessionTrackingDisabled || !_consentService.CheckConsentInternal(Consents.Sessions)) - { + if (_configuration.IsAutomaticSessionTrackingDisabled || !_consentService.CheckConsentInternal(Consents.Sessions)) { /* If location is disabled in init and no session consent is given. Send empty location as separate request.*/ - if (_locationService.IsLocationDisabled || !_consentService.CheckConsentInternal(Consents.Location)) - { + if (_locationService.IsLocationDisabled || !_consentService.CheckConsentInternal(Consents.Location)) { await _locationService.SendRequestWithEmptyLocation(); - } - else - { + } else { /* * If there is no session consent or automatic session tracking is disabled, * location values set in init should be sent as a separate location request. */ await _locationService.SendIndependantLocationRequest(); } - } - else - { + } else { //Start Session await BeginSessionAsync(); } @@ -79,10 +74,60 @@ and no session consent is given. Send empty location as separate request.*/ /// private void InitSessionTimer() { - _sessionTimer = new Timer { Interval = _configuration.SessionDuration * 1000 }; +#if UNITY_WEBGL + _monoBehaviour.StartCoroutine(SessionTimerCoroutine()); +#else + _sessionTimer = new Timer { Interval = _configuration.GetUpdateSessionTimerDelay() * 1000 }; _sessionTimer.Elapsed += SessionTimerOnElapsedAsync; _sessionTimer.AutoReset = true; _sessionTimer.Start(); +#endif + } + + private void SendRequestsAndExtendSession() + { + //Countly.Instance.UserProfile.Save(); + _eventService.AddEventsToRequestQueue(); + _ = _requestCountlyHelper.ProcessQueue(); + + if (!_configuration.IsAutomaticSessionTrackingDisabled) { + _ = ExtendSessionAsync(); + } + } + + private IEnumerator SessionTimerCoroutine() + { + Log.Debug("[SessionCountlyService] SessionTimerCoroutine, Start"); + + if (isInternalTimerStopped) { + yield break; + } + + yield return new WaitForSeconds(_configuration.SessionDuration); + SendRequestsAndExtendSession(); + Log.Debug("[SessionCountlyService] SessionTimerCoroutine, Coroutine completed."); + } + + /// + /// Stops the timer and unsubscribes from the Elapsed event. + /// This exists for preventing session extending after tests. + /// + internal void StopSessionTimer() + { + isInternalTimerStopped = true; + + #if UNITY_WEBGL + _monoBehaviour.StopCoroutine(SessionTimerCoroutine()); + #else + if (_sessionTimer != null) { + // Unsubscribe from the Elapsed event + _sessionTimer.Elapsed -= SessionTimerOnElapsedAsync; + + // Stop and dispose the timer + _sessionTimer.Stop(); + _sessionTimer.Dispose(); + } + #endif } /// @@ -92,17 +137,14 @@ private void InitSessionTimer() /// Provides data for Timer.Elapsedevent. private async void SessionTimerOnElapsedAsync(object sender, ElapsedEventArgs elapsedEventArgs) { - lock (LockObj) - { - Log.Debug("[SessionCountlyService] SessionTimerOnElapsedAsync"); - - _eventService.AddEventsToRequestQueue(); - _ = _requestCountlyHelper.ProcessQueue(); + lock (LockObj) { - if (!_configuration.IsAutomaticSessionTrackingDisabled) - { - _ = ExtendSessionAsync(); + if (isInternalTimerStopped) { + return; } + + Log.Debug("[SessionCountlyService] SessionTimerOnElapsedAsync"); + SendRequestsAndExtendSession(); } await Task.CompletedTask; @@ -115,13 +157,11 @@ internal async Task BeginSessionAsync() { Log.Debug("[SessionCountlyService] BeginSessionAsync"); - if (!_consentService.CheckConsentInternal(Consents.Sessions)) - { + if (!_consentService.CheckConsentInternal(Consents.Sessions)) { return; } - if (IsSessionInitiated) - { + if (IsSessionInitiated) { Log.Warning("[SessionCountlyService] BeginSessionAsync: The session has already started!"); return; } @@ -130,37 +170,27 @@ internal async Task BeginSessionAsync() //Session initiated IsSessionInitiated = true; - Dictionary requestParams = - new Dictionary(); - - + Dictionary requestParams = new Dictionary(); requestParams.Add("begin_session", 1); /* If location is disabled or no location consent is given, the SDK adds an empty location entry to every "begin_session" request. */ - if (_locationService.IsLocationDisabled || !_consentService.CheckConsentInternal(Consents.Location)) - { + if (_locationService.IsLocationDisabled || !_consentService.CheckConsentInternal(Consents.Location)) { requestParams.Add("location", string.Empty); - } - else - { - if (!string.IsNullOrEmpty(_locationService.IPAddress)) - { + } else { + if (!string.IsNullOrEmpty(_locationService.IPAddress)) { requestParams.Add("ip_address", _locationService.IPAddress); } - if (!string.IsNullOrEmpty(_locationService.CountryCode)) - { + if (!string.IsNullOrEmpty(_locationService.CountryCode)) { requestParams.Add("country_code", _locationService.CountryCode); } - if (!string.IsNullOrEmpty(_locationService.City)) - { + if (!string.IsNullOrEmpty(_locationService.City)) { requestParams.Add("city", _locationService.City); } - if (!string.IsNullOrEmpty(_locationService.Location)) - { + if (!string.IsNullOrEmpty(_locationService.Location)) { requestParams.Add("location", _locationService.Location); } } @@ -179,35 +209,28 @@ internal async Task EndSessionAsync() { Log.Debug("[SessionCountlyService] EndSessionAsync"); - if (!_consentService.CheckConsentInternal(Consents.Sessions)) - { + if (!_consentService.CheckConsentInternal(Consents.Sessions)) { return; } - if (!IsSessionInitiated) - { + if (!IsSessionInitiated) { Log.Warning("[SessionCountlyService] EndSessionAsync: The session isn't started yet!"); return; } IsSessionInitiated = false; - _eventService.AddEventsToRequestQueue(); - - - Dictionary requestParams = - new Dictionary + // Countly.Instance.UserProfile.Save(); + Dictionary requestParams = new Dictionary { {"end_session", 1}, {"session_duration", Convert.ToInt32((DateTime.Now - _lastSessionRequestTime).TotalSeconds)} }; - _requestCountlyHelper.AddToRequestQueue(requestParams); await _requestCountlyHelper.ProcessQueue(); } - /// /// Extends a session by another session duration provided in configuration. By default session duration is 60 seconds. /// @@ -215,19 +238,16 @@ internal async Task ExtendSessionAsync() { Log.Debug("[SessionCountlyService] ExtendSessionAsync"); - if (!_consentService.CheckConsentInternal(Consents.Sessions)) - { + if (!_consentService.CheckConsentInternal(Consents.Sessions)) { return; } - if (!IsSessionInitiated) - { + if (!IsSessionInitiated) { Log.Warning("[SessionCountlyService] ExtendSessionAsync: The session isn't started yet!"); return; } - Dictionary requestParams = - new Dictionary + Dictionary requestParams = new Dictionary { { "session_duration", Convert.ToInt32((DateTime.Now - _lastSessionRequestTime).TotalSeconds) @@ -238,22 +258,22 @@ internal async Task ExtendSessionAsync() _requestCountlyHelper.AddToRequestQueue(requestParams); await _requestCountlyHelper.ProcessQueue(); - + + #if UNITY_WEBGL + _monoBehaviour.StartCoroutine(SessionTimerCoroutine()); + #endif } #region override Methods internal override async void ConsentChanged(List updatedConsents, bool newConsentValue, ConsentChangedAction action) { - if (updatedConsents.Contains(Consents.Sessions) && newConsentValue) - { - if (!_configuration.IsAutomaticSessionTrackingDisabled) - { + if (updatedConsents.Contains(Consents.Sessions) && newConsentValue) { + if (!_configuration.IsAutomaticSessionTrackingDisabled) { IsSessionInitiated = false; await BeginSessionAsync(); } } - } #endregion } -} +} \ No newline at end of file From 6d0a009600f898496a98b248886c67864949bbd6 Mon Sep 17 00:00:00 2001 From: Nikola Garabandic Date: Wed, 7 Aug 2024 18:06:41 +0200 Subject: [PATCH 2/2] Analytics available by defafult --- src/UnitySampleProject/ProjectSettings/ProjectSettings.asset | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/UnitySampleProject/ProjectSettings/ProjectSettings.asset b/src/UnitySampleProject/ProjectSettings/ProjectSettings.asset index be3fa1a2a..f44536f56 100644 --- a/src/UnitySampleProject/ProjectSettings/ProjectSettings.asset +++ b/src/UnitySampleProject/ProjectSettings/ProjectSettings.asset @@ -792,7 +792,7 @@ PlayerSettings: webGLPowerPreference: 2 scriptingDefineSymbols: Standalone: - WebGL: RAMP_AVAILABLE + WebGL: RAMP_AVAILABLE;ENABLE_ANALYTICS iPhone: additionalCompilerArguments: {} platformArchitecture: {}