@loggerjs/browser 0.6.0 → 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 (59) hide show
  1. package/README.md +2 -2
  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 +90 -16
  17. package/dist/http-transport.cjs.map +1 -1
  18. package/dist/http-transport.d.ts +17 -0
  19. package/dist/http-transport.d.ts.map +1 -1
  20. package/dist/http-transport.js +90 -16
  21. package/dist/http-transport.js.map +1 -1
  22. package/dist/index.cjs +58 -4167
  23. package/dist/index.d.ts +243 -17
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +25 -4135
  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 +2 -1029
  37. package/dist/offline-first-transport.cjs.map +1 -1
  38. package/dist/offline-first-transport.js +2 -1029
  39. package/dist/offline-first-transport.js.map +1 -1
  40. package/dist/payload-transforms.cjs +3 -3
  41. package/dist/payload-transforms.cjs.map +1 -1
  42. package/dist/payload-transforms.d.ts.map +1 -1
  43. package/dist/payload-transforms.js +1 -1
  44. package/dist/payload-transforms.js.map +1 -1
  45. package/dist/websocket-integration.cjs +5 -32
  46. package/dist/websocket-integration.cjs.map +1 -1
  47. package/dist/websocket-integration.js +1 -28
  48. package/dist/websocket-integration.js.map +1 -1
  49. package/dist/websocket-transport.cjs +1 -1
  50. package/dist/websocket-transport.cjs.map +1 -1
  51. package/dist/websocket-transport.js +2 -2
  52. package/dist/websocket-transport.js.map +1 -1
  53. package/dist/xhr-integration.cjs +6 -33
  54. package/dist/xhr-integration.cjs.map +1 -1
  55. package/dist/xhr-integration.js +1 -28
  56. package/dist/xhr-integration.js.map +1 -1
  57. package/package.json +5 -2
  58. package/dist/index.cjs.map +0 -1
  59. package/dist/index.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,4137 +1,27 @@
1
- import { addContextProvider, applyPayloadTransforms, encodedPayloadToUint8Array, formatBaggage, formatTraceparent, incrementLoggerMetaCounter, normalizeValue, parseBaggage, parseTraceparent, recordToEvent, retryTransport, safeJsonCodec, safeJsonStringify, toLevelValue } from "@loggerjs/core";
1
+ import { browserBroadcastChannelTransport } from "./broadcast-channel-transport.js";
2
+ import { browserHttpTransport, memoryBrowserHttpOfflineQueue } from "./http-transport.js";
3
+ import { browserCompressionPayloadTransform } from "./payload-transforms.js";
4
+ import { browserServiceWorkerTransport } from "./service-worker-transport.js";
5
+ import { browserWebSocketTransport } from "./websocket-transport.js";
6
+ import { indexedDbBrowserHttpOfflineQueue } from "./indexeddb-offline-queue.js";
7
+ import { indexedDbTransport } from "./indexeddb-transport.js";
8
+ import { offlineFirstTransport } from "./offline-first-transport.js";
9
+ import { createLogZipBlob, downloadBlob, exportLogsToZip } from "./zip-export.js";
10
+ import { captureConsoleIntegration } from "./console-integration.js";
11
+ import { browserContextPropagationIntegration } from "./context-propagation-integration.js";
12
+ import { captureBrowserErrorsIntegration } from "./error-integration.js";
13
+ import { captureFetchIntegration } from "./fetch-integration.js";
14
+ import { captureXHRIntegration } from "./xhr-integration.js";
15
+ import { captureFrameworkErrorsIntegration } from "./framework-error-integration.js";
16
+ import { nextRouterIntegration, nuxtRouterIntegration, reactRouterIntegration, vueRouterIntegration } from "./framework-router-integrations.js";
17
+ import { captureReportingIntegration } from "./reporting-integration.js";
18
+ import { captureRouterIntegration } from "./router-integration.js";
19
+ import { captureRuntimeHostIntegration } from "./runtime-host-integration.js";
20
+ import { captureServiceWorkerIntegration } from "./service-worker-integration.js";
21
+ import { captureUserActionsIntegration } from "./user-action-integration.js";
22
+ import { captureWebSocketIntegration } from "./websocket-integration.js";
23
+ import { captureWebVitalsIntegration } from "./web-vitals-integration.js";
24
+ import { capturePerformanceIntegration, normalizeBrowserPerformanceEntry } from "./performance-integration.js";
25
+ import { pageLifecycleIntegration } from "./page-lifecycle.js";
2
26
  export * from "@loggerjs/core";
