@loggerjs/browser 0.5.6 → 0.7.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.
Files changed (60) hide show
  1. package/README.md +13 -16
  2. package/dist/chunks/http-capture-utils-DbhpUirh.cjs +83 -0
  3. package/dist/chunks/http-capture-utils-DbhpUirh.cjs.map +1 -0
  4. package/dist/chunks/http-capture-utils-_hG5wepA.js +48 -0
  5. package/dist/chunks/http-capture-utils-_hG5wepA.js.map +1 -0
  6. package/dist/context-propagation-integration.cjs +5 -4
  7. package/dist/context-propagation-integration.cjs.map +1 -1
  8. package/dist/context-propagation-integration.d.ts +2 -1
  9. package/dist/context-propagation-integration.d.ts.map +1 -1
  10. package/dist/context-propagation-integration.js +2 -1
  11. package/dist/context-propagation-integration.js.map +1 -1
  12. package/dist/fetch-integration.cjs +8 -52
  13. package/dist/fetch-integration.cjs.map +1 -1
  14. package/dist/fetch-integration.js +1 -45
  15. package/dist/fetch-integration.js.map +1 -1
  16. package/dist/http-transport.cjs +121 -29
  17. package/dist/http-transport.cjs.map +1 -1
  18. package/dist/http-transport.d.ts +18 -0
  19. package/dist/http-transport.d.ts.map +1 -1
  20. package/dist/http-transport.js +121 -29
  21. package/dist/http-transport.js.map +1 -1
  22. package/dist/index.cjs +58 -4148
  23. package/dist/index.d.ts +243 -17
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +25 -4116
  26. package/dist/indexeddb-offline-queue.cjs +8 -1
  27. package/dist/indexeddb-offline-queue.cjs.map +1 -1
  28. package/dist/indexeddb-offline-queue.d.ts.map +1 -1
  29. package/dist/indexeddb-offline-queue.js +8 -1
  30. package/dist/indexeddb-offline-queue.js.map +1 -1
  31. package/dist/indexeddb-transport.cjs +24 -4
  32. package/dist/indexeddb-transport.cjs.map +1 -1
  33. package/dist/indexeddb-transport.d.ts.map +1 -1
  34. package/dist/indexeddb-transport.js +24 -4
  35. package/dist/indexeddb-transport.js.map +1 -1
  36. package/dist/offline-first-transport.cjs +3 -1029
  37. package/dist/offline-first-transport.cjs.map +1 -1
  38. package/dist/offline-first-transport.d.ts.map +1 -1
  39. package/dist/offline-first-transport.js +3 -1029
  40. package/dist/offline-first-transport.js.map +1 -1
  41. package/dist/payload-transforms.cjs +3 -3
  42. package/dist/payload-transforms.cjs.map +1 -1
  43. package/dist/payload-transforms.d.ts.map +1 -1
  44. package/dist/payload-transforms.js +1 -1
  45. package/dist/payload-transforms.js.map +1 -1
  46. package/dist/websocket-integration.cjs +5 -32
  47. package/dist/websocket-integration.cjs.map +1 -1
  48. package/dist/websocket-integration.js +1 -28
  49. package/dist/websocket-integration.js.map +1 -1
  50. package/dist/websocket-transport.cjs +1 -1
  51. package/dist/websocket-transport.cjs.map +1 -1
  52. package/dist/websocket-transport.js +2 -2
  53. package/dist/websocket-transport.js.map +1 -1
  54. package/dist/xhr-integration.cjs +6 -33
  55. package/dist/xhr-integration.cjs.map +1 -1
  56. package/dist/xhr-integration.js +1 -28
  57. package/dist/xhr-integration.js.map +1 -1
  58. package/package.json +5 -2
  59. package/dist/index.cjs.map +0 -1
  60. package/dist/index.js.map +0 -1
@@ -1,4 +1,5 @@
1
- import { applyPayloadTransforms, incrementLoggerMetaCounter, safeJsonCodec, toLevelValue } from "@loggerjs/core";
1
+ import { httpStatusError, incrementLoggerMetaCounter, safeJsonEncoder, toLevelValue } from "@loggerjs/core";
2
+ import { applyPayloadTransforms } from "@loggerjs/core/payload-transforms";
2
3
  //#region src/http-transport.ts
