@corbado/observe 1.10.1 → 1.11.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 (39) hide show
  1. package/dist/analysis/client.mjs +37 -1595
  2. package/dist/analysis/default-tracker.mjs +16 -0
  3. package/dist/analysis/index.mjs +29 -41
  4. package/dist/analysis/plugins/cookie-storage.mjs +11 -0
  5. package/dist/analysis/plugins/cross-origin-session.mjs +171 -0
  6. package/dist/analysis/plugins/probes.mjs +69 -0
  7. package/dist/analysis/plugins/raw-errors.mjs +8 -0
  8. package/dist/analysis/probe-lane.mjs +175 -0
  9. package/dist/analysis/queue.mjs +18 -141
  10. package/dist/analysis/session-keys.mjs +11 -0
  11. package/dist/analysis/tracker-core.mjs +1352 -0
  12. package/dist/analysis/tracker-features.mjs +80 -0
  13. package/dist/analysis/tracker-functions.mjs +103 -0
  14. package/dist/analysis/utils/appconfirmation-operation-full.mjs +5 -1
  15. package/dist/analysis/utils/captcha-operation-full.mjs +5 -1
  16. package/dist/analysis/utils/email-link-operation-full.mjs +9 -1
  17. package/dist/analysis/utils/email-otp-operation-full.mjs +5 -1
  18. package/dist/analysis/utils/form-operation-full.mjs +5 -1
  19. package/dist/analysis/utils/key-operation-full.mjs +9 -1
  20. package/dist/analysis/utils/operation-full-provide-identifier-with-cui.mjs +8 -1
  21. package/dist/analysis/utils/operation-full.mjs +4 -2
  22. package/dist/analysis/utils/passkey-deletion-operation-full.mjs +5 -1
  23. package/dist/analysis/utils/passkey-enrollment-operation-full.mjs +5 -1
  24. package/dist/analysis/utils/passkey-login-operation-full.mjs +5 -1
  25. package/dist/analysis/utils/password-enrollment-operation-full.mjs +5 -1
  26. package/dist/analysis/utils/password-login-operation-full.mjs +5 -1
  27. package/dist/analysis/utils/provide-data-operation-full.mjs +5 -1
  28. package/dist/analysis/utils/sms-otp-operation-full.mjs +5 -1
  29. package/dist/analysis/utils/social-operation-login.mjs +5 -1
  30. package/dist/analysis/utils/sso-operation-login.mjs +5 -1
  31. package/dist/analysis/utils/totp-operation-full.mjs +5 -1
  32. package/dist/analysis/utils/trusted-device-operation-full.mjs +9 -1
  33. package/dist/analysis/version.mjs +1 -1
  34. package/dist/cdn/script.global.js +4 -4
  35. package/dist/index.d.mts +1366 -883
  36. package/dist/index.d.ts +1366 -883
  37. package/dist/index.js +4 -4
  38. package/dist/index.mjs +4 -4
  39. package/package.json +1 -1
@@ -4,13 +4,8 @@ import { AuthEventName } from "./types.mjs";
4
4
  import { CONFIG_REQUEST_HEADER, DEFAULT_RELIABILITY_CONFIG } from "./config.mjs";
5
5
  import { EventStore } from "./store/event-store.mjs";
6
6
  import { listenWindow, readDocumentHidden, readPageUrl } from "./readers/page.mjs";
7
- var PROBE_QUEUE_MAX_ITEMS = 500;
8
- var PROBE_QUEUE_MAX_BYTES = 512 * 1024;
9
- var PROBE_BATCH_MAX_ITEMS = 200;
10
- var PROBE_BATCH_MAX_BYTES = 200 * 1024;
11
- var PROBE_PAYLOAD_MAX_BYTES = 16 * 1024;
12
7
  var UNLOAD_BUDGET_BYTES = 6e4;
