@corbado/observe 1.7.2 → 1.9.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 (76) hide show
  1. package/dist/analysis/client.mjs +1877 -0
  2. package/dist/analysis/config-manager.mjs +108 -0
  3. package/dist/analysis/config.mjs +216 -0
  4. package/dist/analysis/device-info-manager.mjs +168 -0
  5. package/dist/analysis/device.mjs +640 -0
  6. package/dist/analysis/handoff.mjs +7 -0
  7. package/dist/analysis/index.mjs +118 -0
  8. package/dist/analysis/logger.mjs +50 -0
  9. package/dist/analysis/low-events/capture-sequence.mjs +11 -0
  10. package/dist/analysis/low-events/ceremony-initiator.mjs +26 -0
  11. package/dist/analysis/low-events/form-input-binding.mjs +46 -0
  12. package/dist/analysis/low-events/grouped-input-tracker.mjs +248 -0
  13. package/dist/analysis/low-events/input-change-recorder.mjs +200 -0
  14. package/dist/analysis/low-events/input-tracker.mjs +302 -0
  15. package/dist/analysis/low-events/otp-input-binding.mjs +67 -0
  16. package/dist/analysis/low-events/shared-window-observer.mjs +296 -0
  17. package/dist/analysis/low-events/sink.mjs +0 -0
  18. package/dist/analysis/low-events/viewport-batcher.mjs +42 -0
  19. package/dist/analysis/low-events/window-tracker.mjs +40 -0
  20. package/dist/analysis/queue.mjs +954 -0
  21. package/dist/analysis/readers/device.mjs +73 -0
  22. package/dist/analysis/readers/input-paste.mjs +91 -0
  23. package/dist/analysis/readers/input.mjs +61 -0
  24. package/dist/analysis/readers/loader-global.mjs +15 -0
  25. package/dist/analysis/readers/meta.mjs +18 -0
  26. package/dist/analysis/readers/network.mjs +14 -0
  27. package/dist/analysis/readers/page.mjs +30 -0
  28. package/dist/analysis/readers/password-manager-dom.mjs +112 -0
  29. package/dist/analysis/readers/password-manager-extensions.mjs +247 -0
  30. package/dist/analysis/readers/private-mode.mjs +9 -0
  31. package/dist/analysis/readers/storage.mjs +28 -0
  32. package/dist/analysis/readers/web-lock.mjs +56 -0
  33. package/dist/analysis/readers/window-events.mjs +48 -0
  34. package/dist/analysis/storage/index.mjs +249 -0
  35. package/dist/analysis/storage/migration.mjs +56 -0
  36. package/dist/analysis/store/event-store.mjs +132 -0
  37. package/dist/analysis/transport.mjs +0 -0
  38. package/dist/analysis/transports/fetch.mjs +150 -0
  39. package/dist/analysis/types.mjs +25 -0
  40. package/dist/analysis/utils/appconfirmation-operation-full.mjs +19 -0
  41. package/dist/analysis/utils/blocked-user-agents.mjs +37 -0
  42. package/dist/analysis/utils/captcha-operation-full.mjs +17 -0
  43. package/dist/analysis/utils/email-link-continuity.mjs +50 -0
  44. package/dist/analysis/utils/email-link-operation-full.mjs +162 -0
  45. package/dist/analysis/utils/email-otp-operation-full.mjs +38 -0
  46. package/dist/analysis/utils/form-operation-full.mjs +80 -0
  47. package/dist/analysis/utils/key-operation-full.mjs +38 -0
  48. package/dist/analysis/utils/normalize-error.mjs +29 -0
  49. package/dist/analysis/utils/operation-full-provide-identifier-with-cui.mjs +46 -0
  50. package/dist/analysis/utils/operation-full.mjs +235 -0
  51. package/dist/analysis/utils/passkey-deletion-operation-full.mjs +17 -0
  52. package/dist/analysis/utils/passkey-enrollment-operation-full.mjs +35 -0
  53. package/dist/analysis/utils/passkey-login-operation-full.mjs +39 -0
  54. package/dist/analysis/utils/password-enrollment-operation-full.mjs +27 -0
  55. package/dist/analysis/utils/password-login-operation-full.mjs +65 -0
  56. package/dist/analysis/utils/password-manager-sampler.mjs +170 -0
  57. package/dist/analysis/utils/provide-data-operation-full.mjs +28 -0
  58. package/dist/analysis/utils/serialize-raw-error.mjs +205 -0
  59. package/dist/analysis/utils/sms-otp-operation-full.mjs +38 -0
  60. package/dist/analysis/utils/social-operation-login.mjs +17 -0
  61. package/dist/analysis/utils/source-path.mjs +39 -0
  62. package/dist/analysis/utils/sso-operation-login.mjs +37 -0
  63. package/dist/analysis/utils/totp-operation-full.mjs +34 -0
  64. package/dist/analysis/utils/trusted-device-operation-full.mjs +100 -0
  65. package/dist/analysis/utils/webauthn-payloads.mjs +101 -0
  66. package/dist/analysis/version.mjs +7 -0
  67. package/dist/cdn/script.global.js +4 -4
  68. package/dist/index.d.mts +285 -21
  69. package/dist/index.d.ts +285 -21
  70. package/dist/index.js +4 -4
  71. package/dist/index.mjs +4 -4
  72. package/package.json +10 -3
  73. package/tools/reader-meta-text.d.mts +2 -0
  74. package/tools/reader-meta-text.mjs +26 -0
  75. package/tools/strip-reader-meta.d.mts +33 -0
  76. package/tools/strip-reader-meta.mjs +131 -0
