@corbado/observe 1.8.0 → 1.9.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.
Files changed (43) hide show
  1. package/dist/analysis/client.mjs +82 -56
  2. package/dist/analysis/config-manager.mjs +172 -18
  3. package/dist/analysis/config.mjs +12 -4
  4. package/dist/analysis/device-info-manager.mjs +16 -1
  5. package/dist/analysis/device.mjs +88 -10
  6. package/dist/analysis/handoff.mjs +7 -0
  7. package/dist/analysis/low-events/grouped-input-tracker.mjs +148 -33
  8. package/dist/analysis/low-events/input-tracker.mjs +138 -59
  9. package/dist/analysis/low-events/shared-window-observer.mjs +148 -77
  10. package/dist/analysis/low-events/viewport-batcher.mjs +3 -3
  11. package/dist/analysis/queue.mjs +75 -8
  12. package/dist/analysis/{low-events/input-paste-observer.mjs → readers/input-paste.mjs} +2 -2
  13. package/dist/analysis/readers/input.mjs +61 -0
  14. package/dist/analysis/readers/loader-global.mjs +15 -0
  15. package/dist/analysis/readers/network.mjs +14 -0
  16. package/dist/analysis/readers/page.mjs +30 -0
  17. package/dist/analysis/readers/storage.mjs +28 -0
  18. package/dist/analysis/{utils → readers}/web-lock.mjs +6 -2
  19. package/dist/analysis/readers/window-events.mjs +48 -0
  20. package/dist/analysis/storage/index.mjs +61 -9
  21. package/dist/analysis/store/event-store.mjs +34 -16
  22. package/dist/analysis/transports/fetch.mjs +45 -3
  23. package/dist/analysis/utils/email-link-operation-full.mjs +22 -2
  24. package/dist/analysis/utils/email-otp-operation-full.mjs +0 -1
  25. package/dist/analysis/utils/form-operation-full.mjs +0 -1
  26. package/dist/analysis/utils/operation-full-provide-identifier-with-cui.mjs +6 -6
  27. package/dist/analysis/utils/passkey-enrollment-operation-full.mjs +9 -6
  28. package/dist/analysis/utils/passkey-login-operation-full.mjs +10 -5
  29. package/dist/analysis/utils/password-login-operation-full.mjs +8 -4
  30. package/dist/analysis/utils/password-manager-sampler.mjs +44 -4
  31. package/dist/analysis/utils/sms-otp-operation-full.mjs +0 -1
  32. package/dist/analysis/utils/totp-operation-full.mjs +0 -1
  33. package/dist/analysis/version.mjs +1 -1
  34. package/dist/cdn/script.global.js +4 -4
  35. package/dist/index.d.mts +151 -26
  36. package/dist/index.d.ts +151 -26
  37. package/dist/index.js +2 -2
  38. package/dist/index.mjs +2 -2
  39. package/package.json +5 -3
  40. package/tools/reader-meta-text.d.mts +2 -0
  41. package/tools/reader-meta-text.mjs +26 -0
  42. package/tools/strip-reader-meta.d.mts +33 -0
  43. package/tools/strip-reader-meta.mjs +131 -0
@@ -1,4 +1,5 @@
1
1
  // src/storage/index.ts
2
+ import { cookieJar, localStorageArea, sessionStorageArea } from "../readers/storage.mjs";
2
3
  function namespacedKey(key, namespace) {
3
4
  return `${key}_${namespace}`;
4
5
  }
