Skip to content

Commit 9c6d6b5

Browse files
authored
Merge pull request #2 from NikTheNak/main
Event batching
2 parents e627d21 + c918e43 commit 9c6d6b5

27 files changed

Lines changed: 1062 additions & 649 deletions

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
## 0.2.0
2+
3+
- Events are now sent in batches to reduce network overhead
4+
- Automatic flush of events when app loses focus
5+
- While offline, events will be enqueue and sent when the app is back online
6+
- Added an option to set the appVersion during initialization
7+
- Replaced MiniJSON for TinyJSON for better serialization
8+
- Fixed issue with OS version
9+
10+
## 0.0.1
11+
12+
- Initial release

CHANGELOG.md.meta

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Editor/AptabaseImporter.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@ internal class AptabaseImporter
1515

1616
static AptabaseImporter()
1717
{
18-
var instance = AssetDatabase.LoadAssetAtPath<AptabaseSettings>(PATH);
18+
var instance = AssetDatabase.LoadAssetAtPath<Settings>(PATH);
1919

2020
if (instance != null)
2121
return;
2222

2323
// Create new instance
24-
instance = ScriptableObject.CreateInstance<AptabaseSettings>();
24+
instance = ScriptableObject.CreateInstance<Settings>();
2525
if (!System.IO.Directory.Exists(RESOURCE_PATH))
2626
System.IO.Directory.CreateDirectory(RESOURCE_PATH);
2727

Editor/AptabaseSettingsEditor.cs

Lines changed: 0 additions & 22 deletions
This file was deleted.

Editor/SettingsEditor.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using UnityEditor;
2+
3+
namespace AptabaseSDK
4+
{
5+
[CustomEditor(typeof(Settings))]
6+
public class SettingsEditor : Editor
7+
{
8+
public override void OnInspectorGUI()
9+
{
10+
var settings = (Settings)target;
11+
12+
EditorGUILayout.PropertyField(serializedObject.FindProperty("AppKey"));
13+
14+
if (settings.AppKey.Contains("SH"))
15+
EditorGUILayout.PropertyField(serializedObject.FindProperty("SelfHostURL"));
16+
17+
EditorGUILayout.PropertyField(serializedObject.FindProperty("AppBuildNumber"));
18+
19+
EditorGUILayout.PropertyField(serializedObject.FindProperty("EnableOverride"));
20+
if (settings.EnableOverride)
21+
{
22+
EditorGUILayout.PropertyField(serializedObject.FindProperty("AppVersion"));
23+
EditorGUILayout.PropertyField(serializedObject.FindProperty("FlushInterval"));
24+
}
25+
26+
serializedObject.ApplyModifiedProperties();
27+
}
28+
}
29+
}

README.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ Then you have to set it inside the settings file located at `Aptabase/Reosources
1818

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

21-
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.
21+
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.
22+
23+
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.
2224

2325
## Usage
2426

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

36+
If you want to manually flush the event queue you can use
37+
```csharp
38+
Aptabase.Flush();
39+
```
40+
3441
A few important notes:
3542

3643
1. The SDK will automatically enhance the event with some useful information, like the OS, the app version, and other things.

Runtime/Aptabase.cs

Lines changed: 90 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,22 @@
11
using System;
22
using System.Collections.Generic;
3-
using System.Globalization;
4-
using System.Text;
3+
using System.Threading;
54
using System.Threading.Tasks;
5+
using AptabaseSDK.TinyJson;
66
using UnityEngine;
7-
using UnityEngine.Networking;
87

98
namespace AptabaseSDK
109
{
1110
public static class Aptabase
1211
{
1312
private static string _sessionId = NewSessionId();
13+
private static Dispatcher _dispatcher;
14+
private static EnvironmentInfo _env;
15+
private static Settings _settings;
1416

1517
private static DateTime _lastTouched = DateTime.UtcNow;
1618
private static string _baseURL;
17-
19+
1820
private static readonly TimeSpan _sessionTimeout = TimeSpan.FromMinutes(60);
1921
private static readonly Dictionary<string, string> _hosts = new()
2022
{
@@ -24,16 +26,14 @@ public static class Aptabase
2426
{ "SH", "" },
2527
};
2628

27-
private const string EVENT_ENDPOINT = "/api/v0/event";
28-
private const string SDK_VERSION = "Aptabase.Unity@0.0.1";
29-
30-
private static AptabaseSettings _settings;
29+
private static int _flushTimer;
30+
private static CancellationTokenSource _pollingCancellationTokenSource;
3131

3232
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
3333
private static void Initialize()
3434
{
3535
//load settings
36-
_settings = Resources.Load<AptabaseSettings>("AptabaseSettings");
36+
_settings = Resources.Load<Settings>("AptabaseSettings");
3737
if (_settings == null)
3838
{
3939
Debug.LogWarning("Aptabase Settings not found. Tracking will be disabled");
@@ -48,8 +48,70 @@ private static void Initialize()
4848
Debug.LogWarning($"The Aptabase App Key {key} is invalid. Tracking will be disabled");
4949
return;
5050
}
51+
52+
_env = Environment.GetEnvironmentInfo(Version.GetVersionInfo(_settings));
5153

5254
_baseURL = GetBaseUrl(parts[1]);
55+
_dispatcher = new Dispatcher(_settings.AppKey, _baseURL, _env);
56+
57+
//create listener
58+
var eventFocusHandler = new GameObject("AptabaseService");
59+
eventFocusHandler.AddComponent<AptabaseService>();
60+
}
61+
62+
private static async void StartPolling(int flushTimer)
63+
{
64+
StopPolling();
65+
66+
_flushTimer = flushTimer;
67+
_pollingCancellationTokenSource = new CancellationTokenSource();
68+
69+
while (_pollingCancellationTokenSource is { IsCancellationRequested: false })
70+
{
71+
try
72+
{
73+
await Task.Delay(_flushTimer, _pollingCancellationTokenSource.Token);
74+
Flush();
75+
}
76+
catch (TaskCanceledException)
77+
{
78+
break;
79+
}
80+
}
81+
}
82+
83+
private static void StopPolling()
84+
{
85+
if (_flushTimer <= 0)
86+
return;
87+
88+
_pollingCancellationTokenSource?.Cancel();
89+
_pollingCancellationTokenSource = null;
90+
_flushTimer = 0;
91+
}
92+
93+
public static void OnApplicationFocus(bool hasFocus)
94+
{
95+
if (hasFocus)
96+
{
97+
StartPolling(GetFlushInterval());
98+
}
99+
else
100+
{
101+
Flush();
102+
StopPolling();
103+
}
104+
}
105+
106+
private static string EvalSessionId()
107+
{
108+
var now = DateTime.UtcNow;
109+
var timeSince = now.Subtract(_lastTouched);
110+
if (timeSince >= _sessionTimeout)
111+
_sessionId = NewSessionId();
112+
113+
_lastTouched = now;
114+
return _sessionId;
53115
}
54116

55117
private static string GetBaseUrl(string region)
@@ -68,71 +130,35 @@ private static string GetBaseUrl(string region)
68130
return _hosts[region];
69131
}
70132

71-
public static void TrackEvent(string eventName, Dictionary<string, object> props = null)
133+
public static void Flush()
72134
{
73-
SendEvent(eventName, props);
135+
_dispatcher.Flush();
74136
}
75137

76-
private static async void SendEvent(string eventName, Dictionary<string, object> props)
138+
public static void TrackEvent(string eventName, Dictionary<string, object> props = null)
77139
{
78140
if (string.IsNullOrEmpty(_baseURL))
79141
return;
80142

81-
try
143+
props ??= new Dictionary<string, object>();
144+
var eventData = new Event()
82145
{
83-
var now = DateTime.UtcNow;
84-
var timeSince = now.Subtract(_lastTouched);
85-
if (timeSince >= _sessionTimeout)
86-
_sessionId = NewSessionId();
87-
88-
_lastTouched = now;
89-
90-
props ??= new Dictionary<string, object>();
91-
92-
//create the main dictionary for EventData
93-
var eventData = Json.Serialize(new Dictionary<string, object>(5)
94-
{
95-
{ "timestamp", DateTime.UtcNow.ToString("o") },
96-
{ "sessionId", _sessionId },
97-
{ "eventName", eventName },
98-
{ "systemProps", new Dictionary<string, object>(7)
99-
{
100-
{ "isDebug", Application.isEditor || Debug.isDebugBuild },
101-
{ "osName", Application.platform.ToString() },
102-
{ "osVersion", SystemInfo.operatingSystem },
103-
{ "locale", CultureInfo.CurrentCulture.Name },
104-
{ "appVersion", Application.version },
105-
{ "appBuildNumber", _settings.BuildNumber },
106-
{ "sdkVersion", SDK_VERSION }
107-
}},
108-
{ "props", props }
109-
});
110-
111-
//send request to end point
112-
using var www = new UnityWebRequest($"{_baseURL}{EVENT_ENDPOINT}",
113-
UnityWebRequest.kHttpVerbPOST);
114-
www.SetRequestHeader("Content-Type", "application/json");
115-
www.SetRequestHeader("App-Key", _settings.AppKey);
116-
var requestBytes = Encoding.UTF8.GetBytes(eventData);
117-
www.uploadHandler = new UploadHandlerRaw(requestBytes);
118-
www.downloadHandler = new DownloadHandlerBuffer();
119-
120-
var operation = www.SendWebRequest();
146+
timestamp = DateTime.UtcNow.ToString("o"),
147+
sessionId = EvalSessionId(),
148+
eventName = eventName,
149+
systemProps = _env,
150+
props = props
151+
};
152+
153+
_dispatcher.Enqueue(eventData);
154+
}
121155

122-
//wait for complete
123-
while (!operation.isDone)
124-
await Task.Yield();
156+
private static int GetFlushInterval()
157+
{
158+
if (_settings.EnableOverride && _settings.FlushInterval > 0)
159+
return Mathf.Max(0, _settings.FlushInterval);
125160

126-
//handle results
127-
if (www.result != UnityWebRequest.Result.Success)
128-
{
129-
Debug.LogError($"Failed to perform TrackEvent due to {www.responseCode} and response body {www.error}");
130-
}
131-
}
132-
catch (Exception e)
133-
{
134-
Debug.LogError($"Failed to perform TrackEvent {e}");
135-
}
161+
return _env.isDebug ? 2000 : 60000;
136162
}
137163

138164
private static string NewSessionId() => Guid.NewGuid().ToString().ToLower();

Runtime/AptabaseService.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using UnityEngine;
2+
3+
namespace AptabaseSDK
4+
{
5+
public class AptabaseService : MonoBehaviour
6+
{
7+
private void Awake()
8+
{
9+
DontDestroyOnLoad(gameObject);
10+
}
11+
12+
private void OnApplicationFocus(bool hasFocus)
13+
{
14+
Aptabase.OnApplicationFocus(hasFocus);
15+
}
16+
}
17+
}
Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)