Files
2026-05-27 23:47:53 +01:00

2.7 KiB

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

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

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<boolean> — true on beacon success or 200 fallback fetch. Throws AnalyticsBrowserError(BEACON_REJECTED) when sendBeaconFn returns false and fallbackToFetch === false.
    • shutdown() → Promise<void> — 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.