@tallyrow/safesignal 1.0.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/transport-beacon/batcher.ts","../src/transport-beacon/errors.ts","../src/transport-beacon/endpoint-validation.ts","../src/transport-beacon/delivery.ts","../src/transport-beacon/lifecycle.ts","../src/transport-beacon/beacon-transport.ts"],"names":["state"],"mappings":"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","file":"transport-beacon.mjs","sourcesContent":["/**\n * Batcher state machine for opt-in beacon-transport batching.\n *\n * T027 replaces T024's stub with the working state machine. T028\n * wires this batcher into `createBeaconTransport`'s send path.\n *\n * Behaviour (data-model.md § BatcherOptions + contracts/batching.md):\n *\n * - `push(event)` appends to the in-memory buffer.\n * - If this is the first event in an empty batch AND\n * `maxBatchAgeMs` is set, arm a one-shot timer.\n * - If the buffer reaches `maxBatchSize`, flush synchronously\n * at the end of `push`.\n * - `flush()` drains any pending buffer through the consumer's\n * `flush` callback. Empty buffer → no-op.\n * - `shutdown()` cancels the timer and nulls the flush callback\n * reference, so any timer/microtask queued before shutdown\n * becomes a no-op when it fires.\n *\n * Buffer-clear ordering (B-5c): the buffer is cleared BEFORE the\n * consumer's flush callback is invoked. A re-entrant `push()`\n * during the callback sees an empty buffer and starts a fresh\n * batch — no double-flush, no re-push.\n *\n * Throw safety (B-5d): if the consumer's flush callback throws,\n * the throw is swallowed. The events that were drained into the\n * callback are gone — the batcher does NOT re-push them. The\n * batcher itself never throws from `push`/`flush`/`shutdown`.\n *\n * Boundary discipline (TB-11): the only `src/` import is\n * `import type` from `'../api/types.js'`. The module is import-pure\n * — no listeners at module scope, no timers at module scope.\n */\n\nimport type { LogEvent } from '../api/types.js';\n\nexport interface BatcherOptions {\n /** Maximum events per batch. Positive integer in [1, 1000]. */\n maxBatchSize: number;\n /**\n * Optional age trigger. When set, a one-shot timer fires `maxBatchAgeMs`\n * ms after the first event enters an empty batch, flushing whatever is\n * pending. Cancelled on any other flush trigger.\n */\n maxBatchAgeMs?: number;\n /**\n * Callback invoked with the drained event array on every flush trigger\n * (size-threshold, age-timer, manual `flush()`). Throws are swallowed\n * — the events are considered lost from the batcher's perspective.\n */\n flush: (events: LogEvent[]) => void;\n}\n\nexport interface Batcher {\n /** Push an event onto the buffer. May trigger an immediate flush. */\n push(event: LogEvent): void;\n /** Drain whatever is pending. No-op when the buffer is empty. */\n flush(): void;\n /** Cancel the age timer and inhibit further flush callbacks. */\n shutdown(): void;\n}\n\nexport function createBatcher(opts: BatcherOptions): Batcher {\n const buffer: LogEvent[] = [];\n let maxAgeTimer: ReturnType<typeof setTimeout> | null = null;\n let flushCallback: ((events: LogEvent[]) => void) | null = opts.flush;\n\n const clearTimer = (): void => {\n if (maxAgeTimer !== null) {\n clearTimeout(maxAgeTimer);\n maxAgeTimer = null;\n }\n };\n\n const armTimer = (): void => {\n if (opts.maxBatchAgeMs === undefined) return;\n maxAgeTimer = setTimeout(() => {\n maxAgeTimer = null;\n doFlush();\n }, opts.maxBatchAgeMs);\n };\n\n const doFlush = (): void => {\n if (buffer.length === 0) return;\n // Post-shutdown: discard the buffered events silently. The caller\n // is responsible for draining via `flush()` before `shutdown()`\n // if it wants the data delivered.\n if (flushCallback === null) {\n buffer.length = 0;\n clearTimer();\n return;\n }\n // B-5c: copy + clear BEFORE the user callback runs, so a\n // re-entrant push during the callback starts a fresh batch.\n const events = buffer.slice();\n buffer.length = 0;\n clearTimer();\n try {\n flushCallback(events);\n } catch {\n // B-5d: swallow. The events are gone — they were copied out and\n // the consumer's callback chose to throw. Re-pushing into the\n // buffer would conflict with the \"single-flush-attempt\" model\n // in contracts/batching.md B-5.\n }\n };\n\n return {\n push(event: LogEvent): void {\n // Post-shutdown sends are silently dropped. The transport-level\n // shutdownComplete flag (T028) handles the same guarantee at\n // the createBeaconTransport boundary; this is defense in depth.\n if (flushCallback === null) return;\n buffer.push(event);\n // B-8a: arm timer when first event enters an empty batch.\n if (buffer.length === 1 && opts.maxBatchAgeMs !== undefined) {\n armTimer();\n }\n // B-5b: size threshold reached → flush synchronously at the end\n // of this push call.\n if (buffer.length >= opts.maxBatchSize) {\n doFlush();\n }\n },\n flush(): void {\n doFlush();\n },\n shutdown(): void {\n clearTimer();\n // Discard any buffered events. The caller (T028's beacon-\n // transport.ts shutdown handler) calls flush() before shutdown()\n // if it wants the pending batch drained.\n buffer.length = 0;\n flushCallback = null;\n },\n };\n}\n","/**\n * Subpath-owned diagnostic error class.\n *\n * Drop notices fired by the beacon transport are `BeaconError` instances\n * (a subclass of `Error`) owned by this subpath. They are NOT\n * `PackageError` instances and do NOT depend on the core's\n * `src/internal/errors/internal-errors.ts` module — preserving the\n * boundary in TB-11 (no runtime imports from `src/internal/**`).\n *\n * The class shape (`.code`, `.transportName`, optional `.cause`) is\n * **by-convention compatible** with `PackageError` so a consumer's\n * diagnostics handler reading `err.code` and `err.transportName` cannot\n * tell the difference between a notice emitted by `SafeTransport`\n * (a `PackageError`) and one emitted by the beacon transport\n * (a `BeaconError`).\n *\n * This module is INTERNAL to the subpath — `src/transport-beacon/index.ts`\n * does NOT re-export `BeaconError` or `BeaconErrorCode`. The\n * `onInternalError` hook receives the instance typed as `Error` per the\n * public callback signature.\n *\n * Specs: `specs/002-beacon-transport/data-model.md` § BeaconError;\n * `specs/002-beacon-transport/contracts/failure-modes.md` F-1..F-10.\n */\n\n/**\n * Documented `BeaconError.code` values. Internal to the subpath; the\n * public consumer surface for these strings is the\n * `BeaconTransportOptions.onInternalError` callback, where the value\n * arrives as `err.code` on an `Error`-shaped argument.\n */\nexport type BeaconErrorCode =\n | 'oversized_event'\n | 'beacon_batch_drop'\n | 'beacon_unavailable'\n | 'transport_send_failed'\n | 'transport_shutdown_failed';\n\n/**\n * Subclass of `Error` carrying a discriminating `.code`, the originating\n * transport's `.transportName`, and an optional `.cause` chain to the\n * underlying failure (a rejected `fetch` Promise, a thrown\n * `JSON.stringify` error, etc.).\n *\n * Construction-time invariants:\n * - `.code` is read-only (set via `Object.defineProperty` on the class\n * side by `readonly` + plain assignment).\n * - `.transportName` is read-only.\n * - `.cause` is read-only when provided (set via `Object.defineProperty`\n * with `writable: false`, `enumerable: true`). When `cause` is\n * `undefined`, the property is left unset — matching the ES2022\n * `Error.cause` convention where omitted causes are absent rather\n * than `undefined`.\n * - `.name` is `'BeaconError'`.\n */\nexport class BeaconError extends Error {\n readonly code: BeaconErrorCode;\n readonly transportName: string;\n // ES2022 standard Error.cause — declared as readonly so this\n // typechecks regardless of the current `lib` setting in tsconfig and\n // so the type system reflects the runtime non-writable contract.\n declare readonly cause?: unknown;\n\n constructor(\n code: BeaconErrorCode,\n transportName: string,\n message: string,\n cause?: unknown,\n ) {\n super(message);\n this.name = 'BeaconError';\n this.code = code;\n this.transportName = transportName;\n if (cause !== undefined) {\n Object.defineProperty(this, 'cause', {\n value: cause,\n enumerable: true,\n writable: false,\n configurable: false,\n });\n }\n }\n}\n\n/**\n * Type guard for `BeaconError` instances. Distinguishes errors created\n * by this subpath from arbitrary errors that flow through the\n * `onInternalError` callback.\n */\nexport function isBeaconError(value: unknown): value is BeaconError {\n return value instanceof BeaconError;\n}\n","/**\n * Construction-time endpoint validation for `createBeaconTransport`.\n *\n * Locked behaviour:\n * - HTTPS endpoints always pass.\n * - HTTP endpoints pass IFF `allowInsecureLoopback === true` AND the\n * parsed URL's hostname is in `{ localhost, 127.0.0.1, [::1] }`.\n * - Every other case throws a typed error at construction time, before\n * any logger derives the runtime and before any listener is\n * attached (FR-016).\n *\n * The function is pure and side-effect-free: it parses the endpoint via\n * `new URL(...)` and inspects the result. It MUST NOT read ambient state\n * (no `process.env`, no `window.location`, no build-define plugin), and\n * MUST NOT read `allowInsecureLoopback` from anywhere except the\n * argument the caller passed (FR-016 clarification).\n *\n * Specs: `specs/002-beacon-transport/contracts/failure-modes.md` F-1;\n * `specs/002-beacon-transport/contracts/transport-beacon-public-api.md`\n * TB-5; `specs/002-beacon-transport/spec.md` FR-016.\n */\n\n/**\n * The exact set of hostnames permitted under\n * `allowInsecureLoopback: true`. WHATWG URL normalises `http://[::1]`\n * and `http://[0:0:0:0:0:0:0:1]` to hostname `[::1]` (with brackets),\n * so the allowlist matches the canonical form.\n */\nconst LOOPBACK_HOSTS: ReadonlySet<string> = new Set([\n 'localhost',\n '127.0.0.1',\n '[::1]',\n]);\n\n/**\n * Validate a consumer-supplied `endpoint` string. Returns the parsed\n * `URL` on success. Throws on every form of violation.\n *\n * The thrown error's `.message` always names (a) the violated\n * constraint and (b) the offending endpoint string, so the consumer\n * can act on the diagnostic without parsing the error's stack.\n */\nexport function validateEndpoint(\n endpoint: unknown,\n allowInsecureLoopback: boolean,\n): URL {\n if (typeof endpoint !== 'string') {\n throw new TypeError(\n `beacon transport: endpoint must be a string, got ${typeName(endpoint)}`,\n );\n }\n\n let parsed: URL;\n try {\n parsed = new URL(endpoint);\n } catch {\n throw new TypeError(\n `beacon transport: invalid endpoint URL: '${endpoint}'`,\n );\n }\n\n if (parsed.protocol === 'https:') {\n return parsed;\n }\n\n if (parsed.protocol === 'http:') {\n if (!allowInsecureLoopback) {\n throw new Error(\n `beacon transport refuses non-HTTPS endpoint '${endpoint}'`,\n );\n }\n if (!LOOPBACK_HOSTS.has(parsed.hostname)) {\n throw new Error(\n `beacon transport: allowInsecureLoopback permits only ` +\n `localhost / 127.0.0.1 / [::1]; got '${parsed.hostname}' ` +\n `in '${endpoint}'`,\n );\n }\n return parsed;\n }\n\n throw new Error(\n `beacon transport refuses non-HTTPS endpoint '${endpoint}' ` +\n `(scheme '${parsed.protocol}' is not permitted)`,\n );\n}\n\nfunction typeName(value: unknown): string {\n if (value === null) return 'null';\n return typeof value;\n}\n","/**\n * Low-level delivery primitives for the beacon transport.\n *\n * The functions here are deliberately small and composable. The\n * `beacon-transport.ts` factory (T016) wires them into the per-event\n * delivery policy defined in `contracts/delivery.md` D-3..D-7:\n *\n * 1. Compute `payload = JSON.stringify(event)`.\n * 2. If `getPayloadByteLength(payload) > BEACON_SIZE_LIMIT_BYTES`:\n * drop the event and fire `oversized_event` (F-2).\n * 3. `tryBeacon(endpoint, payload)`. On `true`, done.\n * 4. `await tryFetchKeepalive(endpoint, payload)`. On `true`, done.\n * On `false`, drop with `transport_send_failed` (F-4). On reject,\n * drop with `transport_send_failed` carrying `.cause` (F-7).\n *\n * Boundary discipline (TB-11): zero imports from `src/internal/**`,\n * `src/runtime/**`, `src/pipeline/**`, `src/config/**`, `src/context/**`,\n * or `src/transport/**`. Zero vendor-SDK imports.\n *\n * Specs: `specs/002-beacon-transport/contracts/delivery.md` D-2..D-7;\n * `specs/002-beacon-transport/research.md` §1, §2.\n */\n\n/**\n * The effective per-call `navigator.sendBeacon` size budget. ~64 KiB\n * per origin in modern browsers (research §1). Bodies whose serialized\n * byte length exceeds this constant short-circuit to `oversized_event`\n * without invoking either primitive — the fetch keepalive fallback\n * shares the same budget (research §2) so attempting it would waste a\n * network call on a guaranteed-failure payload.\n */\nexport const BEACON_SIZE_LIMIT_BYTES = 65536;\n\n/**\n * Return the UTF-8 byte length of `payload`. Uses `TextEncoder` because\n * `payload.length` is UTF-16 code-unit count, not byte count — and\n * `sendBeacon`'s budget is measured in bytes.\n *\n * `TextEncoder` is baseline-available in every modern browser this\n * package targets; it is the only ambient API the transport touches at\n * the delivery layer.\n */\nexport function getPayloadByteLength(payload: string): number {\n return new TextEncoder().encode(payload).length;\n}\n\n/**\n * Attempt delivery via `navigator.sendBeacon(endpoint, blob)` where\n * `blob` is `new Blob([payload], { type: 'application/json' })`. The\n * `Blob` form is required so the browser sends the body as\n * `application/json` and so the request is unambiguously body-only\n * (T-S1..T-S5).\n *\n * Returns:\n * - `true` — the browser accepted the payload onto the beacon queue.\n * - `false` — the browser refused (size limit / queue full / etc.),\n * `navigator.sendBeacon` is unavailable, OR a synchronous\n * throw was caught internally. Either way the caller\n * should fall through to `tryFetchKeepalive`.\n *\n * Never throws. Never returns a Promise.\n */\nexport function tryBeacon(endpoint: string, payload: string): boolean {\n const nav = (globalThis as { navigator?: Navigator }).navigator;\n if (nav === undefined || typeof nav.sendBeacon !== 'function') {\n return false;\n }\n try {\n const blob = new Blob([payload], { type: 'application/json' });\n return nav.sendBeacon(endpoint, blob);\n } catch {\n // Some legacy environments throw on oversized sendBeacon payloads\n // instead of returning false. Treat as \"refused\" so the caller\n // falls through; the same caller's size pre-check already short-\n // circuited the oversized case for modern runtimes.\n return false;\n }\n}\n\n/**\n * Attempt delivery via `fetch(endpoint, { method: 'POST', body: payload,\n * keepalive: true, headers: { 'content-type': 'application/json' },\n * credentials: 'same-origin' })`.\n *\n * Resolves:\n * - `true` — `fetch` resolved with `response.ok` (status in\n * `[200, 299]`).\n * - `false` — `fetch` is unavailable in the runtime, OR `fetch`\n * resolved with a non-2xx status.\n *\n * Rejects:\n * - With the underlying `fetch` rejection reason if the Promise\n * rejected (network error, browser-enforced budget overflow, etc.).\n * The caller wraps this in a `BeaconError(transport_send_failed)`\n * carrying `.cause` per F-7.\n *\n * The `credentials: 'same-origin'` choice keeps cookies from leaking\n * cross-origin by default (Principle IV). Consumers who need credentialed\n * delivery to a same-origin endpoint inherit the correct behaviour\n * automatically; cross-origin endpoints get no cookies, which is the\n * safer default.\n */\nexport async function tryFetchKeepalive(\n endpoint: string,\n payload: string,\n): Promise<boolean> {\n const fetchFn = (globalThis as { fetch?: typeof fetch }).fetch;\n if (typeof fetchFn !== 'function') {\n return false;\n }\n const response = await fetchFn(endpoint, {\n method: 'POST',\n body: payload,\n headers: { 'content-type': 'application/json' },\n keepalive: true,\n credentials: 'same-origin',\n });\n return response.ok;\n}\n","/**\n * Lazy lifecycle helper for the beacon transport's `pagehide` handler.\n *\n * Per FR-008 and contract D-10, the transport MUST NOT attach any\n * global listener at construction time. The `pagehide` listener\n * attaches on the first `send()` call that proceeds past the payload\n * size check, and detaches on `shutdown()`. This module supplies the\n * install/uninstall primitives; the caller (the transport factory)\n * owns the `installed` flag that gates against double-install.\n *\n * Why a separate module: keeps `beacon-transport.ts` focused on the\n * delivery policy, and makes the `globalThis.addEventListener`\n * boundary easy to spy on in tests.\n *\n * Boundary discipline (TB-11): zero imports from anywhere in `src/`,\n * zero vendor-SDK imports. The module is import-pure — no side effects\n * at module-evaluation time.\n *\n * Specs: `specs/002-beacon-transport/contracts/delivery.md` D-10;\n * `specs/002-beacon-transport/research.md` §3.\n */\n\n/**\n * Install a `pagehide` listener on the global event target if one is\n * available, returning an `uninstall()` function that removes exactly\n * that listener.\n *\n * If `globalThis.addEventListener` is not a function (vanishingly rare\n * outside of bare Node-like runtimes), this function is a no-op and\n * returns a no-op uninstaller. The caller's `installed` flag is the\n * authoritative single-install guard — this module does NOT track\n * installation state itself.\n *\n * Uninstall is idempotent at the DOM level: calling `removeEventListener`\n * a second time after the handler is already detached is a no-op\n * specified by the DOM standard.\n */\nexport function installPagehideHandler(handler: () => void): () => void {\n const target = globalThis as {\n addEventListener?: typeof globalThis.addEventListener;\n removeEventListener?: typeof globalThis.removeEventListener;\n };\n if (typeof target.addEventListener !== 'function') {\n return noopUninstall;\n }\n target.addEventListener('pagehide', handler);\n return (): void => {\n if (typeof target.removeEventListener === 'function') {\n target.removeEventListener('pagehide', handler);\n }\n };\n}\n\nfunction noopUninstall(): void {\n // intentionally empty\n}\n","/**\n * Default-mode `createBeaconTransport` factory.\n *\n * Composes the primitives from T004 (BeaconError), T005\n * (validateEndpoint), T006 (delivery primitives), and T007\n * (installPagehideHandler) into the `Transport`-shaped factory the\n * `./transport-beacon` subpath exports.\n *\n * Per-event delivery policy (D-3..D-7, F-2..F-7):\n *\n * send(event)\n * ├── if shutdownComplete: no-op\n * ├── payload = JSON.stringify(event) // F-4 cause if throws\n * ├── if size > 64 KiB → oversized_event drop // F-2 (D-3)\n * ├── lazy install pagehide listener // FR-008 / D-10\n * ├── if !sendBeacon AND !fetch → beacon_unavailable drop // F-3 (D-7)\n * ├── tryBeacon(endpoint, payload) // D-4\n * │ └── true ────────────────────────────── delivered\n * └── tryFetchKeepalive(endpoint, payload) // D-5, D-6\n * ├── 2xx ───────────────────────────────── delivered\n * ├── non-2xx → transport_send_failed // F-4\n * └── reject → transport_send_failed // F-4, F-7 (with .cause)\n *\n * Every notice is rate-limited per `state.notified[code]` — one\n * notice per failure class per transport instance per session (F-8).\n *\n * The factory NEVER throws from `send()`, `flush()`, or `shutdown()`.\n * Construction-time errors (invalid options, non-HTTPS endpoint) are\n * the only throws — and they happen at the consumer's call site,\n * outside the emit hot path.\n *\n * Boundary discipline (TB-11): the only `src/` import in this file\n * is `import type` from `'../api/types.js'`; the other imports are\n * intra-subpath.\n *\n * Specs: `specs/002-beacon-transport/contracts/delivery.md` D-1..D-12;\n * `specs/002-beacon-transport/contracts/failure-modes.md` F-1..F-10;\n * `specs/002-beacon-transport/data-model.md` § BeaconTransportState.\n */\n\nimport type { LogEvent, Transport } from '../api/types.js';\n\nimport { type Batcher, createBatcher } from './batcher.js';\nimport { BeaconError, type BeaconErrorCode } from './errors.js';\nimport { validateEndpoint } from './endpoint-validation.js';\nimport {\n BEACON_SIZE_LIMIT_BYTES,\n getPayloadByteLength,\n tryBeacon,\n tryFetchKeepalive,\n} from './delivery.js';\nimport { installPagehideHandler } from './lifecycle.js';\n\n// ---------------------------------------------------------------------------\n// Public options shape (data-model.md § BeaconTransportOptions)\n// ---------------------------------------------------------------------------\n\nexport interface BeaconTransportOptions {\n endpoint: string;\n batching?: {\n maxBatchSize: number;\n maxBatchAgeMs?: number;\n };\n allowInsecureLoopback?: boolean;\n name?: string;\n onInternalError?: (err: Error) => void;\n}\n\n// ---------------------------------------------------------------------------\n// Internal per-instance state (data-model.md § BeaconTransportState)\n// ---------------------------------------------------------------------------\n\ninterface BeaconTransportState {\n readonly endpoint: string;\n readonly name: string;\n readonly onInternalError: (err: Error) => void;\n readonly batching: BeaconTransportOptions['batching'] | undefined;\n /** Batcher instance when batching is enabled; `null` in default mode. */\n batcher: Batcher | null;\n pagehideInstalled: boolean;\n pagehideUninstall: (() => void) | null;\n shutdownComplete: boolean;\n notified: {\n oversized_event: boolean;\n beacon_unavailable: boolean;\n transport_send_failed: boolean;\n beacon_batch_drop: boolean;\n transport_shutdown_failed: boolean;\n };\n}\n\n// ---------------------------------------------------------------------------\n// Construction-time validation (F-1, TB-6)\n// ---------------------------------------------------------------------------\n\nfunction validateOptions(options: BeaconTransportOptions): void {\n if (typeof options !== 'object' || options === null) {\n throw new TypeError('beacon transport: options must be a non-null object');\n }\n\n if (options.batching !== undefined) {\n if (typeof options.batching !== 'object' || options.batching === null) {\n throw new TypeError('beacon transport: options.batching must be an object');\n }\n const { maxBatchSize, maxBatchAgeMs } = options.batching;\n if (!Number.isInteger(maxBatchSize) || maxBatchSize < 1 || maxBatchSize > 1000) {\n throw new RangeError(\n `beacon transport: batching.maxBatchSize must be an integer in [1, 1000], got ${String(maxBatchSize)}`,\n );\n }\n if (maxBatchAgeMs !== undefined) {\n if (!Number.isFinite(maxBatchAgeMs) || maxBatchAgeMs < 0) {\n throw new RangeError(\n `beacon transport: batching.maxBatchAgeMs must be a non-negative finite number, got ${String(maxBatchAgeMs)}`,\n );\n }\n }\n }\n\n if (\n options.allowInsecureLoopback !== undefined &&\n typeof options.allowInsecureLoopback !== 'boolean'\n ) {\n throw new TypeError(\n `beacon transport: allowInsecureLoopback must be a boolean, got ${typeof options.allowInsecureLoopback}`,\n );\n }\n\n if (\n options.name !== undefined &&\n (typeof options.name !== 'string' || options.name.length === 0)\n ) {\n throw new TypeError('beacon transport: name must be a non-empty string');\n }\n\n if (\n options.onInternalError !== undefined &&\n typeof options.onInternalError !== 'function'\n ) {\n throw new TypeError('beacon transport: onInternalError must be a function');\n }\n}\n\n// ---------------------------------------------------------------------------\n// Notice routing (F-2..F-7, F-8 rate-limit)\n// ---------------------------------------------------------------------------\n\nfunction notify(\n state: BeaconTransportState,\n code: BeaconErrorCode,\n message: string,\n cause?: unknown,\n): void {\n if (state.notified[code]) return;\n state.notified[code] = true;\n const err = new BeaconError(code, state.name, message, cause);\n try {\n state.onInternalError(err);\n } catch {\n // Consumer's onInternalError threw. Swallow — we cannot re-enter the\n // same callback in response to a failed notification (FR-003).\n }\n}\n\nfunction notifyOversized(state: BeaconTransportState, event: LogEvent, bytes: number): void {\n if (state.notified.oversized_event) return;\n state.notified.oversized_event = true;\n // Truncate the event message to 256 chars (F-2 notice integrity: never\n // include attrs/error/context — only the message, bounded).\n const messagePreview =\n event.message.length > 256 ? event.message.slice(0, 256) : event.message;\n const err = new BeaconError(\n 'oversized_event',\n state.name,\n `beacon transport '${state.name}' dropped oversized event: bytes=${bytes}, message=${messagePreview}`,\n );\n try {\n state.onInternalError(err);\n } catch {\n // swallow per FR-003\n }\n}\n\n/**\n * Fire a `beacon_batch_drop` notice for a failed batch flush. The\n * notice payload is **structural only** (B-11): droppedCount + a short\n * reason summary + the transport name. No event content (no message,\n * no attrs, no error, no context) — including such content would risk\n * leaking the same payload whose size or shape was the problem in the\n * first place.\n */\nfunction notifyBatchDrop(\n state: BeaconTransportState,\n droppedCount: number,\n reason: string,\n cause?: unknown,\n): void {\n if (state.notified.beacon_batch_drop) return;\n state.notified.beacon_batch_drop = true;\n const err = new BeaconError(\n 'beacon_batch_drop',\n state.name,\n `beacon transport '${state.name}' dropped batch: droppedCount=${droppedCount}, reason=${reason}`,\n cause,\n );\n try {\n state.onInternalError(err);\n } catch {\n // swallow per FR-003\n }\n}\n\n// ---------------------------------------------------------------------------\n// Public factory\n// ---------------------------------------------------------------------------\n\n/**\n * Construct a body-only HTTPS beacon transport conforming to the\n * `Transport` contract from `@your-org/frontend-logging-sdk`. Pass the\n * result directly to `configureLogging({ transports: [...] })`.\n *\n * Construction throws synchronously on every form of invalid input\n * (non-string endpoint, malformed URL, non-HTTPS endpoint without\n * `allowInsecureLoopback`, batching fields out of range, etc.) — see\n * `contracts/failure-modes.md` F-1.\n *\n * Once constructed, `send()` / `flush()` / `shutdown()` NEVER throw\n * to the caller. Every drop routes through `options.onInternalError`.\n */\nexport function createBeaconTransport(\n options: BeaconTransportOptions,\n): Transport {\n validateOptions(options);\n\n const allowInsecureLoopback = options.allowInsecureLoopback ?? false;\n const parsedEndpoint = validateEndpoint(options.endpoint, allowInsecureLoopback);\n // We keep the raw endpoint string (not parsedEndpoint.toString()) so the\n // wire matches exactly what the consumer supplied — important for ingestion\n // endpoints that are path-sensitive.\n void parsedEndpoint;\n\n const state: BeaconTransportState = {\n endpoint: options.endpoint,\n name: options.name ?? 'beacon',\n onInternalError: options.onInternalError ?? noopOnInternalError,\n batching: options.batching,\n batcher: null, // assigned below when batching is enabled\n pagehideInstalled: false,\n pagehideUninstall: null,\n shutdownComplete: false,\n notified: {\n oversized_event: false,\n beacon_unavailable: false,\n transport_send_failed: false,\n beacon_batch_drop: false,\n transport_shutdown_failed: false,\n },\n };\n\n // Batching is opt-in. When enabled, the batcher owns the in-memory\n // buffer; `send()` pushes per-event-sized payloads onto it and the\n // batcher invokes `dispatchBatch` on every flush trigger\n // (size-threshold, age-timer, manual `flush()`, `shutdown()`,\n // pagehide). When disabled, `send()` dispatches the event\n // immediately via `dispatchOne`.\n if (options.batching !== undefined) {\n const batcherOptions: Parameters<typeof createBatcher>[0] = {\n maxBatchSize: options.batching.maxBatchSize,\n flush: (events) => {\n dispatchBatch(state, events);\n },\n };\n if (options.batching.maxBatchAgeMs !== undefined) {\n batcherOptions.maxBatchAgeMs = options.batching.maxBatchAgeMs;\n }\n state.batcher = createBatcher(batcherOptions);\n }\n\n const pagehideHandler = (): void => {\n // Default mode: no buffered state to flush. Batching mode: drain\n // the pending batch via the batcher's flush API, which routes\n // through `dispatchBatch` for the same envelope encoding +\n // primitive dispatch as a size/age-triggered flush (B-9).\n if (state.batcher !== null) {\n state.batcher.flush();\n }\n };\n\n function ensureLazyInstall(): void {\n if (state.pagehideInstalled) return;\n state.pagehideInstalled = true;\n state.pagehideUninstall = installPagehideHandler(pagehideHandler);\n }\n\n function send(event: LogEvent): void {\n if (state.shutdownComplete) return;\n\n let payload: string;\n try {\n payload = JSON.stringify(event);\n } catch (cause) {\n notify(\n state,\n 'transport_send_failed',\n `beacon transport '${state.name}' failed: JSON.stringify threw on the event`,\n cause,\n );\n return;\n }\n\n const bytes = getPayloadByteLength(payload);\n if (bytes > BEACON_SIZE_LIMIT_BYTES) {\n // B-7 / F-2: oversized single event is ejected pre-push with one\n // oversized_event notice (rate-limited per session). In batching\n // mode this guarantees the envelope is constructed from\n // bounded-size events only; the per-envelope size check (B-6)\n // still catches over-aggressive maxBatchSize settings.\n notifyOversized(state, event, bytes);\n return;\n }\n\n ensureLazyInstall();\n\n if (state.batcher !== null) {\n // Batching mode: buffer the event. The batcher decides when to\n // flush (size threshold, optional age timer, pagehide, shutdown).\n state.batcher.push(event);\n return;\n }\n\n dispatchOne(state, payload);\n }\n\n function flush(): Promise<void> {\n // Default mode: no buffer to drain.\n // Batching mode: trigger an immediate flush of the current batch.\n if (state.batcher !== null) {\n state.batcher.flush();\n }\n return Promise.resolve();\n }\n\n function shutdown(): Promise<void> {\n if (state.shutdownComplete) return Promise.resolve();\n state.shutdownComplete = true;\n\n // Best-effort drain of the pending batch BEFORE detaching the\n // listener (B-10). The batcher's flush callback fires either a\n // successful delivery or a `beacon_batch_drop` notice. After\n // `batcher.shutdown()`, the batcher discards any further events\n // and a queued timer callback becomes a no-op.\n if (state.batcher !== null) {\n state.batcher.flush();\n state.batcher.shutdown();\n }\n\n if (state.pagehideUninstall !== null) {\n try {\n state.pagehideUninstall();\n } catch (cause) {\n notify(\n state,\n 'transport_shutdown_failed',\n `beacon transport '${state.name}' shutdown: listener removal threw`,\n cause,\n );\n }\n state.pagehideUninstall = null;\n state.pagehideInstalled = false;\n }\n return Promise.resolve();\n }\n\n // -------------------------------------------------------------------------\n // Dispatch helpers (closure-scoped — read state via `state` reference)\n // -------------------------------------------------------------------------\n\n function dispatchOne(state: BeaconTransportState, payload: string): void {\n const { hasSendBeacon, hasFetch } = primitiveAvailability();\n\n if (!hasSendBeacon && !hasFetch) {\n notify(\n state,\n 'beacon_unavailable',\n `beacon transport '${state.name}' has no usable delivery primitive (sendBeacon and fetch both unavailable)`,\n );\n return;\n }\n\n if (hasSendBeacon && tryBeacon(state.endpoint, payload)) {\n return; // delivered\n }\n\n if (hasFetch) {\n tryFetchKeepalive(state.endpoint, payload).then(\n (ok) => {\n if (!ok) {\n notify(\n state,\n 'transport_send_failed',\n `beacon transport '${state.name}' failed: fetch fallback resolved with non-2xx`,\n );\n }\n },\n (cause: unknown) => {\n notify(\n state,\n 'transport_send_failed',\n `beacon transport '${state.name}' failed: fetch fallback rejected`,\n cause,\n );\n },\n );\n return;\n }\n\n notify(\n state,\n 'transport_send_failed',\n `beacon transport '${state.name}' failed: sendBeacon returned false and fetch fallback is unavailable`,\n );\n }\n\n function dispatchBatch(state: BeaconTransportState, events: LogEvent[]): void {\n if (events.length === 0) return;\n\n // Encode envelope. Failure here is unexpected (the events came\n // straight from the pipeline) — handle defensively as a batch\n // drop with the original cause attached.\n let envelope: string;\n try {\n envelope = JSON.stringify({ events });\n } catch (cause) {\n notifyBatchDrop(state, events.length, 'JSON.stringify threw on the envelope', cause);\n return;\n }\n\n const bytes = getPayloadByteLength(envelope);\n if (bytes > BEACON_SIZE_LIMIT_BYTES) {\n // B-6: oversized envelope (consumer's maxBatchSize was too\n // aggressive for the average event size). No primitive call —\n // both sendBeacon and fetch-keepalive share the same per-origin\n // ~64 KiB budget so attempting them would waste a network call.\n notifyBatchDrop(\n state,\n events.length,\n `envelope size ${bytes} bytes exceeds ${BEACON_SIZE_LIMIT_BYTES} bytes`,\n );\n return;\n }\n\n const { hasSendBeacon, hasFetch } = primitiveAvailability();\n\n if (!hasSendBeacon && !hasFetch) {\n notify(\n state,\n 'beacon_unavailable',\n `beacon transport '${state.name}' has no usable delivery primitive (sendBeacon and fetch both unavailable)`,\n );\n return;\n }\n\n if (hasSendBeacon && tryBeacon(state.endpoint, envelope)) {\n return; // delivered\n }\n\n if (hasFetch) {\n tryFetchKeepalive(state.endpoint, envelope).then(\n (ok) => {\n if (!ok) {\n notifyBatchDrop(\n state,\n events.length,\n `fetch fallback resolved with non-2xx`,\n );\n }\n },\n (cause: unknown) => {\n notifyBatchDrop(state, events.length, `fetch fallback rejected`, cause);\n },\n );\n return;\n }\n\n notifyBatchDrop(\n state,\n events.length,\n `sendBeacon returned false and fetch fallback is unavailable`,\n );\n }\n\n function primitiveAvailability(): { hasSendBeacon: boolean; hasFetch: boolean } {\n const nav = (globalThis as { navigator?: Navigator }).navigator;\n const hasSendBeacon = nav !== undefined && typeof nav.sendBeacon === 'function';\n const hasFetch = typeof (globalThis as { fetch?: typeof fetch }).fetch === 'function';\n return { hasSendBeacon, hasFetch };\n }\n\n return {\n name: state.name,\n send,\n flush,\n shutdown,\n };\n}\n\nfunction noopOnInternalError(_err: Error): void {\n // intentional default — see BeaconTransportOptions.onInternalError docs.\n}\n"]}
1
+ {"version":3,"sources":["../src/transport-beacon/batcher.ts","../src/transport-beacon/delivery.ts","../src/transport-beacon/endpoint-validation.ts","../src/transport-beacon/errors.ts","../src/transport-beacon/lifecycle.ts","../src/transport-beacon/beacon-transport.ts"],"names":["state"],"mappings":";AA8DO,SAAS,cAAc,IAAA,EAA+B;AAC3D,EAAA,MAAM,SAAqB,EAAC;AAC5B,EAAA,IAAI,WAAA,GAAoD,IAAA;AACxD,EAAA,IAAI,gBAAuD,IAAA,CAAK,KAAA;AAEhE,EAAA,MAAM,aAAa,MAAY;AAC7B,IAAA,IAAI,gBAAgB,IAAA,EAAM;AACxB,MAAA,YAAA,CAAa,WAAW,CAAA;AACxB,MAAA,WAAA,GAAc,IAAA;AAAA,IAChB;AAAA,EACF,CAAA;AAEA,EAAA,MAAM,WAAW,MAAY;AAC3B,IAAA,IAAI,IAAA,CAAK,kBAAkB,MAAA,EAAW;AACtC,IAAA,WAAA,GAAc,WAAW,MAAM;AAC7B,MAAA,WAAA,GAAc,IAAA;AACd,MAAA,OAAA,EAAQ;AAAA,IACV,CAAA,EAAG,KAAK,aAAa,CAAA;AAAA,EACvB,CAAA;AAEA,EAAA,MAAM,UAAU,MAAY;AAC1B,IAAA,IAAI,MAAA,CAAO,WAAW,CAAA,EAAG;AAIzB,IAAA,IAAI,kBAAkB,IAAA,EAAM;AAC1B,MAAA,MAAA,CAAO,MAAA,GAAS,CAAA;AAChB,MAAA,UAAA,EAAW;AACX,MAAA;AAAA,IACF;AAGA,IAAA,MAAM,MAAA,GAAS,OAAO,KAAA,EAAM;AAC5B,IAAA,MAAA,CAAO,MAAA,GAAS,CAAA;AAChB,IAAA,UAAA,EAAW;AACX,IAAA,IAAI;AACF,MAAA,aAAA,CAAc,MAAM,CAAA;AAAA,IACtB,CAAA,CAAA,MAAQ;AAAA,IAKR;AAAA,EACF,CAAA;AAEA,EAAA,OAAO;AAAA,IACL,KAAK,KAAA,EAAuB;AAI1B,MAAA,IAAI,kBAAkB,IAAA,EAAM;AAC5B,MAAA,MAAA,CAAO,KAAK,KAAK,CAAA;AAEjB,MAAA,IAAI,MAAA,CAAO,MAAA,KAAW,CAAA,IAAK,IAAA,CAAK,kBAAkB,MAAA,EAAW;AAC3D,QAAA,QAAA,EAAS;AAAA,MACX;AAGA,MAAA,IAAI,MAAA,CAAO,MAAA,IAAU,IAAA,CAAK,YAAA,EAAc;AACtC,QAAA,OAAA,EAAQ;AAAA,MACV;AAAA,IACF,CAAA;AAAA,IACA,KAAA,GAAc;AACZ,MAAA,OAAA,EAAQ;AAAA,IACV,CAAA;AAAA,IACA,QAAA,GAAiB;AACf,MAAA,UAAA,EAAW;AAIX,MAAA,MAAA,CAAO,MAAA,GAAS,CAAA;AAChB,MAAA,aAAA,GAAgB,IAAA;AAAA,IAClB;AAAA,GACF;AACF;;;ACzGO,IAAM,uBAAA,GAA0B,KAAA;AAWhC,SAAS,qBAAqB,OAAA,EAAyB;AAC5D,EAAA,OAAO,IAAI,WAAA,EAAY,CAAE,MAAA,CAAO,OAAO,CAAA,CAAE,MAAA;AAC3C;AAkBO,SAAS,SAAA,CAAU,UAAkB,OAAA,EAA0B;AACpE,EAAA,MAAM,MAAO,UAAA,CAAyC,SAAA;AACtD,EAAA,IAAI,GAAA,KAAQ,MAAA,IAAa,OAAO,GAAA,CAAI,eAAe,UAAA,EAAY;AAC7D,IAAA,OAAO,KAAA;AAAA,EACT;AACA,EAAA,IAAI;AACF,IAAA,MAAM,IAAA,GAAO,IAAI,IAAA,CAAK,CAAC,OAAO,CAAA,EAAG,EAAE,IAAA,EAAM,kBAAA,EAAoB,CAAA;AAC7D,IAAA,OAAO,GAAA,CAAI,UAAA,CAAW,QAAA,EAAU,IAAI,CAAA;AAAA,EACtC,CAAA,CAAA,MAAQ;AAKN,IAAA,OAAO,KAAA;AAAA,EACT;AACF;AAyBA,eAAsB,iBAAA,CACpB,UACA,OAAA,EACkB;AAClB,EAAA,MAAM,UAAW,UAAA,CAAwC,KAAA;AACzD,EAAA,IAAI,OAAO,YAAY,UAAA,EAAY;AACjC,IAAA,OAAO,KAAA;AAAA,EACT;AACA,EAAA,MAAM,QAAA,GAAW,MAAM,OAAA,CAAQ,QAAA,EAAU;AAAA,IACvC,MAAA,EAAQ,MAAA;AAAA,IACR,IAAA,EAAM,OAAA;AAAA,IACN,OAAA,EAAS,EAAE,cAAA,EAAgB,kBAAA,EAAmB;AAAA,IAC9C,SAAA,EAAW,IAAA;AAAA,IACX,WAAA,EAAa;AAAA,GACd,CAAA;AACD,EAAA,OAAO,QAAA,CAAS,EAAA;AAClB;;;AC1FA,IAAM,cAAA,uBAA0C,GAAA,CAAI;AAAA,EAClD,WAAA;AAAA,EACA,WAAA;AAAA,EACA;AACF,CAAC,CAAA;AAUM,SAAS,gBAAA,CACd,UACA,qBAAA,EACK;AACL,EAAA,IAAI,OAAO,aAAa,QAAA,EAAU;AAChC,IAAA,MAAM,IAAI,SAAA;AAAA,MACR,CAAA,iDAAA,EAAoD,QAAA,CAAS,QAAQ,CAAC,CAAA;AAAA,KACxE;AAAA,EACF;AAEA,EAAA,IAAI,MAAA;AACJ,EAAA,IAAI;AACF,IAAA,MAAA,GAAS,IAAI,IAAI,QAAQ,CAAA;AAAA,EAC3B,CAAA,CAAA,MAAQ;AACN,IAAA,MAAM,IAAI,SAAA;AAAA,MACR,4CAA4C,QAAQ,CAAA,CAAA;AAAA,KACtD;AAAA,EACF;AAEA,EAAA,IAAI,MAAA,CAAO,aAAa,QAAA,EAAU;AAChC,IAAA,OAAO,MAAA;AAAA,EACT;AAEA,EAAA,IAAI,MAAA,CAAO,aAAa,OAAA,EAAS;AAC/B,IAAA,IAAI,CAAC,qBAAA,EAAuB;AAC1B,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,gDAAgD,QAAQ,CAAA,CAAA;AAAA,OAC1D;AAAA,IACF;AACA,IAAA,IAAI,CAAC,cAAA,CAAe,GAAA,CAAI,MAAA,CAAO,QAAQ,CAAA,EAAG;AACxC,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,CAAA,yFAAA,EACyC,MAAA,CAAO,QAAQ,CAAA,MAAA,EAC/C,QAAQ,CAAA,CAAA;AAAA,OACnB;AAAA,IACF;AACA,IAAA,OAAO,MAAA;AAAA,EACT;AAEA,EAAA,MAAM,IAAI,KAAA;AAAA,IACR,CAAA,6CAAA,EAAgD,QAAQ,CAAA,WAAA,EAC1C,MAAA,CAAO,QAAQ,CAAA,mBAAA;AAAA,GAC/B;AACF;AAEA,SAAS,SAAS,KAAA,EAAwB;AACxC,EAAA,IAAI,KAAA,KAAU,MAAM,OAAO,MAAA;AAC3B,EAAA,OAAO,OAAO,KAAA;AAChB;;;ACnCO,IAAM,WAAA,GAAN,cAA0B,KAAA,CAAM;AAAA,EAQrC,WAAA,CACE,IAAA,EACA,aAAA,EACA,OAAA,EACA,KAAA,EACA;AACA,IAAA,KAAA,CAAM,OAAO,CAAA;AACb,IAAA,IAAA,CAAK,IAAA,GAAO,aAAA;AACZ,IAAA,IAAA,CAAK,IAAA,GAAO,IAAA;AACZ,IAAA,IAAA,CAAK,aAAA,GAAgB,aAAA;AACrB,IAAA,IAAI,UAAU,MAAA,EAAW;AACvB,MAAA,MAAA,CAAO,cAAA,CAAe,MAAM,OAAA,EAAS;AAAA,QACnC,KAAA,EAAO,KAAA;AAAA,QACP,UAAA,EAAY,IAAA;AAAA,QACZ,QAAA,EAAU,KAAA;AAAA,QACV,YAAA,EAAc;AAAA,OACf,CAAA;AAAA,IACH;AAAA,EACF;AACF,CAAA;;;AC7CO,SAAS,uBAAuB,OAAA,EAAiC;AACtE,EAAA,MAAM,MAAA,GAAS,UAAA;AAIf,EAAA,IAAI,OAAO,MAAA,CAAO,gBAAA,KAAqB,UAAA,EAAY;AACjD,IAAA,OAAO,aAAA;AAAA,EACT;AACA,EAAA,MAAA,CAAO,gBAAA,CAAiB,YAAY,OAAO,CAAA;AAC3C,EAAA,OAAO,MAAY;AACjB,IAAA,IAAI,OAAO,MAAA,CAAO,mBAAA,KAAwB,UAAA,EAAY;AACpD,MAAA,MAAA,CAAO,mBAAA,CAAoB,YAAY,OAAO,CAAA;AAAA,IAChD;AAAA,EACF,CAAA;AACF;AAEA,SAAS,aAAA,GAAsB;AAE/B;;;ACwCA,SAAS,gBAAgB,OAAA,EAAuC;AAC9D,EAAA,IAAI,OAAO,OAAA,KAAY,QAAA,IAAY,OAAA,KAAY,IAAA,EAAM;AACnD,IAAA,MAAM,IAAI,UAAU,qDAAqD,CAAA;AAAA,EAC3E;AAEA,EAAA,IAAI,OAAA,CAAQ,aAAa,MAAA,EAAW;AAClC,IAAA,IAAI,OAAO,OAAA,CAAQ,QAAA,KAAa,QAAA,IAAY,OAAA,CAAQ,aAAa,IAAA,EAAM;AACrE,MAAA,MAAM,IAAI,SAAA;AAAA,QACR;AAAA,OACF;AAAA,IACF;AACA,IAAA,MAAM,EAAE,YAAA,EAAc,aAAA,EAAc,GAAI,OAAA,CAAQ,QAAA;AAChD,IAAA,IACE,CAAC,OAAO,SAAA,CAAU,YAAY,KAC9B,YAAA,GAAe,CAAA,IACf,eAAe,GAAA,EACf;AACA,MAAA,MAAM,IAAI,UAAA;AAAA,QACR,CAAA,6EAAA,EAAgF,MAAA,CAAO,YAAY,CAAC,CAAA;AAAA,OACtG;AAAA,IACF;AACA,IAAA,IAAI,kBAAkB,MAAA,EAAW;AAC/B,MAAA,IAAI,CAAC,MAAA,CAAO,QAAA,CAAS,aAAa,CAAA,IAAK,gBAAgB,CAAA,EAAG;AACxD,QAAA,MAAM,IAAI,UAAA;AAAA,UACR,CAAA,mFAAA,EAAsF,MAAA,CAAO,aAAa,CAAC,CAAA;AAAA,SAC7G;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,EAAA,IACE,QAAQ,qBAAA,KAA0B,MAAA,IAClC,OAAO,OAAA,CAAQ,0BAA0B,SAAA,EACzC;AACA,IAAA,MAAM,IAAI,SAAA;AAAA,MACR,CAAA,+DAAA,EAAkE,OAAO,OAAA,CAAQ,qBAAqB,CAAA;AAAA,KACxG;AAAA,EACF;AAEA,EAAA,IACE,OAAA,CAAQ,IAAA,KAAS,MAAA,KAChB,OAAO,OAAA,CAAQ,SAAS,QAAA,IAAY,OAAA,CAAQ,IAAA,CAAK,MAAA,KAAW,CAAA,CAAA,EAC7D;AACA,IAAA,MAAM,IAAI,UAAU,mDAAmD,CAAA;AAAA,EACzE;AAEA,EAAA,IACE,QAAQ,eAAA,KAAoB,MAAA,IAC5B,OAAO,OAAA,CAAQ,oBAAoB,UAAA,EACnC;AACA,IAAA,MAAM,IAAI,UAAU,sDAAsD,CAAA;AAAA,EAC5E;AACF;AAMA,SAAS,MAAA,CACP,KAAA,EACA,IAAA,EACA,OAAA,EACA,KAAA,EACM;AACN,EAAA,IAAI,KAAA,CAAM,QAAA,CAAS,IAAI,CAAA,EAAG;AAC1B,EAAA,KAAA,CAAM,QAAA,CAAS,IAAI,CAAA,GAAI,IAAA;AACvB,EAAA,MAAM,MAAM,IAAI,WAAA,CAAY,MAAM,KAAA,CAAM,IAAA,EAAM,SAAS,KAAK,CAAA;AAC5D,EAAA,IAAI;AACF,IAAA,KAAA,CAAM,gBAAgB,GAAG,CAAA;AAAA,EAC3B,CAAA,CAAA,MAAQ;AAAA,EAGR;AACF;AAEA,SAAS,eAAA,CACP,KAAA,EACA,KAAA,EACA,KAAA,EACM;AACN,EAAA,IAAI,KAAA,CAAM,SAAS,eAAA,EAAiB;AACpC,EAAA,KAAA,CAAM,SAAS,eAAA,GAAkB,IAAA;AAGjC,EAAA,MAAM,cAAA,GACJ,KAAA,CAAM,OAAA,CAAQ,MAAA,GAAS,GAAA,GAAM,KAAA,CAAM,OAAA,CAAQ,KAAA,CAAM,CAAA,EAAG,GAAG,CAAA,GAAI,KAAA,CAAM,OAAA;AACnE,EAAA,MAAM,MAAM,IAAI,WAAA;AAAA,IACd,iBAAA;AAAA,IACA,KAAA,CAAM,IAAA;AAAA,IACN,qBAAqB,KAAA,CAAM,IAAI,CAAA,iCAAA,EAAoC,KAAK,aAAa,cAAc,CAAA;AAAA,GACrG;AACA,EAAA,IAAI;AACF,IAAA,KAAA,CAAM,gBAAgB,GAAG,CAAA;AAAA,EAC3B,CAAA,CAAA,MAAQ;AAAA,EAER;AACF;AAUA,SAAS,eAAA,CACP,KAAA,EACA,YAAA,EACA,MAAA,EACA,KAAA,EACM;AACN,EAAA,IAAI,KAAA,CAAM,SAAS,iBAAA,EAAmB;AACtC,EAAA,KAAA,CAAM,SAAS,iBAAA,GAAoB,IAAA;AACnC,EAAA,MAAM,MAAM,IAAI,WAAA;AAAA,IACd,mBAAA;AAAA,IACA,KAAA,CAAM,IAAA;AAAA,IACN,qBAAqB,KAAA,CAAM,IAAI,CAAA,8BAAA,EAAiC,YAAY,YAAY,MAAM,CAAA,CAAA;AAAA,IAC9F;AAAA,GACF;AACA,EAAA,IAAI;AACF,IAAA,KAAA,CAAM,gBAAgB,GAAG,CAAA;AAAA,EAC3B,CAAA,CAAA,MAAQ;AAAA,EAER;AACF;AAmBO,SAAS,sBACd,OAAA,EACW;AACX,EAAA,eAAA,CAAgB,OAAO,CAAA;AAEvB,EAAA,MAAM,qBAAA,GAAwB,QAAQ,qBAAA,IAAyB,KAAA;AAC/D,EAAuB,gBAAA;AAAA,IACrB,OAAA,CAAQ,QAAA;AAAA,IACR;AAAA;AAOF,EAAA,MAAM,KAAA,GAA8B;AAAA,IAClC,UAAU,OAAA,CAAQ,QAAA;AAAA,IAClB,IAAA,EAAM,QAAQ,IAAA,IAAQ,QAAA;AAAA,IACtB,eAAA,EAAiB,QAAQ,eAAA,IAAmB,mBAAA;AAAA,IAC5C,UAAU,OAAA,CAAQ,QAAA;AAAA,IAClB,OAAA,EAAS,IAAA;AAAA;AAAA,IACT,iBAAA,EAAmB,KAAA;AAAA,IACnB,iBAAA,EAAmB,IAAA;AAAA,IACnB,gBAAA,EAAkB,KAAA;AAAA,IAClB,QAAA,EAAU;AAAA,MACR,eAAA,EAAiB,KAAA;AAAA,MACjB,kBAAA,EAAoB,KAAA;AAAA,MACpB,qBAAA,EAAuB,KAAA;AAAA,MACvB,iBAAA,EAAmB,KAAA;AAAA,MACnB,yBAAA,EAA2B;AAAA;AAC7B,GACF;AAQA,EAAA,IAAI,OAAA,CAAQ,aAAa,MAAA,EAAW;AAClC,IAAA,MAAM,cAAA,GAAsD;AAAA,MAC1D,YAAA,EAAc,QAAQ,QAAA,CAAS,YAAA;AAAA,MAC/B,KAAA,EAAO,CAAC,MAAA,KAAW;AACjB,QAAA,aAAA,CAAc,OAAO,MAAM,CAAA;AAAA,MAC7B;AAAA,KACF;AACA,IAAA,IAAI,OAAA,CAAQ,QAAA,CAAS,aAAA,KAAkB,MAAA,EAAW;AAChD,MAAA,cAAA,CAAe,aAAA,GAAgB,QAAQ,QAAA,CAAS,aAAA;AAAA,IAClD;AACA,IAAA,KAAA,CAAM,OAAA,GAAU,cAAc,cAAc,CAAA;AAAA,EAC9C;AAEA,EAAA,MAAM,kBAAkB,MAAY;AAKlC,IAAA,IAAI,KAAA,CAAM,YAAY,IAAA,EAAM;AAC1B,MAAA,KAAA,CAAM,QAAQ,KAAA,EAAM;AAAA,IACtB;AAAA,EACF,CAAA;AAEA,EAAA,SAAS,iBAAA,GAA0B;AACjC,IAAA,IAAI,MAAM,iBAAA,EAAmB;AAC7B,IAAA,KAAA,CAAM,iBAAA,GAAoB,IAAA;AAC1B,IAAA,KAAA,CAAM,iBAAA,GAAoB,uBAAuB,eAAe,CAAA;AAAA,EAClE;AAEA,EAAA,SAAS,KAAK,KAAA,EAAuB;AACnC,IAAA,IAAI,MAAM,gBAAA,EAAkB;AAE5B,IAAA,IAAI,OAAA;AACJ,IAAA,IAAI;AACF,MAAA,OAAA,GAAU,IAAA,CAAK,UAAU,KAAK,CAAA;AAAA,IAChC,SAAS,KAAA,EAAO;AACd,MAAA,MAAA;AAAA,QACE,KAAA;AAAA,QACA,uBAAA;AAAA,QACA,CAAA,kBAAA,EAAqB,MAAM,IAAI,CAAA,2CAAA,CAAA;AAAA,QAC/B;AAAA,OACF;AACA,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,KAAA,GAAQ,qBAAqB,OAAO,CAAA;AAC1C,IAAA,IAAI,QAAQ,uBAAA,EAAyB;AAMnC,MAAA,eAAA,CAAgB,KAAA,EAAO,OAAO,KAAK,CAAA;AACnC,MAAA;AAAA,IACF;AAEA,IAAA,iBAAA,EAAkB;AAElB,IAAA,IAAI,KAAA,CAAM,YAAY,IAAA,EAAM;AAG1B,MAAA,KAAA,CAAM,OAAA,CAAQ,KAAK,KAAK,CAAA;AACxB,MAAA;AAAA,IACF;AAEA,IAAA,WAAA,CAAY,OAAO,OAAO,CAAA;AAAA,EAC5B;AAEA,EAAA,SAAS,KAAA,GAAuB;AAG9B,IAAA,IAAI,KAAA,CAAM,YAAY,IAAA,EAAM;AAC1B,MAAA,KAAA,CAAM,QAAQ,KAAA,EAAM;AAAA,IACtB;AACA,IAAA,OAAO,QAAQ,OAAA,EAAQ;AAAA,EACzB;AAEA,EAAA,SAAS,QAAA,GAA0B;AACjC,IAAA,IAAI,KAAA,CAAM,gBAAA,EAAkB,OAAO,OAAA,CAAQ,OAAA,EAAQ;AACnD,IAAA,KAAA,CAAM,gBAAA,GAAmB,IAAA;AAOzB,IAAA,IAAI,KAAA,CAAM,YAAY,IAAA,EAAM;AAC1B,MAAA,KAAA,CAAM,QAAQ,KAAA,EAAM;AACpB,MAAA,KAAA,CAAM,QAAQ,QAAA,EAAS;AAAA,IACzB;AAEA,IAAA,IAAI,KAAA,CAAM,sBAAsB,IAAA,EAAM;AACpC,MAAA,IAAI;AACF,QAAA,KAAA,CAAM,iBAAA,EAAkB;AAAA,MAC1B,SAAS,KAAA,EAAO;AACd,QAAA,MAAA;AAAA,UACE,KAAA;AAAA,UACA,2BAAA;AAAA,UACA,CAAA,kBAAA,EAAqB,MAAM,IAAI,CAAA,kCAAA,CAAA;AAAA,UAC/B;AAAA,SACF;AAAA,MACF;AACA,MAAA,KAAA,CAAM,iBAAA,GAAoB,IAAA;AAC1B,MAAA,KAAA,CAAM,iBAAA,GAAoB,KAAA;AAAA,IAC5B;AACA,IAAA,OAAO,QAAQ,OAAA,EAAQ;AAAA,EACzB;AAMA,EAAA,SAAS,WAAA,CAAYA,QAA6B,OAAA,EAAuB;AACvE,IAAA,MAAM,EAAE,aAAA,EAAe,QAAA,EAAS,GAAI,qBAAA,EAAsB;AAE1D,IAAA,IAAI,CAAC,aAAA,IAAiB,CAAC,QAAA,EAAU;AAC/B,MAAA,MAAA;AAAA,QACEA,MAAAA;AAAA,QACA,oBAAA;AAAA,QACA,CAAA,kBAAA,EAAqBA,OAAM,IAAI,CAAA,0EAAA;AAAA,OACjC;AACA,MAAA;AAAA,IACF;AAEA,IAAA,IAAI,aAAA,IAAiB,SAAA,CAAUA,MAAAA,CAAM,QAAA,EAAU,OAAO,CAAA,EAAG;AACvD,MAAA;AAAA,IACF;AAEA,IAAA,IAAI,QAAA,EAAU;AACZ,MAAA,iBAAA,CAAkBA,MAAAA,CAAM,QAAA,EAAU,OAAO,CAAA,CAAE,IAAA;AAAA,QACzC,CAAC,EAAA,KAAO;AACN,UAAA,IAAI,CAAC,EAAA,EAAI;AACP,YAAA,MAAA;AAAA,cACEA,MAAAA;AAAA,cACA,uBAAA;AAAA,cACA,CAAA,kBAAA,EAAqBA,OAAM,IAAI,CAAA,8CAAA;AAAA,aACjC;AAAA,UACF;AAAA,QACF,CAAA;AAAA,QACA,CAAC,KAAA,KAAmB;AAClB,UAAA,MAAA;AAAA,YACEA,MAAAA;AAAA,YACA,uBAAA;AAAA,YACA,CAAA,kBAAA,EAAqBA,OAAM,IAAI,CAAA,iCAAA,CAAA;AAAA,YAC/B;AAAA,WACF;AAAA,QACF;AAAA,OACF;AACA,MAAA;AAAA,IACF;AAEA,IAAA,MAAA;AAAA,MACEA,MAAAA;AAAA,MACA,uBAAA;AAAA,MACA,CAAA,kBAAA,EAAqBA,OAAM,IAAI,CAAA,qEAAA;AAAA,KACjC;AAAA,EACF;AAEA,EAAA,SAAS,aAAA,CACPA,QACA,MAAA,EACM;AACN,IAAA,IAAI,MAAA,CAAO,WAAW,CAAA,EAAG;AAKzB,IAAA,IAAI,QAAA;AACJ,IAAA,IAAI;AACF,MAAA,QAAA,GAAW,IAAA,CAAK,SAAA,CAAU,EAAE,MAAA,EAAQ,CAAA;AAAA,IACtC,SAAS,KAAA,EAAO;AACd,MAAA,eAAA;AAAA,QACEA,MAAAA;AAAA,QACA,MAAA,CAAO,MAAA;AAAA,QACP,sCAAA;AAAA,QACA;AAAA,OACF;AACA,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,KAAA,GAAQ,qBAAqB,QAAQ,CAAA;AAC3C,IAAA,IAAI,QAAQ,uBAAA,EAAyB;AAKnC,MAAA,eAAA;AAAA,QACEA,MAAAA;AAAA,QACA,MAAA,CAAO,MAAA;AAAA,QACP,CAAA,cAAA,EAAiB,KAAK,CAAA,eAAA,EAAkB,uBAAuB,CAAA,MAAA;AAAA,OACjE;AACA,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,EAAE,aAAA,EAAe,QAAA,EAAS,GAAI,qBAAA,EAAsB;AAE1D,IAAA,IAAI,CAAC,aAAA,IAAiB,CAAC,QAAA,EAAU;AAC/B,MAAA,MAAA;AAAA,QACEA,MAAAA;AAAA,QACA,oBAAA;AAAA,QACA,CAAA,kBAAA,EAAqBA,OAAM,IAAI,CAAA,0EAAA;AAAA,OACjC;AACA,MAAA;AAAA,IACF;AAEA,IAAA,IAAI,aAAA,IAAiB,SAAA,CAAUA,MAAAA,CAAM,QAAA,EAAU,QAAQ,CAAA,EAAG;AACxD,MAAA;AAAA,IACF;AAEA,IAAA,IAAI,QAAA,EAAU;AACZ,MAAA,iBAAA,CAAkBA,MAAAA,CAAM,QAAA,EAAU,QAAQ,CAAA,CAAE,IAAA;AAAA,QAC1C,CAAC,EAAA,KAAO;AACN,UAAA,IAAI,CAAC,EAAA,EAAI;AACP,YAAA,eAAA;AAAA,cACEA,MAAAA;AAAA,cACA,MAAA,CAAO,MAAA;AAAA,cACP,CAAA,oCAAA;AAAA,aACF;AAAA,UACF;AAAA,QACF,CAAA;AAAA,QACA,CAAC,KAAA,KAAmB;AAClB,UAAA,eAAA;AAAA,YACEA,MAAAA;AAAA,YACA,MAAA,CAAO,MAAA;AAAA,YACP,CAAA,uBAAA,CAAA;AAAA,YACA;AAAA,WACF;AAAA,QACF;AAAA,OACF;AACA,MAAA;AAAA,IACF;AAEA,IAAA,eAAA;AAAA,MACEA,MAAAA;AAAA,MACA,MAAA,CAAO,MAAA;AAAA,MACP,CAAA,2DAAA;AAAA,KACF;AAAA,EACF;AAEA,EAAA,SAAS,qBAAA,GAGP;AACA,IAAA,MAAM,MAAO,UAAA,CAAyC,SAAA;AACtD,IAAA,MAAM,aAAA,GACJ,GAAA,KAAQ,MAAA,IAAa,OAAO,IAAI,UAAA,KAAe,UAAA;AACjD,IAAA,MAAM,QAAA,GACJ,OAAQ,UAAA,CAAwC,KAAA,KAAU,UAAA;AAC5D,IAAA,OAAO,EAAE,eAAe,QAAA,EAAS;AAAA,EACnC;AAEA,EAAA,OAAO;AAAA,IACL,MAAM,KAAA,CAAM,IAAA;AAAA,IACZ,IAAA;AAAA,IACA,KAAA;AAAA,IACA;AAAA,GACF;AACF;AAEA,SAAS,oBAAoB,IAAA,EAAmB;AAEhD","file":"transport-beacon.mjs","sourcesContent":["/**\n * Batcher state machine for opt-in beacon-transport batching.\n *\n * T027 replaces T024's stub with the working state machine. T028\n * wires this batcher into `createBeaconTransport`'s send path.\n *\n * Behaviour (data-model.md § BatcherOptions + contracts/batching.md):\n *\n * - `push(event)` appends to the in-memory buffer.\n * - If this is the first event in an empty batch AND\n * `maxBatchAgeMs` is set, arm a one-shot timer.\n * - If the buffer reaches `maxBatchSize`, flush synchronously\n * at the end of `push`.\n * - `flush()` drains any pending buffer through the consumer's\n * `flush` callback. Empty buffer → no-op.\n * - `shutdown()` cancels the timer and nulls the flush callback\n * reference, so any timer/microtask queued before shutdown\n * becomes a no-op when it fires.\n *\n * Buffer-clear ordering (B-5c): the buffer is cleared BEFORE the\n * consumer's flush callback is invoked. A re-entrant `push()`\n * during the callback sees an empty buffer and starts a fresh\n * batch — no double-flush, no re-push.\n *\n * Throw safety (B-5d): if the consumer's flush callback throws,\n * the throw is swallowed. The events that were drained into the\n * callback are gone — the batcher does NOT re-push them. The\n * batcher itself never throws from `push`/`flush`/`shutdown`.\n *\n * Boundary discipline (TB-11): the only `src/` import is\n * `import type` from `'../api/types.js'`. The module is import-pure\n * — no listeners at module scope, no timers at module scope.\n */\n\nimport type { LogEvent } from '../api/types.js';\n\nexport interface BatcherOptions {\n /** Maximum events per batch. Positive integer in [1, 1000]. */\n maxBatchSize: number;\n /**\n * Optional age trigger. When set, a one-shot timer fires `maxBatchAgeMs`\n * ms after the first event enters an empty batch, flushing whatever is\n * pending. Cancelled on any other flush trigger.\n */\n maxBatchAgeMs?: number;\n /**\n * Callback invoked with the drained event array on every flush trigger\n * (size-threshold, age-timer, manual `flush()`). Throws are swallowed\n * — the events are considered lost from the batcher's perspective.\n */\n flush: (events: LogEvent[]) => void;\n}\n\nexport interface Batcher {\n /** Push an event onto the buffer. May trigger an immediate flush. */\n push(event: LogEvent): void;\n /** Drain whatever is pending. No-op when the buffer is empty. */\n flush(): void;\n /** Cancel the age timer and inhibit further flush callbacks. */\n shutdown(): void;\n}\n\nexport function createBatcher(opts: BatcherOptions): Batcher {\n const buffer: LogEvent[] = [];\n let maxAgeTimer: ReturnType<typeof setTimeout> | null = null;\n let flushCallback: ((events: LogEvent[]) => void) | null = opts.flush;\n\n const clearTimer = (): void => {\n if (maxAgeTimer !== null) {\n clearTimeout(maxAgeTimer);\n maxAgeTimer = null;\n }\n };\n\n const armTimer = (): void => {\n if (opts.maxBatchAgeMs === undefined) return;\n maxAgeTimer = setTimeout(() => {\n maxAgeTimer = null;\n doFlush();\n }, opts.maxBatchAgeMs);\n };\n\n const doFlush = (): void => {\n if (buffer.length === 0) return;\n // Post-shutdown: discard the buffered events silently. The caller\n // is responsible for draining via `flush()` before `shutdown()`\n // if it wants the data delivered.\n if (flushCallback === null) {\n buffer.length = 0;\n clearTimer();\n return;\n }\n // B-5c: copy + clear BEFORE the user callback runs, so a\n // re-entrant push during the callback starts a fresh batch.\n const events = buffer.slice();\n buffer.length = 0;\n clearTimer();\n try {\n flushCallback(events);\n } catch {\n // B-5d: swallow. The events are gone — they were copied out and\n // the consumer's callback chose to throw. Re-pushing into the\n // buffer would conflict with the \"single-flush-attempt\" model\n // in contracts/batching.md B-5.\n }\n };\n\n return {\n push(event: LogEvent): void {\n // Post-shutdown sends are silently dropped. The transport-level\n // shutdownComplete flag (T028) handles the same guarantee at\n // the createBeaconTransport boundary; this is defense in depth.\n if (flushCallback === null) return;\n buffer.push(event);\n // B-8a: arm timer when first event enters an empty batch.\n if (buffer.length === 1 && opts.maxBatchAgeMs !== undefined) {\n armTimer();\n }\n // B-5b: size threshold reached → flush synchronously at the end\n // of this push call.\n if (buffer.length >= opts.maxBatchSize) {\n doFlush();\n }\n },\n flush(): void {\n doFlush();\n },\n shutdown(): void {\n clearTimer();\n // Discard any buffered events. The caller (T028's beacon-\n // transport.ts shutdown handler) calls flush() before shutdown()\n // if it wants the pending batch drained.\n buffer.length = 0;\n flushCallback = null;\n },\n };\n}\n","/**\n * Low-level delivery primitives for the beacon transport.\n *\n * The functions here are deliberately small and composable. The\n * `beacon-transport.ts` factory (T016) wires them into the per-event\n * delivery policy defined in `contracts/delivery.md` D-3..D-7:\n *\n * 1. Compute `payload = JSON.stringify(event)`.\n * 2. If `getPayloadByteLength(payload) > BEACON_SIZE_LIMIT_BYTES`:\n * drop the event and fire `oversized_event` (F-2).\n * 3. `tryBeacon(endpoint, payload)`. On `true`, done.\n * 4. `await tryFetchKeepalive(endpoint, payload)`. On `true`, done.\n * On `false`, drop with `transport_send_failed` (F-4). On reject,\n * drop with `transport_send_failed` carrying `.cause` (F-7).\n *\n * Boundary discipline (TB-11): zero imports from `src/internal/**`,\n * `src/runtime/**`, `src/pipeline/**`, `src/config/**`, `src/context/**`,\n * or `src/transport/**`. Zero vendor-SDK imports.\n *\n * Specs: `specs/002-beacon-transport/contracts/delivery.md` D-2..D-7;\n * `specs/002-beacon-transport/research.md` §1, §2.\n */\n\n/**\n * The effective per-call `navigator.sendBeacon` size budget. ~64 KiB\n * per origin in modern browsers (research §1). Bodies whose serialized\n * byte length exceeds this constant short-circuit to `oversized_event`\n * without invoking either primitive — the fetch keepalive fallback\n * shares the same budget (research §2) so attempting it would waste a\n * network call on a guaranteed-failure payload.\n */\nexport const BEACON_SIZE_LIMIT_BYTES = 65536;\n\n/**\n * Return the UTF-8 byte length of `payload`. Uses `TextEncoder` because\n * `payload.length` is UTF-16 code-unit count, not byte count — and\n * `sendBeacon`'s budget is measured in bytes.\n *\n * `TextEncoder` is baseline-available in every modern browser this\n * package targets; it is the only ambient API the transport touches at\n * the delivery layer.\n */\nexport function getPayloadByteLength(payload: string): number {\n return new TextEncoder().encode(payload).length;\n}\n\n/**\n * Attempt delivery via `navigator.sendBeacon(endpoint, blob)` where\n * `blob` is `new Blob([payload], { type: 'application/json' })`. The\n * `Blob` form is required so the browser sends the body as\n * `application/json` and so the request is unambiguously body-only\n * (T-S1..T-S5).\n *\n * Returns:\n * - `true` — the browser accepted the payload onto the beacon queue.\n * - `false` — the browser refused (size limit / queue full / etc.),\n * `navigator.sendBeacon` is unavailable, OR a synchronous\n * throw was caught internally. Either way the caller\n * should fall through to `tryFetchKeepalive`.\n *\n * Never throws. Never returns a Promise.\n */\nexport function tryBeacon(endpoint: string, payload: string): boolean {\n const nav = (globalThis as { navigator?: Navigator }).navigator;\n if (nav === undefined || typeof nav.sendBeacon !== 'function') {\n return false;\n }\n try {\n const blob = new Blob([payload], { type: 'application/json' });\n return nav.sendBeacon(endpoint, blob);\n } catch {\n // Some legacy environments throw on oversized sendBeacon payloads\n // instead of returning false. Treat as \"refused\" so the caller\n // falls through; the same caller's size pre-check already short-\n // circuited the oversized case for modern runtimes.\n return false;\n }\n}\n\n/**\n * Attempt delivery via `fetch(endpoint, { method: 'POST', body: payload,\n * keepalive: true, headers: { 'content-type': 'application/json' },\n * credentials: 'same-origin' })`.\n *\n * Resolves:\n * - `true` — `fetch` resolved with `response.ok` (status in\n * `[200, 299]`).\n * - `false` — `fetch` is unavailable in the runtime, OR `fetch`\n * resolved with a non-2xx status.\n *\n * Rejects:\n * - With the underlying `fetch` rejection reason if the Promise\n * rejected (network error, browser-enforced budget overflow, etc.).\n * The caller wraps this in a `BeaconError(transport_send_failed)`\n * carrying `.cause` per F-7.\n *\n * The `credentials: 'same-origin'` choice keeps cookies from leaking\n * cross-origin by default (Principle IV). Consumers who need credentialed\n * delivery to a same-origin endpoint inherit the correct behaviour\n * automatically; cross-origin endpoints get no cookies, which is the\n * safer default.\n */\nexport async function tryFetchKeepalive(\n endpoint: string,\n payload: string,\n): Promise<boolean> {\n const fetchFn = (globalThis as { fetch?: typeof fetch }).fetch;\n if (typeof fetchFn !== 'function') {\n return false;\n }\n const response = await fetchFn(endpoint, {\n method: 'POST',\n body: payload,\n headers: { 'content-type': 'application/json' },\n keepalive: true,\n credentials: 'same-origin',\n });\n return response.ok;\n}\n","/**\n * Construction-time endpoint validation for `createBeaconTransport`.\n *\n * Locked behaviour:\n * - HTTPS endpoints always pass.\n * - HTTP endpoints pass IFF `allowInsecureLoopback === true` AND the\n * parsed URL's hostname is in `{ localhost, 127.0.0.1, [::1] }`.\n * - Every other case throws a typed error at construction time, before\n * any logger derives the runtime and before any listener is\n * attached (FR-016).\n *\n * The function is pure and side-effect-free: it parses the endpoint via\n * `new URL(...)` and inspects the result. It MUST NOT read ambient state\n * (no `process.env`, no `window.location`, no build-define plugin), and\n * MUST NOT read `allowInsecureLoopback` from anywhere except the\n * argument the caller passed (FR-016 clarification).\n *\n * Specs: `specs/002-beacon-transport/contracts/failure-modes.md` F-1;\n * `specs/002-beacon-transport/contracts/transport-beacon-public-api.md`\n * TB-5; `specs/002-beacon-transport/spec.md` FR-016.\n */\n\n/**\n * The exact set of hostnames permitted under\n * `allowInsecureLoopback: true`. WHATWG URL normalises `http://[::1]`\n * and `http://[0:0:0:0:0:0:0:1]` to hostname `[::1]` (with brackets),\n * so the allowlist matches the canonical form.\n */\nconst LOOPBACK_HOSTS: ReadonlySet<string> = new Set([\n 'localhost',\n '127.0.0.1',\n '[::1]',\n]);\n\n/**\n * Validate a consumer-supplied `endpoint` string. Returns the parsed\n * `URL` on success. Throws on every form of violation.\n *\n * The thrown error's `.message` always names (a) the violated\n * constraint and (b) the offending endpoint string, so the consumer\n * can act on the diagnostic without parsing the error's stack.\n */\nexport function validateEndpoint(\n endpoint: unknown,\n allowInsecureLoopback: boolean,\n): URL {\n if (typeof endpoint !== 'string') {\n throw new TypeError(\n `beacon transport: endpoint must be a string, got ${typeName(endpoint)}`,\n );\n }\n\n let parsed: URL;\n try {\n parsed = new URL(endpoint);\n } catch {\n throw new TypeError(\n `beacon transport: invalid endpoint URL: '${endpoint}'`,\n );\n }\n\n if (parsed.protocol === 'https:') {\n return parsed;\n }\n\n if (parsed.protocol === 'http:') {\n if (!allowInsecureLoopback) {\n throw new Error(\n `beacon transport refuses non-HTTPS endpoint '${endpoint}'`,\n );\n }\n if (!LOOPBACK_HOSTS.has(parsed.hostname)) {\n throw new Error(\n `beacon transport: allowInsecureLoopback permits only ` +\n `localhost / 127.0.0.1 / [::1]; got '${parsed.hostname}' ` +\n `in '${endpoint}'`,\n );\n }\n return parsed;\n }\n\n throw new Error(\n `beacon transport refuses non-HTTPS endpoint '${endpoint}' ` +\n `(scheme '${parsed.protocol}' is not permitted)`,\n );\n}\n\nfunction typeName(value: unknown): string {\n if (value === null) return 'null';\n return typeof value;\n}\n","/**\n * Subpath-owned diagnostic error class.\n *\n * Drop notices fired by the beacon transport are `BeaconError` instances\n * (a subclass of `Error`) owned by this subpath. They are NOT\n * `PackageError` instances and do NOT depend on the core's\n * `src/internal/errors/internal-errors.ts` module — preserving the\n * boundary in TB-11 (no runtime imports from `src/internal/**`).\n *\n * The class shape (`.code`, `.transportName`, optional `.cause`) is\n * **by-convention compatible** with `PackageError` so a consumer's\n * diagnostics handler reading `err.code` and `err.transportName` cannot\n * tell the difference between a notice emitted by `SafeTransport`\n * (a `PackageError`) and one emitted by the beacon transport\n * (a `BeaconError`).\n *\n * This module is INTERNAL to the subpath — `src/transport-beacon/index.ts`\n * does NOT re-export `BeaconError` or `BeaconErrorCode`. The\n * `onInternalError` hook receives the instance typed as `Error` per the\n * public callback signature.\n *\n * Specs: `specs/002-beacon-transport/data-model.md` § BeaconError;\n * `specs/002-beacon-transport/contracts/failure-modes.md` F-1..F-10.\n */\n\n/**\n * Documented `BeaconError.code` values. Internal to the subpath; the\n * public consumer surface for these strings is the\n * `BeaconTransportOptions.onInternalError` callback, where the value\n * arrives as `err.code` on an `Error`-shaped argument.\n */\nexport type BeaconErrorCode =\n | 'oversized_event'\n | 'beacon_batch_drop'\n | 'beacon_unavailable'\n | 'transport_send_failed'\n | 'transport_shutdown_failed';\n\n/**\n * Subclass of `Error` carrying a discriminating `.code`, the originating\n * transport's `.transportName`, and an optional `.cause` chain to the\n * underlying failure (a rejected `fetch` Promise, a thrown\n * `JSON.stringify` error, etc.).\n *\n * Construction-time invariants:\n * - `.code` is read-only (set via `Object.defineProperty` on the class\n * side by `readonly` + plain assignment).\n * - `.transportName` is read-only.\n * - `.cause` is read-only when provided (set via `Object.defineProperty`\n * with `writable: false`, `enumerable: true`). When `cause` is\n * `undefined`, the property is left unset — matching the ES2022\n * `Error.cause` convention where omitted causes are absent rather\n * than `undefined`.\n * - `.name` is `'BeaconError'`.\n */\nexport class BeaconError extends Error {\n readonly code: BeaconErrorCode;\n readonly transportName: string;\n // ES2022 standard Error.cause — declared as readonly so this\n // typechecks regardless of the current `lib` setting in tsconfig and\n // so the type system reflects the runtime non-writable contract.\n declare readonly cause?: unknown;\n\n constructor(\n code: BeaconErrorCode,\n transportName: string,\n message: string,\n cause?: unknown,\n ) {\n super(message);\n this.name = 'BeaconError';\n this.code = code;\n this.transportName = transportName;\n if (cause !== undefined) {\n Object.defineProperty(this, 'cause', {\n value: cause,\n enumerable: true,\n writable: false,\n configurable: false,\n });\n }\n }\n}\n\n/**\n * Type guard for `BeaconError` instances. Distinguishes errors created\n * by this subpath from arbitrary errors that flow through the\n * `onInternalError` callback.\n */\nexport function isBeaconError(value: unknown): value is BeaconError {\n return value instanceof BeaconError;\n}\n","/**\n * Lazy lifecycle helper for the beacon transport's `pagehide` handler.\n *\n * Per FR-008 and contract D-10, the transport MUST NOT attach any\n * global listener at construction time. The `pagehide` listener\n * attaches on the first `send()` call that proceeds past the payload\n * size check, and detaches on `shutdown()`. This module supplies the\n * install/uninstall primitives; the caller (the transport factory)\n * owns the `installed` flag that gates against double-install.\n *\n * Why a separate module: keeps `beacon-transport.ts` focused on the\n * delivery policy, and makes the `globalThis.addEventListener`\n * boundary easy to spy on in tests.\n *\n * Boundary discipline (TB-11): zero imports from anywhere in `src/`,\n * zero vendor-SDK imports. The module is import-pure — no side effects\n * at module-evaluation time.\n *\n * Specs: `specs/002-beacon-transport/contracts/delivery.md` D-10;\n * `specs/002-beacon-transport/research.md` §3.\n */\n\n/**\n * Install a `pagehide` listener on the global event target if one is\n * available, returning an `uninstall()` function that removes exactly\n * that listener.\n *\n * If `globalThis.addEventListener` is not a function (vanishingly rare\n * outside of bare Node-like runtimes), this function is a no-op and\n * returns a no-op uninstaller. The caller's `installed` flag is the\n * authoritative single-install guard — this module does NOT track\n * installation state itself.\n *\n * Uninstall is idempotent at the DOM level: calling `removeEventListener`\n * a second time after the handler is already detached is a no-op\n * specified by the DOM standard.\n */\nexport function installPagehideHandler(handler: () => void): () => void {\n const target = globalThis as {\n addEventListener?: typeof globalThis.addEventListener;\n removeEventListener?: typeof globalThis.removeEventListener;\n };\n if (typeof target.addEventListener !== 'function') {\n return noopUninstall;\n }\n target.addEventListener('pagehide', handler);\n return (): void => {\n if (typeof target.removeEventListener === 'function') {\n target.removeEventListener('pagehide', handler);\n }\n };\n}\n\nfunction noopUninstall(): void {\n // intentionally empty\n}\n","/**\n * Default-mode `createBeaconTransport` factory.\n *\n * Composes the primitives from T004 (BeaconError), T005\n * (validateEndpoint), T006 (delivery primitives), and T007\n * (installPagehideHandler) into the `Transport`-shaped factory the\n * `./transport-beacon` subpath exports.\n *\n * Per-event delivery policy (D-3..D-7, F-2..F-7):\n *\n * send(event)\n * ├── if shutdownComplete: no-op\n * ├── payload = JSON.stringify(event) // F-4 cause if throws\n * ├── if size > 64 KiB → oversized_event drop // F-2 (D-3)\n * ├── lazy install pagehide listener // FR-008 / D-10\n * ├── if !sendBeacon AND !fetch → beacon_unavailable drop // F-3 (D-7)\n * ├── tryBeacon(endpoint, payload) // D-4\n * │ └── true ────────────────────────────── delivered\n * └── tryFetchKeepalive(endpoint, payload) // D-5, D-6\n * ├── 2xx ───────────────────────────────── delivered\n * ├── non-2xx → transport_send_failed // F-4\n * └── reject → transport_send_failed // F-4, F-7 (with .cause)\n *\n * Every notice is rate-limited per `state.notified[code]` — one\n * notice per failure class per transport instance per session (F-8).\n *\n * The factory NEVER throws from `send()`, `flush()`, or `shutdown()`.\n * Construction-time errors (invalid options, non-HTTPS endpoint) are\n * the only throws — and they happen at the consumer's call site,\n * outside the emit hot path.\n *\n * Boundary discipline (TB-11): the only `src/` import in this file\n * is `import type` from `'../api/types.js'`; the other imports are\n * intra-subpath.\n *\n * Specs: `specs/002-beacon-transport/contracts/delivery.md` D-1..D-12;\n * `specs/002-beacon-transport/contracts/failure-modes.md` F-1..F-10;\n * `specs/002-beacon-transport/data-model.md` § BeaconTransportState.\n */\n\nimport type { LogEvent, Transport } from '../api/types.js';\n\nimport { type Batcher, createBatcher } from './batcher.js';\nimport {\n BEACON_SIZE_LIMIT_BYTES,\n getPayloadByteLength,\n tryBeacon,\n tryFetchKeepalive,\n} from './delivery.js';\nimport { validateEndpoint } from './endpoint-validation.js';\nimport { BeaconError, type BeaconErrorCode } from './errors.js';\nimport { installPagehideHandler } from './lifecycle.js';\n\n// ---------------------------------------------------------------------------\n// Public options shape (data-model.md § BeaconTransportOptions)\n// ---------------------------------------------------------------------------\n\nexport interface BeaconTransportOptions {\n endpoint: string;\n batching?: {\n maxBatchSize: number;\n maxBatchAgeMs?: number;\n };\n allowInsecureLoopback?: boolean;\n name?: string;\n onInternalError?: (err: Error) => void;\n}\n\n// ---------------------------------------------------------------------------\n// Internal per-instance state (data-model.md § BeaconTransportState)\n// ---------------------------------------------------------------------------\n\ninterface BeaconTransportState {\n readonly endpoint: string;\n readonly name: string;\n readonly onInternalError: (err: Error) => void;\n readonly batching: BeaconTransportOptions['batching'] | undefined;\n /** Batcher instance when batching is enabled; `null` in default mode. */\n batcher: Batcher | null;\n pagehideInstalled: boolean;\n pagehideUninstall: (() => void) | null;\n shutdownComplete: boolean;\n notified: {\n oversized_event: boolean;\n beacon_unavailable: boolean;\n transport_send_failed: boolean;\n beacon_batch_drop: boolean;\n transport_shutdown_failed: boolean;\n };\n}\n\n// ---------------------------------------------------------------------------\n// Construction-time validation (F-1, TB-6)\n// ---------------------------------------------------------------------------\n\nfunction validateOptions(options: BeaconTransportOptions): void {\n if (typeof options !== 'object' || options === null) {\n throw new TypeError('beacon transport: options must be a non-null object');\n }\n\n if (options.batching !== undefined) {\n if (typeof options.batching !== 'object' || options.batching === null) {\n throw new TypeError(\n 'beacon transport: options.batching must be an object',\n );\n }\n const { maxBatchSize, maxBatchAgeMs } = options.batching;\n if (\n !Number.isInteger(maxBatchSize) ||\n maxBatchSize < 1 ||\n maxBatchSize > 1000\n ) {\n throw new RangeError(\n `beacon transport: batching.maxBatchSize must be an integer in [1, 1000], got ${String(maxBatchSize)}`,\n );\n }\n if (maxBatchAgeMs !== undefined) {\n if (!Number.isFinite(maxBatchAgeMs) || maxBatchAgeMs < 0) {\n throw new RangeError(\n `beacon transport: batching.maxBatchAgeMs must be a non-negative finite number, got ${String(maxBatchAgeMs)}`,\n );\n }\n }\n }\n\n if (\n options.allowInsecureLoopback !== undefined &&\n typeof options.allowInsecureLoopback !== 'boolean'\n ) {\n throw new TypeError(\n `beacon transport: allowInsecureLoopback must be a boolean, got ${typeof options.allowInsecureLoopback}`,\n );\n }\n\n if (\n options.name !== undefined &&\n (typeof options.name !== 'string' || options.name.length === 0)\n ) {\n throw new TypeError('beacon transport: name must be a non-empty string');\n }\n\n if (\n options.onInternalError !== undefined &&\n typeof options.onInternalError !== 'function'\n ) {\n throw new TypeError('beacon transport: onInternalError must be a function');\n }\n}\n\n// ---------------------------------------------------------------------------\n// Notice routing (F-2..F-7, F-8 rate-limit)\n// ---------------------------------------------------------------------------\n\nfunction notify(\n state: BeaconTransportState,\n code: BeaconErrorCode,\n message: string,\n cause?: unknown,\n): void {\n if (state.notified[code]) return;\n state.notified[code] = true;\n const err = new BeaconError(code, state.name, message, cause);\n try {\n state.onInternalError(err);\n } catch {\n // Consumer's onInternalError threw. Swallow — we cannot re-enter the\n // same callback in response to a failed notification (FR-003).\n }\n}\n\nfunction notifyOversized(\n state: BeaconTransportState,\n event: LogEvent,\n bytes: number,\n): void {\n if (state.notified.oversized_event) return;\n state.notified.oversized_event = true;\n // Truncate the event message to 256 chars (F-2 notice integrity: never\n // include attrs/error/context — only the message, bounded).\n const messagePreview =\n event.message.length > 256 ? event.message.slice(0, 256) : event.message;\n const err = new BeaconError(\n 'oversized_event',\n state.name,\n `beacon transport '${state.name}' dropped oversized event: bytes=${bytes}, message=${messagePreview}`,\n );\n try {\n state.onInternalError(err);\n } catch {\n // swallow per FR-003\n }\n}\n\n/**\n * Fire a `beacon_batch_drop` notice for a failed batch flush. The\n * notice payload is **structural only** (B-11): droppedCount + a short\n * reason summary + the transport name. No event content (no message,\n * no attrs, no error, no context) — including such content would risk\n * leaking the same payload whose size or shape was the problem in the\n * first place.\n */\nfunction notifyBatchDrop(\n state: BeaconTransportState,\n droppedCount: number,\n reason: string,\n cause?: unknown,\n): void {\n if (state.notified.beacon_batch_drop) return;\n state.notified.beacon_batch_drop = true;\n const err = new BeaconError(\n 'beacon_batch_drop',\n state.name,\n `beacon transport '${state.name}' dropped batch: droppedCount=${droppedCount}, reason=${reason}`,\n cause,\n );\n try {\n state.onInternalError(err);\n } catch {\n // swallow per FR-003\n }\n}\n\n// ---------------------------------------------------------------------------\n// Public factory\n// ---------------------------------------------------------------------------\n\n/**\n * Construct a body-only HTTPS beacon transport conforming to the\n * `Transport` contract from `@your-org/frontend-logging-sdk`. Pass the\n * result directly to `configureLogging({ transports: [...] })`.\n *\n * Construction throws synchronously on every form of invalid input\n * (non-string endpoint, malformed URL, non-HTTPS endpoint without\n * `allowInsecureLoopback`, batching fields out of range, etc.) — see\n * `contracts/failure-modes.md` F-1.\n *\n * Once constructed, `send()` / `flush()` / `shutdown()` NEVER throw\n * to the caller. Every drop routes through `options.onInternalError`.\n */\nexport function createBeaconTransport(\n options: BeaconTransportOptions,\n): Transport {\n validateOptions(options);\n\n const allowInsecureLoopback = options.allowInsecureLoopback ?? false;\n const parsedEndpoint = validateEndpoint(\n options.endpoint,\n allowInsecureLoopback,\n );\n // We keep the raw endpoint string (not parsedEndpoint.toString()) so the\n // wire matches exactly what the consumer supplied — important for ingestion\n // endpoints that are path-sensitive.\n void parsedEndpoint;\n\n const state: BeaconTransportState = {\n endpoint: options.endpoint,\n name: options.name ?? 'beacon',\n onInternalError: options.onInternalError ?? noopOnInternalError,\n batching: options.batching,\n batcher: null, // assigned below when batching is enabled\n pagehideInstalled: false,\n pagehideUninstall: null,\n shutdownComplete: false,\n notified: {\n oversized_event: false,\n beacon_unavailable: false,\n transport_send_failed: false,\n beacon_batch_drop: false,\n transport_shutdown_failed: false,\n },\n };\n\n // Batching is opt-in. When enabled, the batcher owns the in-memory\n // buffer; `send()` pushes per-event-sized payloads onto it and the\n // batcher invokes `dispatchBatch` on every flush trigger\n // (size-threshold, age-timer, manual `flush()`, `shutdown()`,\n // pagehide). When disabled, `send()` dispatches the event\n // immediately via `dispatchOne`.\n if (options.batching !== undefined) {\n const batcherOptions: Parameters<typeof createBatcher>[0] = {\n maxBatchSize: options.batching.maxBatchSize,\n flush: (events) => {\n dispatchBatch(state, events);\n },\n };\n if (options.batching.maxBatchAgeMs !== undefined) {\n batcherOptions.maxBatchAgeMs = options.batching.maxBatchAgeMs;\n }\n state.batcher = createBatcher(batcherOptions);\n }\n\n const pagehideHandler = (): void => {\n // Default mode: no buffered state to flush. Batching mode: drain\n // the pending batch via the batcher's flush API, which routes\n // through `dispatchBatch` for the same envelope encoding +\n // primitive dispatch as a size/age-triggered flush (B-9).\n if (state.batcher !== null) {\n state.batcher.flush();\n }\n };\n\n function ensureLazyInstall(): void {\n if (state.pagehideInstalled) return;\n state.pagehideInstalled = true;\n state.pagehideUninstall = installPagehideHandler(pagehideHandler);\n }\n\n function send(event: LogEvent): void {\n if (state.shutdownComplete) return;\n\n let payload: string;\n try {\n payload = JSON.stringify(event);\n } catch (cause) {\n notify(\n state,\n 'transport_send_failed',\n `beacon transport '${state.name}' failed: JSON.stringify threw on the event`,\n cause,\n );\n return;\n }\n\n const bytes = getPayloadByteLength(payload);\n if (bytes > BEACON_SIZE_LIMIT_BYTES) {\n // B-7 / F-2: oversized single event is ejected pre-push with one\n // oversized_event notice (rate-limited per session). In batching\n // mode this guarantees the envelope is constructed from\n // bounded-size events only; the per-envelope size check (B-6)\n // still catches over-aggressive maxBatchSize settings.\n notifyOversized(state, event, bytes);\n return;\n }\n\n ensureLazyInstall();\n\n if (state.batcher !== null) {\n // Batching mode: buffer the event. The batcher decides when to\n // flush (size threshold, optional age timer, pagehide, shutdown).\n state.batcher.push(event);\n return;\n }\n\n dispatchOne(state, payload);\n }\n\n function flush(): Promise<void> {\n // Default mode: no buffer to drain.\n // Batching mode: trigger an immediate flush of the current batch.\n if (state.batcher !== null) {\n state.batcher.flush();\n }\n return Promise.resolve();\n }\n\n function shutdown(): Promise<void> {\n if (state.shutdownComplete) return Promise.resolve();\n state.shutdownComplete = true;\n\n // Best-effort drain of the pending batch BEFORE detaching the\n // listener (B-10). The batcher's flush callback fires either a\n // successful delivery or a `beacon_batch_drop` notice. After\n // `batcher.shutdown()`, the batcher discards any further events\n // and a queued timer callback becomes a no-op.\n if (state.batcher !== null) {\n state.batcher.flush();\n state.batcher.shutdown();\n }\n\n if (state.pagehideUninstall !== null) {\n try {\n state.pagehideUninstall();\n } catch (cause) {\n notify(\n state,\n 'transport_shutdown_failed',\n `beacon transport '${state.name}' shutdown: listener removal threw`,\n cause,\n );\n }\n state.pagehideUninstall = null;\n state.pagehideInstalled = false;\n }\n return Promise.resolve();\n }\n\n // -------------------------------------------------------------------------\n // Dispatch helpers (closure-scoped — read state via `state` reference)\n // -------------------------------------------------------------------------\n\n function dispatchOne(state: BeaconTransportState, payload: string): void {\n const { hasSendBeacon, hasFetch } = primitiveAvailability();\n\n if (!hasSendBeacon && !hasFetch) {\n notify(\n state,\n 'beacon_unavailable',\n `beacon transport '${state.name}' has no usable delivery primitive (sendBeacon and fetch both unavailable)`,\n );\n return;\n }\n\n if (hasSendBeacon && tryBeacon(state.endpoint, payload)) {\n return; // delivered\n }\n\n if (hasFetch) {\n tryFetchKeepalive(state.endpoint, payload).then(\n (ok) => {\n if (!ok) {\n notify(\n state,\n 'transport_send_failed',\n `beacon transport '${state.name}' failed: fetch fallback resolved with non-2xx`,\n );\n }\n },\n (cause: unknown) => {\n notify(\n state,\n 'transport_send_failed',\n `beacon transport '${state.name}' failed: fetch fallback rejected`,\n cause,\n );\n },\n );\n return;\n }\n\n notify(\n state,\n 'transport_send_failed',\n `beacon transport '${state.name}' failed: sendBeacon returned false and fetch fallback is unavailable`,\n );\n }\n\n function dispatchBatch(\n state: BeaconTransportState,\n events: LogEvent[],\n ): void {\n if (events.length === 0) return;\n\n // Encode envelope. Failure here is unexpected (the events came\n // straight from the pipeline) — handle defensively as a batch\n // drop with the original cause attached.\n let envelope: string;\n try {\n envelope = JSON.stringify({ events });\n } catch (cause) {\n notifyBatchDrop(\n state,\n events.length,\n 'JSON.stringify threw on the envelope',\n cause,\n );\n return;\n }\n\n const bytes = getPayloadByteLength(envelope);\n if (bytes > BEACON_SIZE_LIMIT_BYTES) {\n // B-6: oversized envelope (consumer's maxBatchSize was too\n // aggressive for the average event size). No primitive call —\n // both sendBeacon and fetch-keepalive share the same per-origin\n // ~64 KiB budget so attempting them would waste a network call.\n notifyBatchDrop(\n state,\n events.length,\n `envelope size ${bytes} bytes exceeds ${BEACON_SIZE_LIMIT_BYTES} bytes`,\n );\n return;\n }\n\n const { hasSendBeacon, hasFetch } = primitiveAvailability();\n\n if (!hasSendBeacon && !hasFetch) {\n notify(\n state,\n 'beacon_unavailable',\n `beacon transport '${state.name}' has no usable delivery primitive (sendBeacon and fetch both unavailable)`,\n );\n return;\n }\n\n if (hasSendBeacon && tryBeacon(state.endpoint, envelope)) {\n return; // delivered\n }\n\n if (hasFetch) {\n tryFetchKeepalive(state.endpoint, envelope).then(\n (ok) => {\n if (!ok) {\n notifyBatchDrop(\n state,\n events.length,\n `fetch fallback resolved with non-2xx`,\n );\n }\n },\n (cause: unknown) => {\n notifyBatchDrop(\n state,\n events.length,\n `fetch fallback rejected`,\n cause,\n );\n },\n );\n return;\n }\n\n notifyBatchDrop(\n state,\n events.length,\n `sendBeacon returned false and fetch fallback is unavailable`,\n );\n }\n\n function primitiveAvailability(): {\n hasSendBeacon: boolean;\n hasFetch: boolean;\n } {\n const nav = (globalThis as { navigator?: Navigator }).navigator;\n const hasSendBeacon =\n nav !== undefined && typeof nav.sendBeacon === 'function';\n const hasFetch =\n typeof (globalThis as { fetch?: typeof fetch }).fetch === 'function';\n return { hasSendBeacon, hasFetch };\n }\n\n return {\n name: state.name,\n send,\n flush,\n shutdown,\n };\n}\n\nfunction noopOnInternalError(_err: Error): void {\n // intentional default — see BeaconTransportOptions.onInternalError docs.\n}\n"]}