2.7 KiB
batch.ts
Purpose
Implements BatchTransport — the HTTP transport used by AnalyticsTracker for the steady-state, non-unload flush path. Every call posts a JSON-encoded { events } body to the configured endpoint, wrapped in an AbortController whose timeout is armed via the injected TimersLike. The transport short-circuits on 4xx responses (no retry), retries with exponential backoff on 5xx / network errors, and translates any post-abort failure into a typed AnalyticsBrowserError(TRANSPORT_TIMEOUT) so callers can discriminate timeouts from other transport failures.
Features
AbortController+timeoutMson every fetch — guaranteed bounded latency.4xxshort-circuit — no retry storm on stable client errors.- Exponential backoff on
5xx/ network —retryDelay * 2 ** attempt. - Injectable
fetchFnandtimers— every browser-API touch is reachable from tests without DOM globals. - Idempotent
shutdown()— futuresend()calls are no-ops returningtrue.
Usage: Basic
import { BatchTransport } from "@suaveplan/analytics-browser/transport";
const transport = new BatchTransport({ endpoint: "/api/analytics" });
await transport.send(events);
Usage: Advanced
import { BatchTransport } from "@suaveplan/analytics-browser/transport";
const transport = new BatchTransport({
endpoint: "https://collector.example.com/v1/events",
maxRetries: 5,
retryDelay: 500,
timeoutMs: 5_000,
fetchFn: async (url, init) => {
const req = new Request(url, init);
return fetch(req, { credentials: "include" });
},
});
API Reference
class BatchTransport implements Transportnew BatchTransport(options)—options: BatchTransportOptions { endpoint, maxRetries?, retryDelay?, timeoutMs?, fetchFn?, timers? }. ThrowsAnalyticsBrowserError(INVALID_OPTIONS)whenendpointis empty.send(events) → Promise<boolean>—trueon success,falseon4xxor exhausted retries. ThrowsAnalyticsBrowserError(TRANSPORT_TIMEOUT)when the fetch is aborted by the timeout.shutdown() → Promise<void>— disables further sends.isActive() → boolean.
Implementation Notes
The timeout setTimer callback aborts the controller with an AnalyticsBrowserError(TRANSPORT_TIMEOUT) as the reason. If the host's fetch rejects with that exact error, the transport rethrows it as-is. If the fetch rejects with a different error after the signal has aborted (e.g. a DOMException synthesised by the runtime), the transport still maps the failure to TRANSPORT_TIMEOUT because the signal's aborted state is the authoritative reason. Otherwise the failure is treated as a generic retryable network error and the loop continues until maxRetries is exhausted.