13
- var UNLOAD_PROBE_MAX_BYTES = 32e3;
8
+ var UNLOAD_PROBE_BYTES = 32e3;
14
9
  function utf8Length(text) {
15
10
  let bytes = 0;
16
11
  for (let i = 0; i < text.length; i++) {
@@ -77,15 +72,10 @@ var RequestQueue = class {
77
72
  */
78
73
  telemetryQueue = [];
79
74
  /**
80
- * Probe items (`tracker.probe()`): in memory only — never the durable outbox — capped by
81
- * {@link PROBE_QUEUE_MAX_ITEMS} / {@link PROBE_QUEUE_MAX_BYTES}. They arm the flush timer, ride
82
- * the regular batches (probe-only batches allowed), retry under `config.retry`, and are the lowest
83
- * priority at unload (a separate request after the event request, within the remaining budget).
75
+ * Optional probe lane (`probes()` plugin, see {@link ProbeLane}); without one, probe items are dropped
76
+ * and the flush paths below never see any.
84
77
  */
85
- probeQueue = [];
86
- probeBytes = 0;
87
- /** Probe items dropped on this load (caps, permanent rejection, exhausted retries, unload budget). */
88
- probesDropped = 0;
78
+ lane;
89
79
  timer = null;
90
80
  isFlushing = false;
91
81
  flushingDone;
@@ -220,114 +210,6 @@ var RequestQueue = class {
220
210
  this.logger.error("Failed to enqueue telemetry", error);
221
211
  }
222
212
  }
223
- /**
224
- * Queue a probe item (in memory only). The payload is snapshotted (serialized once) so later host
225
- * mutations cannot change it; items over {@link PROBE_PAYLOAD_MAX_BYTES} are dropped, and the
226
- * oldest items are dropped beyond the queue caps. Arms the normal flush timer.
227
- *
228
- * Error situations:
229
- * - Any error while enqueueing or scheduling the flush timer: log "Failed to enqueue probe"
230
- */
231
- enqueueProbe(entry, capturedSessionId = this.sessionId) {
232
- try {
233
- if (this.destroyed) return;
234
- const payloadJson = JSON.stringify(entry.payload);
235
- if (typeof payloadJson !== "string" || utf8Length(payloadJson) > PROBE_PAYLOAD_MAX_BYTES) {
236
- this.probesDropped++;
237
- return;
238
- }
239
- const snapshot = { ...entry, payload: JSON.parse(payloadJson) };
240
- const bytes = utf8Length(JSON.stringify(snapshot));
241
- this.probeQueue.push({ sessionId: capturedSessionId, entry: snapshot, bytes, attempts: 0, flushAfter: 0 });
242
- this.probeBytes += bytes;
243
- while (this.probeQueue.length > PROBE_QUEUE_MAX_ITEMS || this.probeBytes > PROBE_QUEUE_MAX_BYTES && this.probeQueue.length > 1) {
244
- const dropped = this.probeQueue.shift();
245
- this.probeBytes -= dropped.bytes;
246
- this.probesDropped++;
247
- }
248
- this.scheduleFlush(this.flushInterval(), "timer");
249
- } catch (error) {
250
- this.logger.error("Failed to enqueue probe", error);
251
- }
252
- }
253
- /** Number of probe items dropped on this load (diagnostics / tests). */
254
- droppedProbeCount() {
255
- return this.probesDropped;
256
- }
257
- dueProbes(now) {
258
- return this.probeQueue.filter((item) => item.flushAfter <= now);
259
- }
260
- /** Oldest-first selection for one session, within an item and byte cap (at least one item when any fits the item cap). */
261
- selectProbes(candidates, sessionId, maxItems, maxBytes) {
262
- const selected = [];
263
- let bytes = 0;
264
- for (const item of candidates) {
265
- if (selected.length >= maxItems) break;
266
- if (item.sessionId !== sessionId) continue;
267
- if (bytes + item.bytes > maxBytes) {
268
- if (selected.length === 0 && maxBytes >= PROBE_BATCH_MAX_BYTES) selected.push(item);
269
- break;
270
- }
271
- bytes += item.bytes;
272
- selected.push(item);
273
- }
274
- return selected;
275
- }
276
- removeProbes(items) {
277
- if (items.length === 0) return;
278
- const removed = new Set(items);
279
- this.probeQueue = this.probeQueue.filter((item) => !removed.has(item));
280
- for (const item of items) this.probeBytes -= item.bytes;
281
- }
282
- dropProbes(items) {
283
- this.removeProbes(items);
284
- this.probesDropped += items.length;
285
- }
286
- /** Same policy as events: 2xx delivered, non-retryable dropped, retryable backed off up to `config.retry.maxAttempts`. */
287
- handleProbeSendResult(response, items, now) {
288
- if (items.length === 0) return;
289
- const status = response.statusCode;
290
- if (typeof status === "number" && status >= 200 && status < 300) {
291
- this.removeProbes(items);
292
- return;
293
- }
294
- if (!this.isRetryable(status)) {
295
- this.dropProbes(items);
296
- return;
297
- }
298
- const maxAttempts = Math.max(1, this.config.retry.maxAttempts);
299
- const retryAfterMs = this.parseRetryAfter(response);
300
- const exhausted = [];
301
- for (const item of items) {
302
- item.attempts += 1;
303
- if (item.attempts >= maxAttempts) exhausted.push(item);
304
- else item.flushAfter = now + this.backoffForAttempt(item.attempts, retryAfterMs);
305
- }
306
- this.dropProbes(exhausted);
307
- }
308
- /**
309
- * Unload / final teardown: one probe-only request for the current session within `budget` bytes,
310
- * sent after the event request so the events' delivery is unaffected. Leftovers are dropped when
311
- * `dropRest` (pagehide, destroy); otherwise they stay queued.
312
- */
313
- sendUnloadProbes(budget, reason, dropRest, useBeacon) {
314
- const sessionId = this.sessionId;
315
- const envelope = { sessionId, events: [], sdk: this.sdk, probes: [] };
316
- this.attachBatchMeta(envelope, [], reason);
317
- const overhead = utf8Length(JSON.stringify(envelope)) + 120;
318
- const items = budget > overhead ? this.selectProbes(this.probeQueue, sessionId, PROBE_BATCH_MAX_ITEMS, budget - overhead) : [];
319
- if (items.length > 0) {
320
- envelope.probes = items.map((item) => item.entry);
321
- this.attachBatchMeta(envelope, items, reason);
322
- if (useBeacon && this.config.beaconKeepalive && this.transport.sendBeacon) {
323
- this.transport.sendBeacon(envelope);
324
- } else {
325
- void Promise.resolve(this.transport.send(envelope)).catch(() => void 0);
326
- }
327
- this.removeProbes(items);
328
- }
329
- if (dropRest) this.dropProbes(this.probeQueue.slice());
330
- }
331
213
  scheduleAfterEnqueue(event) {
332
214
  this.dirtySinceSyncFlush = true;
333
215
  if (event !== void 0 && this.isPriorityEvent(event)) {
@@ -389,7 +271,7 @@ var RequestQueue = class {
389
271
  if (reason === "destroy") {
390
272
  await this.flushingDone;
391
273
  try {
392
- this.sendUnloadProbes(UNLOAD_PROBE_MAX_BYTES, reason, true, false);
274
+ this.lane?.sendUnload(UNLOAD_PROBE_BYTES, reason, true, false);
393
275
  } catch (error) {
394
276
  this.logger.error("Unexpected error in queue flush", error);
395
277
  }
@@ -407,7 +289,8 @@ var RequestQueue = class {
407
289
  const now = Date.now();
408
290
  const due = this.getDuePending(now);
409
291
  const dueLows = this.lowPending.filter((entry) => entry.flushAfter <= now);
410
- const dueProbes = reason === "destroy" ? [] : this.dueProbes(now);
292
+ const lane = this.lane;
293
+ const dueProbes = reason === "destroy" || !lane ? [] : lane.items.filter((item) => item.flushAfter <= now);
411
294
  const onlyDeviceInfo = due.length === 0 && dueLows.length === 0 && this.lowsQueue.length === 0 && this.telemetryQueue.length === 0 && dueProbes.length === 0;
412
295
  if (onlyDeviceInfo && !this.hasStandaloneDeviceInfo()) {
413
296
  break;
@@ -419,7 +302,7 @@ var RequestQueue = class {
419
302
  this.prepareEvents(batchEntries, reason);
420
303
  const telemetry = sessionId === this.sessionId ? this.takeTelemetry() : [];
421
304
  const deviceInfo = sessionId === this.sessionId ? this.takeStandaloneDeviceInfo() : void 0;
422
- const probeItems = this.selectProbes(dueProbes, sessionId, PROBE_BATCH_MAX_ITEMS, PROBE_BATCH_MAX_BYTES);
305
+ const probeItems = lane && dueProbes.length > 0 ? lane.selectBatch(dueProbes, sessionId) : [];
423
306
  const batch = {
424
307
  sessionId,
425
308
  events: batchEntries.map((entry) => entry.event),
@@ -452,13 +335,13 @@ var RequestQueue = class {
452
335
  }
453
336
  if (this.requestConfig) this.handleConfigResponse(response);
454
337
  this.handleLowSendResult(response, lowEntries, now);
455
- this.handleProbeSendResult(response, probeItems, now);
338
+ lane?.handleResult(response, probeItems, now);
456
339
  if (!this.handleSendResult(response, batchEntries, now)) {
457
340
  break;
458
341
  }
459
342
  }
460
343
  if (reason === "destroy") {
461
- this.sendUnloadProbes(UNLOAD_PROBE_MAX_BYTES, reason, true, false);
344
+ this.lane?.sendUnload(UNLOAD_PROBE_BYTES, reason, true, false);
462
345
  }
463
346
  } catch (error) {
464
347
  this.logger.error("Unexpected error in queue flush", error);
@@ -734,17 +617,16 @@ var RequestQueue = class {
734
617
  if (this.timer) {
735
618
  return;
736
619
  }
737
- if (this.pending.length === 0 && this.lowPending.length === 0 && this.lowsQueue.length === 0 && this.probeQueue.length === 0) {
620
+ const probes = this.lane ? this.lane.items : [];
621
+ if (this.pending.length === 0 && this.lowPending.length === 0 && this.lowsQueue.length === 0 && probes.length === 0) {
738
622
  return;
739
623
  }
740
624
  const now = Date.now();
741
- if (this.getDuePending(now).length > 0 || this.lowPending.some((entry) => entry.flushAfter <= now) || this.lowsQueue.length > 0 || this.probeQueue.some((item) => item.flushAfter <= now)) {
625
+ if (this.getDuePending(now).length > 0 || this.lowPending.some((entry) => entry.flushAfter <= now) || this.lowsQueue.length > 0 || probes.some((item) => item.flushAfter <= now)) {
742
626
  this.scheduleFlush(this.flushInterval(), "timer");
743
627
  return;
744
628
  }
745
- const nextDue = Math.min(
746
- ...[...this.pending, ...this.lowPending, ...this.probeQueue].map((entry) => entry.flushAfter)
747
- );
629
+ const nextDue = Math.min(...[...this.pending, ...this.lowPending, ...probes].map((entry) => entry.flushAfter));
748
630
  this.scheduleFlush(Math.max(0, nextDue - now), "backoff");
749
631
  } catch (error) {
750
632
  this.logger.error("Failed to reschedule queue flush", error);
@@ -870,8 +752,9 @@ var RequestQueue = class {
870
752
  }
871
753
  flushSync(reason) {
872
754
  const eventBytes = this.flushSyncEvents(reason);
873
- if (reason === "pagehide" && this.probeQueue.length > 0) {
874
- this.sendUnloadProbes(Math.min(UNLOAD_BUDGET_BYTES - eventBytes, UNLOAD_PROBE_MAX_BYTES), reason, true, true);
755
+ const lane = this.lane;
756
+ if (reason === "pagehide" && lane && lane.items.length > 0) {
757
+ lane.sendUnload(Math.min(UNLOAD_BUDGET_BYTES - eventBytes, UNLOAD_PROBE_BYTES), reason, true, true);
875
758
  }
876
759
  }
877
760
  /** The event part of the unload flush; returns the serialized size of what it sent (0 = nothing). */
@@ -936,7 +819,7 @@ var RequestQueue = class {
936
819
  try {
937
820
  this.destroyed = true;
938
821
  this.clearTimer();
939
- this.dropProbes(this.probeQueue.slice());
822
+ if (this.lane) this.lane.drop(this.lane.items.slice());
940
823
  if (typeof window !== "undefined") {
941
824
  if (this.removeVisibilityChange) {
942
825
  this.removeVisibilityChange();
@@ -951,13 +834,7 @@ var RequestQueue = class {
951
834
  }
952
835
  };
953
836
  export {
954
- PROBE_BATCH_MAX_BYTES,
955
- PROBE_BATCH_MAX_ITEMS,
956
- PROBE_PAYLOAD_MAX_BYTES,
957
- PROBE_QUEUE_MAX_BYTES,
958
- PROBE_QUEUE_MAX_ITEMS,
959
837
  RequestQueue,
960
838
  UNLOAD_BUDGET_BYTES,
961
- UNLOAD_PROBE_MAX_BYTES,
962
839
  utf8Length
963
840
  };
@@ -0,0 +1,11 @@
1
+ // src/session-keys.ts
2
+ var SESSION_STORAGE_KEY = "cbo_session_id";
3
+ var SESSION_CONTINUITY_KEY = "cbo_session";
4
+ var SEQ_STORAGE_KEY = "cbo_seq";
5
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
6
+ export {
7
+ SEQ_STORAGE_KEY,
8
+ SESSION_CONTINUITY_KEY,
9
+ SESSION_STORAGE_KEY,
10
+ UUID_RE
11
+ };