@corbado/observe 1.7.1 → 1.8.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 (65) hide show
  1. package/dist/analysis/client.mjs +1851 -0
  2. package/dist/analysis/config-manager.mjs +83 -0
  3. package/dist/analysis/config.mjs +216 -0
  4. package/dist/analysis/device-info-manager.mjs +153 -0
  5. package/dist/analysis/device.mjs +562 -0
  6. package/dist/analysis/index.mjs +118 -0
  7. package/dist/analysis/logger.mjs +50 -0
  8. package/dist/analysis/low-events/capture-sequence.mjs +11 -0
  9. package/dist/analysis/low-events/ceremony-initiator.mjs +26 -0
  10. package/dist/analysis/low-events/form-input-binding.mjs +46 -0
  11. package/dist/analysis/low-events/grouped-input-tracker.mjs +133 -0
  12. package/dist/analysis/low-events/input-change-recorder.mjs +200 -0
  13. package/dist/analysis/low-events/input-paste-observer.mjs +91 -0
  14. package/dist/analysis/low-events/input-tracker.mjs +223 -0
  15. package/dist/analysis/low-events/otp-input-binding.mjs +67 -0
  16. package/dist/analysis/low-events/shared-window-observer.mjs +225 -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 +887 -0
  21. package/dist/analysis/readers/device.mjs +73 -0
  22. package/dist/analysis/readers/meta.mjs +18 -0
  23. package/dist/analysis/readers/password-manager-dom.mjs +112 -0
  24. package/dist/analysis/readers/password-manager-extensions.mjs +247 -0
  25. package/dist/analysis/readers/private-mode.mjs +9 -0
  26. package/dist/analysis/storage/index.mjs +197 -0
  27. package/dist/analysis/storage/migration.mjs +56 -0
  28. package/dist/analysis/store/event-store.mjs +114 -0
  29. package/dist/analysis/transport.mjs +0 -0
  30. package/dist/analysis/transports/fetch.mjs +108 -0
  31. package/dist/analysis/types.mjs +25 -0
  32. package/dist/analysis/utils/appconfirmation-operation-full.mjs +19 -0
  33. package/dist/analysis/utils/blocked-user-agents.mjs +37 -0
  34. package/dist/analysis/utils/captcha-operation-full.mjs +17 -0
  35. package/dist/analysis/utils/email-link-continuity.mjs +50 -0
  36. package/dist/analysis/utils/email-link-operation-full.mjs +142 -0
  37. package/dist/analysis/utils/email-otp-operation-full.mjs +39 -0
  38. package/dist/analysis/utils/form-operation-full.mjs +81 -0
  39. package/dist/analysis/utils/key-operation-full.mjs +38 -0
  40. package/dist/analysis/utils/normalize-error.mjs +29 -0
  41. package/dist/analysis/utils/operation-full-provide-identifier-with-cui.mjs +46 -0
  42. package/dist/analysis/utils/operation-full.mjs +235 -0
  43. package/dist/analysis/utils/passkey-deletion-operation-full.mjs +17 -0
  44. package/dist/analysis/utils/passkey-enrollment-operation-full.mjs +32 -0
  45. package/dist/analysis/utils/passkey-login-operation-full.mjs +34 -0
  46. package/dist/analysis/utils/password-enrollment-operation-full.mjs +27 -0
  47. package/dist/analysis/utils/password-login-operation-full.mjs +61 -0
  48. package/dist/analysis/utils/password-manager-sampler.mjs +130 -0
  49. package/dist/analysis/utils/provide-data-operation-full.mjs +28 -0
  50. package/dist/analysis/utils/serialize-raw-error.mjs +205 -0
  51. package/dist/analysis/utils/sms-otp-operation-full.mjs +39 -0
  52. package/dist/analysis/utils/social-operation-login.mjs +17 -0
  53. package/dist/analysis/utils/source-path.mjs +39 -0
  54. package/dist/analysis/utils/sso-operation-login.mjs +37 -0
  55. package/dist/analysis/utils/totp-operation-full.mjs +35 -0
  56. package/dist/analysis/utils/trusted-device-operation-full.mjs +100 -0
  57. package/dist/analysis/utils/web-lock.mjs +52 -0
  58. package/dist/analysis/utils/webauthn-payloads.mjs +101 -0
  59. package/dist/analysis/version.mjs +7 -0
  60. package/dist/cdn/script.global.js +4 -4
  61. package/dist/index.d.mts +164 -11
  62. package/dist/index.d.ts +164 -11
  63. package/dist/index.js +4 -4
  64. package/dist/index.mjs +4 -4
  65. package/package.json +7 -2
