@loggerjs/browser 0.5.0 → 0.5.1

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 (103) hide show
  1. package/dist/broadcast-channel-transport.cjs +115 -0
  2. package/dist/broadcast-channel-transport.cjs.map +1 -0
  3. package/dist/broadcast-channel-transport.js +114 -0
  4. package/dist/broadcast-channel-transport.js.map +1 -0
  5. package/dist/console-integration.cjs +98 -0
  6. package/dist/console-integration.cjs.map +1 -0
  7. package/dist/console-integration.js +97 -0
  8. package/dist/console-integration.js.map +1 -0
  9. package/dist/context-propagation-integration.cjs +78 -0
  10. package/dist/context-propagation-integration.cjs.map +1 -0
  11. package/dist/context-propagation-integration.js +77 -0
  12. package/dist/context-propagation-integration.js.map +1 -0
  13. package/dist/error-integration.cjs +128 -0
  14. package/dist/error-integration.cjs.map +1 -0
  15. package/dist/error-integration.js +127 -0
  16. package/dist/error-integration.js.map +1 -0
  17. package/dist/fetch-integration.cjs +120 -0
  18. package/dist/fetch-integration.cjs.map +1 -0
  19. package/dist/fetch-integration.js +119 -0
  20. package/dist/fetch-integration.js.map +1 -0
  21. package/dist/framework-error-integration.cjs +102 -0
  22. package/dist/framework-error-integration.cjs.map +1 -0
  23. package/dist/framework-error-integration.js +101 -0
  24. package/dist/framework-error-integration.js.map +1 -0
  25. package/dist/framework-router-integrations.cjs +88 -0
  26. package/dist/framework-router-integrations.cjs.map +1 -0
  27. package/dist/framework-router-integrations.js +84 -0
  28. package/dist/framework-router-integrations.js.map +1 -0
  29. package/dist/http-transport.cjs +301 -0
  30. package/dist/http-transport.cjs.map +1 -0
  31. package/dist/http-transport.js +299 -0
  32. package/dist/http-transport.js.map +1 -0
  33. package/dist/index.cjs.map +1 -1
  34. package/dist/index.js.map +1 -1
  35. package/dist/indexeddb-offline-queue.cjs +105 -0
  36. package/dist/indexeddb-offline-queue.cjs.map +1 -0
  37. package/dist/indexeddb-offline-queue.js +104 -0
  38. package/dist/indexeddb-offline-queue.js.map +1 -0
  39. package/dist/indexeddb-transport.cjs +531 -0
  40. package/dist/indexeddb-transport.cjs.map +1 -0
  41. package/dist/indexeddb-transport.js +530 -0
  42. package/dist/indexeddb-transport.js.map +1 -0
  43. package/dist/offline-first-transport.cjs +675 -0
  44. package/dist/offline-first-transport.cjs.map +1 -0
  45. package/dist/offline-first-transport.js +674 -0
  46. package/dist/offline-first-transport.js.map +1 -0
  47. package/dist/page-lifecycle.cjs +39 -0
  48. package/dist/page-lifecycle.cjs.map +1 -0
  49. package/dist/page-lifecycle.js +38 -0
  50. package/dist/page-lifecycle.js.map +1 -0
  51. package/dist/payload-transforms.cjs +30 -0
  52. package/dist/payload-transforms.cjs.map +1 -0
  53. package/dist/payload-transforms.js +29 -0
  54. package/dist/payload-transforms.js.map +1 -0
  55. package/dist/performance-integration.cjs +135 -0
  56. package/dist/performance-integration.cjs.map +1 -0
  57. package/dist/performance-integration.js +133 -0
  58. package/dist/performance-integration.js.map +1 -0
  59. package/dist/reporting-integration.cjs +110 -0
  60. package/dist/reporting-integration.cjs.map +1 -0
  61. package/dist/reporting-integration.js +109 -0
  62. package/dist/reporting-integration.js.map +1 -0
  63. package/dist/router-integration.cjs +97 -0
  64. package/dist/router-integration.cjs.map +1 -0
  65. package/dist/router-integration.js +96 -0
  66. package/dist/router-integration.js.map +1 -0
  67. package/dist/runtime-host-integration.cjs +181 -0
  68. package/dist/runtime-host-integration.cjs.map +1 -0
  69. package/dist/runtime-host-integration.js +180 -0
  70. package/dist/runtime-host-integration.js.map +1 -0
  71. package/dist/service-worker-integration.cjs +109 -0
  72. package/dist/service-worker-integration.cjs.map +1 -0
  73. package/dist/service-worker-integration.js +108 -0
  74. package/dist/service-worker-integration.js.map +1 -0
  75. package/dist/service-worker-transport.cjs +184 -0
  76. package/dist/service-worker-transport.cjs.map +1 -0
  77. package/dist/service-worker-transport.js +183 -0
  78. package/dist/service-worker-transport.js.map +1 -0
  79. package/dist/user-action-integration.cjs +100 -0
  80. package/dist/user-action-integration.cjs.map +1 -0
  81. package/dist/user-action-integration.js +99 -0
  82. package/dist/user-action-integration.js.map +1 -0
  83. package/dist/web-vitals-integration.cjs +157 -0
  84. package/dist/web-vitals-integration.cjs.map +1 -0
  85. package/dist/web-vitals-integration.js +156 -0
  86. package/dist/web-vitals-integration.js.map +1 -0
  87. package/dist/websocket-integration.cjs +158 -0
  88. package/dist/websocket-integration.cjs.map +1 -0
  89. package/dist/websocket-integration.js +157 -0
  90. package/dist/websocket-integration.js.map +1 -0
  91. package/dist/websocket-transport.cjs +187 -0
  92. package/dist/websocket-transport.cjs.map +1 -0
  93. package/dist/websocket-transport.js +186 -0
  94. package/dist/websocket-transport.js.map +1 -0
  95. package/dist/xhr-integration.cjs +107 -0
  96. package/dist/xhr-integration.cjs.map +1 -0
  97. package/dist/xhr-integration.js +106 -0
  98. package/dist/xhr-integration.js.map +1 -0
  99. package/dist/zip-export.cjs +241 -0
  100. package/dist/zip-export.cjs.map +1 -0
  101. package/dist/zip-export.js +238 -0
  102. package/dist/zip-export.js.map +1 -0
  103. package/package.json +104 -77
