#if UNITY_WEBGL using System; using System.Collections.Generic; using System.Globalization; using System.Runtime.InteropServices; using UnityEngine; namespace JustTrack { /// /// WebGL-specific implementation of the SDK agent that communicates with the JavaScript Web SDK. /// internal class SDKWebGLAgent : ISDKAgent { internal static SDKWebGLAgent INSTANCE = new SDKWebGLAgent(); private bool initialized = false; private SDKWebGLAgent() {} #pragma warning disable SA1309 // Field names should not begin with underscore (required for native interop) [DllImport("__Internal")] private static extern void _justtrack_webgl_init(string apiKey, string bundleId, string appVersion, string appCode, string userId, bool manualStart, bool isLoggingEnabled, string gameObjectName, string callbackMethodName); [DllImport("__Internal")] private static extern void _justtrack_webgl_start(); [DllImport("__Internal")] private static extern void _justtrack_webgl_stop(); [DllImport("__Internal")] private static extern bool _justtrack_webgl_is_running(); [DllImport("__Internal")] private static extern void _justtrack_webgl_anonymize(string gameObjectName, string callbackMethodName); [DllImport("__Internal")] private static extern void _justtrack_webgl_track_event(string eventName, string dimensionsJson, string valueJson, string gameObjectName, string callbackMethodName); #pragma warning restore SA1309 #pragma warning disable SA1401, SA1307 // Fields should be private; field names should begin with upper-case letter (required for JSON serialization) [Serializable] public class WebGLAttributionResponse { public string userType = ""; public string type = ""; public string campaignId = ""; public string campaignName = ""; public string campaignType = ""; public string channelId = ""; public string channelName = ""; public string channelIncent = ""; public string partnerId = ""; public string partnerName = ""; public string? sourceId = null; public string? sourceBundleId = null; public string? sourcePlacement = null; public string? adsetId = null; public string createdAt = ""; } [Serializable] public class WebGLCallbackResponse { public bool success = false; public string? data = null; public string? error = null; } #pragma warning restore SA1401, SA1307 private Action? initSuccessCallback = null; private Action? initFailureCallback = null; private Action? anonymizeSuccessCallback = null; private Action? anonymizeFailureCallback = null; private Action? attributionListenerCallback = null; private Dictionary eventSuccessCallbacks = new Dictionary(); private Dictionary> eventFailureCallbacks = new Dictionary>(); public void Initialize( string pApiKey, string? pTrackingId, string? pTrackingProvider, string? pCustomUserId, bool pAutomaticInAppPurchaseTracking, bool pManualStart, bool pEnableConsoleLogging, string? pServerUrl, string? pBundleId, string? pAppVersion, string? pAppCode, Action pOnSuccess, Action pOnFailure) { initSuccessCallback = pOnSuccess; initFailureCallback = pOnFailure; var settings = JustTrackSettings.LoadFromResources(); string bundleId = !string.IsNullOrEmpty(pBundleId) ? pBundleId : (!string.IsNullOrEmpty(settings?.WebglBundleId) ? settings.WebglBundleId : Application.identifier); string appVersion = !string.IsNullOrEmpty(pAppVersion) ? pAppVersion : (!string.IsNullOrEmpty(settings?.WebglAppVersion) ? settings.WebglAppVersion : Application.version); string appCode = !string.IsNullOrEmpty(pAppCode) ? pAppCode : (!string.IsNullOrEmpty(settings?.WebglAppCode) ? settings.WebglAppCode : appVersion); string userId = !string.IsNullOrEmpty(settings?.WebglUserId) ? settings.WebglUserId : (pCustomUserId ?? ""); try { _justtrack_webgl_init( pApiKey, bundleId, appVersion, appCode, userId, pManualStart, pEnableConsoleLogging, "JustTrackSDKBehaviour", "OnWebGLInitCallback" ); initialized = true; } catch (Exception e) { JustTrackSDKBehaviour.CallOnMainThread(() => { pOnFailure(e.Message); }); } } public void OnWebGLInitCallback(string responseJson) { try { WebGLCallbackResponse response = JsonUtility.FromJson(responseJson); if (response.success && response.data != null) { var attribution = ParseAttributionResponse(response.data); JustTrackSDKBehaviour.CallOnMainThread(() => { initSuccessCallback?.Invoke(attribution); }); } else { JustTrackSDKBehaviour.CallOnMainThread(() => { initFailureCallback?.Invoke(response.error ?? "Unknown initialization error"); }); } } catch (Exception e) { JustTrackSDKBehaviour.CallOnMainThread(() => { initFailureCallback?.Invoke(e.Message); }); } } private AttributionResponse ParseAttributionResponse(string attributionJson) { WebGLAttributionResponse parsed = JsonUtility.FromJson(attributionJson); if (!int.TryParse(parsed.campaignId, out int campaignId)) { campaignId = 0; } if (!int.TryParse(parsed.channelId, out int channelId)) { channelId = 0; } bool channelIncent = parsed.channelIncent == "true"; if (!int.TryParse(parsed.partnerId, out int partnerId)) { partnerId = 0; } CultureInfo provider = CultureInfo.InvariantCulture; if (!DateTime.TryParseExact(parsed.createdAt, "yyyy-MM-dd'T'HH:mm:ssK", provider, DateTimeStyles.None, out DateTime createdAt)) { createdAt = DateTime.UtcNow; } return AttributionResponse.CreateResponse( parsed.userType, parsed.type, campaignId, parsed.campaignName, parsed.campaignType, channelId, parsed.channelName, channelIncent, partnerId, parsed.partnerName, parsed.sourceId, parsed.sourceBundleId, parsed.sourcePlacement, parsed.adsetId, createdAt ); } public void Start() { try { _justtrack_webgl_start(); } catch (Exception) { } } public void Stop() { try { _justtrack_webgl_stop(); } catch (Exception) { } } public void Anonymize(Action pOnSuccess, Action pOnFailure) { anonymizeSuccessCallback = pOnSuccess; anonymizeFailureCallback = pOnFailure; try { _justtrack_webgl_anonymize("JustTrackSDKBehaviour", "OnWebGLAnonymizeCallback"); } catch (Exception e) { JustTrackSDKBehaviour.CallOnMainThread(() => { pOnFailure(e.Message); }); } } public void OnWebGLAnonymizeCallback(string responseJson) { try { WebGLCallbackResponse response = JsonUtility.FromJson(responseJson); if (response.success) { JustTrackSDKBehaviour.CallOnMainThread(() => { anonymizeSuccessCallback?.Invoke(); }); } else { JustTrackSDKBehaviour.CallOnMainThread(() => { anonymizeFailureCallback?.Invoke(response.error ?? "Unknown anonymize error"); }); } } catch (Exception e) { JustTrackSDKBehaviour.CallOnMainThread(() => { anonymizeFailureCallback?.Invoke(e.Message); }); } } public bool IsRunning() { try { return _justtrack_webgl_is_running(); } catch (Exception) { return false; } } public void RegisterAttributionListener(Action pListener) { lock (this) { if (attributionListenerCallback == null) { attributionListenerCallback = pListener; } else { attributionListenerCallback += pListener; } } } public void PublishEvent(AppEvent pEvent, Action? pOnSuccess, Action? pOnFailure) { try { string dimensionsJson = "{}"; if (pEvent.Dimensions.Count > 0) { var dimensionPairs = new System.Collections.Generic.List(); foreach (var kvp in pEvent.Dimensions) { string jsonValue = kvp.Value != null ? JsonUtility.ToJson(new StringWrapper { value = kvp.Value.ToString() }) : "null"; if (jsonValue.StartsWith("{\"value\":\"") && jsonValue.EndsWith("\"}")) { jsonValue = jsonValue.Substring(10, jsonValue.Length - 12); } dimensionPairs.Add($"\"{kvp.Key}\":\"{jsonValue}\""); } dimensionsJson = "{\"dimensions\":{" + string.Join(",", dimensionPairs.ToArray()) + "}}"; } string valueJson = "null"; if (pEvent.Unit != null) { valueJson = JsonUtility.ToJson(new ValueWrapper { value = pEvent.Value, unit = pEvent.Unit.ToString().ToLower() }); } else if (pEvent.Currency != null) { valueJson = JsonUtility.ToJson(new MoneyWrapper { value = pEvent.Value, currency = pEvent.Currency }); } string callbackId = Guid.NewGuid().ToString(); if (pOnSuccess != null) { eventSuccessCallbacks[callbackId] = pOnSuccess; } if (pOnFailure != null) { eventFailureCallbacks[callbackId] = pOnFailure; } string callbackMethodName = pOnSuccess != null || pOnFailure != null ? $"OnWebGLEventCallback_{callbackId}" : ""; _justtrack_webgl_track_event( pEvent.Name, dimensionsJson, valueJson, "JustTrackSDKBehaviour", callbackMethodName ); } catch (Exception e) { if (pOnFailure != null) { JustTrackSDKBehaviour.CallOnMainThread(() => { pOnFailure(e.Message); }); } } } #pragma warning disable SA1401, SA1307 // Fields should be private; field names should begin with upper-case letter (required for JSON serialization) [Serializable] private class StringWrapper { public string value = ""; } [Serializable] private class ValueWrapper { public double value; public string unit = ""; } [Serializable] private class MoneyWrapper { public double value; public string currency = ""; } #pragma warning restore SA1401, SA1307 public void OnWebGLEventCallback(string callbackData) { string[] parts = callbackData.Split(new[] { '|' }, 2); if (parts.Length != 2) { return; } string callbackId = parts[0]; string responseJson = parts[1]; try { WebGLCallbackResponse response = JsonUtility.FromJson(responseJson); if (response.success) { if (eventSuccessCallbacks.TryGetValue(callbackId, out var successCallback)) { JustTrackSDKBehaviour.CallOnMainThread(() => { successCallback?.Invoke(); }); eventSuccessCallbacks.Remove(callbackId); } } else { if (eventFailureCallbacks.TryGetValue(callbackId, out var failureCallback)) { JustTrackSDKBehaviour.CallOnMainThread(() => { failureCallback?.Invoke(response.error ?? "Unknown event tracking error"); }); eventFailureCallbacks.Remove(callbackId); } } } catch (Exception e) { if (eventFailureCallbacks.TryGetValue(callbackId, out var failureCallback)) { JustTrackSDKBehaviour.CallOnMainThread(() => { failureCallback?.Invoke(e.Message); }); eventFailureCallbacks.Remove(callbackId); } } } public bool IsInitialized() { return initialized; } } } #endif