/** * The browser error outbox (#1088) — how a client failure reaches the durable log, reliably. * * The reporter it replaces sent each failure once, fire-and-forget: it deduped BEFORE sending, ignored the * HTTP answer and swallowed a rejected fetch. So a report sent while offline, or refused with a 503, was * gone — and the 30-second dedupe then suppressed the next real report of the same failure too. * * Now every report is an EVENT with a stable id, kept in a bounded outbox until the platform acknowledges * it: * * - **Delivered = acknowledged.** 200 (stored) and 202 (spooled server-side, #1084) remove it. 429, 5xx * and a network failure keep it and retry with backoff; a 400/413/422 can never succeed, so it is dropped * and counted. The id makes a retry idempotent server-side, so "sent but the answer was lost" is safe. * - **Occurrences are counted, not discarded.** A repeat of a failure still waiting folds into its event's * `count`. A repeat within 30 s of a DELIVERED report waits out the 30 s and is sent as one event with * its count — the rate limit holds, nothing is silently dropped, and the throttle starts only on success. * - **Offline and reload.** The outbox lives in `localStorage` (≤ 50 events), so a report made offline or * just before a reload is sent on the next load or `online` event. Overflow keeps the oldest events and * counts the rest (`droppedReports` rides on the next delivered event). * - **Attribution is never upgraded.** An event made with no session stays anonymous when replayed after * sign-in; one made under a session is sent only under that same session (a fingerprint, not the token, * is stored), otherwise anonymously. * * Limits: a browser that closes while offline delivers on its next visit to the same origin, or never; a * cleared site storage loses what was waiting. Both are the browser's to decide, and are stated here. */ export interface OutboxEvent { id: string; source: string; message: string; status?: number; context?: Record; build: string; /** Occurrences this event stands for. */ count: number; /** "anon", or a fingerprint of the session it was made under. */ auth: string; createdAt: number; /** Not before this time — backoff after a failed attempt, or the per-failure throttle. */ nextAt: number; attempts: number; } export interface OutboxRequest { method: "POST"; keepalive: boolean; headers: Record; body: string; } export interface OutboxDeps { endpoint: string; /** The request is described here rather than as DOM `RequestInit`, so a Worker test can drive the outbox too. */ fetch: (url: string, init: OutboxRequest) => Promise<{ ok: boolean; status: number; }>; storage: { getItem(k: string): string | null; setItem(k: string, v: string): void; } | null; now: () => number; online: () => boolean; token: () => string | null; schedule: (fn: () => void, ms: number) => void; newId: () => string; } export declare const OUTBOX_KEY = "pags:error-outbox"; export declare const MAX_EVENTS = 50; export declare const THROTTLE_MS = 30000; /** Backoff per failed attempt; past the last step the event is given up on (and counted). */ export declare const BACKOFF_MS: number[]; /** A short, non-reversible fingerprint of a session — enough to tell "the same session" apart. */ export declare function sessionFingerprint(token: string | null): string; export declare class ErrorOutbox { private readonly deps; private events; private dropped; private lastDelivered; private flushing; private running; private sending; private timerAt; constructor(deps: OutboxDeps); /** Record one occurrence of a failure, and try to send what is due. */ report(e: { source: string; message: string; status?: number; context?: Record; build: string; }): void; /** * Send every event that is due, one at a time. Safe to call at any time; never throws. A call while a * flush is running joins it — the promise settles when the delivery in progress has. */ flush(): Promise; private drain; /** What is waiting — for tests and diagnostics. */ pending(): readonly OutboxEvent[]; droppedCount(): number; private send; private remove; /** Wake up for the next due event. One timer at a time. */ private arm; private load; private save; } //# sourceMappingURL=error-outbox.d.ts.map