@@ -0,0 +1,205 @@
1
+ // src/utils/serialize-raw-error.ts
2
+ var RAW_ERROR_BOUNDS = {
3
+ maxStackFrames: { min: 1, max: 100, default: 50 },
4
+ depth: { min: 0, max: 10, default: 3 },
5
+ maxBreadth: { min: 1, max: 1e3, default: 30 },
6
+ maxValueLength: { min: 1, max: 1e4, default: 1024 },
7
+ maxCauseDepth: { min: 0, max: 10, default: 3 },
8
+ maxBytes: { min: 0, max: 65536, default: 32768 }
9
+ };
10
+ var MAX_NODES = 1024;
11
+ var RawErrorBudgetExceeded = class extends Error {
12
+ };
13
+ var ERROR_FIELDS = ["name", "message", "code", "errors", "stack"];
14
+ var HAS_MULTI_BYTE_CHARACTERS = /[^\x20-\x7E]/;
15
+ var tag = (value) => Object.prototype.toString.call(value).slice(8, -1);
16
+ var typeOf = (value) => value === null ? "null" : typeof value === "object" ? tag(value) : typeof value;
17
+ var isErrorLike = (value) => value instanceof Error || tag(value) === "Error" || tag(value) === "DOMException";
18
+ function byteLength(json) {
19
+ return HAS_MULTI_BYTE_CHARACTERS.test(json) ? new TextEncoder().encode(json).byteLength : json.length;
20
+ }
21
+ function clamp(name, value) {
22
+ const bounds = RAW_ERROR_BOUNDS[name];
23
+ if (typeof value !== "number" || !Number.isFinite(value)) return bounds.default;
24
+ return Math.min(bounds.max, Math.max(bounds.min, Math.floor(value)));
25
+ }
26
+ function resolveRawErrorLimits(options = {}) {
27
+ return {
28
+ stack: options.stack === true,
29
+ maxStackFrames: clamp("maxStackFrames", options.maxStackFrames),
30
+ depth: clamp("depth", options.depth),
31
+ maxBreadth: clamp("maxBreadth", options.maxBreadth),
32
+ maxValueLength: clamp("maxValueLength", options.maxValueLength),
33
+ maxCauseDepth: clamp("maxCauseDepth", options.maxCauseDepth),
34
+ maxBytes: clamp("maxBytes", options.maxBytes)
35
+ };
36
+ }
37
+ function serializeRawError(value, options = {}) {
38
+ if (value === void 0) return void 0;
39
+ let limits = resolveRawErrorLimits();
40
+ const fits = (out) => {
41
+ const json = JSON.stringify(out);
42
+ return json === void 0 || byteLength(json) <= limits.maxBytes;
43
+ };
44
+ try {
45
+ limits = resolveRawErrorLimits(options);
46
+ if (limits.maxBytes === 0) return void 0;
47
+ const type = truncate(typeOf(value), 1024);
48
+ for (let depth = limits.depth; depth >= 0; depth--) {
49
+ const ctx = { limits, causeBudget: limits.maxCauseDepth, seen: /* @__PURE__ */ new Set(), nodes: 0, textBytes: 0 };
50
+ try {
51
+ const out = { type, value: walk(value, depth, ctx) };
52
+ if (fits(out)) return out;
53
+ } catch (error) {
54
+ if (!(error instanceof RawErrorBudgetExceeded)) throw error;
55
+ }
56
+ }
57
+ const fallback = { type, truncated: true };
58
+ return fits(fallback) ? fallback : void 0;
59
+ } catch {
60
+ const fallback = { type: "unserializable" };
61
+ return fits(fallback) ? fallback : void 0;
62
+ }
63
+ }
64
+ function accountText(text, ctx) {
65
+ if (text.length > ctx.limits.maxBytes) throw new RawErrorBudgetExceeded();
66
+ ctx.textBytes += byteLength(JSON.stringify(text));
67
+ if (ctx.textBytes > ctx.limits.maxBytes) throw new RawErrorBudgetExceeded();
68
+ return text;
69
+ }
70
+ function walk(value, depth, ctx) {
71
+ if (++ctx.nodes > MAX_NODES) throw new RawErrorBudgetExceeded();
72
+ const out = walkValue(value, depth, ctx);
73
+ return typeof out === "string" ? accountText(out, ctx) : out;
74
+ }
75
+ function walkValue(value, depth, ctx) {
76
+ switch (typeof value) {
77
+ case "string":
78
+ return truncate(value, ctx.limits.maxValueLength);
79
+ case "number":
80
+ return Number.isFinite(value) ? value : String(value);
81
+ case "boolean":
82
+ case "undefined":
83
+ return value;
84
+ case "bigint":
85
+ case "symbol":
86
+ return truncate(String(value), ctx.limits.maxValueLength);
87
+ case "function":
88
+ return `[Function: ${value.name || "anonymous"}]`;
89
+ }
90
+ if (value === null) return null;
91
+ const obj = value;
92
+ if (ctx.seen.has(obj)) return "[Circular]";
93
+ if (depth < 0) return `[${tag(obj)}]`;
94
+ ctx.seen.add(obj);
95
+ try {
96
+ return walkObject(obj, depth, ctx);
97
+ } finally {
98
+ ctx.seen.delete(obj);
99
+ }
100
+ }
101
+ function truncate(value, max) {
102
+ return value.length > max ? value.slice(0, max) + "\u2026" : value;
103
+ }
104
+ function truncateStack(stack, maxFrames, maxValueLength) {
105
+ const textTruncated = stack.length > maxValueLength;
106
+ stack = truncate(stack, maxValueLength);
107
+ const lines = stack.split("\n");
108
+ if (lines.length <= maxFrames + 1) return truncate(stack, maxValueLength);
109
+ const kept = lines.slice(0, maxFrames + 1).join("\n");
110
+ const marker = textTruncated ? "[truncated]" : `[+${lines.length - maxFrames - 1} frames]`;
111
+ return truncate(`${kept}
112
+ ${marker}`, maxValueLength);
113
+ }
114
+ function walkObject(obj, depth, ctx) {
115
+ const { limits } = ctx;
116
+ const kind = tag(obj);
117
+ const next = (v) => walk(v, depth - 1, ctx);
118
+ if (kind === "Date") return Number.isNaN(obj.getTime()) ? "[Invalid Date]" : obj.toISOString();
119
+ if (kind === "RegExp") return truncate(String(obj), limits.maxValueLength);
120
+ if (kind === "ArrayBuffer") return `[ArrayBuffer(${obj.byteLength})]`;
121
+ if (ArrayBuffer.isView(obj)) return `[${kind}(${obj.byteLength})]`;
122
+ if (kind === "Map" || kind === "Set") {
123
+ const collection = obj;
124
+ const items = [];
125
+ for (const value of collection) {
126
+ if (items.length === limits.maxBreadth) break;
127
+ items.push(next(value));
128
+ }
129
+ if (collection.size > items.length) items.push(`[+${collection.size - items.length} more]`);
130
+ return items;
131
+ }
132
+ if (kind === "Promise" || kind === "WeakMap" || kind === "WeakSet" || kind === "Window") return `[${kind}]`;
133
+ if (typeof Node !== "undefined" && obj instanceof Node) return `[${kind}]`;
134
+ if (Array.isArray(obj)) {
135
+ const items = obj.slice(0, limits.maxBreadth).map(next);
136
+ if (obj.length > limits.maxBreadth) items.push(`[+${obj.length - limits.maxBreadth} more]`);
137
+ return items;
138
+ }
139
+ if (typeof Response !== "undefined" && obj instanceof Response) {
140
+ return {
141
+ status: next(obj.status),
142
+ statusText: next(obj.statusText),
143
+ url: next(obj.url),
144
+ redirected: next(obj.redirected)
145
+ };
146
+ }
147
+ if (typeof Event !== "undefined" && obj instanceof Event) {
148
+ const ev = obj;
149
+ const out2 = { type: next(ev.type) };
150
+ for (const key of ["message", "filename", "lineno", "colno"]) {
151
+ if (key in ev) out2[key] = next(ev[key]);
152
+ }
153
+ if ("error" in ev && ev.error != null) out2.error = next(ev.error);
154
+ return out2;
155
+ }
156
+ const out = /* @__PURE__ */ Object.create(null);
157
+ const record = obj;
158
+ const errorLike = isErrorLike(obj);
159
+ if (errorLike) {
160
+ for (const field of ERROR_FIELDS) {
161
+ if (field === "stack" && !limits.stack) continue;
162
+ const v = record[field];
163
+ if (v === void 0 || v === null || typeof v === "function") continue;
164
+ if (field === "stack") {
165
+ if (limits.stack && typeof v === "string")
166
+ out.stack = accountText(truncateStack(v, limits.maxStackFrames, limits.maxValueLength), ctx);
167
+ continue;
168
+ }
169
+ out[field] = next(v);
170
+ }
171
+ const cause = record.cause;
172
+ if (cause !== void 0 && cause !== null && typeof cause !== "function") {
173
+ out.cause = walkCause(cause, depth - 1, ctx);
174
+ }
175
+ }
176
+ let keys = 0;
177
+ const ownKeys = Object.keys(record);
178
+ for (const key of ownKeys) {
179
+ if (key in out || key === "stack" && !limits.stack || errorLike && key === "cause") continue;
180
+ if (++keys > limits.maxBreadth) {
181
+ out["\u2026"] = `[+${ownKeys.length - limits.maxBreadth} keys]`;
182
+ break;
183
+ }
184
+ const v = record[key];
185
+ if (typeof v === "function") continue;
186
+ out[accountText(key, ctx)] = key === "stack" && typeof v === "string" ? accountText(truncateStack(v, limits.maxStackFrames, limits.maxValueLength), ctx) : next(v);
187
+ }
188
+ if (!errorLike && kind !== "Object" && keys === 0) return `[${kind}]`;
189
+ return out;
190
+ }
191
+ function walkCause(cause, depth, ctx) {
192
+ if (typeof cause !== "object" || cause === null) return walk(cause, depth, ctx);
193
+ if (ctx.causeBudget <= 0) return accountText(`[${tag(cause)}]`, ctx);
194
+ ctx.causeBudget--;
195
+ try {
196
+ return walk(cause, depth, ctx);
197
+ } finally {
198
+ ctx.causeBudget++;
199
+ }
200
+ }
201
+ export {
202
+ RAW_ERROR_BOUNDS,
203
+ resolveRawErrorLimits,
204
+ serializeRawError
205
+ };
@@ -0,0 +1,38 @@
1
+ // src/utils/sms-otp-operation-full.ts
2
+ import { OTPInputBinding } from "../low-events/otp-input-binding.mjs";
3
+ import { OperationFull } from "./operation-full.mjs";
4
+ var SmsOtpOperationFull = class extends OperationFull {
5
+ lowEventTracker;
6
+ send;
7
+ postResponse;
8
+ resend;
9
+ constructor(tracker, config = {}) {
10
+ super(tracker, "sms-otp");
11
+ this.send = this.defineStep("send");
12
+ this.postResponse = this.defineStep("post-response");
13
+ this.resend = this.defineStep("resend");
14
+ void this.subflowStart({ explicitSpecType: config.explicitSpecType });
15
+ this.lowEventTracker = this.createLowCollector(
16
+ () => new OTPInputBinding(
17
+ tracker.getLogger(),
18
+ this.lowEventSink("input", "otp"),
19
+ this.lowEventSink("input-group", "otp")
20
+ )
21
+ );
22
+ if (config.inputHtmlFields?.length) {
23
+ this.setInputFields(config.inputHtmlFields);
24
+ } else if (config.inputHtmlField) {
25
+ this.lowEventTracker?.setFields([config.inputHtmlField], false);
26
+ }
27
+ this.registerLowFlusher(() => this.lowEventTracker?.flushPending());
28
+ }
29
+ setInputFields(fields) {
30
+ this.lowEventTracker?.setFields(fields);
31
+ }
32
+ destroy() {
33
+ this.lowEventTracker?.destroy();
34
+ }
35
+ };
36
+ export {
37
+ SmsOtpOperationFull
38
+ };
@@ -0,0 +1,17 @@
1
+ // src/utils/social-operation-login.ts
2
+ import { OperationFull } from "./operation-full.mjs";
3
+ var SocialLoginOperationFull = class extends OperationFull {
4
+ getRedirectUrl;
5
+ exchangeCode;
6
+ constructor(tracker, config = {}) {
7
+ super(tracker, "social-login");
8
+ this.getRedirectUrl = this.defineStep(
9
+ "get-redirect-url"
10
+ );
11
+ this.exchangeCode = this.defineStep("exchange-code");
12
+ void this.subflowStart({ explicitSpecType: config.explicitSpecType, entry: config.entry });
13
+ }
14
+ };
15
+ export {
16
+ SocialLoginOperationFull
17
+ };
@@ -0,0 +1,39 @@
1
+ // src/utils/source-path.ts
2
+ var MASKED_QUERY_VALUE = "*";
3
+ function decodeKey(raw) {
4
+ try {
5
+ return decodeURIComponent(raw.replace(/\+/g, " "));
6
+ } catch {
7
+ return raw;
8
+ }
9
+ }
10
+ function isAllowed(key, allow) {
11
+ const lower = key.toLowerCase();
12
+ return allow.some((pattern) => {
13
+ const entry = pattern.toLowerCase();
14
+ return entry.endsWith("*") ? lower.startsWith(entry.slice(0, -1)) : lower === entry;
15
+ });
16
+ }
17
+ function applySourcePathQuery(search, mode, allow) {
18
+ if (mode !== "allowlist" || search === "" || search === "?") {
19
+ return search;
20
+ }
21
+ const query = search.startsWith("?") ? search.slice(1) : search;
22
+ const masked = query.split("&").map((pair) => {
23
+ const separator = pair.indexOf("=");
24
+ if (separator === -1) {
25
+ return pair;
26
+ }
27
+ const key = pair.slice(0, separator);
28
+ const value = pair.slice(separator + 1);
29
+ if (value === "" || isAllowed(decodeKey(key), allow)) {
30
+ return pair;
31
+ }
32
+ return `${key}=${MASKED_QUERY_VALUE}`;
33
+ });
34
+ return `?${masked.join("&")}`;
35
+ }
36
+ export {
37
+ MASKED_QUERY_VALUE,
38
+ applySourcePathQuery
39
+ };
@@ -0,0 +1,37 @@
1
+ // src/utils/sso-operation-login.ts
2
+ import { OperationFull } from "./operation-full.mjs";
3
+ var SsoLoginOperationFull = class extends OperationFull {
4
+ getRedirectUrl;
5
+ exchangeCode;
6
+ constructor(tracker, config = {}) {
7
+ super(tracker, "sso-login");
8
+ this.getRedirectUrl = this.defineStep(
9
+ "get-redirect-url"
10
+ );
11
+ this.exchangeCode = this.defineStep("exchange-code");
12
+ const { explicitSpecType, organizationDomain, actor } = validateConfig(config ?? {}, tracker);
13
+ void this.subflowStart({ explicitSpecType, organizationDomain, actor });
14
+ }
15
+ };
16
+ function validateConfig(config, tracker) {
17
+ const result = { ...config };
18
+ const warn = (field) => {
19
+ try {
20
+ tracker.getLogger().warn(`SSO login: unsupported ${field}; omitted from telemetry`);
21
+ } catch {
22
+ }
23
+ };
24
+ const domain = result.organizationDomain;
25
+ if (domain !== void 0 && (typeof domain !== "string" || domain.includes("@") || domain.length > 253)) {
26
+ delete result.organizationDomain;
27
+ warn("organizationDomain");
28
+ }
29
+ if (result.actor !== void 0 && result.actor !== "user" && result.actor !== "system") {
30
+ delete result.actor;
31
+ warn("actor");
32
+ }
33
+ return result;
34
+ }
35
+ export {
36
+ SsoLoginOperationFull
37
+ };
@@ -0,0 +1,34 @@
1
+ // src/utils/totp-operation-full.ts
2
+ import { OTPInputBinding } from "../low-events/otp-input-binding.mjs";
3
+ import { OperationFull } from "./operation-full.mjs";
4
+ var TotpOperationFull = class extends OperationFull {
5
+ lowEventTracker;
6
+ postResponse;
7
+ constructor(tracker, config = {}) {
8
+ super(tracker, "totp");
9
+ this.postResponse = this.defineStep("post-response");
10
+ void this.subflowStart({ explicitSpecType: config.explicitSpecType });
11
+ this.lowEventTracker = this.createLowCollector(
12
+ () => new OTPInputBinding(
13
+ tracker.getLogger(),
14
+ this.lowEventSink("input", "otp"),
15
+ this.lowEventSink("input-group", "otp")
16
+ )
17
+ );
18
+ if (config.inputHtmlFields?.length) {
19
+ this.setInputFields(config.inputHtmlFields);
20
+ } else if (config.inputHtmlField) {
21
+ this.lowEventTracker?.setFields([config.inputHtmlField], false);
22
+ }
23
+ this.registerLowFlusher(() => this.lowEventTracker?.flushPending());
24
+ }
25
+ setInputFields(fields) {
26
+ this.lowEventTracker?.setFields(fields);
27
+ }
28
+ destroy() {
29
+ this.lowEventTracker?.destroy();
30
+ }
31
+ };
32
+ export {
33
+ TotpOperationFull
34
+ };
@@ -0,0 +1,100 @@
1
+ // src/utils/trusted-device-operation-full.ts
2
+ import { normalizeError } from "./normalize-error.mjs";
3
+ import { OperationFull } from "./operation-full.mjs";
4
+ function stampIdentity(data, identity, tracker) {
5
+ if (data === null || typeof data !== "object") return data;
6
+ try {
7
+ const out = { ...data };
8
+ if (out.purpose === void 0 && identity.purpose !== void 0) out.purpose = identity.purpose;
9
+ if (out.storage === void 0 && identity.storage !== void 0) out.storage = identity.storage;
10
+ if (out.trustName === void 0 && identity.trustName !== void 0) out.trustName = identity.trustName;
11
+ for (const [field, limit] of [
12
+ ["bindingReference", 255],
13
+ ["trustName", 128]
14
+ ]) {
15
+ if (typeof out[field] === "string" && [...out[field]].length > limit) {
16
+ delete out[field];
17
+ try {
18
+ tracker.getLogger().debug(`Trusted device: omitted ${field} exceeding ${limit} characters`);
19
+ } catch {
20
+ }
21
+ }
22
+ }
23
+ return out;
24
+ } catch {
25
+ return data;
26
+ }
27
+ }
28
+ function withIdentity(step, identity, tracker) {
29
+ return {
30
+ ...step,
31
+ start: (data, options) => step.start(stampIdentity(data, identity, tracker), options),
32
+ finished: (data, options) => step.finished(stampIdentity(data, identity, tracker), options)
33
+ };
34
+ }
35
+ var TrustedDeviceCheckOperationFull = class extends OperationFull {
36
+ getOptions;
37
+ ceremony;
38
+ postResponse;
39
+ constructor(tracker, config) {
40
+ super(tracker, "trusted-device-check");
41
+ config = validateConfig(config, tracker);
42
+ const identity = { purpose: config.purpose, storage: config.storage, trustName: config.trustName };
43
+ this.getOptions = this.checkStep("get-options", identity, tracker);
44
+ this.ceremony = this.checkStep("ceremony", identity, tracker);
45
+ this.postResponse = this.checkStep("post-response", identity, tracker);
46
+ this.subflowStart({ explicitSpecType: config.explicitSpecType, actor: "system", ignoreAsInteraction: true });
47
+ }
48
+ checkStep(name, identity, tracker) {
49
+ const step = this.defineStep(name, true);
50
+ return {
51
+ ...withIdentity(step, identity, tracker),
52
+ errorTyped: (data, options) => {
53
+ step.errorTyped(
54
+ stampIdentity({ code: data?.code, error: normalizeError(data?.error) }, identity, tracker),
55
+ options
56
+ );
57
+ }
58
+ };
59
+ }
60
+ };
61
+ var TrustedDeviceEnrollmentOperationFull = class extends OperationFull {
62
+ getOptions;
63
+ ceremony;
64
+ postResponse;
65
+ constructor(tracker, config) {
66
+ super(tracker, "trusted-device-enrollment");
67
+ config = validateConfig(config, tracker);
68
+ const identity = { purpose: config.purpose, storage: config.storage, trustName: config.trustName };
69
+ this.getOptions = withIdentity(this.defineStep("get-options"), identity, tracker);
70
+ this.ceremony = withIdentity(this.defineStep("ceremony"), identity, tracker);
71
+ this.postResponse = withIdentity(
72
+ this.defineStep("post-response"),
73
+ identity,
74
+ tracker
75
+ );
76
+ this.subflowStart({ explicitSpecType: config.explicitSpecType });
77
+ }
78
+ };
79
+ function validateConfig(config, tracker) {
80
+ const result = { ...config };
81
+ const warn = (field) => {
82
+ try {
83
+ tracker.getLogger().warn(`Trusted device: missing or unsupported ${field}; omitted from telemetry`);
84
+ } catch {
85
+ }
86
+ };
87
+ if (result.purpose !== "additional-verification" && result.purpose !== "mfa-exemption") {
88
+ delete result.purpose;
89
+ warn("purpose");
90
+ }
91
+ if (!["cookie", "indexeddb", "localstore", "sessionstore"].includes(result.storage)) {
92
+ delete result.storage;
93
+ warn("storage");
94
+ }
95
+ return result;
96
+ }
97
+ export {
98
+ TrustedDeviceCheckOperationFull,
99
+ TrustedDeviceEnrollmentOperationFull
100
+ };
@@ -0,0 +1,101 @@
1
+ // src/utils/webauthn-payloads.ts
2
+ var BLACKLIST = {
3
+ creationOptions: ["/user/name", "/user/displayName"],
4
+ creationResponse: ["/clientExtensionResults/prf/results"],
5
+ requestOptions: [],
6
+ assertionResponse: ["/response/signature", "/clientExtensionResults/prf/results"]
7
+ };
8
+ function sanitizeCreationOptions(value, projectors) {
9
+ return sanitize(value, "creationOptions", projectors);
10
+ }
11
+ function sanitizeCreationResponse(value, projectors) {
12
+ return sanitize(value, "creationResponse", projectors);
13
+ }
14
+ function sanitizeRequestOptions(value) {
15
+ return sanitize(value, "requestOptions");
16
+ }
17
+ function sanitizeAssertionResponse(value, projectors) {
18
+ return sanitize(value, "assertionResponse", projectors);
19
+ }
20
+ function sanitize(input, kind, projectors = {}) {
21
+ try {
22
+ const value = typeof input === "string" ? JSON.parse(input) : input;
23
+ if (!isRecord(value)) return void 0;
24
+ const blocked = new Set(BLACKLIST[kind]);
25
+ const ancestors = /* @__PURE__ */ new Set();
26
+ const clone = (item, path, depth = 0) => {
27
+ if (blocked.has(path)) return void 0;
28
+ if (kind === "creationOptions" && path === "/user/id" || (kind === "creationResponse" || kind === "assertionResponse") && path === "/response/userHandle") {
29
+ if (item === null) return null;
30
+ if (!projectors.userId) return void 0;
31
+ const identifier = typeof item === "string" ? item : item instanceof ArrayBuffer || ArrayBuffer.isView(item) ? base64url(item) : void 0;
32
+ if (identifier === void 0) return void 0;
33
+ try {
34
+ const output = projectors.userId(identifier);
35
+ return typeof output === "string" || output === null ? output : void 0;
36
+ } catch {
37
+ return void 0;
38
+ }
39
+ }
40
+ if (item === null || typeof item === "string" || typeof item === "boolean") return item;
41
+ if (typeof item === "number") return Number.isFinite(item) ? item : null;
42
+ if (typeof item !== "object") return void 0;
43
+ if (item instanceof ArrayBuffer || ArrayBuffer.isView(item)) return base64url(item);
44
+ if (depth > 64 || ancestors.has(item)) throw new TypeError("Invalid telemetry payload");
45
+ ancestors.add(item);
46
+ try {
47
+ if (Array.isArray(item)) {
48
+ return item.map((entry, i) => clone(entry, `${path}/${i}`, depth + 1));
49
+ }
50
+ const result = /* @__PURE__ */ Object.create(null);
51
+ for (const key in item) {
52
+ const childPath = `${path}/${key.replace(/~/g, "~0").replace(/\//g, "~1")}`;
53
+ if (blocked.has(childPath) || key === "toJSON") continue;
54
+ const child = clone(item[key], childPath, depth + 1);
55
+ if (child !== void 0) result[key] = child;
56
+ }
57
+ const source = item;
58
+ const readMethod = (method, field) => {
59
+ if (result[field] !== void 0 || typeof source[method] !== "function") return;
60
+ const childPath = `${path}/${field}`;
61
+ const child = clone(
62
+ Reflect.apply(source[method], item, []),
63
+ childPath,
64
+ depth + 1
65
+ );
66
+ if (child !== void 0) result[field] = child;
67
+ };
68
+ if (path === "" && (kind === "creationResponse" || kind === "assertionResponse")) {
69
+ readMethod("getClientExtensionResults", "clientExtensionResults");
70
+ }
71
+ if (path === "/response" && kind === "creationResponse") {
72
+ readMethod("getTransports", "transports");
73
+ readMethod("getAuthenticatorData", "authenticatorData");
74
+ readMethod("getPublicKey", "publicKey");
75
+ readMethod("getPublicKeyAlgorithm", "publicKeyAlgorithm");
76
+ }
77
+ return result;
78
+ } finally {
79
+ ancestors.delete(item);
80
+ }
81
+ };
82
+ return JSON.stringify(clone(value, ""));
83
+ } catch {
84
+ return void 0;
85
+ }
86
+ }
87
+ function isRecord(value) {
88
+ return value !== null && typeof value === "object" && !Array.isArray(value) && !(value instanceof ArrayBuffer) && !ArrayBuffer.isView(value);
89
+ }
90
+ function base64url(value) {
91
+ const bytes = value instanceof ArrayBuffer ? new Uint8Array(value) : new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
92
+ let binary = "";
93
+ for (const byte of bytes) binary += String.fromCharCode(byte);
94
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
95
+ }
96
+ export {
97
+ sanitizeAssertionResponse,
98
+ sanitizeCreationOptions,
99
+ sanitizeCreationResponse,
100
+ sanitizeRequestOptions
101
+ };
@@ -0,0 +1,7 @@
1
+ // src/version.ts
2
+ var SDK_NAME = "@corbado/observe";
3
+ var SDK_VERSION = true ? "1.9.0" : "0.0.0-dev";
4
+ export {
5
+ SDK_NAME,
6
+ SDK_VERSION
7
+ };