@corbado/observe 0.2.0 → 0.3.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/index.d.mts CHANGED
@@ -23,9 +23,15 @@ declare enum AuthEventName {
23
23
  SubflowTrigger = "subflow_trigger",
24
24
  SubflowError = "subflow_error",
25
25
  SubflowStarted = "subflow_started",
26
- SubflowFinished = "subflow_finished"
26
+ SubflowFinished = "subflow_finished",
27
+ Conversion = "conversion"
27
28
  }
28
29
  interface BaseEvent {
30
+ /**
31
+ * Client-generated idempotency id (uuidv7). Stamped once at capture and persisted with the event so
32
+ * resends (retry / durable outbox / unload beacon) reuse the same value and the server can dedupe.
33
+ */
34
+ id: string;
29
35
  timestamp: number;
30
36
  seq: number;
31
37
  type: EventType;
@@ -33,6 +39,7 @@ interface BaseEvent {
33
39
  contexts?: Record<string, unknown>;
34
40
  tags?: Record<string, string>;
35
41
  deviceInfo?: DeviceInfo;
42
+ meta?: EventMeta;
36
43
  }
37
44
  type UserReference = {
38
45
  userId?: string;
@@ -59,6 +66,16 @@ type MultiFlowStarted = {
59
66
  type FlowDecided = {
60
67
  flowName: FlowType;
61
68
  };
69
+ /**
70
+ * A business/marketing conversion that is not part of an authentication flow,
71
+ * e.g. a completed purchase, a started subscription, or a newsletter signup.
72
+ */
73
+ type Conversion = {
74
+ /** Name of the conversion, e.g. `"purchase"` or `"add-to-cart"`. */
75
+ name: string;
76
+ /** Where the conversion happened, e.g. `"checkout"` or `"product-page"`. */
77
+ touchpoint?: string;
78
+ } & UserReference;
62
79
  type FlowFinished = {
63
80
  flowName: FlowType;
64
81
  explicitOutcome?: "skipped" | "invisible";
@@ -136,6 +153,18 @@ interface LowEvent {
136
153
  }
137
154
  interface EventBatchMeta {
138
155
  sent: number;
156
+ transport: "fetch" | "beacon";
157
+ /** Number of prior failed delivery attempts for this batch; omitted on the first attempt. */
158
+ retryCount?: number;
159
+ /**
160
+ * Version of the SDK reliability config this batch was captured under; omitted when running on
161
+ * built-in defaults. Lets server-side analyses segment by the config actually in effect (config
162
+ * changes propagate with page-load lag, so calendar-date cohorts are mixed).
163
+ */
164
+ configVersion?: string;
165
+ }
166
+ interface EventMeta {
167
+ trackingSourcePath: string;
139
168
  }
140
169
  interface EventBatch {
141
170
  sessionId: string;
@@ -149,7 +178,13 @@ interface DeviceInfo {
149
178
  type: DeviceType;
150
179
  clientEnvHandle: string;
151
180
  clientEnvHandleMeta: ClientEnvHandleMeta;
181
+ /**
182
+ * Per-tab id (uuidv7) stored in `sessionStorage`. Distinguishes two tabs that share one
183
+ * `localStorage`-backed session when session continuity is enabled.
184
+ */
185
+ tabId?: string;
152
186
  data: DeviceInfoDataWeb;
187
+ collectionErrors?: DeviceInfoCollectionError[];
153
188
  }
154
189
  interface DeviceInfoDataWeb {
155
190
  bluetoothAvailable?: boolean;
@@ -160,6 +195,11 @@ interface DeviceInfoDataWeb {
160
195
  privateMode?: boolean;
161
196
  webdriver?: boolean;
162
197
  }
198
+ type DeviceInfoCollectionError = {
199
+ field: keyof DeviceInfoDataWeb;
200
+ source: string;
201
+ error: NormalizedError;
202
+ };
163
203
  type ClientCapabilities = Record<string, string>;
164
204
  type JavaScriptHighEntropy = {
165
205
  platform?: string;
@@ -171,6 +211,70 @@ type ClientEnvHandleMeta = {
171
211
  source: ClientEnvHandleMetaSource;
172
212
  };
173
213
  type ClientEnvHandleMetaSource = "localstorage" | "cookie" | "native";
214
+ /**
215
+ * Client-side retry configuration (exponential backoff) for failed event flushes.
216
+ * Mirrors the server `observeEventCreateResRetry` schema.
217
+ */
218
+ interface SdkRetryConfig {
219
+ /** Maximum number of send attempts, including the first one. `1` disables retries. */
220
+ maxAttempts: number;
221
+ /** Base backoff delay between attempts, in milliseconds. */
222
+ baseDelayMs: number;
223
+ /** Maximum backoff delay between attempts, in milliseconds. */
224
+ maxDelayMs: number;
225
+ }
226
+ /**
227
+ * SDK reliability configuration returned by the events endpoint (opt-in via the
228
+ * `X-Corbado-Observe-Config` header). Mirrors the server `observeEventCreateRes` schema.
229
+ * All delivery-reliability features default OFF until the server returns config.
230
+ *
231
+ * @remarks
232
+ * Boot-snapshot model: the config resolved at construction (from the localStorage cache) is the
233
+ * config for the entire page load. A config received from the server during the load is only
234
+ * cached for the NEXT load, never applied live — this keeps session identity, seq source and
235
+ * outbox mode coherent within one load.
236
+ */
237
+ interface SdkReliabilityConfig {
238
+ /**
239
+ * Content-derived version (hash) assigned by the server. Echoed in the config request header so
240
+ * the server can answer 204 when nothing changed, and stamped into every batch's
241
+ * `meta.configVersion`. Empty string for the built-in defaults (nothing cached yet).
242
+ */
243
+ version: string;
244
+ /** How often the SDK flushes its event queue, in milliseconds. */
245
+ flushIntervalMs: number;
246
+ /** Persist queued events to a durable outbox so they survive reloads, redirects and context switches. */
247
+ durableOutbox: boolean;
248
+ /** Use keepalive/sendBeacon transport for flushes triggered during page unload. */
249
+ beaconKeepalive: boolean;
250
+ /** Maintain session continuity across reloads/tabs by persisting the session id in localStorage. */
251
+ sessionContinuity: boolean;
252
+ /**
253
+ * Inactivity threshold for continuity sessions, in milliseconds. When more time than this has
254
+ * passed since the last tracked activity, the session id is rotated on the next load. Only
255
+ * relevant when `sessionContinuity` is enabled.
256
+ */
257
+ sessionInactivityMs: number;
258
+ /**
259
+ * Flush-trigger switch: flush pending events when the document becomes hidden
260
+ * (`visibilitychange`). Defaults to true — a missing value can never disable delivery.
261
+ */
262
+ tvc: boolean;
263
+ /** Flush-trigger switch: flush pending events on `pagehide` (navigation/close). Defaults to true. */
264
+ tph: boolean;
265
+ /** Flush-trigger switch: flush pending events when the integrator calls `destroy()`. Defaults to true. */
266
+ td: boolean;
267
+ /**
268
+ * Event names that trigger an immediate flush when enqueued (e.g. `flow_finished`,
269
+ * `conversion`), so high-value events go out via a normal fetch while the page is still alive
270
+ * instead of relying on unload delivery. Empty by default (no priority events). The whole
271
+ * pending queue is sent, and the immediate flush coexists with the interval timer without
272
+ * double-sending (the in-flight drain absorbs it).
273
+ */
274
+ flushOnEventNames: string[];
275
+ /** Retry configuration for failed event flushes. */
276
+ retry: SdkRetryConfig;
277
+ }
174
278
 
175
279
  type StepHelper<TStart = {}, TFinished = {}, TTypedError = never> = {
176
280
  start: (data: TStart, options?: StepOptions) => void;
@@ -373,18 +477,14 @@ interface TrackerOptions {
373
477
  deviceInfoDebounceTime?: number;
374
478
  defaultTags?: Record<string, string>;
375
479
  applicationId?: string;
480
+ /**
481
+ * @deprecated The flush interval is server-controlled via the SDK reliability config
482
+ * (`flushIntervalMs`). This option only applies as long as no server config has been received
483
+ * (i.e. before the first config response is cached); once a server config is cached it wins.
484
+ * It will be removed in a future release.
485
+ */
376
486
  flushInterval?: number;
377
487
  }
378
- /**
379
- * @remarks
380
- * **Constructor**
381
- * - If `apiBaseUrl` is non-empty and missing `http://` or `https://`: logs via `logger.critical` (does not throw).
382
- * - If `apiBaseUrl` is non-empty and ends with `/`: logs via `logger.critical` (does not throw).
383
- * - Session bootstrap (`getSessionId`): on failure logs `Failed to get session id` and continues with an empty session id (does not throw).
384
- *
385
- * **Typed tracking helpers** (`flowStarted`, `flowDecided`, and the other public methods that call `track`)
386
- * - Error handling for queued events is defined on {@link CorbadoTracker.track}.
387
- */
388
488
  declare class CorbadoTracker {
389
489
  private options;
390
490
  private logger;
@@ -392,11 +492,21 @@ declare class CorbadoTracker {
392
492
  private sessionId;
393
493
  private storage;
394
494
  private sessionStorage;
495
+ /** Dedicated localStorage engine for reliability state (config cache, outbox, continuity session, seq). */
496
+ private persistentStorage;
497
+ /** Boot snapshot of the reliability config; immutable for the lifetime of this page load. */
498
+ private readonly config;
395
499
  private deviceInfoCollector;
396
500
  private deviceInfoDebounceTime;
397
501
  private deviceInfoTransmittedLastTime;
398
502
  private seq;
399
503
  constructor(options: TrackerOptions);
504
+ /**
505
+ * Cache fresh server config as last-known. Boot-snapshot model: the config is NOT applied to this
506
+ * load — `this.config` stays the construction-time snapshot — it takes effect on the next page
507
+ * load via {@link loadCachedConfig}.
508
+ */
509
+ private handleConfig;
400
510
  /**
401
511
  * Returns the logger used by this tracker.
402
512
  */
@@ -404,7 +514,25 @@ declare class CorbadoTracker {
404
514
  private applicationTag;
405
515
  private isTrackingBlocked;
406
516
  private getSessionId;
517
+ /**
518
+ * Resolve the session id from localStorage so it survives reloads and is shared across tabs of the
519
+ * same browser. Rotates after `config.sessionInactivityMs` of inactivity (server-controlled,
520
+ * boot-snapshot like the rest of the config), resetting the seq counter.
521
+ */
522
+ private getContinuitySessionId;
523
+ /**
524
+ * Return the next sequence number. With session continuity the counter is persisted so ordering
525
+ * survives reloads/redirects; the read-increment-write runs under a cross-tab Web Lock
526
+ * ({@link SEQ_LOCK_NAME}) so two tabs sharing the session cannot allocate the same seq (where Web
527
+ * Locks are unavailable it degrades to the historical best-effort merge). Lock grants are FIFO, so
528
+ * seq order matches `track()` call order within a tab. Every tracked event also bumps the
529
+ * continuity session's `lastActiveAt`, so the inactivity window measures real user inactivity
530
+ * instead of time-since-page-load (a long-lived active tab must not rotate the session on the next
531
+ * reload).
532
+ */
533
+ private nextSeq;
407
534
  private updateDeviceDebounced;
535
+ private resolveTrackingSourcePath;
408
536
  trackSubflowStarted(subflowType: SubflowType, data: Record<string, any>, options?: StepOptions): void;
409
537
  trackSubflowTrigger(subflowType: SubflowType, data: Record<string, any>, options?: StepOptions): void;
410
538
  trackSubflowStepStarted(subflowType: SubflowType, stepName: string, data: Record<string, any>, options?: StepOptions, ignoreAsInteraction?: boolean): void;
@@ -457,6 +585,29 @@ declare class CorbadoTracker {
457
585
  * ```
458
586
  */
459
587
  flowDecided(data: FlowDecided, tags?: Record<string, string>): void;
588
+ /**
589
+ * Track a business/marketing conversion that is not part of an authentication flow.
590
+ *
591
+ * @remarks
592
+ * Use this for outcomes you care about beyond auth, such as a completed purchase,
593
+ * a started subscription, or a newsletter signup. Provide a stable `name` for the
594
+ * conversion and (optionally) the `touchpoint` where it happened so it can be
595
+ * segmented in the same way as flows. If available, include `userId` and/or
596
+ * `identifier` to link the conversion to a user.
597
+ *
598
+ * @param data - Conversion name, optional touchpoint, and optional user reference.
599
+ * @param tags - Optional key-value tags for filtering and segmentation.
600
+ *
601
+ * @example
602
+ * ```typescript
603
+ * tracker.conversion({
604
+ * name: "purchase",
605
+ * touchpoint: "checkout",
606
+ * userId: "usr_123",
607
+ * });
608
+ * ```
609
+ */
610
+ conversion(data: Conversion, tags?: Record<string, string>): void;
460
611
  /**
461
612
  * Track when a flow successfully completes.
462
613
  *
@@ -571,6 +722,8 @@ declare class CorbadoTracker {
571
722
  * After calling destroy, the tracker must not be used. Operation objects
572
723
  * returned by factory methods (e.g. `passwordLoginFullOperation`) that hold
573
724
  * DOM references must be destroyed separately by the caller.
725
+ * The final flush is gated by the server-controlled trigger switch `td` (default true);
726
+ * resources are released either way.
574
727
  */
575
728
  destroy(): Promise<void>;
576
729
  /** @internal */
@@ -583,9 +736,11 @@ declare const SDK_VERSION: string;
583
736
  interface TransportMakeRequestResponse {
584
737
  statusCode?: number;
585
738
  headers?: Record<string, string | null>;
739
+ /** Parsed SDK reliability config, present only when the server returns it (HTTP 200). */
740
+ config?: SdkReliabilityConfig;
586
741
  }
587
742
  interface Transport {
588
- send(batch: EventBatch): Promise<TransportMakeRequestResponse>;
743
+ send(batch: EventBatch, extraHeaders?: Record<string, string>): Promise<TransportMakeRequestResponse>;
589
744
  sendBeacon?(batch: EventBatch): boolean;
590
745
  flush(timeout?: number): Promise<boolean>;
591
746
  }
@@ -595,58 +750,6 @@ interface TransportOptions {
595
750
  headers?: Record<string, string>;
596
751
  }
597
752
 
598
- interface QueueOptions {
599
- batchSize?: number;
600
- flushInterval?: number;
601
- }
602
- /**
603
- * @remarks When `window` is defined, `visibilitychange` (when the document becomes hidden) and `pagehide` trigger a synchronous beacon flush (`flushSync`). Any thrown error in that path is logged as "Unexpected error in queue flushSync".
604
- */
605
- declare class RequestQueue {
606
- private readonly logger;
607
- private transport;
608
- private sessionId;
609
- private readonly sdk;
610
- private options;
611
- private queue;
612
- private lowsQueue;
613
- private timer;
614
- private isFlushing;
615
- private onVisibilityChange;
616
- private onPageHide;
617
- constructor(logger: Logger, transport: Transport, sessionId: string, sdk: SdkInfo, options?: QueueOptions);
618
- setSessionId(sessionId: string): void;
619
- /**
620
- * Error situations:
621
- * - Any error while enqueueing or scheduling flush (including a synchronous throw from `flush`): log "Failed to enqueue event"
622
- */
623
- enqueue(event: Event): void;
624
- /**
625
- * Error situations:
626
- * - Any error while enqueueing or scheduling flush (including a synchronous throw from `flush`): log "Failed to enqueue low event"
627
- */
628
- enqueueLow(low: LowEvent): void;
629
- /**
630
- * Error situations:
631
- * - Queue is empty: return without calling transport.send
632
- * - A flush is already in progress: return without calling transport.send again
633
- * - transport.send rejects or throws, or any other error in the try block: log "Unexpected error in queue flush"
634
- */
635
- flush(): Promise<void>;
636
- private setupLifecycleHooks;
637
- /**
638
- * Synchronously flush both events and lows via `sendBeacon` if the transport supports it.
639
- * Safe to call multiple times: a no-op when both queues are empty.
640
- */
641
- flushKeepalive(): void;
642
- private flushSync;
643
- /**
644
- * Error situations:
645
- * - Timer clearance or event listener removal throws: log "Failed to destroy queue lifecycle hooks"
646
- */
647
- destroy(): void;
648
- }
649
-
650
753
  interface StorageEngine {
651
754
  getItem<T>(key: string): T | null;
652
755
  setItem<T>(key: string, value: T): void;
@@ -727,9 +830,282 @@ declare class SessionStorage implements StorageEngine {
727
830
  removeItem(key: string): void;
728
831
  }
729
832
 
833
+ interface QueueOptions {
834
+ /**
835
+ * Maximum number of events per request (payload cap). A backlog larger than this is drained in
836
+ * sequential capped batches within one flush. Reaching this size does NOT trigger an early
837
+ * flush — delivery cadence is controlled solely by the flush interval.
838
+ */
839
+ batchSize?: number;
840
+ /**
841
+ * Explicit override of the flush timer interval; otherwise `config.flushIntervalMs` is used.
842
+ * Internal/test use only — the tracker does not pass this; the interval is server-controlled.
843
+ */
844
+ flushInterval?: number;
845
+ debug?: boolean;
846
+ /**
847
+ * Reliability config snapshot for this page load; defaults to all-features-OFF. Boot-snapshot
848
+ * model: the queue never changes its behavior mid-load. A fresh config returned by the server is
849
+ * only handed to {@link QueueOptions.onConfigReceived} for caching and applies on the next load.
850
+ */
851
+ config?: SdkReliabilityConfig;
852
+ /** Called when the server returns a fresh config, so the client can cache it for the next load. */
853
+ onConfigReceived?: (config: SdkReliabilityConfig) => void;
854
+ /** Whether to send the opt-in config header on the first flush of this load. Defaults to true. */
855
+ requestConfig?: boolean;
856
+ }
857
+ /**
858
+ * @remarks When `window` is defined, `visibilitychange` (when the document becomes hidden) and `pagehide`
859
+ * trigger a synchronous unload flush (`flushSync`). Any thrown error in that path is logged as
860
+ * "Unexpected error in queue flushSync". Sync flushes are deduplicated: once everything pending has
861
+ * been handed to a sync transport, further lifecycle signals are no-ops until something new is
862
+ * enqueued — otherwise a single navigation would resend the same batch several times
863
+ * (visibilitychange + pagehide + per-input flushKeepalive), since entries are intentionally kept
864
+ * under `durableOutbox`.
865
+ *
866
+ * Pending events are held as {@link OutboxEntry} records. When `config.durableOutbox` is enabled they are
867
+ * additionally persisted to a localStorage-backed {@link EventStore} and recovered on the next load.
868
+ * Delivery removes an entry only on a 2xx response; retryable failures back off (honoring `Retry-After`)
869
+ * up to `config.retry.maxAttempts`. Low events are best-effort and never persisted.
870
+ *
871
+ * Flushing is timer-driven only (the flush interval); `batchSize` caps the events per request, and a
872
+ * flush drains all due work in sequential capped batches, stopping at the first failed send.
873
+ */
874
+ declare class RequestQueue {
875
+ private readonly logger;
876
+ private transport;
877
+ private sessionId;
878
+ private readonly sdk;
879
+ private readonly storage?;
880
+ private pending;
881
+ private lowsQueue;
882
+ private timer;
883
+ private isFlushing;
884
+ /**
885
+ * True when something was enqueued (or recovered) since the last sync flush. visibilitychange,
886
+ * pagehide and per-input flushKeepalive calls all funnel into flushSync; this flag makes the
887
+ * duplicate calls no-ops. The async flush path ignores it.
888
+ */
889
+ private dirtySinceSyncFlush;
890
+ private onVisibilityChange;
891
+ private onPageHide;
892
+ private readonly batchSize;
893
+ private readonly flushIntervalOverride?;
894
+ private readonly debug;
895
+ private readonly requestConfig;
896
+ private readonly onConfigReceived?;
897
+ private readonly config;
898
+ /** Event names that trigger an immediate flush at enqueue time (config.flushOnEventNames). */
899
+ private readonly priorityNames;
900
+ private store?;
901
+ /**
902
+ * Once the server has answered the config request with a 2xx (200 = fresh config cached for the
903
+ * next load, 204 = cached version still current) we stop sending the header for this load. A
904
+ * transport-level failure or error status keeps the request pending for the next flush.
905
+ */
906
+ private configResolved;
907
+ constructor(logger: Logger, transport: Transport, sessionId: string, sdk: SdkInfo, options?: QueueOptions, storage?: StorageEngine | undefined);
908
+ setSessionId(sessionId: string): void;
909
+ private flushInterval;
910
+ /**
911
+ * Error situations:
912
+ * - Any error while enqueueing or scheduling the flush timer: log "Failed to enqueue event"
913
+ */
914
+ enqueue(event: Event): void;
915
+ /**
916
+ * Error situations:
917
+ * - Any error while enqueueing or scheduling the flush timer: log "Failed to enqueue low event"
918
+ */
919
+ enqueueLow(low: LowEvent): void;
920
+ private scheduleAfterEnqueue;
921
+ private scheduleFlush;
922
+ private clearTimer;
923
+ /**
924
+ * Error situations:
925
+ * - Queue is empty: return without calling transport.send
926
+ * - A flush is already in progress: return without calling transport.send again
927
+ * - transport.send rejects or throws, or any other error in the try block: log "Unexpected error in queue flush"
928
+ */
929
+ flush(): Promise<void>;
930
+ /**
931
+ * Stamps delivery metadata onto the batch: the config version this load runs under (omitted on
932
+ * built-in defaults) and the number of prior failed delivery attempts (the max across its entries,
933
+ * only when at least one entry is a retry). `entry.attempts` is incremented on each failed flush,
934
+ * so it equals the retry count at send time. The transport later merges in `sent`/`transport`.
935
+ */
936
+ private attachBatchMeta;
937
+ private getDuePending;
938
+ private takeLows;
939
+ private shouldRequestConfig;
940
+ /**
941
+ * Handle the server's answer to the config request. Boot-snapshot model: a fresh config is ONLY
942
+ * handed to `onConfigReceived` for caching — it is never applied to this load. The behavior the
943
+ * queue was constructed with stays in effect until the page unloads.
944
+ */
945
+ private handleConfigResponse;
946
+ /** Returns true when the batch was acknowledged with a 2xx (safe to keep draining). */
947
+ private handleSendResult;
948
+ private isRetryable;
949
+ /**
950
+ * Exponential backoff with equal jitter. The exponential ceiling is `baseDelayMs * 2^(attempt-1)`
951
+ * (capped at `maxDelayMs`); the actual delay is half that ceiling plus a random portion of the other
952
+ * half. The jitter spreads retries from many clients to avoid synchronized retry storms and needs no
953
+ * extra config. A `Retry-After` header, when present, acts as a lower bound.
954
+ */
955
+ private backoffForAttempt;
956
+ private parseRetryAfter;
957
+ private removeEntries;
958
+ private rescheduleIfPending;
959
+ private setupLifecycleHooks;
960
+ private writeLifecycleDebugOutput;
961
+ /**
962
+ * Synchronously flush pending events (and best-effort lows) during page unload.
963
+ *
964
+ * @remarks
965
+ * When `config.beaconKeepalive` is on, uses `sendBeacon` (falling back to fetch keepalive when
966
+ * the UA refuses the beacon); otherwise fires a fire-and-forget `fetch` keepalive request. The
967
+ * send is capped at one `batchSize` chunk per call (unload transports share an in-flight budget
968
+ * with the host app). With `config.durableOutbox` enabled, entries are NOT removed (they are
969
+ * resent on the next load and deduped server-side); otherwise the sent chunk is removed
970
+ * best-effort and a remainder keeps the next lifecycle signal flush-worthy.
971
+ * Deduplicated: a call with nothing newly enqueued since the last sync flush is a no-op.
972
+ */
973
+ flushKeepalive(): void;
974
+ private flushSync;
975
+ /**
976
+ * Error situations:
977
+ * - Timer clearance or event listener removal throws: log "Failed to destroy queue lifecycle hooks"
978
+ */
979
+ destroy(): void;
980
+ }
981
+
982
+ /** localStorage key under which the last-known server config is cached. */
983
+ declare const CONFIG_STORAGE_KEY = "cbo_sdk_config";
984
+ /**
985
+ * Opt-in request header that asks the events endpoint to return the SDK reliability config. Its value
986
+ * is the config version the SDK is currently running, or "1" when none is cached yet. The server
987
+ * answers 204 when that version is still current and 200 + config body when it is stale.
988
+ */
989
+ declare const CONFIG_REQUEST_HEADER = "X-Corbado-Observe-Config";
990
+ /** Built-in flush interval used until the server provides one. Matches the historical default. */
991
+ declare const DEFAULT_FLUSH_INTERVAL_MS = 500;
992
+ /**
993
+ * Clamping bounds for server-provided config values. They protect the client from a dangerous
994
+ * server-side config mistake (e.g. a 0ms flush interval would busy-loop); the server clamps with
995
+ * the same bounds as the first line of defense.
996
+ */
997
+ declare const CONFIG_BOUNDS: {
998
+ readonly flushIntervalMs: {
999
+ readonly min: 100;
1000
+ readonly max: 60000;
1001
+ };
1002
+ readonly retryMaxAttempts: {
1003
+ readonly min: 1;
1004
+ readonly max: 10;
1005
+ };
1006
+ readonly retryDelayMs: {
1007
+ readonly min: 50;
1008
+ readonly max: 120000;
1009
+ };
1010
+ readonly sessionInactivityMs: {
1011
+ readonly min: 60000;
1012
+ readonly max: 86400000;
1013
+ };
1014
+ readonly flushOnEventNames: {
1015
+ readonly maxEntries: 20;
1016
+ readonly maxNameLength: 100;
1017
+ };
1018
+ };
1019
+ /** Built-in continuity-session inactivity threshold (30 minutes); matches the historical default. */
1020
+ declare const DEFAULT_SESSION_INACTIVITY_MS: number;
1021
+ /**
1022
+ * Default config: every delivery-reliability feature is OFF. The SDK behaves like a plain async
1023
+ * fetch-keepalive flusher until the server returns config (which is then cached as last-known and
1024
+ * applied on the NEXT page load — boot-snapshot model). The empty version means "nothing cached";
1025
+ * the config request header sends "1" in that case.
1026
+ */
1027
+ declare const DEFAULT_RELIABILITY_CONFIG: SdkReliabilityConfig;
1028
+ /**
1029
+ * Coerce an arbitrary value (cached blob or parsed API body) into a {@link SdkReliabilityConfig}.
1030
+ * Returns `null` if the input is not an object or carries no `version` (a config without identity
1031
+ * cannot participate in the version handshake and must not be trusted). Missing fields fall back to
1032
+ * the OFF defaults so a partial/older payload can never silently enable a feature; the
1033
+ * flush-trigger switches (`tvc`/`tph`/`td`) invert this and default ON, so a partial payload can
1034
+ * never silently disable delivery. Numeric values are clamped to {@link CONFIG_BOUNDS} so a bad
1035
+ * server value cannot harm the client.
1036
+ */
1037
+ declare function parseReliabilityConfig(raw: unknown): SdkReliabilityConfig | null;
1038
+ /** Read the cached config from storage, or `null` if absent/invalid. */
1039
+ declare function loadCachedConfig(storage: StorageEngine): SdkReliabilityConfig | null;
1040
+ /** Persist the config as last-known so the next page load starts with these features enabled. */
1041
+ declare function cacheConfig(storage: StorageEngine, config: SdkReliabilityConfig): void;
1042
+
1043
+ /** A persisted, not-yet-acknowledged event awaiting delivery. */
1044
+ interface OutboxEntry {
1045
+ /** Event idempotency id; primary key of the entry. */
1046
+ id: string;
1047
+ /** Session id snapshot taken at enqueue time (so session rotation never relabels old entries). */
1048
+ sessionId: string;
1049
+ /** Earliest timestamp (ms) at which this entry may be sent again (backoff). `0` = due now. */
1050
+ flushAfter: number;
1051
+ /** Number of completed send attempts so far. */
1052
+ attempts: number;
1053
+ /** The event payload. */
1054
+ event: Event;
1055
+ }
1056
+ /** localStorage key under which the durable outbox is persisted. */
1057
+ declare const OUTBOX_STORAGE_KEY = "cbo_outbox";
1058
+ /** Web Lock name guarding outbox mutations across tabs. */
1059
+ declare const OUTBOX_LOCK_NAME = "cbo_outbox";
1060
+ /** Default maximum number of entries kept; oldest are evicted past this. */
1061
+ declare const DEFAULT_MAX_ENTRIES = 500;
1062
+ /**
1063
+ * Default budget for the outbox's serialized size, in UTF-16 code units (the unit localStorage
1064
+ * quotas are measured in; ~1 MB on disk). The entry-count cap alone does not bound bytes —
1065
+ * `event.data` is arbitrary customer payload — and localStorage's ~5M-unit quota is shared with
1066
+ * the HOST app: an unbounded outbox could make the host's own writes throw QuotaExceededError.
1067
+ */
1068
+ declare const DEFAULT_MAX_SERIALIZED_LENGTH: number;
1069
+ /**
1070
+ * Durable, localStorage-backed outbox of pending events, mirrored in memory.
1071
+ *
1072
+ * @remarks
1073
+ * Every mutation is a read-merge-write against storage, executed under a cross-tab Web Lock
1074
+ * ({@link OUTBOX_LOCK_NAME}) so concurrent tabs sharing the same localStorage cannot interleave
1075
+ * between the read and the write (which would silently drop the other tab's entries). Where the
1076
+ * Web Locks API is unavailable the mutation runs unlocked, matching the historical best-effort
1077
+ * behavior. Mutations never reject; failures are logged.
1078
+ * The store is size-capped; overflow evicts the oldest entries and logs the count.
1079
+ */
1080
+ declare class EventStore {
1081
+ private readonly storage;
1082
+ private readonly logger;
1083
+ private readonly maxEntries;
1084
+ private readonly maxSerializedLength;
1085
+ private entries;
1086
+ /** Malformed entries are dropped on every read; report only once per instance to avoid log spam. */
1087
+ private reportedMalformed;
1088
+ constructor(storage: StorageEngine, logger: Logger, maxEntries?: number, maxSerializedLength?: number);
1089
+ private read;
1090
+ private mutate;
1091
+ /** Evict oldest entries until the serialized outbox fits {@link maxSerializedLength}. */
1092
+ private enforceSizeBudget;
1093
+ /** Append an entry. No-op if an entry with the same id already exists. */
1094
+ add(entry: OutboxEntry): Promise<void>;
1095
+ /** Re-read from storage and return all entries (memory mirror refreshed). */
1096
+ all(): OutboxEntry[];
1097
+ /** Entries whose `flushAfter` has elapsed, in insertion order. */
1098
+ getDue(now: number): OutboxEntry[];
1099
+ /** Remove entries by id (acknowledged or permanently dropped). */
1100
+ remove(ids: string[]): Promise<void>;
1101
+ /** Replace existing entries (matched by id) with updated copies, e.g. after bumping backoff. */
1102
+ update(updated: OutboxEntry[]): Promise<void>;
1103
+ size(): number;
1104
+ }
1105
+
730
1106
  declare function init(options: TrackerOptions): CorbadoTracker;
731
1107
  declare function getTracker(): CorbadoTracker | undefined;
732
1108
  declare function resetSession(): string | undefined;
733
1109
  declare function destroy(): Promise<void>;
734
1110
 
735
- export { type AuthDecisionFinished, type AuthDecisionStarted, AuthEventName, type AuthMethodDecisionFinished, type AuthMethodDecisionStarted, type AuthMethodType, type BaseEvent, type ClientCapabilities, type ClientEnvHandleMeta, type ClientEnvHandleMetaSource, CookieStorage, CorbadoTracker, type CreateLoggerOptions, type CustomEvent, type DeviceInfo, type DeviceInfoDataWeb, type DeviceType, EmailLinkOperationFull, type EmailLinkOperationPostResponseStart, type EmailLinkOperationSendStart, type EmailLinkOperationSpecType, type EmailOTPOperationPostResponseStart, type EmailOTPOperationSendOTPStart, type EmailOTPOperationSpecType, EmailOtpOperationFull, type Event, type EventBatch, type EventBatchMeta, type EventType, type FlowAutoFinished, type FlowDecided, type FlowFinished, type FlowReset, type FlowStarted, type FlowType, type JavaScriptHighEntropy, LocalStorage, type Logger, type LowEvent, type MultiFlowReset, type MultiFlowStarted, type NormalizedError, OperationFull, OperationFullProvideIdentifierWithCUI, type PasskeyEnrollmentCeremonyFinished, type PasskeyEnrollmentCeremonyStart, type PasskeyEnrollmentGetOptionsFinished, type PasskeyEnrollmentGetOptionsStart, PasskeyEnrollmentOperationFull, type PasskeyLoginCUIGetOptionsFinished, type PasskeyLoginCUIGetOptionsStart, type PasskeyLoginCUISpecType, type PasskeyLoginCeremonyFinished, type PasskeyLoginCeremonyStart, type PasskeyLoginClientError, type PasskeyLoginFinish, type PasskeyLoginGetOptionsFinished, type PasskeyLoginGetOptionsStart, PasskeyLoginOperationFull, type PasskeyLoginStartable, type PasskeyLoginSubmitted, type PasskeyOperationEnrollmentExplicitSpecType, type PasskeyOperationLoginExplicitSpecType, type PasswordEnrollmentAutoTrackConfig, PasswordEnrollmentOperationFull, type PasswordEnrollmentTypedError, type PasswordLoginAutoTrackConfig, PasswordLoginOperationFull, type PasswordLoginTypedError, type PredefinedEvent, type ProvideIdentifierError, type ProvideIdentifierFinish, type ProvideIdentifierPostResponseStart, type ProvideIdentifierSpecType, type ProvideIdentifierStart, type QueueOptions, RequestQueue, SDK_NAME, SDK_VERSION, type SdkInfo, SessionStorage, type SocialLoginExchangeCodeFinished, type SocialLoginExchangeCodeStart, type SocialLoginGetRedirectUrlFinished, type SocialLoginGetRedirectUrlStart, SocialLoginOperationFull, type SocialLoginProviderType, type SocialLoginSpecType, type StepHelper, type StepOptions, type StorageEngine, type SubflowTrigger, type SubflowType, type TrackerOptions, type Transport, type TransportMakeRequestResponse, type TransportOptions, type UserReference, createLogger, destroy, getTracker, init, resetSession };
1111
+ export { type AuthDecisionFinished, type AuthDecisionStarted, AuthEventName, type AuthMethodDecisionFinished, type AuthMethodDecisionStarted, type AuthMethodType, type BaseEvent, CONFIG_BOUNDS, CONFIG_REQUEST_HEADER, CONFIG_STORAGE_KEY, type ClientCapabilities, type ClientEnvHandleMeta, type ClientEnvHandleMetaSource, type Conversion, CookieStorage, CorbadoTracker, type CreateLoggerOptions, type CustomEvent, DEFAULT_FLUSH_INTERVAL_MS, DEFAULT_MAX_ENTRIES, DEFAULT_MAX_SERIALIZED_LENGTH, DEFAULT_RELIABILITY_CONFIG, DEFAULT_SESSION_INACTIVITY_MS, type DeviceInfo, type DeviceInfoCollectionError, type DeviceInfoDataWeb, type DeviceType, EmailLinkOperationFull, type EmailLinkOperationPostResponseStart, type EmailLinkOperationSendStart, type EmailLinkOperationSpecType, type EmailOTPOperationPostResponseStart, type EmailOTPOperationSendOTPStart, type EmailOTPOperationSpecType, EmailOtpOperationFull, type Event, type EventBatch, type EventBatchMeta, type EventMeta, EventStore, type EventType, type FlowAutoFinished, type FlowDecided, type FlowFinished, type FlowReset, type FlowStarted, type FlowType, type JavaScriptHighEntropy, LocalStorage, type Logger, type LowEvent, type MultiFlowReset, type MultiFlowStarted, type NormalizedError, OUTBOX_LOCK_NAME, OUTBOX_STORAGE_KEY, OperationFull, OperationFullProvideIdentifierWithCUI, type OutboxEntry, type PasskeyEnrollmentCeremonyFinished, type PasskeyEnrollmentCeremonyStart, type PasskeyEnrollmentGetOptionsFinished, type PasskeyEnrollmentGetOptionsStart, PasskeyEnrollmentOperationFull, type PasskeyLoginCUIGetOptionsFinished, type PasskeyLoginCUIGetOptionsStart, type PasskeyLoginCUISpecType, type PasskeyLoginCeremonyFinished, type PasskeyLoginCeremonyStart, type PasskeyLoginClientError, type PasskeyLoginFinish, type PasskeyLoginGetOptionsFinished, type PasskeyLoginGetOptionsStart, PasskeyLoginOperationFull, type PasskeyLoginStartable, type PasskeyLoginSubmitted, type PasskeyOperationEnrollmentExplicitSpecType, type PasskeyOperationLoginExplicitSpecType, type PasswordEnrollmentAutoTrackConfig, PasswordEnrollmentOperationFull, type PasswordEnrollmentTypedError, type PasswordLoginAutoTrackConfig, PasswordLoginOperationFull, type PasswordLoginTypedError, type PredefinedEvent, type ProvideIdentifierError, type ProvideIdentifierFinish, type ProvideIdentifierPostResponseStart, type ProvideIdentifierSpecType, type ProvideIdentifierStart, type QueueOptions, RequestQueue, SDK_NAME, SDK_VERSION, type SdkInfo, type SdkReliabilityConfig, type SdkRetryConfig, SessionStorage, type SocialLoginExchangeCodeFinished, type SocialLoginExchangeCodeStart, type SocialLoginGetRedirectUrlFinished, type SocialLoginGetRedirectUrlStart, SocialLoginOperationFull, type SocialLoginProviderType, type SocialLoginSpecType, type StepHelper, type StepOptions, type StorageEngine, type SubflowTrigger, type SubflowType, type TrackerOptions, type Transport, type TransportMakeRequestResponse, type TransportOptions, type UserReference, cacheConfig, createLogger, destroy, getTracker, init, loadCachedConfig, parseReliabilityConfig, resetSession };