@corbado/observe 1.14.1 → 1.15.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.
- package/CHANGELOG.md +37 -0
- package/dist/analysis/config.mjs +20 -1
- package/dist/analysis/device.mjs +4 -2
- package/dist/analysis/index.mjs +11 -0
- package/dist/analysis/plugins/device-handle-max-age.mjs +8 -0
- package/dist/analysis/queue.mjs +3 -28
- package/dist/analysis/readers/page.mjs +4 -1
- package/dist/analysis/store/event-store.mjs +24 -1
- package/dist/analysis/tracker-core.mjs +30 -19
- package/dist/analysis/utils/email-link-continuity.mjs +6 -1
- package/dist/analysis/utils/source-path.mjs +23 -1
- package/dist/analysis/utils/webauthn-ceremony.mjs +279 -0
- package/dist/analysis/utils/webauthn-payloads.mjs +11 -5
- package/dist/analysis/version.mjs +1 -1
- package/dist/cdn/script.global.js +2 -2
- package/dist/index.d.mts +85 -23
- package/dist/index.d.ts +85 -23
- package/dist/index.js +4 -4
- package/dist/index.mjs +4 -4
- package/dist/manifest/index.d.mts +309 -0
- package/dist/manifest/index.d.ts +309 -0
- package/dist/manifest/index.js +787 -0
- package/dist/manifest/index.mjs +740 -0
- package/manifest/package.json +8 -0
- package/package.json +4 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,42 @@
|
|
|
1
1
|
# @corbado/observe
|
|
2
2
|
|
|
3
|
+
## 1.15.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- a2cb17d: Collection manifest v2 descriptors (tools-only subpaths `@corbado/observe/manifest`, `@corbado/autocapture/manifest`;
|
|
8
|
+
data only, nothing changes in the bundles):
|
|
9
|
+
- Contract (additive, optional): `Conditional` (`when`, now also `"!<switch>"`; `requires` = never-ship item id) on
|
|
10
|
+
side effects, gateway fields, generated and stored entries; `SideEffect`; stored `deleteOnly`, `note` and
|
|
11
|
+
`otherwise` (`StoredVariant`: what the entry becomes when its `when` does not hold).
|
|
12
|
+
- Side effects: `ac:request@1` wraps fetch and XMLHttpRequest; `ac:ceremony@1` wraps navigator.credentials (proxy
|
|
13
|
+
with `aap`); `sdk:input@1` / `ac:element@1` watch the document for added elements.
|
|
14
|
+
- Own storage: the private-mode library's temporary test storage is no longer a stored entry (only the
|
|
15
|
+
`sdk:private-mode@1` side effect); config cache `when: "!static"` (absent from static-config builds); legacy debug keys
|
|
16
|
+
`deleteOnly` (project-scoped keys); low outbox `when: "durableLowOutbox"` with a not-written variant; cross-origin
|
|
17
|
+
session cookie and email-link starts carry `requires`.
|
|
18
|
+
- Texts: password managers ("whether the person is signed in to them", "behavior"), transport IP address and
|
|
19
|
+
client hints.
|
|
20
|
+
|
|
21
|
+
## 1.15.0
|
|
22
|
+
|
|
23
|
+
### Minor Changes
|
|
24
|
+
|
|
25
|
+
- 41434cb: Collection manifest v2 descriptors: new subpath `@corbado/observe/manifest` (Node ESM: `@corbado/observe/dist/manifest/index.mjs`) with the descriptor contract (`CapabilityDescriptor`, `GatewayDescriptor`), the v2 vocabulary (collection levels, categories, kind ladders) and the descriptors of `sdk:input@1`, `sdk:window@1`, `sdk:password-managers@1`, `sdk:private-mode@1` and the device and transport gateways. Every `never` promise names a proof test in this package. Runtime code never imports the descriptors: the npm, CDN and analysis builds contain none of it (checked by tests and `test:data-guards`). No runtime change.
|
|
26
|
+
- b8ef6fb: Own storage and generated ids in the collection manifest v2 (gaps G2, G3), dark: the build define `__CBO_DATA_RETENTION__ = true` deletes queued events and low events older than 7 days from the outboxes when the tracker starts (never sent) and deletes expired email-link start notes on read instead of only ignoring them. The new tracker plugin `deviceHandleMaxAge(ms)` (a plugin option, not a top-level `init()` option; the autocapture runtime passes it from the static config) replaces an older device handle with a new one. Without the define and the plugin (npm, CDN) nothing changes. `@corbado/observe/manifest` adds the descriptors `sdk:own-storage` (every `cbo_` key with area, scope, ISO 8601 retention, owner and typed contents), `sdk:generated` (device handle, session, tab, page, event ids, counters, passkey stand-ins with lifetime and `exposedTo`, incl. `getSessionId`), `sdk:page` (today's page path, declared honestly) and `sdk:api`, and the device fields of private mode and password managers.
|
|
27
|
+
- 4c509f6: Strict page gateway masking (collection manifest v2, gap G8), dark behind the build define `__CBO_DATA_PAGE_STRICT__`: the SDK's own source path masks every path segment (page templates come from an integration's `fixedSourcePath` policy), masks query parameters without `=` and keys that are not plain names unless allowlisted, and treats `sourcePathQuery: "all"` as `"allowlist"`. Without the define (npm, CDN) nothing changes.
|
|
28
|
+
- 1760522: Static SDK config for builds that fix every switch at build time (collection manifest v2): when a bundler defines `__CBO_SDK_CONFIG__` (the complete reliability config as JSON text), the tracker runs exactly that config, makes no config request, neither reads nor writes the config cache, ignores init `sdkConfig` overrides, and `ConfigManager` is removed from the bundle (about 3.6 KB minified). A fixed `fixedDataConfig.pmd` still wins. Malformed text runs the built-in defaults and never throws. The npm and CDN builds never define it: unchanged.
|
|
29
|
+
- c5fd614: WebAuthn allowlist `ac:ceremony@1` (collection manifest v2, gap G1), dark behind a build define: `ceremonyRequestOptions`, `ceremonyCreationOptions`, `ceremonyAssertionResponse` and `ceremonyCreationResponse` keep only the allowlisted fields in the existing payload format, so the backend parses them unchanged: the challenge becomes zeros of the same length (options and `clientDataJSON`), the signature, `getPublicKey()`, user name / display name / id, PRF results and unknown fields never leave, a present user handle becomes a placeholder (`userHandlePresent` becomes accurate), and credential ids are replaced by page-scoped random stand-ins of the same length unless the per-call option `passkeyReference` is set (`sanitizeRequestOptions` takes an optional second argument for it). Builds that define `__CBO_DATA_CEREMONY__ = true` route the existing `sanitize*` functions to the allowlist and drop the blocklist. Without the define (npm, CDN) the sanitizers are unchanged. The `@corbado/observe/manifest` subpath adds the `ac:ceremony@1` descriptor.
|
|
30
|
+
|
|
31
|
+
### Patch Changes
|
|
32
|
+
|
|
33
|
+
- 087d2ad: Debug mode no longer writes `cbo_visibility_change_debug` / `cbo_pagehide_debug` (they held the full, unmasked page
|
|
34
|
+
address; nothing read them); the script deletes what older versions left there. Manifest descriptors (data only, never
|
|
35
|
+
in a bundle): the sequence counters are a technical field of each record (`record:sdk.event#seq`, new source kind
|
|
36
|
+
`record`) instead of a generated id; `cbo_storage_schema` declared in local and session storage, both
|
|
37
|
+
`cbo_low_outbox_v1` key forms, the private-mode library's localStorage `test` key and its test databases per area;
|
|
38
|
+
`ac:ceremony@1` names page attributes as a second payload entry point next to request bodies.
|
|
39
|
+
|
|
3
40
|
## 1.14.1
|
|
4
41
|
|
|
5
42
|
### Patch Changes
|
package/dist/analysis/config.mjs
CHANGED
|
@@ -177,6 +177,23 @@ function resolveReliabilityConfig(serverConfig, overrides = {}) {
|
|
|
177
177
|
resolved.retry.maxDelayMs = Math.max(resolved.retry.baseDelayMs, resolved.retry.maxDelayMs);
|
|
178
178
|
return resolved;
|
|
179
179
|
}
|
|
180
|
+
var STATIC_CONFIG_VERSION = "static";
|
|
181
|
+
function staticReliabilityConfig(json, pmd) {
|
|
182
|
+
let value;
|
|
183
|
+
try {
|
|
184
|
+
value = JSON.parse(json);
|
|
185
|
+
} catch {
|
|
186
|
+
value = void 0;
|
|
187
|
+
}
|
|
188
|
+
const valid = !!value && typeof value === "object" && !Array.isArray(value);
|
|
189
|
+
const fields = valid ? value : {};
|
|
190
|
+
const config = resolveReliabilityConfig(void 0, {
|
|
191
|
+
...fields,
|
|
192
|
+
version: typeof fields.version === "string" && fields.version !== "" ? fields.version : STATIC_CONFIG_VERSION,
|
|
193
|
+
...pmd !== void 0 && { pmd }
|
|
194
|
+
});
|
|
195
|
+
return { config, valid };
|
|
196
|
+
}
|
|
180
197
|
function resolveFixedDataConfig(raw) {
|
|
181
198
|
try {
|
|
182
199
|
if (!raw || typeof raw !== "object") {
|
|
@@ -215,10 +232,12 @@ export {
|
|
|
215
232
|
DEFAULT_RELIABILITY_CONFIG,
|
|
216
233
|
DEFAULT_SEQ_LOCK_TIMEOUT_MS,
|
|
217
234
|
DEFAULT_SESSION_INACTIVITY_MS,
|
|
235
|
+
STATIC_CONFIG_VERSION,
|
|
218
236
|
cacheConfigRecord,
|
|
219
237
|
loadCachedConfig,
|
|
220
238
|
loadConfigRecord,
|
|
221
239
|
parseReliabilityConfig,
|
|
222
240
|
resolveFixedDataConfig,
|
|
223
|
-
resolveReliabilityConfig
|
|
241
|
+
resolveReliabilityConfig,
|
|
242
|
+
staticReliabilityConfig
|
|
224
243
|
};
|
package/dist/analysis/device.mjs
CHANGED
|
@@ -59,7 +59,7 @@ async function settlesWithin(promise, timeoutMs) {
|
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
61
|
var DeviceCollector = class {
|
|
62
|
-
constructor(logger, storage, storageSource = "localstorage", sessionStorage, settings = () => DEFAULT_SETTINGS, emitTelemetry, privateModeEnabled = true) {
|
|
62
|
+
constructor(logger, storage, storageSource = "localstorage", sessionStorage, settings = () => DEFAULT_SETTINGS, emitTelemetry, privateModeEnabled = true, deviceHandleMaxAgeMs) {
|
|
63
63
|
this.logger = logger;
|
|
64
64
|
this.storage = storage;
|
|
65
65
|
this.storageSource = storageSource;
|
|
@@ -67,6 +67,7 @@ var DeviceCollector = class {
|
|
|
67
67
|
this.settings = settings;
|
|
68
68
|
this.emitTelemetry = emitTelemetry;
|
|
69
69
|
this.privateModeEnabled = privateModeEnabled;
|
|
70
|
+
this.deviceHandleMaxAgeMs = deviceHandleMaxAgeMs;
|
|
70
71
|
}
|
|
71
72
|
/** privateMode known on this page (from the cache or a detection), reused by every refresh. */
|
|
72
73
|
pagePrivateMode;
|
|
@@ -525,7 +526,8 @@ var DeviceCollector = class {
|
|
|
525
526
|
}
|
|
526
527
|
getClientEnvHandle() {
|
|
527
528
|
const existingClientEnvHandle = this.storage.getItem("cbo_client_env_handle");
|
|
528
|
-
|
|
529
|
+
const maxAgeMs = this.deviceHandleMaxAgeMs;
|
|
530
|
+
if (existingClientEnvHandle && !(maxAgeMs !== void 0 && !(Date.now() - existingClientEnvHandle.timestamp <= maxAgeMs))) {
|
|
529
531
|
return existingClientEnvHandle;
|
|
530
532
|
}
|
|
531
533
|
const newClientEnvHandleValue = uuidv7();
|
package/dist/analysis/index.mjs
CHANGED
|
@@ -13,6 +13,7 @@ export * from "./plugins/raw-errors.mjs";
|
|
|
13
13
|
export * from "./plugins/probes.mjs";
|
|
14
14
|
export * from "./plugins/fixed-source-path.mjs";
|
|
15
15
|
export * from "./plugins/event-time.mjs";
|
|
16
|
+
export * from "./plugins/device-handle-max-age.mjs";
|
|
16
17
|
export * from "./probe-lane.mjs";
|
|
17
18
|
export * from "./transport.mjs";
|
|
18
19
|
export * from "./queue.mjs";
|
|
@@ -53,6 +54,12 @@ import {
|
|
|
53
54
|
sanitizeRequestOptions,
|
|
54
55
|
sanitizeAssertionResponse
|
|
55
56
|
} from "./utils/webauthn-payloads.mjs";
|
|
57
|
+
import {
|
|
58
|
+
ceremonyCreationOptions,
|
|
59
|
+
ceremonyRequestOptions,
|
|
60
|
+
ceremonyCreationResponse,
|
|
61
|
+
ceremonyAssertionResponse
|
|
62
|
+
} from "./utils/webauthn-ceremony.mjs";
|
|
56
63
|
function init(options) {
|
|
57
64
|
const tracker = new CorbadoTracker(options);
|
|
58
65
|
setDefaultTracker(tracker);
|
|
@@ -93,6 +100,10 @@ export {
|
|
|
93
100
|
READER_EXPOSURES,
|
|
94
101
|
SDK_NAME,
|
|
95
102
|
SDK_VERSION,
|
|
103
|
+
ceremonyAssertionResponse,
|
|
104
|
+
ceremonyCreationOptions,
|
|
105
|
+
ceremonyCreationResponse,
|
|
106
|
+
ceremonyRequestOptions,
|
|
96
107
|
destroy,
|
|
97
108
|
detectPasswordManagerExtensions,
|
|
98
109
|
getExperiments,
|
package/dist/analysis/queue.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import { v7 as uuidv7 } from "uuid";
|
|
|
3
3
|
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
|
-
import { listenWindow, readDocumentHidden
|
|
6
|
+
import { listenWindow, readDocumentHidden } from "./readers/page.mjs";
|
|
7
7
|
var UNLOAD_BUDGET_BYTES = 6e4;
|
|
8
8
|
var UNLOAD_PROBE_BYTES = 32e3;
|
|
9
9
|
function utf8Length(text) {
|
|
@@ -19,6 +19,7 @@ function utf8Length(text) {
|
|
|
19
19
|
}
|
|
20
20
|
return bytes;
|
|
21
21
|
}
|
|
22
|
+
var LEGACY_DEBUG_KEYS = ["cbo_visibility_change_debug", "cbo_pagehide_debug"];
|
|
22
23
|
var RequestQueue = class {
|
|
23
24
|
constructor(logger, transport, sessionId, sdk, options = {}, storage) {
|
|
24
25
|
this.logger = logger;
|
|
@@ -44,6 +45,7 @@ var RequestQueue = class {
|
|
|
44
45
|
this.pending = this.store.all();
|
|
45
46
|
}
|
|
46
47
|
if (this.storage) {
|
|
48
|
+
for (const key of LEGACY_DEBUG_KEYS) this.storage.removeItem(key);
|
|
47
49
|
this.lowStore = new EventStore(
|
|
48
50
|
this.storage,
|
|
49
51
|
this.logger,
|
|
@@ -636,7 +638,6 @@ var RequestQueue = class {
|
|
|
636
638
|
if (typeof window === "undefined") return;
|
|
637
639
|
this.onVisibilityChange = () => {
|
|
638
640
|
try {
|
|
639
|
-
this.writeLifecycleDebugOutput("cbo_visibility_change_debug");
|
|
640
641
|
if (readDocumentHidden({
|
|
641
642
|
key: "page:lifecycle.visibility",
|
|
642
643
|
title: "Page visibility",
|
|
@@ -661,7 +662,6 @@ var RequestQueue = class {
|
|
|
661
662
|
};
|
|
662
663
|
this.onPageHide = () => {
|
|
663
664
|
try {
|
|
664
|
-
this.writeLifecycleDebugOutput("cbo_pagehide_debug");
|
|
665
665
|
if (this.config.tph) {
|
|
666
666
|
this.flushSync("pagehide");
|
|
667
667
|
}
|
|
@@ -710,31 +710,6 @@ var RequestQueue = class {
|
|
|
710
710
|
this.onPageHide
|
|
711
711
|
);
|
|
712
712
|
}
|
|
713
|
-
writeLifecycleDebugOutput(key) {
|
|
714
|
-
if (!this.debug || !this.storage) {
|
|
715
|
-
return;
|
|
716
|
-
}
|
|
717
|
-
const output = {
|
|
718
|
-
last_executed: Date.now(),
|
|
719
|
-
url: readPageUrl({
|
|
720
|
-
key: "page:location.url",
|
|
721
|
-
title: "Page address (debug output only)",
|
|
722
|
-
accessNote: "location.href, only written to the debug log",
|
|
723
|
-
group: "Page",
|
|
724
|
-
origin: "observe-external",
|
|
725
|
-
fields: [
|
|
726
|
-
{
|
|
727
|
-
path: "url",
|
|
728
|
-
label: "Full page address",
|
|
729
|
-
category: "technical",
|
|
730
|
-
exposure: "raw",
|
|
731
|
-
transform: { note: "debug mode only; written to own localStorage, never sent" }
|
|
732
|
-
}
|
|
733
|
-
]
|
|
734
|
-
})
|
|
735
|
-
};
|
|
736
|
-
this.storage.setItem(key, output);
|
|
737
|
-
}
|
|
738
713
|
/**
|
|
739
714
|
* Synchronously flush pending events (and best-effort lows) during page unload.
|
|
740
715
|
*
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/readers/page.ts
|
|
2
|
-
import { applySourcePathQuery } from "../utils/source-path.mjs";
|
|
2
|
+
import { applySourcePathQuery, applyStrictSourcePathQuery, maskPathSegments } from "../utils/source-path.mjs";
|
|
3
3
|
function listenWindow(_meta, type, handler, options) {
|
|
4
4
|
if (options === void 0) {
|
|
5
5
|
window.addEventListener(type, handler);
|
|
@@ -18,6 +18,9 @@ function readPageUrl(_meta) {
|
|
|
18
18
|
return window.location.href;
|
|
19
19
|
}
|
|
20
20
|
function readSourcePath(_meta, mode, allow) {
|
|
21
|
+
if (typeof __CBO_DATA_PAGE_STRICT__ === "boolean" && __CBO_DATA_PAGE_STRICT__) {
|
|
22
|
+
return `${maskPathSegments(window.location.pathname)}${applyStrictSourcePathQuery(window.location.search, allow)}`;
|
|
23
|
+
}
|
|
21
24
|
const search = applySourcePathQuery(window.location.search, mode, allow);
|
|
22
25
|
return `${window.location.pathname}${search}`;
|
|
23
26
|
}
|
|
@@ -4,6 +4,14 @@ var OUTBOX_STORAGE_KEY = "cbo_outbox";
|
|
|
4
4
|
var OUTBOX_LOCK_NAME = "cbo_outbox";
|
|
5
5
|
var DEFAULT_MAX_ENTRIES = 500;
|
|
6
6
|
var DEFAULT_MAX_SERIALIZED_LENGTH = 512 * 1024;
|
|
7
|
+
var OUTBOX_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
8
|
+
function outboxEntryTime(entry) {
|
|
9
|
+
const event = entry.event;
|
|
10
|
+
const time = typeof event.timestamp === "number" ? event.timestamp : event.ts;
|
|
11
|
+
if (typeof time === "number" && Number.isFinite(time)) return time;
|
|
12
|
+
const hex = /^([0-9a-f]{8})-?([0-9a-f]{4})-?7/i.exec(entry.id);
|
|
13
|
+
return hex ? parseInt(hex[1] + hex[2], 16) : void 0;
|
|
14
|
+
}
|
|
7
15
|
function isOutboxEntry(value) {
|
|
8
16
|
if (!value || typeof value !== "object") {
|
|
9
17
|
return false;
|
|
@@ -20,10 +28,23 @@ var EventStore = class {
|
|
|
20
28
|
this.lockName = lockName;
|
|
21
29
|
this.storageKey = storageKey;
|
|
22
30
|
this.entries = this.read();
|
|
31
|
+
if (typeof __CBO_DATA_RETENTION__ === "boolean" && __CBO_DATA_RETENTION__) this.purgeExpired(Date.now());
|
|
23
32
|
}
|
|
24
33
|
entries = [];
|
|
25
34
|
/** Malformed entries are dropped on every read; report only once per instance to avoid log spam. */
|
|
26
35
|
reportedMalformed = false;
|
|
36
|
+
/** Deletes entries older than {@link OUTBOX_MAX_AGE_MS} (they are never sent). Never throws. */
|
|
37
|
+
purgeExpired(now) {
|
|
38
|
+
const expired = (entry) => {
|
|
39
|
+
const time = outboxEntryTime(entry);
|
|
40
|
+
return time !== void 0 && now - time > OUTBOX_MAX_AGE_MS;
|
|
41
|
+
};
|
|
42
|
+
if (!this.entries.some(expired)) return;
|
|
43
|
+
const count = this.entries.filter(expired).length;
|
|
44
|
+
this.entries = this.entries.filter((entry) => !expired(entry));
|
|
45
|
+
this.logger.debug(`EventStore deleted ${count} entries past the retention limit`);
|
|
46
|
+
void this.mutate((current) => current.filter((entry) => !expired(entry)));
|
|
47
|
+
}
|
|
27
48
|
read() {
|
|
28
49
|
const raw = this.storage.getItem(this.storageKey);
|
|
29
50
|
if (!Array.isArray(raw)) {
|
|
@@ -129,5 +150,7 @@ export {
|
|
|
129
150
|
DEFAULT_MAX_SERIALIZED_LENGTH,
|
|
130
151
|
EventStore,
|
|
131
152
|
OUTBOX_LOCK_NAME,
|
|
132
|
-
|
|
153
|
+
OUTBOX_MAX_AGE_MS,
|
|
154
|
+
OUTBOX_STORAGE_KEY,
|
|
155
|
+
outboxEntryTime
|
|
133
156
|
};
|
|
@@ -16,6 +16,7 @@ import { migrateLegacyStorage } from "./storage/migration.mjs";
|
|
|
16
16
|
import {
|
|
17
17
|
DEFAULT_RELIABILITY_CONFIG,
|
|
18
18
|
resolveFixedDataConfig,
|
|
19
|
+
staticReliabilityConfig,
|
|
19
20
|
resolveReliabilityConfig
|
|
20
21
|
} from "./config.mjs";
|
|
21
22
|
import { ConfigManager } from "./config-manager.mjs";
|
|
@@ -91,25 +92,31 @@ var CorbadoTrackerCore = class _CorbadoTrackerCore {
|
|
|
91
92
|
if (this.fixedData) {
|
|
92
93
|
this.configOverrides.pmd = this.fixedData.pmd;
|
|
93
94
|
}
|
|
94
|
-
|
|
95
|
-
(
|
|
96
|
-
|
|
97
|
-
(
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
this.
|
|
103
|
-
this.logger
|
|
95
|
+
if (typeof __CBO_SDK_CONFIG__ === "string") {
|
|
96
|
+
const fixed = staticReliabilityConfig(__CBO_SDK_CONFIG__, this.fixedData?.pmd);
|
|
97
|
+
this.config = fixed.config;
|
|
98
|
+
if (!fixed.valid) this.logger.error("Invalid static SDK config: running the built-in defaults");
|
|
99
|
+
} else {
|
|
100
|
+
const completeOverride = Object.keys(DEFAULT_RELIABILITY_CONFIG).every(
|
|
101
|
+
(key) => this.configOverrides[key] !== void 0
|
|
102
|
+
) && Object.keys(DEFAULT_RELIABILITY_CONFIG.retry).every(
|
|
103
|
+
(key) => this.configOverrides.retry?.[key] !== void 0
|
|
104
104
|
);
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
105
|
+
if (!completeOverride) {
|
|
106
|
+
this.configManager = new ConfigManager(
|
|
107
|
+
options.apiBaseUrl + (options.apiConfigPath ?? "/v1/observe/config") + "/" + options.projectId,
|
|
108
|
+
this.persistentStorage,
|
|
109
|
+
this.logger
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
const cachedConfig = this.configManager?.initialConfig;
|
|
113
|
+
this.config = resolveReliabilityConfig(cachedConfig, this.configOverrides);
|
|
114
|
+
if (!completeOverride && !cachedConfig && options.flushInterval != null && this.configOverrides.flushIntervalMs === void 0) {
|
|
115
|
+
this.config = { ...this.config, flushIntervalMs: options.flushInterval };
|
|
116
|
+
}
|
|
117
|
+
if (!completeOverride && !cachedConfig && options.deviceInfoCollectorTimeout != null && this.configOverrides.deviceInfoCollectorTimeoutMs === void 0) {
|
|
118
|
+
this.config = { ...this.config, deviceInfoCollectorTimeoutMs: options.deviceInfoCollectorTimeout };
|
|
119
|
+
}
|
|
113
120
|
}
|
|
114
121
|
if (options.apiBaseUrl !== "") {
|
|
115
122
|
if (!/^https?:\/\//.test(options.apiBaseUrl)) {
|
|
@@ -134,6 +141,7 @@ var CorbadoTrackerCore = class _CorbadoTrackerCore {
|
|
|
134
141
|
if (plugin.rawErrors) this.rawErrors = plugin.rawErrors;
|
|
135
142
|
if (typeof plugin.sourcePath === "function") this.fixedSourcePath = plugin.sourcePath;
|
|
136
143
|
if (plugin.eventTime) this.eventClock = plugin.eventTime;
|
|
144
|
+
if (plugin.deviceHandleMaxAgeMs !== void 0) this.deviceHandleMaxAgeMs = plugin.deviceHandleMaxAgeMs;
|
|
137
145
|
});
|
|
138
146
|
if (options.fixedDataConfig?.crossOriginSession && !this.bridge) {
|
|
139
147
|
this.logger.warn("fixedDataConfig.crossOriginSession is ignored without the crossOriginSession() plugin");
|
|
@@ -196,7 +204,8 @@ var CorbadoTrackerCore = class _CorbadoTrackerCore {
|
|
|
196
204
|
// rather than reportTelemetry: the latter dedupes by exact message and is capped per load,
|
|
197
205
|
// where ever-changing messages would crowd out real error diagnostics.
|
|
198
206
|
(level, message) => this.telemetry(level, message),
|
|
199
|
-
this.fixedData?.privateMode ?? true
|
|
207
|
+
this.fixedData?.privateMode ?? true,
|
|
208
|
+
this.deviceHandleMaxAgeMs
|
|
200
209
|
);
|
|
201
210
|
this.deviceCollector = deviceInfoCollector;
|
|
202
211
|
const deviceInfoDebounceTime = options.deviceInfoDebounceTime ?? 3e4;
|
|
@@ -276,6 +285,8 @@ var CorbadoTrackerCore = class _CorbadoTrackerCore {
|
|
|
276
285
|
fixedSourcePath;
|
|
277
286
|
/** Event clock of a plugin (never throws); undefined or an undefined result = `explicitTimestamp ?? Date.now()`. */
|
|
278
287
|
eventClock;
|
|
288
|
+
/** Device-handle maximum of a plugin; undefined = no maximum. */
|
|
289
|
+
deviceHandleMaxAgeMs;
|
|
279
290
|
/** @internal Shared passive page observation and per-operation context registration. */
|
|
280
291
|
getWindowObserver() {
|
|
281
292
|
return this.windowObserver ??= new SharedWindowObserver(
|
|
@@ -5,9 +5,14 @@ var MAX_ENTRIES = 10;
|
|
|
5
5
|
function read(storage, now) {
|
|
6
6
|
const raw = storage.getItem(EMAIL_LINK_STARTS_STORAGE_KEY);
|
|
7
7
|
if (!Array.isArray(raw)) return [];
|
|
8
|
-
|
|
8
|
+
const valid = raw.filter(
|
|
9
9
|
(entry) => !!entry && typeof entry === "object" && typeof entry.transactionId === "string" && typeof entry.anchor === "string" && typeof entry.sessionId === "string" && typeof entry.startedAt === "number" && now - entry.startedAt < EMAIL_LINK_START_TTL_MS
|
|
10
10
|
);
|
|
11
|
+
if (typeof __CBO_DATA_RETENTION__ === "boolean" && __CBO_DATA_RETENTION__ && valid.length < raw.length) {
|
|
12
|
+
if (valid.length === 0) storage.removeItem(EMAIL_LINK_STARTS_STORAGE_KEY);
|
|
13
|
+
else storage.setItem(EMAIL_LINK_STARTS_STORAGE_KEY, valid);
|
|
14
|
+
}
|
|
15
|
+
return valid;
|
|
11
16
|
}
|
|
12
17
|
function rememberEmailLinkStart(storage, entry) {
|
|
13
18
|
if (!storage) return;
|
|
@@ -33,7 +33,29 @@ function applySourcePathQuery(search, mode, allow) {
|
|
|
33
33
|
});
|
|
34
34
|
return `?${masked.join("&")}`;
|
|
35
35
|
}
|
|
36
|
+
var PLAIN_KEY = /^[A-Za-z][A-Za-z0-9_.\-[\]]{0,63}$/;
|
|
37
|
+
function applyStrictSourcePathQuery(search, allow) {
|
|
38
|
+
if (search === "" || search === "?") return search;
|
|
39
|
+
const query = search.startsWith("?") ? search.slice(1) : search;
|
|
40
|
+
const masked = query.split("&").map((pair) => {
|
|
41
|
+
if (pair === "") return pair;
|
|
42
|
+
const separator = pair.indexOf("=");
|
|
43
|
+
const rawKey = separator === -1 ? pair : pair.slice(0, separator);
|
|
44
|
+
const key = decodeKey(rawKey);
|
|
45
|
+
if (isAllowed(key, allow)) return pair;
|
|
46
|
+
if (separator === -1) return MASKED_QUERY_VALUE;
|
|
47
|
+
const name = PLAIN_KEY.test(key) ? rawKey : MASKED_QUERY_VALUE;
|
|
48
|
+
return pair.length === separator + 1 ? `${name}=` : `${name}=${MASKED_QUERY_VALUE}`;
|
|
49
|
+
});
|
|
50
|
+
return `?${masked.join("&")}`;
|
|
51
|
+
}
|
|
52
|
+
function maskPathSegments(path) {
|
|
53
|
+
const segments = String(path ?? "").split("/").filter((segment) => segment !== "");
|
|
54
|
+
return segments.length === 0 ? "/" : "/" + segments.map(() => MASKED_QUERY_VALUE).join("/");
|
|
55
|
+
}
|
|
36
56
|
export {
|
|
37
57
|
MASKED_QUERY_VALUE,
|
|
38
|
-
applySourcePathQuery
|
|
58
|
+
applySourcePathQuery,
|
|
59
|
+
applyStrictSourcePathQuery,
|
|
60
|
+
maskPathSegments
|
|
39
61
|
};
|