Initial Commit
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
# `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<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.
|
||||
Reference in New Issue
Block a user