@loggerjs/browser 0.5.4 → 0.5.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -169,8 +169,10 @@ function remainingEvents(chunks, startIndex) {
169
169
  }
170
170
  function browserHttpTransport(options) {
171
171
  const codec = options.codec ?? safeJsonCodec();
172
+ const beaconCodec = options.beaconCodec ?? codec;
172
173
  const queue = [];
173
174
  const maxBatchSize = options.maxBatchSize ?? 50;
175
+ if (!Number.isSafeInteger(maxBatchSize) || maxBatchSize <= 0) throw new RangeError("maxBatchSize must be a positive safe integer");
174
176
  const flushIntervalMs = options.flushIntervalMs ?? 2e3;
175
177
  const maxQueueSize = options.maxQueueSize ?? 1e3;
176
178
  const dropPolicy = options.dropPolicy ?? "drop-oldest";
@@ -183,7 +185,8 @@ function browserHttpTransport(options) {
183
185
  const fetchFn = options.fetchFn ?? globalThis.fetch?.bind(globalThis);
184
186
  let offlineEntrySeq = 0;
185
187
  let timer;
186
- let flushing = false;
188
+ let activeFlush;
189
+ let flushingBeacon = false;
187
190
  let replayingOffline = false;
188
191
  let lastContext;
189
192
  const headers = (payloadHeaders, contentType = codec.contentType) => ({
@@ -209,54 +212,62 @@ function browserHttpTransport(options) {
209
212
  };
210
213
  const createBeaconChunks = (batch) => {
211
214
  const chunks = [];
215
+ const oversized = [];
212
216
  let currentEvents = [];
213
217
  let currentPayload;
214
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
+ }
215
227
  const candidateEvents = [...currentEvents, event];
216
- const candidatePayload = codec.encode(candidateEvents);
228
+ const candidatePayload = beaconCodec.encode(candidateEvents);
217
229
  if (payloadByteLength$1(candidatePayload) <= beaconMaxBytes) {
218
230
  currentEvents = candidateEvents;
219
231
  currentPayload = candidatePayload;
220
232
  continue;
221
233
  }
222
- if (currentEvents.length > 0 && currentPayload) chunks.push({
234
+ if (currentEvents.length > 0 && currentPayload !== void 0) chunks.push({
223
235
  events: currentEvents,
224
236
  payload: currentPayload
225
237
  });
226
- const singlePayload = codec.encode([event]);
238
+ const singlePayload = beaconCodec.encode([event]);
227
239
  if (payloadByteLength$1(singlePayload) <= beaconMaxBytes) {
228
240
  currentEvents = [event];
229
241
  currentPayload = singlePayload;
230
242
  } else {
231
243
  currentEvents = [];
232
244
  currentPayload = void 0;
233
- reportDrop(event, "beacon-too-large");
245
+ oversized.push(event);
234
246
  }
235
247
  }
236
- if (currentEvents.length > 0 && currentPayload) chunks.push({
248
+ if (currentEvents.length > 0 && currentPayload !== void 0) chunks.push({
237
249
  events: currentEvents,
238
250
  payload: currentPayload
239
251
  });
252
+ for (const event of oversized) reportDrop(event, "beacon-too-large");
240
253
  return chunks;
241
254
  };
242
255
  const sendBeaconBatch = (batch) => {
243
- if (typeof navigator === "undefined" || !navigator.sendBeacon) return {
244
- ok: false,
245
- remaining: batch
246
- };
256
+ if (typeof navigator === "undefined" || !navigator.sendBeacon) return { remaining: batch };
247
257
  const chunks = createBeaconChunks(batch);
248
258
  for (let index = 0; index < chunks.length; index++) {
249
259
  const chunk = chunks[index];
250
260
  if (!chunk) continue;
251
- if (!navigator.sendBeacon(options.url, payloadToBeaconBody(chunk.payload, codec.contentType))) return {
252
- ok: false,
253
- remaining: remainingEvents(chunks, index)
254
- };
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
+ }
255
269
  }
256
- return {
257
- ok: true,
258
- remaining: []
259
- };
270
+ return { remaining: [] };
260
271
  };
261
272
  const sendPayload = async (entry) => {
262
273
  if (!fetchFn) throw new Error("fetch is not available for browserHttpTransport");
@@ -333,31 +344,65 @@ function browserHttpTransport(options) {
333
344
  replayingOffline = false;
334
345
  }
335
346
  };
336
- const flush = async (preferBeacon = false) => {
337
- if (flushing || queue.length === 0) return;
338
- flushing = true;
339
- clearTimer();
340
- const batch = queue.splice(0, queue.length);
341
- let pendingBatch = batch;
347
+ const flushBeaconQueue = () => {
348
+ if (flushingBeacon || options.transformPayload || queue.length === 0) return;
349
+ flushingBeacon = true;
350
+ let pending = queue.splice(0, queue.length);
342
351
  try {
343
- if (preferBeacon && !options.transformPayload) {
344
- const beaconResult = sendBeaconBatch(batch);
345
- if (beaconResult.ok || beaconResult.remaining.length === 0) return;
346
- pendingBatch = beaconResult.remaining;
347
- }
348
- await sendFetchBatch(pendingBatch);
349
- pendingBatch = [];
350
- } catch (error) {
351
- if (pendingBatch.length > 0) queue.unshift(...pendingBatch);
352
- throw error;
352
+ const result = sendBeaconBatch(pending);
353
+ pending = result.remaining;
354
+ if (result.failure) throw result.failure.error;
353
355
  } finally {
354
- flushing = false;
355
- if (queue.length > 0) schedule();
356
+ if (pending.length > 0) queue.unshift(...pending);
357
+ flushingBeacon = false;
356
358
  }
357
359
  };
360
+ const flush = (preferBeacon = false) => {
361
+ if (activeFlush) {
362
+ if (preferBeacon) try {
363
+ flushBeaconQueue();
364
+ } catch (error) {
365
+ return activeFlush.then(() => {
366
+ throw error;
367
+ });
368
+ }
369
+ return activeFlush;
370
+ }
371
+ if (queue.length === 0) return Promise.resolve();
372
+ clearTimer();
373
+ let resolve;
374
+ let reject;
375
+ const task = new Promise((onResolve, onReject) => {
376
+ resolve = onResolve;
377
+ reject = onReject;
378
+ });
379
+ activeFlush = task;
380
+ (async () => {
381
+ try {
382
+ if (preferBeacon) flushBeaconQueue();
383
+ while (queue.length > 0) {
384
+ const batch = queue.splice(0, maxBatchSize);
385
+ try {
386
+ await sendFetchBatch(batch);
387
+ } catch (error) {
388
+ queue.unshift(...batch);
389
+ throw error;
390
+ }
391
+ }
392
+ resolve();
393
+ } catch (error) {
394
+ reject(error);
395
+ } finally {
396
+ activeFlush = void 0;
397
+ if (queue.length > 0) schedule();
398
+ }
399
+ })();
400
+ return task;
401
+ };
358
402
  const schedule = () => {
359
- if (timer || flushIntervalMs <= 0) return;
403
+ if (activeFlush || timer || flushIntervalMs <= 0) return;
360
404
  timer = setTimeout(() => {
405
+ timer = void 0;
361
406
  flush(false).catch((error) => reportInternalError(error, "flush"));
362
407
  }, flushIntervalMs);
363
408
  };
@@ -390,6 +435,7 @@ function browserHttpTransport(options) {
390
435
  if (dropped) reportDrop(dropped, "queue-full");
391
436
  }
392
437
  queue.push(event);
438
+ if (activeFlush) return;
393
439
  if (queue.length >= maxBatchSize) flush(false).catch((error) => reportInternalError(error, "flush"));
394
440
  else schedule();
395
441
  },