@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.
- package/dist/analysis/client.mjs +37 -1595
- package/dist/analysis/default-tracker.mjs +16 -0
- package/dist/analysis/index.mjs +29 -41
- package/dist/analysis/plugins/cookie-storage.mjs +11 -0
- package/dist/analysis/plugins/cross-origin-session.mjs +171 -0
- package/dist/analysis/plugins/probes.mjs +69 -0
- package/dist/analysis/plugins/raw-errors.mjs +8 -0
- package/dist/analysis/probe-lane.mjs +175 -0
- package/dist/analysis/queue.mjs +18 -141
- package/dist/analysis/session-keys.mjs +11 -0
- package/dist/analysis/tracker-core.mjs +1352 -0
- package/dist/analysis/tracker-features.mjs +80 -0
- package/dist/analysis/tracker-functions.mjs +103 -0
- package/dist/analysis/utils/appconfirmation-operation-full.mjs +5 -1
- package/dist/analysis/utils/captcha-operation-full.mjs +5 -1
- package/dist/analysis/utils/email-link-operation-full.mjs +9 -1
- package/dist/analysis/utils/email-otp-operation-full.mjs +5 -1
- package/dist/analysis/utils/form-operation-full.mjs +5 -1
- package/dist/analysis/utils/key-operation-full.mjs +9 -1
- package/dist/analysis/utils/operation-full-provide-identifier-with-cui.mjs +8 -1
- package/dist/analysis/utils/operation-full.mjs +4 -2
- package/dist/analysis/utils/passkey-deletion-operation-full.mjs +5 -1
- package/dist/analysis/utils/passkey-enrollment-operation-full.mjs +5 -1
- package/dist/analysis/utils/passkey-login-operation-full.mjs +5 -1
- package/dist/analysis/utils/password-enrollment-operation-full.mjs +5 -1
- package/dist/analysis/utils/password-login-operation-full.mjs +5 -1
- package/dist/analysis/utils/provide-data-operation-full.mjs +5 -1
- package/dist/analysis/utils/sms-otp-operation-full.mjs +5 -1
- package/dist/analysis/utils/social-operation-login.mjs +5 -1
- package/dist/analysis/utils/sso-operation-login.mjs +5 -1
- package/dist/analysis/utils/totp-operation-full.mjs +5 -1
- package/dist/analysis/utils/trusted-device-operation-full.mjs +9 -1
- package/dist/analysis/version.mjs +1 -1
- package/dist/cdn/script.global.js +4 -4
- package/dist/index.d.mts +1366 -883
- package/dist/index.d.ts +1366 -883
- package/dist/index.js +4 -4
- package/dist/index.mjs +4 -4
- package/package.json +1 -1
package/dist/analysis/queue.mjs
CHANGED
|
@@ -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
|
|
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
|
-
*
|
|
81
|
-
*
|
|
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
|
-
|
|
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.
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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 ||
|
|
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
|
-
|
|
874
|
-
|
|
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.
|
|
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
|
+
};
|