fix(telemetry): first-party fetch for play/share, beacon only on unload
Route normal play closes (end/switch/stop) and all shares through a same-origin HttpClient POST so privacy-hardened browsers stop blocking them; keep sendBeacon for the tab-unload edge. Rename the JS module off telemetry/beacon to session/ lifecycle so the retained fallback isn't name-matched. No new data or identifiers.
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
using System.Text;
|
||||
|
||||
namespace DeepDrftPublic.Client.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Production <see cref="IEventPoster"/>: a first-party, same-origin <see cref="System.Net.Http.HttpClient"/>
|
||||
/// POST to the site's own <c>api/event/*</c> 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 <c>telemetry/beacon</c>-named <c>sendBeacon</c>
|
||||
/// 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 <see cref="BeaconInterop"/>, where an awaited
|
||||
/// fetch would be cancelled as the page freezes.
|
||||
///
|
||||
/// <para>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.</para>
|
||||
/// </summary>
|
||||
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.
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user