using System; using System.Collections.Generic; using System.Linq; namespace JustTrack { /// /// Main SDK class providing access to JustTrack analytics functionality. /// public class JustTrackSDK { private static ISDKAgent initializedAgent = null!; private static readonly object InitInProgressLock = new object(); private static readonly object InitLock = new object(); private static Action? onInit = null; private static ISDKAgent Agent { get { if (initializedAgent == null) { #if UNITY_EDITOR initializedAgent = new SDKEditorAgent(); #elif UNITY_WEBGL initializedAgent = SDKWebGLAgent.INSTANCE; #elif UNITY_ANDROID initializedAgent = new SDKAndroidAgent(); #elif UNITY_IOS initializedAgent = SDKiOSAgent.INSTANCE; #endif } return initializedAgent; } } private static void WaitForInitialization(Action pOnInit) { lock (InitLock) { if (onInit == null) { onInit = pOnInit; } else { onInit += pOnInit; } } CheckAfterInit(); } private static void CheckAfterInit() { Action? toCall = null; lock (InitLock) { if (!Agent.IsInitialized()) { return; } toCall = onInit; onInit = null; } if (toCall != null) { toCall(); } } private static event Action? SOnAttributionResponse; #if !UNITY_WEBGL private static IRemoteConfig? remoteConfig; #endif /// /// Event that is triggered when the attribution response is received. /// public static event Action OnAttributionResponse { add { if (SOnAttributionResponse == null || !SOnAttributionResponse.GetInvocationList().Contains(value)) { SOnAttributionResponse += value; } } remove { if (SOnAttributionResponse != null && SOnAttributionResponse.GetInvocationList().Contains(value)) { SOnAttributionResponse -= value; } } } private static event Action? SOnRetargetingParameters; /// /// Event that is triggered when retargeting parameters are received. /// public static event Action OnRetargetingParameters { add { if (SOnRetargetingParameters == null || !SOnRetargetingParameters.GetInvocationList().Contains(value)) { SOnRetargetingParameters += value; } } remove { if (SOnRetargetingParameters != null && SOnRetargetingParameters.GetInvocationList().Contains(value)) { SOnRetargetingParameters -= value; } } } private static event Action? SOnPreliminaryRetargetingParameters; /// /// Event that is triggered when preliminary retargeting parameters are received. /// public static event Action OnPreliminaryRetargetingParameters { add { if (SOnPreliminaryRetargetingParameters == null || !SOnPreliminaryRetargetingParameters.GetInvocationList().Contains(value)) { SOnPreliminaryRetargetingParameters += value; } } remove { if (SOnPreliminaryRetargetingParameters != null && SOnPreliminaryRetargetingParameters.GetInvocationList().Contains(value)) { SOnPreliminaryRetargetingParameters -= value; } } } /// /// Initializes the justtrack SDK with the specified configuration. /// /// The API key for the justtrack SDK. /// Optional tracking ID. /// Optional tracking provider. /// Optional custom user ID. /// Whether to enable automatic in-app purchase tracking. /// Whether to enable debug mode. /// Whether to use manual start mode. /// Whether to enable console logging. /// Callback invoked when initialization succeeds. /// Callback invoked when initialization fails. public static void Init( 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) { if (string.IsNullOrEmpty(pApiKey)) { return; } lock (InitInProgressLock) { if (Agent.IsInitialized()) { return; } Agent.Initialize( pApiKey, pTrackingId, pTrackingProvider, pCustomUserId, pAutomaticInAppPurchaseTracking, pManualStart, pEnableConsoleLogging, pServerUrl, pBundleId, pAppVersion, pAppCode, (attribution) => { if (pOnSuccess != null) { pOnSuccess(attribution); } }, (error) => { if (pOnFailure != null) { pOnFailure(error); } }); } CheckAfterInit(); Agent.RegisterAttributionListener((attribution) => { // already on the main thread if (SOnAttributionResponse != null) { SOnAttributionResponse.Invoke(attribution); } }); #if !UNITY_WEBGL Agent.RegisterRetargetingParameterListener((parameters) => { // already on the main thread if (SOnRetargetingParameters != null) { SOnRetargetingParameters.Invoke(parameters); } }); Agent.RegisterPreliminaryRetargetingListener((parameters) => { // already on the main thread if (SOnPreliminaryRetargetingParameters != null) { SOnPreliminaryRetargetingParameters.Invoke(parameters); } }); remoteConfig = new RemoteConfig(Agent, WaitForInitialization); #endif } /// /// Starts the SDK with default configuration. /// public static void Start() { WaitForInitialization(() => Agent.Start()); } /// /// Once this API is invoked, our SDK no longer communicates with our servers and stops functioning. Useful when implementing user opt-in/opt-out. /// public static void Stop() { WaitForInitialization(() => Agent.Stop()); } /// /// Anonymize a user's installs, events, and sessions. /// /// Callback for success. /// Callback for failure. public static void Anonymize(Action pOnSuccess, Action pOnFailure) { WaitForInitialization(() => Agent.Anonymize(pOnSuccess, pOnFailure)); } /// /// Gets a value indicating whether the SDK is currently running. /// /// true if the SDK is running; otherwise, false. public static bool IsRunning() { return Agent.IsRunning(); } #if !UNITY_WEBGL /// /// Gets the retargeting parameters. /// /// Callback for success. /// Callback for failure. public static void GetRetargetingParameters(Action pOnSuccess, Action pOnFailure) { WaitForInitialization(() => Agent.GetRetargetingParameters(pOnSuccess, pOnFailure)); } /// /// Gets the preliminary retargeting parameters. /// /// The preliminary retargeting parameters, or null if not available. public static PreliminaryRetargetingParameters? GetPreliminaryRetargetingParameters() { if (!Agent.IsInitialized()) { return null; } return Agent.GetPreliminaryRetargetingParameters(); } /// /// Listens for AppLovin impressions and automatically forwards them to the justtrack backend. /// You only have to call this if you are using AppLovin and didn't enable the integration /// on the prefab or if you want to specify the supplied user id. /// /// Optional custom user ID for AppLovin. /// Callback for success. /// Callback for failure. public static void IntegrateWithAppLovin(string? customUserId, Action pOnSuccess, Action pOnFailure) { WaitForInitialization(() => Agent.IntegrateWithAppLovin(customUserId, pOnSuccess, pOnFailure)); } /// /// Automatically publish the Firebase App Instance ID to the justtrack backend. /// /// Callback for success. /// Callback for failure. public static void IntegrateWithFirebase(Action pOnSuccess, Action pOnFailure) { WaitForInitialization(() => Agent.IntegrateWithFirebase(pOnSuccess, pOnFailure)); } /// /// Configure the IronSource SDK to use the user id from justtrack and forward ad impressions as user events. /// You only have to call this if you are not using the prefab (and are using IronSource). If you call this yourself, you have /// to wait for any callback to get called before you initialize IronSource itself. /// /// Custom user identifier. /// Callback for success. /// Callback for failure. public static void IntegrateWithIronSource(string? customUserId, Action pOnSuccess, Action pOnFailure) { WaitForInitialization(() => Agent.IntegrateWithIronSource(customUserId, pOnSuccess, pOnFailure)); } /// /// Listens for UnityAds impressions and automatically forwards them to the justtrack backend. /// You only have to call this if you are using UnityAds and didn't enable the integration /// on the prefab. /// /// Callback invoked on successful integration. /// Callback invoked on integration failure with error message. public static void IntegrateWithUnityAds(Action pOnSuccess, Action pOnFailure) { WaitForInitialization(() => Agent.IntegrateWithUnityAds(pOnSuccess, pOnFailure)); } /// /// Integrates with Google ODM (On Device Mediation) on iOS. /// Automatically forwards relevant data to the justtrack backend. /// /// Callback invoked on successful integration. /// Callback invoked on integration failure with error message. public static void IntegrateWithGoogleOdm(Action pOnSuccess, Action pOnFailure) { WaitForInitialization(() => Agent.IntegrateWithGoogleOdm(pOnSuccess, pOnFailure)); } /// /// Forward an ad impression to the justtrack backend. Depending on the ad SDK we will use this /// data to display the correct amount of ad revenue your app generated. /// /// The ad impression to forward. /// Callback for success. /// Callback for failure. public static void ForwardAdImpression(AdImpression pAdImpression, Action pOnSuccess, Action pOnFailure) { if (pAdImpression.Revenue != null && (pAdImpression.Revenue.Value < 0 || pAdImpression.Revenue.Currency == null || pAdImpression.Revenue.Currency.Length != 3)) { pOnFailure("Invalid AdImpression"); return; } WaitForInitialization(() => Agent.ForwardAdImpression(pAdImpression, pOnSuccess, pOnFailure)); } /// /// Forward an ad impression to the justtrack backend. Depending on the ad SDK we will use this /// data to display the correct amount of ad revenue your app generated. /// /// The ad impression to forward. public static void ForwardAdImpression(AdImpression pAdImpression) { ForwardAdImpression(pAdImpression, () => { }, (_) => { }); } /// /// Forward a user id to the server. /// /// The custom user ID to set. public static void SetUserId(string pCustomUserId) { WaitForInitialization(() => Agent.SetCustomUserId(pCustomUserId)); } /// /// Enable and disable automatic in-app purchase tracking. /// /// Whether to enable automatic in-app purchase tracking. public static void SetAutomaticInAppPurchaseTracking(bool pEnabled) { WaitForInitialization(() => Agent.SetAutomaticInAppPurchaseTracking(pEnabled)); } /// /// Forward the firebase app instance id to the justtrack backend. /// /// The Firebase app instance ID. /// /// See https://firebase.google.com/docs/reference/android/com/google/firebase/analytics/FirebaseAnalytics#public-taskstring-getappinstanceid /// for how to obtain one. /// public static void SetFirebaseAppInstanceId(string pFirebaseAppInstanceId) { WaitForInitialization(() => Agent.SetFirebaseAppInstanceId(pFirebaseAppInstanceId)); } #endif /// /// Publish a new user event to the server. /// /// The event name to publish. [Obsolete("Use Track() instead.")] public static void PublishEvent(string pEvent) { if (string.IsNullOrEmpty(pEvent)) { return; } WaitForInitialization(() => Agent.PublishEvent(new AppEvent(pEvent))); } /// /// Publish a new user event to the server. /// /// The event to publish. [Obsolete("Use Track() instead.")] public static void PublishEvent(AppEvent pEvent) { WaitForInitialization(() => Agent.PublishEvent(pEvent)); } /// /// Publish a new user event to the server. /// /// The event to publish. /// Callback for success. /// Callback for failure. [Obsolete("Use Track() instead.")] public static void PublishEvent(string pEvent, Action pOnSuccess, Action pOnFailure) { if (string.IsNullOrEmpty(pEvent)) { pOnFailure?.Invoke("pEvent parameter cannot be null or empty."); return; } WaitForInitialization(() => Agent.PublishEvent(new AppEvent(pEvent), pOnSuccess, pOnFailure)); } /// /// Publish a new user event to the server. /// /// The event to publish. /// Callback for success. /// Callback for failure. [Obsolete("Use Track() instead.")] public static void PublishEvent(AppEvent pEvent, Action pOnSuccess, Action pOnFailure) { WaitForInitialization(() => Agent.PublishEvent(pEvent, pOnSuccess, pOnFailure)); } /// /// Track an event the user caused to the backend. Events are sent in batches to the backend and /// persisted to disk until they have successfully been sent. /// /// The event you want to track. /// Callback invoked when the event is successfully tracked. /// Callback invoked if the event could not be tracked. public static void Track(AppEvent pEvent, Action pOnSuccess, Action pOnFailure) { WaitForInitialization(() => Agent.PublishEvent(pEvent, pOnSuccess, pOnFailure)); } /// /// Track an event the user caused to the backend. Events are sent in batches to the backend and /// persisted to disk until they have successfully been sent. /// /// The event you want to track. public static void Track(AppEvent pEvent) { WaitForInitialization(() => Agent.PublishEvent(pEvent)); } /// /// Track an event the user caused to the backend. Events are sent in batches to the backend and /// persisted to disk until they have successfully been sent. /// /// The name of the event you want to track. /// Callback invoked when the event is successfully tracked. /// Callback invoked if the event could not be tracked. public static void Track(string pEventName, Action pOnSuccess, Action pOnFailure) { if (string.IsNullOrEmpty(pEventName)) { pOnFailure?.Invoke("pEventName parameter cannot be null or empty."); return; } WaitForInitialization(() => Agent.PublishEvent(new AppEvent(pEventName), pOnSuccess, pOnFailure)); } /// /// Track an event the user caused to the backend. Events are sent in batches to the backend and /// persisted to disk until they have successfully been sent. /// /// The name of the event you want to track. public static void Track(string pEventName) { if (string.IsNullOrEmpty(pEventName)) { return; } WaitForInitialization(() => Agent.PublishEvent(new AppEvent(pEventName))); } /// /// Track an event the user caused to the backend. Events are sent in batches to the backend and /// persisted to disk until they have successfully been sent. /// /// The name of the event you want to track. /// The dimensions of the event you want to track. /// Callback invoked when the event is successfully tracked. /// Callback invoked if the event could not be tracked. public static void Track(string pEventName, Dictionary pDimensions, Action pOnSuccess, Action pOnFailure) { if (string.IsNullOrEmpty(pEventName)) { pOnFailure?.Invoke("pEventName parameter cannot be null or empty."); return; } var appEvent = new AppEvent(pEventName); if (pDimensions != null) { foreach (var dimension in pDimensions) { appEvent.AddDimension(dimension.Key, dimension.Value); } } WaitForInitialization(() => Agent.PublishEvent(appEvent, pOnSuccess, pOnFailure)); } /// /// Track an event the user caused to the backend. Events are sent in batches to the backend and /// persisted to disk until they have successfully been sent. /// /// The name of the event you want to track. /// The dimensions of the event you want to track. public static void Track(string pEventName, Dictionary pDimensions) { if (string.IsNullOrEmpty(pEventName)) { return; } var appEvent = new AppEvent(pEventName); if (pDimensions != null) { foreach (var dimension in pDimensions) { appEvent.AddDimension(dimension.Key, dimension.Value); } } WaitForInitialization(() => Agent.PublishEvent(appEvent)); } #if !UNITY_WEBGL /// /// Get the unique id of the current install of the user. /// /// Callback invoked on success with the install instance ID. /// Callback invoked on failure with error message. public static void GetInstallInstanceId(Action pOnSuccess, Action pOnFailure) { WaitForInitialization(() => Agent.GetInstallInstanceId(pOnSuccess, pOnFailure)); } /// /// Gets the advertiser ID information. /// /// Callback for success. /// Callback for failure. public static void GetAdvertiserIdInfo(Action pOnSuccess, Action pOnFailure) { WaitForInitialization(() => Agent.GetAdvertiserIdInfo(pOnSuccess, pOnFailure)); } /// /// Gets the test group ID. /// /// Callback for success. /// Callback for failure. public static void GetTestGroupId(Action pOnSuccess, Action pOnFailure) { WaitForInitialization(() => Agent.GetTestGroupId(pOnSuccess, pOnFailure)); } #endif #if UNITY_IOS /// /// Request tracking authorization. If not supported on this device, checks if ad tracking is limited. /// If authorization was already permitted or denied, the callback is invoked immediately. /// Otherwise it prompts the user for authorization and returns whether the user allowed tracking. /// /// Callback that receives whether tracking is authorized. public static void RequestTrackingAuthorization(Action pOnAuthorized) { #if UNITY_EDITOR // assume we always get the autorization when running inside the editor pOnAuthorized(true); #else JustTrackSDKNativeBridgeUnity.Instance.RequestTrackingAuthorization(pOnAuthorized); #endif } #endif /// /// Determine if we are allowed to access the IDFA/GAID. On iOS, this calls RequestTrackingAuthorization /// if the justtrack SDK was configured to request the tracking permission on behalf of the developer. /// If this is not the case (or we are on Android), this method determines if we are allowed to access /// the advertiser id and returns this information in the callback. /// /// Callback that receives whether tracking is authorized. public static void OnTrackingAuthorization(Action pOnAuthorized) { #if UNITY_IOS var settings = JustTrackSettings.LoadFromResources(); if (settings.IosTrackingSettings.RequestTrackingPermission) { // if we should request it, we can just make use of the logic there to handle the case where we // already have the permission RequestTrackingAuthorization(pOnAuthorized); return; } #endif #if !UNITY_WEBGL GetAdvertiserIdInfo( (info) => { pOnAuthorized(!info.IsLimitedAdTracking); }, (error) => { // if we can't read the advertiser id, it is not available pOnAuthorized(false); }); #else // WebGL does not yet support advertiser id tracking pOnAuthorized(false); #endif } /// /// Retrieve the current SDK version. /// /// The SDK version string. public static string GetVersion() { return BuildConfig.SdkVersion; } #if !UNITY_WEBGL /// /// Gets the Remote Config interface. /// /// The Remote Config interface, or null if the SDK has not been initialized. public static IRemoteConfig? GetRemoteConfig() { return remoteConfig; } /// /// Use this method to share the information about the test group assigned to the user. /// /// The name of the A/B test. Must be shorter than 255 characters and consist only of printable ASCII characters (U+0020 to U+007E). /// The test group the user is assigned to. Must be shorter than 255 characters and consist only of printable ASCII characters (U+0020 to U+007E). /// You can add tags to your experiments which can describe the purpose. You can add a maximum of 5 tags. /// The time at which the user was assigned to the test group. Defaults to the time at which the server received the assignment request if not provided. /// Callback for success. /// Callback for failure. public static void SetExperimentVariant(string experiment, string variant, string[]? tags, DateTime? happenedAt, Action pOnSuccess, Action pOnFailure) { WaitForInitialization(() => Agent.SetExperimentVariant(experiment, variant, tags, happenedAt, pOnSuccess, pOnFailure)); } #endif #if UNITY_IOS /// /// Forwards StoreKit 2 transaction id, product id and quantity on in-app purchases and subscriptions from your app to the justtrack backend. /// Available starting with iOS 15. /// /// The transaction ID. /// The product ID. /// The quantity purchased. public static void ForwardTransactionId(string transactionId, string productId, int quantity) { WaitForInitialization(() => Agent.ForwardTransactionId(transactionId, productId, quantity)); } /// /// Returns the current tracking authorization status. /// /// The current tracking authorization status. public static AttAuthorizationStatus GetTrackingAuthorizationStatus() { return Agent.GetTrackingAuthorizationStatus(); } #endif #if UNITY_ANDROID /// /// Forwards IAP transaction of in-app purchases and subscriptions from your app to the justtrack backend. /// /// The transaction token. /// The product ID. /// The amount of revenue generated. /// Type of product INAPP or SUBS public static void ForwardTransaction(string token, string productId, Money money, ProductType productType) { WaitForInitialization(() => Agent.ForwardTransaction(token, productId, money, productType)); } #endif private JustTrackSDK() { } } }