@corbado/observe 1.10.0 → 1.10.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.
@@ -1628,16 +1628,6 @@ var CorbadoTracker = class _CorbadoTracker {
1628
1628
  this.logger.error("Failed to flush queue keepalive", error);
1629
1629
  }
1630
1630
  }
1631
- /**
1632
- * Whether input trackers run the DOM password-manager scan (`pa-1`..`pa-6` lows): only an explicitly
1633
- * set `fixedDataConfig.pmd` decides; without `fixedDataConfig` or with `pmd` omitted it always runs
1634
- * (historical behaviour).
1635
- *
1636
- * @internal
1637
- */
1638
- isPasswordManagerDomScanEnabled() {
1639
- return this.fixedData?.pmdExplicit ? this.fixedData.pmd : true;
1640
- }
1641
1631
  /**
1642
1632
  * Keepalive flush requested by a low-event tracker's `pagehide` teardown. Gated by the
1643
1633
  * server-controlled trigger switch `tlf` (boot-snapshot, default true = historical behavior):
@@ -183,13 +183,10 @@ function resolveFixedDataConfig(raw) {
183
183
  return void 0;
184
184
  }
185
185
  const value = raw;
186
- const pmd = value.pmd;
187
- const pmdExplicit = typeof pmd === "boolean";
188
186
  const crossOrigin = value.crossOriginSession;
189
187
  const cookieDomain = crossOrigin && typeof crossOrigin === "object" && typeof crossOrigin.cookieDomain === "string" ? crossOrigin.cookieDomain : void 0;
190
188
  return {
191
- pmd: pmdExplicit ? pmd : false,
192
- pmdExplicit,
189
+ pmd: asBoolean(value.pmd, false),
193
190
  privateMode: asBoolean(value.privateMode, true),
194
191
  ...cookieDomain !== void 0 && { crossOriginSession: { cookieDomain } }
195
192
  };
@@ -7,7 +7,6 @@ import {
7
7
  readConditionalMediationAvailable,
8
8
  readJavaScriptHighEntropy,
9
9
  readMaxTouchPoints,
10
- readUserAgent,
11
10
  readUserVerifyingPlatformAuthenticatorAvailable,
12
11
  readWebdriver
13
12
  } from "./readers/device.mjs";
@@ -515,84 +514,6 @@ var DeviceCollector = class {
515
514
  this.logger.error("Failed to report device info timing", error);
516
515
  }
517
516
  }
518
- /**
519
- * Error situations:
520
- * - navigator is not available: user agent is treated as empty, osName and browserName are "other"
521
- * - navigator is available but userAgent is null or undefined: same as above
522
- */
523
- static getOsAndBrowserInfo() {
524
- return {
525
- osName: this.getOsName(),
526
- browserName: this.getBrowserName()
527
- };
528
- }
529
- static getUserAgent() {
530
- return readUserAgent({
531
- key: "browser-api:device.user-agent",
532
- title: "Browser and operating system",
533
- accessNote: "navigator.userAgent",
534
- group: "Device information",
535
- origin: "observe-external",
536
- fields: [
537
- {
538
- path: "result",
539
- label: "Browser and operating system",
540
- exposure: "class",
541
- transform: {
542
- classes: [
543
- "iOS",
544
- "Android",
545
- "Windows",
546
- "macOS",
547
- "Linux",
548
- "ChromeOS",
549
- "Chrome",
550
- "Firefox",
551
- "Safari",
552
- "Edge",
553
- "Opera",
554
- "IE",
555
- "other"
556
- ]
557
- }
558
- }
559
- ]
560
- });
561
- }
562
- static getOsName() {
563
- const ua = this.getUserAgent();
564
- if (ua.includes("iOS")) {
565
- return "iOS";
566
- } else if (ua.includes("Android")) {
567
- return "Android";
568
- } else if (ua.includes("Windows")) {
569
- return "Windows";
570
- } else if (ua.includes("Macintosh")) {
571
- return "macOS";
572
- } else if (ua.includes("Linux")) {
573
- return "Linux";
574
- } else if (ua.includes("ChromeOS")) {
575
- return "ChromeOS";
576
- }
577
- return "other";
578
- }
579
- static getBrowserName() {
580
- const ua = this.getUserAgent();
581
- if (ua.includes("Chrome")) {
582
- return "Chrome";
583
- } else if (ua.includes("Firefox")) {
584
- return "Firefox";
585
- } else if (ua.includes("Safari")) {
586
- return "Safari";
587
- } else if (ua.includes("Edge")) {
588
- return "Edge";
589
- } else if (ua.includes("Opera")) {
590
- return "Opera";
591
- } else if (ua.includes("IE")) {
592
- return "IE";
593
- }
594
- return "other";
595
- }
596
517
  getClientEnvHandle() {
597
518
  const existingClientEnvHandle = this.storage.getItem("cbo_client_env_handle");
598
519
  if (existingClientEnvHandle) {
@@ -99,7 +99,7 @@ var LowEventGroupedInputTracker = class {
99
99
  this.composing ||= composing;
100
100
  if (this.changeTimer !== null) return;
101
101
  this.changeTime = Date.now();
102
- this.changeSeq = sink.reserveCaptureSeq?.();
102
+ this.changes.observe();
103
103
  this.changeTimer = setTimeout(() => this.safe(() => this.flushChanges()), 0);
104
104
  },
105
105
  focus: (relatedTarget) => {
@@ -174,7 +174,6 @@ var LowEventGroupedInputTracker = class {
174
174
  listeners = [];
175
175
  changeTimer = null;
176
176
  changeTime = 0;
177
- changeSeq;
178
177
  composing = false;
179
178
  destroyed = false;
180
179
  contains(target) {
@@ -214,7 +213,7 @@ var LowEventGroupedInputTracker = class {
214
213
  flushChanges() {
215
214
  if (this.changeTimer === null) return;
216
215
  this.cancelChange();
217
- if (!this.paste.pending) this.changes.record(this.length(), this.changeTime, this.composing, this.changeSeq);
216
+ if (!this.paste.pending) this.changes.record(this.length(), this.changeTime, this.composing);
218
217
  this.composing = false;
219
218
  }
220
219
  emit(lowType) {
@@ -18,9 +18,28 @@ var InputChangeRecorder = class {
18
18
  autofillTimestamps = [];
19
19
  autofillStreak = null;
20
20
  idleTimer = null;
21
+ /** Order of the pending run of changes: reserved when it starts, taken by the first event it sends. */
22
+ runSeq;
21
23
  markInputObserved() {
22
24
  this.prefillSettled = true;
23
25
  }
26
+ /**
27
+ * Observation of a change begins: reserves the capture order of a new run of changes. One number per
28
+ * run, not per change: every reserved number is sent, so the gaps between sent events never count
29
+ * keystrokes (which would reveal the length of the typed value).
30
+ */
31
+ observe() {
32
+ if (this.runSeq !== void 0 || this.inputBatch || this.autofillTimestamps.length > 0 || this.autofillStreak) {
33
+ return;
34
+ }
35
+ this.runSeq = this.reserveCaptureSeq?.();
36
+ }
37
+ /** The run's reserved order for its first event; later events of the run reserve when they are created. */
38
+ takeSeq() {
39
+ const seq = this.runSeq;
40
+ this.runSeq = void 0;
41
+ return seq ?? this.reserveCaptureSeq?.();
42
+ }
24
43
  capturePrefill(length, newCapture = false) {
25
44
  if (newCapture) this.prefillSettled = false;
26
45
  if (this.prefillSettled) return;
@@ -50,17 +69,17 @@ var InputChangeRecorder = class {
50
69
  }
51
70
  }, INPUT_IDLE_MS);
52
71
  }
53
- extendInputBatch(timestamp, effect = this.currentEffect, captureSeq) {
72
+ extendInputBatch(timestamp, effect = this.currentEffect) {
54
73
  const batch = this.inputBatch;
55
74
  if (!batch) {
56
- this.inputBatch = { firstTimestamp: timestamp, lastTimestamp: timestamp, effect, captureSeq };
75
+ this.inputBatch = { firstTimestamp: timestamp, lastTimestamp: timestamp, effect, captureSeq: this.takeSeq() };
57
76
  return;
58
77
  }
59
78
  batch.lastTimestamp = timestamp;
60
79
  }
61
80
  drainAutofillRingToBatch() {
62
81
  for (const ts of this.autofillTimestamps) {
63
- this.extendInputBatch(ts.ts, this.ringEffect, ts.captureSeq);
82
+ this.extendInputBatch(ts, this.ringEffect);
64
83
  }
65
84
  this.autofillTimestamps.length = 0;
66
85
  }
@@ -101,7 +120,7 @@ var InputChangeRecorder = class {
101
120
  let minInterval = Infinity;
102
121
  let maxInterval = -Infinity;
103
122
  for (let i = 1; i < ring.length; i++) {
104
- const interval = ring[i].ts - ring[i - 1].ts;
123
+ const interval = ring[i] - ring[i - 1];
105
124
  if (interval > AUTOFILL_FAST_INTERVAL_MS) {
106
125
  return false;
107
126
  }
@@ -114,7 +133,8 @@ var InputChangeRecorder = class {
114
133
  }
115
134
  return maxInterval - minInterval <= AUTOFILL_UNIFORM_SPREAD_MS;
116
135
  }
117
- record(newLength, timestamp = Date.now(), composing = false, captureSeq = this.reserveCaptureSeq?.()) {
136
+ record(newLength, timestamp = Date.now(), composing = false) {
137
+ this.observe();
118
138
  this.markInputObserved();
119
139
  this.scheduleIdle();
120
140
  const oldLength = this.previousLength;
@@ -123,6 +143,7 @@ var InputChangeRecorder = class {
123
143
  this.previousLength = newLength;
124
144
  if (newLength === 0 && oldLength > 0) {
125
145
  this.flush();
146
+ const captureSeq = this.takeSeq();
126
147
  this.pushLow({
127
148
  ...captureSeq === void 0 ? {} : { captureSeq },
128
149
  lowType: "input-clear",
@@ -134,12 +155,13 @@ var InputChangeRecorder = class {
134
155
  if (composing) {
135
156
  this.flushAutofillStreak();
136
157
  this.drainAutofillRingToBatch();
137
- this.extendInputBatch(timestamp, this.currentEffect, captureSeq);
158
+ this.extendInputBatch(timestamp, this.currentEffect);
138
159
  return;
139
160
  }
140
161
  if (delta >= BIG_INPUT_DELTA_THRESHOLD) {
141
162
  this.flushInputBatch();
142
163
  this.flushAutofillStreak();
164
+ const captureSeq = this.takeSeq();
143
165
  this.pushLow({
144
166
  ...captureSeq === void 0 ? {} : { captureSeq },
145
167
  lowType: "big-input-add",
@@ -151,6 +173,7 @@ var InputChangeRecorder = class {
151
173
  if (delta <= -BIG_INPUT_DELTA_THRESHOLD) {
152
174
  this.flushInputBatch();
153
175
  this.flushAutofillStreak();
176
+ const captureSeq = this.takeSeq();
154
177
  this.pushLow({
155
178
  ...captureSeq === void 0 ? {} : { captureSeq },
156
179
  lowType: "big-input-rem",
@@ -169,15 +192,15 @@ var InputChangeRecorder = class {
169
192
  }
170
193
  if (delta !== 1) {
171
194
  this.drainAutofillRingToBatch();
172
- this.extendInputBatch(timestamp, this.currentEffect, captureSeq);
195
+ this.extendInputBatch(timestamp, this.currentEffect);
173
196
  return;
174
197
  }
175
198
  const ring = this.autofillTimestamps;
176
199
  if (ring.length === 0) this.ringEffect = this.currentEffect;
177
- ring.push({ ts: timestamp, captureSeq });
200
+ ring.push(timestamp);
178
201
  if (ring.length > AUTOFILL_STREAK_LENGTH) {
179
202
  const graduated = ring.shift();
180
- this.extendInputBatch(graduated.ts, this.ringEffect, graduated.captureSeq);
203
+ this.extendInputBatch(graduated, this.ringEffect);
181
204
  this.ringEffect = "modify";
182
205
  }
183
206
  if (!this.detectAutofillStreak()) {
@@ -188,9 +211,9 @@ var InputChangeRecorder = class {
188
211
  ring.length = 0;
189
212
  this.flushInputBatch();
190
213
  this.autofillStreak = {
191
- firstTimestamp: streakStart.ts,
192
- captureSeq: streakStart.captureSeq,
193
- lastTimestamp: streakLast.ts,
214
+ firstTimestamp: streakStart,
215
+ captureSeq: this.takeSeq(),
216
+ lastTimestamp: streakLast,
194
217
  effect: this.ringEffect
195
218
  };
196
219
  }
@@ -3,9 +3,6 @@ import { InputChangeRecorder } from "./input-change-recorder.mjs";
3
3
  import { InputPasteObserver } from "../readers/input-paste.mjs";
4
4
  import { observeInputField, readFocusedField, readInputContentLength } from "../readers/input.mjs";
5
5
  import { listenWindow } from "../readers/page.mjs";
6
- import { readPasswordManagerDomSignals } from "../readers/password-manager-dom.mjs";
7
- var MOUNT_PROBE_DELAY_MS = 500;
8
- var FOCUS_PROBE_DELAY_MS = 250;
9
6
  function fieldContentLength(field) {
10
7
  return readInputContentLength(
11
8
  {
@@ -85,10 +82,6 @@ var LowEventInputTracker = class {
85
82
  removePageHide;
86
83
  changes;
87
84
  pasteObserver;
88
- emittedSignals = /* @__PURE__ */ new Set();
89
- firstFocusHandled = false;
90
- mountProbeTimer = null;
91
- focusProbeTimer = null;
92
85
  destroyed = false;
93
86
  destroy() {
94
87
  if (this.destroyed) {
@@ -96,14 +89,6 @@ var LowEventInputTracker = class {
96
89
  }
97
90
  this.destroyed = true;
98
91
  try {
99
- if (this.mountProbeTimer !== null) {
100
- clearTimeout(this.mountProbeTimer);
101
- this.mountProbeTimer = null;
102
- }
103
- if (this.focusProbeTimer !== null) {
104
- clearTimeout(this.focusProbeTimer);
105
- this.focusProbeTimer = null;
106
- }
107
92
  this.changes.flush();
108
93
  this.unregisterInputs?.();
109
94
  this.pasteObserver.destroy();
@@ -204,9 +189,6 @@ var LowEventInputTracker = class {
204
189
  this.handleFocus();
205
190
  }
206
191
  }
207
- if (typeof window !== "undefined") {
208
- this.mountProbeTimer = setTimeout(() => this.runEnvProbe(), MOUNT_PROBE_DELAY_MS);
209
- }
210
192
  }
211
193
  detach() {
212
194
  this.removeFieldListeners?.();
@@ -217,45 +199,6 @@ var LowEventInputTracker = class {
217
199
  pushLow = (low) => {
218
200
  this.sink.enqueueLowEvent(low);
219
201
  };
220
- runEnvProbe() {
221
- try {
222
- if (typeof __CBO_DATA_PMD__ === "boolean" ? __CBO_DATA_PMD__ : this.sink.passwordManagerDomScan?.() !== false) {
223
- const matches = readPasswordManagerDomSignals({
224
- key: "dom:password-managers.dom-signals",
225
- title: "Password manager traces in the page",
226
- accessNote: "DOM markers that password manager extensions insert (attributes, elements, styles)",
227
- group: "Password manager detection",
228
- origin: "observe-external",
229
- fields: [
230
- {
231
- path: "signals[]",
232
- label: "Password manager traces found",
233
- exposure: "enum",
234
- transform: { values: ["pa-1", "pa-2", "pa-3", "pa-4", "pa-5", "pa-6"] }
235
- }
236
- ],
237
- dataConfig: "pmd"
238
- });
239
- const now = Date.now();
240
- for (const eventType of matches) {
241
- if (this.emittedSignals.has(eventType)) {
242
- continue;
243
- }
244
- this.emittedSignals.add(eventType);
245
- this.pushLow({ lowType: eventType, ts: now });
246
- }
247
- }
248
- } catch (error) {
249
- this.logger.error("Failed to run input env probe", error);
250
- }
251
- }
252
- scheduleFocusProbe() {
253
- if (this.firstFocusHandled) {
254
- return;
255
- }
256
- this.firstFocusHandled = true;
257
- this.focusProbeTimer = setTimeout(() => this.runEnvProbe(), FOCUS_PROBE_DELAY_MS);
258
- }
259
202
  capturePrefill(newCapture = false) {
260
203
  if (this.destroyed) return;
261
204
  this.flushPending();
@@ -270,7 +213,6 @@ var LowEventInputTracker = class {
270
213
  this.pushLow({ lowType: eventType, ts: Date.now() });
271
214
  }
272
215
  handleFocus() {
273
- this.scheduleFocusProbe();
274
216
  this.enqueueNonInputEvent("focus");
275
217
  }
276
218
  handleBlur() {
@@ -1,4 +1,8 @@
1
1
  // src/readers/network.ts
2
+ var declaredByBrowser = /* @__PURE__ */ new Set();
3
+ function declareBrowserRequestData(meta) {
4
+ declaredByBrowser.add(meta);
5
+ }
2
6
  function sendFetch(_meta, url, init) {
3
7
  return fetch(url, init);
4
8
  }
@@ -10,5 +14,6 @@ function beaconSender(_meta) {
10
14
  }
11
15
  export {
12
16
  beaconSender,
17
+ declareBrowserRequestData,
13
18
  sendFetch
14
19
  };
@@ -1,11 +1,26 @@
1
1
  // src/transports/fetch.ts
2
2
  import { parseReliabilityConfig } from "../config.mjs";
3
- import { beaconSender, sendFetch } from "../readers/network.mjs";
3
+ import { beaconSender, declareBrowserRequestData, sendFetch } from "../readers/network.mjs";
4
4
  var MAX_KEEPALIVE_BODY_BYTES = 6e4;
5
5
  var FETCH_TIMEOUT_MS = 6e4;
6
6
  var FetchTransport = class {
7
7
  constructor(options) {
8
8
  this.options = options;
9
+ declareBrowserRequestData({
10
+ key: "browser-api:device.user-agent",
11
+ title: "Browser and operating system",
12
+ accessNote: "User-Agent header the browser sends with every request to the Observe ingestion API (the SDK does not read it); Corbado stores it and derives browser and operating system name and version",
13
+ group: "Device information",
14
+ origin: "observe-external",
15
+ fields: [
16
+ {
17
+ path: "request.header.user-agent",
18
+ label: "Browser and operating system identifier",
19
+ exposure: "raw",
20
+ transform: { note: "full User-Agent string, sent by the browser" }
21
+ }
22
+ ]
23
+ });
9
24
  }
10
25
  /**
11
26
  * Error situations:
@@ -30,7 +30,6 @@ var OperationFull = class {
30
30
  this.captureSessionId
31
31
  ),
32
32
  flushKeepalive: () => this.tracker.lowEventFlushKeepalive(),
33
- passwordManagerDomScan: () => this.tracker.isPasswordManagerDomScanEnabled(),
34
33
  registerInputs: (fields, flush) => this.tracker.getWindowObserver().registerInputs(
35
34
  fields,
36
35
  () => ({
@@ -1,6 +1,6 @@
1
1
  // src/version.ts
2
2
  var SDK_NAME = "@corbado/observe";
3
- var SDK_VERSION = true ? "1.10.0" : "0.0.0-dev";
3
+ var SDK_VERSION = true ? "1.10.1" : "0.0.0-dev";
4
4
  export {
5
5
  SDK_NAME,
6
6
  SDK_VERSION