From 90acd4712dcbd88e1c39db35f5f91c2e43f735c4 Mon Sep 17 00:00:00 2001 From: AdnanKhurshid26 Date: Mon, 4 May 2026 17:28:36 +0530 Subject: [PATCH 1/2] added offline transaction syncing Co-authored-by: Copilot --- Runtime/PlaySuperUnity.cs | 100 ++++++++++++++++++++++++++++++++++++++ Runtime/Transaction.cs | 41 ++++++++++++---- 2 files changed, 132 insertions(+), 9 deletions(-) diff --git a/Runtime/PlaySuperUnity.cs b/Runtime/PlaySuperUnity.cs index b86d725..1eccd94 100644 --- a/Runtime/PlaySuperUnity.cs +++ b/Runtime/PlaySuperUnity.cs @@ -593,6 +593,9 @@ public void OpenStore(string url = null, string utmContent = null) { MixPanelManager.SendEvent(Constants.MixpanelEvent.STORE_OPEN); + // Sync any pending local transactions before opening store + _ = SyncPendingLocalTransactionsAsync(); + if (!IsLoggedIn()) { Debug.LogWarning("[PlaySuper] Opening store without valid auth token - user may need to login"); @@ -733,6 +736,97 @@ private async Task ProcessTokenCommon(string token) _ = FetchSdkTransactionsAfterAuth(); } + /// + /// Sync any pending local transactions (coins earned/spent while offline) to the server. + /// Called automatically on app resume and store open. + /// Requires: internet connectivity + logged in + pending transactions. + /// + private static async Task SyncPendingLocalTransactionsAsync() + { + // Check prerequisites + if (Application.internetReachability == NetworkReachability.NotReachable) + { + Debug.Log("[PlaySuper] Skipping local transaction sync - no internet"); + return; + } + + if (!IsLoggedIn()) + { + Debug.Log("[PlaySuper] Skipping local transaction sync - not logged in"); + return; + } + + if (!TransactionsManager.HasTransactions()) + { + return; // Nothing to sync + } + + Debug.Log("[PlaySuper] Syncing pending local transactions..."); + + try + { + List transactions = TransactionsManager.GetTransactions(); + Dictionary distributeCoinMap = new Dictionary(); + Dictionary deductCoinMap = new Dictionary(); + + foreach (Transaction t in transactions) + { + var targetMap = t.type == "deduct" ? deductCoinMap : distributeCoinMap; + if (targetMap.ContainsKey(t.coinId)) + { + targetMap[t.coinId] += t.amount; + } + else + { + targetMap.Add(t.coinId, t.amount); + } + } + + bool allSucceeded = true; + + foreach (KeyValuePair kvp in distributeCoinMap) + { + Debug.Log($"[PlaySuper] Syncing distribute: {kvp.Value} of {kvp.Key}"); + try + { + await _instance.DistributeCoins(kvp.Key, kvp.Value); + } + catch + { + allSucceeded = false; + } + } + + foreach (KeyValuePair kvp in deductCoinMap) + { + Debug.Log($"[PlaySuper] Syncing deduct: {kvp.Value} of {kvp.Key}"); + try + { + await _instance.DeductCoins(kvp.Key, kvp.Value); + } + catch + { + allSucceeded = false; + } + } + + // Only clear if all succeeded (DistributeCoins/DeductCoins will re-add on failure) + if (allSucceeded) + { + TransactionsManager.ClearTransactions(); + Debug.Log("[PlaySuper] Local transactions synced successfully"); + } + else + { + Debug.LogWarning("[PlaySuper] Some local transactions failed to sync - will retry later"); + } + } + catch (Exception ex) + { + Debug.LogWarning($"[PlaySuper] Error syncing local transactions: {ex.Message}"); + } + } + /// /// Internal method to fetch SDK transactions after authentication. /// Runs in background and fires OnSdkTransactionsReceived event if transactions exist. @@ -1131,6 +1225,12 @@ void OnApplicationPause(bool pauseStatus) PlayerPrefsSaveManager.ForceSaveImmediate(); // Flush any pending debounced saves MixPanelEventQueue.Dispose(); } + else + { + Debug.Log("[PlaySuper] App resuming - checking for pending transactions"); + // Sync any pending local transactions when app resumes + _ = SyncPendingLocalTransactionsAsync(); + } } // Add explicit disposal method for manual cleanup diff --git a/Runtime/Transaction.cs b/Runtime/Transaction.cs index a18757d..54330a4 100644 --- a/Runtime/Transaction.cs +++ b/Runtime/Transaction.cs @@ -6,10 +6,38 @@ namespace PlaySuperUnity internal class TransactionsManager { private const string saveKey = "transactions"; - public static List transactionList = new List(); + private static List transactionList = new List(); + private static bool isInitialized = false; + + /// + /// Ensures transactions are loaded from PlayerPrefs on first access. + /// This fixes offline transactions being lost after app restart. + /// + private static void EnsureInitialized() + { + if (isInitialized) return; + isInitialized = true; + + string json = PlayerPrefs.GetString(saveKey, ""); + if (!string.IsNullOrEmpty(json)) + { + try + { + var wrapper = JsonUtility.FromJson(json); + transactionList = wrapper?.transactions ?? new List(); + Debug.Log($"[PlaySuper] Loaded {transactionList.Count} pending offline transactions from storage"); + } + catch (System.Exception e) + { + Debug.LogWarning($"[PlaySuper] Failed to load offline transactions: {e.Message}"); + transactionList = new List(); + } + } + } public static void AddTransaction(string coinId, int amount, string type = "distribute") { + EnsureInitialized(); Transaction t = new Transaction(coinId, amount, type); transactionList.Add(t); SaveTransactions(); @@ -31,18 +59,13 @@ public static void ClearTransactions() public static List GetTransactions() { - if (PlayerPrefs.HasKey(saveKey)) - { - return transactionList; - } - else - { - return null; - } + EnsureInitialized(); + return transactionList; } public static bool HasTransactions() { + EnsureInitialized(); return transactionList.Count > 0; } } From 7b540d9492f5eb1031c3ba34df6fb2a5b41a970d Mon Sep 17 00:00:00 2001 From: AdnanKhurshid26 Date: Mon, 4 May 2026 18:12:46 +0530 Subject: [PATCH 2/2] refactored and renamed mixpanel classes to analytics classes Co-authored-by: Copilot --- ...elEventQueue.cs => AnalyticsEventQueue.cs} | 150 +++++++++++++----- ...ue.cs.meta => AnalyticsEventQueue.cs.meta} | 0 ...anager.cs => AnalyticsLifeCycleManager.cs} | 16 +- ...meta => AnalyticsLifeCycleManager.cs.meta} | 0 Runtime/Constants.cs | 4 +- Runtime/PlaySuperUnity.cs | 60 +++---- Runtime/PlayerPrefsSaveManager.cs.meta | 2 + Runtime/WebView.cs | 8 +- 8 files changed, 159 insertions(+), 81 deletions(-) rename Runtime/{MixpanelEventQueue.cs => AnalyticsEventQueue.cs} (82%) rename Runtime/{MixpanelEventQueue.cs.meta => AnalyticsEventQueue.cs.meta} (100%) rename Runtime/{MixpanelLifeCycleManager.cs => AnalyticsLifeCycleManager.cs} (75%) rename Runtime/{MixpanelLifeCycleManager.cs.meta => AnalyticsLifeCycleManager.cs.meta} (100%) create mode 100644 Runtime/PlayerPrefsSaveManager.cs.meta diff --git a/Runtime/MixpanelEventQueue.cs b/Runtime/AnalyticsEventQueue.cs similarity index 82% rename from Runtime/MixpanelEventQueue.cs rename to Runtime/AnalyticsEventQueue.cs index b12ed67..ade841b 100644 --- a/Runtime/MixpanelEventQueue.cs +++ b/Runtime/AnalyticsEventQueue.cs @@ -9,7 +9,7 @@ namespace PlaySuperUnity { [System.Serializable] - internal class MixPanelEvent + internal class AnalyticsEvent { public string eventName; public long originalTimestamp; @@ -17,7 +17,7 @@ internal class MixPanelEvent public string payloadJson; // Pre-serialized JSON payload public int retryCount; - public MixPanelEvent(string eventName, long timestamp, string payloadJson) + public AnalyticsEvent(string eventName, long timestamp, string payloadJson) { this.eventName = eventName; this.originalTimestamp = timestamp; @@ -28,24 +28,30 @@ public MixPanelEvent(string eventName, long timestamp, string payloadJson) } [System.Serializable] - internal class MixPanelEventListWrapper + internal class AnalyticsEventListWrapper { - public List events; + public List events; - public MixPanelEventListWrapper(List events) + public AnalyticsEventListWrapper(List events) { this.events = events; } } - internal class MixPanelEventQueue + internal class AnalyticsEventQueue { private static readonly string QueueFilePath = Path.Combine( + Application.persistentDataPath, + "analytics_queue.json" + ); + + // Legacy file path for migration from older SDK versions + private static readonly string LegacyQueueFilePath = Path.Combine( Application.persistentDataPath, "mixpanel_queue.json" ); - private static List eventQueue = new List(); + private static List eventQueue = new List(); private static bool isProcessing = false; private static readonly object queueLock = new object(); @@ -60,7 +66,7 @@ internal class MixPanelEventQueue public enum SendResult { Success, TransientFailure, PermanentFailure } - static MixPanelEventQueue() + static AnalyticsEventQueue() { Initialize(); } @@ -69,7 +75,7 @@ public static void EnqueueEvent(string eventName, long timestamp, string payload { lock (queueLock) { - eventQueue.Add(new MixPanelEvent(eventName, timestamp, payloadJson)); + eventQueue.Add(new AnalyticsEvent(eventName, timestamp, payloadJson)); // Enforce size limit (3MB) - remove oldest events if exceeded while (GetQueueSizeInBytes() > Constants.MAX_QUEUE_SIZE_BYTES && eventQueue.Count > 0) @@ -115,10 +121,10 @@ public static async Task ProcessQueue() isProcessing = true; } - List snapshot; + List snapshot; lock (queueLock) { - snapshot = new List(eventQueue); + snapshot = new List(eventQueue); } int sentCount = 0; @@ -133,7 +139,7 @@ public static async Task ProcessQueue() for (int i = 0; i < snapshot.Count; i += Constants.BATCH_SIZE) { int batchCount = Mathf.Min(Constants.BATCH_SIZE, snapshot.Count - i); - var batch = new List(); + var batch = new List(); for (int j = 0; j < batchCount; j++) { batch.Add(snapshot[i + j]); @@ -230,7 +236,7 @@ public static async Task ProcessQueue() ); } - private static string BuildBatchPayload(List batch) + private static string BuildBatchPayload(List batch) { var eventsJsonArray = new List(); @@ -477,15 +483,15 @@ private static async Task SendBatchToAnalytics(string jsonPayload) /// private static void SaveQueueToFile() { - List snapshot; + List snapshot; lock (queueLock) { - snapshot = new List(eventQueue); + snapshot = new List(eventQueue); } try { - var wrapper = new MixPanelEventListWrapper(snapshot); + var wrapper = new AnalyticsEventListWrapper(snapshot); string json = JsonUtility.ToJson(wrapper); string tmpPath = QueueFilePath + ".tmp"; @@ -539,27 +545,30 @@ private static async Task CoalescedSaveAsync() /// private static async Task SaveQueueToFileAsync() { - List snapshot; + List snapshot; + bool isEmpty; lock (queueLock) { - if (eventQueue.Count == 0) + isEmpty = eventQueue.Count == 0; + snapshot = isEmpty ? null : new List(eventQueue); + } + + if (isEmpty) + { + // Clear file if queue is empty (outside lock) + try { - // Clear file if queue is empty - try - { - await Task.Run(() => - { - if (File.Exists(QueueFilePath)) - File.Delete(QueueFilePath); - }); - } - catch (Exception ex) + await Task.Run(() => { - Debug.LogWarning($"[Analytics] Failed to delete empty queue file: {ex.Message}"); - } - return; + if (File.Exists(QueueFilePath)) + File.Delete(QueueFilePath); + }); } - snapshot = new List(eventQueue); + catch (Exception ex) + { + Debug.LogWarning($"[Analytics] Failed to delete empty queue file: {ex.Message}"); + } + return; } // Perform I/O on background thread to avoid ANR @@ -567,7 +576,7 @@ await Task.Run(() => { try { - var wrapper = new MixPanelEventListWrapper(snapshot); + var wrapper = new AnalyticsEventListWrapper(snapshot); string json = JsonUtility.ToJson(wrapper); string tmpPath = QueueFilePath + ".tmp"; @@ -601,17 +610,20 @@ private static void LoadQueueFromFile() { try { + // First try to migrate from legacy file if it exists + MigrateLegacyQueueFile(); + if (File.Exists(QueueFilePath)) { string json = File.ReadAllText(QueueFilePath); - var wrapper = JsonUtility.FromJson(json); - eventQueue = wrapper?.events ?? new List(); + var wrapper = JsonUtility.FromJson(json); + eventQueue = wrapper?.events ?? new List(); Debug.Log($"[Analytics] Loaded {eventQueue.Count} events from file"); } else { - eventQueue = new List(); + eventQueue = new List(); } } catch (Exception ex) @@ -629,7 +641,71 @@ private static void LoadQueueFromFile() { Debug.LogWarning($"[Analytics] Failed to backup corrupted queue file: {backupEx.Message}"); } - eventQueue = new List(); + eventQueue = new List(); + } + } + + /// + /// Migrate events from legacy mixpanel_queue.json to new analytics_queue.json. + /// This runs once on upgrade - merges any pending events and deletes the old file. + /// + private static void MigrateLegacyQueueFile() + { + try + { + if (!File.Exists(LegacyQueueFilePath)) + return; + + Debug.Log("[Analytics] Found legacy queue file, migrating..."); + + string legacyJson = File.ReadAllText(LegacyQueueFilePath); + var legacyWrapper = JsonUtility.FromJson(legacyJson); + var legacyEvents = legacyWrapper?.events; + + if (legacyEvents != null && legacyEvents.Count > 0) + { + // Load existing new queue if any + List existingEvents = new List(); + if (File.Exists(QueueFilePath)) + { + string existingJson = File.ReadAllText(QueueFilePath); + var existingWrapper = JsonUtility.FromJson(existingJson); + existingEvents = existingWrapper?.events ?? new List(); + } + + // Merge: add legacy events that aren't duplicates (by insertId) + var existingIds = new HashSet(); + foreach (var ev in existingEvents) + { + existingIds.Add(ev.insertId); + } + + int migratedCount = 0; + foreach (var legacyEvent in legacyEvents) + { + if (!existingIds.Contains(legacyEvent.insertId)) + { + existingEvents.Add(legacyEvent); + migratedCount++; + } + } + + // Save merged events to new file + var mergedWrapper = new AnalyticsEventListWrapper(existingEvents); + string mergedJson = JsonUtility.ToJson(mergedWrapper); + File.WriteAllText(QueueFilePath, mergedJson); + + Debug.Log($"[Analytics] Migrated {migratedCount} events from legacy queue"); + } + + // Delete legacy file after successful migration + File.Delete(LegacyQueueFilePath); + Debug.Log("[Analytics] Legacy queue file deleted"); + } + catch (Exception ex) + { + Debug.LogWarning($"[Analytics] Failed to migrate legacy queue: {ex.Message}"); + // Don't fail startup - just log and continue } } diff --git a/Runtime/MixpanelEventQueue.cs.meta b/Runtime/AnalyticsEventQueue.cs.meta similarity index 100% rename from Runtime/MixpanelEventQueue.cs.meta rename to Runtime/AnalyticsEventQueue.cs.meta diff --git a/Runtime/MixpanelLifeCycleManager.cs b/Runtime/AnalyticsLifeCycleManager.cs similarity index 75% rename from Runtime/MixpanelLifeCycleManager.cs rename to Runtime/AnalyticsLifeCycleManager.cs index 2382d78..892cec5 100644 --- a/Runtime/MixpanelLifeCycleManager.cs +++ b/Runtime/AnalyticsLifeCycleManager.cs @@ -3,9 +3,9 @@ namespace PlaySuperUnity { - internal class MixPanelLifecycleManager : MonoBehaviour + internal class AnalyticsLifecycleManager : MonoBehaviour { - private static MixPanelLifecycleManager instance; + private static AnalyticsLifecycleManager instance; private Coroutine processLoopCoroutine; private bool isRunning = true; @@ -14,8 +14,8 @@ public static void Initialize() if (instance != null) return; - GameObject go = new GameObject("MixPanelLifecycleManager"); - instance = go.AddComponent(); + GameObject go = new GameObject("AnalyticsLifecycleManager"); + instance = go.AddComponent(); DontDestroyOnLoad(go); } @@ -41,9 +41,9 @@ IEnumerator ProcessLoop() { yield return new WaitForSeconds(Constants.PROCESS_INTERVAL); - if (isRunning && MixPanelEventQueue.HasQueuedEvents()) + if (isRunning && AnalyticsEventQueue.HasQueuedEvents()) { - _ = MixPanelEventQueue.ProcessQueue(); + _ = AnalyticsEventQueue.ProcessQueue(); } } } @@ -66,9 +66,9 @@ void OnApplicationFocus(bool hasFocus) private void TryProcessQueue() { - if (MixPanelEventQueue.HasQueuedEvents()) + if (AnalyticsEventQueue.HasQueuedEvents()) { - _ = MixPanelEventQueue.ProcessQueue(); + _ = AnalyticsEventQueue.ProcessQueue(); } } diff --git a/Runtime/MixpanelLifeCycleManager.cs.meta b/Runtime/AnalyticsLifeCycleManager.cs.meta similarity index 100% rename from Runtime/MixpanelLifeCycleManager.cs.meta rename to Runtime/AnalyticsLifeCycleManager.cs.meta diff --git a/Runtime/Constants.cs b/Runtime/Constants.cs index 620f717..c3c48a8 100644 --- a/Runtime/Constants.cs +++ b/Runtime/Constants.cs @@ -2,7 +2,7 @@ namespace PlaySuperUnity { internal static class Constants { - internal static class MixpanelEvent + internal static class AnalyticsEvent { public const string GAME_OPEN = "ps_sdk.game_opened"; public const string GAME_CLOSE = "ps_sdk.game_closed"; @@ -39,7 +39,7 @@ internal static class MixpanelEvent internal const string devApiUrl = "https://dev.playsuper.club"; internal const string prodApiUrl = "https://api.playsuper.club"; - // Mixpanel Event Queue + // Analytics Event Queue internal const float PROCESS_INTERVAL = 30f; internal const int MAX_QUEUE_SIZE = 1024; internal const int BATCH_SIZE = 128; diff --git a/Runtime/PlaySuperUnity.cs b/Runtime/PlaySuperUnity.cs index 1eccd94..5ecc281 100644 --- a/Runtime/PlaySuperUnity.cs +++ b/Runtime/PlaySuperUnity.cs @@ -178,8 +178,8 @@ public static void Initialize(string _apiKey, bool _isDev = false, bool _enableA _instance = sdkObject.AddComponent(); DontDestroyOnLoad(sdkObject); - // Initialize MixPanel lifecycle manager AFTER SDK is ready - MixPanelLifecycleManager.Initialize(); + // Initialize Analytics lifecycle manager AFTER SDK is ready + AnalyticsLifecycleManager.Initialize(); LogPrivacySettings(); Debug.Log("PlaySuperUnity initialized with API Key: " + apiKey); @@ -198,7 +198,7 @@ public static void Initialize(string _apiKey, bool _isDev = false, bool _enableA HandlePreviousSessionClose(); // Send game open event - MixPanelManager.SendEvent(Constants.MixpanelEvent.GAME_OPEN); + AnalyticsManager.SendEvent(Constants.AnalyticsEvent.GAME_OPEN); } /// @@ -270,7 +270,7 @@ public static void SetUserProperties(Dictionary properties) return; } - MixPanelManager.SetUserProperties(properties); + AnalyticsManager.SetUserProperties(properties); Debug.Log($"[PlaySuper] User properties set: {properties.Count} properties"); } @@ -279,7 +279,7 @@ public static void SetUserProperties(Dictionary properties) /// public static void ClearUserProperties() { - MixPanelManager.ClearUserProperties(); + AnalyticsManager.ClearUserProperties(); Debug.Log("[PlaySuper] User properties cleared"); } @@ -289,7 +289,7 @@ public static void ClearUserProperties() /// Copy of current user properties dictionary public static Dictionary GetUserProperties() { - return MixPanelManager.GetUserProperties(); + return AnalyticsManager.GetUserProperties(); } #endregion @@ -304,7 +304,7 @@ private static void HandlePreviousSessionClose() string timestamp = PlayerPrefs.GetString(Constants.lastCloseTimestampName); if (long.TryParse(timestamp, out long timestampLong)) { - MixPanelManager.SendEvent(Constants.MixpanelEvent.GAME_CLOSE, timestampLong); + AnalyticsManager.SendEvent(Constants.AnalyticsEvent.GAME_CLOSE, timestampLong); PlayerPrefs.SetString(Constants.lastCloseDoneName, "1"); } } @@ -335,8 +335,8 @@ static bool OnApplicationWantsToQuit() PlayerPrefsSaveManager.ForceSaveImmediate(); // Flush all pending saves before quit // Dispose resources to save any pending data - MixPanelEventQueue.Dispose(); - MixPanelManager.Dispose(); + AnalyticsEventQueue.Dispose(); + AnalyticsManager.Dispose(); return true; // Allow quit } @@ -553,7 +553,7 @@ private async Task SendPlayerIdentificationRequest() { userId = profile.id, timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds(), - deviceId = MixPanelManager.DeviceId + deviceId = AnalyticsManager.DeviceId }; var jsonPayload = JsonUtility.ToJson(payload); @@ -591,7 +591,7 @@ public void OpenStore() public void OpenStore(string url = null, string utmContent = null) { - MixPanelManager.SendEvent(Constants.MixpanelEvent.STORE_OPEN); + AnalyticsManager.SendEvent(Constants.AnalyticsEvent.STORE_OPEN); // Sync any pending local transactions before opening store _ = SyncPendingLocalTransactionsAsync(); @@ -695,7 +695,7 @@ private async Task ProcessTokenCommon(string token) Debug.LogWarning("[PlaySuper] Failed to fetch profile after login - some features may be unavailable"); } - await MixPanelManager.SendEvent(Constants.MixpanelEvent.PLAYER_IDENTIFY); + await AnalyticsManager.SendEvent(Constants.AnalyticsEvent.PLAYER_IDENTIFY); // Send player identification POST request (guards against null profile internally) await SendPlayerIdentificationRequest(); @@ -1205,10 +1205,10 @@ void OnDestroy() Debug.Log("[PlaySuper] SDK destroying - cleaning up resources"); // Stop transaction polling and reset static flag - WebViewManager.StopTransactionPolling(); + WebView.StopTransactionPolling(); - // Dispose MixPanel resources - MixPanelEventQueue.Dispose(); + // Dispose Analytics resources + AnalyticsEventQueue.Dispose(); // Unsubscribe from application events Application.wantsToQuit -= OnApplicationWantsToQuit; @@ -1223,7 +1223,7 @@ void OnApplicationPause(bool pauseStatus) { Debug.Log("[PlaySuper] App pausing - saving state"); PlayerPrefsSaveManager.ForceSaveImmediate(); // Flush any pending debounced saves - MixPanelEventQueue.Dispose(); + AnalyticsEventQueue.Dispose(); } else { @@ -1763,7 +1763,7 @@ private static async Task TrackTransactionsFetched(List transact { "coinName", coinName } }; - await MixPanelManager.SendEvent(Constants.MixpanelEvent.TRANSACTIONS_FETCHED, 0, properties); + await AnalyticsManager.SendEvent(Constants.AnalyticsEvent.TRANSACTIONS_FETCHED, 0, properties); } catch (Exception e) { @@ -1790,7 +1790,7 @@ private static async Task TrackTransactionsCommitted(CommitByIdsResult result, i { "transaction_ids", result.Committed } }; - await MixPanelManager.SendEvent(Constants.MixpanelEvent.TRANSACTIONS_COMMITTED, 0, properties); + await AnalyticsManager.SendEvent(Constants.AnalyticsEvent.TRANSACTIONS_COMMITTED, 0, properties); } catch (Exception e) { @@ -1814,7 +1814,7 @@ private static async Task TrackTransactionsCommitFailed(List transaction { "commit_method", commitMethod } }; - await MixPanelManager.SendEvent(Constants.MixpanelEvent.TRANSACTIONS_COMMIT_FAILED, 0, properties); + await AnalyticsManager.SendEvent(Constants.AnalyticsEvent.TRANSACTIONS_COMMIT_FAILED, 0, properties); } catch (Exception e) { @@ -1838,7 +1838,7 @@ private static async Task TrackLegacyCommitSuccess(string lastProcessedTransacti { "success", true } }; - await MixPanelManager.SendEvent(Constants.MixpanelEvent.TRANSACTIONS_COMMITTED, 0, properties); + await AnalyticsManager.SendEvent(Constants.AnalyticsEvent.TRANSACTIONS_COMMITTED, 0, properties); } catch (Exception e) { @@ -1862,7 +1862,7 @@ public static void Logout() ClearUserProperties(); ClearSdkTransactionSyncState(); TransactionsManager.ClearTransactions(); - MixPanelEventQueue.ClearQueue(); + AnalyticsEventQueue.ClearQueue(); PlayerPrefsSaveManager.ForceSaveImmediate(); // Critical: must complete before method returns Debug.Log("[PlaySuper] Player logged out — all session state cleared"); @@ -1871,7 +1871,7 @@ public static void Logout() #endregion } - internal class MixPanelManager + internal class AnalyticsManager { private static string _deviceId; private static string _advertisingId; @@ -1904,13 +1904,13 @@ private static void EnsureUserPropertiesLoaded() if (parsed != null) { _userProperties = parsed; - Debug.Log($"[MixPanel] Loaded {_userProperties.Count} user properties from storage"); + Debug.Log($"[Analytics] Loaded {_userProperties.Count} user properties from storage"); } } } catch (Exception e) { - Debug.LogWarning($"[MixPanel] Failed to load user properties: {e.Message}"); + Debug.LogWarning($"[Analytics] Failed to load user properties: {e.Message}"); _userProperties = new Dictionary(); } } @@ -1929,7 +1929,7 @@ private static void SaveUserProperties() } catch (Exception e) { - Debug.LogError($"[MixPanel] Failed to save user properties: {e.Message}"); + Debug.LogError($"[Analytics] Failed to save user properties: {e.Message}"); } } @@ -2350,7 +2350,7 @@ internal static async Task SendEvent(string eventName, long timestamp = 0, Dicti } // Create clean payload - var mixPanelPayload = + var analyticsPayload = $@"{{ ""event_name"": ""{eventName}"", ""properties"": {{ @@ -2359,10 +2359,10 @@ internal static async Task SendEvent(string eventName, long timestamp = 0, Dicti }}"; // Log full payload for debugging - Debug.Log($"[Analytics] Sending event '{eventName}':\n{mixPanelPayload}"); + Debug.Log($"[Analytics] Sending event '{eventName}':\n{analyticsPayload}"); // Always queue - let the lifecycle manager handle processing - MixPanelEventQueue.EnqueueEvent(eventName, actualEventTime, mixPanelPayload); + AnalyticsEventQueue.EnqueueEvent(eventName, actualEventTime, analyticsPayload); } catch (Exception e) { @@ -2385,7 +2385,7 @@ private static void CreateFallbackEvent(string eventName, long timestamp) ""$insert_id"": ""{Guid.NewGuid()}"" }} }}"; - MixPanelEventQueue.EnqueueEvent(eventName, fallbackTime, fallbackPayload); + AnalyticsEventQueue.EnqueueEvent(eventName, fallbackTime, fallbackPayload); } /// @@ -2412,7 +2412,7 @@ internal static void Dispose() _advertisingId = null; _userProperties.Clear(); - Debug.Log("[MixPanel] Manager disposed"); + Debug.Log("[Analytics] Manager disposed"); } } } diff --git a/Runtime/PlayerPrefsSaveManager.cs.meta b/Runtime/PlayerPrefsSaveManager.cs.meta new file mode 100644 index 0000000..d37f8f5 --- /dev/null +++ b/Runtime/PlayerPrefsSaveManager.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 9a5b9cbfa983743f784eb5c6845a21e8 \ No newline at end of file diff --git a/Runtime/WebView.cs b/Runtime/WebView.cs index 8cfe1cf..aa263a8 100644 --- a/Runtime/WebView.cs +++ b/Runtime/WebView.cs @@ -151,8 +151,8 @@ GpmWebViewError error PlaySuperUnitySDK.NotifyStoreClosed(); // Fire-and-forget on main thread (SendEvent uses UnityWebRequest internally) - MixPanelManager.SendEvent( - Constants.MixpanelEvent.STORE_CLOSE, + AnalyticsManager.SendEvent( + Constants.AnalyticsEvent.STORE_CLOSE, DateTimeOffset.UtcNow.ToUnixTimeSeconds() ); break; @@ -165,8 +165,8 @@ GpmWebViewError error StartTransactionPolling(); // Fire-and-forget on main thread (SendEvent uses UnityWebRequest internally) - MixPanelManager.SendEvent( - Constants.MixpanelEvent.STORE_OPEN, + AnalyticsManager.SendEvent( + Constants.AnalyticsEvent.STORE_OPEN, DateTimeOffset.UtcNow.ToUnixTimeSeconds() ); break;