@corbado/observe 1.10.0 → 1.11.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.
- package/dist/analysis/client.mjs +37 -1605
- package/dist/analysis/config.mjs +1 -4
- package/dist/analysis/default-tracker.mjs +16 -0
- package/dist/analysis/device.mjs +0 -79
- package/dist/analysis/index.mjs +29 -41
- package/dist/analysis/low-events/grouped-input-tracker.mjs +2 -3
- package/dist/analysis/low-events/input-change-recorder.mjs +35 -12
- package/dist/analysis/low-events/input-tracker.mjs +0 -58
- package/dist/analysis/plugins/cookie-storage.mjs +11 -0
- package/dist/analysis/plugins/cross-origin-session.mjs +171 -0
- package/dist/analysis/plugins/probes.mjs +69 -0
- package/dist/analysis/plugins/raw-errors.mjs +8 -0
- package/dist/analysis/probe-lane.mjs +175 -0
- package/dist/analysis/queue.mjs +18 -141
- package/dist/analysis/readers/network.mjs +5 -0
- package/dist/analysis/session-keys.mjs +11 -0
- package/dist/analysis/tracker-core.mjs +1352 -0
- package/dist/analysis/tracker-features.mjs +80 -0
- package/dist/analysis/tracker-functions.mjs +103 -0
- package/dist/analysis/transports/fetch.mjs +16 -1
- package/dist/analysis/utils/appconfirmation-operation-full.mjs +5 -1
- package/dist/analysis/utils/captcha-operation-full.mjs +5 -1
- package/dist/analysis/utils/email-link-operation-full.mjs +9 -1
- package/dist/analysis/utils/email-otp-operation-full.mjs +5 -1
- package/dist/analysis/utils/form-operation-full.mjs +5 -1
- package/dist/analysis/utils/key-operation-full.mjs +9 -1
- package/dist/analysis/utils/operation-full-provide-identifier-with-cui.mjs +8 -1
- package/dist/analysis/utils/operation-full.mjs +4 -3
- package/dist/analysis/utils/passkey-deletion-operation-full.mjs +5 -1
- package/dist/analysis/utils/passkey-enrollment-operation-full.mjs +5 -1
- package/dist/analysis/utils/passkey-login-operation-full.mjs +5 -1
- package/dist/analysis/utils/password-enrollment-operation-full.mjs +5 -1
- package/dist/analysis/utils/password-login-operation-full.mjs +5 -1
- package/dist/analysis/utils/provide-data-operation-full.mjs +5 -1
- package/dist/analysis/utils/sms-otp-operation-full.mjs +5 -1
- package/dist/analysis/utils/social-operation-login.mjs +5 -1
- package/dist/analysis/utils/sso-operation-login.mjs +5 -1
- package/dist/analysis/utils/totp-operation-full.mjs +5 -1
- package/dist/analysis/utils/trusted-device-operation-full.mjs +9 -1
- package/dist/analysis/version.mjs +1 -1
- package/dist/cdn/script.global.js +4 -4
- package/dist/index.d.mts +1349 -888
- package/dist/index.d.ts +1349 -888
- package/dist/index.js +4 -4
- package/dist/index.mjs +4 -4
- package/package.json +1 -1
- package/dist/analysis/readers/password-manager-dom.mjs +0 -112
package/dist/analysis/config.mjs
CHANGED
|
@@ -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:
|
|
192
|
-
pmdExplicit,
|
|
189
|
+
pmd: asBoolean(value.pmd, false),
|
|
193
190
|
privateMode: asBoolean(value.privateMode, true),
|
|
194
191
|
...cookieDomain !== void 0 && { crossOriginSession: { cookieDomain } }
|
|
195
192
|
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// src/default-tracker.ts
|
|
2
|
+
var current;
|
|
3
|
+
function setDefaultTracker(tracker) {
|
|
4
|
+
current = tracker;
|
|
5
|
+
}
|
|
6
|
+
function getDefaultTracker() {
|
|
7
|
+
return current;
|
|
8
|
+
}
|
|
9
|
+
function isTracker(value) {
|
|
10
|
+
return !!value && typeof value === "object" && typeof value.track === "function" && typeof value.enqueueLowEvent === "function";
|
|
11
|
+
}
|
|
12
|
+
export {
|
|
13
|
+
getDefaultTracker,
|
|
14
|
+
isTracker,
|
|
15
|
+
setDefaultTracker
|
|
16
|
+
};
|
package/dist/analysis/device.mjs
CHANGED
|
@@ -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) {
|
package/dist/analysis/index.mjs
CHANGED
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import { CorbadoTracker } from "./client.mjs";
|
|
3
|
+
import { CorbadoTrackerCore } from "./tracker-core.mjs";
|
|
4
|
+
import { getDefaultTracker, setDefaultTracker } from "./default-tracker.mjs";
|
|
3
5
|
import { SDK_NAME, SDK_VERSION } from "./version.mjs";
|
|
4
6
|
export * from "./types.mjs";
|
|
5
7
|
export * from "./client.mjs";
|
|
8
|
+
export * from "./tracker-core.mjs";
|
|
9
|
+
export * from "./tracker-functions.mjs";
|
|
10
|
+
export * from "./plugins/cookie-storage.mjs";
|
|
11
|
+
export * from "./plugins/cross-origin-session.mjs";
|
|
12
|
+
export * from "./plugins/raw-errors.mjs";
|
|
13
|
+
export * from "./plugins/probes.mjs";
|
|
14
|
+
export * from "./probe-lane.mjs";
|
|
6
15
|
export * from "./transport.mjs";
|
|
7
16
|
export * from "./queue.mjs";
|
|
8
17
|
export * from "./config.mjs";
|
|
@@ -41,78 +50,57 @@ import {
|
|
|
41
50
|
sanitizeRequestOptions,
|
|
42
51
|
sanitizeAssertionResponse
|
|
43
52
|
} from "./utils/webauthn-payloads.mjs";
|
|
44
|
-
var currentTracker;
|
|
45
53
|
function init(options) {
|
|
46
54
|
const tracker = new CorbadoTracker(options);
|
|
47
|
-
|
|
55
|
+
setDefaultTracker(tracker);
|
|
56
|
+
return tracker;
|
|
57
|
+
}
|
|
58
|
+
function initCore(options) {
|
|
59
|
+
const tracker = new CorbadoTrackerCore(options);
|
|
60
|
+
setDefaultTracker(tracker);
|
|
48
61
|
return tracker;
|
|
49
62
|
}
|
|
50
63
|
function getTracker() {
|
|
51
|
-
|
|
64
|
+
const tracker = getDefaultTracker();
|
|
65
|
+
return tracker?.isFullTracker() ? tracker : void 0;
|
|
52
66
|
}
|
|
53
|
-
function
|
|
54
|
-
return
|
|
67
|
+
function getTrackerCore() {
|
|
68
|
+
return getDefaultTracker();
|
|
55
69
|
}
|
|
56
70
|
function getSessionId() {
|
|
57
|
-
return
|
|
58
|
-
}
|
|
59
|
-
function setUser(user) {
|
|
60
|
-
currentTracker?.setUser(user);
|
|
61
|
-
}
|
|
62
|
-
function setFlowTags(tags, options) {
|
|
63
|
-
currentTracker?.setFlowTags(tags, options);
|
|
64
|
-
}
|
|
65
|
-
function setExperiment(key, variant) {
|
|
66
|
-
currentTracker?.setExperiment(key, variant);
|
|
71
|
+
return getDefaultTracker()?.getSessionId();
|
|
67
72
|
}
|
|
68
73
|
function setExperiments(assignments) {
|
|
69
|
-
|
|
70
|
-
}
|
|
71
|
-
function clearExperiment(key) {
|
|
72
|
-
currentTracker?.clearExperiment(key);
|
|
73
|
-
}
|
|
74
|
-
function clearExperiments() {
|
|
75
|
-
currentTracker?.clearExperiments();
|
|
74
|
+
getDefaultTracker()?.setExperiments(assignments);
|
|
76
75
|
}
|
|
77
76
|
function getExperiments() {
|
|
78
|
-
return
|
|
77
|
+
return getDefaultTracker()?.getExperiments() ?? {};
|
|
79
78
|
}
|
|
80
79
|
function telemetry(level, message) {
|
|
81
|
-
|
|
82
|
-
}
|
|
83
|
-
function logInfo(message) {
|
|
84
|
-
currentTracker?.logInfo(message);
|
|
85
|
-
}
|
|
86
|
-
function logError(message) {
|
|
87
|
-
currentTracker?.logError(message);
|
|
80
|
+
getDefaultTracker()?.telemetry(level, message);
|
|
88
81
|
}
|
|
89
82
|
async function destroy() {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
83
|
+
const tracker = getDefaultTracker();
|
|
84
|
+
if (!tracker) return;
|
|
85
|
+
await tracker.destroy();
|
|
86
|
+
setDefaultTracker(void 0);
|
|
93
87
|
}
|
|
94
88
|
export {
|
|
95
89
|
READER_EXPOSURES,
|
|
96
90
|
SDK_NAME,
|
|
97
91
|
SDK_VERSION,
|
|
98
|
-
clearExperiment,
|
|
99
|
-
clearExperiments,
|
|
100
92
|
destroy,
|
|
101
93
|
detectPasswordManagerExtensions,
|
|
102
94
|
getExperiments,
|
|
103
95
|
getSessionId,
|
|
104
96
|
getTracker,
|
|
97
|
+
getTrackerCore,
|
|
105
98
|
init,
|
|
106
|
-
|
|
107
|
-
logInfo,
|
|
108
|
-
resetSession,
|
|
99
|
+
initCore,
|
|
109
100
|
sanitizeAssertionResponse,
|
|
110
101
|
sanitizeCreationOptions,
|
|
111
102
|
sanitizeCreationResponse,
|
|
112
103
|
sanitizeRequestOptions,
|
|
113
|
-
setExperiment,
|
|
114
104
|
setExperiments,
|
|
115
|
-
setFlowTags,
|
|
116
|
-
setUser,
|
|
117
105
|
telemetry
|
|
118
106
|
};
|
|
@@ -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.
|
|
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
|
|
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
|
|
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
|
|
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]
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
200
|
+
ring.push(timestamp);
|
|
178
201
|
if (ring.length > AUTOFILL_STREAK_LENGTH) {
|
|
179
202
|
const graduated = ring.shift();
|
|
180
|
-
this.extendInputBatch(graduated
|
|
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
|
|
192
|
-
captureSeq:
|
|
193
|
-
lastTimestamp: streakLast
|
|
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() {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// src/plugins/cookie-storage.ts
|
|
2
|
+
import { CookieStorage } from "../storage/index.mjs";
|
|
3
|
+
function cookieStorage(options = {}) {
|
|
4
|
+
const cookieDomain = options?.cookieDomain;
|
|
5
|
+
return {
|
|
6
|
+
storage: (logger) => ({ engine: new CookieStorage(logger, cookieDomain), source: "cookie" })
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
export {
|
|
10
|
+
cookieStorage
|
|
11
|
+
};
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
// src/plugins/cross-origin-session.ts
|
|
2
|
+
import { CrossOriginSession, isValidCrossOriginContext } from "../cross-origin-session.mjs";
|
|
3
|
+
import {
|
|
4
|
+
SEQ_STORAGE_KEY,
|
|
5
|
+
SESSION_CONTINUITY_KEY,
|
|
6
|
+
SESSION_STORAGE_KEY
|
|
7
|
+
} from "../session-keys.mjs";
|
|
8
|
+
function crossOriginSession(config) {
|
|
9
|
+
const cookieDomain = config && typeof config === "object" ? config.cookieDomain : void 0;
|
|
10
|
+
return {
|
|
11
|
+
session: (tracker) => typeof cookieDomain === "string" ? CrossOriginBridge.create(tracker, cookieDomain) : void 0
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
var CrossOriginBridge = class _CrossOriginBridge {
|
|
15
|
+
constructor(tracker, cookie) {
|
|
16
|
+
this.tracker = tracker;
|
|
17
|
+
this.cookie = cookie;
|
|
18
|
+
}
|
|
19
|
+
/** The session this load took from the cross-origin cookie, if any (`meta.crossOriginSession`). */
|
|
20
|
+
adoptedId;
|
|
21
|
+
/** The bridge when the cookie is usable here; undefined (feature off) otherwise. Never throws. */
|
|
22
|
+
static create(tracker, cookieDomain) {
|
|
23
|
+
try {
|
|
24
|
+
if (tracker["isTrackingBlocked"]()) {
|
|
25
|
+
return void 0;
|
|
26
|
+
}
|
|
27
|
+
const cookie = CrossOriginSession.create({
|
|
28
|
+
projectId: tracker["options"].projectId,
|
|
29
|
+
cookieDomain,
|
|
30
|
+
logger: tracker.getLogger(),
|
|
31
|
+
inactivityMs: () => tracker["config"].sessionInactivityMs,
|
|
32
|
+
report: (level, message) => tracker["reportTelemetry"](level, message)
|
|
33
|
+
});
|
|
34
|
+
return cookie ? new _CrossOriginBridge(tracker, cookie) : void 0;
|
|
35
|
+
} catch (error) {
|
|
36
|
+
tracker.getLogger().error("Failed to set up the cross-origin session", error);
|
|
37
|
+
return void 0;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
resolve() {
|
|
41
|
+
const tracker = this.tracker;
|
|
42
|
+
const localActivity = this.readLocalSessionActivity();
|
|
43
|
+
tracker["sessionId"] = tracker["resolveSessionId"]();
|
|
44
|
+
this.boot(localActivity);
|
|
45
|
+
return tracker["sessionId"];
|
|
46
|
+
}
|
|
47
|
+
claim(sessionId, seq, now) {
|
|
48
|
+
return this.cookie.claim(sessionId, seq, now);
|
|
49
|
+
}
|
|
50
|
+
/** `meta.crossOriginSession` of a batch of `sessionId`; undefined while the cookie is off on this page. */
|
|
51
|
+
meta(sessionId) {
|
|
52
|
+
if (!this.cookie.isEnabled()) {
|
|
53
|
+
return void 0;
|
|
54
|
+
}
|
|
55
|
+
return sessionId && sessionId === this.adoptedId ? "adopted" : "own";
|
|
56
|
+
}
|
|
57
|
+
reset(sessionId) {
|
|
58
|
+
this.cookie.reset(sessionId);
|
|
59
|
+
}
|
|
60
|
+
stop() {
|
|
61
|
+
this.cookie.stop();
|
|
62
|
+
}
|
|
63
|
+
/** The seq the tracker continues from (persisted counter under continuity). */
|
|
64
|
+
localSeq() {
|
|
65
|
+
const tracker = this.tracker;
|
|
66
|
+
return tracker["config"].sessionContinuity ? Math.max(tracker["seq"], tracker["persistentStorage"].getItem(SEQ_STORAGE_KEY) ?? 0) : tracker["seq"];
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Last activity of the local session records BEFORE this load rewrites them (both paths, by id), so the
|
|
70
|
+
* cross-origin boot can tell a session that was active here from one this load just created.
|
|
71
|
+
*/
|
|
72
|
+
readLocalSessionActivity() {
|
|
73
|
+
const tracker = this.tracker;
|
|
74
|
+
const activity = /* @__PURE__ */ new Map();
|
|
75
|
+
try {
|
|
76
|
+
const continuity = tracker["persistentStorage"].getItem(SESSION_CONTINUITY_KEY);
|
|
77
|
+
if (continuity?.id && typeof continuity.lastActiveAt === "number") {
|
|
78
|
+
activity.set(continuity.id, continuity.lastActiveAt);
|
|
79
|
+
}
|
|
80
|
+
const perTab = tracker["sessionStorage"].getItem(SESSION_STORAGE_KEY);
|
|
81
|
+
if (perTab && typeof perTab === "object" && perTab.id && typeof perTab.lastActiveAt === "number") {
|
|
82
|
+
activity.set(perTab.id, Math.max(activity.get(perTab.id) ?? 0, perTab.lastActiveAt));
|
|
83
|
+
}
|
|
84
|
+
} catch (error) {
|
|
85
|
+
tracker.getLogger().error("Failed to read local session activity", error);
|
|
86
|
+
}
|
|
87
|
+
return activity;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Reconcile the session this load resolved locally with the cross-origin cookie before the first event:
|
|
91
|
+
* adopt the cookie's session (and persist it locally, so reloads and same-origin tabs follow) or keep
|
|
92
|
+
* the local one, and continue the seq at or above the cookie's mark. Never throws.
|
|
93
|
+
*/
|
|
94
|
+
boot(localActivity) {
|
|
95
|
+
const tracker = this.tracker;
|
|
96
|
+
try {
|
|
97
|
+
this.adoptedId = void 0;
|
|
98
|
+
const now = Date.now();
|
|
99
|
+
const continuity = tracker["config"].sessionContinuity;
|
|
100
|
+
const sessionId = tracker["sessionId"];
|
|
101
|
+
const result = this.cookie.boot(
|
|
102
|
+
{
|
|
103
|
+
sessionId,
|
|
104
|
+
lastActiveAt: sessionId && localActivity.get(sessionId) || 0,
|
|
105
|
+
seq: this.localSeq()
|
|
106
|
+
},
|
|
107
|
+
now
|
|
108
|
+
);
|
|
109
|
+
if (!result || !result.sessionId) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (result.adopted) {
|
|
113
|
+
tracker["sessionId"] = result.sessionId;
|
|
114
|
+
this.adoptedId = result.sessionId;
|
|
115
|
+
if (continuity) {
|
|
116
|
+
tracker["persistContinuitySession"](result.sessionId, now);
|
|
117
|
+
} else {
|
|
118
|
+
tracker["persistPerTabSession"](result.sessionId, now);
|
|
119
|
+
}
|
|
120
|
+
tracker.getLogger().debug("Adopted cross-origin session id", result.sessionId);
|
|
121
|
+
}
|
|
122
|
+
tracker["seq"] = result.seq;
|
|
123
|
+
if (continuity) {
|
|
124
|
+
tracker["persistentStorage"].setItem(SEQ_STORAGE_KEY, result.seq);
|
|
125
|
+
}
|
|
126
|
+
} catch (error) {
|
|
127
|
+
tracker.getLogger().error("Failed to apply the cross-origin session", error);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
function setCrossOriginContext(tracker, context) {
|
|
132
|
+
try {
|
|
133
|
+
const bridge = tracker["bridge"];
|
|
134
|
+
if (!(bridge instanceof CrossOriginBridge)) {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
const value = context === null || context === void 0 || context === "" ? void 0 : context;
|
|
138
|
+
if (value !== void 0 && !isValidCrossOriginContext(value)) {
|
|
139
|
+
tracker.getLogger().debug("Ignoring invalid cross-origin context (expected <= 128 chars of [A-Za-z0-9._~-])");
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
const next = bridge.cookie.setContext(tracker["sessionId"], bridge.localSeq(), value);
|
|
143
|
+
if (next === void 0) {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
if (next > tracker["seq"]) {
|
|
147
|
+
tracker["seq"] = next;
|
|
148
|
+
if (tracker["config"].sessionContinuity) {
|
|
149
|
+
tracker["persistentStorage"].setItem(SEQ_STORAGE_KEY, next);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return true;
|
|
153
|
+
} catch (error) {
|
|
154
|
+
tracker.getLogger().error("Failed to set the cross-origin context", error);
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
function getCrossOriginContext(tracker) {
|
|
159
|
+
try {
|
|
160
|
+
const bridge = tracker["bridge"];
|
|
161
|
+
return bridge instanceof CrossOriginBridge ? bridge.cookie.getContext(tracker["sessionId"]) : void 0;
|
|
162
|
+
} catch (error) {
|
|
163
|
+
tracker.getLogger().error("Failed to read the cross-origin context", error);
|
|
164
|
+
return void 0;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
export {
|
|
168
|
+
crossOriginSession,
|
|
169
|
+
getCrossOriginContext,
|
|
170
|
+
setCrossOriginContext
|
|
171
|
+
};
|