using System.Text; namespace DeepDrftPublic.Client.Services; /// /// Production : a first-party, same-origin /// POST to the site's own api/event/* proxy (telemetry transport-resilience). A fetch to the host's /// own origin is not third-party tracking and is not caught by the name-based heuristics (Firefox /// Fingerprinting / Tracking Protection) that block a telemetry/beacon-named sendBeacon /// module. Used for the awaitable play-close paths (organic end / track-switch / stop) and every share /// event; only the rare tab-unload edge still goes through , where an awaited /// fetch would be cancelled as the page freezes. /// /// Best-effort and non-throwing by contract: a failed POST (offline, blocked, server error) is /// swallowed so a dropped telemetry event never throws into the UI or the playback path — identical /// posture to the beacon transport. /// public sealed class HttpEventPoster : IEventPoster { // The default factory client carries no base address; the sink/tracker pass an absolute same-origin // URL built from NavigationManager.BaseUri, so the POST targets the host proxy regardless of how the // named clients are configured. The default client uses the browser fetch handler in WASM, which is // exactly the first-party request the heuristic blockers permit. private readonly IHttpClientFactory _httpClientFactory; public HttpEventPoster(IHttpClientFactory httpClientFactory) { _httpClientFactory = httpClientFactory; } public async Task PostAsync(string url, string json) { try { using var client = _httpClientFactory.CreateClient(); using var content = new StringContent(json, Encoding.UTF8, "application/json"); // Best-effort: the server records the event; we do not act on the status either way. using var response = await client.PostAsync(url, content); } catch { // Swallow — a dropped telemetry event is acceptable; telemetry must never throw into the UI // or the playback path. Mirrors the beacon's fire-and-forget contract. } } }