@@ -21,6 +22,23 @@ var LocalStorage = class {
21
22
  constructor(logger) {
22
23
  this.logger = logger;
23
24
  }
25
+ area = localStorageArea({
26
+ key: "storage:sdk.local-storage",
27
+ title: "Observe data saved in the browser",
28
+ accessNote: "localStorage, Observe's own cbo_ keys",
29
+ group: "Observe SDK state",
30
+ origin: "observe-internal",
31
+ fields: [
32
+ {
33
+ path: "value",
34
+ label: "Observe's own saved data",
35
+ exposure: "none",
36
+ transform: {
37
+ note: "own cbo_* keys only (session, sequence, outbox, config cache, experiments, client env, debug); written and read back by the SDK"
38
+ }
39
+ }
40
+ ]
41
+ });
24
42
  /**
25
43
  * Error situations:
26
44
  * - localStorage is not available: return null
@@ -30,7 +48,7 @@ var LocalStorage = class {
30
48
  getItem(key) {
31
49
  try {
32
50
  if (typeof localStorage === "undefined") return null;
33
- const item = localStorage.getItem(key);
51
+ const item = this.area.getItem(key);
34
52
  if (item) {
35
53
  return JSON.parse(item);
36
54
  }
@@ -49,7 +67,7 @@ var LocalStorage = class {
49
67
  setItem(key, value) {
50
68
  try {
51
69
  if (typeof localStorage === "undefined") return;
52
- localStorage.setItem(key, JSON.stringify(value));
70
+ this.area.setItem(key, JSON.stringify(value));
53
71
  } catch (error) {
54
72
  this.logger.error(`LocalStorage.setItem failed for key "${key}"`, error);
55
73
  this.logger.reportTelemetry?.("error", `localStorage.setItem failed: ${key}`);
@@ -63,7 +81,7 @@ var LocalStorage = class {
63
81
  removeItem(key) {
64
82
  try {
65
83
  if (typeof localStorage === "undefined") return;
66
- localStorage.removeItem(key);
84
+ this.area.removeItem(key);
67
85
  } catch (error) {
68
86
  this.logger.error(`LocalStorage.removeItem failed for key "${key}"`, error);
69
87
  }
@@ -74,6 +92,23 @@ var CookieStorage = class {
74
92
  this.logger = logger;
75
93
  this.domain = domain;
76
94
  }
95
+ jar = cookieJar({
96
+ key: "storage:sdk.cookie",
97
+ title: "Observe data saved in cookies",
98
+ accessNote: "document.cookie, Observe's own cbo_ cookies",
99
+ group: "Observe SDK state",
100
+ origin: "observe-internal",
101
+ fields: [
102
+ {
103
+ path: "value",
104
+ label: "Observe's own cookies",
105
+ exposure: "none",
106
+ transform: {
107
+ note: "own cbo_* cookies only (cookie storage mode); the cookie string is scanned for the requested key"
108
+ }
109
+ }
110
+ ]
111
+ });
77
112
  /**
78
113
  * Error situations:
79
114
  * - document is not available: return null
@@ -85,7 +120,7 @@ var CookieStorage = class {
85
120
  try {
86
121
  if (typeof document === "undefined") return null;
87
122
  const name = key + "=";
88
- const parts = document.cookie.split(";");
123
+ const parts = this.jar.read().split(";");
89
124
  for (let i = 0; i < parts.length; i++) {
90
125
  const part = parts[i].trim();
91
126
  if (part.indexOf(name) === 0) {
@@ -112,7 +147,7 @@ var CookieStorage = class {
112
147
  if (this.domain) {
113
148
  cookieString += `; domain=${this.domain}`;
114
149
  }
115
- document.cookie = cookieString;
150
+ this.jar.write(cookieString);
116
151
  } catch (error) {
117
152
  this.logger.error(`CookieStorage.setItem failed for key "${key}"`, error);
118
153
  this.logger.reportTelemetry?.("error", `cookie.setItem failed: ${key}`);
@@ -130,7 +165,7 @@ var CookieStorage = class {
130
165
  if (this.domain) {
131
166
  cookieString += `; domain=${this.domain}`;
132
167
  }
133
- document.cookie = cookieString;
168
+ this.jar.write(cookieString);
134
169
  } catch (error) {
135
170
  this.logger.error(`CookieStorage.removeItem failed for key "${key}"`, error);
136
171
  }
@@ -140,6 +175,23 @@ var SessionStorage = class {
140
175
  constructor(logger) {
141
176
  this.logger = logger;
142
177
  }
178
+ area = sessionStorageArea({
179
+ key: "storage:sdk.session-storage",
180
+ title: "Observe data saved for the current tab",
181
+ accessNote: "sessionStorage, Observe's own cbo_ keys",
182
+ group: "Observe SDK state",
183
+ origin: "observe-internal",
184
+ fields: [
185
+ {
186
+ path: "value",
187
+ label: "Observe's own data for this tab",
188
+ exposure: "none",
189
+ transform: {
190
+ note: "own cbo_* keys only (per-tab session, sequence and tab id); written and read back by the SDK"
191
+ }
192
+ }
193
+ ]
194
+ });
143
195
  /**
144
196
  * Error situations:
145
197
  * - sessionStorage is not available: return null
@@ -149,7 +201,7 @@ var SessionStorage = class {
149
201
  getItem(key) {
150
202
  try {
151
203
  if (typeof sessionStorage === "undefined") return null;
152
- const item = sessionStorage.getItem(key);
204
+ const item = this.area.getItem(key);
153
205
  if (item) {
154
206
  return JSON.parse(item);
155
207
  }
@@ -168,7 +220,7 @@ var SessionStorage = class {
168
220
  setItem(key, value) {
169
221
  try {
170
222
  if (typeof sessionStorage === "undefined") return;
171
- sessionStorage.setItem(key, JSON.stringify(value));
223
+ this.area.setItem(key, JSON.stringify(value));
172
224
  } catch (error) {
173
225
  this.logger.error(`SessionStorage.setItem failed for key "${key}"`, error);
174
226
  this.logger.reportTelemetry?.("error", `sessionStorage.setItem failed: ${key}`);
@@ -182,7 +234,7 @@ var SessionStorage = class {
182
234
  removeItem(key) {
183
235
  try {
184
236
  if (typeof sessionStorage === "undefined") return;
185
- sessionStorage.removeItem(key);
237
+ this.area.removeItem(key);
186
238
  } catch (error) {
187
239
  this.logger.error(`SessionStorage.removeItem failed for key "${key}"`, error);
188
240
  }
@@ -1,5 +1,5 @@
1
1
  // src/store/event-store.ts
2
- import { withWebLock } from "../utils/web-lock.mjs";
2
+ import { withWebLock } from "../readers/web-lock.mjs";
3
3
  var OUTBOX_STORAGE_KEY = "cbo_outbox";
4
4
  var OUTBOX_LOCK_NAME = "cbo_outbox";
5
5
  var DEFAULT_MAX_ENTRIES = 500;
@@ -38,23 +38,41 @@ var EventStore = class {
38
38
  return valid;
39
39
  }
40
40
  mutate(fn) {
41
- return withWebLock(this.lockName, () => {
42
- try {
43
- let next = fn(this.read());
44
- if (next.length > this.maxEntries) {
45
- const evicted = next.length - this.maxEntries;
46
- next = next.slice(next.length - this.maxEntries);
47
- this.logger.error(`EventStore evicted ${evicted} oldest entries (cap ${this.maxEntries})`);
48
- this.logger.reportTelemetry?.("error", "event store evicted entries (cap)");
41
+ return withWebLock(
42
+ {
43
+ key: "browser-api:sdk.web-locks",
44
+ title: "Coordination of Observe data across open tabs",
45
+ accessNote: "Web Locks API (navigator.locks) on Observe's own lock names",
46
+ group: "Observe SDK state",
47
+ origin: "observe-internal",
48
+ fields: [
49
+ {
50
+ path: "result",
51
+ label: "Coordination between open tabs",
52
+ exposure: "none",
53
+ transform: { note: "own cbo_* lock names; serializes the SDK's storage updates across tabs" }
54
+ }
55
+ ]
56
+ },
57
+ this.lockName,
58
+ () => {
59
+ try {
60
+ let next = fn(this.read());
61
+ if (next.length > this.maxEntries) {
62
+ const evicted = next.length - this.maxEntries;
63
+ next = next.slice(next.length - this.maxEntries);
64
+ this.logger.error(`EventStore evicted ${evicted} oldest entries (cap ${this.maxEntries})`);
65
+ this.logger.reportTelemetry?.("error", "event store evicted entries (cap)");
66
+ }
67
+ next = this.enforceSizeBudget(next);
68
+ this.entries = next;
69
+ this.storage.setItem(this.storageKey, next);
70
+ } catch (error) {
71
+ this.logger.error("Failed to mutate event store", error);
72
+ this.logger.reportTelemetry?.("error", "failed to mutate event store");
49
73
  }
50
- next = this.enforceSizeBudget(next);
51
- this.entries = next;
52
- this.storage.setItem(this.storageKey, next);
53
- } catch (error) {
54
- this.logger.error("Failed to mutate event store", error);
55
- this.logger.reportTelemetry?.("error", "failed to mutate event store");
56
74
  }
57
- });
75
+ );
58
76
  }
59
77
  /** Evict oldest entries until the serialized outbox fits {@link maxSerializedLength}. */
60
78
  enforceSizeBudget(next) {
@@ -1,5 +1,6 @@
1
1
  // src/transports/fetch.ts
2
2
  import { parseReliabilityConfig } from "../config.mjs";
3
+ import { beaconSender, sendFetch } from "../readers/network.mjs";
3
4
  var MAX_KEEPALIVE_BODY_BYTES = 6e4;
4
5
  var FETCH_TIMEOUT_MS = 6e4;
5
6
  var FetchTransport = class {
@@ -37,7 +38,33 @@ var FetchTransport = class {
37
38
  keepalive: new Blob([body]).size <= MAX_KEEPALIVE_BODY_BYTES,
38
39
  ...controller ? { signal: controller.signal } : {}
39
40
  };
40
- const response = await fetch(this.options.url, requestOptions);
41
+ const response = await sendFetch(
42
+ {
43
+ key: "network:observe.events",
44
+ title: "Sending events to Corbado Observe",
45
+ accessNote: "fetch (keepalive) to the Observe ingestion API",
46
+ group: "Event delivery",
47
+ origin: "observe-internal",
48
+ fields: [
49
+ { path: "response.status", label: "Response code", exposure: "raw" },
50
+ { path: "response.header.retry-after", label: "Retry delay requested by the server", exposure: "raw" },
51
+ {
52
+ path: "response.header.x-corbado-observe-accepted",
53
+ label: "Whether Observe accepted the events",
54
+ exposure: "enum",
55
+ transform: { values: ["1", "other"] }
56
+ },
57
+ {
58
+ path: "response.body",
59
+ label: "Observe settings in the response",
60
+ exposure: "raw",
61
+ transform: { note: "SDK reliability config (HTTP 200 only)" }
62
+ }
63
+ ]
64
+ },
65
+ this.options.url,
66
+ requestOptions
67
+ );
41
68
  const result = {
42
69
  statusCode: response.status,
43
70
  headers: {
@@ -77,7 +104,22 @@ var FetchTransport = class {
77
104
  sendBeacon(batch) {
78
105
  try {
79
106
  this.options.logger.debug(`Sending ${batch.events.length} events via beacon`);
80
- if (typeof navigator?.sendBeacon !== "function") {
107
+ const send = beaconSender({
108
+ key: "network:observe.events-beacon",
109
+ title: "Sending events to Corbado Observe when the page closes",
110
+ accessNote: "navigator.sendBeacon to the Observe ingestion API",
111
+ group: "Event delivery",
112
+ origin: "observe-internal",
113
+ fields: [
114
+ {
115
+ path: "result",
116
+ label: "Whether the browser accepted the data for sending",
117
+ exposure: "enum",
118
+ transform: { values: [true, false], note: "whether the browser queued the beacon" }
119
+ }
120
+ ]
121
+ });
122
+ if (!send) {
81
123
  return false;
82
124
  }
83
125
  batch.meta = {
@@ -90,7 +132,7 @@ var FetchTransport = class {
90
132
  // can handle both text/plain and application/json.
91
133
  type: "text/plain"
92
134
  });
93
- return navigator.sendBeacon(this.options.url, blob);
135
+ return send(this.options.url, blob);
94
136
  } catch (error) {
95
137
  this.options.logger.error("Failed to send event via beacon", error);
96
138
  return false;
@@ -2,6 +2,7 @@
2
2
  import { v7 as uuidv7 } from "uuid";
3
3
  import { OperationFull } from "./operation-full.mjs";
4
4
  import { findEmailLinkStart, markEmailLinkContinued, rememberEmailLinkStart } from "./email-link-continuity.mjs";
5
+ import { listenWindow } from "../readers/page.mjs";
5
6
  var EmailLinkOperationFull = class extends OperationFull {
6
7
  send;
7
8
  postResponse;
@@ -42,6 +43,7 @@ var EmailLinkContinuationOperation = class extends OperationFull {
42
43
  started = false;
43
44
  deferred = [];
44
45
  onPageHide = () => this.flush();
46
+ removePageHide;
45
47
  constructor(tracker, config = {}) {
46
48
  super(tracker, "email-link");
47
49
  this.continuationTracker = tracker;
@@ -65,7 +67,25 @@ var EmailLinkContinuationOperation = class extends OperationFull {
65
67
  if (this.transaction) {
66
68
  this.startDetached(this.createdAt);
67
69
  } else if (typeof window !== "undefined") {
68
- window.addEventListener("pagehide", this.onPageHide);
70
+ this.removePageHide = listenWindow(
71
+ {
72
+ key: "page:lifecycle.pagehide",
73
+ title: "Leaving the page",
74
+ accessNote: "pagehide event on window",
75
+ group: "Page lifecycle",
76
+ origin: "observe-external",
77
+ fields: [
78
+ {
79
+ path: "event.pagehide",
80
+ label: "Leaving the page",
81
+ exposure: "presence",
82
+ transform: { note: "flushes a detached email-link continuation on unload" }
83
+ }
84
+ ]
85
+ },
86
+ "pagehide",
87
+ this.onPageHide
88
+ );
69
89
  }
70
90
  }
71
91
  /** Transaction ID of the continued email link, when known. */
@@ -113,7 +133,7 @@ var EmailLinkContinuationOperation = class extends OperationFull {
113
133
  }
114
134
  }
115
135
  removePageHideListener() {
116
- if (typeof window !== "undefined") window.removeEventListener("pagehide", this.onPageHide);
136
+ if (typeof window !== "undefined") this.removePageHide?.();
117
137
  }
118
138
  /** Defer step calls until the detached start is emitted; mark the continued link on completion. */
119
139
  deferrable(step, completesLink = false) {
@@ -26,7 +26,6 @@ var EmailOtpOperationFull = class extends OperationFull {
26
26
  }
27
27
  this.registerLowFlusher(() => this.lowEventTracker?.flushPending());
28
28
  }
29
- /** Rebind the ordered OTP group without starting a new subflow. An empty list detaches it. */
30
29
  setInputFields(fields) {
31
30
  this.lowEventTracker?.setFields(fields);
32
31
  }
@@ -22,7 +22,6 @@ var FormOperationFull = class extends OperationFull {
22
22
  this.registerLowFlusher(() => this.inputBinding?.flushPending());
23
23
  if (config?.inputHtmlFields?.length) this.setInputFields(config.inputHtmlFields);
24
24
  }
25
- /** Bind the fields the form shows now, without starting a new subflow. An empty list detaches all. */
26
25
  setInputFields(fields) {
27
26
  this.inputBinding?.setFields(fields);
28
27
  }
@@ -1,6 +1,7 @@
1
1
  // src/utils/operation-full-provide-identifier-with-cui.ts
2
2
  import { LowEventInputTracker } from "../low-events/input-tracker.mjs";
3
3
  import { OperationFull } from "./operation-full.mjs";
4
+ import { acceptHandoffs } from "../handoff.mjs";
4
5
  var OperationFullProvideIdentifierWithCUI = class extends OperationFull {
5
6
  lowEventTracker;
6
7
  cui;
@@ -12,14 +13,13 @@ var OperationFullProvideIdentifierWithCUI = class extends OperationFull {
12
13
  this.trigger(data, options);
13
14
  },
14
15
  // getOptions only fetches assertion options; it is not a user interaction on its own.
15
- getOptions: this.defineStep(
16
- "cui-get-options",
17
- true
16
+ getOptions: acceptHandoffs(
17
+ this.defineStep("cui-get-options", true)
18
18
  ),
19
- ceremony: this.defineStep(
20
- "cui-ceremony"
19
+ ceremony: acceptHandoffs(
20
+ this.defineStep("cui-ceremony")
21
21
  ),
22
- postResponse: this.defineStep("cui-post-response")
22
+ postResponse: acceptHandoffs(this.defineStep("cui-post-response"))
23
23
  };
24
24
  this.provideIdentifier = {
25
25
  trigger: (data, options) => {
@@ -1,6 +1,7 @@
1
1
  // src/utils/passkey-enrollment-operation-full.ts
2
2
  import { LowEventWindowTracker } from "../low-events/window-tracker.mjs";
3
3
  import { OperationFull } from "./operation-full.mjs";
4
+ import { acceptHandoffs } from "../handoff.mjs";
4
5
  var PasskeyEnrollmentOperationFull = class extends OperationFull {
5
6
  getOptions;
6
7
  ceremony;
@@ -10,14 +11,16 @@ var PasskeyEnrollmentOperationFull = class extends OperationFull {
10
11
  super(tracker, "passkey-enrollment");
11
12
  this.lowEventTracker = new LowEventWindowTracker(tracker.getLogger(), this.lowEventSink("ceremony"));
12
13
  this.registerLowFlusher(() => this.lowEventTracker.disarm());
13
- this.getOptions = this.defineStep(
14
- "get-options"
14
+ this.getOptions = acceptHandoffs(
15
+ this.defineStep("get-options")
15
16
  );
16
- this.ceremony = this.withWindowLowEvents(
17
- this.defineStep("ceremony"),
18
- this.lowEventTracker
17
+ this.ceremony = acceptHandoffs(
18
+ this.withWindowLowEvents(
19
+ this.defineStep("ceremony"),
20
+ this.lowEventTracker
21
+ )
19
22
  );
20
- this.postResponse = this.defineStep("post-response");
23
+ this.postResponse = acceptHandoffs(this.defineStep("post-response"));
21
24
  void this.subflowStart({
22
25
  explicitSpecType: config?.explicitSpecType,
23
26
  ...config?.ignoreAsInteraction ? { ignoreAsInteraction: true } : {}
@@ -1,6 +1,7 @@
1
1
  // src/utils/passkey-login-operation-full.ts
2
2
  import { LowEventWindowTracker } from "../low-events/window-tracker.mjs";
3
3
  import { OperationFull } from "./operation-full.mjs";
4
+ import { acceptHandoffs } from "../handoff.mjs";
4
5
  var PasskeyLoginOperationFull = class extends OperationFull {
5
6
  getOptions;
6
7
  ceremony;
@@ -10,12 +11,16 @@ var PasskeyLoginOperationFull = class extends OperationFull {
10
11
  super(tracker, "passkey-login");
11
12
  this.lowEventTracker = new LowEventWindowTracker(tracker.getLogger(), this.lowEventSink("ceremony"));
12
13
  this.registerLowFlusher(() => this.lowEventTracker.disarm());
13
- this.getOptions = this.defineStep("get-options");
14
- this.ceremony = this.withWindowLowEvents(
15
- this.defineStep("ceremony"),
16
- this.lowEventTracker
14
+ this.getOptions = acceptHandoffs(
15
+ this.defineStep("get-options")
17
16
  );
18
- this.postResponse = this.defineStep("post-response");
17
+ this.ceremony = acceptHandoffs(
18
+ this.withWindowLowEvents(
19
+ this.defineStep("ceremony"),
20
+ this.lowEventTracker
21
+ )
22
+ );
23
+ this.postResponse = acceptHandoffs(this.defineStep("post-response"));
19
24
  void this.subflowStart({ explicitSpecType: config?.explicitSpecType });
20
25
  }
21
26
  /**
@@ -1,6 +1,7 @@
1
1
  // src/utils/password-login-operation-full.ts
2
2
  import { LowEventInputTracker } from "../low-events/input-tracker.mjs";
3
3
  import { OperationFull } from "./operation-full.mjs";
4
+ import { acceptHandoffs } from "../handoff.mjs";
4
5
  var PasswordLoginOperationFull = class extends OperationFull {
5
6
  clientValidation;
6
7
  postResponse;
@@ -18,11 +19,14 @@ var PasswordLoginOperationFull = class extends OperationFull {
18
19
  this.postResponse = this.defineStep("post-response");
19
20
  this.cui = {
20
21
  // getOptions only fetches assertion options; it is not a user interaction on its own.
21
- getOptions: this.defineStep(
22
- "cui-get-options",
23
- true
22
+ getOptions: acceptHandoffs(
23
+ this.defineStep("cui-get-options", true)
24
+ ),
25
+ ceremony: acceptHandoffs(
26
+ this.defineStep(
27
+ "cui-ceremony"
28
+ )
24
29
  ),
25
- ceremony: this.defineStep("cui-ceremony"),
26
30
  postResponse: this.defineStep("cui-post-response")
27
31
  };
28
32
  void this.subflowStart({ explicitSpecType: config?.explicitSpecType });
@@ -1,4 +1,5 @@
1
1
  // src/utils/password-manager-sampler.ts
2
+ import { listenWindow, readDocumentComplete } from "../readers/page.mjs";
2
3
  import {
3
4
  readPasswordManagerExtensions
4
5
  } from "../readers/password-manager-extensions.mjs";
@@ -12,15 +13,49 @@ var PasswordManagerSampler = class {
12
13
  this.isEnabled = isEnabled;
13
14
  if (typeof window === "undefined" || typeof document === "undefined") return;
14
15
  this.sample();
15
- if (document.readyState === "complete") {
16
+ if (readDocumentComplete({
17
+ key: "page:lifecycle.ready-state",
18
+ title: "Page load state",
19
+ accessNote: "document.readyState",
20
+ group: "Page lifecycle",
21
+ origin: "observe-external",
22
+ fields: [
23
+ {
24
+ path: "readyState",
25
+ label: "Whether the page has finished loading",
26
+ exposure: "enum",
27
+ transform: { values: ["complete", "other"] }
28
+ }
29
+ ]
30
+ })) {
16
31
  this.sampleAfter(LOAD_RESAMPLE_DELAY_MS);
17
32
  } else {
18
33
  const onLoad = () => {
19
34
  this.sampleWhenIdle();
20
35
  this.sampleAfter(LOAD_RESAMPLE_DELAY_MS);
21
36
  };
22
- window.addEventListener("load", onLoad, { once: true });
23
- this.removers.push(() => window.removeEventListener("load", onLoad));
37
+ this.removers.push(
38
+ listenWindow(
39
+ {
40
+ key: "page:lifecycle.load",
41
+ title: "Page load",
42
+ accessNote: "load event on window",
43
+ group: "Page lifecycle",
44
+ origin: "observe-external",
45
+ fields: [
46
+ {
47
+ path: "event.load",
48
+ label: "Page finished loading",
49
+ exposure: "presence",
50
+ transform: { note: "schedules a password manager sample" }
51
+ }
52
+ ]
53
+ },
54
+ "load",
55
+ onLoad,
56
+ { once: true }
57
+ )
58
+ );
24
59
  }
25
60
  const removeFocus = windowObserver.onInputFocus((field) => {
26
61
  if (!this.isEnabled() || this.sampledFields.has(field)) return;
@@ -89,9 +124,14 @@ var PasswordManagerSampler = class {
89
124
  try {
90
125
  const found = readPasswordManagerExtensions({
91
126
  key: "browser-api:password-managers.extensions",
127
+ title: "Installed password manager extensions",
128
+ accessNote: "globals and DOM elements that known password manager extensions inject",
129
+ group: "Password manager detection",
130
+ origin: "observe-external",
92
131
  fields: [
93
132
  {
94
133
  path: "result[].name",
134
+ label: "Names of known password managers found",
95
135
  exposure: "enum",
96
136
  transform: {
97
137
  values: [
@@ -107,7 +147,7 @@ var PasswordManagerSampler = class {
107
147
  }
108
148
  },
109
149
  // Read to merge samples, never reported (takeReport keeps only the name).
110
- { path: "result[].signedIn", exposure: "none" }
150
+ { path: "result[].signedIn", label: "Whether the password manager is signed in", exposure: "none" }
111
151
  ],
112
152
  dataConfig: "pmd"
113
153
  });
@@ -26,7 +26,6 @@ var SmsOtpOperationFull = class extends OperationFull {
26
26
  }
27
27
  this.registerLowFlusher(() => this.lowEventTracker?.flushPending());
28
28
  }
29
- /** Rebind the ordered OTP group without starting a new subflow. An empty list detaches it. */
30
29
  setInputFields(fields) {
31
30
  this.lowEventTracker?.setFields(fields);
32
31
  }
@@ -22,7 +22,6 @@ var TotpOperationFull = class extends OperationFull {
22
22
  }
23
23
  this.registerLowFlusher(() => this.lowEventTracker?.flushPending());
24
24
  }
25
- /** Rebind the ordered code input group without starting a new subflow. An empty list detaches it. */
26
25
  setInputFields(fields) {
27
26
  this.lowEventTracker?.setFields(fields);
28
27
  }
@@ -1,6 +1,6 @@
1
1
  // src/version.ts
2
2
  var SDK_NAME = "@corbado/observe";
3
- var SDK_VERSION = true ? "1.8.0" : "0.0.0-dev";
3
+ var SDK_VERSION = true ? "1.9.1" : "0.0.0-dev";
4
4
  export {
5
5
  SDK_NAME,
6
6
  SDK_VERSION