@@ -0,0 +1,73 @@
1
+ // src/readers/device.ts
2
+ function readUserAgent(_meta) {
3
+ if (typeof navigator === "undefined") {
4
+ return "";
5
+ }
6
+ return navigator.userAgent ?? "";
7
+ }
8
+ async function readBluetoothAvailability(_meta) {
9
+ if (typeof navigator === "undefined" || !navigator.bluetooth) {
10
+ return void 0;
11
+ }
12
+ return await navigator.bluetooth.getAvailability();
13
+ }
14
+ async function readUserVerifyingPlatformAuthenticatorAvailable(_meta) {
15
+ if (typeof PublicKeyCredential === "undefined" || !PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable) {
16
+ return void 0;
17
+ }
18
+ return await PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable();
19
+ }
20
+ async function readConditionalMediationAvailable(_meta) {
21
+ if (typeof PublicKeyCredential === "undefined" || !PublicKeyCredential.isConditionalMediationAvailable) {
22
+ return void 0;
23
+ }
24
+ return await PublicKeyCredential.isConditionalMediationAvailable();
25
+ }
26
+ async function readClientCapabilities(_meta) {
27
+ if (typeof PublicKeyCredential === "undefined" || !PublicKeyCredential.getClientCapabilities) {
28
+ return void 0;
29
+ }
30
+ const rawClientCapabilities = await PublicKeyCredential.getClientCapabilities();
31
+ return Object.fromEntries(Object.entries(rawClientCapabilities).map(([key, value]) => [key, value.toString()]));
32
+ }
33
+ async function readJavaScriptHighEntropy(_meta) {
34
+ if (typeof navigator === "undefined" || !navigator.userAgentData) {
35
+ return void 0;
36
+ }
37
+ const ua = await navigator.userAgentData.getHighEntropyValues(["platformVersion", "model"]);
38
+ const platform = ua.platform;
39
+ const mobile = ua.mobile;
40
+ const platformVersion = ua.platformVersion;
41
+ const model = ua.model || void 0;
42
+ if (!platform || mobile === void 0 || !platformVersion) {
43
+ return;
44
+ }
45
+ return {
46
+ platform,
47
+ mobile,
48
+ platformVersion,
49
+ model
50
+ };
51
+ }
52
+ function readWebdriver(_meta) {
53
+ if (typeof navigator === "undefined") {
54
+ return void 0;
55
+ }
56
+ return navigator.webdriver;
57
+ }
58
+ function readMaxTouchPoints(_meta) {
59
+ if (typeof navigator === "undefined" || typeof navigator.maxTouchPoints !== "number") {
60
+ return void 0;
61
+ }
62
+ return navigator.maxTouchPoints;
63
+ }
64
+ export {
65
+ readBluetoothAvailability,
66
+ readClientCapabilities,
67
+ readConditionalMediationAvailable,
68
+ readJavaScriptHighEntropy,
69
+ readMaxTouchPoints,
70
+ readUserAgent,
71
+ readUserVerifyingPlatformAuthenticatorAvailable,
72
+ readWebdriver
73
+ };
@@ -0,0 +1,18 @@
1
+ // src/readers/meta.ts
2
+ var READER_EXPOSURES = [
3
+ "none",
4
+ "presence",
5
+ "size",
6
+ "class",
7
+ "format",
8
+ "partial",
9
+ "enum",
10
+ "computed",
11
+ "token",
12
+ "pseudonym",
13
+ "redacted",
14
+ "raw"
15
+ ];
16
+ export {
17
+ READER_EXPOSURES
18
+ };
@@ -0,0 +1,112 @@
1
+ // src/readers/password-manager-dom.ts
2
+ var SIGNAL_TAG_QUERIES = [
3
+ { query: "com-1password-button", eventType: "pa-1" },
4
+ { query: "com-1password-menu", eventType: "pa-2" },
5
+ { query: "com-1password-notification", eventType: "pa-3" }
6
+ ];
7
+ var SIGNAL_ATTR_SELECTOR = "[data-lastpass-root], [data-lp], [data-lpignore], [data-lpfieldtype]";
8
+ var SIGNAL_ATTR_EVENT_TYPE = "pa-4";
9
+ var POPOVER_MANUAL_SELECTOR = '[popover="manual"]';
10
+ var PROFILE_A_MIN_STYLE_LENGTH = 400;
11
+ var PROFILE_A_MIN_INITIAL_IMPORTANT = 20;
12
+ var PROFILE_A_EVENT_TYPE = "pa-5";
13
+ var PROFILE_B_EVENT_TYPE = "pa-6";
14
+ var safeQueryAll = (root, selector) => {
15
+ try {
16
+ return Array.from(root.querySelectorAll(selector));
17
+ } catch {
18
+ return [];
19
+ }
20
+ };
21
+ var isPopoverProfileA = (el) => {
22
+ if (el.getAttribute("popover") !== "manual") {
23
+ return false;
24
+ }
25
+ const style = el.getAttribute("style") ?? "";
26
+ if (style.length < PROFILE_A_MIN_STYLE_LENGTH) {
27
+ return false;
28
+ }
29
+ if (!/z-index:\s*2147483647\b/i.test(style)) {
30
+ return false;
31
+ }
32
+ if (!/position:\s*fixed\b/i.test(style)) {
33
+ return false;
34
+ }
35
+ if (!/display:\s*block\b/i.test(style)) {
36
+ return false;
37
+ }
38
+ const initialImportantHits = style.match(/initial\s*!important/gi);
39
+ if ((initialImportantHits?.length ?? 0) < PROFILE_A_MIN_INITIAL_IMPORTANT) {
40
+ return false;
41
+ }
42
+ return true;
43
+ };
44
+ var isPopoverProfileB = (el) => {
45
+ if (isPopoverProfileA(el)) {
46
+ return false;
47
+ }
48
+ if (el.getAttribute("popover") !== "manual") {
49
+ return false;
50
+ }
51
+ const style = el.getAttribute("style") ?? "";
52
+ if (!/z-index:\s*2147483647\b/i.test(style)) {
53
+ return false;
54
+ }
55
+ if (!/position:\s*fixed\b/i.test(style)) {
56
+ return false;
57
+ }
58
+ if (!/backdrop-filter:\s*[^;]*blur\(\s*20px\s*\)/i.test(style)) {
59
+ return false;
60
+ }
61
+ if (!/background-color:\s*rgba\(\s*35\s*,\s*35\s*,\s*35\s*,/i.test(style)) {
62
+ return false;
63
+ }
64
+ return true;
65
+ };
66
+ var forEachScanRoot = (cb) => {
67
+ cb(document);
68
+ for (const host of Array.from(document.querySelectorAll("*"))) {
69
+ const shadowRoot = host.shadowRoot;
70
+ if (shadowRoot) {
71
+ cb(shadowRoot);
72
+ }
73
+ }
74
+ };
75
+ var scanInputEnvSignals = () => {
76
+ const matched = /* @__PURE__ */ new Set();
77
+ if (typeof document === "undefined") {
78
+ return matched;
79
+ }
80
+ forEachScanRoot((root) => {
81
+ for (const entry of SIGNAL_TAG_QUERIES) {
82
+ if (matched.has(entry.eventType)) {
83
+ continue;
84
+ }
85
+ if (safeQueryAll(root, entry.query).length > 0) {
86
+ matched.add(entry.eventType);
87
+ }
88
+ }
89
+ if (!matched.has(SIGNAL_ATTR_EVENT_TYPE) && safeQueryAll(root, SIGNAL_ATTR_SELECTOR).length > 0) {
90
+ matched.add(SIGNAL_ATTR_EVENT_TYPE);
91
+ }
92
+ if (!matched.has(PROFILE_A_EVENT_TYPE) || !matched.has(PROFILE_B_EVENT_TYPE)) {
93
+ for (const el of safeQueryAll(root, POPOVER_MANUAL_SELECTOR)) {
94
+ if (!matched.has(PROFILE_A_EVENT_TYPE) && isPopoverProfileA(el)) {
95
+ matched.add(PROFILE_A_EVENT_TYPE);
96
+ continue;
97
+ }
98
+ if (!matched.has(PROFILE_B_EVENT_TYPE) && isPopoverProfileB(el)) {
99
+ matched.add(PROFILE_B_EVENT_TYPE);
100
+ }
101
+ }
102
+ }
103
+ });
104
+ return matched;
105
+ };
106
+ function readPasswordManagerDomSignals(_meta) {
107
+ return scanInputEnvSignals();
108
+ }
109
+ export {
110
+ readPasswordManagerDomSignals,
111
+ scanInputEnvSignals
112
+ };
@@ -0,0 +1,247 @@
1
+ // src/readers/password-manager-extensions.ts
2
+ var ONEPASSWORD_SCRIPT = "/inline/injected/webauthn-listeners.js";
3
+ var DASHLANE_STACK = /\/dashlane-(?:shadow-dom|top-layer)-page-script\.js/;
4
+ var KASPERSKY_SCRIPT = /-extension:\/\/[^/]+\/content-scripts\/passkey_proxy\/index\.js$/;
5
+ var KEEPER_STACK = /-extension:\/\/[^/]+\/content_scripts\/shadow-dom\/shadowAttachNotifier\.js:/;
6
+ var NORDPASS_STACK = /-extension:\/\/[^/]+\/js\/nordpass-script\.js:/;
7
+ var NORDPASS_PASSKEYS_KEY = /-extension:\/\/[^/]+\/js\/passkeys\.js:/;
8
+ var KEEPER_SOURCE = /["'`](?:keeper:shadow-root-attached|webauthn-(?:get|create)-request)["'`]/;
9
+ var PROTONPASS_SOURCE = /Intercept cancelled for `(?:get|create)`|["'`](?:conditionalMediationAvailable|verifyingPlatformAuthenticatorAvailable)["'`]/;
10
+ var sentryDebugIdKeys = () => Object.keys(window._sentryDebugIds ?? {});
11
+ var stackOf = (fn) => {
12
+ try {
13
+ fn();
14
+ } catch (error) {
15
+ return String(error?.stack ?? "");
16
+ }
17
+ return "";
18
+ };
19
+ var attachShadowStack = () => stackOf(() => Element.prototype.attachShadow.call(null, { mode: "open" }));
20
+ var has1PasswordAccessor = () => {
21
+ const hook = Object.getOwnPropertyDescriptor(navigator.credentials ?? {}, "get");
22
+ if (!hook?.get || !hook.set || hook.configurable) {
23
+ return false;
24
+ }
25
+ const marker = () => void 0;
26
+ const read = hook.get.call({ _get: marker });
27
+ return read === marker || typeof read === "function" && read === hook.get.call({}) && !Function.prototype.toString.call(read).includes("[native code]");
28
+ };
29
+ var credentialSlots = (...keys) => {
30
+ const credentials = navigator.credentials;
31
+ return credentials ? keys.map((key) => Object.getOwnPropertyDescriptor(credentials, key)?.value) : [];
32
+ };
33
+ var hasBitwardenOverride = () => [navigator.credentials?.get, ...credentialSlots("get", "_get")].some(
34
+ (method) => typeof method === "function" && method.name === "getWebAuthnCredential"
35
+ );
36
+ var probeBitwardenKeyframes = () => {
37
+ const root = document.documentElement;
38
+ const sheets = document.adoptedStyleSheets;
39
+ if (!root || typeof root.getAnimations !== "function" || typeof sheets?.push !== "function") {
40
+ return false;
41
+ }
42
+ if (root.getAnimations().length > 0) return false;
43
+ const sheet = new CSSStyleSheet();
44
+ sheet.replaceSync(":where(:root){animation:bitwardenfill 1s 1000s paused}");
45
+ sheets.push(sheet);
46
+ try {
47
+ const found = root.getAnimations().filter((animation) => animation.animationName === "bitwardenfill");
48
+ found.forEach((animation) => animation.cancel());
49
+ return found.length > 0;
50
+ } finally {
51
+ const index = sheets.indexOf(sheet);
52
+ if (index >= 0) sheets.splice(index, 1);
53
+ }
54
+ };
55
+ var bitwardenKeyframes;
56
+ var hasBitwardenKeyframes = () => bitwardenKeyframes ??= probeBitwardenKeyframes();
57
+ var hasDashlaneInstanceTrap = () => {
58
+ const credential = typeof PublicKeyCredential === "undefined" ? void 0 : PublicKeyCredential;
59
+ if (typeof credential !== "function" || !Object.prototype.hasOwnProperty.call(credential, Symbol.hasInstance)) {
60
+ return false;
61
+ }
62
+ const result = credential[Symbol.hasInstance]({});
63
+ if (!(result instanceof Promise)) return false;
64
+ result.catch(() => void 0);
65
+ return true;
66
+ };
67
+ var nordPassObserverStack = () => {
68
+ const target = {
69
+ get nodeType() {
70
+ throw new Error("probe");
71
+ }
72
+ };
73
+ return stackOf(() => MutationObserver.prototype.observe.call(null, target));
74
+ };
75
+ var hasNordPassObserverProxy = () => {
76
+ const sentinel = new Error("probe");
77
+ const read = [];
78
+ const target = {
79
+ get nodeType() {
80
+ read.push("nodeType");
81
+ return 1;
82
+ },
83
+ get ownerDocument() {
84
+ read.push("ownerDocument");
85
+ throw sentinel;
86
+ }
87
+ };
88
+ try {
89
+ MutationObserver.prototype.observe.call(null, target);
90
+ } catch (error) {
91
+ return error === sentinel && read.join() === "nodeType,ownerDocument";
92
+ }
93
+ return false;
94
+ };
95
+ var hasKeeperOverride = () => {
96
+ const methods = [
97
+ Element.prototype.attachShadow,
98
+ navigator.credentials?.get,
99
+ navigator.credentials?.create,
100
+ ...credentialSlots("get", "_get", "create")
101
+ ];
102
+ return methods.some((m) => typeof m === "function" && KEEPER_SOURCE.test(Function.prototype.toString.call(m)));
103
+ };
104
+ var hasLastPassRoot = () => {
105
+ let root = document;
106
+ while (root) {
107
+ if (root.querySelector("[data-lastpass-icon-root], [data-lastpass-root]")) {
108
+ return true;
109
+ }
110
+ root = root.activeElement?.shadowRoot;
111
+ }
112
+ return false;
113
+ };
114
+ var hasProtonPassOverride = () => {
115
+ const credential = typeof PublicKeyCredential === "undefined" ? void 0 : PublicKeyCredential;
116
+ const methods = [
117
+ navigator.credentials?.get,
118
+ ...credentialSlots("get", "_get"),
119
+ credential?.isConditionalMediationAvailable,
120
+ credential?._isConditionalMediationAvailable,
121
+ credential?.isUserVerifyingPlatformAuthenticatorAvailable,
122
+ credential?._isUserVerifyingPlatformAuthenticatorAvailable
123
+ ];
124
+ return methods.some((m) => typeof m === "function" && PROTONPASS_SOURCE.test(Function.prototype.toString.call(m)));
125
+ };
126
+ var PROBES = [
127
+ {
128
+ name: "1password",
129
+ signal: "in-field button",
130
+ signedIn: true,
131
+ // Renders only for a signed-in account (locked or not), while a login field is focused.
132
+ test: () => document.querySelector("com-1password-button") !== null
133
+ },
134
+ { name: "1password", signal: "credentials.get accessor", test: has1PasswordAccessor },
135
+ {
136
+ name: "1password",
137
+ signal: "Sentry debug id",
138
+ // Sentry debug-id keys are stack strings naming the injected script.
139
+ test: () => sentryDebugIdKeys().some((key) => key.includes(ONEPASSWORD_SCRIPT))
140
+ },
141
+ { name: "bitwarden", signal: "passkey override", signedIn: true, test: hasBitwardenOverride },
142
+ { name: "bitwarden", signal: "autofill keyframes", test: hasBitwardenKeyframes },
143
+ {
144
+ name: "dashlane",
145
+ signal: "injected script tag",
146
+ // Edge injects its page scripts as tags (https only); they are in the DOM before they execute.
147
+ test: () => document.querySelector('script[src*="-extension://"][src*="/dashlane-"][src$="-page-script.js"]') !== null
148
+ },
149
+ { name: "dashlane", signal: "PublicKeyCredential hasInstance trap", test: hasDashlaneInstanceTrap },
150
+ {
151
+ name: "dashlane",
152
+ signal: "page script stack",
153
+ // The shadow-dom and top-layer page scripts (http too) wrap these methods; a null receiver throws inside them.
154
+ test: () => DASHLANE_STACK.test(attachShadowStack()) || typeof HTMLDialogElement !== "undefined" && DASHLANE_STACK.test(
155
+ stackOf(() => HTMLDialogElement.prototype.showModal.call(null))
156
+ )
157
+ },
158
+ {
159
+ name: "kaspersky",
160
+ signal: "passkey proxy flag",
161
+ // Set in every https frame at document_start, with or without its desktop app.
162
+ test: () => window.wasKPMKeyPassProxyInited === true
163
+ },
164
+ {
165
+ name: "kaspersky",
166
+ signal: "injected script tag",
167
+ // Firefox injects the proxy as a script tag, which is in the DOM before it runs.
168
+ test: () => KASPERSKY_SCRIPT.test(
169
+ document.querySelector('script[src*="/content-scripts/passkey_proxy/"]')?.src ?? ""
170
+ )
171
+ },
172
+ {
173
+ name: "keeper",
174
+ signal: "attachShadow stack",
175
+ // Wrapped at document_start; a null receiver throws inside the wrapper before it notifies.
176
+ test: () => KEEPER_STACK.test(attachShadowStack())
177
+ },
178
+ { name: "keeper", signal: "override source", test: hasKeeperOverride },
179
+ {
180
+ name: "keeper",
181
+ signal: "field lock",
182
+ // Tagged on every login field it decorates, signed in or not, shortly after load.
183
+ test: () => document.querySelector("[data-keeper-lock-id]") !== null
184
+ },
185
+ {
186
+ name: "lastpass",
187
+ signal: "passkey override",
188
+ signedIn: true,
189
+ // Registered at document_start (https) while the vault is unlocked and its passkey support is enabled.
190
+ test: () => typeof window.__nativeCredentialsGet === "function"
191
+ },
192
+ // Otherwise nothing runs in the page until it decorates a login field, shortly after load.
193
+ { name: "lastpass", signal: "icon root", test: hasLastPassRoot },
194
+ { name: "nordpass", signal: "observer proxy stack", test: () => NORDPASS_STACK.test(nordPassObserverStack()) },
195
+ { name: "nordpass", signal: "observer read order", test: hasNordPassObserverProxy },
196
+ {
197
+ name: "nordpass",
198
+ signal: "passkeys Sentry debug id",
199
+ test: () => sentryDebugIdKeys().some((key) => NORDPASS_PASSKEYS_KEY.test(key))
200
+ },
201
+ {
202
+ name: "nordpass",
203
+ signal: "Sentry debug id",
204
+ test: () => sentryDebugIdKeys().some((key) => NORDPASS_STACK.test(key))
205
+ },
206
+ { name: "protonpass", signal: "WebAuthn override source", test: hasProtonPassOverride },
207
+ {
208
+ name: "protonpass",
209
+ signal: "field icon",
210
+ // Plain http, frames and later wrappers: its root and field-icon elements once its client script runs.
211
+ test: () => document.querySelector("[data-protonpass-role]") !== null
212
+ }
213
+ ];
214
+ function probePasswordManagerExtensions() {
215
+ if (typeof window === "undefined" || typeof document === "undefined") {
216
+ return [];
217
+ }
218
+ return PROBES.map(({ name, signal, signedIn, test }) => {
219
+ try {
220
+ return { name, signal, signedIn, matched: test() };
221
+ } catch (error) {
222
+ return { name, signal, signedIn, matched: false, error };
223
+ }
224
+ });
225
+ }
226
+ function detectPasswordManagerExtensions() {
227
+ const detected = /* @__PURE__ */ new Map();
228
+ for (const { name, signedIn, matched } of probePasswordManagerExtensions()) {
229
+ if (!matched) {
230
+ continue;
231
+ }
232
+ const extension = detected.get(name) ?? { name };
233
+ if (signedIn) {
234
+ extension.signedIn = true;
235
+ }
236
+ detected.set(name, extension);
237
+ }
238
+ return [...detected.values()];
239
+ }
240
+ function readPasswordManagerExtensions(_meta) {
241
+ return detectPasswordManagerExtensions();
242
+ }
243
+ export {
244
+ detectPasswordManagerExtensions,
245
+ probePasswordManagerExtensions,
246
+ readPasswordManagerExtensions
247
+ };
@@ -0,0 +1,9 @@
1
+ // src/readers/private-mode.ts
2
+ import detectIncognito from "detectincognitojs";
3
+ async function readPrivateMode(_meta) {
4
+ const res = await detectIncognito();
5
+ return res.isPrivate;
6
+ }
7
+ export {
8
+ readPrivateMode
9
+ };
@@ -0,0 +1,197 @@
1
+ // src/storage/index.ts
2
+ function namespacedKey(key, namespace) {
3
+ return `${key}_${namespace}`;
4
+ }
5
+ var NamespacedStorage = class {
6
+ constructor(engine, namespace) {
7
+ this.engine = engine;
8
+ this.namespace = namespace;
9
+ }
10
+ getItem(key) {
11
+ return this.engine.getItem(namespacedKey(key, this.namespace));
12
+ }
13
+ setItem(key, value) {
14
+ this.engine.setItem(namespacedKey(key, this.namespace), value);
15
+ }
16
+ removeItem(key) {
17
+ this.engine.removeItem(namespacedKey(key, this.namespace));
18
+ }
19
+ };
20
+ var LocalStorage = class {
21
+ constructor(logger) {
22
+ this.logger = logger;
23
+ }
24
+ /**
25
+ * Error situations:
26
+ * - localStorage is not available: return null
27
+ * - localStorage.getItem throws: catch exception, log error and return null
28
+ * - JSON.parse throws: catch exception, log error and return null
29
+ */
30
+ getItem(key) {
31
+ try {
32
+ if (typeof localStorage === "undefined") return null;
33
+ const item = localStorage.getItem(key);
34
+ if (item) {
35
+ return JSON.parse(item);
36
+ }
37
+ return null;
38
+ } catch (error) {
39
+ this.logger.error(`LocalStorage.getItem failed for key "${key}"`, error);
40
+ return null;
41
+ }
42
+ }
43
+ /**
44
+ * Error situations:
45
+ * - localStorage is not available: return without writing
46
+ * - localStorage.setItem throws: catch exception and log error
47
+ * - JSON.stringify throws: catch exception and log error
48
+ */
49
+ setItem(key, value) {
50
+ try {
51
+ if (typeof localStorage === "undefined") return;
52
+ localStorage.setItem(key, JSON.stringify(value));
53
+ } catch (error) {
54
+ this.logger.error(`LocalStorage.setItem failed for key "${key}"`, error);
55
+ this.logger.reportTelemetry?.("error", `localStorage.setItem failed: ${key}`);
56
+ }
57
+ }
58
+ /**
59
+ * Error situations:
60
+ * - localStorage is not available: return without writing
61
+ * - localStorage.removeItem throws: catch exception and log error
62
+ */
63
+ removeItem(key) {
64
+ try {
65
+ if (typeof localStorage === "undefined") return;
66
+ localStorage.removeItem(key);
67
+ } catch (error) {
68
+ this.logger.error(`LocalStorage.removeItem failed for key "${key}"`, error);
69
+ }
70
+ }
71
+ };
72
+ var CookieStorage = class {
73
+ constructor(logger, domain) {
74
+ this.logger = logger;
75
+ this.domain = domain;
76
+ }
77
+ /**
78
+ * Error situations:
79
+ * - document is not available: return null
80
+ * - document.cookie read throws: catch exception, log error and return null
81
+ * - decodeURIComponent throws: catch exception, log error and return null
82
+ * - JSON.parse throws: catch exception, log error and return null
83
+ */
84
+ getItem(key) {
85
+ try {
86
+ if (typeof document === "undefined") return null;
87
+ const name = key + "=";
88
+ const parts = document.cookie.split(";");
89
+ for (let i = 0; i < parts.length; i++) {
90
+ const part = parts[i].trim();
91
+ if (part.indexOf(name) === 0) {
92
+ const rawValue = part.substring(name.length);
93
+ return JSON.parse(decodeURIComponent(rawValue));
94
+ }
95
+ }
96
+ return null;
97
+ } catch (error) {
98
+ this.logger.error(`CookieStorage.getItem failed for key "${key}"`, error);
99
+ return null;
100
+ }
101
+ }
102
+ /**
103
+ * Error situations:
104
+ * - document is not available: return without writing
105
+ * - document.cookie write throws: catch exception and log error
106
+ * - JSON.stringify throws: catch exception and log error
107
+ */
108
+ setItem(key, value) {
109
+ try {
110
+ if (typeof document === "undefined") return;
111
+ let cookieString = `${key}=${encodeURIComponent(JSON.stringify(value))}; path=/; SameSite=Lax`;
112
+ if (this.domain) {
113
+ cookieString += `; domain=${this.domain}`;
114
+ }
115
+ document.cookie = cookieString;
116
+ } catch (error) {
117
+ this.logger.error(`CookieStorage.setItem failed for key "${key}"`, error);
118
+ this.logger.reportTelemetry?.("error", `cookie.setItem failed: ${key}`);
119
+ }
120
+ }
121
+ /**
122
+ * Error situations:
123
+ * - document is not available: return without writing
124
+ * - document.cookie write throws: catch exception and log error
125
+ */
126
+ removeItem(key) {
127
+ try {
128
+ if (typeof document === "undefined") return;
129
+ let cookieString = `${key}=; Max-Age=-99999999; path=/`;
130
+ if (this.domain) {
131
+ cookieString += `; domain=${this.domain}`;
132
+ }
133
+ document.cookie = cookieString;
134
+ } catch (error) {
135
+ this.logger.error(`CookieStorage.removeItem failed for key "${key}"`, error);
136
+ }
137
+ }
138
+ };
139
+ var SessionStorage = class {
140
+ constructor(logger) {
141
+ this.logger = logger;
142
+ }
143
+ /**
144
+ * Error situations:
145
+ * - sessionStorage is not available: return null
146
+ * - sessionStorage.getItem throws: catch exception, log error and return null
147
+ * - JSON.parse throws: catch exception, log error and return null
148
+ */
149
+ getItem(key) {
150
+ try {
151
+ if (typeof sessionStorage === "undefined") return null;
152
+ const item = sessionStorage.getItem(key);
153
+ if (item) {
154
+ return JSON.parse(item);
155
+ }
156
+ return null;
157
+ } catch (error) {
158
+ this.logger.error(`SessionStorage.getItem failed for key "${key}"`, error);
159
+ return null;
160
+ }
161
+ }
162
+ /**
163
+ * Error situations:
164
+ * - sessionStorage is not available: return without writing
165
+ * - sessionStorage.setItem throws: catch exception and log error
166
+ * - JSON.stringify throws: catch exception and log error
167
+ */
168
+ setItem(key, value) {
169
+ try {
170
+ if (typeof sessionStorage === "undefined") return;
171
+ sessionStorage.setItem(key, JSON.stringify(value));
172
+ } catch (error) {
173
+ this.logger.error(`SessionStorage.setItem failed for key "${key}"`, error);
174
+ this.logger.reportTelemetry?.("error", `sessionStorage.setItem failed: ${key}`);
175
+ }
176
+ }
177
+ /**
178
+ * Error situations:
179
+ * - sessionStorage is not available: return without writing
180
+ * - sessionStorage.removeItem throws: catch exception and log error
181
+ */
182
+ removeItem(key) {
183
+ try {
184
+ if (typeof sessionStorage === "undefined") return;
185
+ sessionStorage.removeItem(key);
186
+ } catch (error) {
187
+ this.logger.error(`SessionStorage.removeItem failed for key "${key}"`, error);
188
+ }
189
+ }
190
+ };
191
+ export {
192
+ CookieStorage,
193
+ LocalStorage,
194
+ NamespacedStorage,
195
+ SessionStorage,
196
+ namespacedKey
197
+ };