# `beacon.ts` ## Purpose Implements `BeaconTransport` — the page-unload-safe transport used by `AnalyticsTracker.shutdown()` and any consumer wiring a `visibilitychange → hidden` flush. Browsers guarantee that `navigator.sendBeacon` will complete the request even during unload, which makes it the only reliable way to ship a last batch before navigation. When `sendBeacon` returns `false` (body too large, host rejected the beacon, navigator unavailable), the transport optionally falls back to `fetch` with `keepalive: true` or throws a typed `AnalyticsBrowserError(BEACON_REJECTED)`. ## Features - `navigator.sendBeacon` first, `fetch keepalive` fallback. - Injectable `sendBeaconFn` and `fetchFn` so happy-dom and Node test envs can drive both paths. - Graceful degradation — missing `navigator` or missing `sendBeacon` returns `false`, letting the fallback or the typed error path take over. - `fallbackToFetch` defaults to `true` so the steady-state behaviour matches what production code wants. ## Usage: Basic ```ts import { BeaconTransport } from "@suaveplan/analytics-browser/transport"; const beacon = new BeaconTransport({ endpoint: "/api/analytics" }); window.addEventListener("visibilitychange", () => { if (document.visibilityState === "hidden") { void tracker.flush(); } }); ``` ## Usage: Advanced ```ts import { BeaconTransport } from "@suaveplan/analytics-browser/transport"; const beacon = new BeaconTransport({ endpoint: "/api/analytics", fallbackToFetch: false, // strict — surface BEACON_REJECTED to the caller sendBeaconFn: (url, body) => navigator.sendBeacon(url, body), }); ``` ## API Reference - `class BeaconTransport implements Transport` - `new BeaconTransport(options)` — `options: BeaconTransportOptions { endpoint, fallbackToFetch?, sendBeaconFn?, fetchFn? }`. Throws `AnalyticsBrowserError(INVALID_OPTIONS)` when `endpoint` is empty. - `send(events) → Promise` — `true` on beacon success or `200` fallback fetch. Throws `AnalyticsBrowserError(BEACON_REJECTED)` when `sendBeaconFn` returns `false` and `fallbackToFetch === false`. - `shutdown() → Promise` — disables future sends. - `isActive() → boolean`. ## Implementation Notes `sendBeacon` is synchronous and returns a boolean that signals whether the request was queued, not whether the server received it. The transport treats `true` as success because the browser owns delivery from that point on — there is no observable response to inspect during unload. The fallback fetch sets `keepalive: true` so the same unload-safety guarantee applies; `keepalive` is supported in every browser that ships `sendBeacon`, which keeps the fallback meaningful.