3
4
  const DEFAULT_BEACON_MAX_BYTES = 60 * 1024;
4
5
  const sleep = (delayMs) => delayMs <= 0 ? Promise.resolve() : new Promise((resolve) => setTimeout(resolve, delayMs));
@@ -56,13 +57,30 @@ function remainingEvents(chunks, startIndex) {
56
57
  }
57
58
  return events;
58
59
  }
60
+ function idempotencyKeyHeaders(header) {
61
+ if (!header) return () => void 0;
62
+ new Headers().append(header, "");
63
+ const prefix = Array.from(crypto.getRandomValues(new Uint32Array(2)), (word) => word.toString(36)).join("");
64
+ const keys = /* @__PURE__ */ new WeakMap();
65
+ let next = 0;
66
+ return (events) => {
67
+ const last = events[events.length - 1];
68
+ let key = keys.get(last);
69
+ if (key === void 0) {
70
+ key = `${prefix}-${(next++).toString(36)}`;
71
+ keys.set(last, key);
72
+ }
73
+ return { [header]: key };
74
+ };
75
+ }
59
76
  function browserHttpTransport(options) {
60
- const codec = options.codec ?? safeJsonCodec();
77
+ const codec = options.codec ?? safeJsonEncoder();
61
78
  const beaconCodec = options.beaconCodec ?? codec;
62
79
  const queue = [];
63
80
  const maxBatchSize = options.maxBatchSize ?? 50;
64
81
  if (!Number.isSafeInteger(maxBatchSize) || maxBatchSize <= 0) throw new RangeError("maxBatchSize must be a positive safe integer");
65
82
  const flushIntervalMs = options.flushIntervalMs ?? 2e3;
83
+ const keyHeaders = idempotencyKeyHeaders(options.idempotencyKeyHeader);
66
84
  const maxQueueSize = options.maxQueueSize ?? 1e3;
67
85
  const dropPolicy = options.dropPolicy ?? "drop-oldest";
68
86
  const beaconMaxBytes = options.beaconMaxBytes ?? DEFAULT_BEACON_MAX_BYTES;
@@ -72,11 +90,16 @@ function browserHttpTransport(options) {
72
90
  const offlineReplayMaxDelayMs = options.offlineReplayMaxDelayMs ?? 5e3;
73
91
  const random = options.random ?? Math.random;
74
92
  const fetchFn = options.fetchFn ?? globalThis.fetch?.bind(globalThis);
93
+ const timeoutMs = options.timeoutMs ?? 1e4;
75
94
  let offlineEntrySeq = 0;
95
+ let closed = false;
96
+ let retryNotBefore = 0;
97
+ let replayTimer;
76
98
  let timer;
77
99
  let activeFlush;
78
100
  let flushingBeacon = false;
79
- let replayingOffline = false;
101
+ let replayPromise;
102
+ let heldBatchSize = 0;
80
103
  let lastContext;
81
104
  const headers = (payloadHeaders, contentType = codec.contentType) => ({
82
105
  "content-type": contentType,
@@ -148,13 +171,18 @@ function browserHttpTransport(options) {
148
171
  const chunk = chunks[index];
149
172
  if (!chunk) continue;
150
173
  try {
151
- if (!navigator.sendBeacon(options.url, payloadToBeaconBody(chunk.payload, beaconCodec.contentType))) return { remaining: remainingEvents(chunks, index) };
174
+ if (!navigator.sendBeacon(options.url, payloadToBeaconBody(chunk.payload, beaconCodec.contentType))) {
175
+ incrementLoggerMetaCounter("transport.beacon.rejected", chunk.events.length);
176
+ return { remaining: remainingEvents(chunks, index) };
177
+ }
152
178
  } catch (error) {
179
+ incrementLoggerMetaCounter("transport.beacon.rejected", chunk.events.length);
153
180
  return {
154
181
  remaining: remainingEvents(chunks, index),
155
182
  failure: { error }
156
183
  };
157
184
  }
185
+ incrementLoggerMetaCounter("transport.beacon.accepted", chunk.events.length);
158
186
  }
159
187
  return { remaining: [] };
160
188
  };
@@ -165,9 +193,10 @@ function browserHttpTransport(options) {
165
193
  headers: entry.headers,
166
194
  body: payloadToBody(entry.body),
167
195
  credentials: entry.credentials,
168
- keepalive: entry.keepalive
196
+ keepalive: entry.keepalive,
197
+ signal: timeoutMs > 0 && typeof AbortSignal?.timeout === "function" ? AbortSignal.timeout(timeoutMs) : void 0
169
198
  });
170
- if (!response.ok) throw new Error(`browserHttpTransport failed with status ${response.status}`);
199
+ if (!response.ok) throw httpStatusError("browserHttpTransport", response);
171
200
  };
172
201
  const encodeTransformedPayload = async (batch) => {
173
202
  return applyPayloadTransforms(codec.encode(batch), {
@@ -192,20 +221,33 @@ function browserHttpTransport(options) {
192
221
  incrementLoggerMetaCounter("transport.offline.queued");
193
222
  return true;
194
223
  };
224
+ const isOnline = () => typeof navigator === "undefined" || navigator.onLine !== false;
195
225
  const sendFetchBatch = async (batch) => {
196
- if (batch.length === 0) return;
197
226
  const transformed = await encodeTransformedPayload(batch);
198
- if (offlineQueue && typeof navigator !== "undefined" && navigator.onLine === false) {
199
- await enqueueOfflinePayload(transformed.payload, transformed.headers, transformed.contentType);
200
- return;
227
+ const payloadHeaders = {
228
+ ...transformed.headers,
229
+ ...keyHeaders(batch)
230
+ };
231
+ if (offlineQueue && !isOnline()) {
232
+ await enqueueOfflinePayload(transformed.payload, payloadHeaders, transformed.contentType);
233
+ return "queued";
201
234
  }
202
235
  try {
203
- await sendPayload(createOfflineEntry(transformed.payload, transformed.headers, transformed.contentType));
236
+ await sendPayload(createOfflineEntry(transformed.payload, payloadHeaders, transformed.contentType));
237
+ return "sent";
204
238
  } catch (error) {
205
- if (await enqueueOfflinePayload(transformed.payload, transformed.headers, transformed.contentType)) return;
239
+ noteRetryAfter(error);
240
+ if (await enqueueOfflinePayload(transformed.payload, payloadHeaders, transformed.contentType)) return "queued";
206
241
  throw error;
207
242
  }
208
243
  };
244
+ const retryWaitMs = () => Math.max(0, retryNotBefore - Date.now());
245
+ const noteRetryAfter = (error) => {
246
+ const retryAfterMs = error?.retryAfterMs;
247
+ if (typeof retryAfterMs !== "number" || !(retryAfterMs >= 0)) return void 0;
248
+ retryNotBefore = Math.max(retryNotBefore, Date.now() + retryAfterMs);
249
+ return retryAfterMs;
250
+ };
209
251
  const replayRetryDelay = (attempt) => {
210
252
  const cap = Math.min(offlineReplayMaxDelayMs, offlineReplayBaseDelayMs * 2 ** attempt);
211
253
  return cap <= 0 ? 0 : random() * cap;
@@ -216,33 +258,54 @@ function browserHttpTransport(options) {
216
258
  incrementLoggerMetaCounter("transport.offline.replayed");
217
259
  return;
218
260
  } catch (error) {
219
- if (attempt >= offlineReplayMaxRetries) {
261
+ const retryAfterMs = noteRetryAfter(error);
262
+ if (attempt >= offlineReplayMaxRetries || (retryAfterMs ?? 0) > offlineReplayMaxDelayMs) {
220
263
  incrementLoggerMetaCounter("transport.offline.replay.failed");
221
264
  throw error;
222
265
  }
223
266
  incrementLoggerMetaCounter("transport.offline.retry");
224
- await sleep(replayRetryDelay(attempt));
267
+ await sleep(Math.max(replayRetryDelay(attempt), retryAfterMs ?? 0));
225
268
  }
226
269
  };
227
- const replayOfflineQueue = async () => {
228
- if (!offlineQueue || replayingOffline) return;
229
- replayingOffline = true;
270
+ const replayOfflineQueue = () => {
271
+ if (!offlineQueue) return Promise.resolve();
272
+ replayPromise ??= (async () => {
273
+ try {
274
+ await offlineQueue.replay(sendOfflineEntryWithRetry);
275
+ } finally {
276
+ replayPromise = void 0;
277
+ }
278
+ })();
279
+ return replayPromise;
280
+ };
281
+ const replayOfflineQueueSafely = async (operation) => {
282
+ if (!offlineQueue || !isOnline()) return;
283
+ const waitMs = retryWaitMs();
284
+ if (waitMs > 0) {
285
+ if (!closed && replayTimer === void 0) replayTimer = setTimeout(() => {
286
+ replayTimer = void 0;
287
+ replayOfflineQueueSafely(operation);
288
+ }, waitMs);
289
+ return;
290
+ }
230
291
  try {
231
- await offlineQueue.replay(sendOfflineEntryWithRetry);
232
- } finally {
233
- replayingOffline = false;
292
+ await replayOfflineQueue();
293
+ } catch (error) {
294
+ reportInternalError(error, operation);
234
295
  }
235
296
  };
236
297
  const flushBeaconQueue = () => {
237
298
  if (flushingBeacon || options.transformPayload || queue.length === 0) return;
238
299
  flushingBeacon = true;
239
- let pending = queue.splice(0, queue.length);
300
+ const taken = queue.length;
301
+ let pending = queue.splice(0, taken);
240
302
  try {
241
303
  const result = sendBeaconBatch(pending);
242
304
  pending = result.remaining;
243
305
  if (result.failure) throw result.failure.error;
244
306
  } finally {
245
307
  if (pending.length > 0) queue.unshift(...pending);
308
+ heldBatchSize = Math.max(0, heldBatchSize - (taken - pending.length));
246
309
  flushingBeacon = false;
247
310
  }
248
311
  };
@@ -257,7 +320,11 @@ function browserHttpTransport(options) {
257
320
  }
258
321
  return activeFlush;
259
322
  }
260
- if (queue.length === 0) return Promise.resolve();
323
+ if (queue.length === 0) return preferBeacon ? Promise.resolve() : replayOfflineQueueSafely("replay");
324
+ if (!preferBeacon && retryWaitMs() > 0) {
325
+ schedule();
326
+ return Promise.resolve();
327
+ }
261
328
  clearTimer();
262
329
  let resolve;
263
330
  let reject;
@@ -269,15 +336,21 @@ function browserHttpTransport(options) {
269
336
  (async () => {
270
337
  try {
271
338
  if (preferBeacon) flushBeaconQueue();
339
+ let sent = false;
340
+ let queued = false;
272
341
  while (queue.length > 0) {
273
- const batch = queue.splice(0, maxBatchSize);
342
+ const batch = queue.splice(0, heldBatchSize || maxBatchSize);
343
+ heldBatchSize = 0;
274
344
  try {
275
- await sendFetchBatch(batch);
345
+ if (await sendFetchBatch(batch) === "sent") sent = true;
346
+ else queued = true;
276
347
  } catch (error) {
277
348
  queue.unshift(...batch);
349
+ heldBatchSize = batch.length;
278
350
  throw error;
279
351
  }
280
352
  }
353
+ if (!preferBeacon && sent && !queued) await replayOfflineQueueSafely("replay");
281
354
  resolve();
282
355
  } catch (error) {
283
356
  reject(error);
@@ -289,11 +362,13 @@ function browserHttpTransport(options) {
289
362
  return task;
290
363
  };
291
364
  const schedule = () => {
292
- if (activeFlush || timer || flushIntervalMs <= 0) return;
365
+ if (closed || activeFlush || timer) return;
366
+ const delayMs = Math.max(flushIntervalMs, retryWaitMs());
367
+ if (delayMs <= 0) return;
293
368
  timer = setTimeout(() => {
294
369
  timer = void 0;
295
370
  flush(false).catch((error) => reportInternalError(error, "flush"));
296
- }, flushIntervalMs);
371
+ }, delayMs);
297
372
  };
298
373
  const onPageHide = () => {
299
374
  flush(true).catch((error) => reportInternalError(error, "pagehide-flush"));
@@ -302,25 +377,33 @@ function browserHttpTransport(options) {
302
377
  if (typeof document !== "undefined" && document.visibilityState === "hidden") flush(true).catch((error) => reportInternalError(error, "visibility-flush"));
303
378
  };
304
379
  const onOnline = () => {
305
- replayOfflineQueue().catch((error) => reportInternalError(error, "replay"));
380
+ replayOfflineQueueSafely("replay");
306
381
  };
307
382
  if (options.useBeaconOnPageHide ?? true) {
308
383
  globalThis.addEventListener?.("pagehide", onPageHide);
309
384
  globalThis.addEventListener?.("visibilitychange", onVisibilityChange);
310
385
  }
311
- if (offlineQueue) globalThis.addEventListener?.("online", onOnline);
386
+ if (offlineQueue) {
387
+ globalThis.addEventListener?.("online", onOnline);
388
+ if (options.offlineReplayOnStart ?? true) setTimeout(() => void replayOfflineQueueSafely("startup-replay"), 0);
389
+ }
312
390
  return {
313
391
  name: options.name ?? "browser-http",
314
392
  minLevel: options.minLevel,
315
393
  log(event, context) {
316
394
  if (options.minLevel !== void 0 && event.level < toLevelValue(options.minLevel)) return;
317
395
  lastContext = context;
396
+ if (closed) {
397
+ reportDrop(event, "closed");
398
+ return;
399
+ }
318
400
  if (queue.length >= maxQueueSize) {
319
401
  if (dropPolicy === "drop-newest") {
320
402
  reportDrop(event, "queue-full");
321
403
  return;
322
404
  }
323
405
  const dropped = queue.shift();
406
+ if (heldBatchSize > 0) heldBatchSize -= 1;
324
407
  if (dropped) reportDrop(dropped, "queue-full");
325
408
  }
326
409
  queue.push(event);
@@ -335,7 +418,16 @@ function browserHttpTransport(options) {
335
418
  globalThis.removeEventListener?.("pagehide", onPageHide);
336
419
  globalThis.removeEventListener?.("visibilitychange", onVisibilityChange);
337
420
  globalThis.removeEventListener?.("online", onOnline);
338
- return flush(true);
421
+ closed = true;
422
+ clearTimer();
423
+ if (replayTimer !== void 0) clearTimeout(replayTimer);
424
+ const dropUndelivered = () => {
425
+ for (const event of queue.splice(0)) reportDrop(event, "closed");
426
+ };
427
+ return flush(true).then(dropUndelivered, (error) => {
428
+ dropUndelivered();
429
+ throw error;
430
+ });
339
431
  }
340
432
  };
341
433
  }
@@ -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":";;AAgBA,MAAM,2BAA2B,KAAK;AAEtC,MAAM,SAAS,YACb,WAAW,IAAI,QAAQ,QAAQ,IAAI,IAAI,SAAe,YAAY,WAAW,SAAS,OAAO,CAAC;AAkDhG,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,2BAA2B,2BAA2B;EACtD,2BAA2B,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,SAAS,cAAc;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,mBAAmB;CACvB,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,2BAA2B,mBAAmB;EAC9C,2BAA2B,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,OAAO,uBADS,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,2BAA2B,0BAA0B;EACrD,OAAO;CACT;CAEA,MAAM,iBAAiB,OAAO,UAAsB;EAClD,IAAI,MAAM,WAAW,GAAG;EACxB,MAAM,cAAc,MAAM,yBAAyB,KAAK;EACxD,IAAI,gBAAgB,OAAO,cAAc,eAAe,UAAU,WAAW,OAAO;GAClF,MAAM,sBACJ,YAAY,SACZ,YAAY,SACZ,YAAY,WACd;GACA;EACF;EACA,IAAI;GACF,MAAM,YACJ,mBAAmB,YAAY,SAAS,YAAY,SAAS,YAAY,WAAW,CACtF;EACF,SAAS,OAAO;GACd,IACE,MAAM,sBACJ,YAAY,SACZ,YAAY,SACZ,YAAY,WACd,GAEA;GAEF,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,2BAA2B,4BAA4B;GACvD;EACF,SAAS,OAAO;GACd,IAAI,WAAW,yBAAyB;IACtC,2BAA2B,iCAAiC;IAC5D,MAAM;GACR;GACA,2BAA2B,yBAAyB;GAEpD,MAAM,MAAM,iBAAiB,OAAO,CAAC;EACvC;CAEJ;CAEA,MAAM,qBAAqB,YAAY;EACrC,IAAI,CAAC,gBAAgB,kBAAkB;EACvC,mBAAmB;EACnB,IAAI;GACF,MAAM,aAAa,OAAO,yBAAyB;EACrD,UAAU;GACR,mBAAmB;EACrB;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,GAAG,OAAO,QAAQ,QAAQ;EAC/C,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,OAAO,MAAM,SAAS,GAAG;KACvB,MAAM,QAAQ,MAAM,OAAO,GAAG,YAAY;KAC1C,IAAI;MAEF,MAAM,eAAe,KAAK;KAC5B,SAAS,OAAO;MACd,MAAM,QAAQ,GAAG,KAAK;MACtB,MAAM;KACR;IACF;IACA,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,WAAW,mBAAmB,UAAU,QAAQ;CAElE,OAAO;EACL,MAAM,QAAQ,QAAQ;EACtB,UAAU,QAAQ;EAClB,IAAI,OAAO,SAAS;GAClB,IAAI,QAAQ,aAAa,KAAA,KAAa,MAAM,QAAQ,aAAa,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"}
1
+ {"version":3,"file":"http-transport.js","names":[],"sources":["../src/http-transport.ts"],"sourcesContent":["import {\n httpStatusError,\n incrementLoggerMetaCounter,\n safeJsonEncoder,\n toLevelValue,\n type Codec,\n type EncodedPayload,\n type LogEvent,\n type LoggerLevel,\n type PayloadTransform,\n type Transport,\n type TransportContext,\n type HttpStatusError,\n} from \"@loggerjs/core\";\nimport { applyPayloadTransforms } from \"@loggerjs/core/payload-transforms\";\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 * Abort a Fetch delivery after this many milliseconds. Browsers never time\n * out a request on their own, so one stalled request would otherwise hold\n * every later flush and close(). A timed-out batch falls back to the\n * offline queue when one is configured. Set to 0 to disable. Defaults to\n * 10000.\n */\n timeoutMs?: number;\n /**\n * Header that carries an idempotency key for each Fetch request, for\n * example \"Idempotency-Key\". Every resend of a batch repeats its key, and\n * offline-queue entries store it, so the collector can drop duplicates.\n * Keys start with a random per-transport prefix, so senders never share\n * one. Off by default because a cross-origin collector must allow the\n * header in Access-Control-Allow-Headers. Beacon requests cannot carry it.\n */\n idempotencyKeyHeader?: string;\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\n// Keys are a random per-transport prefix, which keeps senders apart, and a\n// batch number. A batch's key is stored under its last event: resends of a\n// held batch keep that event, because a full queue and Beacon delivery only\n// remove events from the front. Mirrors @loggerjs/node.\nfunction idempotencyKeyHeaders(\n header: string | undefined,\n): (events: readonly LogEvent[]) => Record<string, string> | undefined {\n if (!header) return () => undefined;\n // Throws a TypeError for an invalid header name now instead of on every send.\n new Headers().append(header, \"\");\n const prefix = Array.from(crypto.getRandomValues(new Uint32Array(2)), (word) =>\n word.toString(36),\n ).join(\"\");\n const keys = new WeakMap<LogEvent, string>();\n let next = 0;\n return (events) => {\n const last = events[events.length - 1]!;\n let key = keys.get(last);\n if (key === undefined) {\n key = `${prefix}-${(next++).toString(36)}`;\n keys.set(last, key);\n }\n return { [header]: key };\n };\n}\n\nexport function browserHttpTransport(options: BrowserHttpTransportOptions): Transport {\n const codec = options.codec ?? safeJsonEncoder();\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 keyHeaders = idempotencyKeyHeaders(options.idempotencyKeyHeader);\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 const timeoutMs = options.timeoutMs ?? 10_000;\n let offlineEntrySeq = 0;\n let closed = false;\n // Set from a Retry-After response: automatic sends and replays wait until then.\n let retryNotBefore = 0;\n let replayTimer: ReturnType<typeof setTimeout> | undefined;\n let timer: ReturnType<typeof setTimeout> | undefined;\n let activeFlush: Promise<void> | undefined;\n let flushingBeacon = false;\n let replayPromise: Promise<void> | undefined;\n // Events at the front of the queue that a failed send handed back. They are\n // resent as the same batch, so its idempotency key repeats.\n let heldBatchSize = 0;\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) {\n incrementLoggerMetaCounter(\"transport.beacon.rejected\", chunk.events.length);\n return { remaining: remainingEvents(chunks, index) };\n }\n } catch (error) {\n incrementLoggerMetaCounter(\"transport.beacon.rejected\", chunk.events.length);\n return { remaining: remainingEvents(chunks, index), failure: { error } };\n }\n // Accepted only means the browser queued the request: nothing reports\n // whether it reaches the collector, so these events are never dropped.\n incrementLoggerMetaCounter(\"transport.beacon.accepted\", chunk.events.length);\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 signal:\n timeoutMs > 0 && typeof AbortSignal?.timeout === \"function\"\n ? AbortSignal.timeout(timeoutMs)\n : undefined,\n });\n if (!response.ok) throw httpStatusError(\"browserHttpTransport\", response);\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 // The key travels in the stored headers, so replays repeat it.\n const payloadHeaders = { ...transformed.headers, ...keyHeaders(batch) };\n if (offlineQueue && !isOnline()) {\n await enqueueOfflinePayload(transformed.payload, payloadHeaders, transformed.contentType);\n return \"queued\";\n }\n try {\n await sendPayload(\n createOfflineEntry(transformed.payload, payloadHeaders, transformed.contentType),\n );\n return \"sent\";\n } catch (error) {\n noteRetryAfter(error);\n if (\n await enqueueOfflinePayload(transformed.payload, payloadHeaders, transformed.contentType)\n ) {\n return \"queued\";\n }\n throw error;\n }\n };\n\n const retryWaitMs = () => Math.max(0, retryNotBefore - Date.now());\n\n // Cross-origin collectors must expose Retry-After through\n // Access-Control-Expose-Headers for the browser to read it.\n const noteRetryAfter = (error: unknown): number | undefined => {\n const retryAfterMs = (error as Partial<HttpStatusError> | null)?.retryAfterMs;\n if (typeof retryAfterMs !== \"number\" || !(retryAfterMs >= 0)) return undefined;\n retryNotBefore = Math.max(retryNotBefore, Date.now() + retryAfterMs);\n return retryAfterMs;\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 const retryAfterMs = noteRetryAfter(error);\n // A longer server-requested wait ends this replay round; the entry\n // stays queued and replays after the wait.\n if (attempt >= offlineReplayMaxRetries || (retryAfterMs ?? 0) > offlineReplayMaxDelayMs) {\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(Math.max(replayRetryDelay(attempt), retryAfterMs ?? 0));\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): Promise<void> => {\n if (!offlineQueue || !isOnline()) return;\n const waitMs = retryWaitMs();\n if (waitMs > 0) {\n if (!closed && replayTimer === undefined) {\n replayTimer = setTimeout(() => {\n replayTimer = undefined;\n void replayOfflineQueueSafely(operation);\n }, waitMs);\n }\n return;\n }\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 const taken = queue.length;\n let pending = queue.splice(0, taken);\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 heldBatchSize = Math.max(0, heldBatchSize - (taken - pending.length));\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 // While the collector's Retry-After runs, keep events queued; the\n // scheduler sends them when it ends. Page-exit Beacon flushes still go out.\n if (!preferBeacon && retryWaitMs() > 0) {\n schedule();\n return Promise.resolve();\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, heldBatchSize || maxBatchSize);\n heldBatchSize = 0;\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 heldBatchSize = batch.length;\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 (closed || activeFlush || timer) return;\n const delayMs = Math.max(flushIntervalMs, retryWaitMs());\n if (delayMs <= 0) return;\n timer = setTimeout(() => {\n timer = undefined;\n void flush(false).catch((error: unknown) => reportInternalError(error, \"flush\"));\n }, delayMs);\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 replayOfflineQueueSafely(\"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 (closed) {\n reportDrop(event, \"closed\");\n return;\n }\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 (heldBatchSize > 0) heldBatchSize -= 1;\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 closed = true;\n clearTimer();\n if (replayTimer !== undefined) clearTimeout(replayTimer);\n // Events the final flush could not deliver or store are accounted for\n // as dropped instead of disappearing with the transport.\n const dropUndelivered = () => {\n for (const event of queue.splice(0)) reportDrop(event, \"closed\");\n };\n return flush(true).then(dropUndelivered, (error: unknown) => {\n dropUndelivered();\n throw error;\n });\n },\n };\n}\n"],"mappings":"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"}