3
- //#region src/broadcast-channel-transport.ts
4
- let sourceSequence$1 = 0;
5
- function createSourceId$1() {
6
- sourceSequence$1 += 1;
7
- return `loggerjs-${Date.now().toString(36)}-${sourceSequence$1.toString(36)}`;
8
- }
9
- function defaultChannelFactory(channelName) {
10
- if (typeof BroadcastChannel === "undefined") throw new Error("BroadcastChannel is not available");
11
- return new BroadcastChannel(channelName);
12
- }
13
- function defaultMapEvent$1(event, context) {
14
- return {
15
- type: "loggerjs.event",
16
- source: context.source,
17
- event
18
- };
19
- }
20
- function defaultMapBatch$1(events, context) {
21
- return {
22
- type: "loggerjs.batch",
23
- source: context.source,
24
- events
25
- };
26
- }
27
- function browserBroadcastChannelTransport(options) {
28
- const transportName = options.name ?? "browser-broadcast-channel";
29
- const source = options.source ?? createSourceId$1();
30
- const mapContext = {
31
- channelName: options.channelName,
32
- source
33
- };
34
- const mapEvent = options.mapEvent ?? defaultMapEvent$1;
35
- const mapBatch = options.mapBatch ?? defaultMapBatch$1;
36
- const channelFactory = options.channelFactory ?? defaultChannelFactory;
37
- const closeChannelOnClose = options.closeChannelOnClose ?? true;
38
- let channel;
39
- let closed = false;
40
- const reportError = (error, context, detail) => {
41
- if (detail.droppedEvents > 0) {
42
- incrementLoggerMetaCounter("transport.dropped", detail.droppedEvents);
43
- incrementLoggerMetaCounter(`transport.dropped.${detail.operation}`, detail.droppedEvents);
44
- }
45
- try {
46
- options.onError?.(error, detail);
47
- } catch (onErrorError) {
48
- context?.reportInternalError(onErrorError, {
49
- operation: "on-error",
50
- phase: "transport",
51
- transport: transportName
52
- });
53
- }
54
- context?.reportInternalError(error, {
55
- operation: detail.operation,
56
- phase: "transport",
57
- transport: transportName
58
- });
59
- };
60
- const getChannel = (context) => {
61
- if (closed) return void 0;
62
- if (channel) return channel;
63
- try {
64
- channel = channelFactory(options.channelName);
65
- return channel;
66
- } catch (error) {
67
- reportError(error, context, {
68
- droppedEvents: 1,
69
- operation: "create-channel"
70
- });
71
- return;
72
- }
73
- };
74
- const postMessage = (message, context, droppedEvents) => {
75
- const currentChannel = getChannel(context);
76
- if (!currentChannel) return;
77
- try {
78
- currentChannel.postMessage(message);
79
- } catch (error) {
80
- reportError(error, context, {
81
- droppedEvents,
82
- operation: "post-message"
83
- });
84
- }
85
- };
86
- return {
87
- name: transportName,
88
- minLevel: options.minLevel,
89
- log(event, context) {
90
- postMessage(mapEvent(event, mapContext), context, 1);
91
- },
92
- logBatch(events, context) {
93
- if (events.length === 0) return;
94
- postMessage(mapBatch(events, mapContext), context, events.length);
95
- },
96
- async close() {
97
- closed = true;
98
- if (!channel || !closeChannelOnClose) return;
99
- try {
100
- channel.close?.();
101
- } catch (error) {
102
- reportError(error, void 0, {
103
- droppedEvents: 0,
104
- operation: "close-channel"
105
- });
106
- } finally {
107
- channel = void 0;
108
- }
109
- }
110
- };
111
- }
112
- //#endregion
113
- //#region src/http-transport.ts
114
- const DEFAULT_BEACON_MAX_BYTES = 60 * 1024;
115
- const sleep = (delayMs) => delayMs <= 0 ? Promise.resolve() : new Promise((resolve) => setTimeout(resolve, delayMs));
116
- function memoryBrowserHttpOfflineQueue(options = {}) {
117
- const entries = [];
118
- const maxEntries = options.maxEntries ?? 1e3;
119
- const dropPolicy = options.dropPolicy ?? "drop-oldest";
120
- const drop = (entry, reason) => {
121
- incrementLoggerMetaCounter("transport.offline.dropped");
122
- incrementLoggerMetaCounter(`transport.offline.dropped.${reason}`);
123
- options.onDrop?.(entry, reason);
124
- };
125
- return {
126
- enqueue(entry) {
127
- if (entries.length >= maxEntries) {
128
- if (dropPolicy === "drop-newest") {
129
- drop(entry, "queue-full");
130
- return;
131
- }
132
- const dropped = entries.shift();
133
- if (dropped) drop(dropped, "queue-full");
134
- }
135
- entries.push(entry);
136
- },
137
- async replay(send) {
138
- while (entries.length > 0) {
139
- const entry = entries[0];
140
- if (!entry) return;
141
- await send(entry);
142
- entries.shift();
143
- }
144
- },
145
- size() {
146
- return entries.length;
147
- }
148
- };
149
- }
150
- function payloadToBody(payload) {
151
- if (typeof payload === "string") return payload;
152
- return Uint8Array.from(payload);
153
- }
154
- function payloadByteLength$1(payload) {
155
- if (typeof payload !== "string") return payload.byteLength;
156
- if (typeof TextEncoder !== "undefined") return new TextEncoder().encode(payload).byteLength;
157
- return new Blob([payload]).size;
158
- }
159
- function payloadToBeaconBody(payload, contentType) {
160
- return new Blob([typeof payload === "string" ? payload : Uint8Array.from(payload)], { type: contentType });
161
- }
162
- function remainingEvents(chunks, startIndex) {
163
- const events = [];
164
- for (let index = startIndex; index < chunks.length; index++) {
165
- const chunk = chunks[index];
166
- if (chunk) events.push(...chunk.events);
167
- }
168
- return events;
169
- }
170
- function browserHttpTransport(options) {
171
- const codec = options.codec ?? safeJsonCodec();
172
- const beaconCodec = options.beaconCodec ?? codec;
173
- const queue = [];
174
- const maxBatchSize = options.maxBatchSize ?? 50;
175
- if (!Number.isSafeInteger(maxBatchSize) || maxBatchSize <= 0) throw new RangeError("maxBatchSize must be a positive safe integer");
176
- const flushIntervalMs = options.flushIntervalMs ?? 2e3;
177
- const maxQueueSize = options.maxQueueSize ?? 1e3;
178
- const dropPolicy = options.dropPolicy ?? "drop-oldest";
179
- const beaconMaxBytes = options.beaconMaxBytes ?? DEFAULT_BEACON_MAX_BYTES;
180
- const offlineQueue = options.offlineQueue;
181
- const offlineReplayMaxRetries = options.offlineReplayMaxRetries ?? 3;
182
- const offlineReplayBaseDelayMs = options.offlineReplayBaseDelayMs ?? 250;
183
- const offlineReplayMaxDelayMs = options.offlineReplayMaxDelayMs ?? 5e3;
184
- const random = options.random ?? Math.random;
185
- const fetchFn = options.fetchFn ?? globalThis.fetch?.bind(globalThis);
186
- let offlineEntrySeq = 0;
187
- let timer;
188
- let activeFlush;
189
- let flushingBeacon = false;
190
- let replayPromise;
191
- let lastContext;
192
- const headers = (payloadHeaders, contentType = codec.contentType) => ({
193
- "content-type": contentType,
194
- ...payloadHeaders,
195
- ...options.headers
196
- });
197
- const clearTimer = () => {
198
- if (timer) clearTimeout(timer);
199
- timer = void 0;
200
- };
201
- const reportDrop = (event, reason) => {
202
- incrementLoggerMetaCounter("transport.dropped");
203
- incrementLoggerMetaCounter(`transport.dropped.${reason}`);
204
- options.onDrop?.(event, reason);
205
- };
206
- const reportInternalError = (error, operation) => {
207
- lastContext?.reportInternalError(error, {
208
- phase: "transport",
209
- transport: options.name ?? "browser-http",
210
- operation
211
- });
212
- };
213
- const createBeaconChunks = (batch) => {
214
- const chunks = [];
215
- const oversized = [];
216
- let currentEvents = [];
217
- let currentPayload;
218
- for (const event of batch) {
219
- if (currentEvents.length === maxBatchSize && currentPayload !== void 0) {
220
- chunks.push({
221
- events: currentEvents,
222
- payload: currentPayload
223
- });
224
- currentEvents = [];
225
- currentPayload = void 0;
226
- }
227
- const candidateEvents = [...currentEvents, event];
228
- const candidatePayload = beaconCodec.encode(candidateEvents);
229
- if (payloadByteLength$1(candidatePayload) <= beaconMaxBytes) {
230
- currentEvents = candidateEvents;
231
- currentPayload = candidatePayload;
232
- continue;
233
- }
234
- if (currentEvents.length > 0 && currentPayload !== void 0) chunks.push({
235
- events: currentEvents,
236
- payload: currentPayload
237
- });
238
- const singlePayload = beaconCodec.encode([event]);
239
- if (payloadByteLength$1(singlePayload) <= beaconMaxBytes) {
240
- currentEvents = [event];
241
- currentPayload = singlePayload;
242
- } else {
243
- currentEvents = [];
244
- currentPayload = void 0;
245
- oversized.push(event);
246
- }
247
- }
248
- if (currentEvents.length > 0 && currentPayload !== void 0) chunks.push({
249
- events: currentEvents,
250
- payload: currentPayload
251
- });
252
- for (const event of oversized) reportDrop(event, "beacon-too-large");
253
- return chunks;
254
- };
255
- const sendBeaconBatch = (batch) => {
256
- if (typeof navigator === "undefined" || !navigator.sendBeacon) return { remaining: batch };
257
- const chunks = createBeaconChunks(batch);
258
- for (let index = 0; index < chunks.length; index++) {
259
- const chunk = chunks[index];
260
- if (!chunk) continue;
261
- try {
262
- if (!navigator.sendBeacon(options.url, payloadToBeaconBody(chunk.payload, beaconCodec.contentType))) return { remaining: remainingEvents(chunks, index) };
263
- } catch (error) {
264
- return {
265
- remaining: remainingEvents(chunks, index),
266
- failure: { error }
267
- };
268
- }
269
- }
270
- return { remaining: [] };
271
- };
272
- const sendPayload = async (entry) => {
273
- if (!fetchFn) throw new Error("fetch is not available for browserHttpTransport");
274
- const response = await fetchFn(entry.url, {
275
- method: entry.method,
276
- headers: entry.headers,
277
- body: payloadToBody(entry.body),
278
- credentials: entry.credentials,
279
- keepalive: entry.keepalive
280
- });
281
- if (!response.ok) throw new Error(`browserHttpTransport failed with status ${response.status}`);
282
- };
283
- const encodeTransformedPayload = async (batch) => {
284
- return applyPayloadTransforms(codec.encode(batch), {
285
- contentType: codec.contentType,
286
- events: batch,
287
- transport: options.name ?? "browser-http"
288
- }, options.transformPayload);
289
- };
290
- const createOfflineEntry = (payload, payloadHeaders, contentType) => ({
291
- id: `${Date.now().toString(36)}-${(offlineEntrySeq++).toString(36)}`,
292
- url: options.url,
293
- method: options.method ?? "POST",
294
- headers: headers(payloadHeaders, contentType),
295
- body: payload,
296
- credentials: options.credentials,
297
- keepalive: options.keepalive ?? true,
298
- createdAt: Date.now()
299
- });
300
- const enqueueOfflinePayload = async (payload, payloadHeaders, contentType) => {
301
- if (!offlineQueue) return false;
302
- await offlineQueue.enqueue(createOfflineEntry(payload, payloadHeaders, contentType));
303
- incrementLoggerMetaCounter("transport.offline.queued");
304
- return true;
305
- };
306
- const isOnline = () => typeof navigator === "undefined" || navigator.onLine !== false;
307
- const sendFetchBatch = async (batch) => {
308
- const transformed = await encodeTransformedPayload(batch);
309
- if (offlineQueue && !isOnline()) {
310
- await enqueueOfflinePayload(transformed.payload, transformed.headers, transformed.contentType);
311
- return "queued";
312
- }
313
- try {
314
- await sendPayload(createOfflineEntry(transformed.payload, transformed.headers, transformed.contentType));
315
- return "sent";
316
- } catch (error) {
317
- if (await enqueueOfflinePayload(transformed.payload, transformed.headers, transformed.contentType)) return "queued";
318
- throw error;
319
- }
320
- };
321
- const replayRetryDelay = (attempt) => {
322
- const cap = Math.min(offlineReplayMaxDelayMs, offlineReplayBaseDelayMs * 2 ** attempt);
323
- return cap <= 0 ? 0 : random() * cap;
324
- };
325
- const sendOfflineEntryWithRetry = async (entry) => {
326
- for (let attempt = 0;; attempt++) try {
327
- await sendPayload(entry);
328
- incrementLoggerMetaCounter("transport.offline.replayed");
329
- return;
330
- } catch (error) {
331
- if (attempt >= offlineReplayMaxRetries) {
332
- incrementLoggerMetaCounter("transport.offline.replay.failed");
333
- throw error;
334
- }
335
- incrementLoggerMetaCounter("transport.offline.retry");
336
- await sleep(replayRetryDelay(attempt));
337
- }
338
- };
339
- const replayOfflineQueue = () => {
340
- if (!offlineQueue) return Promise.resolve();
341
- replayPromise ??= (async () => {
342
- try {
343
- await offlineQueue.replay(sendOfflineEntryWithRetry);
344
- } finally {
345
- replayPromise = void 0;
346
- }
347
- })();
348
- return replayPromise;
349
- };
350
- const replayOfflineQueueSafely = async (operation) => {
351
- if (!offlineQueue || !isOnline()) return;
352
- try {
353
- await replayOfflineQueue();
354
- } catch (error) {
355
- reportInternalError(error, operation);
356
- }
357
- };
358
- const flushBeaconQueue = () => {
359
- if (flushingBeacon || options.transformPayload || queue.length === 0) return;
360
- flushingBeacon = true;
361
- let pending = queue.splice(0, queue.length);
362
- try {
363
- const result = sendBeaconBatch(pending);
364
- pending = result.remaining;
365
- if (result.failure) throw result.failure.error;
366
- } finally {
367
- if (pending.length > 0) queue.unshift(...pending);
368
- flushingBeacon = false;
369
- }
370
- };
371
- const flush = (preferBeacon = false) => {
372
- if (activeFlush) {
373
- if (preferBeacon) try {
374
- flushBeaconQueue();
375
- } catch (error) {
376
- return activeFlush.then(() => {
377
- throw error;
378
- });
379
- }
380
- return activeFlush;
381
- }
382
- if (queue.length === 0) return preferBeacon ? Promise.resolve() : replayOfflineQueueSafely("replay");
383
- clearTimer();
384
- let resolve;
385
- let reject;
386
- const task = new Promise((onResolve, onReject) => {
387
- resolve = onResolve;
388
- reject = onReject;
389
- });
390
- activeFlush = task;
391
- (async () => {
392
- try {
393
- if (preferBeacon) flushBeaconQueue();
394
- let sent = false;
395
- let queued = false;
396
- while (queue.length > 0) {
397
- const batch = queue.splice(0, maxBatchSize);
398
- try {
399
- if (await sendFetchBatch(batch) === "sent") sent = true;
400
- else queued = true;
401
- } catch (error) {
402
- queue.unshift(...batch);
403
- throw error;
404
- }
405
- }
406
- if (!preferBeacon && sent && !queued) await replayOfflineQueueSafely("replay");
407
- resolve();
408
- } catch (error) {
409
- reject(error);
410
- } finally {
411
- activeFlush = void 0;
412
- if (queue.length > 0) schedule();
413
- }
414
- })();
415
- return task;
416
- };
417
- const schedule = () => {
418
- if (activeFlush || timer || flushIntervalMs <= 0) return;
419
- timer = setTimeout(() => {
420
- timer = void 0;
421
- flush(false).catch((error) => reportInternalError(error, "flush"));
422
- }, flushIntervalMs);
423
- };
424
- const onPageHide = () => {
425
- flush(true).catch((error) => reportInternalError(error, "pagehide-flush"));
426
- };
427
- const onVisibilityChange = () => {
428
- if (typeof document !== "undefined" && document.visibilityState === "hidden") flush(true).catch((error) => reportInternalError(error, "visibility-flush"));
429
- };
430
- const onOnline = () => {
431
- replayOfflineQueue().catch((error) => reportInternalError(error, "replay"));
432
- };
433
- if (options.useBeaconOnPageHide ?? true) {
434
- globalThis.addEventListener?.("pagehide", onPageHide);
435
- globalThis.addEventListener?.("visibilitychange", onVisibilityChange);
436
- }
437
- if (offlineQueue) {
438
- globalThis.addEventListener?.("online", onOnline);
439
- if (options.offlineReplayOnStart ?? true) setTimeout(() => void replayOfflineQueueSafely("startup-replay"), 0);
440
- }
441
- return {
442
- name: options.name ?? "browser-http",
443
- minLevel: options.minLevel,
444
- log(event, context) {
445
- if (options.minLevel !== void 0 && event.level < toLevelValue(options.minLevel)) return;
446
- lastContext = context;
447
- if (queue.length >= maxQueueSize) {
448
- if (dropPolicy === "drop-newest") {
449
- reportDrop(event, "queue-full");
450
- return;
451
- }
452
- const dropped = queue.shift();
453
- if (dropped) reportDrop(dropped, "queue-full");
454
- }
455
- queue.push(event);
456
- if (activeFlush) return;
457
- if (queue.length >= maxBatchSize) flush(false).catch((error) => reportInternalError(error, "flush"));
458
- else schedule();
459
- },
460
- flush() {
461
- return flush(false);
462
- },
463
- close() {
464
- globalThis.removeEventListener?.("pagehide", onPageHide);
465
- globalThis.removeEventListener?.("visibilitychange", onVisibilityChange);
466
- globalThis.removeEventListener?.("online", onOnline);
467
- return flush(true);
468
- }
469
- };
470
- }
471
- //#endregion
472
- //#region src/payload-transforms.ts
473
- async function compressWithStream(payload, format, streamFactory) {
474
- const stream = (streamFactory ?? ((nextFormat) => {
475
- if (typeof CompressionStream === "undefined") throw new Error("CompressionStream is not available in this browser");
476
- return new CompressionStream(nextFormat);
477
- }))(format);
478
- const writer = stream.writable.getWriter();
479
- await writer.write(Uint8Array.from(payload));
480
- await writer.close();
481
- return new Uint8Array(await new Response(stream.readable).arrayBuffer());
482
- }
483
- function browserCompressionPayloadTransform(options = {}) {
484
- const format = options.format ?? "gzip";
485
- return async (payload, context) => ({
486
- payload: await (options.compress ? options.compress(encodedPayloadToUint8Array(payload), {
487
- ...context,
488
- format
489
- }) : compressWithStream(encodedPayloadToUint8Array(payload), format, options.streamFactory)),
490
- headers: {
491
- "content-encoding": format,
492
- ...options.headers
493
- }
494
- });
495
- }
496
- //#endregion
497
- //#region src/service-worker-transport.ts
498
- let sourceSequence = 0;
499
- function createSourceId() {
500
- sourceSequence += 1;
501
- return `loggerjs-${Date.now().toString(36)}-${sourceSequence.toString(36)}`;
502
- }
503
- function defaultMapEvent(event, context) {
504
- return {
505
- type: "loggerjs.event",
506
- source: context.source,
507
- event
508
- };
509
- }
510
- function defaultMapBatch(events, context) {
511
- return {
512
- type: "loggerjs.batch",
513
- source: context.source,
514
- events
515
- };
516
- }
517
- function serviceWorkerContainer() {
518
- return typeof navigator === "undefined" ? void 0 : navigator.serviceWorker;
519
- }
520
- function workerFromRegistration(registration) {
521
- return registration.active ?? registration.waiting ?? registration.installing ?? void 0;
522
- }
523
- function browserServiceWorkerTransport(options = {}) {
524
- const transportName = options.name ?? "browser-service-worker";
525
- const target = options.target ?? "controller";
526
- const source = options.source ?? createSourceId();
527
- const maxQueueSize = options.maxQueueSize ?? 1e3;
528
- const dropPolicy = options.dropPolicy ?? "drop-oldest";
529
- const mapContext = {
530
- source,
531
- target
532
- };
533
- const mapEvent = options.mapEvent ?? defaultMapEvent;
534
- const mapBatch = options.mapBatch ?? defaultMapBatch;
535
- const queue = [];
536
- let readyWorker;
537
- let readyPromise;
538
- let lastContext;
539
- const reportError = (error, context, operation, droppedEvents = 0) => {
540
- try {
541
- options.onError?.(error, {
542
- droppedEvents,
543
- operation
544
- });
545
- } catch (onErrorError) {
546
- context?.reportInternalError(onErrorError, {
547
- operation: "on-error",
548
- phase: "transport",
549
- transport: transportName
550
- });
551
- }
552
- context?.reportInternalError(error, {
553
- operation,
554
- phase: "transport",
555
- transport: transportName
556
- });
557
- };
558
- const reportDrop = (item, reason) => {
559
- incrementLoggerMetaCounter("transport.dropped", item.events.length);
560
- incrementLoggerMetaCounter(`transport.dropped.${reason}`, item.events.length);
561
- for (const event of item.events) options.onDrop?.(event, reason);
562
- };
563
- const currentWorker = () => {
564
- const container = options.serviceWorker ?? serviceWorkerContainer();
565
- if (!container) return void 0;
566
- if (target === "controller") return container.controller ?? void 0;
567
- return readyWorker;
568
- };
569
- const post = (worker, item, context) => {
570
- try {
571
- worker.postMessage(item.message, options.transfer?.(item.message));
572
- return true;
573
- } catch (error) {
574
- reportDrop(item, "post-message");
575
- reportError(error, context, "post-message", item.events.length);
576
- return false;
577
- }
578
- };
579
- const drain = (context = lastContext) => {
580
- const worker = currentWorker();
581
- if (!worker || !context) return;
582
- while (queue.length > 0) {
583
- const item = queue.shift();
584
- if (!item) return;
585
- post(worker, item, context);
586
- }
587
- };
588
- const dropQueued = (reason) => {
589
- const pending = queue.splice(0);
590
- for (const item of pending) reportDrop(item, reason);
591
- return pending.reduce((total, item) => total + item.events.length, 0);
592
- };
593
- const waitForReady = (context) => {
594
- if (currentWorker()) return Promise.resolve();
595
- if (target !== "ready") return Promise.reject(/* @__PURE__ */ new Error("service worker controller is not available"));
596
- if (readyPromise) return readyPromise;
597
- const container = options.serviceWorker ?? serviceWorkerContainer();
598
- if (!container?.ready) {
599
- const error = /* @__PURE__ */ new Error("serviceWorker.ready is not available");
600
- reportError(error, context, "ready", dropQueued("ready"));
601
- return Promise.reject(error);
602
- }
603
- readyPromise = container.ready.then((registration) => {
604
- readyWorker = workerFromRegistration(registration);
605
- if (!readyWorker) throw new Error("service worker registration has no active worker");
606
- drain(context ?? lastContext);
607
- }).catch((error) => {
608
- readyPromise = void 0;
609
- const droppedEvents = dropQueued("ready");
610
- reportError(error, context ?? lastContext, "ready", droppedEvents);
611
- throw error;
612
- });
613
- return readyPromise;
614
- };
615
- const startReady = (context) => {
616
- waitForReady(context).catch(() => {});
617
- };
618
- const enqueue = (item) => {
619
- if (queue.length >= maxQueueSize) {
620
- if (dropPolicy === "drop-newest") {
621
- reportDrop(item, "queue-full");
622
- return false;
623
- }
624
- const dropped = queue.shift();
625
- if (dropped) reportDrop(dropped, "queue-full");
626
- }
627
- queue.push(item);
628
- return true;
629
- };
630
- const send = (item, context) => {
631
- lastContext = context;
632
- const worker = currentWorker();
633
- if (worker) {
634
- post(worker, item, context);
635
- return;
636
- }
637
- if (target === "ready") {
638
- if (enqueue(item)) startReady(context);
639
- return;
640
- }
641
- reportDrop(item, "unavailable");
642
- reportError(/* @__PURE__ */ new Error("service worker controller is not available"), context, "unavailable", item.events.length);
643
- };
644
- return {
645
- name: transportName,
646
- minLevel: options.minLevel,
647
- queueSize() {
648
- return queue.length;
649
- },
650
- ready() {
651
- return waitForReady();
652
- },
653
- log(event, context) {
654
- send({
655
- events: [event],
656
- message: mapEvent(event, mapContext)
657
- }, context);
658
- },
659
- logBatch(events, context) {
660
- if (events.length === 0) return;
661
- send({
662
- events,
663
- message: mapBatch(events, mapContext)
664
- }, context);
665
- },
666
- flush() {
667
- drain();
668
- },
669
- close() {
670
- const pending = queue.splice(0);
671
- for (const item of pending) reportDrop(item, "closed");
672
- }
673
- };
674
- }
675
- //#endregion
676
- //#region src/websocket-transport.ts
677
- const WS_CONNECTING = 0;
678
- const WS_OPEN = 1;
679
- function defaultWebSocketFactory(url, protocols) {
680
- if (typeof WebSocket === "undefined") throw new Error("WebSocket is not available");
681
- return new WebSocket(url, protocols);
682
- }
683
- function toSendPayload(payload) {
684
- if (typeof payload === "string") return payload;
685
- return Uint8Array.from(payload);
686
- }
687
- function browserWebSocketTransport(options) {
688
- const transportName = options.name ?? "browser-websocket";
689
- const codec = options.codec ?? safeJsonCodec();
690
- const maxQueueSize = options.maxQueueSize ?? 1e3;
691
- const dropPolicy = options.dropPolicy ?? "drop-oldest";
692
- const webSocketFactory = options.webSocketFactory ?? defaultWebSocketFactory;
693
- const queue = [];
694
- let active;
695
- let closed = false;
696
- let lastContext;
697
- const reportDrop = (item, reason) => {
698
- incrementLoggerMetaCounter("transport.dropped", item.events.length);
699
- incrementLoggerMetaCounter(`transport.dropped.${reason}`, item.events.length);
700
- for (const event of item.events) options.onDrop?.(event, reason);
701
- };
702
- const reportError = (error, context, detail) => {
703
- try {
704
- options.onError?.(error, detail);
705
- } catch (onErrorError) {
706
- context?.reportInternalError(onErrorError, {
707
- operation: "on-error",
708
- phase: "transport",
709
- transport: transportName
710
- });
711
- }
712
- context?.reportInternalError(error, {
713
- operation: detail.operation,
714
- phase: "transport",
715
- transport: transportName
716
- });
717
- };
718
- const detachSocket = () => {
719
- if (!active) return;
720
- const current = active;
721
- current.socket.removeEventListener("open", current.onOpen);
722
- current.socket.removeEventListener("close", current.onClose);
723
- current.socket.removeEventListener("error", current.onError);
724
- active = void 0;
725
- };
726
- const drainQueue = (context = lastContext) => {
727
- const socket = active?.socket;
728
- if (!socket || socket.readyState !== WS_OPEN) return;
729
- while (queue.length > 0) {
730
- const item = queue.shift();
731
- if (!item) return;
732
- try {
733
- socket.send(toSendPayload(item.payload));
734
- } catch (error) {
735
- reportDrop(item, "send");
736
- reportError(error, context, {
737
- droppedEvents: item.events.length,
738
- operation: "send"
739
- });
740
- return;
741
- }
742
- }
743
- };
744
- const createSocket = (context, droppedEvents) => {
745
- if (closed) return void 0;
746
- if (active && (active.socket.readyState === WS_CONNECTING || active.socket.readyState === WS_OPEN)) return active.socket;
747
- detachSocket();
748
- try {
749
- const socket = webSocketFactory(options.url, options.protocols);
750
- const nextActive = {
751
- socket,
752
- onOpen() {
753
- drainQueue(context);
754
- },
755
- onClose() {
756
- if (active?.socket === socket) detachSocket();
757
- },
758
- onError(errorEvent) {
759
- reportError(errorEvent, context, {
760
- droppedEvents: 0,
761
- operation: "socket-error"
762
- });
763
- }
764
- };
765
- active = nextActive;
766
- socket.addEventListener("open", nextActive.onOpen);
767
- socket.addEventListener("close", nextActive.onClose);
768
- socket.addEventListener("error", nextActive.onError);
769
- return socket;
770
- } catch (error) {
771
- reportError(error, context, {
772
- droppedEvents,
773
- operation: "create-socket"
774
- });
775
- return;
776
- }
777
- };
778
- const enqueue = (item) => {
779
- if (queue.length >= maxQueueSize) {
780
- if (dropPolicy === "drop-newest") {
781
- reportDrop(item, "queue-full");
782
- return false;
783
- }
784
- const dropped = queue.shift();
785
- if (dropped) reportDrop(dropped, "queue-full");
786
- }
787
- queue.push(item);
788
- return true;
789
- };
790
- const sendOrQueue = (item, context) => {
791
- lastContext = context;
792
- if (closed) {
793
- reportDrop(item, "closed");
794
- return;
795
- }
796
- const socket = createSocket(context, item.events.length);
797
- if (!socket) {
798
- reportDrop(item, "create-socket");
799
- return;
800
- }
801
- if (socket.readyState === WS_OPEN) {
802
- try {
803
- socket.send(toSendPayload(item.payload));
804
- } catch (error) {
805
- reportDrop(item, "send");
806
- reportError(error, context, {
807
- droppedEvents: item.events.length,
808
- operation: "send"
809
- });
810
- }
811
- return;
812
- }
813
- enqueue(item);
814
- };
815
- return {
816
- name: transportName,
817
- minLevel: options.minLevel,
818
- queueSize() {
819
- return queue.length;
820
- },
821
- log(event, context) {
822
- sendOrQueue({
823
- events: [event],
824
- payload: codec.encode(event)
825
- }, context);
826
- },
827
- logBatch(events, context) {
828
- if (events.length === 0) return;
829
- sendOrQueue({
830
- events,
831
- payload: codec.encode(events)
832
- }, context);
833
- },
834
- flush() {
835
- drainQueue();
836
- },
837
- async close() {
838
- closed = true;
839
- while (queue.length > 0) {
840
- const item = queue.shift();
841
- if (item) reportDrop(item, "closed");
842
- }
843
- const socket = active?.socket;
844
- detachSocket();
845
- if (!socket) return;
846
- try {
847
- socket.close(options.closeCode, options.closeReason);
848
- } catch (error) {
849
- reportError(error, lastContext, {
850
- droppedEvents: 0,
851
- operation: "close-socket"
852
- });
853
- }
854
- }
855
- };
856
- }
857
- //#endregion
858
- //#region src/indexeddb-offline-queue.ts
859
- const DEFAULT_DB_NAME$1 = "loggerjs";
860
- const DEFAULT_STORE_NAME$1 = "browser-http-offline";
861
- function normalizeMaxEntries(value) {
862
- if (value === void 0 || !Number.isFinite(value)) return 1e3;
863
- return Math.max(0, Math.floor(value));
864
- }
865
- function requestToPromise$1(request) {
866
- return new Promise((resolve, reject) => {
867
- request.addEventListener("success", () => resolve(request.result), { once: true });
868
- request.addEventListener("error", () => reject(request.error ?? /* @__PURE__ */ new Error("IndexedDB request failed")), { once: true });
869
- });
870
- }
871
- function compareEntries$1(a, b) {
872
- if (a.createdAt !== b.createdAt) return a.createdAt - b.createdAt;
873
- return a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
874
- }
875
- function orderEntries$1(entries) {
876
- const ordered = [];
877
- for (const entry of entries) {
878
- let index = 0;
879
- while (index < ordered.length && compareEntries$1(ordered[index], entry) <= 0) index += 1;
880
- ordered.splice(index, 0, entry);
881
- }
882
- return ordered;
883
- }
884
- function indexedDbBrowserHttpOfflineQueue(options = {}) {
885
- const dbName = options.dbName ?? DEFAULT_DB_NAME$1;
886
- const storeName = options.storeName ?? DEFAULT_STORE_NAME$1;
887
- const maxEntries = normalizeMaxEntries(options.maxEntries);
888
- const dropPolicy = options.dropPolicy ?? "drop-oldest";
889
- const idb = options.indexedDB ?? globalThis.indexedDB;
890
- let dbPromise;
891
- const drop = (entry, reason) => {
892
- incrementLoggerMetaCounter("transport.offline.dropped");
893
- incrementLoggerMetaCounter(`transport.offline.dropped.${reason}`);
894
- options.onDrop?.(entry, reason);
895
- };
896
- const openDb = () => {
897
- if (!idb) throw new Error("IndexedDB is not available for loggerjs offline queue");
898
- dbPromise ??= new Promise((resolve, reject) => {
899
- const request = idb.open(dbName, 1);
900
- request.addEventListener("upgradeneeded", () => {
901
- const db = request.result;
902
- if (!db.objectStoreNames.contains(storeName)) db.createObjectStore(storeName, { keyPath: "id" });
903
- });
904
- request.addEventListener("success", () => resolve(request.result), { once: true });
905
- request.addEventListener("error", () => reject(request.error ?? /* @__PURE__ */ new Error("IndexedDB open failed")), { once: true });
906
- });
907
- return dbPromise;
908
- };
909
- const withStore = async (mode, run) => {
910
- return run((await openDb()).transaction(storeName, mode).objectStore(storeName));
911
- };
912
- const getEntries = async () => orderEntries$1(await withStore("readonly", (store) => requestToPromise$1(store.getAll())));
913
- const putEntry = (entry) => withStore("readwrite", (store) => requestToPromise$1(store.put(entry)));
914
- const deleteEntry = (id) => withStore("readwrite", (store) => requestToPromise$1(store.delete(id)));
915
- return {
916
- async enqueue(entry) {
917
- if (maxEntries === 0) {
918
- drop(entry, "queue-full");
919
- return;
920
- }
921
- const entries = await getEntries();
922
- if (entries.length >= maxEntries) {
923
- if (dropPolicy === "drop-newest") {
924
- drop(entry, "queue-full");
925
- return;
926
- }
927
- const dropCount = entries.length - maxEntries + 1;
928
- for (let index = 0; index < dropCount; index += 1) {
929
- const dropped = entries[index];
930
- if (!dropped) continue;
931
- await deleteEntry(dropped.id);
932
- drop(dropped, "queue-full");
933
- }
934
- }
935
- await putEntry(entry);
936
- },
937
- async replay(send) {
938
- const entries = await getEntries();
939
- for (const entry of entries) {
940
- await send(entry);
941
- await deleteEntry(entry.id);
942
- }
943
- },
944
- async size() {
945
- return (await getEntries()).length;
946
- },
947
- clear() {
948
- return withStore("readwrite", (store) => requestToPromise$1(store.clear()));
949
- },
950
- close() {
951
- const promise = dbPromise;
952
- dbPromise = void 0;
953
- promise?.then((db) => db.close());
954
- }
955
- };
956
- }
957
- //#endregion
958
- //#region src/indexeddb-transport.ts
959
- const DEFAULT_DB_NAME = "loggerjs";
960
- const DEFAULT_STORE_NAME = "logs";
961
- const DEFAULT_SESSION_CONTEXT_KEY$1 = "sessionId";
962
- const DEFAULT_SPILL_MAX_ENTRIES = 200;
963
- const DEFAULT_SPILL_MAX_BYTES = 512 * 1024;
964
- const DEFAULT_SPILL_MAX_ORIGIN_BYTES = 5 * 1024 * 1024;
965
- const DB_VERSION = 4;
966
- const ORDER_INDEX_NAME = "createdAtSeq";
967
- const SESSION_INDEX_NAME = "sessionId";
968
- const SESSION_ORDER_INDEX_NAME = "sessionIdCreatedAtSeq";
969
- const SESSION_STORE_SUFFIX = "__sessions";
970
- const SESSION_LAST_SEEN_INDEX_NAME = "lastSeenSessionId";
971
- const SPILL_SCHEMA = "loggerjs.indexeddb-spill.v1";
972
- function normalizePositiveInteger(value, fallback) {
973
- if (value === void 0 || !Number.isFinite(value)) return fallback;
974
- return Math.max(0, Math.floor(value));
975
- }
976
- let fallbackSessionSeq = 0;
977
- function defaultPageSessionId() {
978
- const randomUUID = globalThis.crypto?.randomUUID;
979
- if (randomUUID) return randomUUID.call(globalThis.crypto);
980
- return `${Date.now().toString(36)}-${(fallbackSessionSeq++).toString(36)}`;
981
- }
982
- function normalizeSessionId(value) {
983
- if (typeof value !== "string") return void 0;
984
- const normalized = value.trim();
985
- return normalized.length > 0 ? normalized : void 0;
986
- }
987
- function normalizeSessionOptions(session) {
988
- if (session === false) return void 0;
989
- if (typeof session === "string") return {
990
- contextKey: DEFAULT_SESSION_CONTEXT_KEY$1,
991
- id: normalizeSessionId(session) ?? defaultPageSessionId()
992
- };
993
- return {
994
- contextKey: session?.contextKey ?? DEFAULT_SESSION_CONTEXT_KEY$1,
995
- getId: session?.getId,
996
- id: normalizeSessionId(session?.id) ?? defaultPageSessionId()
997
- };
998
- }
999
- function resolveLocalStorage(storage) {
1000
- if (storage) return storage;
1001
- try {
1002
- return globalThis.localStorage;
1003
- } catch {
1004
- return;
1005
- }
1006
- }
1007
- function normalizeLocalStorageSpillOptions(spill, dbName, storeName) {
1008
- if (!spill) return void 0;
1009
- const storage = resolveLocalStorage(spill.storage);
1010
- if (!storage) return void 0;
1011
- const namespace = spill.namespace ?? `${dbName}:${storeName}`;
1012
- return {
1013
- drainOnCreate: spill.drainOnCreate ?? true,
1014
- key: `loggerjs:spill:v1:${namespace}`,
1015
- maxBytes: normalizePositiveInteger(spill.maxBytes, DEFAULT_SPILL_MAX_BYTES),
1016
- maxEntries: normalizePositiveInteger(spill.maxEntries, DEFAULT_SPILL_MAX_ENTRIES),
1017
- maxOriginBytes: normalizePositiveInteger(spill.maxOriginBytes, DEFAULT_SPILL_MAX_ORIGIN_BYTES),
1018
- minLevelValue: spill.minLevel === void 0 ? void 0 : toLevelValue(spill.minLevel),
1019
- onDrop: spill.onDrop,
1020
- spillOnPageHide: spill.spillOnPageHide ?? true,
1021
- storage
1022
- };
1023
- }
1024
- function storageByteLength(key, value) {
1025
- return 2 * (key.length + value.length);
1026
- }
1027
- function estimateLocalStorageBytes(storage) {
1028
- try {
1029
- let total = 0;
1030
- for (let index = 0; index < storage.length; index += 1) {
1031
- const key = storage.key(index);
1032
- if (key === null) continue;
1033
- total += storageByteLength(key, storage.getItem(key) ?? "");
1034
- }
1035
- return total;
1036
- } catch {
1037
- return;
1038
- }
1039
- }
1040
- function isQuotaExceededError(error) {
1041
- return typeof DOMException !== "undefined" && error instanceof DOMException && (error.name === "QuotaExceededError" || error.name === "NS_ERROR_DOM_QUOTA_REACHED");
1042
- }
1043
- function isSpillLogEvent(value) {
1044
- if (!value || typeof value !== "object") return false;
1045
- const event = value;
1046
- return typeof event.id === "string" && typeof event.time === "number" && typeof event.seq === "number" && typeof event.level === "number" && typeof event.levelName === "string" && typeof event.logger === "string" && typeof event.message === "string";
1047
- }
1048
- function parseSpillPayload(raw) {
1049
- const parsed = JSON.parse(raw);
1050
- if (parsed.schema !== SPILL_SCHEMA || !Array.isArray(parsed.entries)) return void 0;
1051
- return parsed.entries.filter(isSpillLogEvent);
1052
- }
1053
- function nowMs$1() {
1054
- return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
1055
- }
1056
- function incrementRecord(record, key, amount = 1) {
1057
- record[key] = (record[key] ?? 0) + amount;
1058
- }
1059
- function requestToPromise(request) {
1060
- return new Promise((resolve, reject) => {
1061
- request.addEventListener("success", () => resolve(request.result), { once: true });
1062
- request.addEventListener("error", () => reject(request.error ?? /* @__PURE__ */ new Error("IndexedDB request failed")), { once: true });
1063
- });
1064
- }
1065
- function transactionToPromise(transaction) {
1066
- return new Promise((resolve, reject) => {
1067
- transaction.addEventListener("complete", () => resolve(), { once: true });
1068
- transaction.addEventListener("abort", () => reject(transaction.error ?? /* @__PURE__ */ new Error("IndexedDB transaction aborted")), { once: true });
1069
- transaction.addEventListener("error", () => reject(transaction.error ?? /* @__PURE__ */ new Error("IndexedDB transaction failed")), { once: true });
1070
- });
1071
- }
1072
- function payloadByteLength(payload) {
1073
- if (typeof payload !== "string") return payload.byteLength;
1074
- if (typeof TextEncoder !== "undefined") return new TextEncoder().encode(payload).byteLength;
1075
- return new Blob([payload]).size;
1076
- }
1077
- function compareEntries(a, b) {
1078
- if (a.createdAt !== b.createdAt) return a.createdAt - b.createdAt;
1079
- if (a.seq !== b.seq) return a.seq - b.seq;
1080
- return a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
1081
- }
1082
- function orderEntries(entries, order = "asc") {
1083
- const sorted = [...entries].sort(compareEntries);
1084
- if (order === "asc") return sorted;
1085
- const reversed = [];
1086
- for (let index = sorted.length - 1; index >= 0; index -= 1) reversed.push(sorted[index]);
1087
- return reversed;
1088
- }
1089
- function indexExists(store, name) {
1090
- return store.indexNames.contains(name);
1091
- }
1092
- function ensureIndex(store, name, keyPath) {
1093
- if (!indexExists(store, name)) store.createIndex(name, keyPath);
1094
- }
1095
- function sessionStoreNameFor(storeName) {
1096
- return `${storeName}${SESSION_STORE_SUFFIX}`;
1097
- }
1098
- function getStoreIndex(store, name) {
1099
- const maybeStore = store;
1100
- if (typeof maybeStore.index !== "function" || !indexExists(store, name)) return void 0;
1101
- return maybeStore.index(name);
1102
- }
1103
- function getStorageBucketManager() {
1104
- return globalThis.navigator?.storageBuckets;
1105
- }
1106
- function storageBucketOpenOptions(options) {
1107
- if (options.storageBucketDurability === void 0 && options.storageBucketPersisted === void 0) return;
1108
- return {
1109
- durability: options.storageBucketDurability,
1110
- persisted: options.storageBucketPersisted
1111
- };
1112
- }
1113
- function decodeEntry(entry, codec) {
1114
- if (!codec.decode) throw new Error("indexedDbTransport query requires a codec with decode");
1115
- const decoded = codec.decode(entry.payload);
1116
- return Array.isArray(decoded) ? decoded[0] : decoded;
1117
- }
1118
- function shouldKeepEntry(entry, options) {
1119
- if (options.from !== void 0 && entry.createdAt < options.from) return false;
1120
- if (options.to !== void 0 && entry.createdAt > options.to) return false;
1121
- if (options.sessionId !== void 0 && entry.sessionId !== options.sessionId) return false;
1122
- if (options.minLevel !== void 0 && entry.level < toLevelValue(options.minLevel)) return false;
1123
- if (options.logger !== void 0 && entry.logger !== options.logger) return false;
1124
- if (options.type !== void 0 && entry.type !== options.type) return false;
1125
- return true;
1126
- }
1127
- function addSessionSummary(sessions, entry) {
1128
- if (!entry.sessionId) return;
1129
- const current = sessions.get(entry.sessionId);
1130
- if (current) {
1131
- current.firstSeen = Math.min(current.firstSeen, entry.createdAt);
1132
- current.lastSeen = Math.max(current.lastSeen, entry.createdAt);
1133
- current.count += 1;
1134
- current.byteLength += entry.byteLength;
1135
- return;
1136
- }
1137
- sessions.set(entry.sessionId, {
1138
- byteLength: entry.byteLength,
1139
- count: 1,
1140
- firstSeen: entry.createdAt,
1141
- lastSeen: entry.createdAt,
1142
- sessionId: entry.sessionId
1143
- });
1144
- }
1145
- function mergeSessionSummary(current, incoming) {
1146
- if (!current) return incoming;
1147
- return {
1148
- byteLength: current.byteLength + incoming.byteLength,
1149
- count: current.count + incoming.count,
1150
- firstSeen: Math.min(current.firstSeen, incoming.firstSeen),
1151
- lastSeen: Math.max(current.lastSeen, incoming.lastSeen),
1152
- sessionId: incoming.sessionId
1153
- };
1154
- }
1155
- function orderSessions(sessions, order = "desc") {
1156
- const sorted = [...sessions].sort((left, right) => {
1157
- if (left.lastSeen !== right.lastSeen) return left.lastSeen - right.lastSeen;
1158
- return left.sessionId < right.sessionId ? -1 : left.sessionId > right.sessionId ? 1 : 0;
1159
- });
1160
- if (order === "asc") return sorted;
1161
- const reversed = [];
1162
- for (let index = sorted.length - 1; index >= 0; index -= 1) reversed.push(sorted[index]);
1163
- return reversed;
1164
- }
1165
- function orderRangeForQuery(options) {
1166
- if (typeof IDBKeyRange === "undefined") return void 0;
1167
- if (options.sessionId !== void 0) {
1168
- const lower = [
1169
- options.sessionId,
1170
- options.from ?? Number.MIN_SAFE_INTEGER,
1171
- Number.MIN_SAFE_INTEGER,
1172
- ""
1173
- ];
1174
- const upper = [
1175
- options.sessionId,
1176
- options.to ?? Number.MAX_SAFE_INTEGER,
1177
- Number.MAX_SAFE_INTEGER,
1178
- "￿"
1179
- ];
1180
- return IDBKeyRange.bound(lower, upper);
1181
- }
1182
- const lower = options.from === void 0 ? void 0 : [
1183
- options.from,
1184
- Number.MIN_SAFE_INTEGER,
1185
- ""
1186
- ];
1187
- const upper = options.to === void 0 ? void 0 : [
1188
- options.to,
1189
- Number.MAX_SAFE_INTEGER,
1190
- "￿"
1191
- ];
1192
- if (lower && upper) return IDBKeyRange.bound(lower, upper);
1193
- if (lower) return IDBKeyRange.lowerBound(lower);
1194
- if (upper) return IDBKeyRange.upperBound(upper);
1195
- }
1196
- function orderRangeForSessionId(sessionId) {
1197
- if (typeof IDBKeyRange === "undefined") return void 0;
1198
- return IDBKeyRange.bound([
1199
- sessionId,
1200
- Number.MIN_SAFE_INTEGER,
1201
- Number.MIN_SAFE_INTEGER,
1202
- ""
1203
- ], [
1204
- sessionId,
1205
- Number.MAX_SAFE_INTEGER,
1206
- Number.MAX_SAFE_INTEGER,
1207
- "￿"
1208
- ]);
1209
- }
1210
- function migrateSessionSummaries(logStore, sessionStore) {
1211
- const summaries = /* @__PURE__ */ new Map();
1212
- const request = logStore.openCursor();
1213
- request.addEventListener("success", () => {
1214
- const cursor = request.result;
1215
- if (!cursor) {
1216
- for (const summary of summaries.values()) sessionStore.put(summary);
1217
- return;
1218
- }
1219
- addSessionSummary(summaries, cursor.value);
1220
- cursor.continue();
1221
- });
1222
- }
1223
- function iterateCursor(request, visit) {
1224
- return new Promise((resolve, reject) => {
1225
- request.addEventListener("success", () => {
1226
- const cursor = request.result;
1227
- if (!cursor) {
1228
- resolve();
1229
- return;
1230
- }
1231
- try {
1232
- if (visit(cursor) === false) {
1233
- resolve();
1234
- return;
1235
- }
1236
- cursor.continue();
1237
- } catch (error) {
1238
- reject(error);
1239
- }
1240
- }, false);
1241
- request.addEventListener("error", () => reject(request.error ?? /* @__PURE__ */ new Error("IndexedDB cursor failed")), { once: true });
1242
- });
1243
- }
1244
- function indexedDbTransport(options = {}) {
1245
- const dbName = options.dbName ?? DEFAULT_DB_NAME;
1246
- const storeName = options.storeName ?? DEFAULT_STORE_NAME;
1247
- const sessionStoreName = sessionStoreNameFor(storeName);
1248
- const codec = options.codec ?? safeJsonCodec();
1249
- const batchSize = normalizePositiveInteger(options.batchSize, 100);
1250
- const flushIntervalMs = normalizePositiveInteger(options.flushIntervalMs, 1e3);
1251
- const maxBufferSize = normalizePositiveInteger(options.maxBufferSize, 1e3);
1252
- const maxEntries = normalizePositiveInteger(options.maxEntries, 5e4);
1253
- const maxBytes = normalizePositiveInteger(options.maxBytes, 50 * 1024 * 1024);
1254
- const dropPolicy = options.dropPolicy ?? "drop-oldest";
1255
- const flushOnPageHide = options.flushOnPageHide ?? true;
1256
- const durability = options.durability ?? "default";
1257
- const rootIndexedDB = options.indexedDB ?? globalThis.indexedDB;
1258
- const sessionOptions = normalizeSessionOptions(options.session);
1259
- const spillOptions = normalizeLocalStorageSpillOptions(options.localStorageSpill, dbName, storeName);
1260
- const transactionOptions = durability === "default" ? void 0 : { durability };
1261
- const buffer = [];
1262
- let dbPromise;
1263
- let indexedDBFactoryPromise;
1264
- let timer;
1265
- let flushPromise;
1266
- let clearPromise;
1267
- let pendingFlushBatch;
1268
- let spillDrainPromise;
1269
- let localStorageSpillClearCandidate;
1270
- let lastContext;
1271
- let closed = false;
1272
- const statsState = {
1273
- bufferDepth: 0,
1274
- maxBufferDepth: 0,
1275
- pendingFlush: false,
1276
- enqueued: 0,
1277
- persisted: 0,
1278
- dropped: 0,
1279
- droppedByReason: {},
1280
- persistedDropped: 0,
1281
- persistedDroppedByReason: {},
1282
- flushes: 0,
1283
- flushErrors: 0,
1284
- lastFlushBatchSize: 0,
1285
- lastFlushDurationMs: 0,
1286
- prunes: 0,
1287
- pruneFallbacks: 0,
1288
- lastPruneDurationMs: 0,
1289
- queries: 0,
1290
- queryFallbacks: 0,
1291
- lastQueryDurationMs: 0,
1292
- databaseOpenCount: 0,
1293
- storageBucketFallbacks: 0,
1294
- transactionOptionFallbacks: 0,
1295
- errors: 0,
1296
- errorsByOperation: {},
1297
- localStorageSpillWrites: 0,
1298
- localStorageSpillEntries: 0,
1299
- localStorageSpillDrains: 0,
1300
- localStorageSpillDrainedEntries: 0,
1301
- localStorageSpillDropped: 0,
1302
- localStorageSpillDroppedByReason: {},
1303
- localStorageSpillErrors: 0,
1304
- localStorageSpillErrorsByOperation: {}
1305
- };
1306
- const snapshotStats = () => ({
1307
- ...statsState,
1308
- bufferDepth: buffer.length,
1309
- droppedByReason: { ...statsState.droppedByReason },
1310
- errorsByOperation: { ...statsState.errorsByOperation },
1311
- localStorageSpillDroppedByReason: { ...statsState.localStorageSpillDroppedByReason },
1312
- localStorageSpillErrorsByOperation: { ...statsState.localStorageSpillErrorsByOperation },
1313
- pendingFlush: flushPromise !== void 0,
1314
- persistedDroppedByReason: { ...statsState.persistedDroppedByReason }
1315
- });
1316
- const reportInternalError = (error, operation) => {
1317
- statsState.errors += 1;
1318
- incrementRecord(statsState.errorsByOperation, operation);
1319
- lastContext?.reportInternalError(error, {
1320
- operation,
1321
- phase: "transport",
1322
- transport: options.name ?? "indexeddb"
1323
- });
1324
- };
1325
- const dropEvent = (event, reason) => {
1326
- statsState.dropped += 1;
1327
- incrementRecord(statsState.droppedByReason, reason);
1328
- incrementLoggerMetaCounter("transport.indexeddb.dropped");
1329
- incrementLoggerMetaCounter(`transport.indexeddb.dropped.${reason}`);
1330
- options.onDrop?.(event, reason);
1331
- };
1332
- const dropPersistedEntry = (entry, reason) => {
1333
- statsState.persistedDropped += 1;
1334
- incrementRecord(statsState.persistedDroppedByReason, reason);
1335
- incrementLoggerMetaCounter("transport.indexeddb.persisted.dropped");
1336
- incrementLoggerMetaCounter(`transport.indexeddb.persisted.dropped.${reason}`);
1337
- options.onPersistedDrop?.(entry, reason);
1338
- };
1339
- const reportSpillError = (error, operation) => {
1340
- statsState.localStorageSpillErrors += 1;
1341
- incrementRecord(statsState.localStorageSpillErrorsByOperation, operation);
1342
- reportInternalError(error, `localstorage-spill-${operation}`);
1343
- };
1344
- const dropSpillEvent = (event, reason) => {
1345
- statsState.localStorageSpillDropped += 1;
1346
- incrementRecord(statsState.localStorageSpillDroppedByReason, reason);
1347
- incrementLoggerMetaCounter("transport.indexeddb.localstorage_spill.dropped");
1348
- incrementLoggerMetaCounter(`transport.indexeddb.localstorage_spill.dropped.${reason}`);
1349
- spillOptions?.onDrop?.(event, reason);
1350
- };
1351
- const resolveIndexedDBFactory = async () => {
1352
- if (options.indexedDB || !options.storageBucketName) return rootIndexedDB;
1353
- indexedDBFactoryPromise ??= (async () => {
1354
- const manager = getStorageBucketManager();
1355
- if (!manager) {
1356
- statsState.storageBucketFallbacks += 1;
1357
- return rootIndexedDB;
1358
- }
1359
- try {
1360
- const bucket = await manager.open(options.storageBucketName, storageBucketOpenOptions(options));
1361
- if (bucket.indexedDB) return bucket.indexedDB;
1362
- statsState.storageBucketFallbacks += 1;
1363
- return rootIndexedDB;
1364
- } catch (error) {
1365
- statsState.storageBucketFallbacks += 1;
1366
- reportInternalError(error, "storage-bucket-open");
1367
- return rootIndexedDB;
1368
- }
1369
- })();
1370
- return indexedDBFactoryPromise;
1371
- };
1372
- const openDb = () => {
1373
- dbPromise ??= resolveIndexedDBFactory().then((idb) => {
1374
- if (!idb) throw new Error("IndexedDB is not available for indexedDbTransport");
1375
- return new Promise((resolve, reject) => {
1376
- const request = idb.open(dbName, DB_VERSION);
1377
- request.addEventListener("upgradeneeded", (event) => {
1378
- const db = request.result;
1379
- const store = db.objectStoreNames.contains(storeName) ? request.transaction?.objectStore(storeName) : db.createObjectStore(storeName, { keyPath: "id" });
1380
- if (!store) return;
1381
- ensureIndex(store, "createdAt", "createdAt");
1382
- ensureIndex(store, "level", "level");
1383
- ensureIndex(store, "logger", "logger");
1384
- ensureIndex(store, "type", "type");
1385
- ensureIndex(store, ORDER_INDEX_NAME, [
1386
- "createdAt",
1387
- "seq",
1388
- "id"
1389
- ]);
1390
- ensureIndex(store, SESSION_INDEX_NAME, "sessionId");
1391
- ensureIndex(store, SESSION_ORDER_INDEX_NAME, [
1392
- "sessionId",
1393
- "createdAt",
1394
- "seq",
1395
- "id"
1396
- ]);
1397
- const sessionStore = db.objectStoreNames.contains(sessionStoreName) ? request.transaction?.objectStore(sessionStoreName) : db.createObjectStore(sessionStoreName, { keyPath: "sessionId" });
1398
- if (!sessionStore) return;
1399
- ensureIndex(sessionStore, SESSION_LAST_SEEN_INDEX_NAME, ["lastSeen", "sessionId"]);
1400
- if (event.oldVersion < 4) migrateSessionSummaries(store, sessionStore);
1401
- });
1402
- request.addEventListener("success", () => {
1403
- statsState.databaseOpenCount += 1;
1404
- resolve(request.result);
1405
- }, { once: true });
1406
- request.addEventListener("error", () => reject(request.error ?? /* @__PURE__ */ new Error("IndexedDB open failed")), { once: true });
1407
- });
1408
- });
1409
- return dbPromise;
1410
- };
1411
- const createTransaction = (db, mode, stores = storeName) => {
1412
- if (mode !== "readwrite" || !transactionOptions) return db.transaction(stores, mode);
1413
- try {
1414
- return db.transaction(stores, mode, transactionOptions);
1415
- } catch {
1416
- statsState.transactionOptionFallbacks += 1;
1417
- return db.transaction(stores, mode);
1418
- }
1419
- };
1420
- const withStore = async (mode, run) => {
1421
- const tx = createTransaction(await openDb(), mode);
1422
- const settled = transactionToPromise(tx);
1423
- try {
1424
- const result = await run(tx.objectStore(storeName), tx);
1425
- await settled;
1426
- return result;
1427
- } catch (error) {
1428
- if (tx.error === null) try {
1429
- tx.abort();
1430
- } catch {}
1431
- throw error;
1432
- }
1433
- };
1434
- const withLogAndSessionStores = async (mode, run) => {
1435
- const tx = createTransaction(await openDb(), mode, [storeName, sessionStoreName]);
1436
- const settled = transactionToPromise(tx);
1437
- try {
1438
- const result = await run(tx.objectStore(storeName), tx.objectStore(sessionStoreName), tx);
1439
- await settled;
1440
- return result;
1441
- } catch (error) {
1442
- if (tx.error === null) try {
1443
- tx.abort();
1444
- } catch {}
1445
- throw error;
1446
- }
1447
- };
1448
- const withSessionStore = async (mode, run) => {
1449
- const tx = createTransaction(await openDb(), mode, sessionStoreName);
1450
- const settled = transactionToPromise(tx);
1451
- try {
1452
- const result = await run(tx.objectStore(sessionStoreName), tx);
1453
- await settled;
1454
- return result;
1455
- } catch (error) {
1456
- if (tx.error === null) try {
1457
- tx.abort();
1458
- } catch {}
1459
- throw error;
1460
- }
1461
- };
1462
- const mergeSessionSummaries = async (sessionStore, entries) => {
1463
- const summaries = /* @__PURE__ */ new Map();
1464
- for (const entry of entries) addSessionSummary(summaries, entry);
1465
- for (const summary of summaries.values()) {
1466
- const current = await requestToPromise(sessionStore.get(summary.sessionId));
1467
- await requestToPromise(sessionStore.put(mergeSessionSummary(current, summary)));
1468
- }
1469
- };
1470
- const rebuildSessionSummaries = async (store, sessionStore, sessionIds) => {
1471
- if (sessionIds.size === 0) return;
1472
- const index = getStoreIndex(store, SESSION_ORDER_INDEX_NAME);
1473
- for (const sessionId of sessionIds) {
1474
- const summaries = /* @__PURE__ */ new Map();
1475
- const range = orderRangeForSessionId(sessionId);
1476
- await iterateCursor(index && range ? index.openCursor(range) : store.openCursor(), (cursor) => {
1477
- const entry = cursor.value;
1478
- if (entry.sessionId === sessionId) addSessionSummary(summaries, entry);
1479
- });
1480
- const summary = summaries.get(sessionId);
1481
- if (summary) await requestToPromise(sessionStore.put(summary));
1482
- else await requestToPromise(sessionStore.delete(sessionId));
1483
- }
1484
- };
1485
- const getEntries = async () => orderEntries(await withStore("readonly", (store) => requestToPromise(store.getAll())));
1486
- const countEntries = async () => withStore("readonly", (store) => {
1487
- const maybeStore = store;
1488
- if (typeof maybeStore.count !== "function") return void 0;
1489
- return requestToPromise(maybeStore.count.call(store));
1490
- });
1491
- const deleteEntries = async (entries, reason) => {
1492
- if (entries.length === 0) return;
1493
- await withLogAndSessionStores("readwrite", async (store, sessionStore) => {
1494
- const affectedSessionIds = /* @__PURE__ */ new Set();
1495
- for (const entry of entries) {
1496
- await requestToPromise(store.delete(entry.id));
1497
- if (entry.sessionId) affectedSessionIds.add(entry.sessionId);
1498
- dropPersistedEntry(entry, reason);
1499
- }
1500
- await rebuildSessionSummaries(store, sessionStore, affectedSessionIds);
1501
- });
1502
- };
1503
- const deleteByCursor = async (reason, openCursor, shouldDelete) => withLogAndSessionStores("readwrite", async (store, sessionStore) => {
1504
- const request = openCursor(store);
1505
- if (!request) return void 0;
1506
- let deleted = 0;
1507
- const affectedSessionIds = /* @__PURE__ */ new Set();
1508
- await iterateCursor(request, (cursor) => {
1509
- const entry = cursor.value;
1510
- if (!shouldDelete(entry, deleted)) return false;
1511
- cursor.delete();
1512
- deleted += 1;
1513
- if (entry.sessionId) affectedSessionIds.add(entry.sessionId);
1514
- dropPersistedEntry(entry, reason);
1515
- return true;
1516
- });
1517
- await rebuildSessionSummaries(store, sessionStore, affectedSessionIds);
1518
- return deleted;
1519
- });
1520
- const deleteExpiredByCursor = async (cutoff) => {
1521
- if (typeof IDBKeyRange === "undefined") return void 0;
1522
- const range = IDBKeyRange.upperBound(cutoff, true);
1523
- return deleteByCursor("ttl", (store) => getStoreIndex(store, "createdAt")?.openCursor(range), () => true);
1524
- };
1525
- const deleteOldestByCursor = async (reason, count) => {
1526
- if (count <= 0) return 0;
1527
- return deleteByCursor(reason, (store) => getStoreIndex(store, ORDER_INDEX_NAME)?.openCursor(), (_entry, deleted) => deleted < count);
1528
- };
1529
- const deleteOldestBytesByCursor = async (bytesToFree) => {
1530
- if (bytesToFree <= 0) return 0;
1531
- let freed = 0;
1532
- return deleteByCursor("max-bytes", (store) => getStoreIndex(store, ORDER_INDEX_NAME)?.openCursor(), (entry) => {
1533
- if (freed >= bytesToFree) return false;
1534
- freed += entry.byteLength;
1535
- return true;
1536
- });
1537
- };
1538
- const sumBytesByCursor = async () => withStore("readonly", async (store) => {
1539
- const index = getStoreIndex(store, ORDER_INDEX_NAME);
1540
- if (!index) return void 0;
1541
- let total = 0;
1542
- await iterateCursor(index.openCursor(), (cursor) => {
1543
- total += cursor.value.byteLength;
1544
- });
1545
- return total;
1546
- });
1547
- const queryEntriesByCursor = async (queryOptions) => withStore("readonly", async (store) => {
1548
- const index = getStoreIndex(store, queryOptions.sessionId === void 0 ? ORDER_INDEX_NAME : SESSION_ORDER_INDEX_NAME);
1549
- if (!index) return void 0;
1550
- const entries = [];
1551
- const limit = queryOptions.limit;
1552
- if (limit !== void 0 && limit <= 0) return entries;
1553
- await iterateCursor(index.openCursor(orderRangeForQuery(queryOptions), queryOptions.order === "desc" ? "prev" : "next"), (cursor) => {
1554
- const entry = cursor.value;
1555
- if (!shouldKeepEntry(entry, queryOptions)) return true;
1556
- entries.push(entry);
1557
- return limit === void 0 || entries.length < limit;
1558
- });
1559
- return entries;
1560
- });
1561
- const listSessions = async (sessionQueryOptions = {}) => {
1562
- const limit = sessionQueryOptions.limit;
1563
- if (limit !== void 0 && limit <= 0) return [];
1564
- return withSessionStore("readonly", async (sessionStore) => {
1565
- const sessions = [];
1566
- const index = getStoreIndex(sessionStore, SESSION_LAST_SEEN_INDEX_NAME);
1567
- if (!index) return orderSessions(await requestToPromise(sessionStore.getAll()), sessionQueryOptions.order).slice(0, limit);
1568
- await iterateCursor(index.openCursor(void 0, sessionQueryOptions.order === "asc" ? "next" : "prev"), (cursor) => {
1569
- sessions.push(cursor.value);
1570
- return limit === void 0 || sessions.length < limit;
1571
- });
1572
- return sessions;
1573
- });
1574
- };
1575
- const pruneWithEntries = async () => {
1576
- const now = Date.now();
1577
- let entries = await getEntries();
1578
- const expired = options.ttlMs === void 0 ? [] : entries.filter((entry) => now - entry.createdAt > options.ttlMs);
1579
- if (expired.length > 0) {
1580
- await deleteEntries(expired, "ttl");
1581
- const expiredIds = new Set(expired.map((entry) => entry.id));
1582
- entries = entries.filter((entry) => !expiredIds.has(entry.id));
1583
- }
1584
- if (maxEntries >= 0 && entries.length > maxEntries) {
1585
- await deleteEntries(entries.slice(0, entries.length - maxEntries), "max-entries");
1586
- entries = entries.slice(entries.length - maxEntries);
1587
- }
1588
- if (maxBytes >= 0) {
1589
- let total = entries.reduce((sum, entry) => sum + entry.byteLength, 0);
1590
- const dropped = [];
1591
- for (const entry of entries) {
1592
- if (total <= maxBytes) break;
1593
- total -= entry.byteLength;
1594
- dropped.push(entry);
1595
- }
1596
- await deleteEntries(dropped, "max-bytes");
1597
- }
1598
- };
1599
- const pruneWithCursors = async () => {
1600
- if (options.ttlMs !== void 0) {
1601
- if (await deleteExpiredByCursor(Date.now() - options.ttlMs) === void 0) return false;
1602
- }
1603
- if (maxEntries >= 0) {
1604
- const persistedCount = await countEntries();
1605
- if (persistedCount === void 0) return false;
1606
- if (persistedCount > maxEntries) {
1607
- if (await deleteOldestByCursor("max-entries", persistedCount - maxEntries) === void 0) return false;
1608
- }
1609
- }
1610
- if (maxBytes >= 0) {
1611
- const totalBytes = await sumBytesByCursor();
1612
- if (totalBytes === void 0) return false;
1613
- if (totalBytes > maxBytes) {
1614
- if (await deleteOldestBytesByCursor(totalBytes - maxBytes) === void 0) return false;
1615
- }
1616
- }
1617
- return true;
1618
- };
1619
- const prune = async () => {
1620
- const startedAt = nowMs$1();
1621
- try {
1622
- if (await pruneWithCursors()) return;
1623
- statsState.pruneFallbacks += 1;
1624
- await pruneWithEntries();
1625
- } finally {
1626
- statsState.prunes += 1;
1627
- statsState.lastPruneDurationMs = nowMs$1() - startedAt;
1628
- }
1629
- };
1630
- const sessionIdForEvent = (event) => {
1631
- if (!sessionOptions) return void 0;
1632
- return normalizeSessionId(sessionOptions.getId?.(event)) ?? normalizeSessionId(event.context?.[sessionOptions.contextKey]) ?? sessionOptions.id;
1633
- };
1634
- const eventWithSessionContext = (event, sessionId) => {
1635
- if (!sessionId || !sessionOptions?.contextKey) return event;
1636
- if (event.context?.[sessionOptions.contextKey] === sessionId) return event;
1637
- return {
1638
- ...event,
1639
- context: {
1640
- ...event.context,
1641
- [sessionOptions.contextKey]: sessionId
1642
- }
1643
- };
1644
- };
1645
- const eventWithResolvedSession = (event) => {
1646
- const sessionId = sessionIdForEvent(event);
1647
- return {
1648
- event: eventWithSessionContext(event, sessionId),
1649
- sessionId
1650
- };
1651
- };
1652
- const eventToEntry = (event) => {
1653
- const resolved = eventWithResolvedSession(event);
1654
- const payload = codec.encode(resolved.event);
1655
- return {
1656
- id: event.id,
1657
- sessionId: resolved.sessionId,
1658
- seq: event.seq,
1659
- createdAt: event.time,
1660
- level: event.level,
1661
- levelName: event.levelName,
1662
- logger: event.logger,
1663
- type: event.type,
1664
- byteLength: payloadByteLength(payload),
1665
- payload
1666
- };
1667
- };
1668
- const clearLocalStorageSpill = (expectedValue) => {
1669
- if (!spillOptions) return false;
1670
- try {
1671
- if (expectedValue !== void 0 && spillOptions.storage.getItem(spillOptions.key) !== expectedValue) return false;
1672
- spillOptions.storage.removeItem(spillOptions.key);
1673
- if (expectedValue === void 0 || localStorageSpillClearCandidate?.value === expectedValue) localStorageSpillClearCandidate = void 0;
1674
- return true;
1675
- } catch (error) {
1676
- reportSpillError(error, "clear");
1677
- return false;
1678
- }
1679
- };
1680
- const rememberLocalStorageSpill = (value, events) => {
1681
- localStorageSpillClearCandidate = {
1682
- pendingIds: new Set(events.map((event) => event.id)),
1683
- value
1684
- };
1685
- };
1686
- const markLocalStorageSpillPersisted = (events) => {
1687
- const candidate = localStorageSpillClearCandidate;
1688
- if (!candidate || events.length === 0) return;
1689
- for (const event of events) candidate.pendingIds.delete(event.id);
1690
- if (candidate.pendingIds.size === 0) clearLocalStorageSpill(candidate.value);
1691
- };
1692
- const writeBatch = async (events) => {
1693
- if (events.length === 0) return;
1694
- const startedAt = nowMs$1();
1695
- statsState.lastFlushBatchSize = events.length;
1696
- const entries = events.map(eventToEntry);
1697
- try {
1698
- await withLogAndSessionStores("readwrite", async (store, sessionStore) => {
1699
- const rebuildSessionIds = /* @__PURE__ */ new Set();
1700
- for (const entry of entries) {
1701
- const existing = await requestToPromise(store.get(entry.id));
1702
- if (existing?.sessionId) rebuildSessionIds.add(existing.sessionId);
1703
- if (existing && entry.sessionId) rebuildSessionIds.add(entry.sessionId);
1704
- await requestToPromise(store.put(entry));
1705
- }
1706
- await rebuildSessionSummaries(store, sessionStore, rebuildSessionIds);
1707
- await mergeSessionSummaries(sessionStore, entries.filter((entry) => !entry.sessionId || !rebuildSessionIds.has(entry.sessionId)));
1708
- });
1709
- statsState.persisted += entries.length;
1710
- incrementLoggerMetaCounter("transport.indexeddb.persisted", entries.length);
1711
- markLocalStorageSpillPersisted(events);
1712
- await prune();
1713
- statsState.flushes += 1;
1714
- } catch (error) {
1715
- statsState.flushErrors += 1;
1716
- throw error;
1717
- } finally {
1718
- statsState.lastFlushDurationMs = nowMs$1() - startedAt;
1719
- }
1720
- };
1721
- const stringifySpillPayload = (events) => safeJsonStringify({
1722
- createdAt: Date.now(),
1723
- entries: events,
1724
- schema: SPILL_SCHEMA
1725
- }, { maxArrayLength: spillOptions?.maxEntries ?? DEFAULT_SPILL_MAX_ENTRIES });
1726
- const spillStorageBudget = (currentValue) => {
1727
- if (!spillOptions) return 0;
1728
- const ownBytes = currentValue ? storageByteLength(spillOptions.key, currentValue) : 0;
1729
- const originBytes = estimateLocalStorageBytes(spillOptions.storage);
1730
- if (originBytes === void 0) return spillOptions.maxBytes;
1731
- return Math.max(0, Math.min(spillOptions.maxBytes, spillOptions.maxOriginBytes - originBytes + ownBytes));
1732
- };
1733
- const pendingEventsForSpill = () => {
1734
- const events = [...pendingFlushBatch ?? [], ...buffer];
1735
- const seen = /* @__PURE__ */ new Set();
1736
- const deduped = [];
1737
- for (const event of events) {
1738
- if (seen.has(event.id)) continue;
1739
- seen.add(event.id);
1740
- if (spillOptions?.minLevelValue !== void 0 && event.level < spillOptions.minLevelValue) continue;
1741
- deduped.push(eventWithResolvedSession(event).event);
1742
- }
1743
- return deduped;
1744
- };
1745
- const trimSpillEventsToBudget = (events, budget) => {
1746
- if (!spillOptions) return void 0;
1747
- while (events.length > spillOptions.maxEntries) {
1748
- const dropped = events.shift();
1749
- if (dropped) dropSpillEvent(dropped, "max-entries");
1750
- }
1751
- while (events.length > 0) {
1752
- const value = stringifySpillPayload(events);
1753
- if (storageByteLength(spillOptions.key, value) <= budget) return value;
1754
- const dropped = events.shift();
1755
- if (dropped) dropSpillEvent(dropped, "max-bytes");
1756
- }
1757
- };
1758
- const writeLocalStorageSpill = () => {
1759
- if (!spillOptions) return;
1760
- const pendingEvents = pendingEventsForSpill();
1761
- if (pendingEvents.length === 0) return;
1762
- try {
1763
- const currentValue = spillOptions.storage.getItem(spillOptions.key);
1764
- let events = pendingEvents;
1765
- if (currentValue) {
1766
- const merged = /* @__PURE__ */ new Map();
1767
- try {
1768
- for (const event of parseSpillPayload(currentValue) ?? []) merged.set(event.id, event);
1769
- } catch {}
1770
- for (const event of pendingEvents) merged.set(event.id, event);
1771
- events = [...merged.values()];
1772
- }
1773
- const budget = spillStorageBudget(currentValue);
1774
- const value = trimSpillEventsToBudget(events, budget);
1775
- if (!value) return;
1776
- try {
1777
- spillOptions.storage.setItem(spillOptions.key, value);
1778
- } catch (error) {
1779
- if (!isQuotaExceededError(error)) throw error;
1780
- while (events.length > 0) {
1781
- const dropped = events.shift();
1782
- if (dropped) dropSpillEvent(dropped, "quota");
1783
- const retryValue = trimSpillEventsToBudget(events, budget);
1784
- if (!retryValue) return;
1785
- try {
1786
- spillOptions.storage.setItem(spillOptions.key, retryValue);
1787
- rememberLocalStorageSpill(retryValue, events);
1788
- statsState.localStorageSpillWrites += 1;
1789
- statsState.localStorageSpillEntries += events.length;
1790
- return;
1791
- } catch (retryError) {
1792
- if (!isQuotaExceededError(retryError)) throw retryError;
1793
- }
1794
- }
1795
- return;
1796
- }
1797
- rememberLocalStorageSpill(value, events);
1798
- statsState.localStorageSpillWrites += 1;
1799
- statsState.localStorageSpillEntries += events.length;
1800
- } catch (error) {
1801
- reportSpillError(error, "write");
1802
- }
1803
- };
1804
- const drainLocalStorageSpill = async () => {
1805
- if (!spillOptions) return;
1806
- let raw;
1807
- try {
1808
- raw = spillOptions.storage.getItem(spillOptions.key);
1809
- } catch (error) {
1810
- reportSpillError(error, "read");
1811
- return;
1812
- }
1813
- if (!raw) return;
1814
- let events;
1815
- try {
1816
- events = parseSpillPayload(raw);
1817
- } catch (error) {
1818
- reportSpillError(error, "parse");
1819
- clearLocalStorageSpill();
1820
- return;
1821
- }
1822
- if (!events || events.length === 0) {
1823
- clearLocalStorageSpill();
1824
- return;
1825
- }
1826
- await writeBatch(events);
1827
- clearLocalStorageSpill(raw);
1828
- statsState.localStorageSpillDrains += 1;
1829
- statsState.localStorageSpillDrainedEntries += events.length;
1830
- };
1831
- const ensureSpillDrained = async () => {
1832
- const promise = spillDrainPromise;
1833
- if (!promise) return;
1834
- await promise;
1835
- };
1836
- if (spillOptions?.drainOnCreate) spillDrainPromise = drainLocalStorageSpill().catch((error) => reportSpillError(error, "drain")).finally(() => {
1837
- spillDrainPromise = void 0;
1838
- });
1839
- const clearTimer = () => {
1840
- if (timer) clearTimeout(timer);
1841
- timer = void 0;
1842
- };
1843
- const flushPending = async () => {
1844
- if (flushPromise) return flushPromise;
1845
- clearTimer();
1846
- const batch = buffer.splice(0, buffer.length);
1847
- pendingFlushBatch = batch;
1848
- const clearBarrier = clearPromise;
1849
- flushPromise = (async () => {
1850
- await clearBarrier;
1851
- await ensureSpillDrained();
1852
- await writeBatch(batch);
1853
- })().finally(() => {
1854
- if (pendingFlushBatch === batch) pendingFlushBatch = void 0;
1855
- flushPromise = void 0;
1856
- if (buffer.length > 0 && !closed) schedule();
1857
- });
1858
- return flushPromise;
1859
- };
1860
- const schedule = () => {
1861
- if (timer || flushIntervalMs <= 0 || closed) return;
1862
- timer = setTimeout(() => {
1863
- flushPending().catch((error) => reportInternalError(error, "flush"));
1864
- }, flushIntervalMs);
1865
- };
1866
- const onPageHide = () => {
1867
- if (spillOptions?.spillOnPageHide) writeLocalStorageSpill();
1868
- if (flushOnPageHide) flushPending().catch((error) => reportInternalError(error, "pagehide-flush"));
1869
- };
1870
- const onVisibilityChange = () => {
1871
- if (typeof document !== "undefined" && document.visibilityState === "hidden") {
1872
- if (spillOptions?.spillOnPageHide) writeLocalStorageSpill();
1873
- if (flushOnPageHide) flushPending().catch((error) => reportInternalError(error, "visibility-flush"));
1874
- }
1875
- };
1876
- const addVisibilityChangeListener = () => {
1877
- if (typeof document !== "undefined" && typeof document.addEventListener === "function") {
1878
- document.addEventListener("visibilitychange", onVisibilityChange);
1879
- return () => document.removeEventListener("visibilitychange", onVisibilityChange);
1880
- }
1881
- if (typeof globalThis.addEventListener !== "function") return void 0;
1882
- globalThis.addEventListener("visibilitychange", onVisibilityChange);
1883
- return () => globalThis.removeEventListener?.("visibilitychange", onVisibilityChange);
1884
- };
1885
- let removeVisibilityChangeListener;
1886
- if (flushOnPageHide || spillOptions?.spillOnPageHide) {
1887
- globalThis.addEventListener?.("pagehide", onPageHide);
1888
- removeVisibilityChangeListener = addVisibilityChangeListener();
1889
- }
1890
- return {
1891
- name: options.name ?? "indexeddb",
1892
- minLevel: options.minLevel,
1893
- log(event, context) {
1894
- if (closed) return;
1895
- if (options.minLevel !== void 0 && event.level < toLevelValue(options.minLevel)) return;
1896
- lastContext = context;
1897
- if (maxBufferSize === 0 || buffer.length >= maxBufferSize) {
1898
- if (dropPolicy === "drop-newest") {
1899
- dropEvent(event, "buffer-full");
1900
- return;
1901
- }
1902
- const dropped = buffer.shift();
1903
- if (dropped) dropEvent(dropped, "buffer-full");
1904
- }
1905
- buffer.push(event);
1906
- statsState.enqueued += 1;
1907
- statsState.maxBufferDepth = Math.max(statsState.maxBufferDepth, buffer.length);
1908
- if (buffer.length >= batchSize || flushIntervalMs === 0) flushPending().catch((error) => reportInternalError(error, "flush"));
1909
- else schedule();
1910
- },
1911
- flush() {
1912
- return flushPending();
1913
- },
1914
- async count() {
1915
- await flushPending();
1916
- return await countEntries() ?? (await getEntries()).length;
1917
- },
1918
- async clear() {
1919
- if (clearPromise) return clearPromise;
1920
- const inFlightFlush = flushPromise;
1921
- const inFlightDrain = spillDrainPromise;
1922
- buffer.length = 0;
1923
- clearTimer();
1924
- clearLocalStorageSpill();
1925
- clearPromise = (async () => {
1926
- await inFlightFlush;
1927
- await inFlightDrain;
1928
- await withLogAndSessionStores("readwrite", async (store, sessionStore) => {
1929
- await requestToPromise(store.clear());
1930
- await requestToPromise(sessionStore.clear());
1931
- });
1932
- })().finally(() => {
1933
- clearPromise = void 0;
1934
- });
1935
- return clearPromise;
1936
- },
1937
- async remove(ids) {
1938
- await flushPending();
1939
- const items = typeof ids === "string" ? [ids] : ids;
1940
- if (items.length === 0) return;
1941
- await withLogAndSessionStores("readwrite", async (store, sessionStore) => {
1942
- const affectedSessionIds = /* @__PURE__ */ new Set();
1943
- for (const id of items) {
1944
- const entry = await requestToPromise(store.get(id));
1945
- if (entry?.sessionId) affectedSessionIds.add(entry.sessionId);
1946
- await requestToPromise(store.delete(id));
1947
- }
1948
- await rebuildSessionSummaries(store, sessionStore, affectedSessionIds);
1949
- });
1950
- },
1951
- async *query(queryOptions = {}) {
1952
- await flushPending();
1953
- const startedAt = nowMs$1();
1954
- const cursorEntries = await queryEntriesByCursor(queryOptions);
1955
- if (cursorEntries === void 0) statsState.queryFallbacks += 1;
1956
- const entries = cursorEntries ?? orderEntries(await getEntries(), queryOptions.order ?? "asc").filter((entry) => shouldKeepEntry(entry, queryOptions)).slice(0, queryOptions.limit);
1957
- statsState.queries += 1;
1958
- statsState.lastQueryDurationMs = nowMs$1() - startedAt;
1959
- for (const entry of entries) {
1960
- const event = decodeEntry(entry, codec);
1961
- if (event) yield event;
1962
- }
1963
- },
1964
- async sessions(sessionQueryOptions = {}) {
1965
- await flushPending();
1966
- return listSessions(sessionQueryOptions);
1967
- },
1968
- stats() {
1969
- return snapshotStats();
1970
- },
1971
- close() {
1972
- closed = true;
1973
- clearTimer();
1974
- globalThis.removeEventListener?.("pagehide", onPageHide);
1975
- removeVisibilityChangeListener?.();
1976
- removeVisibilityChangeListener = void 0;
1977
- return flushPending().finally(() => {
1978
- const promise = dbPromise;
1979
- dbPromise = void 0;
1980
- return promise?.then((db) => db.close());
1981
- });
1982
- }
1983
- };
1984
- }
1985
- //#endregion
1986
- //#region src/offline-first-transport.ts
1987
- function normalizeBatchSize(value) {
1988
- if (value === void 0 || !Number.isFinite(value) || value <= 0) return 100;
1989
- return Math.floor(value);
1990
- }
1991
- async function collectBatch(queue, replayBatchSize) {
1992
- const events = [];
1993
- for await (const event of queue.query({
1994
- order: "asc",
1995
- limit: replayBatchSize
1996
- })) events.push(event);
1997
- return events;
1998
- }
1999
- function isBrowserOnline(options) {
2000
- if (options.online) return options.online();
2001
- if (typeof navigator === "undefined") return true;
2002
- return navigator.onLine !== false;
2003
- }
2004
- function offlineFirstTransport(remote, options = {}) {
2005
- const name = options.name ?? `offline-first(${remote.name ?? "transport"})`;
2006
- const replayBatchSize = normalizeBatchSize(options.replayBatchSize);
2007
- const queueOptions = options.queueOptions;
2008
- const queue = options.queue ?? indexedDbTransport({
2009
- dbName: "loggerjs-offline",
2010
- flushIntervalMs: 0,
2011
- name: `${name}:queue`,
2012
- storeName: "offline-events",
2013
- ...queueOptions,
2014
- session: queueOptions?.session ?? false
2015
- });
2016
- const reliableRemote = retryTransport(remote, {
2017
- maxRetries: 2,
2018
- retryBaseDelayMs: 250,
2019
- retryMaxDelayMs: 5e3,
2020
- ...options.retry,
2021
- name: `${name}:remote`
2022
- });
2023
- let lastContext;
2024
- let replayPromise;
2025
- const replayContext = {
2026
- loggerName: name,
2027
- now: () => Date.now(),
2028
- toEvent: recordToEvent,
2029
- reportInternalError() {}
2030
- };
2031
- const contextForReplay = () => lastContext ?? replayContext;
2032
- const reportInternalError = (error, operation) => {
2033
- contextForReplay().reportInternalError(error, {
2034
- phase: "transport",
2035
- transport: name,
2036
- operation
2037
- });
2038
- };
2039
- const queueEvent = async (event, context, error) => {
2040
- await queue.log(event, context);
2041
- await queue.flush?.();
2042
- incrementLoggerMetaCounter("transport.offline.queued");
2043
- options.onQueued?.(event, error);
2044
- };
2045
- const sendRemoteBatch = async (events, context) => {
2046
- await reliableRemote.logBatch?.([...events], context);
2047
- };
2048
- const sendOrQueue = async (event, context) => {
2049
- lastContext = context;
2050
- if (!isBrowserOnline(options)) {
2051
- await queueEvent(event, context);
2052
- return;
2053
- }
2054
- try {
2055
- await reliableRemote.log?.(event, context);
2056
- } catch (error) {
2057
- incrementLoggerMetaCounter("transport.offline.remote.failed");
2058
- await queueEvent(event, context, error);
2059
- }
2060
- };
2061
- const replay = async () => {
2062
- if (replayPromise) return replayPromise;
2063
- replayPromise = (async () => {
2064
- if (!isBrowserOnline(options)) return;
2065
- const context = contextForReplay();
2066
- await queue.flush?.();
2067
- for (;;) {
2068
- const batch = await collectBatch(queue, replayBatchSize);
2069
- if (batch.length === 0) return;
2070
- try {
2071
- await sendRemoteBatch(batch, context);
2072
- await queue.remove(batch.map((event) => event.id));
2073
- incrementLoggerMetaCounter("transport.offline.replayed", batch.length);
2074
- options.onReplayed?.(batch);
2075
- } catch (error) {
2076
- incrementLoggerMetaCounter("transport.offline.replay.failed");
2077
- reportInternalError(error, "replay");
2078
- throw error;
2079
- }
2080
- }
2081
- })().finally(() => {
2082
- replayPromise = void 0;
2083
- });
2084
- return replayPromise;
2085
- };
2086
- const onOnline = () => {
2087
- replay().catch((error) => reportInternalError(error, "online-replay"));
2088
- };
2089
- if (options.replayOnOnline ?? true) globalThis.addEventListener?.("online", onOnline);
2090
- return {
2091
- name,
2092
- minLevel: options.minLevel ?? remote.minLevel,
2093
- queue,
2094
- ready: remote.ready && (() => remote.ready?.()),
2095
- write(record, context) {
2096
- return sendOrQueue(context.toEvent(record), context);
2097
- },
2098
- writeBatch(records, context) {
2099
- return Promise.all(records.map((record) => sendOrQueue(context.toEvent(record), context))).then(() => void 0);
2100
- },
2101
- log(event, context) {
2102
- if (options.minLevel !== void 0 && event.level < toLevelValue(options.minLevel)) return;
2103
- return sendOrQueue(event, context);
2104
- },
2105
- logBatch(events, context) {
2106
- return Promise.all(events.map((event) => options.minLevel !== void 0 && event.level < toLevelValue(options.minLevel) ? void 0 : sendOrQueue(event, context))).then(() => void 0);
2107
- },
2108
- replay,
2109
- async flush() {
2110
- await replay();
2111
- await reliableRemote.flush?.();
2112
- await queue.flush?.();
2113
- },
2114
- flushSync() {
2115
- reliableRemote.flushSync?.();
2116
- },
2117
- async close() {
2118
- globalThis.removeEventListener?.("online", onOnline);
2119
- await replay();
2120
- await reliableRemote.close?.();
2121
- await queue.close?.();
2122
- },
2123
- async queuedCount() {
2124
- await queue.flush?.();
2125
- return queue.count?.();
2126
- },
2127
- async clearQueue() {
2128
- await queue.clear?.();
2129
- }
2130
- };
2131
- }
2132
- //#endregion
2133
- //#region src/zip-export.ts
2134
- const ZIP_UTF8_FLAG = 2048;
2135
- const ZIP_STORE_METHOD = 0;
2136
- const ZIP_VERSION = 20;
2137
- const ZIP_MAX_UINT16 = 65535;
2138
- const ZIP_MAX_UINT32 = 4294967295;
2139
- const LOCAL_FILE_HEADER_SIGNATURE = 67324752;
2140
- const CENTRAL_DIRECTORY_SIGNATURE = 33639248;
2141
- const END_OF_CENTRAL_DIRECTORY_SIGNATURE = 101010256;
2142
- const DEFAULT_SESSION_CONTEXT_KEY = "sessionId";
2143
- let crc32Table;
2144
- function getCrc32Table() {
2145
- if (crc32Table) return crc32Table;
2146
- const table = new Uint32Array(256);
2147
- for (let index = 0; index < table.length; index += 1) {
2148
- let crc = index;
2149
- for (let bit = 0; bit < 8; bit += 1) crc = crc & 1 ? 3988292384 ^ crc >>> 1 : crc >>> 1;
2150
- table[index] = crc >>> 0;
2151
- }
2152
- crc32Table = table;
2153
- return table;
2154
- }
2155
- function crc32(bytes) {
2156
- const table = getCrc32Table();
2157
- let crc = 4294967295;
2158
- for (const byte of bytes) crc = crc >>> 8 ^ (table[(crc ^ byte) & 255] ?? 0);
2159
- return (crc ^ 4294967295) >>> 0;
2160
- }
2161
- function encodeText(value) {
2162
- return new TextEncoder().encode(value);
2163
- }
2164
- function normalizeFileContent(content) {
2165
- if (typeof content === "string") return encodeText(content);
2166
- if (content instanceof Uint8Array) return content;
2167
- return new Uint8Array(content);
2168
- }
2169
- function normalizeZipFileName(name) {
2170
- const normalized = name.replace(/\\/g, "/").split("/").filter((part) => part.length > 0 && part !== "." && part !== "..").join("/");
2171
- if (!normalized) throw new Error("ZIP file name cannot be empty");
2172
- return normalized;
2173
- }
2174
- function toDosDateTime(timestamp) {
2175
- const date = new Date(Number.isFinite(timestamp) ? timestamp : Date.now());
2176
- const year = Math.min(2107, Math.max(1980, date.getFullYear()));
2177
- return {
2178
- time: date.getHours() << 11 | date.getMinutes() << 5 | Math.floor(date.getSeconds() / 2),
2179
- date: year - 1980 << 9 | date.getMonth() + 1 << 5 | date.getDate()
2180
- };
2181
- }
2182
- function assertZipUint16(value, label) {
2183
- if (!Number.isSafeInteger(value) || value < 0 || value > ZIP_MAX_UINT16) throw new Error(`${label} exceeds ZIP32 limit`);
2184
- }
2185
- function assertZipUint32(value, label) {
2186
- if (!Number.isSafeInteger(value) || value < 0 || value > ZIP_MAX_UINT32) throw new Error(`${label} exceeds ZIP32 limit`);
2187
- }
2188
- function createLocalFileHeader(file) {
2189
- const header = new Uint8Array(30);
2190
- const view = new DataView(header.buffer);
2191
- view.setUint32(0, LOCAL_FILE_HEADER_SIGNATURE, true);
2192
- view.setUint16(4, ZIP_VERSION, true);
2193
- view.setUint16(6, ZIP_UTF8_FLAG, true);
2194
- view.setUint16(8, ZIP_STORE_METHOD, true);
2195
- view.setUint16(10, file.modifiedTime, true);
2196
- view.setUint16(12, file.modifiedDate, true);
2197
- view.setUint32(14, file.crc32, true);
2198
- view.setUint32(18, file.contentBytes.byteLength, true);
2199
- view.setUint32(22, file.contentBytes.byteLength, true);
2200
- view.setUint16(26, file.nameBytes.byteLength, true);
2201
- view.setUint16(28, 0, true);
2202
- return header;
2203
- }
2204
- function createCentralDirectoryHeader(file) {
2205
- const header = new Uint8Array(46);
2206
- const view = new DataView(header.buffer);
2207
- view.setUint32(0, CENTRAL_DIRECTORY_SIGNATURE, true);
2208
- view.setUint16(4, ZIP_VERSION, true);
2209
- view.setUint16(6, ZIP_VERSION, true);
2210
- view.setUint16(8, ZIP_UTF8_FLAG, true);
2211
- view.setUint16(10, ZIP_STORE_METHOD, true);
2212
- view.setUint16(12, file.modifiedTime, true);
2213
- view.setUint16(14, file.modifiedDate, true);
2214
- view.setUint32(16, file.crc32, true);
2215
- view.setUint32(20, file.contentBytes.byteLength, true);
2216
- view.setUint32(24, file.contentBytes.byteLength, true);
2217
- view.setUint16(28, file.nameBytes.byteLength, true);
2218
- view.setUint16(30, 0, true);
2219
- view.setUint16(32, 0, true);
2220
- view.setUint16(34, 0, true);
2221
- view.setUint16(36, 0, true);
2222
- view.setUint32(38, 0, true);
2223
- view.setUint32(42, file.localHeaderOffset, true);
2224
- return header;
2225
- }
2226
- function createEndOfCentralDirectory(fileCount, centralDirectorySize, centralDirectoryOffset) {
2227
- assertZipUint16(fileCount, "ZIP file count");
2228
- assertZipUint32(centralDirectorySize, "ZIP central directory size");
2229
- assertZipUint32(centralDirectoryOffset, "ZIP central directory offset");
2230
- const footer = new Uint8Array(22);
2231
- const view = new DataView(footer.buffer);
2232
- view.setUint32(0, END_OF_CENTRAL_DIRECTORY_SIGNATURE, true);
2233
- view.setUint16(4, 0, true);
2234
- view.setUint16(6, 0, true);
2235
- view.setUint16(8, fileCount, true);
2236
- view.setUint16(10, fileCount, true);
2237
- view.setUint32(12, centralDirectorySize, true);
2238
- view.setUint32(16, centralDirectoryOffset, true);
2239
- view.setUint16(20, 0, true);
2240
- return footer;
2241
- }
2242
- function createNormalizedZipFiles(files, options) {
2243
- const names = /* @__PURE__ */ new Set();
2244
- let offset = 0;
2245
- return files.map((file) => {
2246
- const name = normalizeZipFileName(file.name);
2247
- if (names.has(name)) throw new Error(`Duplicate ZIP file name: ${name}`);
2248
- names.add(name);
2249
- const nameBytes = encodeText(name);
2250
- const contentBytes = normalizeFileContent(file.content);
2251
- assertZipUint16(nameBytes.byteLength, `ZIP file name ${name}`);
2252
- assertZipUint32(contentBytes.byteLength, `ZIP file ${name}`);
2253
- assertZipUint32(offset, "ZIP local header offset");
2254
- const modified = toDosDateTime(file.lastModified ?? options.lastModified ?? Date.now());
2255
- const normalized = {
2256
- name,
2257
- nameBytes,
2258
- contentBytes,
2259
- crc32: crc32(contentBytes),
2260
- modifiedTime: modified.time,
2261
- modifiedDate: modified.date,
2262
- localHeaderOffset: offset
2263
- };
2264
- offset += 30 + nameBytes.byteLength + contentBytes.byteLength;
2265
- assertZipUint32(offset, "ZIP local data size");
2266
- return normalized;
2267
- });
2268
- }
2269
- function isQuerySource(source) {
2270
- return "query" in source && typeof source.query === "function";
2271
- }
2272
- function resolveSource(source, query) {
2273
- if (isQuerySource(source)) return source.query(query);
2274
- if (query) throw new Error("Log ZIP export query options require a source with query()");
2275
- return source;
2276
- }
2277
- function normalizeMaxEvents(value) {
2278
- if (value === void 0) return void 0;
2279
- if (!Number.isFinite(value)) return void 0;
2280
- return Math.max(0, Math.floor(value));
2281
- }
2282
- function normalizeSessionExportOptions(value, logFileName) {
2283
- if (!value) return void 0;
2284
- const options = value === true ? {} : value;
2285
- return {
2286
- contextKey: options.contextKey ?? DEFAULT_SESSION_CONTEXT_KEY,
2287
- directory: options.directory ?? "sessions",
2288
- fallbackSessionId: options.fallbackSessionId ?? "unknown",
2289
- logFileName: options.logFileName ?? logFileName
2290
- };
2291
- }
2292
- function normalizeRecentExportOptions(value, format) {
2293
- if (!value) return void 0;
2294
- const options = value === true ? {} : value;
2295
- return {
2296
- logFileName: options.logFileName ?? (format === "json" ? "recent.json" : "recent.ndjson"),
2297
- maxEvents: normalizeMaxEvents(options.maxEvents) ?? 100
2298
- };
2299
- }
2300
- function logContentForLines(lines, format) {
2301
- if (format === "json") return `[${lines.join(",")}]`;
2302
- return lines.length > 0 ? `${lines.join("\n")}\n` : "";
2303
- }
2304
- function eventTimeRange(items) {
2305
- let from;
2306
- let to;
2307
- for (const item of items) {
2308
- from = from === void 0 ? item.event.time : Math.min(from, item.event.time);
2309
- to = to === void 0 ? item.event.time : Math.max(to, item.event.time);
2310
- }
2311
- return from === void 0 || to === void 0 ? void 0 : {
2312
- from,
2313
- to
2314
- };
2315
- }
2316
- function sessionIdForEvent(event, options) {
2317
- const value = event.context?.[options.contextKey];
2318
- return typeof value === "string" && value.length > 0 ? value : options.fallbackSessionId;
2319
- }
2320
- function safeZipPathSegment(value) {
2321
- const encoded = encodeURIComponent(value).replace(/%/g, "_");
2322
- if (!encoded || encoded === "." || encoded === "..") return "_";
2323
- return encoded;
2324
- }
2325
- function sortByEventTime(items) {
2326
- return [...items].sort((left, right) => {
2327
- if (left.event.time !== right.event.time) return left.event.time - right.event.time;
2328
- if (left.event.seq !== right.event.seq) return left.event.seq - right.event.seq;
2329
- return left.event.id < right.event.id ? -1 : left.event.id > right.event.id ? 1 : 0;
2330
- });
2331
- }
2332
- function createLogZipBlob(files, options = {}) {
2333
- const normalizedFiles = createNormalizedZipFiles(files, options);
2334
- const parts = [];
2335
- let centralDirectoryOffset = 0;
2336
- for (const file of normalizedFiles) {
2337
- parts.push(createLocalFileHeader(file), file.nameBytes, file.contentBytes);
2338
- centralDirectoryOffset += 30 + file.nameBytes.byteLength + file.contentBytes.byteLength;
2339
- }
2340
- let centralDirectorySize = 0;
2341
- for (const file of normalizedFiles) {
2342
- const header = createCentralDirectoryHeader(file);
2343
- parts.push(header, file.nameBytes);
2344
- centralDirectorySize += header.byteLength + file.nameBytes.byteLength;
2345
- }
2346
- parts.push(createEndOfCentralDirectory(normalizedFiles.length, centralDirectorySize, centralDirectoryOffset));
2347
- return new Blob(parts, { type: options.type ?? "application/zip" });
2348
- }
2349
- async function exportLogsToZip(source, options = {}) {
2350
- const format = options.format ?? "ndjson";
2351
- const createdAt = options.createdAt ?? Date.now();
2352
- const logFileName = options.logFileName ?? (format === "json" ? "logs.json" : "logs.ndjson");
2353
- const maxEvents = normalizeMaxEvents(options.maxEvents);
2354
- const sessionExportOptions = normalizeSessionExportOptions(options.groupBySession, logFileName);
2355
- const recentExportOptions = normalizeRecentExportOptions(options.includeRecent, format);
2356
- const serialize = options.serializeEvent ?? ((event) => safeJsonStringify(event, options.stringify ?? {}));
2357
- const exported = [];
2358
- let skippedCount = 0;
2359
- for await (const event of resolveSource(source, options.query)) {
2360
- if (maxEvents !== void 0 && exported.length >= maxEvents) break;
2361
- const mapped = options.mapEvent ? options.mapEvent(event) : event;
2362
- if (!mapped) {
2363
- skippedCount += 1;
2364
- continue;
2365
- }
2366
- exported.push({
2367
- event: mapped,
2368
- line: serialize(mapped)
2369
- });
2370
- }
2371
- const files = [{
2372
- name: logFileName,
2373
- content: logContentForLines(exported.map((item) => item.line), format),
2374
- lastModified: createdAt
2375
- }];
2376
- const sessionManifests = [];
2377
- if (sessionExportOptions) {
2378
- const groups = /* @__PURE__ */ new Map();
2379
- for (const item of exported) {
2380
- const sessionId = sessionIdForEvent(item.event, sessionExportOptions);
2381
- const group = groups.get(sessionId) ?? [];
2382
- group.push(item);
2383
- groups.set(sessionId, group);
2384
- }
2385
- const usedFileNames = new Set(files.map((file) => normalizeZipFileName(file.name)));
2386
- const sortedGroups = [...groups.entries()];
2387
- sortedGroups.sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0);
2388
- for (const [sessionId, group] of sortedGroups) {
2389
- let fileName = normalizeZipFileName(`${sessionExportOptions.directory}/${safeZipPathSegment(sessionId)}/${sessionExportOptions.logFileName}`);
2390
- let suffix = 1;
2391
- while (usedFileNames.has(fileName)) {
2392
- fileName = normalizeZipFileName(`${sessionExportOptions.directory}/${safeZipPathSegment(sessionId)}-${suffix}/${sessionExportOptions.logFileName}`);
2393
- suffix += 1;
2394
- }
2395
- usedFileNames.add(fileName);
2396
- const orderedGroup = sortByEventTime(group);
2397
- files.push({
2398
- name: fileName,
2399
- content: logContentForLines(orderedGroup.map((item) => item.line), format),
2400
- lastModified: createdAt
2401
- });
2402
- sessionManifests.push({
2403
- logCount: orderedGroup.length,
2404
- logFileName: fileName,
2405
- sessionId,
2406
- timeRange: eventTimeRange(orderedGroup)
2407
- });
2408
- }
2409
- }
2410
- let recentLogFileName;
2411
- if (recentExportOptions) {
2412
- const recentItems = recentExportOptions.maxEvents <= 0 ? [] : sortByEventTime(exported).slice(-recentExportOptions.maxEvents);
2413
- recentLogFileName = normalizeZipFileName(recentExportOptions.logFileName);
2414
- files.push({
2415
- name: recentLogFileName,
2416
- content: logContentForLines(recentItems.map((item) => item.line), format),
2417
- lastModified: createdAt
2418
- });
2419
- }
2420
- if (options.includeManifest ?? true) {
2421
- const manifest = {
2422
- schema: "loggerjs.log-export.v1",
2423
- createdAt: new Date(createdAt).toISOString(),
2424
- source: options.source,
2425
- format,
2426
- logFileName: normalizeZipFileName(logFileName),
2427
- logCount: exported.length,
2428
- skippedCount,
2429
- query: options.query,
2430
- recentLogFileName,
2431
- sessionCount: sessionExportOptions ? sessionManifests.length : void 0,
2432
- sessions: sessionExportOptions ? sessionManifests : void 0,
2433
- timeRange: eventTimeRange(exported)
2434
- };
2435
- files.push({
2436
- name: options.manifestFileName ?? "manifest.json",
2437
- content: `${safeJsonStringify(manifest, { space: 2 })}\n`,
2438
- lastModified: createdAt
2439
- });
2440
- }
2441
- return createLogZipBlob(files, { lastModified: createdAt });
2442
- }
2443
- function downloadBlob(blob, filenameOrOptions = {}) {
2444
- const options = typeof filenameOrOptions === "string" ? { filename: filenameOrOptions } : filenameOrOptions;
2445
- const documentRef = options.document ?? globalThis.document;
2446
- const urlRef = options.url ?? globalThis.URL;
2447
- if (!documentRef?.body || !urlRef?.createObjectURL || !urlRef.revokeObjectURL) throw new Error("downloadBlob requires document and URL.createObjectURL");
2448
- const href = urlRef.createObjectURL(blob);
2449
- const anchor = documentRef.createElement("a");
2450
- anchor.href = href;
2451
- anchor.download = options.filename ?? "loggerjs-logs.zip";
2452
- anchor.style.display = "none";
2453
- documentRef.body.appendChild(anchor);
2454
- anchor.click();
2455
- anchor.remove();
2456
- const revoke = () => urlRef.revokeObjectURL(href);
2457
- const revokeDelayMs = options.revokeDelayMs ?? 1e3;
2458
- if (revokeDelayMs <= 0) revoke();
2459
- else setTimeout(revoke, revokeDelayMs);
2460
- return href;
2461
- }
2462
- //#endregion
2463
- //#region src/console-integration.ts
2464
- const defaultLevels = [
2465
- "debug",
2466
- "info",
2467
- "log",
2468
- "trace",
2469
- "warn",
2470
- "error"
2471
- ];
2472
- const levelMap = {
2473
- debug: "debug",
2474
- info: "info",
2475
- log: "info",
2476
- trace: "trace",
2477
- warn: "warn",
2478
- error: "error"
2479
- };
2480
- function formatConsoleMessage(args) {
2481
- return args.map((arg) => {
2482
- if (typeof arg === "string") return arg;
2483
- if (arg instanceof Error) return arg.message;
2484
- try {
2485
- return JSON.stringify(normalizeValue(arg, {
2486
- maxDepth: 3,
2487
- maxArrayLength: 20,
2488
- maxObjectKeys: 40
2489
- }));
2490
- } catch {
2491
- return String(arg);
2492
- }
2493
- }).join(" ");
2494
- }
2495
- function reportRateLimitDrop() {
2496
- incrementLoggerMetaCounter("integration.dropped");
2497
- incrementLoggerMetaCounter("integration.dropped.rate-limit");
2498
- }
2499
- function captureConsoleIntegration(options = {}) {
2500
- const levels = options.levels ?? defaultLevels;
2501
- const preserveConsole = options.preserveConsole ?? true;
2502
- const captureArguments = options.captureArguments ?? false;
2503
- const maxCapturesPerSecond = options.maxCapturesPerSecond ?? 100;
2504
- return {
2505
- name: "capture-console",
2506
- setup(api) {
2507
- if (typeof console === "undefined") return;
2508
- const originals = {};
2509
- const boundOriginals = {};
2510
- let windowStarted = Date.now();
2511
- let capturesInWindow = 0;
2512
- let disposed = false;
2513
- const shouldCapture = () => {
2514
- const now = Date.now();
2515
- if (now - windowStarted >= 1e3) {
2516
- windowStarted = now;
2517
- capturesInWindow = 0;
2518
- }
2519
- if (capturesInWindow >= maxCapturesPerSecond) {
2520
- reportRateLimitDrop();
2521
- return false;
2522
- }
2523
- capturesInWindow += 1;
2524
- return true;
2525
- };
2526
- for (const level of levels) {
2527
- const original = console[level] ?? console.log.bind(console);
2528
- originals[level] = original;
2529
- const unpatched = api.unpatched.console[level] ?? original;
2530
- api.unpatched.console[level] ??= original;
2531
- boundOriginals[level] = unpatched.bind(console);
2532
- const capture = api.guard((args) => {
2533
- if (!shouldCapture()) return;
2534
- const consoleData = { level };
2535
- if (captureArguments) consoleData.arguments = normalizeValue(args, { maxDepth: 4 });
2536
- api.capture({
2537
- level: levelMap[level],
2538
- message: formatConsoleMessage(args),
2539
- props: { console: consoleData }
2540
- });
2541
- });
2542
- console[level] = (...args) => {
2543
- capture(args);
2544
- if (preserveConsole) boundOriginals[level]?.(...args);
2545
- };
2546
- }
2547
- return () => {
2548
- if (disposed) return;
2549
- disposed = true;
2550
- for (const level of levels) if (originals[level]) console[level] = originals[level];
2551
- };
2552
- }
2553
- };
2554
- }
2555
- //#endregion
2556
- //#region src/context-propagation-integration.ts
2557
- const defaultActionEvents = [
2558
- "click",
2559
- "input",
2560
- "change",
2561
- "submit"
2562
- ];
2563
- let fallbackIdSeq = 0;
2564
- function defaultIdFactory() {
2565
- const cryptoRandomUUID = globalThis.crypto?.randomUUID;
2566
- if (cryptoRandomUUID) return cryptoRandomUUID.call(globalThis.crypto);
2567
- return `${Date.now().toString(36)}-${(fallbackIdSeq++).toString(36)}`;
2568
- }
2569
- function resolveValue(value) {
2570
- return typeof value === "function" ? value() : value;
2571
- }
2572
- function normalizeBaggage(value) {
2573
- if (typeof value === "string") return parseBaggage(value);
2574
- return value;
2575
- }
2576
- function actionTargetName(target) {
2577
- const item = target;
2578
- if (!item) return void 0;
2579
- return item.id || item.name || item.tagName?.toLowerCase();
2580
- }
2581
- function browserContextPropagationIntegration(options = {}) {
2582
- const actionEvents = options.actionEvents ?? defaultActionEvents;
2583
- const actionTtlMs = Math.max(0, Math.floor(options.actionTtlMs ?? 5e3));
2584
- const clock = options.clock ?? Date.now;
2585
- const idFactory = options.idFactory ?? defaultIdFactory;
2586
- let lastAction;
2587
- return {
2588
- name: "browser-context-propagation",
2589
- setup() {
2590
- const root = options.root ?? globalThis.document;
2591
- const disposeProvider = addContextProvider(() => {
2592
- const sessionId = resolveValue(options.sessionId);
2593
- const requestId = options.requestId?.();
2594
- const trace = resolveValue(options.trace) ?? parseTraceparent(resolveValue(options.traceparent));
2595
- const baggage = normalizeBaggage(resolveValue(options.baggage)) ?? trace?.baggage;
2596
- const traceparent = formatTraceparent(trace);
2597
- const now = clock();
2598
- const action = lastAction && now - lastAction.time <= actionTtlMs ? {
2599
- actionId: lastAction.id,
2600
- actionType: lastAction.type,
2601
- actionTarget: lastAction.target
2602
- } : void 0;
2603
- return {
2604
- sessionId,
2605
- requestId,
2606
- traceparent,
2607
- baggage: formatBaggage(baggage),
2608
- ...action
2609
- };
2610
- });
2611
- if (!root?.addEventListener || !root.removeEventListener || actionEvents.length === 0) return disposeProvider;
2612
- const onAction = (event) => {
2613
- lastAction = {
2614
- id: idFactory(),
2615
- type: event.type,
2616
- target: actionTargetName(event.target),
2617
- time: clock()
2618
- };
2619
- };
2620
- for (const event of actionEvents) root.addEventListener(event, onAction, true);
2621
- return () => {
2622
- for (const event of actionEvents) root.removeEventListener(event, onAction, true);
2623
- disposeProvider();
2624
- };
2625
- }
2626
- };
2627
- }
2628
- //#endregion
2629
- //#region src/error-integration.ts
2630
- function resourceInfo(target) {
2631
- const element = target;
2632
- return {
2633
- tagName: element?.tagName,
2634
- url: element?.src || element?.currentSrc || element?.href,
2635
- html: element?.outerHTML?.slice(0, 500)
2636
- };
2637
- }
2638
- function reportDrop(reason) {
2639
- incrementLoggerMetaCounter("integration.dropped");
2640
- incrementLoggerMetaCounter(`integration.dropped.${reason}`);
2641
- }
2642
- function captureBrowserErrorsIntegration(options = {}) {
2643
- const captureWindowError = options.captureWindowError ?? true;
2644
- const captureUnhandledRejection = options.captureUnhandledRejection ?? true;
2645
- const captureResourceErrors = options.captureResourceErrors ?? true;
2646
- const captureSecurityPolicyViolation = options.captureSecurityPolicyViolation ?? false;
2647
- const scriptErrorDedupeWindowMs = options.scriptErrorDedupeWindowMs ?? 1e3;
2648
- return {
2649
- name: "capture-browser-errors",
2650
- setup(api) {
2651
- const disposers = [];
2652
- let lastScriptErrorAt = 0;
2653
- let disposed = false;
2654
- const shouldDropScriptError = (message) => {
2655
- if (message !== "Script error.") return false;
2656
- const now = Date.now();
2657
- if (now - lastScriptErrorAt <= scriptErrorDedupeWindowMs) {
2658
- reportDrop("script-error-duplicate");
2659
- return true;
2660
- }
2661
- lastScriptErrorAt = now;
2662
- return false;
2663
- };
2664
- if (captureWindowError && typeof addEventListener !== "undefined") {
2665
- const onError = api.guard((event) => {
2666
- const errorEvent = event;
2667
- if ("message" in errorEvent && shouldDropScriptError(errorEvent.message)) return;
2668
- if ("error" in errorEvent && errorEvent.error) {
2669
- api.capture({
2670
- level: "error",
2671
- message: errorEvent.message || errorEvent.error.message,
2672
- error: errorEvent.error,
2673
- props: { browser: {
2674
- kind: "script-error",
2675
- file: errorEvent.filename,
2676
- line: errorEvent.lineno,
2677
- column: errorEvent.colno
2678
- } }
2679
- });
2680
- return;
2681
- }
2682
- if ("message" in errorEvent && errorEvent.message) {
2683
- api.capture({
2684
- level: "error",
2685
- message: errorEvent.message,
2686
- props: { browser: {
2687
- kind: "script-error",
2688
- file: errorEvent.filename,
2689
- line: errorEvent.lineno,
2690
- column: errorEvent.colno
2691
- } }
2692
- });
2693
- return;
2694
- }
2695
- if (captureResourceErrors) api.capture({
2696
- level: "error",
2697
- message: "Browser resource load error",
2698
- props: { browser: {
2699
- kind: "resource-error",
2700
- ...resourceInfo(event.target)
2701
- } }
2702
- });
2703
- });
2704
- addEventListener("error", onError, true);
2705
- disposers.push(() => removeEventListener("error", onError, true));
2706
- }
2707
- if (captureUnhandledRejection && typeof addEventListener !== "undefined") {
2708
- const onUnhandledRejection = api.guard((event) => {
2709
- const reason = event.reason;
2710
- api.capture({
2711
- level: "error",
2712
- message: reason instanceof Error ? reason.message : "Unhandled promise rejection",
2713
- error: reason,
2714
- props: {
2715
- browser: { kind: "unhandledrejection" },
2716
- reason: normalizeValue(reason, { maxDepth: 5 })
2717
- }
2718
- });
2719
- });
2720
- addEventListener("unhandledrejection", onUnhandledRejection);
2721
- disposers.push(() => removeEventListener("unhandledrejection", onUnhandledRejection));
2722
- }
2723
- if (captureSecurityPolicyViolation && typeof addEventListener !== "undefined") {
2724
- const onSecurityPolicyViolation = api.guard((event) => {
2725
- api.capture({
2726
- level: "warn",
2727
- message: `Security policy violation: ${event.violatedDirective}`,
2728
- props: { browser: {
2729
- kind: "securitypolicyviolation",
2730
- blockedURI: event.blockedURI,
2731
- documentURI: event.documentURI,
2732
- file: event.sourceFile,
2733
- line: event.lineNumber,
2734
- column: event.columnNumber,
2735
- originalPolicy: event.originalPolicy,
2736
- effectiveDirective: event.effectiveDirective
2737
- } }
2738
- });
2739
- });
2740
- addEventListener("securitypolicyviolation", onSecurityPolicyViolation);
2741
- disposers.push(() => removeEventListener("securitypolicyviolation", onSecurityPolicyViolation));
2742
- }
2743
- return () => {
2744
- if (disposed) return;
2745
- disposed = true;
2746
- for (const dispose of disposers) dispose();
2747
- };
2748
- }
2749
- };
2750
- }
2751
- //#endregion
2752
- //#region src/http-capture-utils.ts
2753
- function nowMs() {
2754
- return typeof performance !== "undefined" ? performance.now() : Date.now();
2755
- }
2756
- function durationMs(started) {
2757
- return nowMs() - started;
2758
- }
2759
- function sanitizeHttpUrl(rawUrl, sanitizer) {
2760
- const stripped = stripSensitiveUrlParts(rawUrl);
2761
- return sanitizer ? sanitizer(stripped) : stripped;
2762
- }
2763
- function pickAllowedHeaders(headers, allowList) {
2764
- if (!headers || !allowList || allowList.length === 0) return void 0;
2765
- const out = {};
2766
- for (const name of allowList) {
2767
- const value = headers.get(name);
2768
- if (value !== null) out[name.toLowerCase()] = value;
2769
- }
2770
- return Object.keys(out).length > 0 ? out : void 0;
2771
- }
2772
- function headersFromInit(headers) {
2773
- if (!headers) return void 0;
2774
- try {
2775
- return new Headers(headers);
2776
- } catch {
2777
- return;
2778
- }
2779
- }
2780
- function shouldSample(sampleRate, random) {
2781
- if (sampleRate >= 1) return true;
2782
- if (sampleRate <= 0) return false;
2783
- return random() < sampleRate;
2784
- }
2785
- function stripSensitiveUrlParts(rawUrl) {
2786
- if (!/^[a-zA-Z][a-zA-Z\d+.-]*:/.test(rawUrl)) return rawUrl.split(/[?#]/, 1)[0] ?? rawUrl;
2787
- try {
2788
- const url = new URL(rawUrl);
2789
- url.search = "";
2790
- url.hash = "";
2791
- return url.toString();
2792
- } catch {
2793
- return rawUrl.split(/[?#]/, 1)[0] ?? rawUrl;
2794
- }
2795
- }
2796
- //#endregion
2797
- //#region src/fetch-integration.ts
2798
- function requestUrl(input) {
2799
- if (typeof input === "string") return input;
2800
- if (input instanceof URL) return input.toString();
2801
- return input.url;
2802
- }
2803
- function requestMethod(input, init) {
2804
- if (init?.method) return init.method.toUpperCase();
2805
- if (typeof input === "object" && "method" in input && input.method) return input.method.toUpperCase();
2806
- return "GET";
2807
- }
2808
- function captureFetchIntegration(options = {}) {
2809
- const minStatus = options.minStatus ?? 400;
2810
- const captureAll = options.captureAll ?? options.captureSuccessful ?? false;
2811
- const sampleRate = options.sampleRate ?? 1;
2812
- const random = options.random ?? Math.random;
2813
- return {
2814
- name: "capture-fetch",
2815
- setup(api) {
2816
- const current = globalThis.fetch;
2817
- if (!current) return;
2818
- const original = api.unpatched.fetch ?? current.bind(globalThis);
2819
- api.unpatched.fetch ??= original;
2820
- const capture = api.guard((input) => api.capture(input));
2821
- globalThis.fetch = async (input, init) => {
2822
- const started = nowMs();
2823
- const url = sanitizeHttpUrl(requestUrl(input), options.sanitizeUrl);
2824
- const method = requestMethod(input, init);
2825
- try {
2826
- const response = await original(input, init);
2827
- if (response.status >= minStatus || captureAll && shouldSample(sampleRate, random)) capture({
2828
- level: response.status >= minStatus ? "warn" : "debug",
2829
- message: `Fetch ${response.status} ${method} ${url}`,
2830
- props: { http: {
2831
- kind: "fetch",
2832
- method,
2833
- url,
2834
- status: response.status,
2835
- ok: response.ok,
2836
- durationMs: durationMs(started),
2837
- requestHeaders: pickAllowedHeaders(headersFromInit(init?.headers), options.captureRequestHeaders),
2838
- responseHeaders: pickAllowedHeaders(response.headers, options.captureResponseHeaders)
2839
- } }
2840
- });
2841
- return response;
2842
- } catch (error) {
2843
- capture({
2844
- level: "error",
2845
- message: `Fetch network error ${method} ${url}`,
2846
- error,
2847
- props: {
2848
- http: {
2849
- kind: "fetch",
2850
- method,
2851
- url,
2852
- durationMs: durationMs(started)
2853
- },
2854
- input: normalizeValue(input, { maxDepth: 3 })
2855
- }
2856
- });
2857
- throw error;
2858
- }
2859
- };
2860
- return () => {
2861
- globalThis.fetch = current;
2862
- };
2863
- }
2864
- };
2865
- }
2866
- //#endregion
2867
- //#region src/xhr-integration.ts
2868
- const XHR_META = "__LOGGERJS_XHR_META__";
2869
- function captureXHRIntegration(options = {}) {
2870
- const minStatus = options.minStatus ?? 400;
2871
- const captureAll = options.captureAll ?? options.captureSuccessful ?? false;
2872
- const sampleRate = options.sampleRate ?? 1;
2873
- const random = options.random ?? Math.random;
2874
- return {
2875
- name: "capture-xhr",
2876
- setup(api) {
2877
- if (typeof XMLHttpRequest === "undefined") return;
2878
- const proto = XMLHttpRequest.prototype;
2879
- const originalOpen = proto.open;
2880
- const originalSend = proto.send;
2881
- api.unpatched.XMLHttpRequest ??= XMLHttpRequest;
2882
- const capture = api.guard((input) => api.capture(input));
2883
- proto.open = function patchedOpen(method, url, ...rest) {
2884
- this[XHR_META] = {
2885
- method: method.toUpperCase(),
2886
- url: sanitizeHttpUrl(String(url), options.sanitizeUrl),
2887
- started: 0
2888
- };
2889
- return originalOpen.apply(this, [
2890
- method,
2891
- url,
2892
- ...rest
2893
- ]);
2894
- };
2895
- proto.send = function patchedSend(body) {
2896
- const meta = this[XHR_META];
2897
- if (meta) meta.started = nowMs();
2898
- let captured = false;
2899
- const onLoadEnd = () => {
2900
- if (!meta || captured) return;
2901
- if (this.status >= minStatus || captureAll && shouldSample(sampleRate, random)) {
2902
- captured = true;
2903
- capture({
2904
- level: this.status >= minStatus ? "warn" : "debug",
2905
- message: `XHR ${this.status} ${meta.method} ${meta.url}`,
2906
- props: { http: {
2907
- kind: "xhr",
2908
- method: meta.method,
2909
- url: meta.url,
2910
- status: this.status,
2911
- durationMs: durationMs(meta.started)
2912
- } }
2913
- });
2914
- }
2915
- };
2916
- const onError = () => {
2917
- if (!meta || captured) return;
2918
- captured = true;
2919
- capture({
2920
- level: "error",
2921
- message: `XHR network error ${meta.method} ${meta.url}`,
2922
- props: { http: {
2923
- kind: "xhr",
2924
- method: meta.method,
2925
- url: meta.url,
2926
- durationMs: durationMs(meta.started)
2927
- } }
2928
- });
2929
- };
2930
- this.addEventListener("loadend", onLoadEnd, { once: true });
2931
- this.addEventListener("error", onError, { once: true });
2932
- return originalSend.apply(this, [body]);
2933
- };
2934
- return () => {
2935
- proto.open = originalOpen;
2936
- proto.send = originalSend;
2937
- };
2938
- }
2939
- };
2940
- }
2941
- //#endregion
2942
- //#region src/framework-error-integration.ts
2943
- function errorMessage(error) {
2944
- return error instanceof Error ? error.message : "Framework error";
2945
- }
2946
- function instanceComponentName(instance) {
2947
- const record = instance;
2948
- return record?.$options?.name ?? record?.type?.displayName ?? record?.type?.name ?? record?.constructor?.name;
2949
- }
2950
- function normalizeFrameworkInfo(input, fallbackFramework) {
2951
- if (typeof input === "string") return {
2952
- framework: fallbackFramework,
2953
- info: input
2954
- };
2955
- return {
2956
- framework: fallbackFramework,
2957
- ...input
2958
- };
2959
- }
2960
- function captureFrameworkErrorsIntegration(options = {}) {
2961
- const integrationName = options.name ?? "capture-framework-errors";
2962
- const fallbackFramework = options.framework ?? "framework";
2963
- const level = options.level ?? "error";
2964
- const maxPending = Math.max(0, Math.floor(options.maxPending ?? 10));
2965
- const infoMaxDepth = options.infoMaxDepth ?? 6;
2966
- const pending = [];
2967
- let api;
2968
- let started = false;
2969
- let disposed = false;
2970
- const emit = (error, rawInfo) => {
2971
- const info = normalizeFrameworkInfo(rawInfo, fallbackFramework);
2972
- const message = options.getMessage?.(error, info) ?? errorMessage(error);
2973
- api?.capture({
2974
- level,
2975
- message,
2976
- error,
2977
- props: { browser: {
2978
- kind: "framework-error",
2979
- framework: info.framework,
2980
- componentName: info.componentName,
2981
- componentStack: info.componentStack,
2982
- info: normalizeValue(info.info, {
2983
- maxDepth: infoMaxDepth,
2984
- maxObjectKeys: 80
2985
- }),
2986
- props: normalizeValue(info.props, {
2987
- maxDepth: infoMaxDepth,
2988
- maxObjectKeys: 80
2989
- })
2990
- } }
2991
- });
2992
- };
2993
- const capture = (error, info) => {
2994
- if (api) {
2995
- emit(error, info);
2996
- return;
2997
- }
2998
- if (started || disposed || pending.length >= maxPending) return;
2999
- pending.push({
3000
- error,
3001
- info
3002
- });
3003
- };
3004
- return {
3005
- name: integrationName,
3006
- setup(nextApi) {
3007
- api = nextApi;
3008
- started = true;
3009
- disposed = false;
3010
- for (const item of pending.splice(0)) emit(item.error, item.info);
3011
- return () => {
3012
- api = void 0;
3013
- disposed = true;
3014
- };
3015
- },
3016
- capture,
3017
- reactComponentDidCatch(error, errorInfo) {
3018
- capture(error, {
3019
- framework: "react",
3020
- componentStack: errorInfo?.componentStack
3021
- });
3022
- },
3023
- vueErrorHandler(error, instance, info) {
3024
- capture(error, {
3025
- framework: "vue",
3026
- componentName: instanceComponentName(instance),
3027
- info
3028
- });
3029
- },
3030
- solidErrorHandler(error) {
3031
- capture(error, { framework: "solid" });
3032
- },
3033
- svelteErrorHandler(error, info) {
3034
- capture(error, normalizeFrameworkInfo(info, "svelte"));
3035
- }
3036
- };
3037
- }
3038
- //#endregion
3039
- //#region src/framework-router-integrations.ts
3040
- function routePath(input) {
3041
- if (typeof input === "string") return input;
3042
- if (input && typeof input === "object") {
3043
- const route = input;
3044
- if (typeof route.fullPath === "string") return route.fullPath;
3045
- if (typeof route.pathname === "string") return route.pathname;
3046
- if (typeof route.path === "string") return route.path;
3047
- if (route.location) return routePath(route.location);
3048
- }
3049
- return "";
3050
- }
3051
- function emitRouteChange(api, framework, level, to, from, options) {
3052
- const sanitize = options.sanitizeUrl;
3053
- const toPath = sanitize ? sanitize(routePath(to)) : routePath(to);
3054
- const fromPath = from === void 0 ? void 0 : sanitize ? sanitize(routePath(from)) : routePath(from);
3055
- api.capture({
3056
- level,
3057
- message: `${framework} route ${toPath}`,
3058
- props: { browser: {
3059
- kind: "framework-route-change",
3060
- framework,
3061
- route: {
3062
- to: toPath,
3063
- from: fromPath,
3064
- state: options.includeState ? normalizeValue(to, { maxDepth: 4 }) : void 0
3065
- }
3066
- } }
3067
- });
3068
- }
3069
- function nextRouterIntegration(options) {
3070
- const level = options.level ?? "info";
3071
- return {
3072
- name: "next-router",
3073
- setup(api) {
3074
- const router = options.router;
3075
- if (!router.events?.on || !router.events.off) return;
3076
- const onRoute = api.guard((url) => emitRouteChange(api, "next", level, url, router.asPath, options));
3077
- router.events.on("routeChangeComplete", onRoute);
3078
- router.events.on("hashChangeComplete", onRoute);
3079
- return () => {
3080
- router.events?.off?.("routeChangeComplete", onRoute);
3081
- router.events?.off?.("hashChangeComplete", onRoute);
3082
- };
3083
- }
3084
- };
3085
- }
3086
- function reactRouterIntegration(options) {
3087
- const level = options.level ?? "info";
3088
- return {
3089
- name: "react-router",
3090
- setup(api) {
3091
- let previous = options.history.location;
3092
- const dispose = options.history.listen?.(api.guard((update) => {
3093
- const next = update?.location ?? update;
3094
- emitRouteChange(api, "react-router", level, next, previous, options);
3095
- previous = next;
3096
- }));
3097
- return typeof dispose === "function" ? dispose : void 0;
3098
- }
3099
- };
3100
- }
3101
- function vueRouterIntegration(options) {
3102
- const level = options.level ?? "info";
3103
- const framework = options.framework ?? "vue-router";
3104
- return {
3105
- name: framework,
3106
- setup(api) {
3107
- const dispose = options.router.afterEach?.(api.guard((to, from) => emitRouteChange(api, framework, level, to, from, options)));
3108
- return typeof dispose === "function" ? dispose : void 0;
3109
- }
3110
- };
3111
- }
3112
- function nuxtRouterIntegration(options) {
3113
- return vueRouterIntegration({
3114
- ...options,
3115
- framework: "nuxt"
3116
- });
3117
- }
3118
- //#endregion
3119
- //#region src/reporting-integration.ts
3120
- const defaultReportTypes = [
3121
- "csp-violation",
3122
- "deprecation",
3123
- "intervention",
3124
- "crash"
3125
- ];
3126
- function levelFor$1(level, report) {
3127
- if (typeof level === "function") return level(report);
3128
- if (level) return level;
3129
- return report.type === "deprecation" ? "info" : "warn";
3130
- }
3131
- function maybeSanitize(url, sanitizeUrl) {
3132
- return url && sanitizeUrl ? sanitizeUrl(url) : url;
3133
- }
3134
- function normalizeReport(report, sanitizeUrl) {
3135
- const raw = typeof report.toJSON === "function" ? report.toJSON() : report;
3136
- const record = raw && typeof raw === "object" ? raw : {};
3137
- const body = "body" in record ? record.body : report.body;
3138
- const url = typeof record.url === "string" ? record.url : report.url;
3139
- return {
3140
- type: typeof record.type === "string" ? record.type : report.type ?? "unknown",
3141
- url: maybeSanitize(url, sanitizeUrl),
3142
- body: normalizeValue(body, {
3143
- maxDepth: 6,
3144
- maxObjectKeys: 80
3145
- })
3146
- };
3147
- }
3148
- function normalizeCspViolation(event, sanitizeUrl) {
3149
- return {
3150
- type: "securitypolicyviolation",
3151
- blockedURI: maybeSanitize(event.blockedURI, sanitizeUrl),
3152
- documentURI: maybeSanitize(event.documentURI, sanitizeUrl),
3153
- effectiveDirective: event.effectiveDirective,
3154
- violatedDirective: event.violatedDirective,
3155
- disposition: event.disposition,
3156
- sourceFile: maybeSanitize(event.sourceFile, sanitizeUrl),
3157
- lineNumber: event.lineNumber,
3158
- columnNumber: event.columnNumber,
3159
- statusCode: event.statusCode,
3160
- sample: event.sample
3161
- };
3162
- }
3163
- function captureReportingIntegration(options = {}) {
3164
- const captureSecurityPolicyViolation = options.captureSecurityPolicyViolation ?? true;
3165
- const captureReportingObserver = options.captureReportingObserver ?? true;
3166
- const reportTypes = options.reportTypes ?? defaultReportTypes;
3167
- const buffered = options.buffered ?? true;
3168
- return {
3169
- name: "capture-reporting",
3170
- setup(api) {
3171
- const add = options.addEventListener ?? globalThis.addEventListener?.bind(globalThis);
3172
- const remove = options.removeEventListener ?? globalThis.removeEventListener?.bind(globalThis);
3173
- const Observer = options.ReportingObserver ?? globalThis.ReportingObserver;
3174
- const disposers = [];
3175
- let disposed = false;
3176
- const captureReport = api.guard((report) => {
3177
- if (disposed) return;
3178
- api.capture({
3179
- level: levelFor$1(options.level, report),
3180
- message: `Browser report ${report.type}`,
3181
- props: { browser: {
3182
- kind: "report",
3183
- report
3184
- } }
3185
- });
3186
- });
3187
- if (captureSecurityPolicyViolation && add && remove) {
3188
- const onViolation = api.guard((event) => {
3189
- const report = normalizeCspViolation(event, options.sanitizeUrl);
3190
- api.capture({
3191
- level: levelFor$1(options.level, report),
3192
- message: `Security policy violation: ${report.effectiveDirective ?? report.violatedDirective ?? "unknown"}`,
3193
- props: { browser: {
3194
- kind: "securitypolicyviolation",
3195
- report
3196
- } }
3197
- });
3198
- });
3199
- add("securitypolicyviolation", onViolation);
3200
- disposers.push(() => remove("securitypolicyviolation", onViolation));
3201
- }
3202
- if (captureReportingObserver && Observer) try {
3203
- const observer = new Observer((reports) => {
3204
- for (const report of reports) captureReport(normalizeReport(report, options.sanitizeUrl));
3205
- }, {
3206
- buffered,
3207
- types: reportTypes
3208
- });
3209
- observer.observe();
3210
- disposers.push(() => {
3211
- for (const report of observer.takeRecords?.() ?? []) captureReport(normalizeReport(report, options.sanitizeUrl));
3212
- observer.disconnect();
3213
- });
3214
- } catch {}
3215
- return () => {
3216
- if (disposed) return;
3217
- for (const dispose of disposers) dispose();
3218
- disposed = true;
3219
- };
3220
- }
3221
- };
3222
- }
3223
- //#endregion
3224
- //#region src/router-integration.ts
3225
- function locationHref(location) {
3226
- if (!location) return "http://localhost/";
3227
- if (location.href) return location.href;
3228
- return `${location.pathname ?? "/"}${location.search ?? ""}${location.hash ?? ""}`;
3229
- }
3230
- function normalizeRouteUrl(url, location, mode, sanitizeUrl) {
3231
- const fallback = locationHref(location);
3232
- const raw = url === void 0 || url === null ? fallback : String(url);
3233
- let normalized = raw;
3234
- try {
3235
- const parsed = new URL(raw, fallback);
3236
- normalized = mode === "href" ? parsed.href : `${parsed.pathname}${parsed.search}${parsed.hash}`;
3237
- } catch {
3238
- normalized = raw;
3239
- }
3240
- return sanitizeUrl ? sanitizeUrl(normalized) : normalized;
3241
- }
3242
- function routeState(includeState, state, maxDepth) {
3243
- return includeState ? normalizeValue(state, {
3244
- maxDepth,
3245
- maxObjectKeys: 80
3246
- }) : void 0;
3247
- }
3248
- function captureRouterIntegration(options = {}) {
3249
- const level = options.level ?? "info";
3250
- const captureInitial = options.captureInitial ?? true;
3251
- const includeState = options.includeState ?? false;
3252
- const stateMaxDepth = options.stateMaxDepth ?? 4;
3253
- const urlMode = options.urlMode ?? "path";
3254
- return {
3255
- name: "capture-router",
3256
- setup(api) {
3257
- const history = options.history ?? globalThis.history;
3258
- const location = options.location ?? globalThis.location;
3259
- const add = options.addEventListener ?? globalThis.addEventListener?.bind(globalThis);
3260
- const remove = options.removeEventListener ?? globalThis.removeEventListener?.bind(globalThis);
3261
- const originalPushState = history?.pushState;
3262
- const originalReplaceState = history?.replaceState;
3263
- let current = normalizeRouteUrl(void 0, location, urlMode, options.sanitizeUrl);
3264
- let disposed = false;
3265
- const capture = (trigger, to, state) => {
3266
- if (disposed) return;
3267
- const previous = current;
3268
- current = to;
3269
- if (trigger !== "initial" && previous === to) return;
3270
- const route = {
3271
- trigger,
3272
- from: trigger === "initial" ? void 0 : previous,
3273
- to,
3274
- state: routeState(includeState, state, stateMaxDepth)
3275
- };
3276
- api.capture({
3277
- level,
3278
- message: trigger === "initial" ? `Route initial ${to}` : `Route change ${to}`,
3279
- props: { browser: {
3280
- kind: "route-change",
3281
- route
3282
- } }
3283
- });
3284
- };
3285
- if (captureInitial) capture("initial", current, history?.state);
3286
- if (history && originalPushState) history.pushState = function pushState(data, unused, url) {
3287
- const result = originalPushState.call(this, data, unused, url);
3288
- capture("pushState", normalizeRouteUrl(url, location, urlMode, options.sanitizeUrl), data);
3289
- return result;
3290
- };
3291
- if (history && originalReplaceState) history.replaceState = function replaceState(data, unused, url) {
3292
- const result = originalReplaceState.call(this, data, unused, url);
3293
- capture("replaceState", normalizeRouteUrl(url, location, urlMode, options.sanitizeUrl), data);
3294
- return result;
3295
- };
3296
- const onPopState = api.guard((event) => {
3297
- capture("popstate", normalizeRouteUrl(void 0, location, urlMode, options.sanitizeUrl), event.state);
3298
- });
3299
- const onHashChange = api.guard((event) => {
3300
- capture("hashchange", normalizeRouteUrl(event.newURL, location, urlMode, options.sanitizeUrl), history?.state);
3301
- });
3302
- add?.("popstate", onPopState);
3303
- add?.("hashchange", onHashChange);
3304
- return () => {
3305
- if (disposed) return;
3306
- disposed = true;
3307
- if (history && originalPushState) history.pushState = originalPushState;
3308
- if (history && originalReplaceState) history.replaceState = originalReplaceState;
3309
- remove?.("popstate", onPopState);
3310
- remove?.("hashchange", onHashChange);
3311
- };
3312
- }
3313
- };
3314
- }
3315
- //#endregion
3316
- //#region src/runtime-host-integration.ts
3317
- const textEncoder$2 = new TextEncoder();
3318
- function byteLength(value) {
3319
- if (typeof value === "string") return textEncoder$2.encode(value).byteLength;
3320
- if (value instanceof ArrayBuffer) return value.byteLength;
3321
- if (ArrayBuffer.isView(value)) return value.byteLength;
3322
- }
3323
- function valueSummary(value, includeData) {
3324
- return {
3325
- dataType: typeof value === "object" && value ? value.constructor?.name ?? "object" : typeof value,
3326
- byteLength: byteLength(value),
3327
- data: includeData ? normalizeValue(value, {
3328
- maxDepth: 4,
3329
- maxObjectKeys: 80
3330
- }) : void 0
3331
- };
3332
- }
3333
- function senderInfo(sender, sanitizeUrl) {
3334
- if (!sender) return void 0;
3335
- const sanitize = (url) => url && sanitizeUrl ? sanitizeUrl(url) : url;
3336
- return {
3337
- id: sender.id,
3338
- origin: sender.origin,
3339
- url: sanitize(sender.url),
3340
- tab: sender.tab ? {
3341
- id: sender.tab.id,
3342
- url: sanitize(sender.tab.url)
3343
- } : void 0
3344
- };
3345
- }
3346
- function defaultExtensionRuntime() {
3347
- const global = globalThis;
3348
- return global.browser?.runtime ?? global.chrome?.runtime;
3349
- }
3350
- function defaultIpcRenderer() {
3351
- const global = globalThis;
3352
- if (global.electron?.ipcRenderer) return global.electron.ipcRenderer;
3353
- try {
3354
- return (global.require?.("electron"))?.ipcRenderer;
3355
- } catch {
3356
- return;
3357
- }
3358
- }
3359
- function captureRuntimeHostIntegration(options = {}) {
3360
- const level = options.level ?? "debug";
3361
- const captureExtensionMessages = options.captureExtensionMessages ?? true;
3362
- const captureExtensionInstalled = options.captureExtensionInstalled ?? true;
3363
- const captureExtensionMessageData = options.captureExtensionMessageData ?? false;
3364
- const captureElectronMessages = options.captureElectronMessages ?? true;
3365
- const captureElectronSend = options.captureElectronSend ?? true;
3366
- const captureElectronInvoke = options.captureElectronInvoke ?? true;
3367
- const captureElectronMessageData = options.captureElectronMessageData ?? false;
3368
- const electronChannels = options.electronChannels ?? [];
3369
- return {
3370
- name: "capture-runtime-host",
3371
- setup(api) {
3372
- const extensionRuntime = options.extensionRuntime ?? defaultExtensionRuntime();
3373
- const ipcRenderer = options.ipcRenderer ?? defaultIpcRenderer();
3374
- const disposers = [];
3375
- let disposed = false;
3376
- const capture = api.guard((input) => {
3377
- if (!disposed) api.capture(input);
3378
- });
3379
- if (extensionRuntime) {
3380
- const manifest = extensionRuntime.getManifest?.();
3381
- if (captureExtensionMessages && extensionRuntime.onMessage?.addListener) {
3382
- const onMessage = (message, sender) => {
3383
- capture({
3384
- level,
3385
- message: "Browser extension message",
3386
- props: { browser: {
3387
- kind: "extension-message",
3388
- extension: {
3389
- id: extensionRuntime.id,
3390
- name: manifest?.name,
3391
- version: manifest?.version
3392
- },
3393
- message: valueSummary(message, captureExtensionMessageData),
3394
- sender: senderInfo(sender, options.sanitizeUrl)
3395
- } }
3396
- });
3397
- };
3398
- extensionRuntime.onMessage.addListener(onMessage);
3399
- disposers.push(() => extensionRuntime.onMessage?.removeListener?.(onMessage));
3400
- }
3401
- if (captureExtensionInstalled && extensionRuntime.onInstalled?.addListener) {
3402
- const onInstalled = (details) => {
3403
- capture({
3404
- level,
3405
- message: "Browser extension installed",
3406
- props: { browser: {
3407
- kind: "extension-installed",
3408
- extension: {
3409
- id: extensionRuntime.id,
3410
- name: manifest?.name,
3411
- version: manifest?.version
3412
- },
3413
- details: normalizeValue(details, {
3414
- maxDepth: 4,
3415
- maxObjectKeys: 80
3416
- })
3417
- } }
3418
- });
3419
- };
3420
- extensionRuntime.onInstalled.addListener(onInstalled);
3421
- disposers.push(() => extensionRuntime.onInstalled?.removeListener?.(onInstalled));
3422
- }
3423
- }
3424
- if (ipcRenderer) {
3425
- if (captureElectronMessages && ipcRenderer.on) for (const channel of electronChannels) {
3426
- const listener = (_event, ...args) => {
3427
- capture({
3428
- level,
3429
- message: `Electron IPC message ${channel}`,
3430
- props: { browser: {
3431
- kind: "electron-ipc",
3432
- direction: "incoming",
3433
- channel,
3434
- args: args.map((arg) => valueSummary(arg, captureElectronMessageData))
3435
- } }
3436
- });
3437
- };
3438
- ipcRenderer.on(channel, listener);
3439
- disposers.push(() => {
3440
- if (ipcRenderer.off) ipcRenderer.off(channel, listener);
3441
- else ipcRenderer.removeListener?.(channel, listener);
3442
- });
3443
- }
3444
- if (captureElectronSend && ipcRenderer.send) {
3445
- const originalSend = ipcRenderer.send;
3446
- ipcRenderer.send = (channel, ...args) => {
3447
- capture({
3448
- level,
3449
- message: `Electron IPC send ${channel}`,
3450
- props: { browser: {
3451
- kind: "electron-ipc",
3452
- direction: "outgoing",
3453
- channel,
3454
- args: args.map((arg) => valueSummary(arg, captureElectronMessageData))
3455
- } }
3456
- });
3457
- return originalSend.call(ipcRenderer, channel, ...args);
3458
- };
3459
- disposers.push(() => {
3460
- ipcRenderer.send = originalSend;
3461
- });
3462
- }
3463
- if (captureElectronInvoke && ipcRenderer.invoke) {
3464
- const originalInvoke = ipcRenderer.invoke;
3465
- ipcRenderer.invoke = (channel, ...args) => {
3466
- capture({
3467
- level,
3468
- message: `Electron IPC invoke ${channel}`,
3469
- props: { browser: {
3470
- kind: "electron-ipc",
3471
- direction: "invoke",
3472
- channel,
3473
- args: args.map((arg) => valueSummary(arg, captureElectronMessageData))
3474
- } }
3475
- });
3476
- return originalInvoke.call(ipcRenderer, channel, ...args);
3477
- };
3478
- disposers.push(() => {
3479
- ipcRenderer.invoke = originalInvoke;
3480
- });
3481
- }
3482
- }
3483
- return () => {
3484
- if (disposed) return;
3485
- disposed = true;
3486
- for (const dispose of disposers) dispose();
3487
- };
3488
- }
3489
- };
3490
- }
3491
- //#endregion
3492
- //#region src/service-worker-integration.ts
3493
- const textEncoder$1 = new TextEncoder();
3494
- function dataType$1(data) {
3495
- if (typeof data === "string") return "string";
3496
- if (data instanceof ArrayBuffer) return "arraybuffer";
3497
- if (ArrayBuffer.isView(data)) return data.constructor.name;
3498
- return data?.constructor?.name ?? typeof data;
3499
- }
3500
- function dataByteLength$1(data) {
3501
- if (typeof data === "string") return textEncoder$1.encode(data).byteLength;
3502
- if (data instanceof ArrayBuffer) return data.byteLength;
3503
- if (ArrayBuffer.isView(data)) return data.byteLength;
3504
- const size = data?.size;
3505
- return typeof size === "number" && Number.isFinite(size) ? size : void 0;
3506
- }
3507
- function controllerInfo(controller, sanitizeUrl) {
3508
- if (!controller) return void 0;
3509
- return {
3510
- scriptURL: controller.scriptURL && sanitizeUrl ? sanitizeUrl(controller.scriptURL) : controller.scriptURL,
3511
- state: controller.state
3512
- };
3513
- }
3514
- function messagePayload$1(event, captureMessageData) {
3515
- return {
3516
- dataType: dataType$1(event.data),
3517
- byteLength: dataByteLength$1(event.data),
3518
- data: captureMessageData ? normalizeValue(event.data, {
3519
- maxDepth: 4,
3520
- maxObjectKeys: 80
3521
- }) : void 0,
3522
- origin: event.origin,
3523
- lastEventId: event.lastEventId
3524
- };
3525
- }
3526
- function captureServiceWorkerIntegration(options = {}) {
3527
- const level = options.level ?? "debug";
3528
- const captureControllerChange = options.captureControllerChange ?? true;
3529
- const captureMessages = options.captureMessages ?? true;
3530
- const captureMessageErrors = options.captureMessageErrors ?? true;
3531
- const captureMessageData = options.captureMessageData ?? false;
3532
- return {
3533
- name: "capture-service-worker",
3534
- setup(api) {
3535
- const serviceWorker = options.serviceWorker ?? (typeof navigator === "undefined" ? void 0 : navigator.serviceWorker);
3536
- if (!serviceWorker?.addEventListener || !serviceWorker.removeEventListener) return;
3537
- const disposers = [];
3538
- let disposed = false;
3539
- const capture = api.guard((input) => {
3540
- if (!disposed) api.capture(input);
3541
- });
3542
- if (captureControllerChange) {
3543
- const onControllerChange = () => {
3544
- capture({
3545
- level,
3546
- message: "Service worker controller change",
3547
- props: { browser: {
3548
- kind: "service-worker",
3549
- event: "controllerchange",
3550
- controller: controllerInfo(serviceWorker.controller, options.sanitizeUrl)
3551
- } }
3552
- });
3553
- };
3554
- serviceWorker.addEventListener("controllerchange", onControllerChange);
3555
- disposers.push(() => serviceWorker.removeEventListener("controllerchange", onControllerChange));
3556
- }
3557
- if (captureMessages) {
3558
- const onMessage = (event) => {
3559
- capture({
3560
- level,
3561
- message: "Service worker message",
3562
- props: { browser: {
3563
- kind: "service-worker",
3564
- event: "message",
3565
- message: messagePayload$1(event, captureMessageData)
3566
- } }
3567
- });
3568
- };
3569
- serviceWorker.addEventListener("message", onMessage);
3570
- disposers.push(() => serviceWorker.removeEventListener("message", onMessage));
3571
- }
3572
- if (captureMessageErrors) {
3573
- const onMessageError = (event) => {
3574
- capture({
3575
- level: "warn",
3576
- message: "Service worker message error",
3577
- props: { browser: {
3578
- kind: "service-worker",
3579
- event: "messageerror",
3580
- type: event.type
3581
- } }
3582
- });
3583
- };
3584
- serviceWorker.addEventListener("messageerror", onMessageError);
3585
- disposers.push(() => serviceWorker.removeEventListener("messageerror", onMessageError));
3586
- }
3587
- return () => {
3588
- if (disposed) return;
3589
- disposed = true;
3590
- for (const dispose of disposers) dispose();
3591
- };
3592
- }
3593
- };
3594
- }
3595
- //#endregion
3596
- //#region src/user-action-integration.ts
3597
- const defaultEvents = [
3598
- "click",
3599
- "input",
3600
- "change",
3601
- "submit"
3602
- ];
3603
- const defaultLabelAttributes = [
3604
- "data-loggerjs-label",
3605
- "aria-label",
3606
- "name",
3607
- "id"
3608
- ];
3609
- function truncate(value, maxLength) {
3610
- if (!value) return void 0;
3611
- return value.length > maxLength ? value.slice(0, maxLength) : value;
3612
- }
3613
- function readAttribute(target, name) {
3614
- return target.getAttribute?.(name) || void 0;
3615
- }
3616
- function sanitizeField(value, field, sanitize) {
3617
- if (!value) return void 0;
3618
- return sanitize ? sanitize(value, field) : value;
3619
- }
3620
- function firstLabel(target, attributes) {
3621
- for (const name of attributes) {
3622
- const value = readAttribute(target, name) ?? target[name];
3623
- if (typeof value === "string" && value) return value;
3624
- }
3625
- }
3626
- function targetInfo(target, options) {
3627
- const element = target;
3628
- if (!element || typeof element !== "object") return {};
3629
- const maxTextLength = options.maxTextLength ?? 120;
3630
- const sanitize = options.sanitize;
3631
- const info = {
3632
- tagName: sanitizeField(element.tagName?.toLowerCase(), "tagName", sanitize),
3633
- id: sanitizeField(element.id, "id", sanitize),
3634
- name: sanitizeField(element.name, "name", sanitize),
3635
- role: sanitizeField(element.role ?? readAttribute(element, "role"), "role", sanitize),
3636
- type: sanitizeField(element.type, "type", sanitize),
3637
- href: sanitizeField(element.href, "href", sanitize),
3638
- label: sanitizeField(firstLabel(element, options.labelAttributes ?? defaultLabelAttributes), "label", sanitize)
3639
- };
3640
- if (options.captureText) info.text = sanitizeField(truncate(element.textContent ?? void 0, maxTextLength), "text", sanitize);
3641
- if (options.captureValue) info.value = sanitizeField(truncate(element.value, maxTextLength), "value", sanitize);
3642
- return info;
3643
- }
3644
- function throttleKey(event, target) {
3645
- return `${event.type}:${target.tagName ?? ""}:${target.id ?? ""}:${target.name ?? ""}:${target.label ?? ""}`;
3646
- }
3647
- function captureUserActionsIntegration(options = {}) {
3648
- const events = options.events ?? defaultEvents;
3649
- const level = options.level ?? "info";
3650
- const listenerCapture = options.listenerCapture ?? true;
3651
- const throttleMs = Math.max(0, Math.floor(options.throttleMs ?? 250));
3652
- const clock = options.clock ?? Date.now;
3653
- return {
3654
- name: "capture-user-actions",
3655
- setup(api) {
3656
- const root = options.root ?? globalThis.document;
3657
- if (!root?.addEventListener || !root.removeEventListener) return;
3658
- const lastSeen = /* @__PURE__ */ new Map();
3659
- let disposed = false;
3660
- const onAction = api.guard((event) => {
3661
- if (disposed) return;
3662
- const target = targetInfo(event.target, options);
3663
- if (options.ignore?.(event, target)) return;
3664
- const now = clock();
3665
- const key = throttleKey(event, target);
3666
- const last = lastSeen.get(key) ?? 0;
3667
- if (throttleMs > 0 && now - last < throttleMs) return;
3668
- lastSeen.set(key, now);
3669
- const action = {
3670
- type: event.type,
3671
- target
3672
- };
3673
- api.capture({
3674
- level,
3675
- message: `User action ${event.type}${target.label ? ` ${target.label}` : ""}`,
3676
- props: { browser: {
3677
- kind: "user-action",
3678
- action
3679
- } }
3680
- });
3681
- });
3682
- for (const event of events) root.addEventListener(event, onAction, listenerCapture);
3683
- return () => {
3684
- if (disposed) return;
3685
- disposed = true;
3686
- for (const event of events) root.removeEventListener(event, onAction, listenerCapture);
3687
- };
3688
- }
3689
- };
3690
- }
3691
- //#endregion
3692
- //#region src/websocket-integration.ts
3693
- const registryKey = "browser.WebSocket";
3694
- const textEncoder = new TextEncoder();
3695
- function dataType(data) {
3696
- if (typeof data === "string") return "string";
3697
- if (data instanceof ArrayBuffer) return "arraybuffer";
3698
- if (ArrayBuffer.isView(data)) return data.constructor.name;
3699
- return data?.constructor?.name ?? typeof data;
3700
- }
3701
- function dataByteLength(data) {
3702
- if (typeof data === "string") return textEncoder.encode(data).byteLength;
3703
- if (data instanceof ArrayBuffer) return data.byteLength;
3704
- if (ArrayBuffer.isView(data)) return data.byteLength;
3705
- const size = data?.size;
3706
- return typeof size === "number" && Number.isFinite(size) ? size : void 0;
3707
- }
3708
- function messagePayload(direction, data, captureMessageData) {
3709
- return {
3710
- direction,
3711
- dataType: dataType(data),
3712
- byteLength: dataByteLength(data),
3713
- data: captureMessageData ? normalizeValue(data, {
3714
- maxDepth: 4,
3715
- maxObjectKeys: 80
3716
- }) : void 0
3717
- };
3718
- }
3719
- function closeEventPayload(event) {
3720
- const close = event;
3721
- return {
3722
- code: close.code,
3723
- reason: close.reason,
3724
- wasClean: close.wasClean
3725
- };
3726
- }
3727
- function copyStaticConstants(target, source) {
3728
- for (const key of [
3729
- "CONNECTING",
3730
- "OPEN",
3731
- "CLOSING",
3732
- "CLOSED"
3733
- ]) if (source[key] !== void 0) target[key] = source[key];
3734
- }
3735
- function captureWebSocketIntegration(options = {}) {
3736
- const level = options.level ?? "debug";
3737
- const captureConnect = options.captureConnect ?? true;
3738
- const captureOpen = options.captureOpen ?? true;
3739
- const captureClose = options.captureClose ?? true;
3740
- const captureError = options.captureError ?? true;
3741
- const captureMessages = options.captureMessages ?? false;
3742
- const captureSentMessages = options.captureSentMessages ?? false;
3743
- const captureMessageData = options.captureMessageData ?? false;
3744
- const sampleRate = options.sampleRate ?? 1;
3745
- const random = options.random ?? Math.random;
3746
- return {
3747
- name: "capture-websocket",
3748
- setup(api) {
3749
- const globalWebSocket = globalThis.WebSocket;
3750
- const current = options.WebSocket ?? globalWebSocket;
3751
- if (!current) return;
3752
- const original = api.unpatched.get(registryKey) ?? current;
3753
- api.unpatched.set(registryKey, original);
3754
- const capture = api.guard((input) => api.capture(input));
3755
- const PatchedWebSocket = function WebSocket(url, protocols) {
3756
- const started = nowMs();
3757
- const sanitizedUrl = sanitizeHttpUrl(String(url), options.sanitizeUrl);
3758
- const socket = new original(url, protocols);
3759
- const captureLifecycle = (event, extra = {}, error) => {
3760
- if (!shouldSample(sampleRate, random)) return;
3761
- capture({
3762
- level: event === "error" ? "error" : level,
3763
- message: `WebSocket ${event} ${sanitizedUrl}`,
3764
- error,
3765
- props: { websocket: {
3766
- kind: "websocket",
3767
- event,
3768
- url: sanitizedUrl,
3769
- durationMs: durationMs(started),
3770
- ...extra
3771
- } }
3772
- });
3773
- };
3774
- if (captureConnect) captureLifecycle("connect");
3775
- if (captureOpen) socket.addEventListener?.("open", () => captureLifecycle("open"));
3776
- if (captureClose) socket.addEventListener?.("close", (event) => captureLifecycle("close", closeEventPayload(event)));
3777
- if (captureError) socket.addEventListener?.("error", (event) => captureLifecycle("error", { type: event.type }, event));
3778
- if (captureMessages) socket.addEventListener?.("message", (event) => {
3779
- const message = event;
3780
- capture({
3781
- level,
3782
- message: `WebSocket message ${sanitizedUrl}`,
3783
- props: { websocket: {
3784
- kind: "websocket-message",
3785
- url: sanitizedUrl,
3786
- message: messagePayload("incoming", message.data, captureMessageData)
3787
- } }
3788
- });
3789
- });
3790
- if (captureSentMessages && socket.send) {
3791
- const originalSend = socket.send.bind(socket);
3792
- socket.send = (data) => {
3793
- capture({
3794
- level,
3795
- message: `WebSocket send ${sanitizedUrl}`,
3796
- props: { websocket: {
3797
- kind: "websocket-message",
3798
- url: sanitizedUrl,
3799
- message: messagePayload("outgoing", data, captureMessageData)
3800
- } }
3801
- });
3802
- return originalSend(data);
3803
- };
3804
- }
3805
- return socket;
3806
- };
3807
- PatchedWebSocket.prototype = original.prototype;
3808
- copyStaticConstants(PatchedWebSocket, original);
3809
- globalThis.WebSocket = PatchedWebSocket;
3810
- return () => {
3811
- globalThis.WebSocket = current;
3812
- };
3813
- }
3814
- };
3815
- }
3816
- //#endregion
3817
- //#region src/web-vitals-integration.ts
3818
- const defaultMetrics = [
3819
- "CLS",
3820
- "FCP",
3821
- "INP",
3822
- "LCP",
3823
- "TTFB"
3824
- ];
3825
- const thresholds = {
3826
- CLS: [.1, .25],
3827
- FCP: [1800, 3e3],
3828
- INP: [200, 500],
3829
- LCP: [2500, 4e3],
3830
- TTFB: [800, 1800]
3831
- };
3832
- function ratingFor(name, value) {
3833
- const [good, needsImprovement] = thresholds[name];
3834
- if (value <= good) return "good";
3835
- if (value <= needsImprovement) return "needs-improvement";
3836
- return "poor";
3837
- }
3838
- function metricId(name) {
3839
- return `${name.toLowerCase()}-${Date.now().toString(36)}`;
3840
- }
3841
- function entryValue(entry, field) {
3842
- const value = entry[field];
3843
- return typeof value === "number" && Number.isFinite(value) ? value : void 0;
3844
- }
3845
- function hasMetric(metrics, name) {
3846
- return metrics.includes(name);
3847
- }
3848
- function supportsEntryType$1(Observer, type) {
3849
- const supported = Observer?.supportedEntryTypes;
3850
- return !supported || supported.includes(type);
3851
- }
3852
- function captureWebVitalsIntegration(options = {}) {
3853
- const metrics = options.metrics ?? defaultMetrics;
3854
- const level = options.level ?? "info";
3855
- const reportAllChanges = options.reportAllChanges ?? false;
3856
- const flushOnHidden = options.flushOnHidden ?? true;
3857
- return {
3858
- name: "capture-web-vitals",
3859
- setup(api) {
3860
- const Observer = options.PerformanceObserver ?? globalThis.PerformanceObserver;
3861
- const perf = options.performance ?? globalThis.performance;
3862
- const add = options.addEventListener ?? globalThis.addEventListener?.bind(globalThis);
3863
- const remove = options.removeEventListener ?? globalThis.removeEventListener?.bind(globalThis);
3864
- const disposers = [];
3865
- const currentValues = /* @__PURE__ */ new Map();
3866
- const reportedValues = /* @__PURE__ */ new Map();
3867
- const emitted = /* @__PURE__ */ new Set();
3868
- let disposed = false;
3869
- const emit = (name, value, final) => {
3870
- if (!Number.isFinite(value)) return;
3871
- const finalKey = `${name}:final`;
3872
- if (final && emitted.has(finalKey)) return;
3873
- const previous = reportedValues.get(name) ?? 0;
3874
- currentValues.set(name, value);
3875
- reportedValues.set(name, value);
3876
- if (final) emitted.add(finalKey);
3877
- const metric = {
3878
- name,
3879
- value,
3880
- delta: value - previous,
3881
- rating: ratingFor(name, value),
3882
- id: metricId(name),
3883
- final
3884
- };
3885
- api.capture({
3886
- level,
3887
- message: `Web vital ${metric.name} ${metric.value}`,
3888
- props: { webVital: metric }
3889
- });
3890
- };
3891
- const observe = (type, callback, init = {
3892
- type,
3893
- buffered: true
3894
- }) => {
3895
- if (!Observer || !supportsEntryType$1(Observer, type)) return;
3896
- try {
3897
- const observer = new Observer((list) => {
3898
- for (const entry of list.getEntries()) callback(entry);
3899
- });
3900
- observer.observe(init);
3901
- disposers.push(() => observer.disconnect());
3902
- } catch {}
3903
- };
3904
- if (hasMetric(metrics, "TTFB") && perf) {
3905
- const navigation = perf.getEntriesByType("navigation")[0];
3906
- if (navigation) {
3907
- const responseStart = entryValue(navigation, "responseStart");
3908
- const startTime = entryValue(navigation, "startTime") ?? 0;
3909
- if (responseStart !== void 0) emit("TTFB", responseStart - startTime, true);
3910
- }
3911
- }
3912
- if (hasMetric(metrics, "FCP") && perf) {
3913
- const existing = perf.getEntriesByName("first-contentful-paint")[0];
3914
- if (existing) emit("FCP", existing.startTime, true);
3915
- observe("paint", (entry) => {
3916
- if (entry.name === "first-contentful-paint") emit("FCP", entry.startTime, true);
3917
- });
3918
- }
3919
- if (hasMetric(metrics, "CLS")) observe("layout-shift", (entry) => {
3920
- if (entry.hadRecentInput === true) return;
3921
- const value = entryValue(entry, "value") ?? 0;
3922
- const next = (currentValues.get("CLS") ?? 0) + value;
3923
- if (reportAllChanges) emit("CLS", next, false);
3924
- else currentValues.set("CLS", next);
3925
- });
3926
- if (hasMetric(metrics, "LCP")) observe("largest-contentful-paint", (entry) => {
3927
- currentValues.set("LCP", entry.startTime);
3928
- if (reportAllChanges) emit("LCP", entry.startTime, false);
3929
- });
3930
- if (hasMetric(metrics, "INP")) observe("event", (entry) => {
3931
- const duration = entryValue(entry, "duration") ?? 0;
3932
- if (duration <= (currentValues.get("INP") ?? 0)) return;
3933
- currentValues.set("INP", duration);
3934
- if (reportAllChanges) emit("INP", duration, false);
3935
- }, {
3936
- type: "event",
3937
- buffered: true,
3938
- durationThreshold: 40
3939
- });
3940
- const flushFinal = api.guard(() => {
3941
- const cls = currentValues.get("CLS");
3942
- if (cls !== void 0 && cls > 0) emit("CLS", cls, true);
3943
- const lcp = currentValues.get("LCP");
3944
- if (lcp !== void 0) emit("LCP", lcp, true);
3945
- const inp = currentValues.get("INP");
3946
- if (inp !== void 0) emit("INP", inp, true);
3947
- });
3948
- if (flushOnHidden && add && remove) {
3949
- const onPageHide = () => flushFinal();
3950
- const onVisibilityChange = () => {
3951
- if (typeof document !== "undefined" && document.visibilityState === "hidden") flushFinal();
3952
- };
3953
- add("pagehide", onPageHide);
3954
- add("visibilitychange", onVisibilityChange);
3955
- disposers.push(() => {
3956
- remove("pagehide", onPageHide);
3957
- remove("visibilitychange", onVisibilityChange);
3958
- });
3959
- }
3960
- return () => {
3961
- if (disposed) return;
3962
- disposed = true;
3963
- flushFinal();
3964
- for (const dispose of disposers) dispose();
3965
- };
3966
- }
3967
- };
3968
- }
3969
- //#endregion
3970
- //#region src/performance-integration.ts
3971
- const defaultEntryTypes = [
3972
- "navigation",
3973
- "resource",
3974
- "longtask",
3975
- "measure",
3976
- "mark"
3977
- ];
3978
- const numberFields = [
3979
- "transferSize",
3980
- "encodedBodySize",
3981
- "decodedBodySize",
3982
- "workerStart",
3983
- "redirectStart",
3984
- "redirectEnd",
3985
- "fetchStart",
3986
- "domainLookupStart",
3987
- "domainLookupEnd",
3988
- "connectStart",
3989
- "connectEnd",
3990
- "requestStart",
3991
- "responseStart",
3992
- "responseEnd"
3993
- ];
3994
- function supportsEntryType(Observer, type) {
3995
- const supported = Observer?.supportedEntryTypes;
3996
- return !supported || supported.includes(type);
3997
- }
3998
- function finiteNumber(value) {
3999
- return typeof value === "number" && Number.isFinite(value) ? value : void 0;
4000
- }
4001
- function stringValue(value) {
4002
- return typeof value === "string" && value ? value : void 0;
4003
- }
4004
- function normalizeBrowserPerformanceEntry(entry, options = {}) {
4005
- const raw = entry;
4006
- const payload = {
4007
- name: options.sanitizeName ? options.sanitizeName(entry.name, entry.entryType) : entry.name,
4008
- entryType: entry.entryType,
4009
- startTime: entry.startTime,
4010
- duration: entry.duration
4011
- };
4012
- for (const field of numberFields) {
4013
- const value = finiteNumber(raw[field]);
4014
- if (value !== void 0) payload[field] = value;
4015
- }
4016
- const responseStatus = finiteNumber(raw.responseStatus);
4017
- if (responseStatus !== void 0) payload.responseStatus = responseStatus;
4018
- const initiatorType = stringValue(raw.initiatorType);
4019
- if (initiatorType) payload.initiatorType = initiatorType;
4020
- const nextHopProtocol = stringValue(raw.nextHopProtocol);
4021
- if (nextHopProtocol) payload.nextHopProtocol = nextHopProtocol;
4022
- const renderBlockingStatus = stringValue(raw.renderBlockingStatus);
4023
- if (renderBlockingStatus) payload.renderBlockingStatus = renderBlockingStatus;
4024
- if (options.captureDetail && raw.detail !== void 0) payload.detail = raw.detail;
4025
- return payload;
4026
- }
4027
- function minDurationFor(value, entryType) {
4028
- if (typeof value === "number") return value;
4029
- return value?.[entryType] ?? 0;
4030
- }
4031
- function levelFor(value, entry) {
4032
- if (typeof value === "function") return value(entry);
4033
- if (value) return value;
4034
- return entry.entryType === "longtask" ? "warn" : "info";
4035
- }
4036
- function entryKey(entry) {
4037
- return `${entry.entryType}:${entry.name}:${entry.startTime}:${entry.duration}`;
4038
- }
4039
- function capturePerformanceIntegration(options = {}) {
4040
- const entryTypes = options.entryTypes ?? defaultEntryTypes;
4041
- const buffered = options.buffered ?? true;
4042
- const emitExisting = options.emitExisting ?? true;
4043
- const maxEntries = Math.max(0, Math.floor(options.maxEntries ?? 200));
4044
- const sampleRate = options.sampleRate ?? 1;
4045
- const random = options.random ?? Math.random;
4046
- return {
4047
- name: "capture-performance",
4048
- setup(api) {
4049
- const Observer = options.PerformanceObserver ?? globalThis.PerformanceObserver;
4050
- const perf = options.performance ?? globalThis.performance;
4051
- const seen = /* @__PURE__ */ new Set();
4052
- const disposers = [];
4053
- let emitted = 0;
4054
- let disposed = false;
4055
- const capture = api.guard((entry) => {
4056
- if (disposed || emitted >= maxEntries) return;
4057
- const payload = normalizeBrowserPerformanceEntry(entry, options);
4058
- if (payload.duration < minDurationFor(options.minDurationMs, payload.entryType)) return;
4059
- if (sampleRate < 1 && random() >= sampleRate) return;
4060
- if (options.ignore?.(payload)) return;
4061
- const key = entryKey(payload);
4062
- if (seen.has(key)) return;
4063
- seen.add(key);
4064
- emitted += 1;
4065
- api.capture({
4066
- level: levelFor(options.level, payload),
4067
- message: `Performance ${payload.entryType} ${payload.name}`,
4068
- props: { performance: payload }
4069
- });
4070
- });
4071
- const emitExistingEntries = (type) => {
4072
- if (!emitExisting || !perf) return;
4073
- try {
4074
- for (const entry of perf.getEntriesByType(type)) capture(entry);
4075
- } catch {}
4076
- };
4077
- for (const type of entryTypes) {
4078
- emitExistingEntries(type);
4079
- if (!Observer || !supportsEntryType(Observer, type)) continue;
4080
- try {
4081
- const observer = new Observer((list) => {
4082
- for (const entry of list.getEntries()) capture(entry);
4083
- });
4084
- observer.observe({
4085
- type,
4086
- buffered
4087
- });
4088
- disposers.push(() => observer.disconnect());
4089
- } catch {}
4090
- }
4091
- return () => {
4092
- if (disposed) return;
4093
- disposed = true;
4094
- for (const dispose of disposers) dispose();
4095
- };
4096
- }
4097
- };
4098
- }
4099
- //#endregion
4100
- //#region src/page-lifecycle.ts
4101
- function pageLifecycleIntegration(options = {}) {
4102
- const flushOnPageHide = options.flushOnPageHide ?? true;
4103
- const flushOnHidden = options.flushOnHidden ?? true;
4104
- const coalesceMs = options.coalesceMs ?? 250;
4105
- return {
4106
- name: "page-lifecycle",
4107
- setup(api) {
4108
- if (typeof addEventListener === "undefined") return;
4109
- let disposed = false;
4110
- let flushInFlight;
4111
- let lastFlushAt = 0;
4112
- const flush = () => {
4113
- const now = Date.now();
4114
- if (flushInFlight || now - lastFlushAt < coalesceMs) return;
4115
- lastFlushAt = now;
4116
- flushInFlight = Promise.resolve(api.flush()).catch(() => {}).finally(() => {
4117
- flushInFlight = void 0;
4118
- });
4119
- };
4120
- const onVisibility = () => {
4121
- if (typeof document !== "undefined" && document.visibilityState === "hidden") flush();
4122
- };
4123
- if (flushOnPageHide) addEventListener("pagehide", flush);
4124
- if (flushOnHidden) addEventListener("visibilitychange", onVisibility);
4125
- return () => {
4126
- if (disposed) return;
4127
- disposed = true;
4128
- if (flushOnPageHide) removeEventListener("pagehide", flush);
4129
- if (flushOnHidden) removeEventListener("visibilitychange", onVisibility);
4130
- };
4131
- }
4132
- };
4133
- }
4134
- //#endregion
4135
27
  export { browserBroadcastChannelTransport, browserCompressionPayloadTransform, browserContextPropagationIntegration, browserHttpTransport, browserServiceWorkerTransport, browserWebSocketTransport, captureBrowserErrorsIntegration, captureConsoleIntegration, captureFetchIntegration, captureFrameworkErrorsIntegration, capturePerformanceIntegration, captureReportingIntegration, captureRouterIntegration, captureRuntimeHostIntegration, captureServiceWorkerIntegration, captureUserActionsIntegration, captureWebSocketIntegration, captureWebVitalsIntegration, captureXHRIntegration, createLogZipBlob, downloadBlob, exportLogsToZip, indexedDbBrowserHttpOfflineQueue, indexedDbTransport, memoryBrowserHttpOfflineQueue, nextRouterIntegration, normalizeBrowserPerformanceEntry, nuxtRouterIntegration, offlineFirstTransport, pageLifecycleIntegration, reactRouterIntegration, vueRouterIntegration };
4136
-
4137
- //# sourceMappingURL=index.js.map