Skip to content

Commit

Permalink
Merge pull request #2 from NikTheNak/main
Browse files Browse the repository at this point in the history
Event batching
  • Loading branch information
goenning authored Sep 8, 2023
2 parents e627d21 + c918e43 commit 9c6d6b5
Show file tree
Hide file tree
Showing 27 changed files with 1,062 additions and 649 deletions.
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
## 0.2.0

- Events are now sent in batches to reduce network overhead
- Automatic flush of events when app loses focus
- While offline, events will be enqueue and sent when the app is back online
- Added an option to set the appVersion during initialization
- Replaced MiniJSON for TinyJSON for better serialization
- Fixed issue with OS version

## 0.0.1

- Initial release
7 changes: 7 additions & 0 deletions CHANGELOG.md.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Editor/AptabaseImporter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ internal class AptabaseImporter

static AptabaseImporter()
{
var instance = AssetDatabase.LoadAssetAtPath<AptabaseSettings>(PATH);
var instance = AssetDatabase.LoadAssetAtPath<Settings>(PATH);

if (instance != null)
return;

// Create new instance
instance = ScriptableObject.CreateInstance<AptabaseSettings>();
instance = ScriptableObject.CreateInstance<Settings>();
if (!System.IO.Directory.Exists(RESOURCE_PATH))
System.IO.Directory.CreateDirectory(RESOURCE_PATH);

Expand Down
22 changes: 0 additions & 22 deletions Editor/AptabaseSettingsEditor.cs

This file was deleted.

29 changes: 29 additions & 0 deletions Editor/SettingsEditor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using UnityEditor;

namespace AptabaseSDK
{
[CustomEditor(typeof(Settings))]
public class SettingsEditor : Editor
{
public override void OnInspectorGUI()
{
var settings = (Settings)target;

EditorGUILayout.PropertyField(serializedObject.FindProperty("AppKey"));

if (settings.AppKey.Contains("SH"))
EditorGUILayout.PropertyField(serializedObject.FindProperty("SelfHostURL"));

EditorGUILayout.PropertyField(serializedObject.FindProperty("AppBuildNumber"));

EditorGUILayout.PropertyField(serializedObject.FindProperty("EnableOverride"));
if (settings.EnableOverride)
{
EditorGUILayout.PropertyField(serializedObject.FindProperty("AppVersion"));
EditorGUILayout.PropertyField(serializedObject.FindProperty("FlushInterval"));
}

serializedObject.ApplyModifiedProperties();
}
}
}
File renamed without changes.
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ Then you have to set it inside the settings file located at `Aptabase/Reosources

Based on the key, your `Host` will be selected. In the case of self-hosted versions a new `SelfHostURL` field will appear for input.

App Version is automatically detected, but you will need to provide a `BuildNumber` as it may vary across different platforms. This allows you to specify a platform-specific build number to ensure accurate version tracking and compatibility.
App Version is automatically detected, but you can override it with the `AppVersion` field. You may want to provide an `AppBuildNumber` as it may vary across different platforms. This allows you to specify a platform-specific build number to ensure accurate version tracking and compatibility.

Events are batched and sent every 60 seconds in production and 2 seconds in development by default. You can override these values with the `FlushInterval` field by inputting desired time in milliseconds.

## Usage

Expand All @@ -31,6 +33,11 @@ Aptabase.TrackEvent("app_started", new Dictionary<string, object>
});
```

If you want to manually flush the event queue you can use
```csharp
Aptabase.Flush();
```

A few important notes:

1. The SDK will automatically enhance the event with some useful information, like the OS, the app version, and other things.
Expand Down
154 changes: 90 additions & 64 deletions Runtime/Aptabase.cs
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using AptabaseSDK.TinyJson;
using UnityEngine;
using UnityEngine.Networking;

namespace AptabaseSDK
{
public static class Aptabase
{
private static string _sessionId = NewSessionId();
private static Dispatcher _dispatcher;
private static EnvironmentInfo _env;
private static Settings _settings;

private static DateTime _lastTouched = DateTime.UtcNow;
private static string _baseURL;

private static readonly TimeSpan _sessionTimeout = TimeSpan.FromMinutes(60);
private static readonly Dictionary<string, string> _hosts = new()
{
Expand All @@ -24,16 +26,14 @@ public static class Aptabase
{ "SH", "" },
};

private const string EVENT_ENDPOINT = "/api/v0/event";
private const string SDK_VERSION = "Aptabase.Unity@0.0.1";

private static AptabaseSettings _settings;
private static int _flushTimer;
private static CancellationTokenSource _pollingCancellationTokenSource;

[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
private static void Initialize()
{
//load settings
_settings = Resources.Load<AptabaseSettings>("AptabaseSettings");
_settings = Resources.Load<Settings>("AptabaseSettings");
if (_settings == null)
{
Debug.LogWarning("Aptabase Settings not found. Tracking will be disabled");
Expand All @@ -48,8 +48,70 @@ private static void Initialize()
Debug.LogWarning($"The Aptabase App Key {key} is invalid. Tracking will be disabled");
return;
}

_env = Environment.GetEnvironmentInfo(Version.GetVersionInfo(_settings));

_baseURL = GetBaseUrl(parts[1]);
_dispatcher = new Dispatcher(_settings.AppKey, _baseURL, _env);

//create listener
var eventFocusHandler = new GameObject("AptabaseService");
eventFocusHandler.AddComponent<AptabaseService>();
}

private static async void StartPolling(int flushTimer)
{
StopPolling();

_flushTimer = flushTimer;
_pollingCancellationTokenSource = new CancellationTokenSource();

while (_pollingCancellationTokenSource is { IsCancellationRequested: false })
{
try
{
await Task.Delay(_flushTimer, _pollingCancellationTokenSource.Token);
Flush();
}
catch (TaskCanceledException)
{
break;
}
}
}

private static void StopPolling()
{
if (_flushTimer <= 0)
return;

_pollingCancellationTokenSource?.Cancel();
_pollingCancellationTokenSource = null;
_flushTimer = 0;
}

public static void OnApplicationFocus(bool hasFocus)
{
if (hasFocus)
{
StartPolling(GetFlushInterval());
}
else
{
Flush();
StopPolling();
}
}

private static string EvalSessionId()
{
var now = DateTime.UtcNow;
var timeSince = now.Subtract(_lastTouched);
if (timeSince >= _sessionTimeout)
_sessionId = NewSessionId();

_lastTouched = now;
return _sessionId;
}

private static string GetBaseUrl(string region)
Expand All @@ -68,71 +130,35 @@ private static string GetBaseUrl(string region)
return _hosts[region];
}

public static void TrackEvent(string eventName, Dictionary<string, object> props = null)
public static void Flush()
{
SendEvent(eventName, props);
_dispatcher.Flush();
}

private static async void SendEvent(string eventName, Dictionary<string, object> props)
public static void TrackEvent(string eventName, Dictionary<string, object> props = null)
{
if (string.IsNullOrEmpty(_baseURL))
return;

try
props ??= new Dictionary<string, object>();
var eventData = new Event()
{
var now = DateTime.UtcNow;
var timeSince = now.Subtract(_lastTouched);
if (timeSince >= _sessionTimeout)
_sessionId = NewSessionId();

_lastTouched = now;

props ??= new Dictionary<string, object>();

//create the main dictionary for EventData
var eventData = Json.Serialize(new Dictionary<string, object>(5)
{
{ "timestamp", DateTime.UtcNow.ToString("o") },
{ "sessionId", _sessionId },
{ "eventName", eventName },
{ "systemProps", new Dictionary<string, object>(7)
{
{ "isDebug", Application.isEditor || Debug.isDebugBuild },
{ "osName", Application.platform.ToString() },
{ "osVersion", SystemInfo.operatingSystem },
{ "locale", CultureInfo.CurrentCulture.Name },
{ "appVersion", Application.version },
{ "appBuildNumber", _settings.BuildNumber },
{ "sdkVersion", SDK_VERSION }
}},
{ "props", props }
});

//send request to end point
using var www = new UnityWebRequest($"{_baseURL}{EVENT_ENDPOINT}",
UnityWebRequest.kHttpVerbPOST);
www.SetRequestHeader("Content-Type", "application/json");
www.SetRequestHeader("App-Key", _settings.AppKey);
var requestBytes = Encoding.UTF8.GetBytes(eventData);
www.uploadHandler = new UploadHandlerRaw(requestBytes);
www.downloadHandler = new DownloadHandlerBuffer();

var operation = www.SendWebRequest();
timestamp = DateTime.UtcNow.ToString("o"),
sessionId = EvalSessionId(),
eventName = eventName,
systemProps = _env,
props = props
};

_dispatcher.Enqueue(eventData);
}

//wait for complete
while (!operation.isDone)
await Task.Yield();
private static int GetFlushInterval()
{
if (_settings.EnableOverride && _settings.FlushInterval > 0)
return Mathf.Max(0, _settings.FlushInterval);

//handle results
if (www.result != UnityWebRequest.Result.Success)
{
Debug.LogError($"Failed to perform TrackEvent due to {www.responseCode} and response body {www.error}");
}
}
catch (Exception e)
{
Debug.LogError($"Failed to perform TrackEvent {e}");
}
return _env.isDebug ? 2000 : 60000;
}

private static string NewSessionId() => Guid.NewGuid().ToString().ToLower();
Expand Down
17 changes: 17 additions & 0 deletions Runtime/AptabaseService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using UnityEngine;

namespace AptabaseSDK
{
public class AptabaseService : MonoBehaviour
{
private void Awake()
{
DontDestroyOnLoad(gameObject);
}

private void OnApplicationFocus(bool hasFocus)
{
Aptabase.OnApplicationFocus(hasFocus);
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 0 additions & 11 deletions Runtime/AptabaseSettings.cs

This file was deleted.

Loading

0 comments on commit 9c6d6b5

Please sign in to comment.