@loggerjs/browser 0.5.6 → 0.6.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.
package/README.md CHANGED
@@ -1,3 +1,7 @@
1
+ <p align="center">
2
+ <a href="https://jskits.github.io/loggerjs/"><img src="https://raw.githubusercontent.com/jskits/loggerjs/main/docs/public/logo.png" alt="LoggerJS logo" width="64" height="64" /></a>
3
+ </p>
4
+
1
5
  # @loggerjs/browser
2
6
 
3
7
  > Browser transports, offline persistence, and 19 automatic integrations — re-exports all of `@loggerjs/core`.
@@ -62,7 +66,7 @@ Logs batch over HTTP, queue while offline, replay with backoff when the network
62
66
  | `browserWebSocketTransport` | a WebSocket (codec-encoded batches, queues while closed) |
63
67
  | `browserServiceWorkerTransport` | a service worker for centralized delivery; `ready()` waits for `serviceWorker.ready` with `target: "ready"` |
64
68
  | `browserBroadcastChannelTransport` | other tabs via `BroadcastChannel` |
65
- | `offlineFirstTransport` | a local store first, then forwards online |
69
+ | `offlineFirstTransport` | your remote transport, queueing to IndexedDB while offline or when delivery fails and replaying later |
66
70
 
67
71
  ### Offline queues & export
68
72
 
@@ -71,8 +75,7 @@ Logs batch over HTTP, queue while offline, replay with backoff when the network
71
75
  - `exportLogsToZip()` + `downloadBlob()` — export a persisted store as a zip containing `logs.ndjson` or `logs.json`, `manifest.json`, optional per-session files, and optional `recent.ndjson` or `recent.json`.
72
76
  - Call the `indexedDbTransport()` instance's `stats()` to read flush, prune, query, drop, and buffer-depth counters.
73
77
 
74
- For a support-log store that survives reloads and exports per page session, the
75
- short setup is:
78
+ For a support-log store that survives reloads and exports per page session, the short setup is:
76
79
 
77
80
  ```ts
78
81
  import { createLogger } from "@loggerjs/core";
@@ -102,17 +105,9 @@ export async function downloadSupportLogs() {
102
105
  }
103
106
  ```
104
107
 
105
- `indexedDbTransport()` assigns a page-session id by default for browser support
106
- stores. Use `query({ sessionId })`, `sessions()`, and
107
- `exportLogsToZip(..., { groupBySession: true, includeRecent: true })` to build
108
- support bundles around reload-sized sessions. Enable `localStorageSpill` only
109
- as a bounded last-chance buffer for the still-unconfirmed memory tail during
110
- ordinary reloads or tab closes; IndexedDB remains the queryable source of truth.
108
+ `indexedDbTransport()` assigns a page-session id by default for browser support stores. Use `query({ sessionId })`, `sessions()`, and `exportLogsToZip(..., { groupBySession: true, includeRecent: true })` to build support bundles around reload-sized sessions. Enable `localStorageSpill` only as a bounded last-chance buffer for the still-unconfirmed memory tail during ordinary reloads or tab closes; IndexedDB remains the queryable source of truth.
111
109
 
112
- Browser delivery has runtime loss windows: tab close can cut off async work,
113
- `sendBeacon` is size- and user-agent-limited, service worker delivery depends on
114
- activation and lifetime, and IndexedDB persistence depends on quota and browser
115
- storage policy. Use an IndexedDB offline queue when reload survival matters.
110
+ Browser delivery has runtime loss windows: tab close can cut off async work, `sendBeacon` is size- and user-agent-limited, service worker delivery depends on activation and lifetime, and IndexedDB persistence depends on quota and browser storage policy. Use an IndexedDB offline queue when reload survival matters.
116
111
 
117
112
  ## Integrations (19)
118
113
 
