-
Notifications
You must be signed in to change notification settings - Fork 35
WebGL: Add offline support #265
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,6 +8,7 @@ | |
| using Backtrace.Unity.Runtime.Native; | ||
| using Backtrace.Unity.Services; | ||
| using Backtrace.Unity.Types; | ||
| using Backtrace.Unity.WebGL; | ||
| using System; | ||
| using System.Collections; | ||
| using System.Collections.Generic; | ||
|
|
@@ -131,6 +132,17 @@ internal System.Random Random | |
| /// </summary> | ||
| private HashSet<string> _clientReportAttachments; | ||
|
|
||
| #if UNITY_WEBGL | ||
| private WebGLOfflineDatabase _webglOfflineDatabase; | ||
| private Coroutine _webglOfflineReplayCoroutine; | ||
| private bool _webglOfflineReplayInProgress; | ||
|
|
||
| // WebGL builds may have multiple BacktraceClient instances if DestroyOnLoad is enabled. | ||
| // Ensure only one instance performs offline queue replay to avoid duplicate sends. | ||
| private static BacktraceClient _webglOfflineReplayOwner; | ||
| #endif | ||
|
Comment on lines
+135
to
+143
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It should be moved to BacktraceDatabase - BacktraceDatabase offerts the same Untiy scope. |
||
|
|
||
|
|
||
| /// <summary> | ||
| /// Attribute object accessor | ||
| /// </summary> | ||
|
|
@@ -518,6 +530,9 @@ public static BacktraceClient Initialize(string url, Dictionary<string, string> | |
| public void OnDisable() | ||
| { | ||
| Enabled = false; | ||
| #if UNITY_WEBGL | ||
| StopWebGLOfflineReplay(); | ||
| #endif | ||
| } | ||
|
|
||
| public void Refresh() | ||
|
|
@@ -553,6 +568,11 @@ public void Refresh() | |
| #endif | ||
| ); | ||
| BacktraceApi.EnablePerformanceStatistics = Configuration.PerformanceStatistics; | ||
| #if UNITY_WEBGL | ||
| // JS page lifecycle hooks | ||
| BacktraceWebGLSync.TryInstallPageLifecycleHooks(); | ||
| #endif | ||
|
|
||
|
|
||
| if (!Configuration.DestroyOnLoad) | ||
| { | ||
|
|
@@ -579,6 +599,9 @@ public void Refresh() | |
| } | ||
| } | ||
| } | ||
| #if UNITY_WEBGL | ||
| InitializeWebGLOfflineSupport(); | ||
| #endif | ||
| if (Database != null) | ||
| { | ||
| // send minidump files generated by unity engine or unity game, not captured by Windows native integration | ||
|
|
@@ -598,6 +621,178 @@ public void Refresh() | |
| AttributeProvider.AddDynamicAttributeProvider(_nativeClient); | ||
| } | ||
| } | ||
| #if UNITY_WEBGL | ||
| private void InitializeWebGLOfflineSupport() | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It should be abstracted in the BacktraceDatabase implementation - we have the Start/Awake support there. |
||
| { | ||
| // WebGL offline queue is only active when Backtrace offline database is enabled in configuration. | ||
| if (Configuration == null || !Configuration.Enabled) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| if (_webglOfflineDatabase == null) | ||
| { | ||
| _webglOfflineDatabase = new WebGLOfflineDatabase(Configuration); | ||
| } | ||
|
|
||
| // Clean up corrupted/invalid entries and enforce size/count bounds on start. | ||
| _webglOfflineDatabase.Compact(); | ||
|
|
||
| // auto-send stored reports if AutoSendMode is enabled. | ||
| if (!Configuration.AutoSendMode) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| // Avoid multiple BacktraceClient instances replaying the same persisted queue. | ||
| if (_webglOfflineReplayOwner != null && _webglOfflineReplayOwner != this) | ||
| { | ||
| return; | ||
| } | ||
| _webglOfflineReplayOwner = this; | ||
|
|
||
| if (_webglOfflineReplayCoroutine == null) | ||
| { | ||
| _webglOfflineReplayCoroutine = StartCoroutine(WebGLOfflineReplayLoop()); | ||
| } | ||
| } | ||
|
|
||
| private IEnumerator WebGLOfflineReplayLoop() | ||
| { | ||
| // Wait one frame to allow other initialization to complete. | ||
| yield return null; | ||
|
|
||
| while (Enabled && Configuration != null && Configuration.Enabled && Configuration.AutoSendMode) | ||
| { | ||
| if (_webglOfflineDatabase != null && | ||
| !_webglOfflineDatabase.IsEmpty && | ||
| BacktraceApi != null && | ||
| RequestHandler == null) | ||
| { | ||
| yield return SendCachedWebGLReports(); | ||
| } | ||
|
|
||
| var retryInterval = Configuration.RetryInterval > 0 | ||
| ? Configuration.RetryInterval | ||
| : BacktraceConfiguration.DefaultRetryInterval; | ||
|
|
||
| yield return new WaitForSecondsRealtime(retryInterval); | ||
| } | ||
|
|
||
| _webglOfflineReplayCoroutine = null; | ||
| _webglOfflineReplayInProgress = false; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Send any reports cached in PlayerPrefs while the WebGL client was offline and the BacktraceDatabase was not available or disabled. | ||
| /// </summary> | ||
| private IEnumerator SendCachedWebGLReports() | ||
| { | ||
| if (_webglOfflineReplayInProgress) | ||
| { | ||
| yield break; | ||
| } | ||
|
|
||
| _webglOfflineReplayInProgress = true; | ||
|
|
||
| try | ||
| { | ||
| if (_webglOfflineDatabase == null || BacktraceApi == null || Configuration == null) | ||
| { | ||
| yield break; | ||
| } | ||
|
|
||
| // RequestHandler overrides normal send path. We can't replay stored JSON through it. | ||
| if (RequestHandler != null) | ||
| { | ||
| yield break; | ||
| } | ||
|
|
||
| var retryOrder = Configuration.RetryOrder; | ||
| var retryLimit = Mathf.Max(1, Configuration.RetryLimit); | ||
|
|
||
| while (_webglOfflineDatabase.TryPeek(retryOrder, out var record)) | ||
| { | ||
| if (record == null) | ||
| { | ||
| yield break; | ||
| } | ||
|
|
||
| // Respect client-side report rate limiting. | ||
| if (_reportLimitWatcher != null && !_reportLimitWatcher.WatchReport(DateTimeHelper.Timestamp())) | ||
| { | ||
| yield break; | ||
| } | ||
|
|
||
| // Give up on records that exceeded retry limit. | ||
| if (record.attempts >= retryLimit) | ||
| { | ||
| _webglOfflineDatabase.Remove(record.uuid); | ||
| yield return null; | ||
| continue; | ||
| } | ||
|
|
||
| var queryAttributes = new Dictionary<string, string>(); | ||
| if (record.deduplication != 0) | ||
| { | ||
| queryAttributes["_mod_duplicate"] = record.deduplication.ToString(CultureInfo.InvariantCulture); | ||
| } | ||
|
|
||
| BacktraceResult sendResult = null; | ||
| yield return BacktraceApi.Send( | ||
| record.json, | ||
| record.attachments ?? new string[0], | ||
| queryAttributes, | ||
| result => sendResult = result); | ||
|
|
||
| if (sendResult == null) | ||
| { | ||
| _webglOfflineDatabase.IncrementAttempts(record.uuid); | ||
| yield break; | ||
| } | ||
|
|
||
| if (sendResult.Status == BacktraceResultStatus.Ok || sendResult.Status == BacktraceResultStatus.Empty) | ||
| { | ||
| _webglOfflineDatabase.Remove(record.uuid); | ||
| yield return null; | ||
| continue; | ||
| } | ||
|
|
||
| // Treat all non-success status as retryable attempts for the WebGL offline replay. | ||
| if (sendResult.Status == BacktraceResultStatus.ServerError || | ||
| sendResult.Status == BacktraceResultStatus.NetworkError || | ||
| sendResult.Status == BacktraceResultStatus.LimitReached) | ||
| { | ||
| _webglOfflineDatabase.IncrementAttempts(record.uuid); | ||
| yield break; | ||
| } | ||
|
|
||
| yield break; | ||
| } | ||
| } | ||
| finally | ||
| { | ||
| _webglOfflineReplayInProgress = false; | ||
| } | ||
| } | ||
|
|
||
| private void StopWebGLOfflineReplay() | ||
| { | ||
| if (_webglOfflineReplayCoroutine != null) | ||
| { | ||
| StopCoroutine(_webglOfflineReplayCoroutine); | ||
| _webglOfflineReplayCoroutine = null; | ||
| } | ||
|
|
||
| _webglOfflineReplayInProgress = false; | ||
|
|
||
| if (_webglOfflineReplayOwner == this) | ||
| { | ||
| _webglOfflineReplayOwner = null; | ||
| } | ||
| } | ||
| #endif | ||
|
|
||
|
|
||
| public bool EnableBreadcrumbsSupport() | ||
| { | ||
|
|
@@ -673,6 +868,10 @@ private void StartupMetrics() | |
|
|
||
| private void OnApplicationQuit() | ||
| { | ||
| #if UNITY_WEBGL | ||
| StopWebGLOfflineReplay(); | ||
| BacktraceWebGLSync.TrySyncFileSystem(true); | ||
| #endif | ||
| if (_nativeClient != null) | ||
| { | ||
| _nativeClient.Disable(); | ||
|
|
@@ -721,6 +920,10 @@ private void LateUpdate() | |
| private void OnDestroy() | ||
| { | ||
| Enabled = false; | ||
| #if UNITY_WEBGL | ||
| StopWebGLOfflineReplay(); | ||
| BacktraceWebGLSync.TrySyncFileSystem(true); | ||
| #endif | ||
| if (_breadcrumbs != null) | ||
| { | ||
| _breadcrumbs.FromMonoBehavior("Backtrace Client: OnDestroy", LogType.Warning, null); | ||
|
|
@@ -867,7 +1070,8 @@ private IEnumerator CollectDataAndSend(BacktraceReport report, Action<BacktraceR | |
| } | ||
| BacktraceDatabaseRecord record = null; | ||
|
|
||
| if (Database != null && Database.Enabled()) | ||
| bool databaseEnabled = Database != null && Database.Enabled(); | ||
| if (databaseEnabled) | ||
| { | ||
| yield return WaitForFrame.Wait(); | ||
| if (EnablePerformanceStatistics) | ||
|
|
@@ -880,6 +1084,9 @@ record = Database.Add(data); | |
| { | ||
| //Extend backtrace data with additional attachments from backtrace database | ||
| data = record.BacktraceData; | ||
| #if UNITY_WEBGL | ||
| BacktraceWebGLSync.TrySyncFileSystem(); | ||
| #endif | ||
| if (EnablePerformanceStatistics) | ||
| { | ||
| stopWatch.Stop(); | ||
|
|
@@ -938,11 +1145,22 @@ record = Database.Add(data); | |
| if (record != null) | ||
| { | ||
| record.Unlock(); | ||
| if (Database != null && result.Status != BacktraceResultStatus.ServerError && result.Status != BacktraceResultStatus.NetworkError) | ||
| if (databaseEnabled && result.Status != BacktraceResultStatus.ServerError && result.Status != BacktraceResultStatus.NetworkError) | ||
| { | ||
| Database.Delete(record); | ||
| } | ||
| } | ||
| #if UNITY_WEBGL | ||
| if (!databaseEnabled && | ||
| Configuration != null && | ||
| Configuration.Enabled && | ||
| _webglOfflineDatabase != null && | ||
| (result.Status == BacktraceResultStatus.ServerError || result.Status == BacktraceResultStatus.NetworkError)) | ||
| { | ||
| _webglOfflineDatabase.Enqueue(data.Uuid, json, data.Attachments, data.Deduplication); | ||
| } | ||
| #endif | ||
|
|
||
| //check if there is more errors to send | ||
| //handle inner exception | ||
| HandleInnerException(report); | ||
|
|
@@ -1073,6 +1291,23 @@ internal void OnApplicationPause(bool pause) | |
| { | ||
| _nativeClient.PauseAnrThread(pause); | ||
| } | ||
| #if UNITY_WEBGL | ||
| if (pause) | ||
| { | ||
| BacktraceWebGLSync.TrySyncFileSystem(true); | ||
| } | ||
| #endif | ||
|
|
||
| } | ||
|
|
||
| private void OnApplicationFocus(bool hasFocus) | ||
| { | ||
| #if UNITY_WEBGL | ||
| if (!hasFocus) | ||
| { | ||
| BacktraceWebGLSync.TrySyncFileSystem(true); | ||
| } | ||
| #endif | ||
| } | ||
|
|
||
| internal void HandleUnityBackgroundException(string message, string stackTrace, LogType type) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we already have a static Backtrace instance available here.