using DeepDrftModels.Enums; namespace DeepDrftPublic.Client.Services; /// /// The emit seam for the (Phase 16 §2.1). The tracker owns the session /// lifecycle, the engagement floor, and the bucket classification but knows nothing about transport — /// it hands a finished classification to a sink, choosing only which arm fits the close that triggered /// it. Two arms exist because the close paths differ in whether the page survives the call (telemetry /// transport-resilience): /// /// — normal closes (organic end / track-switch / stop), where the page /// stays alive, go over a first-party HttpClient POST to api/event/play. A first-party /// fetch is not name-matched by tracking/fingerprinting heuristics the way a sendBeacon module is. /// — the page-unload edge (pagehide / visibility→hidden), where an /// awaited fetch would be cancelled, still goes over navigator.sendBeacon. /// /// Tests substitute a fake sink to assert floor and bucket behaviour with no transport. /// public interface IPlayEventSink { /// /// Emit one recorded play over the first-party fetch transport (normal close: end / switch / stop). /// Called at most once per session, only when the floor is crossed. Awaitable but safe to /// fire-and-forget on a live page; never throws. /// Task EmitPlayAsync(string trackEntryKey, PlayBucket bucket); /// /// Emit one recorded play over sendBeacon for the page-unload edge, where an awaited fetch /// would be cancelled as the page freezes. Synchronous and fire-and-forget; never throws. /// void EmitPlayOnUnload(string trackEntryKey, PlayBucket bucket); }