@@ -0,0 +1,301 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let _loggerjs_core = require("@loggerjs/core");
3
+ //#region src/http-transport.ts
4
+ const DEFAULT_BEACON_MAX_BYTES = 60 * 1024;
5
+ const sleep = (delayMs) => delayMs <= 0 ? Promise.resolve() : new Promise((resolve) => setTimeout(resolve, delayMs));
6
+ function memoryBrowserHttpOfflineQueue(options = {}) {
7
+ const entries = [];
8
+ const maxEntries = options.maxEntries ?? 1e3;
9
+ const dropPolicy = options.dropPolicy ?? "drop-oldest";
10
+ const drop = (entry, reason) => {
11
+ (0, _loggerjs_core.incrementLoggerMetaCounter)("transport.offline.dropped");
12
+ (0, _loggerjs_core.incrementLoggerMetaCounter)(`transport.offline.dropped.${reason}`);
13
+ options.onDrop?.(entry, reason);
14
+ };
15
+ return {
16
+ enqueue(entry) {
17
+ if (entries.length >= maxEntries) {
18
+ if (dropPolicy === "drop-newest") {
19
+ drop(entry, "queue-full");
20
+ return;
21
+ }
22
+ const dropped = entries.shift();
23
+ if (dropped) drop(dropped, "queue-full");
24
+ }
25
+ entries.push(entry);
26
+ },
27
+ async replay(send) {
28
+ while (entries.length > 0) {
29
+ const entry = entries[0];
30
+ if (!entry) return;
31
+ await send(entry);
32
+ entries.shift();
33
+ }
34
+ },
35
+ size() {
36
+ return entries.length;
37
+ }
38
+ };
39
+ }
40
+ function payloadToBody(payload) {
41
+ if (typeof payload === "string") return payload;
42
+ return Uint8Array.from(payload);
43
+ }
44
+ function payloadByteLength(payload) {
45
+ if (typeof payload !== "string") return payload.byteLength;
46
+ if (typeof TextEncoder !== "undefined") return new TextEncoder().encode(payload).byteLength;
47
+ return new Blob([payload]).size;
48
+ }
49
+ function payloadToBeaconBody(payload, contentType) {
50
+ return new Blob([typeof payload === "string" ? payload : Uint8Array.from(payload)], { type: contentType });
51
+ }
52
+ function remainingEvents(chunks, startIndex) {
53
+ const events = [];
54
+ for (let index = startIndex; index < chunks.length; index++) {
55
+ const chunk = chunks[index];
56
+ if (chunk) events.push(...chunk.events);
57
+ }
58
+ return events;
59
+ }
60
+ function browserHttpTransport(options) {
61
+ const codec = options.codec ?? (0, _loggerjs_core.safeJsonCodec)();
62
+ const queue = [];
63
+ const maxBatchSize = options.maxBatchSize ?? 50;
64
+ const flushIntervalMs = options.flushIntervalMs ?? 2e3;
65
+ const maxQueueSize = options.maxQueueSize ?? 1e3;
66
+ const dropPolicy = options.dropPolicy ?? "drop-oldest";
67
+ const beaconMaxBytes = options.beaconMaxBytes ?? DEFAULT_BEACON_MAX_BYTES;
68
+ const offlineQueue = options.offlineQueue;
69
+ const offlineReplayMaxRetries = options.offlineReplayMaxRetries ?? 3;
70
+ const offlineReplayBaseDelayMs = options.offlineReplayBaseDelayMs ?? 250;
71
+ const offlineReplayMaxDelayMs = options.offlineReplayMaxDelayMs ?? 5e3;
72
+ const random = options.random ?? Math.random;
73
+ const fetchFn = options.fetchFn ?? globalThis.fetch?.bind(globalThis);
74
+ let offlineEntrySeq = 0;
75
+ let timer;
76
+ let flushing = false;
77
+ let replayingOffline = false;
78
+ let lastContext;
79
+ const headers = (payloadHeaders, contentType = codec.contentType) => ({
80
+ "content-type": contentType,
81
+ ...payloadHeaders,
82
+ ...options.headers
83
+ });
84
+ const clearTimer = () => {
85
+ if (timer) clearTimeout(timer);
86
+ timer = void 0;
87
+ };
88
+ const reportDrop = (event, reason) => {
89
+ (0, _loggerjs_core.incrementLoggerMetaCounter)("transport.dropped");
90
+ (0, _loggerjs_core.incrementLoggerMetaCounter)(`transport.dropped.${reason}`);
91
+ options.onDrop?.(event, reason);
92
+ };
93
+ const reportInternalError = (error, operation) => {
94
+ lastContext?.reportInternalError(error, {
95
+ phase: "transport",
96
+ transport: options.name ?? "browser-http",
97
+ operation
98
+ });
99
+ };
100
+ const createBeaconChunks = (batch) => {
101
+ const chunks = [];
102
+ let currentEvents = [];
103
+ let currentPayload;
104
+ for (const event of batch) {
105
+ const candidateEvents = [...currentEvents, event];
106
+ const candidatePayload = codec.encode(candidateEvents);
107
+ if (payloadByteLength(candidatePayload) <= beaconMaxBytes) {
108
+ currentEvents = candidateEvents;
109
+ currentPayload = candidatePayload;
110
+ continue;
111
+ }
112
+ if (currentEvents.length > 0 && currentPayload) chunks.push({
113
+ events: currentEvents,
114
+ payload: currentPayload
115
+ });
116
+ const singlePayload = codec.encode([event]);
117
+ if (payloadByteLength(singlePayload) <= beaconMaxBytes) {
118
+ currentEvents = [event];
119
+ currentPayload = singlePayload;
120
+ } else {
121
+ currentEvents = [];
122
+ currentPayload = void 0;
123
+ reportDrop(event, "beacon-too-large");
124
+ }
125
+ }
126
+ if (currentEvents.length > 0 && currentPayload) chunks.push({
127
+ events: currentEvents,
128
+ payload: currentPayload
129
+ });
130
+ return chunks;
131
+ };
132
+ const sendBeaconBatch = (batch) => {
133
+ if (typeof navigator === "undefined" || !navigator.sendBeacon) return {
134
+ ok: false,
135
+ remaining: batch
136
+ };
137
+ const chunks = createBeaconChunks(batch);
138
+ for (let index = 0; index < chunks.length; index++) {
139
+ const chunk = chunks[index];
140
+ if (!chunk) continue;
141
+ if (!navigator.sendBeacon(options.url, payloadToBeaconBody(chunk.payload, codec.contentType))) return {
142
+ ok: false,
143
+ remaining: remainingEvents(chunks, index)
144
+ };
145
+ }
146
+ return {
147
+ ok: true,
148
+ remaining: []
149
+ };
150
+ };
151
+ const sendPayload = async (entry) => {
152
+ if (!fetchFn) throw new Error("fetch is not available for browserHttpTransport");
153
+ const response = await fetchFn(entry.url, {
154
+ method: entry.method,
155
+ headers: entry.headers,
156
+ body: payloadToBody(entry.body),
157
+ credentials: entry.credentials,
158
+ keepalive: entry.keepalive
159
+ });
160
+ if (!response.ok) throw new Error(`browserHttpTransport failed with status ${response.status}`);
161
+ };
162
+ const encodeTransformedPayload = async (batch) => {
163
+ return (0, _loggerjs_core.applyPayloadTransforms)(codec.encode(batch), {
164
+ contentType: codec.contentType,
165
+ events: batch,
166
+ transport: options.name ?? "browser-http"
167
+ }, options.transformPayload);
168
+ };
169
+ const createOfflineEntry = (payload, payloadHeaders, contentType) => ({
170
+ id: `${Date.now().toString(36)}-${(offlineEntrySeq++).toString(36)}`,
171
+ url: options.url,
172
+ method: options.method ?? "POST",
173
+ headers: headers(payloadHeaders, contentType),
174
+ body: payload,
175
+ credentials: options.credentials,
176
+ keepalive: options.keepalive ?? true,
177
+ createdAt: Date.now()
178
+ });
179
+ const enqueueOfflinePayload = async (payload, payloadHeaders, contentType) => {
180
+ if (!offlineQueue) return false;
181
+ await offlineQueue.enqueue(createOfflineEntry(payload, payloadHeaders, contentType));
182
+ (0, _loggerjs_core.incrementLoggerMetaCounter)("transport.offline.queued");
183
+ return true;
184
+ };
185
+ const sendFetchBatch = async (batch) => {
186
+ if (batch.length === 0) return;
187
+ const transformed = await encodeTransformedPayload(batch);
188
+ if (offlineQueue && typeof navigator !== "undefined" && navigator.onLine === false) {
189
+ await enqueueOfflinePayload(transformed.payload, transformed.headers, transformed.contentType);
190
+ return;
191
+ }
192
+ try {
193
+ await sendPayload(createOfflineEntry(transformed.payload, transformed.headers, transformed.contentType));
194
+ } catch (error) {
195
+ if (await enqueueOfflinePayload(transformed.payload, transformed.headers, transformed.contentType)) return;
196
+ throw error;
197
+ }
198
+ };
199
+ const replayRetryDelay = (attempt) => {
200
+ const cap = Math.min(offlineReplayMaxDelayMs, offlineReplayBaseDelayMs * 2 ** attempt);
201
+ return cap <= 0 ? 0 : random() * cap;
202
+ };
203
+ const sendOfflineEntryWithRetry = async (entry) => {
204
+ for (let attempt = 0;; attempt++) try {
205
+ await sendPayload(entry);
206
+ (0, _loggerjs_core.incrementLoggerMetaCounter)("transport.offline.replayed");
207
+ return;
208
+ } catch (error) {
209
+ if (attempt >= offlineReplayMaxRetries) {
210
+ (0, _loggerjs_core.incrementLoggerMetaCounter)("transport.offline.replay.failed");
211
+ throw error;
212
+ }
213
+ (0, _loggerjs_core.incrementLoggerMetaCounter)("transport.offline.retry");
214
+ await sleep(replayRetryDelay(attempt));
215
+ }
216
+ };
217
+ const replayOfflineQueue = async () => {
218
+ if (!offlineQueue || replayingOffline) return;
219
+ replayingOffline = true;
220
+ try {
221
+ await offlineQueue.replay(sendOfflineEntryWithRetry);
222
+ } finally {
223
+ replayingOffline = false;
224
+ }
225
+ };
226
+ const flush = async (preferBeacon = false) => {
227
+ if (flushing || queue.length === 0) return;
228
+ flushing = true;
229
+ clearTimer();
230
+ const batch = queue.splice(0, queue.length);
231
+ let pendingBatch = batch;
232
+ try {
233
+ if (preferBeacon && !options.transformPayload) {
234
+ const beaconResult = sendBeaconBatch(batch);
235
+ if (beaconResult.ok || beaconResult.remaining.length === 0) return;
236
+ pendingBatch = beaconResult.remaining;
237
+ }
238
+ await sendFetchBatch(pendingBatch);
239
+ pendingBatch = [];
240
+ } catch (error) {
241
+ if (pendingBatch.length > 0) queue.unshift(...pendingBatch);
242
+ throw error;
243
+ } finally {
244
+ flushing = false;
245
+ if (queue.length > 0) schedule();
246
+ }
247
+ };
248
+ const schedule = () => {
249
+ if (timer || flushIntervalMs <= 0) return;
250
+ timer = setTimeout(() => {
251
+ flush(false).catch((error) => reportInternalError(error, "flush"));
252
+ }, flushIntervalMs);
253
+ };
254
+ const onPageHide = () => {
255
+ flush(true).catch((error) => reportInternalError(error, "pagehide-flush"));
256
+ };
257
+ const onVisibilityChange = () => {
258
+ if (typeof document !== "undefined" && document.visibilityState === "hidden") flush(true).catch((error) => reportInternalError(error, "visibility-flush"));
259
+ };
260
+ const onOnline = () => {
261
+ replayOfflineQueue().catch((error) => reportInternalError(error, "replay"));
262
+ };
263
+ if (options.useBeaconOnPageHide ?? true) {
264
+ globalThis.addEventListener?.("pagehide", onPageHide);
265
+ globalThis.addEventListener?.("visibilitychange", onVisibilityChange);
266
+ }
267
+ if (offlineQueue) globalThis.addEventListener?.("online", onOnline);
268
+ return {
269
+ name: options.name ?? "browser-http",
270
+ minLevel: options.minLevel,
271
+ log(event, context) {
272
+ if (options.minLevel !== void 0 && event.level < (0, _loggerjs_core.toLevelValue)(options.minLevel)) return;
273
+ lastContext = context;
274
+ if (queue.length >= maxQueueSize) {
275
+ if (dropPolicy === "drop-newest") {
276
+ reportDrop(event, "queue-full");
277
+ return;
278
+ }
279
+ const dropped = queue.shift();
280
+ if (dropped) reportDrop(dropped, "queue-full");
281
+ }
282
+ queue.push(event);
283
+ if (queue.length >= maxBatchSize) flush(false).catch((error) => reportInternalError(error, "flush"));
284
+ else schedule();
285
+ },
286
+ flush() {
287
+ return flush(false);
288
+ },
289
+ close() {
290
+ globalThis.removeEventListener?.("pagehide", onPageHide);
291
+ globalThis.removeEventListener?.("visibilitychange", onVisibilityChange);
292
+ globalThis.removeEventListener?.("online", onOnline);
293
+ return flush(true);
294
+ }
295
+ };
296
+ }
297
+ //#endregion
298
+ exports.browserHttpTransport = browserHttpTransport;
299
+ exports.memoryBrowserHttpOfflineQueue = memoryBrowserHttpOfflineQueue;
300
+
301
+ //# sourceMappingURL=http-transport.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-transport.cjs","names":[],"sources":["../src/http-transport.ts"],"sourcesContent":["import {\n applyPayloadTransforms,\n incrementLoggerMetaCounter,\n safeJsonCodec,\n toLevelValue,\n type Codec,\n type EncodedPayload,\n type LogEvent,\n type LoggerLevel,\n type PayloadTransform,\n type Transport,\n type TransportContext,\n} from \"@loggerjs/core\";\n\nexport type BrowserHttpDropPolicy = \"drop-oldest\" | \"drop-newest\";\n\nconst DEFAULT_BEACON_MAX_BYTES = 60 * 1024;\n\nconst sleep = (delayMs: number) =>\n delayMs <= 0 ? Promise.resolve() : new Promise<void>((resolve) => setTimeout(resolve, delayMs));\n\nexport interface BrowserHttpOfflineEntry {\n id: string;\n url: string;\n method: \"POST\" | \"PUT\";\n headers: Record<string, string>;\n body: string | Uint8Array;\n credentials?: RequestCredentials;\n keepalive: boolean;\n createdAt: number;\n}\n\nexport interface BrowserHttpOfflineQueue {\n enqueue: (entry: BrowserHttpOfflineEntry) => void | Promise<void>;\n replay: (send: (entry: BrowserHttpOfflineEntry) => Promise<void>) => void | Promise<void>;\n}\n\nexport interface MemoryBrowserHttpOfflineQueueOptions {\n maxEntries?: number;\n dropPolicy?: BrowserHttpDropPolicy;\n onDrop?: (entry: BrowserHttpOfflineEntry, reason: string) => void;\n}\n\nexport interface BrowserHttpTransportOptions {\n url: string;\n name?: string;\n method?: \"POST\" | \"PUT\";\n headers?: Record<string, string>;\n credentials?: RequestCredentials;\n keepalive?: boolean;\n codec?: Codec<string | Uint8Array>;\n minLevel?: LoggerLevel;\n maxBatchSize?: number;\n flushIntervalMs?: number;\n maxQueueSize?: number;\n dropPolicy?: BrowserHttpDropPolicy;\n useBeaconOnPageHide?: boolean;\n beaconMaxBytes?: number;\n offlineQueue?: BrowserHttpOfflineQueue;\n offlineReplayMaxRetries?: number;\n offlineReplayBaseDelayMs?: number;\n offlineReplayMaxDelayMs?: number;\n random?: () => number;\n fetchFn?: typeof fetch;\n transformPayload?: PayloadTransform | readonly PayloadTransform[];\n onDrop?: (event: LogEvent, reason: string) => void;\n}\n\nexport function memoryBrowserHttpOfflineQueue(\n options: MemoryBrowserHttpOfflineQueueOptions = {},\n): BrowserHttpOfflineQueue & { size: () => number } {\n const entries: BrowserHttpOfflineEntry[] = [];\n const maxEntries = options.maxEntries ?? 1000;\n const dropPolicy = options.dropPolicy ?? \"drop-oldest\";\n\n const drop = (entry: BrowserHttpOfflineEntry, reason: string) => {\n incrementLoggerMetaCounter(\"transport.offline.dropped\");\n incrementLoggerMetaCounter(`transport.offline.dropped.${reason}`);\n options.onDrop?.(entry, reason);\n };\n\n return {\n enqueue(entry) {\n if (entries.length >= maxEntries) {\n if (dropPolicy === \"drop-newest\") {\n drop(entry, \"queue-full\");\n return;\n }\n const dropped = entries.shift();\n if (dropped) drop(dropped, \"queue-full\");\n }\n entries.push(entry);\n },\n async replay(send) {\n while (entries.length > 0) {\n const entry = entries[0];\n if (!entry) return;\n // oxlint-disable-next-line no-await-in-loop -- Replay must remove entries only after each send succeeds.\n await send(entry);\n entries.shift();\n }\n },\n size() {\n return entries.length;\n },\n };\n}\n\nfunction payloadToBody(payload: EncodedPayload): BodyInit {\n if (typeof payload === \"string\") return payload;\n return Uint8Array.from(payload);\n}\n\nfunction payloadByteLength(payload: EncodedPayload): number {\n if (typeof payload !== \"string\") return payload.byteLength;\n if (typeof TextEncoder !== \"undefined\") return new TextEncoder().encode(payload).byteLength;\n return new Blob([payload]).size;\n}\n\nfunction payloadToBeaconBody(payload: EncodedPayload, contentType: string): BodyInit {\n return new Blob([typeof payload === \"string\" ? payload : Uint8Array.from(payload)], {\n type: contentType,\n });\n}\n\ninterface BeaconChunk {\n events: LogEvent[];\n payload: string | Uint8Array;\n}\n\nfunction remainingEvents(chunks: BeaconChunk[], startIndex: number): LogEvent[] {\n const events: LogEvent[] = [];\n for (let index = startIndex; index < chunks.length; index++) {\n const chunk = chunks[index];\n if (chunk) events.push(...chunk.events);\n }\n return events;\n}\n\nexport function browserHttpTransport(options: BrowserHttpTransportOptions): Transport {\n const codec = options.codec ?? safeJsonCodec();\n const queue: LogEvent[] = [];\n const maxBatchSize = options.maxBatchSize ?? 50;\n const flushIntervalMs = options.flushIntervalMs ?? 2000;\n const maxQueueSize = options.maxQueueSize ?? 1000;\n const dropPolicy = options.dropPolicy ?? \"drop-oldest\";\n const beaconMaxBytes = options.beaconMaxBytes ?? DEFAULT_BEACON_MAX_BYTES;\n const offlineQueue = options.offlineQueue;\n const offlineReplayMaxRetries = options.offlineReplayMaxRetries ?? 3;\n const offlineReplayBaseDelayMs = options.offlineReplayBaseDelayMs ?? 250;\n const offlineReplayMaxDelayMs = options.offlineReplayMaxDelayMs ?? 5000;\n const random = options.random ?? Math.random;\n const fetchFn = options.fetchFn ?? globalThis.fetch?.bind(globalThis);\n let offlineEntrySeq = 0;\n let timer: ReturnType<typeof setTimeout> | undefined;\n let flushing = false;\n let replayingOffline = false;\n let lastContext: TransportContext | undefined;\n\n const headers = (payloadHeaders?: Record<string, string>, contentType = codec.contentType) => ({\n \"content-type\": contentType,\n ...payloadHeaders,\n ...options.headers,\n });\n\n const clearTimer = () => {\n if (timer) clearTimeout(timer);\n timer = undefined;\n };\n\n const reportDrop = (event: LogEvent, reason: string) => {\n incrementLoggerMetaCounter(\"transport.dropped\");\n incrementLoggerMetaCounter(`transport.dropped.${reason}`);\n options.onDrop?.(event, reason);\n };\n\n const reportInternalError = (error: unknown, operation: string) => {\n lastContext?.reportInternalError(error, {\n phase: \"transport\",\n transport: options.name ?? \"browser-http\",\n operation,\n });\n };\n\n const createBeaconChunks = (batch: LogEvent[]): BeaconChunk[] => {\n const chunks: BeaconChunk[] = [];\n let currentEvents: LogEvent[] = [];\n let currentPayload: string | Uint8Array | undefined;\n\n for (const event of batch) {\n const candidateEvents = [...currentEvents, event];\n const candidatePayload = codec.encode(candidateEvents);\n if (payloadByteLength(candidatePayload) <= beaconMaxBytes) {\n currentEvents = candidateEvents;\n currentPayload = candidatePayload;\n continue;\n }\n\n if (currentEvents.length > 0 && currentPayload) {\n chunks.push({ events: currentEvents, payload: currentPayload });\n }\n\n const singlePayload = codec.encode([event]);\n if (payloadByteLength(singlePayload) <= beaconMaxBytes) {\n currentEvents = [event];\n currentPayload = singlePayload;\n } else {\n currentEvents = [];\n currentPayload = undefined;\n reportDrop(event, \"beacon-too-large\");\n }\n }\n\n if (currentEvents.length > 0 && currentPayload) {\n chunks.push({ events: currentEvents, payload: currentPayload });\n }\n\n return chunks;\n };\n\n const sendBeaconBatch = (batch: LogEvent[]): { ok: boolean; remaining: LogEvent[] } => {\n if (typeof navigator === \"undefined\" || !navigator.sendBeacon) {\n return { ok: false, remaining: batch };\n }\n\n const chunks = createBeaconChunks(batch);\n for (let index = 0; index < chunks.length; index++) {\n const chunk = chunks[index];\n if (!chunk) continue;\n const ok = navigator.sendBeacon(\n options.url,\n payloadToBeaconBody(chunk.payload, codec.contentType),\n );\n if (!ok) return { ok: false, remaining: remainingEvents(chunks, index) };\n }\n\n return { ok: true, remaining: [] };\n };\n\n const sendPayload = async (entry: BrowserHttpOfflineEntry) => {\n if (!fetchFn) throw new Error(\"fetch is not available for browserHttpTransport\");\n const response = await fetchFn(entry.url, {\n method: entry.method,\n headers: entry.headers,\n body: payloadToBody(entry.body),\n credentials: entry.credentials,\n keepalive: entry.keepalive,\n });\n if (!response.ok) throw new Error(`browserHttpTransport failed with status ${response.status}`);\n };\n\n const encodeTransformedPayload = async (batch: LogEvent[]) => {\n const encoded = codec.encode(batch);\n return applyPayloadTransforms(\n encoded,\n {\n contentType: codec.contentType,\n events: batch,\n transport: options.name ?? \"browser-http\",\n },\n options.transformPayload,\n );\n };\n\n const createOfflineEntry = (\n payload: string | Uint8Array,\n payloadHeaders?: Record<string, string>,\n contentType?: string,\n ): BrowserHttpOfflineEntry => ({\n id: `${Date.now().toString(36)}-${(offlineEntrySeq++).toString(36)}`,\n url: options.url,\n method: options.method ?? \"POST\",\n headers: headers(payloadHeaders, contentType),\n body: payload,\n credentials: options.credentials,\n keepalive: options.keepalive ?? true,\n createdAt: Date.now(),\n });\n\n const enqueueOfflinePayload = async (\n payload: string | Uint8Array,\n payloadHeaders?: Record<string, string>,\n contentType?: string,\n ) => {\n if (!offlineQueue) return false;\n await offlineQueue.enqueue(createOfflineEntry(payload, payloadHeaders, contentType));\n incrementLoggerMetaCounter(\"transport.offline.queued\");\n return true;\n };\n\n const sendFetchBatch = async (batch: LogEvent[]) => {\n if (batch.length === 0) return;\n const transformed = await encodeTransformedPayload(batch);\n if (offlineQueue && typeof navigator !== \"undefined\" && navigator.onLine === false) {\n await enqueueOfflinePayload(\n transformed.payload,\n transformed.headers,\n transformed.contentType,\n );\n return;\n }\n try {\n await sendPayload(\n createOfflineEntry(transformed.payload, transformed.headers, transformed.contentType),\n );\n } catch (error) {\n if (\n await enqueueOfflinePayload(\n transformed.payload,\n transformed.headers,\n transformed.contentType,\n )\n ) {\n return;\n }\n throw error;\n }\n };\n\n const replayRetryDelay = (attempt: number): number => {\n const cap = Math.min(offlineReplayMaxDelayMs, offlineReplayBaseDelayMs * 2 ** attempt);\n return cap <= 0 ? 0 : random() * cap;\n };\n\n const sendOfflineEntryWithRetry = async (entry: BrowserHttpOfflineEntry) => {\n for (let attempt = 0; ; attempt++) {\n try {\n // oxlint-disable-next-line no-await-in-loop -- Retry attempts must run sequentially.\n await sendPayload(entry);\n incrementLoggerMetaCounter(\"transport.offline.replayed\");\n return;\n } catch (error) {\n if (attempt >= offlineReplayMaxRetries) {\n incrementLoggerMetaCounter(\"transport.offline.replay.failed\");\n throw error;\n }\n incrementLoggerMetaCounter(\"transport.offline.retry\");\n // oxlint-disable-next-line no-await-in-loop -- Backoff must complete before the next retry.\n await sleep(replayRetryDelay(attempt));\n }\n }\n };\n\n const replayOfflineQueue = async () => {\n if (!offlineQueue || replayingOffline) return;\n replayingOffline = true;\n try {\n await offlineQueue.replay(sendOfflineEntryWithRetry);\n } finally {\n replayingOffline = false;\n }\n };\n\n const flush = async (preferBeacon = false) => {\n if (flushing || queue.length === 0) return;\n flushing = true;\n clearTimer();\n const batch = queue.splice(0, queue.length);\n let pendingBatch = batch;\n\n try {\n if (preferBeacon && !options.transformPayload) {\n const beaconResult = sendBeaconBatch(batch);\n if (beaconResult.ok || beaconResult.remaining.length === 0) return;\n pendingBatch = beaconResult.remaining;\n }\n\n await sendFetchBatch(pendingBatch);\n pendingBatch = [];\n } catch (error) {\n if (pendingBatch.length > 0) queue.unshift(...pendingBatch);\n throw error;\n } finally {\n flushing = false;\n if (queue.length > 0) schedule();\n }\n };\n\n const schedule = () => {\n if (timer || flushIntervalMs <= 0) return;\n timer = setTimeout(() => {\n void flush(false).catch((error: unknown) => reportInternalError(error, \"flush\"));\n }, flushIntervalMs);\n };\n\n const onPageHide = () => {\n void flush(true).catch((error: unknown) => reportInternalError(error, \"pagehide-flush\"));\n };\n const onVisibilityChange = () => {\n if (typeof document !== \"undefined\" && document.visibilityState === \"hidden\") {\n void flush(true).catch((error: unknown) => reportInternalError(error, \"visibility-flush\"));\n }\n };\n const onOnline = () => {\n void replayOfflineQueue().catch((error: unknown) => reportInternalError(error, \"replay\"));\n };\n\n if (options.useBeaconOnPageHide ?? true) {\n globalThis.addEventListener?.(\"pagehide\", onPageHide);\n globalThis.addEventListener?.(\"visibilitychange\", onVisibilityChange);\n }\n if (offlineQueue) globalThis.addEventListener?.(\"online\", onOnline);\n\n return {\n name: options.name ?? \"browser-http\",\n minLevel: options.minLevel,\n log(event, context) {\n if (options.minLevel !== undefined && event.level < toLevelValue(options.minLevel)) return;\n lastContext = context;\n if (queue.length >= maxQueueSize) {\n if (dropPolicy === \"drop-newest\") {\n reportDrop(event, \"queue-full\");\n return;\n }\n const dropped = queue.shift();\n if (dropped) reportDrop(dropped, \"queue-full\");\n }\n queue.push(event);\n if (queue.length >= maxBatchSize) {\n void flush(false).catch((error: unknown) => reportInternalError(error, \"flush\"));\n } else schedule();\n },\n flush() {\n return flush(false);\n },\n close() {\n globalThis.removeEventListener?.(\"pagehide\", onPageHide);\n globalThis.removeEventListener?.(\"visibilitychange\", onVisibilityChange);\n globalThis.removeEventListener?.(\"online\", onOnline);\n return flush(true);\n },\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,299 @@
1
+ import { applyPayloadTransforms, incrementLoggerMetaCounter, safeJsonCodec, toLevelValue } from "@loggerjs/core";
2
+ //#region src/http-transport.ts
3
+ const DEFAULT_BEACON_MAX_BYTES = 60 * 1024;
4
+ const sleep = (delayMs) => delayMs <= 0 ? Promise.resolve() : new Promise((resolve) => setTimeout(resolve, delayMs));
5
+ function memoryBrowserHttpOfflineQueue(options = {}) {
6
+ const entries = [];
7
+ const maxEntries = options.maxEntries ?? 1e3;
8
+ const dropPolicy = options.dropPolicy ?? "drop-oldest";
9
+ const drop = (entry, reason) => {
10
+ incrementLoggerMetaCounter("transport.offline.dropped");
11
+ incrementLoggerMetaCounter(`transport.offline.dropped.${reason}`);
12
+ options.onDrop?.(entry, reason);
13
+ };
14
+ return {
15
+ enqueue(entry) {
16
+ if (entries.length >= maxEntries) {
17
+ if (dropPolicy === "drop-newest") {
18
+ drop(entry, "queue-full");
19
+ return;
20
+ }
21
+ const dropped = entries.shift();
22
+ if (dropped) drop(dropped, "queue-full");
23
+ }
24
+ entries.push(entry);
25
+ },
26
+ async replay(send) {
27
+ while (entries.length > 0) {
28
+ const entry = entries[0];
29
+ if (!entry) return;
30
+ await send(entry);
31
+ entries.shift();
32
+ }
33
+ },
34
+ size() {
35
+ return entries.length;
36
+ }
37
+ };
38
+ }
39
+ function payloadToBody(payload) {
40
+ if (typeof payload === "string") return payload;
41
+ return Uint8Array.from(payload);
42
+ }
43
+ function payloadByteLength(payload) {
44
+ if (typeof payload !== "string") return payload.byteLength;
45
+ if (typeof TextEncoder !== "undefined") return new TextEncoder().encode(payload).byteLength;
46
+ return new Blob([payload]).size;
47
+ }
48
+ function payloadToBeaconBody(payload, contentType) {
49
+ return new Blob([typeof payload === "string" ? payload : Uint8Array.from(payload)], { type: contentType });
50
+ }
51
+ function remainingEvents(chunks, startIndex) {
52
+ const events = [];
53
+ for (let index = startIndex; index < chunks.length; index++) {
54
+ const chunk = chunks[index];
55
+ if (chunk) events.push(...chunk.events);
56
+ }
57
+ return events;
58
+ }
59
+ function browserHttpTransport(options) {
60
+ const codec = options.codec ?? safeJsonCodec();
61
+ const queue = [];
62
+ const maxBatchSize = options.maxBatchSize ?? 50;
63
+ const flushIntervalMs = options.flushIntervalMs ?? 2e3;
64
+ const maxQueueSize = options.maxQueueSize ?? 1e3;
65
+ const dropPolicy = options.dropPolicy ?? "drop-oldest";
66
+ const beaconMaxBytes = options.beaconMaxBytes ?? DEFAULT_BEACON_MAX_BYTES;
67
+ const offlineQueue = options.offlineQueue;
68
+ const offlineReplayMaxRetries = options.offlineReplayMaxRetries ?? 3;
69
+ const offlineReplayBaseDelayMs = options.offlineReplayBaseDelayMs ?? 250;
70
+ const offlineReplayMaxDelayMs = options.offlineReplayMaxDelayMs ?? 5e3;
71
+ const random = options.random ?? Math.random;
72
+ const fetchFn = options.fetchFn ?? globalThis.fetch?.bind(globalThis);
73
+ let offlineEntrySeq = 0;
74
+ let timer;
75
+ let flushing = false;
76
+ let replayingOffline = false;
77
+ let lastContext;
78
+ const headers = (payloadHeaders, contentType = codec.contentType) => ({
79
+ "content-type": contentType,
80
+ ...payloadHeaders,
81
+ ...options.headers
82
+ });
83
+ const clearTimer = () => {
84
+ if (timer) clearTimeout(timer);
85
+ timer = void 0;
86
+ };
87
+ const reportDrop = (event, reason) => {
88
+ incrementLoggerMetaCounter("transport.dropped");
89
+ incrementLoggerMetaCounter(`transport.dropped.${reason}`);
90
+ options.onDrop?.(event, reason);
91
+ };
92
+ const reportInternalError = (error, operation) => {
93
+ lastContext?.reportInternalError(error, {
94
+ phase: "transport",
95
+ transport: options.name ?? "browser-http",
96
+ operation
97
+ });
98
+ };
99
+ const createBeaconChunks = (batch) => {
100
+ const chunks = [];
101
+ let currentEvents = [];
102
+ let currentPayload;
103
+ for (const event of batch) {
104
+ const candidateEvents = [...currentEvents, event];
105
+ const candidatePayload = codec.encode(candidateEvents);
106
+ if (payloadByteLength(candidatePayload) <= beaconMaxBytes) {
107
+ currentEvents = candidateEvents;
108
+ currentPayload = candidatePayload;
109
+ continue;
110
+ }
111
+ if (currentEvents.length > 0 && currentPayload) chunks.push({
112
+ events: currentEvents,
113
+ payload: currentPayload
114
+ });
115
+ const singlePayload = codec.encode([event]);
116
+ if (payloadByteLength(singlePayload) <= beaconMaxBytes) {
117
+ currentEvents = [event];
118
+ currentPayload = singlePayload;
119
+ } else {
120
+ currentEvents = [];
121
+ currentPayload = void 0;
122
+ reportDrop(event, "beacon-too-large");
123
+ }
124
+ }
125
+ if (currentEvents.length > 0 && currentPayload) chunks.push({
126
+ events: currentEvents,
127
+ payload: currentPayload
128
+ });
129
+ return chunks;
130
+ };
131
+ const sendBeaconBatch = (batch) => {
132
+ if (typeof navigator === "undefined" || !navigator.sendBeacon) return {
133
+ ok: false,
134
+ remaining: batch
135
+ };
136
+ const chunks = createBeaconChunks(batch);
137
+ for (let index = 0; index < chunks.length; index++) {
138
+ const chunk = chunks[index];
139
+ if (!chunk) continue;
140
+ if (!navigator.sendBeacon(options.url, payloadToBeaconBody(chunk.payload, codec.contentType))) return {
141
+ ok: false,
142
+ remaining: remainingEvents(chunks, index)
143
+ };
144
+ }
145
+ return {
146
+ ok: true,
147
+ remaining: []
148
+ };
149
+ };
150
+ const sendPayload = async (entry) => {
151
+ if (!fetchFn) throw new Error("fetch is not available for browserHttpTransport");
152
+ const response = await fetchFn(entry.url, {
153
+ method: entry.method,
154
+ headers: entry.headers,
155
+ body: payloadToBody(entry.body),
156
+ credentials: entry.credentials,
157
+ keepalive: entry.keepalive
158
+ });
159
+ if (!response.ok) throw new Error(`browserHttpTransport failed with status ${response.status}`);
160
+ };
161
+ const encodeTransformedPayload = async (batch) => {
162
+ return applyPayloadTransforms(codec.encode(batch), {
163
+ contentType: codec.contentType,
164
+ events: batch,
165
+ transport: options.name ?? "browser-http"
166
+ }, options.transformPayload);
167
+ };
168
+ const createOfflineEntry = (payload, payloadHeaders, contentType) => ({
169
+ id: `${Date.now().toString(36)}-${(offlineEntrySeq++).toString(36)}`,
170
+ url: options.url,
171
+ method: options.method ?? "POST",
172
+ headers: headers(payloadHeaders, contentType),
173
+ body: payload,
174
+ credentials: options.credentials,
175
+ keepalive: options.keepalive ?? true,
176
+ createdAt: Date.now()
177
+ });
178
+ const enqueueOfflinePayload = async (payload, payloadHeaders, contentType) => {
179
+ if (!offlineQueue) return false;
180
+ await offlineQueue.enqueue(createOfflineEntry(payload, payloadHeaders, contentType));
181
+ incrementLoggerMetaCounter("transport.offline.queued");
182
+ return true;
183
+ };
184
+ const sendFetchBatch = async (batch) => {
185
+ if (batch.length === 0) return;
186
+ const transformed = await encodeTransformedPayload(batch);
187
+ if (offlineQueue && typeof navigator !== "undefined" && navigator.onLine === false) {
188
+ await enqueueOfflinePayload(transformed.payload, transformed.headers, transformed.contentType);
189
+ return;
190
+ }
191
+ try {
192
+ await sendPayload(createOfflineEntry(transformed.payload, transformed.headers, transformed.contentType));
193
+ } catch (error) {
194
+ if (await enqueueOfflinePayload(transformed.payload, transformed.headers, transformed.contentType)) return;
195
+ throw error;
196
+ }
197
+ };
198
+ const replayRetryDelay = (attempt) => {
199
+ const cap = Math.min(offlineReplayMaxDelayMs, offlineReplayBaseDelayMs * 2 ** attempt);
200
+ return cap <= 0 ? 0 : random() * cap;
201
+ };
202
+ const sendOfflineEntryWithRetry = async (entry) => {
203
+ for (let attempt = 0;; attempt++) try {
204
+ await sendPayload(entry);
205
+ incrementLoggerMetaCounter("transport.offline.replayed");
206
+ return;
207
+ } catch (error) {
208
+ if (attempt >= offlineReplayMaxRetries) {
209
+ incrementLoggerMetaCounter("transport.offline.replay.failed");
210
+ throw error;
211
+ }
212
+ incrementLoggerMetaCounter("transport.offline.retry");
213
+ await sleep(replayRetryDelay(attempt));
214
+ }
215
+ };
216
+ const replayOfflineQueue = async () => {
217
+ if (!offlineQueue || replayingOffline) return;
218
+ replayingOffline = true;
219
+ try {
220
+ await offlineQueue.replay(sendOfflineEntryWithRetry);
221
+ } finally {
222
+ replayingOffline = false;
223
+ }
224
+ };
225
+ const flush = async (preferBeacon = false) => {
226
+ if (flushing || queue.length === 0) return;
227
+ flushing = true;
228
+ clearTimer();
229
+ const batch = queue.splice(0, queue.length);
230
+ let pendingBatch = batch;
231
+ try {
232
+ if (preferBeacon && !options.transformPayload) {
233
+ const beaconResult = sendBeaconBatch(batch);
234
+ if (beaconResult.ok || beaconResult.remaining.length === 0) return;
235
+ pendingBatch = beaconResult.remaining;
236
+ }
237
+ await sendFetchBatch(pendingBatch);
238
+ pendingBatch = [];
239
+ } catch (error) {
240
+ if (pendingBatch.length > 0) queue.unshift(...pendingBatch);
241
+ throw error;
242
+ } finally {
243
+ flushing = false;
244
+ if (queue.length > 0) schedule();
245
+ }
246
+ };
247
+ const schedule = () => {
248
+ if (timer || flushIntervalMs <= 0) return;
249
+ timer = setTimeout(() => {
250
+ flush(false).catch((error) => reportInternalError(error, "flush"));
251
+ }, flushIntervalMs);
252
+ };
253
+ const onPageHide = () => {
254
+ flush(true).catch((error) => reportInternalError(error, "pagehide-flush"));
255
+ };
256
+ const onVisibilityChange = () => {
257
+ if (typeof document !== "undefined" && document.visibilityState === "hidden") flush(true).catch((error) => reportInternalError(error, "visibility-flush"));
258
+ };
259
+ const onOnline = () => {
260
+ replayOfflineQueue().catch((error) => reportInternalError(error, "replay"));
261
+ };
262
+ if (options.useBeaconOnPageHide ?? true) {
263
+ globalThis.addEventListener?.("pagehide", onPageHide);
264
+ globalThis.addEventListener?.("visibilitychange", onVisibilityChange);
265
+ }
266
+ if (offlineQueue) globalThis.addEventListener?.("online", onOnline);
267
+ return {
268
+ name: options.name ?? "browser-http",
269
+ minLevel: options.minLevel,
270
+ log(event, context) {
271
+ if (options.minLevel !== void 0 && event.level < toLevelValue(options.minLevel)) return;
272
+ lastContext = context;
273
+ if (queue.length >= maxQueueSize) {
274
+ if (dropPolicy === "drop-newest") {
275
+ reportDrop(event, "queue-full");
276
+ return;
277
+ }
278
+ const dropped = queue.shift();
279
+ if (dropped) reportDrop(dropped, "queue-full");
280
+ }
281
+ queue.push(event);
282
+ if (queue.length >= maxBatchSize) flush(false).catch((error) => reportInternalError(error, "flush"));
283
+ else schedule();
284
+ },
285
+ flush() {
286
+ return flush(false);
287
+ },
288
+ close() {
289
+ globalThis.removeEventListener?.("pagehide", onPageHide);
290
+ globalThis.removeEventListener?.("visibilitychange", onVisibilityChange);
291
+ globalThis.removeEventListener?.("online", onOnline);
292
+ return flush(true);
293
+ }
294
+ };
295
+ }
296
+ //#endregion
297
+ export { browserHttpTransport, memoryBrowserHttpOfflineQueue };
298
+
299
+ //# sourceMappingURL=http-transport.js.map