c084efa78e
Mint a first-party localStorage anonId, thread it onto play/share beacons, persist it via EventController, and add all-time distinct-listener counts (site/track/release). Storage columns + indexes already existed from 16.1.
33 lines
1.7 KiB
C#
33 lines
1.7 KiB
C#
namespace DeepDrftPublic.Client.Services;
|
|
|
|
/// <summary>
|
|
/// Supplies the client-minted anonymous listener id (Phase 16 §3, wave 16.3, D5 Option A) to the
|
|
/// fire-and-forget telemetry sinks. The id is a random first-party <c>localStorage</c> GUID — opaque, no
|
|
/// PII, no fingerprinting, clearable. Split into an async warm (<see cref="EnsureLoadedAsync"/>) and a
|
|
/// synchronous read (<see cref="Current"/>) so the existing sync emit paths (the beacon sink, the share
|
|
/// tracker) need no async signature change: a caller warms the cache when it goes interactive, and the
|
|
/// emit then reads the cached value with no JS round-trip on the close/unload path.
|
|
///
|
|
/// <para>
|
|
/// Degrades to null when <c>localStorage</c> is unavailable (private mode / blocked / partitioned
|
|
/// third-party iframe) — the sink then omits the id and sends an anonId-less event. Over-counting is the
|
|
/// accepted direction of error (§3); a missing id never throws.
|
|
/// </para>
|
|
/// </summary>
|
|
public interface IAnonIdProvider
|
|
{
|
|
/// <summary>
|
|
/// The cached anon id, or null if not yet warmed or if storage is unavailable. Synchronous and safe
|
|
/// to read from the player close path and the page-unload handler, neither of which can await.
|
|
/// </summary>
|
|
string? Current { get; }
|
|
|
|
/// <summary>
|
|
/// Warm the cache from <c>localStorage</c> via JS interop (minting on first visit). Idempotent — only
|
|
/// the first successful read populates the cache; later calls are no-ops. Best-effort: a JS failure
|
|
/// (interop unavailable during prerender, storage blocked) leaves <see cref="Current"/> null and never
|
|
/// throws.
|
|
/// </summary>
|
|
ValueTask EnsureLoadedAsync();
|
|
}
|