@@ -134,7 +129,9 @@ const frameworkErrors = captureFrameworkErrorsIntegration({ framework: "react" }
134
129
 
135
130
  ## Subpath exports
136
131
 
137
- Transports — `transport-http` · `transport-indexeddb` · `transport-websocket` · `transport-service-worker` · `transport-broadcast-channel` · `offline-first-transport` · `offline-indexeddb` · `export-zip` · `payload-transforms`
132
+ Transports — `transport-http` · `transport-indexeddb` · `transport-websocket` · `transport-service-worker` · `transport-broadcast-channel` · `offline-first-transport`
133
+
134
+ Utilities — `offline-indexeddb` · `export-zip` · `payload-transforms`
138
135
 
139
136
  Integrations — `integration-console` · `integration-errors` · `integration-framework-errors` · `integration-reporting` · `integration-fetch` · `integration-xhr` · `integration-websocket` · `integration-web-vitals` · `integration-performance` · `integration-router` · `integration-framework-routers` · `integration-page-lifecycle` · `integration-user-actions` · `integration-service-worker` · `integration-runtime-host` · `integration-context`
140
137
 
@@ -77,7 +77,7 @@ function browserHttpTransport(options) {
77
77
  let timer;
78
78
  let activeFlush;
79
79
  let flushingBeacon = false;
80
- let replayingOffline = false;
80
+ let replayPromise;
81
81
  let lastContext;
82
82
  const headers = (payloadHeaders, contentType = codec.contentType) => ({
83
83
  "content-type": contentType,
@@ -193,17 +193,18 @@ function browserHttpTransport(options) {
193
193
  (0, _loggerjs_core.incrementLoggerMetaCounter)("transport.offline.queued");
194
194
  return true;
195
195
  };
196
+ const isOnline = () => typeof navigator === "undefined" || navigator.onLine !== false;
196
197
  const sendFetchBatch = async (batch) => {
197
- if (batch.length === 0) return;
198
198
  const transformed = await encodeTransformedPayload(batch);
199
- if (offlineQueue && typeof navigator !== "undefined" && navigator.onLine === false) {
199
+ if (offlineQueue && !isOnline()) {
200
200
  await enqueueOfflinePayload(transformed.payload, transformed.headers, transformed.contentType);
201
- return;
201
+ return "queued";
202
202
  }
203
203
  try {
204
204
  await sendPayload(createOfflineEntry(transformed.payload, transformed.headers, transformed.contentType));
205
+ return "sent";
205
206
  } catch (error) {
206
- if (await enqueueOfflinePayload(transformed.payload, transformed.headers, transformed.contentType)) return;
207
+ if (await enqueueOfflinePayload(transformed.payload, transformed.headers, transformed.contentType)) return "queued";
207
208
  throw error;
208
209
  }
209
210
  };
@@ -225,13 +226,23 @@ function browserHttpTransport(options) {
225
226
  await sleep(replayRetryDelay(attempt));
226
227
  }
227
228
  };
228
- const replayOfflineQueue = async () => {
229
- if (!offlineQueue || replayingOffline) return;
230
- replayingOffline = true;
229
+ const replayOfflineQueue = () => {
230
+ if (!offlineQueue) return Promise.resolve();
231
+ replayPromise ??= (async () => {
232
+ try {
233
+ await offlineQueue.replay(sendOfflineEntryWithRetry);
234
+ } finally {
235
+ replayPromise = void 0;
236
+ }
237
+ })();
238
+ return replayPromise;
239
+ };
240
+ const replayOfflineQueueSafely = async (operation) => {
241
+ if (!offlineQueue || !isOnline()) return;
231
242
  try {
232
- await offlineQueue.replay(sendOfflineEntryWithRetry);
233
- } finally {
234
- replayingOffline = false;
243
+ await replayOfflineQueue();
244
+ } catch (error) {
245
+ reportInternalError(error, operation);
235
246
  }
236
247
  };
237
248
  const flushBeaconQueue = () => {
@@ -258,7 +269,7 @@ function browserHttpTransport(options) {
258
269
  }
259
270
  return activeFlush;
260
271
  }
261
- if (queue.length === 0) return Promise.resolve();
272
+ if (queue.length === 0) return preferBeacon ? Promise.resolve() : replayOfflineQueueSafely("replay");
262
273
  clearTimer();
263
274
  let resolve;
264
275
  let reject;
@@ -270,15 +281,19 @@ function browserHttpTransport(options) {
270
281
  (async () => {
271
282
  try {
272
283
  if (preferBeacon) flushBeaconQueue();
284
+ let sent = false;
285
+ let queued = false;
273
286
  while (queue.length > 0) {
274
287
  const batch = queue.splice(0, maxBatchSize);
275
288
  try {
276
- await sendFetchBatch(batch);
289
+ if (await sendFetchBatch(batch) === "sent") sent = true;
290
+ else queued = true;
277
291
  } catch (error) {
278
292
  queue.unshift(...batch);
279
293
  throw error;
280
294
  }
281
295
  }
296
+ if (!preferBeacon && sent && !queued) await replayOfflineQueueSafely("replay");
282
297
  resolve();
283
298
  } catch (error) {
284
299
  reject(error);
@@ -309,7 +324,10 @@ function browserHttpTransport(options) {
309
324
  globalThis.addEventListener?.("pagehide", onPageHide);
310
325
  globalThis.addEventListener?.("visibilitychange", onVisibilityChange);
311
326
  }
312
- if (offlineQueue) globalThis.addEventListener?.("online", onOnline);
327
+ if (offlineQueue) {
328
+ globalThis.addEventListener?.("online", onOnline);
329
+ if (options.offlineReplayOnStart ?? true) setTimeout(() => void replayOfflineQueueSafely("startup-replay"), 0);
330
+ }
313
331
  return {
314
332
  name: options.name ?? "browser-http",
315
333
  minLevel: options.minLevel,
@@ -1 +1 @@
1
- {"version":3,"file":"http-transport.cjs","names":[],"sources":["../src/http-transport.ts"],"sourcesContent":["import {\n applyPayloadTransforms,\n incrementLoggerMetaCounter,\n safeJsonCodec,\n toLevelValue,\n type Codec,\n type EncodedPayload,\n type LogEvent,\n type LoggerLevel,\n type PayloadTransform,\n type Transport,\n type TransportContext,\n} from \"@loggerjs/core\";\n\nexport type BrowserHttpDropPolicy = \"drop-oldest\" | \"drop-newest\";\n\nconst DEFAULT_BEACON_MAX_BYTES = 60 * 1024;\n\nconst sleep = (delayMs: number) =>\n delayMs <= 0 ? Promise.resolve() : new Promise<void>((resolve) => setTimeout(resolve, delayMs));\n\nexport interface BrowserHttpOfflineEntry {\n id: string;\n url: string;\n method: \"POST\" | \"PUT\";\n headers: Record<string, string>;\n body: string | Uint8Array;\n credentials?: RequestCredentials;\n keepalive: boolean;\n createdAt: number;\n}\n\nexport interface BrowserHttpOfflineQueue {\n enqueue: (entry: BrowserHttpOfflineEntry) => void | Promise<void>;\n replay: (send: (entry: BrowserHttpOfflineEntry) => Promise<void>) => void | Promise<void>;\n}\n\nexport interface MemoryBrowserHttpOfflineQueueOptions {\n maxEntries?: number;\n dropPolicy?: BrowserHttpDropPolicy;\n onDrop?: (entry: BrowserHttpOfflineEntry, reason: string) => void;\n}\n\nexport interface BrowserHttpTransportOptions {\n url: string;\n name?: string;\n method?: \"POST\" | \"PUT\";\n headers?: Record<string, string>;\n credentials?: RequestCredentials;\n keepalive?: boolean;\n codec?: Codec<string | Uint8Array>;\n beaconCodec?: Codec<string | Uint8Array>;\n minLevel?: LoggerLevel;\n maxBatchSize?: number;\n flushIntervalMs?: number;\n maxQueueSize?: number;\n dropPolicy?: BrowserHttpDropPolicy;\n useBeaconOnPageHide?: boolean;\n beaconMaxBytes?: number;\n offlineQueue?: BrowserHttpOfflineQueue;\n offlineReplayMaxRetries?: number;\n offlineReplayBaseDelayMs?: number;\n offlineReplayMaxDelayMs?: number;\n random?: () => number;\n fetchFn?: typeof fetch;\n transformPayload?: PayloadTransform | readonly PayloadTransform[];\n onDrop?: (event: LogEvent, reason: string) => void;\n}\n\nexport function memoryBrowserHttpOfflineQueue(\n options: MemoryBrowserHttpOfflineQueueOptions = {},\n): BrowserHttpOfflineQueue & { size: () => number } {\n const entries: BrowserHttpOfflineEntry[] = [];\n const maxEntries = options.maxEntries ?? 1000;\n const dropPolicy = options.dropPolicy ?? \"drop-oldest\";\n\n const drop = (entry: BrowserHttpOfflineEntry, reason: string) => {\n incrementLoggerMetaCounter(\"transport.offline.dropped\");\n incrementLoggerMetaCounter(`transport.offline.dropped.${reason}`);\n options.onDrop?.(entry, reason);\n };\n\n return {\n enqueue(entry) {\n if (entries.length >= maxEntries) {\n if (dropPolicy === \"drop-newest\") {\n drop(entry, \"queue-full\");\n return;\n }\n const dropped = entries.shift();\n if (dropped) drop(dropped, \"queue-full\");\n }\n entries.push(entry);\n },\n async replay(send) {\n while (entries.length > 0) {\n const entry = entries[0];\n if (!entry) return;\n // oxlint-disable-next-line no-await-in-loop -- Replay must remove entries only after each send succeeds.\n await send(entry);\n entries.shift();\n }\n },\n size() {\n return entries.length;\n },\n };\n}\n\nfunction payloadToBody(payload: EncodedPayload): BodyInit {\n if (typeof payload === \"string\") return payload;\n return Uint8Array.from(payload);\n}\n\nfunction payloadByteLength(payload: EncodedPayload): number {\n if (typeof payload !== \"string\") return payload.byteLength;\n if (typeof TextEncoder !== \"undefined\") return new TextEncoder().encode(payload).byteLength;\n return new Blob([payload]).size;\n}\n\nfunction payloadToBeaconBody(payload: EncodedPayload, contentType: string): BodyInit {\n return new Blob([typeof payload === \"string\" ? payload : Uint8Array.from(payload)], {\n type: contentType,\n });\n}\n\ninterface BeaconChunk {\n events: LogEvent[];\n payload: string | Uint8Array;\n}\n\nfunction remainingEvents(chunks: BeaconChunk[], startIndex: number): LogEvent[] {\n const events: LogEvent[] = [];\n for (let index = startIndex; index < chunks.length; index++) {\n const chunk = chunks[index];\n if (chunk) events.push(...chunk.events);\n }\n return events;\n}\n\nexport function browserHttpTransport(options: BrowserHttpTransportOptions): Transport {\n const codec = options.codec ?? safeJsonCodec();\n const beaconCodec = options.beaconCodec ?? codec;\n const queue: LogEvent[] = [];\n const maxBatchSize = options.maxBatchSize ?? 50;\n if (!Number.isSafeInteger(maxBatchSize) || maxBatchSize <= 0) {\n throw new RangeError(\"maxBatchSize must be a positive safe integer\");\n }\n const flushIntervalMs = options.flushIntervalMs ?? 2000;\n const maxQueueSize = options.maxQueueSize ?? 1000;\n const dropPolicy = options.dropPolicy ?? \"drop-oldest\";\n const beaconMaxBytes = options.beaconMaxBytes ?? DEFAULT_BEACON_MAX_BYTES;\n const offlineQueue = options.offlineQueue;\n const offlineReplayMaxRetries = options.offlineReplayMaxRetries ?? 3;\n const offlineReplayBaseDelayMs = options.offlineReplayBaseDelayMs ?? 250;\n const offlineReplayMaxDelayMs = options.offlineReplayMaxDelayMs ?? 5000;\n const random = options.random ?? Math.random;\n const fetchFn = options.fetchFn ?? globalThis.fetch?.bind(globalThis);\n let offlineEntrySeq = 0;\n let timer: ReturnType<typeof setTimeout> | undefined;\n let activeFlush: Promise<void> | undefined;\n let flushingBeacon = false;\n let replayingOffline = false;\n let lastContext: TransportContext | undefined;\n\n const headers = (payloadHeaders?: Record<string, string>, contentType = codec.contentType) => ({\n \"content-type\": contentType,\n ...payloadHeaders,\n ...options.headers,\n });\n\n const clearTimer = () => {\n if (timer) clearTimeout(timer);\n timer = undefined;\n };\n\n const reportDrop = (event: LogEvent, reason: string) => {\n incrementLoggerMetaCounter(\"transport.dropped\");\n incrementLoggerMetaCounter(`transport.dropped.${reason}`);\n options.onDrop?.(event, reason);\n };\n\n const reportInternalError = (error: unknown, operation: string) => {\n lastContext?.reportInternalError(error, {\n phase: \"transport\",\n transport: options.name ?? \"browser-http\",\n operation,\n });\n };\n\n const createBeaconChunks = (batch: LogEvent[]): BeaconChunk[] => {\n const chunks: BeaconChunk[] = [];\n const oversized: LogEvent[] = [];\n let currentEvents: LogEvent[] = [];\n let currentPayload: string | Uint8Array | undefined;\n\n for (const event of batch) {\n if (currentEvents.length === maxBatchSize && currentPayload !== undefined) {\n chunks.push({ events: currentEvents, payload: currentPayload });\n currentEvents = [];\n currentPayload = undefined;\n }\n const candidateEvents = [...currentEvents, event];\n const candidatePayload = beaconCodec.encode(candidateEvents);\n if (payloadByteLength(candidatePayload) <= beaconMaxBytes) {\n currentEvents = candidateEvents;\n currentPayload = candidatePayload;\n continue;\n }\n\n if (currentEvents.length > 0 && currentPayload !== undefined) {\n chunks.push({ events: currentEvents, payload: currentPayload });\n }\n\n const singlePayload = beaconCodec.encode([event]);\n if (payloadByteLength(singlePayload) <= beaconMaxBytes) {\n currentEvents = [event];\n currentPayload = singlePayload;\n } else {\n currentEvents = [];\n currentPayload = undefined;\n oversized.push(event);\n }\n }\n\n if (currentEvents.length > 0 && currentPayload !== undefined) {\n chunks.push({ events: currentEvents, payload: currentPayload });\n }\n\n for (const event of oversized) reportDrop(event, \"beacon-too-large\");\n return chunks;\n };\n\n const sendBeaconBatch = (\n batch: LogEvent[],\n ): { remaining: LogEvent[]; failure?: { error: unknown } } => {\n if (typeof navigator === \"undefined\" || !navigator.sendBeacon) {\n return { remaining: batch };\n }\n\n const chunks = createBeaconChunks(batch);\n for (let index = 0; index < chunks.length; index++) {\n const chunk = chunks[index];\n if (!chunk) continue;\n try {\n const ok = navigator.sendBeacon(\n options.url,\n payloadToBeaconBody(chunk.payload, beaconCodec.contentType),\n );\n if (!ok) return { remaining: remainingEvents(chunks, index) };\n } catch (error) {\n return { remaining: remainingEvents(chunks, index), failure: { error } };\n }\n }\n\n return { remaining: [] };\n };\n\n const sendPayload = async (entry: BrowserHttpOfflineEntry) => {\n if (!fetchFn) throw new Error(\"fetch is not available for browserHttpTransport\");\n const response = await fetchFn(entry.url, {\n method: entry.method,\n headers: entry.headers,\n body: payloadToBody(entry.body),\n credentials: entry.credentials,\n keepalive: entry.keepalive,\n });\n if (!response.ok) throw new Error(`browserHttpTransport failed with status ${response.status}`);\n };\n\n const encodeTransformedPayload = async (batch: LogEvent[]) => {\n const encoded = codec.encode(batch);\n return applyPayloadTransforms(\n encoded,\n {\n contentType: codec.contentType,\n events: batch,\n transport: options.name ?? \"browser-http\",\n },\n options.transformPayload,\n );\n };\n\n const createOfflineEntry = (\n payload: string | Uint8Array,\n payloadHeaders?: Record<string, string>,\n contentType?: string,\n ): BrowserHttpOfflineEntry => ({\n id: `${Date.now().toString(36)}-${(offlineEntrySeq++).toString(36)}`,\n url: options.url,\n method: options.method ?? \"POST\",\n headers: headers(payloadHeaders, contentType),\n body: payload,\n credentials: options.credentials,\n keepalive: options.keepalive ?? true,\n createdAt: Date.now(),\n });\n\n const enqueueOfflinePayload = async (\n payload: string | Uint8Array,\n payloadHeaders?: Record<string, string>,\n contentType?: string,\n ) => {\n if (!offlineQueue) return false;\n await offlineQueue.enqueue(createOfflineEntry(payload, payloadHeaders, contentType));\n incrementLoggerMetaCounter(\"transport.offline.queued\");\n return true;\n };\n\n const sendFetchBatch = async (batch: LogEvent[]) => {\n if (batch.length === 0) return;\n const transformed = await encodeTransformedPayload(batch);\n if (offlineQueue && typeof navigator !== \"undefined\" && navigator.onLine === false) {\n await enqueueOfflinePayload(\n transformed.payload,\n transformed.headers,\n transformed.contentType,\n );\n return;\n }\n try {\n await sendPayload(\n createOfflineEntry(transformed.payload, transformed.headers, transformed.contentType),\n );\n } catch (error) {\n if (\n await enqueueOfflinePayload(\n transformed.payload,\n transformed.headers,\n transformed.contentType,\n )\n ) {\n return;\n }\n throw error;\n }\n };\n\n const replayRetryDelay = (attempt: number): number => {\n const cap = Math.min(offlineReplayMaxDelayMs, offlineReplayBaseDelayMs * 2 ** attempt);\n return cap <= 0 ? 0 : random() * cap;\n };\n\n const sendOfflineEntryWithRetry = async (entry: BrowserHttpOfflineEntry) => {\n for (let attempt = 0; ; attempt++) {\n try {\n // oxlint-disable-next-line no-await-in-loop -- Retry attempts must run sequentially.\n await sendPayload(entry);\n incrementLoggerMetaCounter(\"transport.offline.replayed\");\n return;\n } catch (error) {\n if (attempt >= offlineReplayMaxRetries) {\n incrementLoggerMetaCounter(\"transport.offline.replay.failed\");\n throw error;\n }\n incrementLoggerMetaCounter(\"transport.offline.retry\");\n // oxlint-disable-next-line no-await-in-loop -- Backoff must complete before the next retry.\n await sleep(replayRetryDelay(attempt));\n }\n }\n };\n\n const replayOfflineQueue = async () => {\n if (!offlineQueue || replayingOffline) return;\n replayingOffline = true;\n try {\n await offlineQueue.replay(sendOfflineEntryWithRetry);\n } finally {\n replayingOffline = false;\n }\n };\n\n // Beacon submission is synchronous, even while an earlier Fetch is pending.\n // Only queued events are eligible; the active Fetch batch keeps its ownership.\n const flushBeaconQueue = () => {\n if (flushingBeacon || options.transformPayload || queue.length === 0) return;\n flushingBeacon = true;\n let pending = queue.splice(0, queue.length);\n try {\n const result = sendBeaconBatch(pending);\n pending = result.remaining;\n if (result.failure) throw result.failure.error;\n } finally {\n if (pending.length > 0) queue.unshift(...pending);\n flushingBeacon = false;\n }\n };\n\n const flush = (preferBeacon = false): Promise<void> => {\n if (activeFlush) {\n if (preferBeacon) {\n try {\n flushBeaconQueue();\n } catch (error) {\n // close() must still wait for the active delivery before reporting a Beacon error.\n return activeFlush.then(() => {\n throw error;\n });\n }\n }\n return activeFlush;\n }\n if (queue.length === 0) return Promise.resolve();\n clearTimer();\n\n // Publish the task before invoking user codecs/transforms, which may reenter log().\n let resolve!: () => void;\n let reject!: (error: unknown) => void;\n const task = new Promise<void>((onResolve, onReject) => {\n resolve = onResolve;\n reject = onReject;\n });\n activeFlush = task;\n void (async () => {\n try {\n if (preferBeacon) flushBeaconQueue();\n while (queue.length > 0) {\n const batch = queue.splice(0, maxBatchSize);\n try {\n // oxlint-disable-next-line no-await-in-loop -- Preserve batch order and stop on the first unhandled failure.\n await sendFetchBatch(batch);\n } catch (error) {\n queue.unshift(...batch);\n throw error;\n }\n }\n resolve();\n } catch (error) {\n reject(error);\n } finally {\n activeFlush = undefined;\n if (queue.length > 0) schedule();\n }\n })();\n return task;\n };\n\n const schedule = () => {\n if (activeFlush || timer || flushIntervalMs <= 0) return;\n timer = setTimeout(() => {\n timer = undefined;\n void flush(false).catch((error: unknown) => reportInternalError(error, \"flush\"));\n }, flushIntervalMs);\n };\n\n const onPageHide = () => {\n void flush(true).catch((error: unknown) => reportInternalError(error, \"pagehide-flush\"));\n };\n const onVisibilityChange = () => {\n if (typeof document !== \"undefined\" && document.visibilityState === \"hidden\") {\n void flush(true).catch((error: unknown) => reportInternalError(error, \"visibility-flush\"));\n }\n };\n const onOnline = () => {\n void replayOfflineQueue().catch((error: unknown) => reportInternalError(error, \"replay\"));\n };\n\n if (options.useBeaconOnPageHide ?? true) {\n globalThis.addEventListener?.(\"pagehide\", onPageHide);\n globalThis.addEventListener?.(\"visibilitychange\", onVisibilityChange);\n }\n if (offlineQueue) globalThis.addEventListener?.(\"online\", onOnline);\n\n return {\n name: options.name ?? \"browser-http\",\n minLevel: options.minLevel,\n log(event, context) {\n if (options.minLevel !== undefined && event.level < toLevelValue(options.minLevel)) return;\n lastContext = context;\n if (queue.length >= maxQueueSize) {\n if (dropPolicy === \"drop-newest\") {\n reportDrop(event, \"queue-full\");\n return;\n }\n const dropped = queue.shift();\n if (dropped) reportDrop(dropped, \"queue-full\");\n }\n queue.push(event);\n if (activeFlush) return;\n if (queue.length >= maxBatchSize) {\n void flush(false).catch((error: unknown) => reportInternalError(error, \"flush\"));\n } else schedule();\n },\n flush() {\n return flush(false);\n },\n close() {\n globalThis.removeEventListener?.(\"pagehide\", onPageHide);\n globalThis.removeEventListener?.(\"visibilitychange\", onVisibilityChange);\n globalThis.removeEventListener?.(\"online\", onOnline);\n return flush(true);\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"http-transport.cjs","names":[],"sources":["../src/http-transport.ts"],"sourcesContent":["import {\n applyPayloadTransforms,\n incrementLoggerMetaCounter,\n safeJsonCodec,\n toLevelValue,\n type Codec,\n type EncodedPayload,\n type LogEvent,\n type LoggerLevel,\n type PayloadTransform,\n type Transport,\n type TransportContext,\n} from \"@loggerjs/core\";\n\nexport type BrowserHttpDropPolicy = \"drop-oldest\" | \"drop-newest\";\n\nconst DEFAULT_BEACON_MAX_BYTES = 60 * 1024;\n\nconst sleep = (delayMs: number) =>\n delayMs <= 0 ? Promise.resolve() : new Promise<void>((resolve) => setTimeout(resolve, delayMs));\n\nexport interface BrowserHttpOfflineEntry {\n id: string;\n url: string;\n method: \"POST\" | \"PUT\";\n headers: Record<string, string>;\n body: string | Uint8Array;\n credentials?: RequestCredentials;\n keepalive: boolean;\n createdAt: number;\n}\n\nexport interface BrowserHttpOfflineQueue {\n enqueue: (entry: BrowserHttpOfflineEntry) => void | Promise<void>;\n replay: (send: (entry: BrowserHttpOfflineEntry) => Promise<void>) => void | Promise<void>;\n}\n\nexport interface MemoryBrowserHttpOfflineQueueOptions {\n maxEntries?: number;\n dropPolicy?: BrowserHttpDropPolicy;\n onDrop?: (entry: BrowserHttpOfflineEntry, reason: string) => void;\n}\n\nexport interface BrowserHttpTransportOptions {\n url: string;\n name?: string;\n method?: \"POST\" | \"PUT\";\n headers?: Record<string, string>;\n credentials?: RequestCredentials;\n keepalive?: boolean;\n codec?: Codec<string | Uint8Array>;\n beaconCodec?: Codec<string | Uint8Array>;\n minLevel?: LoggerLevel;\n maxBatchSize?: number;\n flushIntervalMs?: number;\n maxQueueSize?: number;\n dropPolicy?: BrowserHttpDropPolicy;\n useBeaconOnPageHide?: boolean;\n beaconMaxBytes?: number;\n offlineQueue?: BrowserHttpOfflineQueue;\n offlineReplayMaxRetries?: number;\n offlineReplayBaseDelayMs?: number;\n offlineReplayMaxDelayMs?: number;\n offlineReplayOnStart?: boolean;\n random?: () => number;\n fetchFn?: typeof fetch;\n transformPayload?: PayloadTransform | readonly PayloadTransform[];\n onDrop?: (event: LogEvent, reason: string) => void;\n}\n\nexport function memoryBrowserHttpOfflineQueue(\n options: MemoryBrowserHttpOfflineQueueOptions = {},\n): BrowserHttpOfflineQueue & { size: () => number } {\n const entries: BrowserHttpOfflineEntry[] = [];\n const maxEntries = options.maxEntries ?? 1000;\n const dropPolicy = options.dropPolicy ?? \"drop-oldest\";\n\n const drop = (entry: BrowserHttpOfflineEntry, reason: string) => {\n incrementLoggerMetaCounter(\"transport.offline.dropped\");\n incrementLoggerMetaCounter(`transport.offline.dropped.${reason}`);\n options.onDrop?.(entry, reason);\n };\n\n return {\n enqueue(entry) {\n if (entries.length >= maxEntries) {\n if (dropPolicy === \"drop-newest\") {\n drop(entry, \"queue-full\");\n return;\n }\n const dropped = entries.shift();\n if (dropped) drop(dropped, \"queue-full\");\n }\n entries.push(entry);\n },\n async replay(send) {\n while (entries.length > 0) {\n const entry = entries[0];\n if (!entry) return;\n // oxlint-disable-next-line no-await-in-loop -- Replay must remove entries only after each send succeeds.\n await send(entry);\n entries.shift();\n }\n },\n size() {\n return entries.length;\n },\n };\n}\n\nfunction payloadToBody(payload: EncodedPayload): BodyInit {\n if (typeof payload === \"string\") return payload;\n return Uint8Array.from(payload);\n}\n\nfunction payloadByteLength(payload: EncodedPayload): number {\n if (typeof payload !== \"string\") return payload.byteLength;\n if (typeof TextEncoder !== \"undefined\") return new TextEncoder().encode(payload).byteLength;\n return new Blob([payload]).size;\n}\n\nfunction payloadToBeaconBody(payload: EncodedPayload, contentType: string): BodyInit {\n return new Blob([typeof payload === \"string\" ? payload : Uint8Array.from(payload)], {\n type: contentType,\n });\n}\n\ninterface BeaconChunk {\n events: LogEvent[];\n payload: string | Uint8Array;\n}\n\nfunction remainingEvents(chunks: BeaconChunk[], startIndex: number): LogEvent[] {\n const events: LogEvent[] = [];\n for (let index = startIndex; index < chunks.length; index++) {\n const chunk = chunks[index];\n if (chunk) events.push(...chunk.events);\n }\n return events;\n}\n\nexport function browserHttpTransport(options: BrowserHttpTransportOptions): Transport {\n const codec = options.codec ?? safeJsonCodec();\n const beaconCodec = options.beaconCodec ?? codec;\n const queue: LogEvent[] = [];\n const maxBatchSize = options.maxBatchSize ?? 50;\n if (!Number.isSafeInteger(maxBatchSize) || maxBatchSize <= 0) {\n throw new RangeError(\"maxBatchSize must be a positive safe integer\");\n }\n const flushIntervalMs = options.flushIntervalMs ?? 2000;\n const maxQueueSize = options.maxQueueSize ?? 1000;\n const dropPolicy = options.dropPolicy ?? \"drop-oldest\";\n const beaconMaxBytes = options.beaconMaxBytes ?? DEFAULT_BEACON_MAX_BYTES;\n const offlineQueue = options.offlineQueue;\n const offlineReplayMaxRetries = options.offlineReplayMaxRetries ?? 3;\n const offlineReplayBaseDelayMs = options.offlineReplayBaseDelayMs ?? 250;\n const offlineReplayMaxDelayMs = options.offlineReplayMaxDelayMs ?? 5000;\n const random = options.random ?? Math.random;\n const fetchFn = options.fetchFn ?? globalThis.fetch?.bind(globalThis);\n let offlineEntrySeq = 0;\n let timer: ReturnType<typeof setTimeout> | undefined;\n let activeFlush: Promise<void> | undefined;\n let flushingBeacon = false;\n let replayPromise: Promise<void> | undefined;\n let lastContext: TransportContext | undefined;\n\n const headers = (payloadHeaders?: Record<string, string>, contentType = codec.contentType) => ({\n \"content-type\": contentType,\n ...payloadHeaders,\n ...options.headers,\n });\n\n const clearTimer = () => {\n if (timer) clearTimeout(timer);\n timer = undefined;\n };\n\n const reportDrop = (event: LogEvent, reason: string) => {\n incrementLoggerMetaCounter(\"transport.dropped\");\n incrementLoggerMetaCounter(`transport.dropped.${reason}`);\n options.onDrop?.(event, reason);\n };\n\n const reportInternalError = (error: unknown, operation: string) => {\n lastContext?.reportInternalError(error, {\n phase: \"transport\",\n transport: options.name ?? \"browser-http\",\n operation,\n });\n };\n\n const createBeaconChunks = (batch: LogEvent[]): BeaconChunk[] => {\n const chunks: BeaconChunk[] = [];\n const oversized: LogEvent[] = [];\n let currentEvents: LogEvent[] = [];\n let currentPayload: string | Uint8Array | undefined;\n\n for (const event of batch) {\n if (currentEvents.length === maxBatchSize && currentPayload !== undefined) {\n chunks.push({ events: currentEvents, payload: currentPayload });\n currentEvents = [];\n currentPayload = undefined;\n }\n const candidateEvents = [...currentEvents, event];\n const candidatePayload = beaconCodec.encode(candidateEvents);\n if (payloadByteLength(candidatePayload) <= beaconMaxBytes) {\n currentEvents = candidateEvents;\n currentPayload = candidatePayload;\n continue;\n }\n\n if (currentEvents.length > 0 && currentPayload !== undefined) {\n chunks.push({ events: currentEvents, payload: currentPayload });\n }\n\n const singlePayload = beaconCodec.encode([event]);\n if (payloadByteLength(singlePayload) <= beaconMaxBytes) {\n currentEvents = [event];\n currentPayload = singlePayload;\n } else {\n currentEvents = [];\n currentPayload = undefined;\n oversized.push(event);\n }\n }\n\n if (currentEvents.length > 0 && currentPayload !== undefined) {\n chunks.push({ events: currentEvents, payload: currentPayload });\n }\n\n for (const event of oversized) reportDrop(event, \"beacon-too-large\");\n return chunks;\n };\n\n const sendBeaconBatch = (\n batch: LogEvent[],\n ): { remaining: LogEvent[]; failure?: { error: unknown } } => {\n if (typeof navigator === \"undefined\" || !navigator.sendBeacon) {\n return { remaining: batch };\n }\n\n const chunks = createBeaconChunks(batch);\n for (let index = 0; index < chunks.length; index++) {\n const chunk = chunks[index];\n if (!chunk) continue;\n try {\n const ok = navigator.sendBeacon(\n options.url,\n payloadToBeaconBody(chunk.payload, beaconCodec.contentType),\n );\n if (!ok) return { remaining: remainingEvents(chunks, index) };\n } catch (error) {\n return { remaining: remainingEvents(chunks, index), failure: { error } };\n }\n }\n\n return { remaining: [] };\n };\n\n const sendPayload = async (entry: BrowserHttpOfflineEntry) => {\n if (!fetchFn) throw new Error(\"fetch is not available for browserHttpTransport\");\n const response = await fetchFn(entry.url, {\n method: entry.method,\n headers: entry.headers,\n body: payloadToBody(entry.body),\n credentials: entry.credentials,\n keepalive: entry.keepalive,\n });\n if (!response.ok) throw new Error(`browserHttpTransport failed with status ${response.status}`);\n };\n\n const encodeTransformedPayload = async (batch: LogEvent[]) => {\n const encoded = codec.encode(batch);\n return applyPayloadTransforms(\n encoded,\n {\n contentType: codec.contentType,\n events: batch,\n transport: options.name ?? \"browser-http\",\n },\n options.transformPayload,\n );\n };\n\n const createOfflineEntry = (\n payload: string | Uint8Array,\n payloadHeaders?: Record<string, string>,\n contentType?: string,\n ): BrowserHttpOfflineEntry => ({\n id: `${Date.now().toString(36)}-${(offlineEntrySeq++).toString(36)}`,\n url: options.url,\n method: options.method ?? \"POST\",\n headers: headers(payloadHeaders, contentType),\n body: payload,\n credentials: options.credentials,\n keepalive: options.keepalive ?? true,\n createdAt: Date.now(),\n });\n\n const enqueueOfflinePayload = async (\n payload: string | Uint8Array,\n payloadHeaders?: Record<string, string>,\n contentType?: string,\n ) => {\n if (!offlineQueue) return false;\n await offlineQueue.enqueue(createOfflineEntry(payload, payloadHeaders, contentType));\n incrementLoggerMetaCounter(\"transport.offline.queued\");\n return true;\n };\n\n const isOnline = () => typeof navigator === \"undefined\" || navigator.onLine !== false;\n\n // Resolves to \"sent\" when the collector accepted the batch and \"queued\" when\n // the offline queue took it instead.\n const sendFetchBatch = async (batch: LogEvent[]): Promise<\"sent\" | \"queued\"> => {\n const transformed = await encodeTransformedPayload(batch);\n if (offlineQueue && !isOnline()) {\n await enqueueOfflinePayload(\n transformed.payload,\n transformed.headers,\n transformed.contentType,\n );\n return \"queued\";\n }\n try {\n await sendPayload(\n createOfflineEntry(transformed.payload, transformed.headers, transformed.contentType),\n );\n return \"sent\";\n } catch (error) {\n if (\n await enqueueOfflinePayload(\n transformed.payload,\n transformed.headers,\n transformed.contentType,\n )\n ) {\n return \"queued\";\n }\n throw error;\n }\n };\n\n const replayRetryDelay = (attempt: number): number => {\n const cap = Math.min(offlineReplayMaxDelayMs, offlineReplayBaseDelayMs * 2 ** attempt);\n return cap <= 0 ? 0 : random() * cap;\n };\n\n const sendOfflineEntryWithRetry = async (entry: BrowserHttpOfflineEntry) => {\n for (let attempt = 0; ; attempt++) {\n try {\n // oxlint-disable-next-line no-await-in-loop -- Retry attempts must run sequentially.\n await sendPayload(entry);\n incrementLoggerMetaCounter(\"transport.offline.replayed\");\n return;\n } catch (error) {\n if (attempt >= offlineReplayMaxRetries) {\n incrementLoggerMetaCounter(\"transport.offline.replay.failed\");\n throw error;\n }\n incrementLoggerMetaCounter(\"transport.offline.retry\");\n // oxlint-disable-next-line no-await-in-loop -- Backoff must complete before the next retry.\n await sleep(replayRetryDelay(attempt));\n }\n }\n };\n\n const replayOfflineQueue = (): Promise<void> => {\n if (!offlineQueue) return Promise.resolve();\n replayPromise ??= (async () => {\n try {\n await offlineQueue.replay(sendOfflineEntryWithRetry);\n } finally {\n replayPromise = undefined;\n }\n })();\n return replayPromise;\n };\n\n // Stored entries stay in the offline queue when replay fails, so a failed\n // replay is reported instead of failing the flush that triggered it.\n const replayOfflineQueueSafely = async (operation: string) => {\n if (!offlineQueue || !isOnline()) return;\n try {\n await replayOfflineQueue();\n } catch (error) {\n reportInternalError(error, operation);\n }\n };\n\n // Beacon submission is synchronous, even while an earlier Fetch is pending.\n // Only queued events are eligible; the active Fetch batch keeps its ownership.\n const flushBeaconQueue = () => {\n if (flushingBeacon || options.transformPayload || queue.length === 0) return;\n flushingBeacon = true;\n let pending = queue.splice(0, queue.length);\n try {\n const result = sendBeaconBatch(pending);\n pending = result.remaining;\n if (result.failure) throw result.failure.error;\n } finally {\n if (pending.length > 0) queue.unshift(...pending);\n flushingBeacon = false;\n }\n };\n\n const flush = (preferBeacon = false): Promise<void> => {\n if (activeFlush) {\n if (preferBeacon) {\n try {\n flushBeaconQueue();\n } catch (error) {\n // close() must still wait for the active delivery before reporting a Beacon error.\n return activeFlush.then(() => {\n throw error;\n });\n }\n }\n return activeFlush;\n }\n if (queue.length === 0) {\n // An explicit flush with nothing live still retries stored payloads, so\n // entries queued during a server outage do not wait for an `online` event.\n return preferBeacon ? Promise.resolve() : replayOfflineQueueSafely(\"replay\");\n }\n clearTimer();\n\n // Publish the task before invoking user codecs/transforms, which may reenter log().\n let resolve!: () => void;\n let reject!: (error: unknown) => void;\n const task = new Promise<void>((onResolve, onReject) => {\n resolve = onResolve;\n reject = onReject;\n });\n activeFlush = task;\n void (async () => {\n try {\n if (preferBeacon) flushBeaconQueue();\n let sent = false;\n let queued = false;\n while (queue.length > 0) {\n const batch = queue.splice(0, maxBatchSize);\n try {\n // oxlint-disable-next-line no-await-in-loop -- Preserve batch order and stop on the first unhandled failure.\n const outcome = await sendFetchBatch(batch);\n if (outcome === \"sent\") sent = true;\n else queued = true;\n } catch (error) {\n queue.unshift(...batch);\n throw error;\n }\n }\n // A live batch reached the collector and none fell back to the offline\n // queue, so the collector is reachable again: replay stored payloads.\n if (!preferBeacon && sent && !queued) await replayOfflineQueueSafely(\"replay\");\n resolve();\n } catch (error) {\n reject(error);\n } finally {\n activeFlush = undefined;\n if (queue.length > 0) schedule();\n }\n })();\n return task;\n };\n\n const schedule = () => {\n if (activeFlush || timer || flushIntervalMs <= 0) return;\n timer = setTimeout(() => {\n timer = undefined;\n void flush(false).catch((error: unknown) => reportInternalError(error, \"flush\"));\n }, flushIntervalMs);\n };\n\n const onPageHide = () => {\n void flush(true).catch((error: unknown) => reportInternalError(error, \"pagehide-flush\"));\n };\n const onVisibilityChange = () => {\n if (typeof document !== \"undefined\" && document.visibilityState === \"hidden\") {\n void flush(true).catch((error: unknown) => reportInternalError(error, \"visibility-flush\"));\n }\n };\n const onOnline = () => {\n void replayOfflineQueue().catch((error: unknown) => reportInternalError(error, \"replay\"));\n };\n\n if (options.useBeaconOnPageHide ?? true) {\n globalThis.addEventListener?.(\"pagehide\", onPageHide);\n globalThis.addEventListener?.(\"visibilitychange\", onVisibilityChange);\n }\n if (offlineQueue) {\n globalThis.addEventListener?.(\"online\", onOnline);\n // Payloads persisted by an earlier page load (for example in IndexedDB)\n // would otherwise wait for an `online` event that may never fire.\n if (options.offlineReplayOnStart ?? true) {\n setTimeout(() => void replayOfflineQueueSafely(\"startup-replay\"), 0);\n }\n }\n\n return {\n name: options.name ?? \"browser-http\",\n minLevel: options.minLevel,\n log(event, context) {\n if (options.minLevel !== undefined && event.level < toLevelValue(options.minLevel)) return;\n lastContext = context;\n if (queue.length >= maxQueueSize) {\n if (dropPolicy === \"drop-newest\") {\n reportDrop(event, \"queue-full\");\n return;\n }\n const dropped = queue.shift();\n if (dropped) reportDrop(dropped, \"queue-full\");\n }\n queue.push(event);\n if (activeFlush) return;\n if (queue.length >= maxBatchSize) {\n void flush(false).catch((error: unknown) => reportInternalError(error, \"flush\"));\n } else schedule();\n },\n flush() {\n return flush(false);\n },\n close() {\n globalThis.removeEventListener?.(\"pagehide\", onPageHide);\n globalThis.removeEventListener?.(\"visibilitychange\", onVisibilityChange);\n globalThis.removeEventListener?.(\"online\", onOnline);\n return flush(true);\n },\n };\n}\n"],"mappings":";;;AAgBA,MAAM,2BAA2B,KAAK;AAEtC,MAAM,SAAS,YACb,WAAW,IAAI,QAAQ,QAAQ,IAAI,IAAI,SAAe,YAAY,WAAW,SAAS,OAAO,CAAC;AAmDhG,SAAgB,8BACd,UAAgD,CAAC,GACC;CAClD,MAAM,UAAqC,CAAC;CAC5C,MAAM,aAAa,QAAQ,cAAc;CACzC,MAAM,aAAa,QAAQ,cAAc;CAEzC,MAAM,QAAQ,OAAgC,WAAmB;EAC/D,CAAA,GAAA,eAAA,2BAAA,CAA2B,2BAA2B;EACtD,CAAA,GAAA,eAAA,2BAAA,CAA2B,6BAA6B,QAAQ;EAChE,QAAQ,SAAS,OAAO,MAAM;CAChC;CAEA,OAAO;EACL,QAAQ,OAAO;GACb,IAAI,QAAQ,UAAU,YAAY;IAChC,IAAI,eAAe,eAAe;KAChC,KAAK,OAAO,YAAY;KACxB;IACF;IACA,MAAM,UAAU,QAAQ,MAAM;IAC9B,IAAI,SAAS,KAAK,SAAS,YAAY;GACzC;GACA,QAAQ,KAAK,KAAK;EACpB;EACA,MAAM,OAAO,MAAM;GACjB,OAAO,QAAQ,SAAS,GAAG;IACzB,MAAM,QAAQ,QAAQ;IACtB,IAAI,CAAC,OAAO;IAEZ,MAAM,KAAK,KAAK;IAChB,QAAQ,MAAM;GAChB;EACF;EACA,OAAO;GACL,OAAO,QAAQ;EACjB;CACF;AACF;AAEA,SAAS,cAAc,SAAmC;CACxD,IAAI,OAAO,YAAY,UAAU,OAAO;CACxC,OAAO,WAAW,KAAK,OAAO;AAChC;AAEA,SAAS,kBAAkB,SAAiC;CAC1D,IAAI,OAAO,YAAY,UAAU,OAAO,QAAQ;CAChD,IAAI,OAAO,gBAAgB,aAAa,OAAO,IAAI,YAAY,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC;CACjF,OAAO,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;AAC7B;AAEA,SAAS,oBAAoB,SAAyB,aAA+B;CACnF,OAAO,IAAI,KAAK,CAAC,OAAO,YAAY,WAAW,UAAU,WAAW,KAAK,OAAO,CAAC,GAAG,EAClF,MAAM,YACR,CAAC;AACH;AAOA,SAAS,gBAAgB,QAAuB,YAAgC;CAC9E,MAAM,SAAqB,CAAC;CAC5B,KAAK,IAAI,QAAQ,YAAY,QAAQ,OAAO,QAAQ,SAAS;EAC3D,MAAM,QAAQ,OAAO;EACrB,IAAI,OAAO,OAAO,KAAK,GAAG,MAAM,MAAM;CACxC;CACA,OAAO;AACT;AAEA,SAAgB,qBAAqB,SAAiD;CACpF,MAAM,QAAQ,QAAQ,UAAA,GAAA,eAAA,cAAA,CAAuB;CAC7C,MAAM,cAAc,QAAQ,eAAe;CAC3C,MAAM,QAAoB,CAAC;CAC3B,MAAM,eAAe,QAAQ,gBAAgB;CAC7C,IAAI,CAAC,OAAO,cAAc,YAAY,KAAK,gBAAgB,GACzD,MAAM,IAAI,WAAW,8CAA8C;CAErE,MAAM,kBAAkB,QAAQ,mBAAmB;CACnD,MAAM,eAAe,QAAQ,gBAAgB;CAC7C,MAAM,aAAa,QAAQ,cAAc;CACzC,MAAM,iBAAiB,QAAQ,kBAAkB;CACjD,MAAM,eAAe,QAAQ;CAC7B,MAAM,0BAA0B,QAAQ,2BAA2B;CACnE,MAAM,2BAA2B,QAAQ,4BAA4B;CACrE,MAAM,0BAA0B,QAAQ,2BAA2B;CACnE,MAAM,SAAS,QAAQ,UAAU,KAAK;CACtC,MAAM,UAAU,QAAQ,WAAW,WAAW,OAAO,KAAK,UAAU;CACpE,IAAI,kBAAkB;CACtB,IAAI;CACJ,IAAI;CACJ,IAAI,iBAAiB;CACrB,IAAI;CACJ,IAAI;CAEJ,MAAM,WAAW,gBAAyC,cAAc,MAAM,iBAAiB;EAC7F,gBAAgB;EAChB,GAAG;EACH,GAAG,QAAQ;CACb;CAEA,MAAM,mBAAmB;EACvB,IAAI,OAAO,aAAa,KAAK;EAC7B,QAAQ,KAAA;CACV;CAEA,MAAM,cAAc,OAAiB,WAAmB;EACtD,CAAA,GAAA,eAAA,2BAAA,CAA2B,mBAAmB;EAC9C,CAAA,GAAA,eAAA,2BAAA,CAA2B,qBAAqB,QAAQ;EACxD,QAAQ,SAAS,OAAO,MAAM;CAChC;CAEA,MAAM,uBAAuB,OAAgB,cAAsB;EACjE,aAAa,oBAAoB,OAAO;GACtC,OAAO;GACP,WAAW,QAAQ,QAAQ;GAC3B;EACF,CAAC;CACH;CAEA,MAAM,sBAAsB,UAAqC;EAC/D,MAAM,SAAwB,CAAC;EAC/B,MAAM,YAAwB,CAAC;EAC/B,IAAI,gBAA4B,CAAC;EACjC,IAAI;EAEJ,KAAK,MAAM,SAAS,OAAO;GACzB,IAAI,cAAc,WAAW,gBAAgB,mBAAmB,KAAA,GAAW;IACzE,OAAO,KAAK;KAAE,QAAQ;KAAe,SAAS;IAAe,CAAC;IAC9D,gBAAgB,CAAC;IACjB,iBAAiB,KAAA;GACnB;GACA,MAAM,kBAAkB,CAAC,GAAG,eAAe,KAAK;GAChD,MAAM,mBAAmB,YAAY,OAAO,eAAe;GAC3D,IAAI,kBAAkB,gBAAgB,KAAK,gBAAgB;IACzD,gBAAgB;IAChB,iBAAiB;IACjB;GACF;GAEA,IAAI,cAAc,SAAS,KAAK,mBAAmB,KAAA,GACjD,OAAO,KAAK;IAAE,QAAQ;IAAe,SAAS;GAAe,CAAC;GAGhE,MAAM,gBAAgB,YAAY,OAAO,CAAC,KAAK,CAAC;GAChD,IAAI,kBAAkB,aAAa,KAAK,gBAAgB;IACtD,gBAAgB,CAAC,KAAK;IACtB,iBAAiB;GACnB,OAAO;IACL,gBAAgB,CAAC;IACjB,iBAAiB,KAAA;IACjB,UAAU,KAAK,KAAK;GACtB;EACF;EAEA,IAAI,cAAc,SAAS,KAAK,mBAAmB,KAAA,GACjD,OAAO,KAAK;GAAE,QAAQ;GAAe,SAAS;EAAe,CAAC;EAGhE,KAAK,MAAM,SAAS,WAAW,WAAW,OAAO,kBAAkB;EACnE,OAAO;CACT;CAEA,MAAM,mBACJ,UAC4D;EAC5D,IAAI,OAAO,cAAc,eAAe,CAAC,UAAU,YACjD,OAAO,EAAE,WAAW,MAAM;EAG5B,MAAM,SAAS,mBAAmB,KAAK;EACvC,KAAK,IAAI,QAAQ,GAAG,QAAQ,OAAO,QAAQ,SAAS;GAClD,MAAM,QAAQ,OAAO;GACrB,IAAI,CAAC,OAAO;GACZ,IAAI;IAKF,IAAI,CAJO,UAAU,WACnB,QAAQ,KACR,oBAAoB,MAAM,SAAS,YAAY,WAAW,CAEtD,GAAG,OAAO,EAAE,WAAW,gBAAgB,QAAQ,KAAK,EAAE;GAC9D,SAAS,OAAO;IACd,OAAO;KAAE,WAAW,gBAAgB,QAAQ,KAAK;KAAG,SAAS,EAAE,MAAM;IAAE;GACzE;EACF;EAEA,OAAO,EAAE,WAAW,CAAC,EAAE;CACzB;CAEA,MAAM,cAAc,OAAO,UAAmC;EAC5D,IAAI,CAAC,SAAS,MAAM,IAAI,MAAM,iDAAiD;EAC/E,MAAM,WAAW,MAAM,QAAQ,MAAM,KAAK;GACxC,QAAQ,MAAM;GACd,SAAS,MAAM;GACf,MAAM,cAAc,MAAM,IAAI;GAC9B,aAAa,MAAM;GACnB,WAAW,MAAM;EACnB,CAAC;EACD,IAAI,CAAC,SAAS,IAAI,MAAM,IAAI,MAAM,2CAA2C,SAAS,QAAQ;CAChG;CAEA,MAAM,2BAA2B,OAAO,UAAsB;EAE5D,QAAA,GAAA,eAAA,uBAAA,CADgB,MAAM,OAAO,KAErB,GACN;GACE,aAAa,MAAM;GACnB,QAAQ;GACR,WAAW,QAAQ,QAAQ;EAC7B,GACA,QAAQ,gBACV;CACF;CAEA,MAAM,sBACJ,SACA,gBACA,iBAC6B;EAC7B,IAAI,GAAG,KAAK,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,IAAI,kBAAA,CAAmB,SAAS,EAAE;EACjE,KAAK,QAAQ;EACb,QAAQ,QAAQ,UAAU;EAC1B,SAAS,QAAQ,gBAAgB,WAAW;EAC5C,MAAM;EACN,aAAa,QAAQ;EACrB,WAAW,QAAQ,aAAa;EAChC,WAAW,KAAK,IAAI;CACtB;CAEA,MAAM,wBAAwB,OAC5B,SACA,gBACA,gBACG;EACH,IAAI,CAAC,cAAc,OAAO;EAC1B,MAAM,aAAa,QAAQ,mBAAmB,SAAS,gBAAgB,WAAW,CAAC;EACnF,CAAA,GAAA,eAAA,2BAAA,CAA2B,0BAA0B;EACrD,OAAO;CACT;CAEA,MAAM,iBAAiB,OAAO,cAAc,eAAe,UAAU,WAAW;CAIhF,MAAM,iBAAiB,OAAO,UAAkD;EAC9E,MAAM,cAAc,MAAM,yBAAyB,KAAK;EACxD,IAAI,gBAAgB,CAAC,SAAS,GAAG;GAC/B,MAAM,sBACJ,YAAY,SACZ,YAAY,SACZ,YAAY,WACd;GACA,OAAO;EACT;EACA,IAAI;GACF,MAAM,YACJ,mBAAmB,YAAY,SAAS,YAAY,SAAS,YAAY,WAAW,CACtF;GACA,OAAO;EACT,SAAS,OAAO;GACd,IACE,MAAM,sBACJ,YAAY,SACZ,YAAY,SACZ,YAAY,WACd,GAEA,OAAO;GAET,MAAM;EACR;CACF;CAEA,MAAM,oBAAoB,YAA4B;EACpD,MAAM,MAAM,KAAK,IAAI,yBAAyB,2BAA2B,KAAK,OAAO;EACrF,OAAO,OAAO,IAAI,IAAI,OAAO,IAAI;CACnC;CAEA,MAAM,4BAA4B,OAAO,UAAmC;EAC1E,KAAK,IAAI,UAAU,IAAK,WACtB,IAAI;GAEF,MAAM,YAAY,KAAK;GACvB,CAAA,GAAA,eAAA,2BAAA,CAA2B,4BAA4B;GACvD;EACF,SAAS,OAAO;GACd,IAAI,WAAW,yBAAyB;IACtC,CAAA,GAAA,eAAA,2BAAA,CAA2B,iCAAiC;IAC5D,MAAM;GACR;GACA,CAAA,GAAA,eAAA,2BAAA,CAA2B,yBAAyB;GAEpD,MAAM,MAAM,iBAAiB,OAAO,CAAC;EACvC;CAEJ;CAEA,MAAM,2BAA0C;EAC9C,IAAI,CAAC,cAAc,OAAO,QAAQ,QAAQ;EAC1C,mBAAmB,YAAY;GAC7B,IAAI;IACF,MAAM,aAAa,OAAO,yBAAyB;GACrD,UAAU;IACR,gBAAgB,KAAA;GAClB;EACF,EAAA,CAAG;EACH,OAAO;CACT;CAIA,MAAM,2BAA2B,OAAO,cAAsB;EAC5D,IAAI,CAAC,gBAAgB,CAAC,SAAS,GAAG;EAClC,IAAI;GACF,MAAM,mBAAmB;EAC3B,SAAS,OAAO;GACd,oBAAoB,OAAO,SAAS;EACtC;CACF;CAIA,MAAM,yBAAyB;EAC7B,IAAI,kBAAkB,QAAQ,oBAAoB,MAAM,WAAW,GAAG;EACtE,iBAAiB;EACjB,IAAI,UAAU,MAAM,OAAO,GAAG,MAAM,MAAM;EAC1C,IAAI;GACF,MAAM,SAAS,gBAAgB,OAAO;GACtC,UAAU,OAAO;GACjB,IAAI,OAAO,SAAS,MAAM,OAAO,QAAQ;EAC3C,UAAU;GACR,IAAI,QAAQ,SAAS,GAAG,MAAM,QAAQ,GAAG,OAAO;GAChD,iBAAiB;EACnB;CACF;CAEA,MAAM,SAAS,eAAe,UAAyB;EACrD,IAAI,aAAa;GACf,IAAI,cACF,IAAI;IACF,iBAAiB;GACnB,SAAS,OAAO;IAEd,OAAO,YAAY,WAAW;KAC5B,MAAM;IACR,CAAC;GACH;GAEF,OAAO;EACT;EACA,IAAI,MAAM,WAAW,GAGnB,OAAO,eAAe,QAAQ,QAAQ,IAAI,yBAAyB,QAAQ;EAE7E,WAAW;EAGX,IAAI;EACJ,IAAI;EACJ,MAAM,OAAO,IAAI,SAAe,WAAW,aAAa;GACtD,UAAU;GACV,SAAS;EACX,CAAC;EACD,cAAc;EACd,CAAM,YAAY;GAChB,IAAI;IACF,IAAI,cAAc,iBAAiB;IACnC,IAAI,OAAO;IACX,IAAI,SAAS;IACb,OAAO,MAAM,SAAS,GAAG;KACvB,MAAM,QAAQ,MAAM,OAAO,GAAG,YAAY;KAC1C,IAAI;MAGF,IAAI,MADkB,eAAe,KAAK,MAC1B,QAAQ,OAAO;WAC1B,SAAS;KAChB,SAAS,OAAO;MACd,MAAM,QAAQ,GAAG,KAAK;MACtB,MAAM;KACR;IACF;IAGA,IAAI,CAAC,gBAAgB,QAAQ,CAAC,QAAQ,MAAM,yBAAyB,QAAQ;IAC7E,QAAQ;GACV,SAAS,OAAO;IACd,OAAO,KAAK;GACd,UAAU;IACR,cAAc,KAAA;IACd,IAAI,MAAM,SAAS,GAAG,SAAS;GACjC;EACF,EAAA,CAAG;EACH,OAAO;CACT;CAEA,MAAM,iBAAiB;EACrB,IAAI,eAAe,SAAS,mBAAmB,GAAG;EAClD,QAAQ,iBAAiB;GACvB,QAAQ,KAAA;GACR,MAAW,KAAK,CAAC,CAAC,OAAO,UAAmB,oBAAoB,OAAO,OAAO,CAAC;EACjF,GAAG,eAAe;CACpB;CAEA,MAAM,mBAAmB;EACvB,MAAW,IAAI,CAAC,CAAC,OAAO,UAAmB,oBAAoB,OAAO,gBAAgB,CAAC;CACzF;CACA,MAAM,2BAA2B;EAC/B,IAAI,OAAO,aAAa,eAAe,SAAS,oBAAoB,UAClE,MAAW,IAAI,CAAC,CAAC,OAAO,UAAmB,oBAAoB,OAAO,kBAAkB,CAAC;CAE7F;CACA,MAAM,iBAAiB;EACrB,mBAAwB,CAAC,CAAC,OAAO,UAAmB,oBAAoB,OAAO,QAAQ,CAAC;CAC1F;CAEA,IAAI,QAAQ,uBAAuB,MAAM;EACvC,WAAW,mBAAmB,YAAY,UAAU;EACpD,WAAW,mBAAmB,oBAAoB,kBAAkB;CACtE;CACA,IAAI,cAAc;EAChB,WAAW,mBAAmB,UAAU,QAAQ;EAGhD,IAAI,QAAQ,wBAAwB,MAClC,iBAAiB,KAAK,yBAAyB,gBAAgB,GAAG,CAAC;CAEvE;CAEA,OAAO;EACL,MAAM,QAAQ,QAAQ;EACtB,UAAU,QAAQ;EAClB,IAAI,OAAO,SAAS;GAClB,IAAI,QAAQ,aAAa,KAAA,KAAa,MAAM,SAAA,GAAA,eAAA,aAAA,CAAqB,QAAQ,QAAQ,GAAG;GACpF,cAAc;GACd,IAAI,MAAM,UAAU,cAAc;IAChC,IAAI,eAAe,eAAe;KAChC,WAAW,OAAO,YAAY;KAC9B;IACF;IACA,MAAM,UAAU,MAAM,MAAM;IAC5B,IAAI,SAAS,WAAW,SAAS,YAAY;GAC/C;GACA,MAAM,KAAK,KAAK;GAChB,IAAI,aAAa;GACjB,IAAI,MAAM,UAAU,cAClB,MAAW,KAAK,CAAC,CAAC,OAAO,UAAmB,oBAAoB,OAAO,OAAO,CAAC;QAC1E,SAAS;EAClB;EACA,QAAQ;GACN,OAAO,MAAM,KAAK;EACpB;EACA,QAAQ;GACN,WAAW,sBAAsB,YAAY,UAAU;GACvD,WAAW,sBAAsB,oBAAoB,kBAAkB;GACvE,WAAW,sBAAsB,UAAU,QAAQ;GACnD,OAAO,MAAM,IAAI;EACnB;CACF;AACF"}
@@ -39,6 +39,7 @@ export interface BrowserHttpTransportOptions {
39
39
  offlineReplayMaxRetries?: number;
40
40
  offlineReplayBaseDelayMs?: number;
41
41
  offlineReplayMaxDelayMs?: number;
42
+ offlineReplayOnStart?: boolean;
42
43
  random?: () => number;
43
44
  fetchFn?: typeof fetch;
44
45
  transformPayload?: PayloadTransform | readonly PayloadTransform[];
@@ -1 +1 @@
1
- {"version":3,"file":"http-transport.d.ts","sourceRoot":"","sources":["../src/http-transport.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,KAAK,EAEV,KAAK,QAAQ,EACb,KAAK,WAAW,EAChB,KAAK,gBAAgB,EACrB,KAAK,SAAS,EAEf,MAAM,gBAAgB,CAAC;AAExB,MAAM,MAAM,qBAAqB,GAAG,aAAa,GAAG,aAAa,CAAC;AAOlE,MAAM,WAAW,uBAAuB;IACtC,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,GAAG,KAAK,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,IAAI,EAAE,MAAM,GAAG,UAAU,CAAC;IAC1B,WAAW,CAAC,EAAE,kBAAkB,CAAC;IACjC,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,CAAC,KAAK,EAAE,uBAAuB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClE,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE,uBAAuB,KAAK,OAAO,CAAC,IAAI,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3F;AAED,MAAM,WAAW,oCAAoC;IACnD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,qBAAqB,CAAC;IACnC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,uBAAuB,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;CACnE;AAED,MAAM,WAAW,2BAA2B;IAC1C,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,WAAW,CAAC,EAAE,kBAAkB,CAAC;IACjC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,KAAK,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,UAAU,CAAC,CAAC;IACnC,WAAW,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,UAAU,CAAC,CAAC;IACzC,QAAQ,CAAC,EAAE,WAAW,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,qBAAqB,CAAC;IACnC,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,uBAAuB,CAAC;IACvC,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,MAAM,CAAC,EAAE,MAAM,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;IACvB,gBAAgB,CAAC,EAAE,gBAAgB,GAAG,SAAS,gBAAgB,EAAE,CAAC;IAClE,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;CACpD;AAED,wBAAgB,6BAA6B,CAC3C,OAAO,GAAE,oCAAyC,GACjD,uBAAuB,GAAG;IAAE,IAAI,EAAE,MAAM,MAAM,CAAA;CAAE,CAoClD;AAiCD,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,2BAA2B,GAAG,SAAS,CAiWpF"}
1
+ {"version":3,"file":"http-transport.d.ts","sourceRoot":"","sources":["../src/http-transport.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,KAAK,EAEV,KAAK,QAAQ,EACb,KAAK,WAAW,EAChB,KAAK,gBAAgB,EACrB,KAAK,SAAS,EAEf,MAAM,gBAAgB,CAAC;AAExB,MAAM,MAAM,qBAAqB,GAAG,aAAa,GAAG,aAAa,CAAC;AAOlE,MAAM,WAAW,uBAAuB;IACtC,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,GAAG,KAAK,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,IAAI,EAAE,MAAM,GAAG,UAAU,CAAC;IAC1B,WAAW,CAAC,EAAE,kBAAkB,CAAC;IACjC,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,CAAC,KAAK,EAAE,uBAAuB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClE,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE,uBAAuB,KAAK,OAAO,CAAC,IAAI,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3F;AAED,MAAM,WAAW,oCAAoC;IACnD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,qBAAqB,CAAC;IACnC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,uBAAuB,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;CACnE;AAED,MAAM,WAAW,2BAA2B;IAC1C,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,WAAW,CAAC,EAAE,kBAAkB,CAAC;IACjC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,KAAK,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,UAAU,CAAC,CAAC;IACnC,WAAW,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,UAAU,CAAC,CAAC;IACzC,QAAQ,CAAC,EAAE,WAAW,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,qBAAqB,CAAC;IACnC,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,uBAAuB,CAAC;IACvC,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,MAAM,CAAC,EAAE,MAAM,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;IACvB,gBAAgB,CAAC,EAAE,gBAAgB,GAAG,SAAS,gBAAgB,EAAE,CAAC;IAClE,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;CACpD;AAED,wBAAgB,6BAA6B,CAC3C,OAAO,GAAE,oCAAyC,GACjD,uBAAuB,GAAG;IAAE,IAAI,EAAE,MAAM,MAAM,CAAA;CAAE,CAoClD;AAiCD,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,2BAA2B,GAAG,SAAS,CAoYpF"}
@@ -76,7 +76,7 @@ function browserHttpTransport(options) {
76
76
  let timer;
77
77
  let activeFlush;
78
78
  let flushingBeacon = false;
79
- let replayingOffline = false;
79
+ let replayPromise;
80
80
  let lastContext;
81
81
  const headers = (payloadHeaders, contentType = codec.contentType) => ({
82
82
  "content-type": contentType,
@@ -192,17 +192,18 @@ function browserHttpTransport(options) {
192
192
  incrementLoggerMetaCounter("transport.offline.queued");
193
193
  return true;
194
194
  };
195
+ const isOnline = () => typeof navigator === "undefined" || navigator.onLine !== false;
195
196
  const sendFetchBatch = async (batch) => {
196
- if (batch.length === 0) return;
197
197
  const transformed = await encodeTransformedPayload(batch);
198
- if (offlineQueue && typeof navigator !== "undefined" && navigator.onLine === false) {
198
+ if (offlineQueue && !isOnline()) {
199
199
  await enqueueOfflinePayload(transformed.payload, transformed.headers, transformed.contentType);
200
- return;
200
+ return "queued";
201
201
  }
202
202
  try {
203
203
  await sendPayload(createOfflineEntry(transformed.payload, transformed.headers, transformed.contentType));
204
+ return "sent";
204
205
  } catch (error) {
205
- if (await enqueueOfflinePayload(transformed.payload, transformed.headers, transformed.contentType)) return;
206
+ if (await enqueueOfflinePayload(transformed.payload, transformed.headers, transformed.contentType)) return "queued";
206
207
  throw error;
207
208
  }
208
209
  };
@@ -224,13 +225,23 @@ function browserHttpTransport(options) {
224
225
  await sleep(replayRetryDelay(attempt));
225
226
  }
226
227
  };
227
- const replayOfflineQueue = async () => {
228
- if (!offlineQueue || replayingOffline) return;
229
- replayingOffline = true;
228
+ const replayOfflineQueue = () => {
229
+ if (!offlineQueue) return Promise.resolve();
230
+ replayPromise ??= (async () => {
231
+ try {
232
+ await offlineQueue.replay(sendOfflineEntryWithRetry);
233
+ } finally {
234
+ replayPromise = void 0;
235
+ }
236
+ })();
237
+ return replayPromise;
238
+ };
239
+ const replayOfflineQueueSafely = async (operation) => {
240
+ if (!offlineQueue || !isOnline()) return;
230
241
  try {
231
- await offlineQueue.replay(sendOfflineEntryWithRetry);
232
- } finally {
233
- replayingOffline = false;
242
+ await replayOfflineQueue();
243
+ } catch (error) {
244
+ reportInternalError(error, operation);
234
245
  }
235
246
  };
236
247
  const flushBeaconQueue = () => {
@@ -257,7 +268,7 @@ function browserHttpTransport(options) {
257
268
  }
258
269
  return activeFlush;
259
270
  }
260
- if (queue.length === 0) return Promise.resolve();
271
+ if (queue.length === 0) return preferBeacon ? Promise.resolve() : replayOfflineQueueSafely("replay");
261
272
  clearTimer();
262
273
  let resolve;
263
274
  let reject;
@@ -269,15 +280,19 @@ function browserHttpTransport(options) {
269
280
  (async () => {
270
281
  try {
271
282
  if (preferBeacon) flushBeaconQueue();
283
+ let sent = false;
284
+ let queued = false;
272
285
  while (queue.length > 0) {
273
286
  const batch = queue.splice(0, maxBatchSize);
274
287
  try {
275
- await sendFetchBatch(batch);
288
+ if (await sendFetchBatch(batch) === "sent") sent = true;
289
+ else queued = true;
276
290
  } catch (error) {
277
291
  queue.unshift(...batch);
278
292
  throw error;
279
293
  }
280
294
  }
295
+ if (!preferBeacon && sent && !queued) await replayOfflineQueueSafely("replay");
281
296
  resolve();
282
297
  } catch (error) {
283
298
  reject(error);
@@ -308,7 +323,10 @@ function browserHttpTransport(options) {
308
323
  globalThis.addEventListener?.("pagehide", onPageHide);
309
324
  globalThis.addEventListener?.("visibilitychange", onVisibilityChange);
310
325
  }
311
- if (offlineQueue) globalThis.addEventListener?.("online", onOnline);
326
+ if (offlineQueue) {
327
+ globalThis.addEventListener?.("online", onOnline);
328
+ if (options.offlineReplayOnStart ?? true) setTimeout(() => void replayOfflineQueueSafely("startup-replay"), 0);
329
+ }
312
330
  return {
313
331
  name: options.name ?? "browser-http",
314
332
  minLevel: options.minLevel,
@@ -1 +1 @@
1
- {"version":3,"file":"http-transport.js","names":[],"sources":["../src/http-transport.ts"],"sourcesContent":["import {\n applyPayloadTransforms,\n incrementLoggerMetaCounter,\n safeJsonCodec,\n toLevelValue,\n type Codec,\n type EncodedPayload,\n type LogEvent,\n type LoggerLevel,\n type PayloadTransform,\n type Transport,\n type TransportContext,\n} from \"@loggerjs/core\";\n\nexport type BrowserHttpDropPolicy = \"drop-oldest\" | \"drop-newest\";\n\nconst DEFAULT_BEACON_MAX_BYTES = 60 * 1024;\n\nconst sleep = (delayMs: number) =>\n delayMs <= 0 ? Promise.resolve() : new Promise<void>((resolve) => setTimeout(resolve, delayMs));\n\nexport interface BrowserHttpOfflineEntry {\n id: string;\n url: string;\n method: \"POST\" | \"PUT\";\n headers: Record<string, string>;\n body: string | Uint8Array;\n credentials?: RequestCredentials;\n keepalive: boolean;\n createdAt: number;\n}\n\nexport interface BrowserHttpOfflineQueue {\n enqueue: (entry: BrowserHttpOfflineEntry) => void | Promise<void>;\n replay: (send: (entry: BrowserHttpOfflineEntry) => Promise<void>) => void | Promise<void>;\n}\n\nexport interface MemoryBrowserHttpOfflineQueueOptions {\n maxEntries?: number;\n dropPolicy?: BrowserHttpDropPolicy;\n onDrop?: (entry: BrowserHttpOfflineEntry, reason: string) => void;\n}\n\nexport interface BrowserHttpTransportOptions {\n url: string;\n name?: string;\n method?: \"POST\" | \"PUT\";\n headers?: Record<string, string>;\n credentials?: RequestCredentials;\n keepalive?: boolean;\n codec?: Codec<string | Uint8Array>;\n beaconCodec?: Codec<string | Uint8Array>;\n minLevel?: LoggerLevel;\n maxBatchSize?: number;\n flushIntervalMs?: number;\n maxQueueSize?: number;\n dropPolicy?: BrowserHttpDropPolicy;\n useBeaconOnPageHide?: boolean;\n beaconMaxBytes?: number;\n offlineQueue?: BrowserHttpOfflineQueue;\n offlineReplayMaxRetries?: number;\n offlineReplayBaseDelayMs?: number;\n offlineReplayMaxDelayMs?: number;\n random?: () => number;\n fetchFn?: typeof fetch;\n transformPayload?: PayloadTransform | readonly PayloadTransform[];\n onDrop?: (event: LogEvent, reason: string) => void;\n}\n\nexport function memoryBrowserHttpOfflineQueue(\n options: MemoryBrowserHttpOfflineQueueOptions = {},\n): BrowserHttpOfflineQueue & { size: () => number } {\n const entries: BrowserHttpOfflineEntry[] = [];\n const maxEntries = options.maxEntries ?? 1000;\n const dropPolicy = options.dropPolicy ?? \"drop-oldest\";\n\n const drop = (entry: BrowserHttpOfflineEntry, reason: string) => {\n incrementLoggerMetaCounter(\"transport.offline.dropped\");\n incrementLoggerMetaCounter(`transport.offline.dropped.${reason}`);\n options.onDrop?.(entry, reason);\n };\n\n return {\n enqueue(entry) {\n if (entries.length >= maxEntries) {\n if (dropPolicy === \"drop-newest\") {\n drop(entry, \"queue-full\");\n return;\n }\n const dropped = entries.shift();\n if (dropped) drop(dropped, \"queue-full\");\n }\n entries.push(entry);\n },\n async replay(send) {\n while (entries.length > 0) {\n const entry = entries[0];\n if (!entry) return;\n // oxlint-disable-next-line no-await-in-loop -- Replay must remove entries only after each send succeeds.\n await send(entry);\n entries.shift();\n }\n },\n size() {\n return entries.length;\n },\n };\n}\n\nfunction payloadToBody(payload: EncodedPayload): BodyInit {\n if (typeof payload === \"string\") return payload;\n return Uint8Array.from(payload);\n}\n\nfunction payloadByteLength(payload: EncodedPayload): number {\n if (typeof payload !== \"string\") return payload.byteLength;\n if (typeof TextEncoder !== \"undefined\") return new TextEncoder().encode(payload).byteLength;\n return new Blob([payload]).size;\n}\n\nfunction payloadToBeaconBody(payload: EncodedPayload, contentType: string): BodyInit {\n return new Blob([typeof payload === \"string\" ? payload : Uint8Array.from(payload)], {\n type: contentType,\n });\n}\n\ninterface BeaconChunk {\n events: LogEvent[];\n payload: string | Uint8Array;\n}\n\nfunction remainingEvents(chunks: BeaconChunk[], startIndex: number): LogEvent[] {\n const events: LogEvent[] = [];\n for (let index = startIndex; index < chunks.length; index++) {\n const chunk = chunks[index];\n if (chunk) events.push(...chunk.events);\n }\n return events;\n}\n\nexport function browserHttpTransport(options: BrowserHttpTransportOptions): Transport {\n const codec = options.codec ?? safeJsonCodec();\n const beaconCodec = options.beaconCodec ?? codec;\n const queue: LogEvent[] = [];\n const maxBatchSize = options.maxBatchSize ?? 50;\n if (!Number.isSafeInteger(maxBatchSize) || maxBatchSize <= 0) {\n throw new RangeError(\"maxBatchSize must be a positive safe integer\");\n }\n const flushIntervalMs = options.flushIntervalMs ?? 2000;\n const maxQueueSize = options.maxQueueSize ?? 1000;\n const dropPolicy = options.dropPolicy ?? \"drop-oldest\";\n const beaconMaxBytes = options.beaconMaxBytes ?? DEFAULT_BEACON_MAX_BYTES;\n const offlineQueue = options.offlineQueue;\n const offlineReplayMaxRetries = options.offlineReplayMaxRetries ?? 3;\n const offlineReplayBaseDelayMs = options.offlineReplayBaseDelayMs ?? 250;\n const offlineReplayMaxDelayMs = options.offlineReplayMaxDelayMs ?? 5000;\n const random = options.random ?? Math.random;\n const fetchFn = options.fetchFn ?? globalThis.fetch?.bind(globalThis);\n let offlineEntrySeq = 0;\n let timer: ReturnType<typeof setTimeout> | undefined;\n let activeFlush: Promise<void> | undefined;\n let flushingBeacon = false;\n let replayingOffline = false;\n let lastContext: TransportContext | undefined;\n\n const headers = (payloadHeaders?: Record<string, string>, contentType = codec.contentType) => ({\n \"content-type\": contentType,\n ...payloadHeaders,\n ...options.headers,\n });\n\n const clearTimer = () => {\n if (timer) clearTimeout(timer);\n timer = undefined;\n };\n\n const reportDrop = (event: LogEvent, reason: string) => {\n incrementLoggerMetaCounter(\"transport.dropped\");\n incrementLoggerMetaCounter(`transport.dropped.${reason}`);\n options.onDrop?.(event, reason);\n };\n\n const reportInternalError = (error: unknown, operation: string) => {\n lastContext?.reportInternalError(error, {\n phase: \"transport\",\n transport: options.name ?? \"browser-http\",\n operation,\n });\n };\n\n const createBeaconChunks = (batch: LogEvent[]): BeaconChunk[] => {\n const chunks: BeaconChunk[] = [];\n const oversized: LogEvent[] = [];\n let currentEvents: LogEvent[] = [];\n let currentPayload: string | Uint8Array | undefined;\n\n for (const event of batch) {\n if (currentEvents.length === maxBatchSize && currentPayload !== undefined) {\n chunks.push({ events: currentEvents, payload: currentPayload });\n currentEvents = [];\n currentPayload = undefined;\n }\n const candidateEvents = [...currentEvents, event];\n const candidatePayload = beaconCodec.encode(candidateEvents);\n if (payloadByteLength(candidatePayload) <= beaconMaxBytes) {\n currentEvents = candidateEvents;\n currentPayload = candidatePayload;\n continue;\n }\n\n if (currentEvents.length > 0 && currentPayload !== undefined) {\n chunks.push({ events: currentEvents, payload: currentPayload });\n }\n\n const singlePayload = beaconCodec.encode([event]);\n if (payloadByteLength(singlePayload) <= beaconMaxBytes) {\n currentEvents = [event];\n currentPayload = singlePayload;\n } else {\n currentEvents = [];\n currentPayload = undefined;\n oversized.push(event);\n }\n }\n\n if (currentEvents.length > 0 && currentPayload !== undefined) {\n chunks.push({ events: currentEvents, payload: currentPayload });\n }\n\n for (const event of oversized) reportDrop(event, \"beacon-too-large\");\n return chunks;\n };\n\n const sendBeaconBatch = (\n batch: LogEvent[],\n ): { remaining: LogEvent[]; failure?: { error: unknown } } => {\n if (typeof navigator === \"undefined\" || !navigator.sendBeacon) {\n return { remaining: batch };\n }\n\n const chunks = createBeaconChunks(batch);\n for (let index = 0; index < chunks.length; index++) {\n const chunk = chunks[index];\n if (!chunk) continue;\n try {\n const ok = navigator.sendBeacon(\n options.url,\n payloadToBeaconBody(chunk.payload, beaconCodec.contentType),\n );\n if (!ok) return { remaining: remainingEvents(chunks, index) };\n } catch (error) {\n return { remaining: remainingEvents(chunks, index), failure: { error } };\n }\n }\n\n return { remaining: [] };\n };\n\n const sendPayload = async (entry: BrowserHttpOfflineEntry) => {\n if (!fetchFn) throw new Error(\"fetch is not available for browserHttpTransport\");\n const response = await fetchFn(entry.url, {\n method: entry.method,\n headers: entry.headers,\n body: payloadToBody(entry.body),\n credentials: entry.credentials,\n keepalive: entry.keepalive,\n });\n if (!response.ok) throw new Error(`browserHttpTransport failed with status ${response.status}`);\n };\n\n const encodeTransformedPayload = async (batch: LogEvent[]) => {\n const encoded = codec.encode(batch);\n return applyPayloadTransforms(\n encoded,\n {\n contentType: codec.contentType,\n events: batch,\n transport: options.name ?? \"browser-http\",\n },\n options.transformPayload,\n );\n };\n\n const createOfflineEntry = (\n payload: string | Uint8Array,\n payloadHeaders?: Record<string, string>,\n contentType?: string,\n ): BrowserHttpOfflineEntry => ({\n id: `${Date.now().toString(36)}-${(offlineEntrySeq++).toString(36)}`,\n url: options.url,\n method: options.method ?? \"POST\",\n headers: headers(payloadHeaders, contentType),\n body: payload,\n credentials: options.credentials,\n keepalive: options.keepalive ?? true,\n createdAt: Date.now(),\n });\n\n const enqueueOfflinePayload = async (\n payload: string | Uint8Array,\n payloadHeaders?: Record<string, string>,\n contentType?: string,\n ) => {\n if (!offlineQueue) return false;\n await offlineQueue.enqueue(createOfflineEntry(payload, payloadHeaders, contentType));\n incrementLoggerMetaCounter(\"transport.offline.queued\");\n return true;\n };\n\n const sendFetchBatch = async (batch: LogEvent[]) => {\n if (batch.length === 0) return;\n const transformed = await encodeTransformedPayload(batch);\n if (offlineQueue && typeof navigator !== \"undefined\" && navigator.onLine === false) {\n await enqueueOfflinePayload(\n transformed.payload,\n transformed.headers,\n transformed.contentType,\n );\n return;\n }\n try {\n await sendPayload(\n createOfflineEntry(transformed.payload, transformed.headers, transformed.contentType),\n );\n } catch (error) {\n if (\n await enqueueOfflinePayload(\n transformed.payload,\n transformed.headers,\n transformed.contentType,\n )\n ) {\n return;\n }\n throw error;\n }\n };\n\n const replayRetryDelay = (attempt: number): number => {\n const cap = Math.min(offlineReplayMaxDelayMs, offlineReplayBaseDelayMs * 2 ** attempt);\n return cap <= 0 ? 0 : random() * cap;\n };\n\n const sendOfflineEntryWithRetry = async (entry: BrowserHttpOfflineEntry) => {\n for (let attempt = 0; ; attempt++) {\n try {\n // oxlint-disable-next-line no-await-in-loop -- Retry attempts must run sequentially.\n await sendPayload(entry);\n incrementLoggerMetaCounter(\"transport.offline.replayed\");\n return;\n } catch (error) {\n if (attempt >= offlineReplayMaxRetries) {\n incrementLoggerMetaCounter(\"transport.offline.replay.failed\");\n throw error;\n }\n incrementLoggerMetaCounter(\"transport.offline.retry\");\n // oxlint-disable-next-line no-await-in-loop -- Backoff must complete before the next retry.\n await sleep(replayRetryDelay(attempt));\n }\n }\n };\n\n const replayOfflineQueue = async () => {\n if (!offlineQueue || replayingOffline) return;\n replayingOffline = true;\n try {\n await offlineQueue.replay(sendOfflineEntryWithRetry);\n } finally {\n replayingOffline = false;\n }\n };\n\n // Beacon submission is synchronous, even while an earlier Fetch is pending.\n // Only queued events are eligible; the active Fetch batch keeps its ownership.\n const flushBeaconQueue = () => {\n if (flushingBeacon || options.transformPayload || queue.length === 0) return;\n flushingBeacon = true;\n let pending = queue.splice(0, queue.length);\n try {\n const result = sendBeaconBatch(pending);\n pending = result.remaining;\n if (result.failure) throw result.failure.error;\n } finally {\n if (pending.length > 0) queue.unshift(...pending);\n flushingBeacon = false;\n }\n };\n\n const flush = (preferBeacon = false): Promise<void> => {\n if (activeFlush) {\n if (preferBeacon) {\n try {\n flushBeaconQueue();\n } catch (error) {\n // close() must still wait for the active delivery before reporting a Beacon error.\n return activeFlush.then(() => {\n throw error;\n });\n }\n }\n return activeFlush;\n }\n if (queue.length === 0) return Promise.resolve();\n clearTimer();\n\n // Publish the task before invoking user codecs/transforms, which may reenter log().\n let resolve!: () => void;\n let reject!: (error: unknown) => void;\n const task = new Promise<void>((onResolve, onReject) => {\n resolve = onResolve;\n reject = onReject;\n });\n activeFlush = task;\n void (async () => {\n try {\n if (preferBeacon) flushBeaconQueue();\n while (queue.length > 0) {\n const batch = queue.splice(0, maxBatchSize);\n try {\n // oxlint-disable-next-line no-await-in-loop -- Preserve batch order and stop on the first unhandled failure.\n await sendFetchBatch(batch);\n } catch (error) {\n queue.unshift(...batch);\n throw error;\n }\n }\n resolve();\n } catch (error) {\n reject(error);\n } finally {\n activeFlush = undefined;\n if (queue.length > 0) schedule();\n }\n })();\n return task;\n };\n\n const schedule = () => {\n if (activeFlush || timer || flushIntervalMs <= 0) return;\n timer = setTimeout(() => {\n timer = undefined;\n void flush(false).catch((error: unknown) => reportInternalError(error, \"flush\"));\n }, flushIntervalMs);\n };\n\n const onPageHide = () => {\n void flush(true).catch((error: unknown) => reportInternalError(error, \"pagehide-flush\"));\n };\n const onVisibilityChange = () => {\n if (typeof document !== \"undefined\" && document.visibilityState === \"hidden\") {\n void flush(true).catch((error: unknown) => reportInternalError(error, \"visibility-flush\"));\n }\n };\n const onOnline = () => {\n void replayOfflineQueue().catch((error: unknown) => reportInternalError(error, \"replay\"));\n };\n\n if (options.useBeaconOnPageHide ?? true) {\n globalThis.addEventListener?.(\"pagehide\", onPageHide);\n globalThis.addEventListener?.(\"visibilitychange\", onVisibilityChange);\n }\n if (offlineQueue) globalThis.addEventListener?.(\"online\", onOnline);\n\n return {\n name: options.name ?? \"browser-http\",\n minLevel: options.minLevel,\n log(event, context) {\n if (options.minLevel !== undefined && event.level < toLevelValue(options.minLevel)) return;\n lastContext = context;\n if (queue.length >= maxQueueSize) {\n if (dropPolicy === \"drop-newest\") {\n reportDrop(event, \"queue-full\");\n return;\n }\n const dropped = queue.shift();\n if (dropped) reportDrop(dropped, \"queue-full\");\n }\n queue.push(event);\n if (activeFlush) return;\n if (queue.length >= maxBatchSize) {\n void flush(false).catch((error: unknown) => reportInternalError(error, \"flush\"));\n } else schedule();\n },\n flush() {\n return flush(false);\n },\n close() {\n globalThis.removeEventListener?.(\"pagehide\", onPageHide);\n globalThis.removeEventListener?.(\"visibilitychange\", onVisibilityChange);\n globalThis.removeEventListener?.(\"online\", onOnline);\n return flush(true);\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"http-transport.js","names":[],"sources":["../src/http-transport.ts"],"sourcesContent":["import {\n applyPayloadTransforms,\n incrementLoggerMetaCounter,\n safeJsonCodec,\n toLevelValue,\n type Codec,\n type EncodedPayload,\n type LogEvent,\n type LoggerLevel,\n type PayloadTransform,\n type Transport,\n type TransportContext,\n} from \"@loggerjs/core\";\n\nexport type BrowserHttpDropPolicy = \"drop-oldest\" | \"drop-newest\";\n\nconst DEFAULT_BEACON_MAX_BYTES = 60 * 1024;\n\nconst sleep = (delayMs: number) =>\n delayMs <= 0 ? Promise.resolve() : new Promise<void>((resolve) => setTimeout(resolve, delayMs));\n\nexport interface BrowserHttpOfflineEntry {\n id: string;\n url: string;\n method: \"POST\" | \"PUT\";\n headers: Record<string, string>;\n body: string | Uint8Array;\n credentials?: RequestCredentials;\n keepalive: boolean;\n createdAt: number;\n}\n\nexport interface BrowserHttpOfflineQueue {\n enqueue: (entry: BrowserHttpOfflineEntry) => void | Promise<void>;\n replay: (send: (entry: BrowserHttpOfflineEntry) => Promise<void>) => void | Promise<void>;\n}\n\nexport interface MemoryBrowserHttpOfflineQueueOptions {\n maxEntries?: number;\n dropPolicy?: BrowserHttpDropPolicy;\n onDrop?: (entry: BrowserHttpOfflineEntry, reason: string) => void;\n}\n\nexport interface BrowserHttpTransportOptions {\n url: string;\n name?: string;\n method?: \"POST\" | \"PUT\";\n headers?: Record<string, string>;\n credentials?: RequestCredentials;\n keepalive?: boolean;\n codec?: Codec<string | Uint8Array>;\n beaconCodec?: Codec<string | Uint8Array>;\n minLevel?: LoggerLevel;\n maxBatchSize?: number;\n flushIntervalMs?: number;\n maxQueueSize?: number;\n dropPolicy?: BrowserHttpDropPolicy;\n useBeaconOnPageHide?: boolean;\n beaconMaxBytes?: number;\n offlineQueue?: BrowserHttpOfflineQueue;\n offlineReplayMaxRetries?: number;\n offlineReplayBaseDelayMs?: number;\n offlineReplayMaxDelayMs?: number;\n offlineReplayOnStart?: boolean;\n random?: () => number;\n fetchFn?: typeof fetch;\n transformPayload?: PayloadTransform | readonly PayloadTransform[];\n onDrop?: (event: LogEvent, reason: string) => void;\n}\n\nexport function memoryBrowserHttpOfflineQueue(\n options: MemoryBrowserHttpOfflineQueueOptions = {},\n): BrowserHttpOfflineQueue & { size: () => number } {\n const entries: BrowserHttpOfflineEntry[] = [];\n const maxEntries = options.maxEntries ?? 1000;\n const dropPolicy = options.dropPolicy ?? \"drop-oldest\";\n\n const drop = (entry: BrowserHttpOfflineEntry, reason: string) => {\n incrementLoggerMetaCounter(\"transport.offline.dropped\");\n incrementLoggerMetaCounter(`transport.offline.dropped.${reason}`);\n options.onDrop?.(entry, reason);\n };\n\n return {\n enqueue(entry) {\n if (entries.length >= maxEntries) {\n if (dropPolicy === \"drop-newest\") {\n drop(entry, \"queue-full\");\n return;\n }\n const dropped = entries.shift();\n if (dropped) drop(dropped, \"queue-full\");\n }\n entries.push(entry);\n },\n async replay(send) {\n while (entries.length > 0) {\n const entry = entries[0];\n if (!entry) return;\n // oxlint-disable-next-line no-await-in-loop -- Replay must remove entries only after each send succeeds.\n await send(entry);\n entries.shift();\n }\n },\n size() {\n return entries.length;\n },\n };\n}\n\nfunction payloadToBody(payload: EncodedPayload): BodyInit {\n if (typeof payload === \"string\") return payload;\n return Uint8Array.from(payload);\n}\n\nfunction payloadByteLength(payload: EncodedPayload): number {\n if (typeof payload !== \"string\") return payload.byteLength;\n if (typeof TextEncoder !== \"undefined\") return new TextEncoder().encode(payload).byteLength;\n return new Blob([payload]).size;\n}\n\nfunction payloadToBeaconBody(payload: EncodedPayload, contentType: string): BodyInit {\n return new Blob([typeof payload === \"string\" ? payload : Uint8Array.from(payload)], {\n type: contentType,\n });\n}\n\ninterface BeaconChunk {\n events: LogEvent[];\n payload: string | Uint8Array;\n}\n\nfunction remainingEvents(chunks: BeaconChunk[], startIndex: number): LogEvent[] {\n const events: LogEvent[] = [];\n for (let index = startIndex; index < chunks.length; index++) {\n const chunk = chunks[index];\n if (chunk) events.push(...chunk.events);\n }\n return events;\n}\n\nexport function browserHttpTransport(options: BrowserHttpTransportOptions): Transport {\n const codec = options.codec ?? safeJsonCodec();\n const beaconCodec = options.beaconCodec ?? codec;\n const queue: LogEvent[] = [];\n const maxBatchSize = options.maxBatchSize ?? 50;\n if (!Number.isSafeInteger(maxBatchSize) || maxBatchSize <= 0) {\n throw new RangeError(\"maxBatchSize must be a positive safe integer\");\n }\n const flushIntervalMs = options.flushIntervalMs ?? 2000;\n const maxQueueSize = options.maxQueueSize ?? 1000;\n const dropPolicy = options.dropPolicy ?? \"drop-oldest\";\n const beaconMaxBytes = options.beaconMaxBytes ?? DEFAULT_BEACON_MAX_BYTES;\n const offlineQueue = options.offlineQueue;\n const offlineReplayMaxRetries = options.offlineReplayMaxRetries ?? 3;\n const offlineReplayBaseDelayMs = options.offlineReplayBaseDelayMs ?? 250;\n const offlineReplayMaxDelayMs = options.offlineReplayMaxDelayMs ?? 5000;\n const random = options.random ?? Math.random;\n const fetchFn = options.fetchFn ?? globalThis.fetch?.bind(globalThis);\n let offlineEntrySeq = 0;\n let timer: ReturnType<typeof setTimeout> | undefined;\n let activeFlush: Promise<void> | undefined;\n let flushingBeacon = false;\n let replayPromise: Promise<void> | undefined;\n let lastContext: TransportContext | undefined;\n\n const headers = (payloadHeaders?: Record<string, string>, contentType = codec.contentType) => ({\n \"content-type\": contentType,\n ...payloadHeaders,\n ...options.headers,\n });\n\n const clearTimer = () => {\n if (timer) clearTimeout(timer);\n timer = undefined;\n };\n\n const reportDrop = (event: LogEvent, reason: string) => {\n incrementLoggerMetaCounter(\"transport.dropped\");\n incrementLoggerMetaCounter(`transport.dropped.${reason}`);\n options.onDrop?.(event, reason);\n };\n\n const reportInternalError = (error: unknown, operation: string) => {\n lastContext?.reportInternalError(error, {\n phase: \"transport\",\n transport: options.name ?? \"browser-http\",\n operation,\n });\n };\n\n const createBeaconChunks = (batch: LogEvent[]): BeaconChunk[] => {\n const chunks: BeaconChunk[] = [];\n const oversized: LogEvent[] = [];\n let currentEvents: LogEvent[] = [];\n let currentPayload: string | Uint8Array | undefined;\n\n for (const event of batch) {\n if (currentEvents.length === maxBatchSize && currentPayload !== undefined) {\n chunks.push({ events: currentEvents, payload: currentPayload });\n currentEvents = [];\n currentPayload = undefined;\n }\n const candidateEvents = [...currentEvents, event];\n const candidatePayload = beaconCodec.encode(candidateEvents);\n if (payloadByteLength(candidatePayload) <= beaconMaxBytes) {\n currentEvents = candidateEvents;\n currentPayload = candidatePayload;\n continue;\n }\n\n if (currentEvents.length > 0 && currentPayload !== undefined) {\n chunks.push({ events: currentEvents, payload: currentPayload });\n }\n\n const singlePayload = beaconCodec.encode([event]);\n if (payloadByteLength(singlePayload) <= beaconMaxBytes) {\n currentEvents = [event];\n currentPayload = singlePayload;\n } else {\n currentEvents = [];\n currentPayload = undefined;\n oversized.push(event);\n }\n }\n\n if (currentEvents.length > 0 && currentPayload !== undefined) {\n chunks.push({ events: currentEvents, payload: currentPayload });\n }\n\n for (const event of oversized) reportDrop(event, \"beacon-too-large\");\n return chunks;\n };\n\n const sendBeaconBatch = (\n batch: LogEvent[],\n ): { remaining: LogEvent[]; failure?: { error: unknown } } => {\n if (typeof navigator === \"undefined\" || !navigator.sendBeacon) {\n return { remaining: batch };\n }\n\n const chunks = createBeaconChunks(batch);\n for (let index = 0; index < chunks.length; index++) {\n const chunk = chunks[index];\n if (!chunk) continue;\n try {\n const ok = navigator.sendBeacon(\n options.url,\n payloadToBeaconBody(chunk.payload, beaconCodec.contentType),\n );\n if (!ok) return { remaining: remainingEvents(chunks, index) };\n } catch (error) {\n return { remaining: remainingEvents(chunks, index), failure: { error } };\n }\n }\n\n return { remaining: [] };\n };\n\n const sendPayload = async (entry: BrowserHttpOfflineEntry) => {\n if (!fetchFn) throw new Error(\"fetch is not available for browserHttpTransport\");\n const response = await fetchFn(entry.url, {\n method: entry.method,\n headers: entry.headers,\n body: payloadToBody(entry.body),\n credentials: entry.credentials,\n keepalive: entry.keepalive,\n });\n if (!response.ok) throw new Error(`browserHttpTransport failed with status ${response.status}`);\n };\n\n const encodeTransformedPayload = async (batch: LogEvent[]) => {\n const encoded = codec.encode(batch);\n return applyPayloadTransforms(\n encoded,\n {\n contentType: codec.contentType,\n events: batch,\n transport: options.name ?? \"browser-http\",\n },\n options.transformPayload,\n );\n };\n\n const createOfflineEntry = (\n payload: string | Uint8Array,\n payloadHeaders?: Record<string, string>,\n contentType?: string,\n ): BrowserHttpOfflineEntry => ({\n id: `${Date.now().toString(36)}-${(offlineEntrySeq++).toString(36)}`,\n url: options.url,\n method: options.method ?? \"POST\",\n headers: headers(payloadHeaders, contentType),\n body: payload,\n credentials: options.credentials,\n keepalive: options.keepalive ?? true,\n createdAt: Date.now(),\n });\n\n const enqueueOfflinePayload = async (\n payload: string | Uint8Array,\n payloadHeaders?: Record<string, string>,\n contentType?: string,\n ) => {\n if (!offlineQueue) return false;\n await offlineQueue.enqueue(createOfflineEntry(payload, payloadHeaders, contentType));\n incrementLoggerMetaCounter(\"transport.offline.queued\");\n return true;\n };\n\n const isOnline = () => typeof navigator === \"undefined\" || navigator.onLine !== false;\n\n // Resolves to \"sent\" when the collector accepted the batch and \"queued\" when\n // the offline queue took it instead.\n const sendFetchBatch = async (batch: LogEvent[]): Promise<\"sent\" | \"queued\"> => {\n const transformed = await encodeTransformedPayload(batch);\n if (offlineQueue && !isOnline()) {\n await enqueueOfflinePayload(\n transformed.payload,\n transformed.headers,\n transformed.contentType,\n );\n return \"queued\";\n }\n try {\n await sendPayload(\n createOfflineEntry(transformed.payload, transformed.headers, transformed.contentType),\n );\n return \"sent\";\n } catch (error) {\n if (\n await enqueueOfflinePayload(\n transformed.payload,\n transformed.headers,\n transformed.contentType,\n )\n ) {\n return \"queued\";\n }\n throw error;\n }\n };\n\n const replayRetryDelay = (attempt: number): number => {\n const cap = Math.min(offlineReplayMaxDelayMs, offlineReplayBaseDelayMs * 2 ** attempt);\n return cap <= 0 ? 0 : random() * cap;\n };\n\n const sendOfflineEntryWithRetry = async (entry: BrowserHttpOfflineEntry) => {\n for (let attempt = 0; ; attempt++) {\n try {\n // oxlint-disable-next-line no-await-in-loop -- Retry attempts must run sequentially.\n await sendPayload(entry);\n incrementLoggerMetaCounter(\"transport.offline.replayed\");\n return;\n } catch (error) {\n if (attempt >= offlineReplayMaxRetries) {\n incrementLoggerMetaCounter(\"transport.offline.replay.failed\");\n throw error;\n }\n incrementLoggerMetaCounter(\"transport.offline.retry\");\n // oxlint-disable-next-line no-await-in-loop -- Backoff must complete before the next retry.\n await sleep(replayRetryDelay(attempt));\n }\n }\n };\n\n const replayOfflineQueue = (): Promise<void> => {\n if (!offlineQueue) return Promise.resolve();\n replayPromise ??= (async () => {\n try {\n await offlineQueue.replay(sendOfflineEntryWithRetry);\n } finally {\n replayPromise = undefined;\n }\n })();\n return replayPromise;\n };\n\n // Stored entries stay in the offline queue when replay fails, so a failed\n // replay is reported instead of failing the flush that triggered it.\n const replayOfflineQueueSafely = async (operation: string) => {\n if (!offlineQueue || !isOnline()) return;\n try {\n await replayOfflineQueue();\n } catch (error) {\n reportInternalError(error, operation);\n }\n };\n\n // Beacon submission is synchronous, even while an earlier Fetch is pending.\n // Only queued events are eligible; the active Fetch batch keeps its ownership.\n const flushBeaconQueue = () => {\n if (flushingBeacon || options.transformPayload || queue.length === 0) return;\n flushingBeacon = true;\n let pending = queue.splice(0, queue.length);\n try {\n const result = sendBeaconBatch(pending);\n pending = result.remaining;\n if (result.failure) throw result.failure.error;\n } finally {\n if (pending.length > 0) queue.unshift(...pending);\n flushingBeacon = false;\n }\n };\n\n const flush = (preferBeacon = false): Promise<void> => {\n if (activeFlush) {\n if (preferBeacon) {\n try {\n flushBeaconQueue();\n } catch (error) {\n // close() must still wait for the active delivery before reporting a Beacon error.\n return activeFlush.then(() => {\n throw error;\n });\n }\n }\n return activeFlush;\n }\n if (queue.length === 0) {\n // An explicit flush with nothing live still retries stored payloads, so\n // entries queued during a server outage do not wait for an `online` event.\n return preferBeacon ? Promise.resolve() : replayOfflineQueueSafely(\"replay\");\n }\n clearTimer();\n\n // Publish the task before invoking user codecs/transforms, which may reenter log().\n let resolve!: () => void;\n let reject!: (error: unknown) => void;\n const task = new Promise<void>((onResolve, onReject) => {\n resolve = onResolve;\n reject = onReject;\n });\n activeFlush = task;\n void (async () => {\n try {\n if (preferBeacon) flushBeaconQueue();\n let sent = false;\n let queued = false;\n while (queue.length > 0) {\n const batch = queue.splice(0, maxBatchSize);\n try {\n // oxlint-disable-next-line no-await-in-loop -- Preserve batch order and stop on the first unhandled failure.\n const outcome = await sendFetchBatch(batch);\n if (outcome === \"sent\") sent = true;\n else queued = true;\n } catch (error) {\n queue.unshift(...batch);\n throw error;\n }\n }\n // A live batch reached the collector and none fell back to the offline\n // queue, so the collector is reachable again: replay stored payloads.\n if (!preferBeacon && sent && !queued) await replayOfflineQueueSafely(\"replay\");\n resolve();\n } catch (error) {\n reject(error);\n } finally {\n activeFlush = undefined;\n if (queue.length > 0) schedule();\n }\n })();\n return task;\n };\n\n const schedule = () => {\n if (activeFlush || timer || flushIntervalMs <= 0) return;\n timer = setTimeout(() => {\n timer = undefined;\n void flush(false).catch((error: unknown) => reportInternalError(error, \"flush\"));\n }, flushIntervalMs);\n };\n\n const onPageHide = () => {\n void flush(true).catch((error: unknown) => reportInternalError(error, \"pagehide-flush\"));\n };\n const onVisibilityChange = () => {\n if (typeof document !== \"undefined\" && document.visibilityState === \"hidden\") {\n void flush(true).catch((error: unknown) => reportInternalError(error, \"visibility-flush\"));\n }\n };\n const onOnline = () => {\n void replayOfflineQueue().catch((error: unknown) => reportInternalError(error, \"replay\"));\n };\n\n if (options.useBeaconOnPageHide ?? true) {\n globalThis.addEventListener?.(\"pagehide\", onPageHide);\n globalThis.addEventListener?.(\"visibilitychange\", onVisibilityChange);\n }\n if (offlineQueue) {\n globalThis.addEventListener?.(\"online\", onOnline);\n // Payloads persisted by an earlier page load (for example in IndexedDB)\n // would otherwise wait for an `online` event that may never fire.\n if (options.offlineReplayOnStart ?? true) {\n setTimeout(() => void replayOfflineQueueSafely(\"startup-replay\"), 0);\n }\n }\n\n return {\n name: options.name ?? \"browser-http\",\n minLevel: options.minLevel,\n log(event, context) {\n if (options.minLevel !== undefined && event.level < toLevelValue(options.minLevel)) return;\n lastContext = context;\n if (queue.length >= maxQueueSize) {\n if (dropPolicy === \"drop-newest\") {\n reportDrop(event, \"queue-full\");\n return;\n }\n const dropped = queue.shift();\n if (dropped) reportDrop(dropped, \"queue-full\");\n }\n queue.push(event);\n if (activeFlush) return;\n if (queue.length >= maxBatchSize) {\n void flush(false).catch((error: unknown) => reportInternalError(error, \"flush\"));\n } else schedule();\n },\n flush() {\n return flush(false);\n },\n close() {\n globalThis.removeEventListener?.(\"pagehide\", onPageHide);\n globalThis.removeEventListener?.(\"visibilitychange\", onVisibilityChange);\n globalThis.removeEventListener?.(\"online\", onOnline);\n return flush(true);\n },\n };\n}\n"],"mappings":"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"}