using Microsoft.JSInterop; namespace DeepDrftPublic.Client.Services; /// /// Thin C# wrapper over the window.DeepDrftLifecycle TS interop. Wraps the navigator.sendBeacon /// POST and the page-unload registration so the rest of the client never touches /// string identifiers directly. After the transport-resilience split this is the unload-edge transport /// only: normal play closes and shares go over the first-party fetch, and /// sendBeacon is retained solely for the page-unload path (pagehide / visibility→hidden) where an /// awaited fetch would be cancelled. The module is named off the former telemetry/beacon path /// (DeepDrftLifecycle, served from js/session/lifecycle.js) so even this retained fallback is /// not caught by name-based tracking/fingerprinting blockers. All calls are best-effort: a JS failure /// (module not yet loaded, interop unavailable during prerender) is swallowed — telemetry must never throw /// into the UI or the playback path. /// public sealed class BeaconInterop { private readonly IJSRuntime _js; public BeaconInterop(IJSRuntime js) { _js = js; } /// Queue a fire-and-forget POST of a JSON body to the given absolute URL. public async Task SendAsync(string url, string json) { try { await _js.InvokeAsync("DeepDrftLifecycle.send", url, json); } catch { // Module not loaded / not interactive yet — drop the event silently. } } /// Register a .NET unload callback (fires on pagehide / visibility→hidden) under a key. public async Task RegisterUnloadAsync(string key, DotNetObjectReference dotNetRef, string methodName) where T : class { try { await _js.InvokeVoidAsync("DeepDrftLifecycle.registerUnload", key, dotNetRef, methodName); } catch { // Best-effort — without the unload handler, mid-play tab-close simply isn't recorded. } } /// Detach a previously-registered unload callback. public async Task UnregisterUnloadAsync(string key) { try { await _js.InvokeVoidAsync("DeepDrftLifecycle.unregisterUnload", key); } catch { // Disposal best-effort. } } }