@oxvo/browser 7.3.0 → 7.4.15
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/README.md +32 -0
- package/dist/cjs/entry.js +1961 -377
- package/dist/cjs/entry.js.map +1 -1
- package/dist/cjs/index.js +1955 -377
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/main/app/canvas.d.ts +5 -0
- package/dist/cjs/main/app/cross_domain_iframe.d.ts +44 -0
- package/dist/cjs/main/app/debugRedaction.d.ts +11 -0
- package/dist/cjs/main/app/index.d.ts +70 -14
- package/dist/cjs/main/app/messages.gen.d.ts +2 -0
- package/dist/cjs/main/app/session.d.ts +1 -0
- package/dist/cjs/main/entry.d.ts +5 -5
- package/dist/cjs/main/index.d.ts +6 -1
- package/dist/cjs/main/modules/analytics/index.d.ts +5 -0
- package/dist/cjs/main/singleton.d.ts +5 -0
- package/dist/cjs/package.json +1 -0
- package/dist/lib/entry.js +1961 -377
- package/dist/lib/entry.js.map +1 -1
- package/dist/lib/index.js +1955 -377
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/main/app/canvas.d.ts +5 -0
- package/dist/lib/main/app/cross_domain_iframe.d.ts +44 -0
- package/dist/lib/main/app/debugRedaction.d.ts +11 -0
- package/dist/lib/main/app/index.d.ts +70 -14
- package/dist/lib/main/app/messages.gen.d.ts +2 -0
- package/dist/lib/main/app/session.d.ts +1 -0
- package/dist/lib/main/entry.d.ts +5 -5
- package/dist/lib/main/index.d.ts +6 -1
- package/dist/lib/main/modules/analytics/index.d.ts +5 -0
- package/dist/lib/main/singleton.d.ts +5 -0
- package/dist/types/main/app/canvas.d.ts +5 -0
- package/dist/types/main/app/cross_domain_iframe.d.ts +44 -0
- package/dist/types/main/app/debugRedaction.d.ts +11 -0
- package/dist/types/main/app/index.d.ts +70 -14
- package/dist/types/main/app/messages.gen.d.ts +2 -0
- package/dist/types/main/app/session.d.ts +1 -0
- package/dist/types/main/entry.d.ts +5 -5
- package/dist/types/main/index.d.ts +6 -1
- package/dist/types/main/modules/analytics/index.d.ts +5 -0
- package/dist/types/main/singleton.d.ts +5 -0
- package/package.json +5 -4
package/dist/lib/entry.js
CHANGED
|
@@ -530,7 +530,7 @@ function dec2hex(dec) {
|
|
|
530
530
|
return dec.toString(16).padStart(2, '0');
|
|
531
531
|
}
|
|
532
532
|
function generateRandomId(len) {
|
|
533
|
-
const arr = new Uint8Array((len) / 2);
|
|
533
|
+
const arr = new Uint8Array((len || 40) / 2);
|
|
534
534
|
// msCrypto = IE11
|
|
535
535
|
// @ts-ignore
|
|
536
536
|
const safeCrypto = window.crypto || window.msCrypto;
|
|
@@ -539,7 +539,7 @@ function generateRandomId(len) {
|
|
|
539
539
|
return Array.from(arr, dec2hex).join('');
|
|
540
540
|
}
|
|
541
541
|
else {
|
|
542
|
-
return Array.from({ length: len }, () => dec2hex(Math.floor(Math.random() * 16))).join('');
|
|
542
|
+
return Array.from({ length: len || 40 }, () => dec2hex(Math.floor(Math.random() * 16))).join('');
|
|
543
543
|
}
|
|
544
544
|
}
|
|
545
545
|
function inIframe() {
|
|
@@ -736,6 +736,88 @@ function throttleWithTrailing(fn, interval) {
|
|
|
736
736
|
|
|
737
737
|
// Auto-generated, do not edit
|
|
738
738
|
/* eslint-disable */
|
|
739
|
+
const MESSAGE_FIELD_TYPES = {
|
|
740
|
+
0: ['number'],
|
|
741
|
+
4: ['string', 'string', 'number'],
|
|
742
|
+
5: ['number', 'number'],
|
|
743
|
+
6: ['number', 'number'],
|
|
744
|
+
7: [],
|
|
745
|
+
8: ['number', 'number', 'number', 'string', 'boolean'],
|
|
746
|
+
9: ['number', 'number', 'number'],
|
|
747
|
+
10: ['number', 'number', 'number'],
|
|
748
|
+
11: ['number'],
|
|
749
|
+
12: ['number', 'string', 'string'],
|
|
750
|
+
13: ['number', 'string'],
|
|
751
|
+
14: ['number', 'string'],
|
|
752
|
+
16: ['number', 'number', 'number'],
|
|
753
|
+
17: ['number', 'string'],
|
|
754
|
+
18: ['number', 'string', 'number'],
|
|
755
|
+
19: ['number', 'boolean'],
|
|
756
|
+
20: ['number', 'number'],
|
|
757
|
+
21: ['string', 'string', 'string', 'string', 'string', 'number', 'number', 'number'],
|
|
758
|
+
22: ['string', 'string'],
|
|
759
|
+
23: ['number', 'number', 'number', 'number', 'number', 'number', 'number', 'number', 'number'],
|
|
760
|
+
24: ['number', 'number', 'number'],
|
|
761
|
+
27: ['string', 'string'],
|
|
762
|
+
28: ['string'],
|
|
763
|
+
29: ['string'],
|
|
764
|
+
30: ['string', 'string'],
|
|
765
|
+
34: ['number', 'string'],
|
|
766
|
+
35: ['number', 'number', 'number'],
|
|
767
|
+
36: ['number', 'string'],
|
|
768
|
+
40: ['string', 'number', 'string', 'string'],
|
|
769
|
+
41: ['string', 'string'],
|
|
770
|
+
42: ['string'],
|
|
771
|
+
44: ['string', 'string', 'number'],
|
|
772
|
+
45: ['string', 'string'],
|
|
773
|
+
46: ['string', 'string'],
|
|
774
|
+
47: ['string', 'string', 'number'],
|
|
775
|
+
48: ['string', 'string', 'string', 'string', 'number'],
|
|
776
|
+
49: ['number', 'number', 'number', 'number'],
|
|
777
|
+
50: ['number', 'string'],
|
|
778
|
+
51: ['number', 'number', 'number'],
|
|
779
|
+
43: ['string', 'string'],
|
|
780
|
+
52: ['number', 'string', 'string'],
|
|
781
|
+
53: ['number', 'number', 'number', 'number', 'number', 'number', 'string', 'string'],
|
|
782
|
+
54: ['number', 'string'],
|
|
783
|
+
55: ['boolean'],
|
|
784
|
+
57: ['number', 'string', 'string', 'string'],
|
|
785
|
+
58: ['number'],
|
|
786
|
+
60: ['number', 'string', 'string', 'string'],
|
|
787
|
+
61: ['number', 'string', 'string'],
|
|
788
|
+
63: ['string', 'string'],
|
|
789
|
+
64: ['string', 'string'],
|
|
790
|
+
65: ['number', 'number'],
|
|
791
|
+
68: ['number', 'number', 'string', 'string', 'number', 'number'],
|
|
792
|
+
69: ['number', 'number', 'string', 'string'],
|
|
793
|
+
70: ['number', 'number'],
|
|
794
|
+
71: ['number', 'string', 'string'],
|
|
795
|
+
73: ['number', 'string', 'number', 'string'],
|
|
796
|
+
75: ['number', 'number'],
|
|
797
|
+
76: ['number', 'number'],
|
|
798
|
+
77: ['number', 'number'],
|
|
799
|
+
78: ['string', 'string', 'string', 'string'],
|
|
800
|
+
79: ['string', 'string'],
|
|
801
|
+
81: ['number', 'number', 'number', 'number', 'string'],
|
|
802
|
+
83: ['string', 'string', 'string', 'string', 'string', 'number', 'number', 'number', 'number'],
|
|
803
|
+
84: ['string', 'string', 'string', 'number', 'string', 'string'],
|
|
804
|
+
85: ['number', 'number', 'number', 'number', 'number', 'number', 'string', 'string', 'number', 'boolean', 'number', 'number', 'number', 'number', 'number', 'number', 'number'],
|
|
805
|
+
87: ['string', 'number', 'number'],
|
|
806
|
+
89: ['string', 'number', 'number', 'number', 'number', 'string'],
|
|
807
|
+
112: ['number', 'string', 'boolean', 'string', 'number', 'number'],
|
|
808
|
+
113: ['number', 'number', 'string'],
|
|
809
|
+
114: ['number'],
|
|
810
|
+
115: ['number'],
|
|
811
|
+
116: ['number', 'number', 'number', 'number', 'number', 'number', 'string', 'string', 'number', 'boolean'],
|
|
812
|
+
117: ['string'],
|
|
813
|
+
118: ['string'],
|
|
814
|
+
119: ['string', 'number'],
|
|
815
|
+
120: ['number'],
|
|
816
|
+
121: ['string', 'string', 'number', 'number'],
|
|
817
|
+
122: ['string', 'string', 'number', 'string'],
|
|
818
|
+
123: ['string', 'string', 'string', 'string', 'number'],
|
|
819
|
+
124: ['string', 'string'],
|
|
820
|
+
};
|
|
739
821
|
function Timestamp(timestamp) {
|
|
740
822
|
return [
|
|
741
823
|
0 /* Messages.Type.Timestamp */,
|
|
@@ -1418,6 +1500,7 @@ var _Messages = /*#__PURE__*/Object.freeze({
|
|
|
1418
1500
|
JSException: JSException,
|
|
1419
1501
|
LoadFontFace: LoadFontFace,
|
|
1420
1502
|
LongAnimationTask: LongAnimationTask$1,
|
|
1503
|
+
MESSAGE_FIELD_TYPES: MESSAGE_FIELD_TYPES,
|
|
1421
1504
|
Metadata: Metadata,
|
|
1422
1505
|
MobX: MobX,
|
|
1423
1506
|
MouseClick: MouseClick,
|
|
@@ -1798,6 +1881,8 @@ class CanvasRecorder {
|
|
|
1798
1881
|
this.uploadQueue = 0;
|
|
1799
1882
|
this.MAX_CONCURRENT_UPLOADS = 2;
|
|
1800
1883
|
this.MAX_QUEUE_SIZE = 50; // ~500 images max (50 batches × 10 images)
|
|
1884
|
+
this.MAX_UPLOAD_ATTEMPTS = 5;
|
|
1885
|
+
this.MAX_RETRY_DELAY_MS = 10000;
|
|
1801
1886
|
this.pendingBatches = [];
|
|
1802
1887
|
this.isProcessingQueue = false;
|
|
1803
1888
|
/**
|
|
@@ -1938,26 +2023,41 @@ class CanvasRecorder {
|
|
|
1938
2023
|
this.app.debug.warn('Upload queue full, dropping canvas batch');
|
|
1939
2024
|
return;
|
|
1940
2025
|
}
|
|
1941
|
-
this.
|
|
2026
|
+
const token = this.app.session.getSessionSecret();
|
|
2027
|
+
if (!token) {
|
|
2028
|
+
this.app.debug.warn('Canvas batch has no session token');
|
|
2029
|
+
return;
|
|
2030
|
+
}
|
|
2031
|
+
this.pendingBatches.push({ images, canvasId, createdAt, token });
|
|
1942
2032
|
if (!this.isProcessingQueue) {
|
|
1943
|
-
this.processUploadQueue();
|
|
2033
|
+
void this.processUploadQueue();
|
|
1944
2034
|
}
|
|
1945
2035
|
}
|
|
1946
2036
|
async processUploadQueue() {
|
|
2037
|
+
if (this.isProcessingQueue)
|
|
2038
|
+
return;
|
|
1947
2039
|
this.isProcessingQueue = true;
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
2040
|
+
const worker = async () => {
|
|
2041
|
+
while (true) {
|
|
2042
|
+
const batch = this.pendingBatches.shift();
|
|
2043
|
+
if (!batch)
|
|
2044
|
+
return;
|
|
2045
|
+
await this.uploadBatch(batch.images, batch.canvasId, batch.createdAt, batch.token);
|
|
2046
|
+
}
|
|
2047
|
+
};
|
|
2048
|
+
try {
|
|
2049
|
+
await Promise.all(Array.from({ length: this.MAX_CONCURRENT_UPLOADS }, () => worker()));
|
|
2050
|
+
}
|
|
2051
|
+
finally {
|
|
2052
|
+
this.isProcessingQueue = false;
|
|
2053
|
+
// A batch can be appended between the final worker check and the flag
|
|
2054
|
+
// reset. Restart once so that race cannot strand queued frames.
|
|
2055
|
+
if (this.pendingBatches.length > 0) {
|
|
2056
|
+
void this.processUploadQueue();
|
|
1952
2057
|
}
|
|
1953
|
-
const batch = this.pendingBatches.shift();
|
|
1954
|
-
if (!batch)
|
|
1955
|
-
break;
|
|
1956
|
-
this.uploadBatch(batch.images, batch.canvasId, batch.createdAt);
|
|
1957
2058
|
}
|
|
1958
|
-
this.isProcessingQueue = false;
|
|
1959
2059
|
}
|
|
1960
|
-
async uploadBatch(images, canvasId, createdAt) {
|
|
2060
|
+
async uploadBatch(images, canvasId, createdAt, token) {
|
|
1961
2061
|
if (this.options.isDebug) {
|
|
1962
2062
|
const packed = await packFrames(images);
|
|
1963
2063
|
if (packed) {
|
|
@@ -2025,6 +2125,9 @@ class CanvasRecorder {
|
|
|
2025
2125
|
});
|
|
2026
2126
|
}
|
|
2027
2127
|
const initRestart = () => {
|
|
2128
|
+
if (this.app.session.getSessionSecret() !== token) {
|
|
2129
|
+
return;
|
|
2130
|
+
}
|
|
2028
2131
|
this.app.debug.log('Restarting tracker; token expired');
|
|
2029
2132
|
this.app.stop(false);
|
|
2030
2133
|
setTimeout(() => {
|
|
@@ -2034,25 +2137,72 @@ class CanvasRecorder {
|
|
|
2034
2137
|
this.uploadQueue++;
|
|
2035
2138
|
const base = this.app.options.endpoint.replace(/\/$/, '');
|
|
2036
2139
|
const ingestPoint = base.endsWith('/oxvo') ? base : `${base}/oxvo`;
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2140
|
+
try {
|
|
2141
|
+
for (let attempt = 1; attempt <= this.MAX_UPLOAD_ATTEMPTS; attempt++) {
|
|
2142
|
+
try {
|
|
2143
|
+
const response = await fetch(ingestPoint + '/v1/assets', {
|
|
2144
|
+
method: 'POST',
|
|
2145
|
+
headers: {
|
|
2146
|
+
Authorization: `Bearer ${token}`,
|
|
2147
|
+
},
|
|
2148
|
+
body: formData,
|
|
2149
|
+
});
|
|
2150
|
+
if (response.ok)
|
|
2151
|
+
return;
|
|
2152
|
+
if (response.status === 401) {
|
|
2153
|
+
initRestart();
|
|
2154
|
+
return;
|
|
2155
|
+
}
|
|
2156
|
+
if (response.status === 413) {
|
|
2157
|
+
this.app.debug.warn('Canvas batch exceeds the upload size limit');
|
|
2158
|
+
return;
|
|
2159
|
+
}
|
|
2160
|
+
if (!this.isRetryableUploadStatus(response.status)) {
|
|
2161
|
+
this.app.debug.error(`Canvas upload failed with status ${response.status}`);
|
|
2162
|
+
return;
|
|
2163
|
+
}
|
|
2164
|
+
if (attempt === this.MAX_UPLOAD_ATTEMPTS) {
|
|
2165
|
+
this.app.debug.error(`Canvas upload failed after ${attempt} attempts (status ${response.status})`);
|
|
2166
|
+
return;
|
|
2167
|
+
}
|
|
2168
|
+
await this.waitBeforeUploadRetry(response, attempt);
|
|
2169
|
+
}
|
|
2170
|
+
catch (error) {
|
|
2171
|
+
if (attempt === this.MAX_UPLOAD_ATTEMPTS) {
|
|
2172
|
+
this.app.debug.error('Canvas upload failed after retrying', error);
|
|
2173
|
+
return;
|
|
2174
|
+
}
|
|
2175
|
+
await this.waitBeforeUploadRetry(undefined, attempt);
|
|
2176
|
+
}
|
|
2047
2177
|
}
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
.catch((e) => {
|
|
2051
|
-
this.app.debug.error('error saving canvas', e);
|
|
2052
|
-
})
|
|
2053
|
-
.finally(() => {
|
|
2178
|
+
}
|
|
2179
|
+
finally {
|
|
2054
2180
|
this.uploadQueue--;
|
|
2055
|
-
}
|
|
2181
|
+
}
|
|
2182
|
+
}
|
|
2183
|
+
isRetryableUploadStatus(status) {
|
|
2184
|
+
return status === 429 || status === 408 || status >= 500;
|
|
2185
|
+
}
|
|
2186
|
+
async waitBeforeUploadRetry(response, attempt) {
|
|
2187
|
+
const retryAfter = response?.headers.get('Retry-After');
|
|
2188
|
+
let retryDelayMs = 0;
|
|
2189
|
+
if (retryAfter) {
|
|
2190
|
+
const seconds = Number(retryAfter);
|
|
2191
|
+
if (Number.isFinite(seconds) && seconds >= 0) {
|
|
2192
|
+
retryDelayMs = seconds * 1000;
|
|
2193
|
+
}
|
|
2194
|
+
else {
|
|
2195
|
+
const retryAt = Date.parse(retryAfter);
|
|
2196
|
+
if (Number.isFinite(retryAt)) {
|
|
2197
|
+
retryDelayMs = Math.max(0, retryAt - Date.now());
|
|
2198
|
+
}
|
|
2199
|
+
}
|
|
2200
|
+
}
|
|
2201
|
+
if (retryDelayMs === 0) {
|
|
2202
|
+
retryDelayMs = 250 * 2 ** (attempt - 1);
|
|
2203
|
+
}
|
|
2204
|
+
retryDelayMs = Math.min(retryDelayMs, this.MAX_RETRY_DELAY_MS);
|
|
2205
|
+
await new Promise((resolve) => setTimeout(resolve, retryDelayMs));
|
|
2056
2206
|
}
|
|
2057
2207
|
cleanupCanvas(id) {
|
|
2058
2208
|
if (this.snapshots[id]) {
|
|
@@ -2075,8 +2225,7 @@ class CanvasRecorder {
|
|
|
2075
2225
|
}
|
|
2076
2226
|
delete this.snapshots[id];
|
|
2077
2227
|
}
|
|
2078
|
-
|
|
2079
|
-
// Flush remaining images before cleanup
|
|
2228
|
+
flushPendingSnapshots() {
|
|
2080
2229
|
Object.keys(this.snapshots).forEach((idStr) => {
|
|
2081
2230
|
const id = parseInt(idStr, 10);
|
|
2082
2231
|
const snapshot = this.snapshots[id];
|
|
@@ -2085,6 +2234,9 @@ class CanvasRecorder {
|
|
|
2085
2234
|
snapshot.images = [];
|
|
2086
2235
|
}
|
|
2087
2236
|
});
|
|
2237
|
+
}
|
|
2238
|
+
clear() {
|
|
2239
|
+
this.flushPendingSnapshots();
|
|
2088
2240
|
Object.keys(this.snapshots).forEach((idStr) => {
|
|
2089
2241
|
const id = parseInt(idStr, 10);
|
|
2090
2242
|
this.cleanupCanvas(id);
|
|
@@ -2151,6 +2303,505 @@ async function packFrames(images) {
|
|
|
2151
2303
|
return packed;
|
|
2152
2304
|
}
|
|
2153
2305
|
|
|
2306
|
+
const CROSS_DOMAIN_IFRAME_PROTOCOL_VERSION = 1;
|
|
2307
|
+
const SECRET_PATTERN = /^[A-Za-z0-9_-]{32,128}$/;
|
|
2308
|
+
const CONTEXT_PATTERN = /^[A-Za-z0-9_-]{1,128}$/;
|
|
2309
|
+
const MAX_MESSAGE_FIELDS = 24;
|
|
2310
|
+
const MAX_MESSAGE_STRING_LENGTH = 100000;
|
|
2311
|
+
const MAX_BATCH_MESSAGES = 25000;
|
|
2312
|
+
const MAX_BATCH_BYTES = 2 * 1024 * 1024;
|
|
2313
|
+
// Cross-frame batches may contain only recording events. Identity, dictionary,
|
|
2314
|
+
// batch-header, and tab/session control opcodes remain owned by the top-level
|
|
2315
|
+
// tracker.
|
|
2316
|
+
const CROSS_DOMAIN_IFRAME_ALLOWED_MESSAGE_TYPES = new Set([
|
|
2317
|
+
4 /* MType.SetPageLocationDeprecated */,
|
|
2318
|
+
5 /* MType.SetViewportSize */,
|
|
2319
|
+
6 /* MType.SetViewportScroll */,
|
|
2320
|
+
7 /* MType.CreateDocument */,
|
|
2321
|
+
8 /* MType.CreateElementNode */,
|
|
2322
|
+
9 /* MType.CreateTextNode */,
|
|
2323
|
+
10 /* MType.MoveNode */,
|
|
2324
|
+
11 /* MType.RemoveNode */,
|
|
2325
|
+
12 /* MType.SetNodeAttribute */,
|
|
2326
|
+
13 /* MType.RemoveNodeAttribute */,
|
|
2327
|
+
14 /* MType.SetNodeData */,
|
|
2328
|
+
16 /* MType.SetNodeScroll */,
|
|
2329
|
+
17 /* MType.SetInputTarget */,
|
|
2330
|
+
18 /* MType.SetInputValue */,
|
|
2331
|
+
19 /* MType.SetInputChecked */,
|
|
2332
|
+
20 /* MType.MouseMove */,
|
|
2333
|
+
21 /* MType.NetworkRequestDeprecated */,
|
|
2334
|
+
22 /* MType.ConsoleLog */,
|
|
2335
|
+
23 /* MType.PageLoadTiming */,
|
|
2336
|
+
24 /* MType.PageRenderTiming */,
|
|
2337
|
+
27 /* MType.CustomEvent */,
|
|
2338
|
+
36 /* MType.NodeAnimationResult */,
|
|
2339
|
+
40 /* MType.Profiler */,
|
|
2340
|
+
41 /* MType.OTable */,
|
|
2341
|
+
42 /* MType.StateAction */,
|
|
2342
|
+
44 /* MType.ReduxDeprecated */,
|
|
2343
|
+
45 /* MType.Vuex */,
|
|
2344
|
+
46 /* MType.MobX */,
|
|
2345
|
+
47 /* MType.NgRx */,
|
|
2346
|
+
48 /* MType.GraphQLDeprecated */,
|
|
2347
|
+
49 /* MType.PerformanceTrack */,
|
|
2348
|
+
53 /* MType.ResourceTimingDeprecatedDeprecated */,
|
|
2349
|
+
54 /* MType.ConnectionInformation */,
|
|
2350
|
+
55 /* MType.SetPageVisibility */,
|
|
2351
|
+
57 /* MType.LoadFontFace */,
|
|
2352
|
+
58 /* MType.SetNodeFocus */,
|
|
2353
|
+
60 /* MType.SetNodeAttributeURLBased */,
|
|
2354
|
+
61 /* MType.SetCSSDataURLBased */,
|
|
2355
|
+
63 /* MType.TechnicalInfo */,
|
|
2356
|
+
64 /* MType.CustomIssue */,
|
|
2357
|
+
65 /* MType.SetNodeSlot */,
|
|
2358
|
+
68 /* MType.MouseClick */,
|
|
2359
|
+
69 /* MType.MouseClickDeprecated */,
|
|
2360
|
+
70 /* MType.CreateIFrameDocument */,
|
|
2361
|
+
71 /* MType.AdoptedSSReplaceURLBased */,
|
|
2362
|
+
73 /* MType.AdoptedSSInsertRuleURLBased */,
|
|
2363
|
+
75 /* MType.AdoptedSSDeleteRule */,
|
|
2364
|
+
76 /* MType.AdoptedSSAddOwner */,
|
|
2365
|
+
77 /* MType.AdoptedSSRemoveOwner */,
|
|
2366
|
+
78 /* MType.JSException */,
|
|
2367
|
+
79 /* MType.Zustand */,
|
|
2368
|
+
83 /* MType.NetworkRequest */,
|
|
2369
|
+
84 /* MType.WSChannel */,
|
|
2370
|
+
85 /* MType.ResourceTiming */,
|
|
2371
|
+
87 /* MType.Incident */,
|
|
2372
|
+
89 /* MType.LongAnimationTask */,
|
|
2373
|
+
112 /* MType.InputChange */,
|
|
2374
|
+
113 /* MType.SelectionChange */,
|
|
2375
|
+
114 /* MType.MouseThrashing */,
|
|
2376
|
+
115 /* MType.UnbindNodes */,
|
|
2377
|
+
116 /* MType.ResourceTimingDeprecated */,
|
|
2378
|
+
119 /* MType.CanvasNode */,
|
|
2379
|
+
120 /* MType.TagTrigger */,
|
|
2380
|
+
121 /* MType.Redux */,
|
|
2381
|
+
122 /* MType.SetPageLocation */,
|
|
2382
|
+
123 /* MType.GraphQL */,
|
|
2383
|
+
124 /* MType.WebVitals */,
|
|
2384
|
+
]);
|
|
2385
|
+
const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
2386
|
+
const isFiniteNumber = (value) => typeof value === 'number' && Number.isFinite(value);
|
|
2387
|
+
const isSafeIntegerAtLeast = (value, minimum) => typeof value === 'number' && Number.isSafeInteger(value) && value >= minimum;
|
|
2388
|
+
const isBoundedString = (value, maxLength) => typeof value === 'string' && value.length > 0 && value.length <= maxLength;
|
|
2389
|
+
const isCanvasQuality = (value) => value === 'low' || value === 'medium' || value === 'high';
|
|
2390
|
+
function normalizeCrossDomainOrigin(value) {
|
|
2391
|
+
if (typeof value !== 'string' || value.trim() === '' || value.trim() === '*') {
|
|
2392
|
+
return null;
|
|
2393
|
+
}
|
|
2394
|
+
try {
|
|
2395
|
+
const url = new URL(value.trim());
|
|
2396
|
+
if (!['http:', 'https:'].includes(url.protocol) ||
|
|
2397
|
+
url.origin === 'null' ||
|
|
2398
|
+
url.username !== '' ||
|
|
2399
|
+
url.password !== '' ||
|
|
2400
|
+
(url.pathname !== '' && url.pathname !== '/') ||
|
|
2401
|
+
url.search !== '' ||
|
|
2402
|
+
url.hash !== '') {
|
|
2403
|
+
return null;
|
|
2404
|
+
}
|
|
2405
|
+
return url.origin;
|
|
2406
|
+
}
|
|
2407
|
+
catch {
|
|
2408
|
+
return null;
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
2411
|
+
function normalizeCrossDomainOrigins(values) {
|
|
2412
|
+
if (!Array.isArray(values)) {
|
|
2413
|
+
return new Set();
|
|
2414
|
+
}
|
|
2415
|
+
return new Set(values.map(normalizeCrossDomainOrigin).filter((origin) => origin !== null));
|
|
2416
|
+
}
|
|
2417
|
+
function createCrossDomainSecret() {
|
|
2418
|
+
if (!globalThis.crypto?.getRandomValues) {
|
|
2419
|
+
return null;
|
|
2420
|
+
}
|
|
2421
|
+
const bytes = new Uint8Array(24);
|
|
2422
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
2423
|
+
return Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0')).join('');
|
|
2424
|
+
}
|
|
2425
|
+
function parseCrossDomainHandshake(data, expectedLine, expectedProjectKey) {
|
|
2426
|
+
if (!isRecord(data) ||
|
|
2427
|
+
data.line !== expectedLine ||
|
|
2428
|
+
data.protocolVersion !== CROSS_DOMAIN_IFRAME_PROTOCOL_VERSION ||
|
|
2429
|
+
data.projectKey !== expectedProjectKey ||
|
|
2430
|
+
!isBoundedString(data.context, 128) ||
|
|
2431
|
+
!CONTEXT_PATTERN.test(data.context) ||
|
|
2432
|
+
!isBoundedString(data.nonce, 128) ||
|
|
2433
|
+
!SECRET_PATTERN.test(data.nonce)) {
|
|
2434
|
+
return null;
|
|
2435
|
+
}
|
|
2436
|
+
return {
|
|
2437
|
+
protocolVersion: CROSS_DOMAIN_IFRAME_PROTOCOL_VERSION,
|
|
2438
|
+
context: data.context,
|
|
2439
|
+
nonce: data.nonce,
|
|
2440
|
+
projectKey: expectedProjectKey,
|
|
2441
|
+
};
|
|
2442
|
+
}
|
|
2443
|
+
function matchesCrossDomainEnvelope(data, expectedLine, expected) {
|
|
2444
|
+
return (isRecord(data) &&
|
|
2445
|
+
data.line === expectedLine &&
|
|
2446
|
+
data.protocolVersion === CROSS_DOMAIN_IFRAME_PROTOCOL_VERSION &&
|
|
2447
|
+
data.context === expected.context &&
|
|
2448
|
+
data.nonce === expected.nonce &&
|
|
2449
|
+
data.capability === expected.capability);
|
|
2450
|
+
}
|
|
2451
|
+
function parseCrossDomainFrameInit(data, expectedLine, expected) {
|
|
2452
|
+
if (!isRecord(data) ||
|
|
2453
|
+
data.line !== expectedLine ||
|
|
2454
|
+
data.protocolVersion !== CROSS_DOMAIN_IFRAME_PROTOCOL_VERSION ||
|
|
2455
|
+
data.context !== expected.context ||
|
|
2456
|
+
data.nonce !== expected.nonce ||
|
|
2457
|
+
!isBoundedString(data.capability, 128) ||
|
|
2458
|
+
!SECRET_PATTERN.test(data.capability) ||
|
|
2459
|
+
!isSafeIntegerAtLeast(data.id, 0) ||
|
|
2460
|
+
!isSafeIntegerAtLeast(data.frameOrderNumber, 1) ||
|
|
2461
|
+
!isSafeIntegerAtLeast(data.frameLevel, 0) ||
|
|
2462
|
+
!isSafeIntegerAtLeast(data.capabilityExpiresAt, 1) ||
|
|
2463
|
+
data.capabilityExpiresAt <= Date.now() ||
|
|
2464
|
+
!isRecord(data.session)) {
|
|
2465
|
+
return null;
|
|
2466
|
+
}
|
|
2467
|
+
const session = data.session;
|
|
2468
|
+
if (!isBoundedString(session.sessionID, 256) ||
|
|
2469
|
+
!isFiniteNumber(session.startedAt) ||
|
|
2470
|
+
!isFiniteNumber(session.delayMs) ||
|
|
2471
|
+
typeof session.userUUID !== 'string' ||
|
|
2472
|
+
session.userUUID.length > 256) {
|
|
2473
|
+
return null;
|
|
2474
|
+
}
|
|
2475
|
+
let projectID;
|
|
2476
|
+
if (session.projectID !== undefined) {
|
|
2477
|
+
if (!isBoundedString(session.projectID, 256)) {
|
|
2478
|
+
return null;
|
|
2479
|
+
}
|
|
2480
|
+
projectID = session.projectID;
|
|
2481
|
+
}
|
|
2482
|
+
const canvas = parseCanvasConfig(data.canvas);
|
|
2483
|
+
if (data.canvas !== undefined && canvas === null) {
|
|
2484
|
+
return null;
|
|
2485
|
+
}
|
|
2486
|
+
return {
|
|
2487
|
+
protocolVersion: CROSS_DOMAIN_IFRAME_PROTOCOL_VERSION,
|
|
2488
|
+
context: data.context,
|
|
2489
|
+
nonce: data.nonce,
|
|
2490
|
+
capability: data.capability,
|
|
2491
|
+
id: data.id,
|
|
2492
|
+
frameOrderNumber: data.frameOrderNumber,
|
|
2493
|
+
frameLevel: data.frameLevel,
|
|
2494
|
+
capabilityExpiresAt: data.capabilityExpiresAt,
|
|
2495
|
+
session: {
|
|
2496
|
+
sessionID: session.sessionID,
|
|
2497
|
+
projectID,
|
|
2498
|
+
startedAt: session.startedAt,
|
|
2499
|
+
delayMs: session.delayMs,
|
|
2500
|
+
userUUID: session.userUUID,
|
|
2501
|
+
},
|
|
2502
|
+
...(canvas ? { canvas } : {}),
|
|
2503
|
+
};
|
|
2504
|
+
}
|
|
2505
|
+
function parseCanvasConfig(value) {
|
|
2506
|
+
if (value === undefined) {
|
|
2507
|
+
return null;
|
|
2508
|
+
}
|
|
2509
|
+
if (!isRecord(value) || typeof value.enabled !== 'boolean') {
|
|
2510
|
+
return null;
|
|
2511
|
+
}
|
|
2512
|
+
let quality;
|
|
2513
|
+
if (value.quality !== undefined) {
|
|
2514
|
+
if (!isCanvasQuality(value.quality)) {
|
|
2515
|
+
return null;
|
|
2516
|
+
}
|
|
2517
|
+
quality = value.quality;
|
|
2518
|
+
}
|
|
2519
|
+
let fps;
|
|
2520
|
+
if (value.fps !== undefined) {
|
|
2521
|
+
if (!isFiniteNumber(value.fps)) {
|
|
2522
|
+
return null;
|
|
2523
|
+
}
|
|
2524
|
+
fps = value.fps;
|
|
2525
|
+
}
|
|
2526
|
+
let framesSupport;
|
|
2527
|
+
if (value.framesSupport !== undefined) {
|
|
2528
|
+
if (typeof value.framesSupport !== 'boolean') {
|
|
2529
|
+
return null;
|
|
2530
|
+
}
|
|
2531
|
+
framesSupport = value.framesSupport;
|
|
2532
|
+
}
|
|
2533
|
+
return {
|
|
2534
|
+
enabled: value.enabled,
|
|
2535
|
+
quality,
|
|
2536
|
+
fps,
|
|
2537
|
+
framesSupport,
|
|
2538
|
+
};
|
|
2539
|
+
}
|
|
2540
|
+
function parseCrossDomainIframeBatch(messages) {
|
|
2541
|
+
if (!Array.isArray(messages) || messages.length > MAX_BATCH_MESSAGES) {
|
|
2542
|
+
return null;
|
|
2543
|
+
}
|
|
2544
|
+
let bytes = 0;
|
|
2545
|
+
for (const message of messages) {
|
|
2546
|
+
if (!Array.isArray(message) || message.length < 1 || message.length > MAX_MESSAGE_FIELDS) {
|
|
2547
|
+
return null;
|
|
2548
|
+
}
|
|
2549
|
+
const messageType = message[0];
|
|
2550
|
+
if (typeof messageType !== 'number' ||
|
|
2551
|
+
!Number.isInteger(messageType) ||
|
|
2552
|
+
!CROSS_DOMAIN_IFRAME_ALLOWED_MESSAGE_TYPES.has(messageType)) {
|
|
2553
|
+
return null;
|
|
2554
|
+
}
|
|
2555
|
+
const expectedFieldTypes = MESSAGE_FIELD_TYPES[messageType];
|
|
2556
|
+
if (expectedFieldTypes === undefined || message.length !== expectedFieldTypes.length + 1) {
|
|
2557
|
+
return null;
|
|
2558
|
+
}
|
|
2559
|
+
bytes += 8;
|
|
2560
|
+
for (let fieldIndex = 0; fieldIndex < expectedFieldTypes.length; fieldIndex++) {
|
|
2561
|
+
const field = message[fieldIndex + 1];
|
|
2562
|
+
if (typeof field !== expectedFieldTypes[fieldIndex]) {
|
|
2563
|
+
return null;
|
|
2564
|
+
}
|
|
2565
|
+
if (typeof field === 'string') {
|
|
2566
|
+
if (field.length > MAX_MESSAGE_STRING_LENGTH) {
|
|
2567
|
+
return null;
|
|
2568
|
+
}
|
|
2569
|
+
bytes += field.length * 3;
|
|
2570
|
+
}
|
|
2571
|
+
else if (typeof field === 'number') {
|
|
2572
|
+
if (!Number.isFinite(field)) {
|
|
2573
|
+
return null;
|
|
2574
|
+
}
|
|
2575
|
+
bytes += 8;
|
|
2576
|
+
}
|
|
2577
|
+
else if (typeof field === 'boolean') {
|
|
2578
|
+
bytes += 1;
|
|
2579
|
+
}
|
|
2580
|
+
else {
|
|
2581
|
+
return null;
|
|
2582
|
+
}
|
|
2583
|
+
if (bytes > MAX_BATCH_BYTES) {
|
|
2584
|
+
return null;
|
|
2585
|
+
}
|
|
2586
|
+
}
|
|
2587
|
+
}
|
|
2588
|
+
return { messages: messages, bytes };
|
|
2589
|
+
}
|
|
2590
|
+
|
|
2591
|
+
const REDACTED_DEBUG_VALUE = '[REDACTED]';
|
|
2592
|
+
const TRUNCATED_DEBUG_VALUE = '[TRUNCATED]';
|
|
2593
|
+
const MAX_DEBUG_TEXT_LENGTH = 2048;
|
|
2594
|
+
const MAX_DEBUG_CREDENTIAL_LOOKAHEAD = 512;
|
|
2595
|
+
const MAX_DEBUG_REDACTION_DEPTH = 5;
|
|
2596
|
+
const MAX_DEBUG_REDACTION_ITEMS = 50;
|
|
2597
|
+
const DEBUG_FIELD_VALUE_PATTERN = /(["']?)([ \t]*[a-z0-9_-]{1,64}[ \t]*)\1(\s*[:=]\s*)("[^"\r\n]*"|'[^'\r\n]*'|[^\s,;&{}\[\]()\r\n]+)/gim;
|
|
2598
|
+
const DEBUG_AUTHORIZATION_FIELD_PATTERN = /(["']?)((?:proxy[-_]?authorization|authorization)(?:[-_]?header)?)\1(\s*[:=]\s*)("[^"\r\n]*"|'[^'\r\n]*'|\[(?:REDACTED|TRUNCATED)\]|[^,;&}\]\)\r\n]+)/gi;
|
|
2599
|
+
const DEBUG_COOKIE_FIELD_PATTERN = /(["']?)((?:set[-_]?cookie|cookies?)(?:[-_]?header)?)\1(\s*[:=]\s*)("[^"\r\n]*"|'[^'\r\n]*'|\[(?:REDACTED|TRUNCATED)\]|[^\r\n]+)/gi;
|
|
2600
|
+
const DEBUG_AUTH_SCHEME_VALUE_PATTERN = /(\b(?:ApiKey|Basic|Bearer|Digest|Negotiate)\s+)[a-z0-9._~+/=-]+/gi;
|
|
2601
|
+
const DEBUG_TOKEN_AUTH_SCHEME_VALUE_PATTERN = /(\bToken\s+)[a-z0-9._~+/=-]+/g;
|
|
2602
|
+
const DEBUG_JWT_VALUE_PATTERN = /\beyJ[a-z0-9_-]{5,}\.[a-z0-9_-]{5,}\.[a-z0-9_-]{5,}\b/gi;
|
|
2603
|
+
const DEBUG_URL_CREDENTIAL_PATTERN = /(\bhttps?:\/\/)([^\s/:@]+):([^\s/@]+)@/gi;
|
|
2604
|
+
const DEBUG_ENCODED_FIELD_VALUE_PATTERN = /([a-z0-9_-]{0,48}(?:token|secret|grant|credential|api[-_]?key|private[-_]?key|signing[-_]?key|authorization|cookie))(%3a|%3d)(?:%22|%27)?[a-z0-9._~+/%=-]+/gi;
|
|
2605
|
+
const DEBUG_PROVIDER_CREDENTIAL_PATTERNS = [
|
|
2606
|
+
/sk-(?:(?:proj|svcacct|ant)-)?[a-z0-9_-]{8,}/gi,
|
|
2607
|
+
/AIza[a-z0-9_-]{20,}/gi,
|
|
2608
|
+
/(?:AKIA|ASIA)[A-Z0-9]{16}/g,
|
|
2609
|
+
/gh[pousr]_[a-z0-9]{20,}/gi,
|
|
2610
|
+
/xox[a-z]-[a-z0-9-]{10,}/gi,
|
|
2611
|
+
];
|
|
2612
|
+
const DEBUG_PRIVATE_KEY_PATTERN = /-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----[\s\S]*?(?:-----END [A-Z0-9 ]*PRIVATE KEY-----|$)/g;
|
|
2613
|
+
const PROVIDER_KEY_PREFIXES = [
|
|
2614
|
+
'anthropic',
|
|
2615
|
+
'azureopenai',
|
|
2616
|
+
'cohere',
|
|
2617
|
+
'deepseek',
|
|
2618
|
+
'elevenlabs',
|
|
2619
|
+
'gemini',
|
|
2620
|
+
'google',
|
|
2621
|
+
'groq',
|
|
2622
|
+
'mistral',
|
|
2623
|
+
'openai',
|
|
2624
|
+
'openrouter',
|
|
2625
|
+
'perplexity',
|
|
2626
|
+
];
|
|
2627
|
+
const isSensitiveDebugField = (key) => {
|
|
2628
|
+
const normalized = key.replace(/[^a-z0-9]/gi, '').toLowerCase();
|
|
2629
|
+
return (normalized === 'auth' ||
|
|
2630
|
+
normalized.includes('authorization') ||
|
|
2631
|
+
normalized.includes('bearer') ||
|
|
2632
|
+
normalized.includes('cookie') ||
|
|
2633
|
+
normalized === 'sessionhash' ||
|
|
2634
|
+
normalized === 'oxvolinkageassertion' ||
|
|
2635
|
+
normalized === 'xoxvosessionstrace' ||
|
|
2636
|
+
normalized.includes('password') ||
|
|
2637
|
+
normalized === 'privatekey' ||
|
|
2638
|
+
normalized === 'privatekeyid' ||
|
|
2639
|
+
normalized === 'signingkey' ||
|
|
2640
|
+
normalized === 'jwt' ||
|
|
2641
|
+
normalized.endsWith('jwt') ||
|
|
2642
|
+
normalized.endsWith('token') ||
|
|
2643
|
+
normalized.endsWith('grant') ||
|
|
2644
|
+
normalized.includes('secret') ||
|
|
2645
|
+
normalized.endsWith('apikey') ||
|
|
2646
|
+
normalized.endsWith('accesskey') ||
|
|
2647
|
+
normalized.endsWith('accesskeyid') ||
|
|
2648
|
+
normalized.endsWith('subscriptionkey') ||
|
|
2649
|
+
normalized.endsWith('credential') ||
|
|
2650
|
+
normalized.endsWith('credentials') ||
|
|
2651
|
+
(normalized.includes('provider') && normalized.endsWith('key')) ||
|
|
2652
|
+
(normalized.endsWith('key') &&
|
|
2653
|
+
PROVIDER_KEY_PREFIXES.some((provider) => normalized.startsWith(provider))));
|
|
2654
|
+
};
|
|
2655
|
+
const redactedFieldValue = (rawValue) => {
|
|
2656
|
+
const valueQuote = rawValue[0];
|
|
2657
|
+
return valueQuote === '"' || valueQuote === "'"
|
|
2658
|
+
? `${valueQuote}${REDACTED_DEBUG_VALUE}${valueQuote}`
|
|
2659
|
+
: REDACTED_DEBUG_VALUE;
|
|
2660
|
+
};
|
|
2661
|
+
const redactDebugTextToLength = (value, maxOutputLength) => {
|
|
2662
|
+
const truncated = value.length > maxOutputLength;
|
|
2663
|
+
let redacted = value
|
|
2664
|
+
.slice(0, maxOutputLength + MAX_DEBUG_CREDENTIAL_LOOKAHEAD)
|
|
2665
|
+
.replace(DEBUG_AUTHORIZATION_FIELD_PATTERN, (_match, keyQuote, key, separator, rawValue) => `${keyQuote}${key}${keyQuote}${separator}${redactedFieldValue(rawValue)}`)
|
|
2666
|
+
.replace(DEBUG_COOKIE_FIELD_PATTERN, (_match, keyQuote, key, separator, rawValue) => `${keyQuote}${key}${keyQuote}${separator}${redactedFieldValue(rawValue)}`)
|
|
2667
|
+
.replace(DEBUG_AUTH_SCHEME_VALUE_PATTERN, `$1${REDACTED_DEBUG_VALUE}`)
|
|
2668
|
+
.replace(DEBUG_TOKEN_AUTH_SCHEME_VALUE_PATTERN, `$1${REDACTED_DEBUG_VALUE}`)
|
|
2669
|
+
.replace(DEBUG_FIELD_VALUE_PATTERN, (match, keyQuote, key, separator, rawValue) => {
|
|
2670
|
+
if (!isSensitiveDebugField(key)) {
|
|
2671
|
+
return match;
|
|
2672
|
+
}
|
|
2673
|
+
return `${keyQuote}${key}${keyQuote}${separator}${redactedFieldValue(rawValue)}`;
|
|
2674
|
+
})
|
|
2675
|
+
.replace(DEBUG_ENCODED_FIELD_VALUE_PATTERN, (_match, key, separator) => `${key}${separator}${REDACTED_DEBUG_VALUE}`)
|
|
2676
|
+
.replace(DEBUG_URL_CREDENTIAL_PATTERN, `$1${REDACTED_DEBUG_VALUE}:${REDACTED_DEBUG_VALUE}@`)
|
|
2677
|
+
.replace(DEBUG_JWT_VALUE_PATTERN, REDACTED_DEBUG_VALUE)
|
|
2678
|
+
.replace(DEBUG_PRIVATE_KEY_PATTERN, REDACTED_DEBUG_VALUE);
|
|
2679
|
+
for (const credentialPattern of DEBUG_PROVIDER_CREDENTIAL_PATTERNS) {
|
|
2680
|
+
redacted = redacted.replace(credentialPattern, REDACTED_DEBUG_VALUE);
|
|
2681
|
+
}
|
|
2682
|
+
const bounded = redacted.slice(0, maxOutputLength);
|
|
2683
|
+
return truncated ? `${bounded} ${TRUNCATED_DEBUG_VALUE}` : bounded;
|
|
2684
|
+
};
|
|
2685
|
+
const redactDebugText = (value) => redactDebugTextToLength(value, MAX_DEBUG_TEXT_LENGTH);
|
|
2686
|
+
const createDebugRedactionState = () => ({
|
|
2687
|
+
seen: new WeakSet(),
|
|
2688
|
+
remainingItems: MAX_DEBUG_REDACTION_ITEMS,
|
|
2689
|
+
remainingText: MAX_DEBUG_TEXT_LENGTH,
|
|
2690
|
+
});
|
|
2691
|
+
const redactDebugTextWithBudget = (value, state) => {
|
|
2692
|
+
if (state.remainingText <= 0) {
|
|
2693
|
+
return TRUNCATED_DEBUG_VALUE;
|
|
2694
|
+
}
|
|
2695
|
+
const permittedLength = Math.min(value.length, state.remainingText);
|
|
2696
|
+
state.remainingText -= permittedLength;
|
|
2697
|
+
return redactDebugTextToLength(value, permittedLength);
|
|
2698
|
+
};
|
|
2699
|
+
const redactDebugValue = (value, state = createDebugRedactionState(), depth = 0) => {
|
|
2700
|
+
if (typeof value === 'string') {
|
|
2701
|
+
return redactDebugTextWithBudget(value, state);
|
|
2702
|
+
}
|
|
2703
|
+
if (value instanceof Error) {
|
|
2704
|
+
const redacted = new Error(redactDebugTextWithBudget(value.message, state));
|
|
2705
|
+
redacted.name = redactDebugTextWithBudget(value.name, state);
|
|
2706
|
+
if (value.stack) {
|
|
2707
|
+
redacted.stack = redactDebugTextWithBudget(value.stack, state);
|
|
2708
|
+
}
|
|
2709
|
+
return redacted;
|
|
2710
|
+
}
|
|
2711
|
+
if (value === null || typeof value === 'number' || typeof value === 'boolean') {
|
|
2712
|
+
return value;
|
|
2713
|
+
}
|
|
2714
|
+
if (typeof value === 'undefined') {
|
|
2715
|
+
return undefined;
|
|
2716
|
+
}
|
|
2717
|
+
if (typeof value === 'bigint' || typeof value === 'symbol') {
|
|
2718
|
+
return String(value);
|
|
2719
|
+
}
|
|
2720
|
+
if (typeof value === 'function') {
|
|
2721
|
+
return '[Function]';
|
|
2722
|
+
}
|
|
2723
|
+
const objectValue = value;
|
|
2724
|
+
if (depth >= MAX_DEBUG_REDACTION_DEPTH || state.remainingItems <= 0) {
|
|
2725
|
+
return TRUNCATED_DEBUG_VALUE;
|
|
2726
|
+
}
|
|
2727
|
+
if (state.seen.has(objectValue)) {
|
|
2728
|
+
return '[Circular]';
|
|
2729
|
+
}
|
|
2730
|
+
state.seen.add(objectValue);
|
|
2731
|
+
if (Array.isArray(value)) {
|
|
2732
|
+
const count = Math.min(value.length, state.remainingItems);
|
|
2733
|
+
state.remainingItems -= count;
|
|
2734
|
+
const redacted = value.slice(0, count).map((entry) => redactDebugValue(entry, state, depth + 1));
|
|
2735
|
+
if (count < value.length) {
|
|
2736
|
+
redacted.push(TRUNCATED_DEBUG_VALUE);
|
|
2737
|
+
}
|
|
2738
|
+
return redacted;
|
|
2739
|
+
}
|
|
2740
|
+
const redacted = {};
|
|
2741
|
+
let allKeys;
|
|
2742
|
+
try {
|
|
2743
|
+
allKeys = Object.keys(objectValue);
|
|
2744
|
+
}
|
|
2745
|
+
catch {
|
|
2746
|
+
return '[Unavailable]';
|
|
2747
|
+
}
|
|
2748
|
+
const keys = allKeys.slice(0, state.remainingItems);
|
|
2749
|
+
state.remainingItems -= keys.length;
|
|
2750
|
+
for (const key of keys) {
|
|
2751
|
+
if (isSensitiveDebugField(key)) {
|
|
2752
|
+
redacted[key] = REDACTED_DEBUG_VALUE;
|
|
2753
|
+
continue;
|
|
2754
|
+
}
|
|
2755
|
+
try {
|
|
2756
|
+
redacted[key] = redactDebugValue(value[key], state, depth + 1);
|
|
2757
|
+
}
|
|
2758
|
+
catch {
|
|
2759
|
+
redacted[key] = '[Unavailable]';
|
|
2760
|
+
}
|
|
2761
|
+
}
|
|
2762
|
+
if (keys.length < allKeys.length) {
|
|
2763
|
+
redacted.__truncated__ = TRUNCATED_DEBUG_VALUE;
|
|
2764
|
+
}
|
|
2765
|
+
return redacted;
|
|
2766
|
+
};
|
|
2767
|
+
const redactDebugArgs = (values) => {
|
|
2768
|
+
const state = createDebugRedactionState();
|
|
2769
|
+
return values.map((value) => redactDebugValue(value, state));
|
|
2770
|
+
};
|
|
2771
|
+
const debugValueText = (value) => {
|
|
2772
|
+
if (value instanceof Error) {
|
|
2773
|
+
return `${value.name}: ${value.message}`;
|
|
2774
|
+
}
|
|
2775
|
+
if (typeof value === 'string') {
|
|
2776
|
+
return value;
|
|
2777
|
+
}
|
|
2778
|
+
let serialized;
|
|
2779
|
+
try {
|
|
2780
|
+
serialized = JSON.stringify(value);
|
|
2781
|
+
}
|
|
2782
|
+
catch {
|
|
2783
|
+
return '[Unavailable]';
|
|
2784
|
+
}
|
|
2785
|
+
if (serialized === undefined) {
|
|
2786
|
+
return String(value);
|
|
2787
|
+
}
|
|
2788
|
+
return serialized.length > MAX_DEBUG_TEXT_LENGTH
|
|
2789
|
+
? `${serialized.slice(0, MAX_DEBUG_TEXT_LENGTH)} ${TRUNCATED_DEBUG_VALUE}`
|
|
2790
|
+
: serialized;
|
|
2791
|
+
};
|
|
2792
|
+
const redactedErrorFrom = (value, fallback, knownSecrets = []) => {
|
|
2793
|
+
const uniqueKnownSecrets = [
|
|
2794
|
+
...new Set(knownSecrets.filter((secret) => !!secret)),
|
|
2795
|
+
].sort((left, right) => right.length - left.length);
|
|
2796
|
+
const redacted = value instanceof Error || typeof value === 'string' ? value : redactDebugValue(value);
|
|
2797
|
+
let message = redacted instanceof Error ? redacted.message.trim() : debugValueText(redacted).trim();
|
|
2798
|
+
for (const secret of uniqueKnownSecrets) {
|
|
2799
|
+
message = message.split(secret).join(REDACTED_DEBUG_VALUE);
|
|
2800
|
+
}
|
|
2801
|
+
message = redactDebugText(message);
|
|
2802
|
+
return new Error(message.length > 0 ? message : fallback);
|
|
2803
|
+
};
|
|
2804
|
+
|
|
2154
2805
|
const LogLevel = {
|
|
2155
2806
|
Verbose: 5,
|
|
2156
2807
|
Log: 4,
|
|
@@ -2165,26 +2816,22 @@ class Logger {
|
|
|
2165
2816
|
};
|
|
2166
2817
|
this.info = (...args) => {
|
|
2167
2818
|
if (this.shouldLog(LogLevel.Verbose)) {
|
|
2168
|
-
|
|
2169
|
-
console.info(...args);
|
|
2819
|
+
console.info(...redactDebugArgs(args));
|
|
2170
2820
|
}
|
|
2171
2821
|
};
|
|
2172
2822
|
this.log = (...args) => {
|
|
2173
2823
|
if (this.shouldLog(LogLevel.Log)) {
|
|
2174
|
-
|
|
2175
|
-
console.log(...args);
|
|
2824
|
+
console.log(...redactDebugArgs(args));
|
|
2176
2825
|
}
|
|
2177
2826
|
};
|
|
2178
2827
|
this.warn = (...args) => {
|
|
2179
2828
|
if (this.shouldLog(LogLevel.Warnings)) {
|
|
2180
|
-
|
|
2181
|
-
console.warn(...args);
|
|
2829
|
+
console.warn(...redactDebugArgs(args));
|
|
2182
2830
|
}
|
|
2183
2831
|
};
|
|
2184
2832
|
this.error = (...args) => {
|
|
2185
2833
|
if (this.shouldLog(LogLevel.Errors)) {
|
|
2186
|
-
|
|
2187
|
-
console.error(...args);
|
|
2834
|
+
console.error(...redactDebugArgs(args));
|
|
2188
2835
|
}
|
|
2189
2836
|
};
|
|
2190
2837
|
this.level = debugLevel;
|
|
@@ -3954,6 +4601,10 @@ class Session {
|
|
|
3954
4601
|
this.token = `${token}${tokenSeparator}${projectKey}`;
|
|
3955
4602
|
this.app.sessionStorage.setItem(this.options.session_token_key, `${token}${tokenSeparator}${projectKey}`);
|
|
3956
4603
|
};
|
|
4604
|
+
this.clearSessionSecret = () => {
|
|
4605
|
+
this.token = undefined;
|
|
4606
|
+
this.app.sessionStorage.removeItem(this.options.session_token_key);
|
|
4607
|
+
};
|
|
3957
4608
|
this.app = params.app;
|
|
3958
4609
|
this.options = params.options;
|
|
3959
4610
|
this.createTabId();
|
|
@@ -4050,7 +4701,7 @@ class Session {
|
|
|
4050
4701
|
};
|
|
4051
4702
|
}
|
|
4052
4703
|
reset() {
|
|
4053
|
-
this.
|
|
4704
|
+
this.clearSessionSecret();
|
|
4054
4705
|
this.metadata = {};
|
|
4055
4706
|
this.userID = null;
|
|
4056
4707
|
this.sessionID = undefined;
|
|
@@ -4108,10 +4759,67 @@ class Ticker {
|
|
|
4108
4759
|
* this value is injected during build time via rollup
|
|
4109
4760
|
* */
|
|
4110
4761
|
// @ts-ignore
|
|
4111
|
-
const workerBodyFn = "!function(){\"use strict\";class t{constructor(t,s,i,e=10,n=250,h,r){this.onUnauthorised=s,this.onFailure=i,this.MAX_ATTEMPTS_COUNT=e,this.ATTEMPT_TIMEOUT=n,this.onCompress=h,this.pageNo=r,this.attemptsCount=0,this.busy=!1,this.queue=[],this.token=null,this.lastBatchNum=0,this.inflightKeepaliveBytes=0,this.ingestURL=t+\"/v1/events\",this.isCompressing=void 0!==h}getQueueStatus(){return 0===this.queue.length&&!this.busy}authorise(t){this.token=t,this.busy||this.sendNext()}push(t,s=\"player\"){if(this.busy||!this.token)this.queue.push({batch:t,dataType:s});else if(this.busy=!0,this.isCompressing&&this.onCompress)this.onCompress(t,s);else{const i=++this.lastBatchNum;this.sendBatch(t,!1,i,s)}}sendNext(){const t=this.queue.shift();if(t)if(this.busy=!0,this.isCompressing&&this.onCompress)this.onCompress(t.batch,t.dataType);else{const s=++this.lastBatchNum;this.sendBatch(t.batch,!1,s,t.dataType)}else this.busy=!1}retry(t,s,i,e=\"player\"){if(this.attemptsCount>=this.MAX_ATTEMPTS_COUNT)return void this.onFailure(`Failed to send batch after ${this.attemptsCount} attempts.`);this.attemptsCount++;const n=new Uint8Array(t);setTimeout((()=>this.sendBatch(n,s,i,e)),this.ATTEMPT_TIMEOUT*this.attemptsCount)}sendBatch(t,s,i,e=\"player\"){var n;if(0===t.length)return console.error(\"OxvoSessions: refusing to send 0-byte batch.\",{batchNum:i,dataType:e,isCompressed:s,batch:t}),this.attemptsCount=0,void this.sendNext();const h=null==i?void 0:i.toString().replace(/^([^_]+)_([^_]+).*/,\"$1_$2_$3\");this.busy=!0;const r={Authorization:`Bearer ${this.token}`,DataType:e};if(s&&(r[\"Content-Encoding\"]=\"gzip\"),null===this.token)return void setTimeout((()=>{this.sendBatch(t,s,`${null!=i?i:\"noBatchNum\"}_newToken`,e)}),500);const a=t.length<65536&&this.inflightKeepaliveBytes+t.length<=65536;a&&(this.inflightKeepaliveBytes+=t.length);const o=()=>{a&&(this.inflightKeepaliveBytes-=t.length)},u=`${null!==(n=this.pageNo)&&void 0!==n?n:\"noPageNum\"}_${null!=h?h:\"noBatchNum\"}`;fetch(`${this.ingestURL}?seq=${u}&keepalive=${a?\"yes\":\"no\"}`,{body:t,method:\"POST\",headers:r,keepalive:a}).then((n=>{var h;if(o(),null===(h=n.body)||void 0===h||h.cancel().catch((()=>{})),401===n.status)return this.busy=!1,void this.onUnauthorised();n.status>=400?this.retry(t,s,`${null!=i?i:\"noBatchNum\"}_network:${n.status}`,e):(this.attemptsCount=0,this.sendNext())})).catch((n=>{o(),console.warn(\"OxvoSessions:\",n),this.retry(t,s,`${null!=i?i:\"noBatchNum\"}_reject:${n.message}`,e)}))}sendCompressed(t,s=\"player\"){const i=++this.lastBatchNum;this.sendBatch(t,!0,i,s)}sendUncompressed(t,s=\"player\"){const i=++this.lastBatchNum;this.sendBatch(t,!1,i,s)}clean(){this.sendNext(),setTimeout((()=>{this.token=null,this.queue.length=0}),10)}}const s=new Set([60,61,71,73]),i=new Set([21,22,40,41,44,45,46,47,48,79,83,84,85,87,89,116,120,121,123]),e=new Set([17,23,24,27,28,29,30,42,63,64,78,112,115,124]),n=\"function\"==typeof TextEncoder?new TextEncoder:{encode(t){const s=t.length,i=new Uint8Array(3*s);let e=-1;for(let n=0,h=0,r=0;r!==s;){if(n=t.charCodeAt(r),r+=1,n>=55296&&n<=56319){if(r===s){i[e+=1]=239,i[e+=1]=191,i[e+=1]=189;break}if(h=t.charCodeAt(r),!(h>=56320&&h<=57343)){i[e+=1]=239,i[e+=1]=191,i[e+=1]=189;continue}if(n=1024*(n-55296)+h-56320+65536,r+=1,n>65535){i[e+=1]=240|n>>>18,i[e+=1]=128|n>>>12&63,i[e+=1]=128|n>>>6&63,i[e+=1]=128|63&n;continue}}n<=127?i[e+=1]=0|n:n<=2047?(i[e+=1]=192|n>>>6,i[e+=1]=128|63&n):(i[e+=1]=224|n>>>12,i[e+=1]=128|n>>>6&63,i[e+=1]=128|63&n)}return i.subarray(0,e+1)}};class h{constructor(t){this.size=t,this.offset=0,this.checkpointOffset=0,this.data=new Uint8Array(t)}getCurrentOffset(){return this.offset}getCurrentCheckpoint(){return this.checkpointOffset}checkpoint(){this.checkpointOffset=this.offset}get isEmpty(){return 0===this.offset}skip(t){return this.offset+=t,this.offset<=this.size}set(t,s){this.data.set(t,s)}boolean(t){return this.data[this.offset++]=+t,this.offset<=this.size}uint(t){for((t<0||t>Number.MAX_SAFE_INTEGER)&&(t=0);t>=128;)this.data[this.offset++]=t%256|128,t=Math.floor(t/128);return this.data[this.offset++]=t,this.offset<=this.size}int(t){return t=Math.round(t),this.uint(t>=0?2*t:-2*t-1)}string(t){const s=n.encode(t),i=s.byteLength;return!(!this.uint(i)||this.offset+i>this.size)&&(this.data.set(s,this.offset),this.offset+=i,!0)}reset(){this.offset=0,this.checkpointOffset=0}rewind(t,s){t>this.offset||s>this.checkpointOffset||(this.offset=t,this.checkpointOffset=s)}flush(){const t=this.data.slice(0,this.checkpointOffset);return this.reset(),t}}class r extends h{encode(t){switch(t[0]){case 0:case 11:case 114:case 115:return this.uint(t[1]);case 4:case 44:case 47:return this.string(t[1])&&this.string(t[2])&&this.uint(t[3]);case 5:case 20:case 65:case 70:case 75:case 76:case 77:return this.uint(t[1])&&this.uint(t[2]);case 6:return this.int(t[1])&&this.int(t[2]);case 7:return!0;case 8:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.string(t[4])&&this.boolean(t[5]);case 9:case 10:case 24:case 35:case 51:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3]);case 12:case 52:case 61:case 71:return this.uint(t[1])&&this.string(t[2])&&this.string(t[3]);case 13:case 14:case 17:case 34:case 36:case 50:case 54:return this.uint(t[1])&&this.string(t[2]);case 16:return this.uint(t[1])&&this.int(t[2])&&this.int(t[3]);case 18:return this.uint(t[1])&&this.string(t[2])&&this.int(t[3]);case 19:return this.uint(t[1])&&this.boolean(t[2]);case 21:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4])&&this.string(t[5])&&this.uint(t[6])&&this.uint(t[7])&&this.uint(t[8]);case 22:case 27:case 30:case 41:case 45:case 46:case 43:case 63:case 64:case 79:case 124:return this.string(t[1])&&this.string(t[2]);case 23:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.uint(t[4])&&this.uint(t[5])&&this.uint(t[6])&&this.uint(t[7])&&this.uint(t[8])&&this.uint(t[9]);case 28:case 29:case 42:case 117:case 118:return this.string(t[1]);case 40:return this.string(t[1])&&this.uint(t[2])&&this.string(t[3])&&this.string(t[4]);case 48:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4])&&this.int(t[5]);case 49:return this.int(t[1])&&this.int(t[2])&&this.uint(t[3])&&this.uint(t[4]);case 53:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.uint(t[4])&&this.uint(t[5])&&this.uint(t[6])&&this.string(t[7])&&this.string(t[8]);case 55:return this.boolean(t[1]);case 57:case 60:return this.uint(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4]);case 58:case 120:return this.int(t[1]);case 68:return this.uint(t[1])&&this.uint(t[2])&&this.string(t[3])&&this.string(t[4])&&this.uint(t[5])&&this.uint(t[6]);case 69:return this.uint(t[1])&&this.uint(t[2])&&this.string(t[3])&&this.string(t[4]);case 73:return this.uint(t[1])&&this.string(t[2])&&this.uint(t[3])&&this.string(t[4]);case 78:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4]);case 81:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.int(t[4])&&this.string(t[5]);case 83:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4])&&this.string(t[5])&&this.uint(t[6])&&this.uint(t[7])&&this.uint(t[8])&&this.uint(t[9]);case 84:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.uint(t[4])&&this.string(t[5])&&this.string(t[6]);case 85:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.uint(t[4])&&this.uint(t[5])&&this.uint(t[6])&&this.string(t[7])&&this.string(t[8])&&this.uint(t[9])&&this.boolean(t[10])&&this.uint(t[11])&&this.uint(t[12])&&this.uint(t[13])&&this.uint(t[14])&&this.uint(t[15])&&this.uint(t[16])&&this.uint(t[17]);case 87:return this.string(t[1])&&this.int(t[2])&&this.int(t[3]);case 89:return this.string(t[1])&&this.int(t[2])&&this.int(t[3])&&this.int(t[4])&&this.int(t[5])&&this.string(t[6]);case 112:return this.uint(t[1])&&this.string(t[2])&&this.boolean(t[3])&&this.string(t[4])&&this.int(t[5])&&this.int(t[6]);case 113:return this.uint(t[1])&&this.uint(t[2])&&this.string(t[3]);case 116:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.uint(t[4])&&this.uint(t[5])&&this.uint(t[6])&&this.string(t[7])&&this.string(t[8])&&this.uint(t[9])&&this.boolean(t[10]);case 119:return this.string(t[1])&&this.uint(t[2]);case 121:return this.string(t[1])&&this.string(t[2])&&this.uint(t[3])&&this.uint(t[4]);case 122:return this.string(t[1])&&this.string(t[2])&&this.uint(t[3])&&this.string(t[4]);case 123:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4])&&this.uint(t[5])}}}class a{constructor(t,s,i){this.bufferSize=t,this.version=s,this.dataType=i,this.sizeBuffer=new Uint8Array(3),this.snap=null,this.hasNonTimestamp=!1,this.lastPushedTs=0,this.encoder=new r(t)}push(t,s){const i=this.encoder,e=null===this.snap,n=i.getCurrentOffset(),h=i.getCurrentCheckpoint();if(e){const t={pageNo:s.pageNo,firstIndex:s.index,timestamp:s.timestamp,url:s.url,tabId:s.tabId};if(!this.writeHeader(t))return i.rewind(n,h),!1;this.snap=t,this.lastPushedTs=s.timestamp}return 0===t[0]||s.timestamp===this.lastPushedTs||this.writeMessageWithSize([0,s.timestamp])?this.writeMessageWithSize(t)?(this.lastPushedTs=s.timestamp,0!==t[0]&&(this.hasNonTimestamp=!0),!0):(i.rewind(n,h),e&&(this.snap=null),!1):(i.rewind(n,h),!1)}hasContent(){return null!==this.snap&&this.hasNonTimestamp}flush(){if(!this.hasContent())return this.reset(),null;const t=this.encoder.flush();return this.snap=null,this.hasNonTimestamp=!1,this.lastPushedTs=0,t}reset(){this.encoder.reset(),this.snap=null,this.hasNonTimestamp=!1,this.lastPushedTs=0}writeHeader(t){const s=this.encoder,i=[81,this.version,t.pageNo,t.firstIndex,t.timestamp,t.url];return!!s.uint(i[0])&&(!!s.encode(i)&&(!(s.getCurrentOffset()>this.bufferSize)&&(s.checkpoint(),!!this.writeMessageWithSize([0,t.timestamp])&&!!this.writeMessageWithSize([118,t.tabId]))))}writeMessageWithSize(t){const s=this.encoder;if(!s.uint(t[0])||!s.skip(3))return!1;const i=s.getCurrentOffset();if(!s.encode(t))return!1;const e=s.getCurrentOffset(),n=e-i;return n>16777215?(console.warn(\"OxvoSessions: max message size overflow.\"),!1):!(e>this.bufferSize)&&(this.writeSizeAt(n,i-3),s.checkpoint(),!0)}writeSizeAt(t,s){for(let s=0;s<3;s++)this.sizeBuffer[s]=t>>8*s;this.encoder.set(this.sizeBuffer,s)}}class o{constructor(t,s,i,e,n,h,r=!1,o){this.pageNo=t,this.timestamp=s,this.url=i,this.onBatch=e,this.tabId=n,this.onOfflineEnd=h,this.localDebug=r,this.onLocalSave=o,this.nextIndex=0,this.beaconSize=2e5,this.beaconSizeLimit=1e6,this.protocolVersion=1,this.playerBuilder=new a(this.beaconSize,this.playerVersion(),\"player\"),this.assetBuilder=new a(this.beaconSize,3,\"assets\"),this.devtoolsBuilder=new a(this.beaconSize,4,\"devtools\"),this.analyticsBuilder=new a(this.beaconSize,5,\"analytics\")}playerVersion(){return 2===this.protocolVersion?2:1}currentCtx(){return{pageNo:this.pageNo,index:this.nextIndex,timestamp:this.timestamp,url:this.url,tabId:this.tabId}}setBeaconSizeLimit(t){this.beaconSizeLimit=t}setProtocolVersion(t){this.protocolVersion!==t&&(this.protocolVersion=t,this.playerBuilder.reset(),this.playerBuilder=new a(this.beaconSize,this.playerVersion(),\"player\"))}writeMessage(t){if(-1===t[0])return this.finaliseBatch(),this.onOfflineEnd();0===t[0]&&(this.timestamp=t[1]),122===t[0]&&(this.url=t[1]);const s=this.routeMessage(t);this.pushTo(s,t)}routeMessage(t){if(2===this.protocolVersion){const n=t[0];if(s.has(n))return this.assetBuilder;if(i.has(n))return this.devtoolsBuilder;if(e.has(n))return this.analyticsBuilder}return this.playerBuilder}pushTo(t,s){const i=this.currentCtx();if(t.push(s,i))return void this.nextIndex++;if(this.flushBuilder(t),t.push(s,i))return void this.nextIndex++;const e=new a(this.beaconSizeLimit,t.version,t.dataType);if(!e.push(s,i))return void console.warn(\"OxvoSessions: beacon size overflow. Skipping large message.\",s);this.nextIndex++;const n=e.flush();n&&this.emitBatch(n,t.dataType,!1)}flushBuilder(t,s=!1){const i=t.flush();return!!i&&(this.emitBatch(i,t.dataType,s),!0)}emitBatch(t,s,i){this.localDebug&&this.onLocalSave&&this.onLocalSave(`${s}-${Date.now()}`,t.slice()),this.onBatch(t,i,s)}finaliseBatch(t=!1){this.flushBuilder(this.playerBuilder,t),this.flushBuilder(this.assetBuilder,t),this.flushBuilder(this.devtoolsBuilder,t),this.flushBuilder(this.analyticsBuilder,t)}clean(){this.playerBuilder.reset(),this.assetBuilder.reset(),this.devtoolsBuilder.reset(),this.analyticsBuilder.reset()}}var u;!function(t){t[t.NotActive=0]=\"NotActive\",t[t.Starting=1]=\"Starting\",t[t.Stopping=2]=\"Stopping\",t[t.Active=3]=\"Active\",t[t.Stopped=4]=\"Stopped\"}(u||(u={}));let c=null,l=null,d=u.NotActive;function p(t){l&&l.finaliseBatch(t)}function f(){return new Promise((t=>{d=u.Stopping,null!==b&&(clearInterval(b),b=null),l&&(l.clean(),l=null),c&&(c.clean(),setTimeout((()=>{c=null}),20)),setTimeout((()=>{d=u.NotActive,t(null)}),100)}))}function g(){[u.Stopped,u.Stopping].includes(d)||(postMessage(\"a_stop\"),f().then((()=>{postMessage(\"a_start\")})))}let m,b=null;self.onmessage=({data:s})=>{var i;if(\"stop\"===s)return p(),void f().then((()=>{d=u.Stopped}));if(\"forceFlushBatch\"!==s)if(\"closing\"!==s){if(!Array.isArray(s)){if(\"compressed\"===s.type){if(!c)return console.debug(\"OR WebWorker: sender not initialised. Compressed batch.\"),void g();s.batch&&c.sendCompressed(s.batch,s.dataType)}if(\"uncompressed\"===s.type){if(!c)return console.debug(\"OR WebWorker: sender not initialised. Uncompressed batch.\"),void g();s.batch&&c.sendUncompressed(s.batch,s.dataType)}return\"start\"===s.type?(d=u.Starting,c=new t(s.ingestPoint,(()=>{g()}),(t=>{!function(t){postMessage({type:\"failure\",reason:t}),f()}(t)}),s.connAttemptCount,s.connAttemptGap,((t,s)=>{postMessage({type:\"compress\",batch:t,dataType:s},[t.buffer])}),s.pageNo),l=new o(s.pageNo,s.timestamp,s.url,((t,s,i=\"player\")=>{c&&(s?c.sendUncompressed(t,i):c.push(t,i))}),s.tabId,(()=>postMessage({type:\"queue_empty\"})),null!==(i=s.localDebug)&&void 0!==i&&i,((t,s)=>{postMessage({type:\"local_save\",name:t,batch:s},[s.buffer])})),null===b&&(b=setInterval(p,3e4)),d=u.Active):\"auth\"===s.type?c?l?(c.authorise(s.token),s.beaconSizeLimit&&l.setBeaconSizeLimit(s.beaconSizeLimit),void(s.protocolVersion&&l.setProtocolVersion(s.protocolVersion))):(console.debug(\"OR WebWorker: writer not initialised. Received auth.\"),void g()):(console.debug(\"OR WebWorker: sender not initialised. Received auth.\"),void g()):void 0}if(l){const t=l;s.forEach((s=>{55===s[0]&&(s[1]?m=setTimeout((()=>g()),18e5):clearTimeout(m)),t.writeMessage(s)}))}else postMessage(\"not_init\"),g()}else p(!0);else p()}}();\n";
|
|
4762
|
+
const workerBodyFn = "!function(){\"use strict\";class t{constructor(t,s,i,e=10,n=250,h,r){this.onUnauthorised=s,this.onFailure=i,this.MAX_ATTEMPTS_COUNT=e,this.ATTEMPT_TIMEOUT=n,this.onCompress=h,this.pageNo=r,this.attemptsCount=0,this.busy=!1,this.queue=[],this.token=null,this.lastBatchNum=0,this.inflightKeepaliveBytes=0,this.ingestURL=t+\"/v1/events\",this.analyticsIngestURL=t+\"/v1/analytics\",this.isCompressing=void 0!==h}getQueueStatus(){return 0===this.queue.length&&!this.busy}authorise(t){this.token=t,this.busy||this.sendNext()}push(t,s=\"player\"){if(this.busy||!this.token)this.queue.push({batch:t,dataType:s});else if(this.busy=!0,this.isCompressing&&this.onCompress)this.onCompress(t,s);else{const i=++this.lastBatchNum;this.sendBatch(t,!1,i,s)}}sendNext(){const t=this.queue.shift();if(t)if(this.busy=!0,this.isCompressing&&this.onCompress)this.onCompress(t.batch,t.dataType);else{const s=++this.lastBatchNum;this.sendBatch(t.batch,!1,s,t.dataType)}else this.busy=!1}retry(t,s,i,e=\"player\"){if(this.attemptsCount>=this.MAX_ATTEMPTS_COUNT)return void this.onFailure(`Failed to send batch after ${this.attemptsCount} attempts.`);this.attemptsCount++;const n=new Uint8Array(t);setTimeout((()=>this.sendBatch(n,s,i,e)),this.ATTEMPT_TIMEOUT*this.attemptsCount)}sendBatch(t,s,i,e=\"player\"){var n;if(0===t.length)return console.error(\"OxvoSessions: refusing to send 0-byte batch.\",{batchNum:i,dataType:e,isCompressed:s,batch:t}),this.attemptsCount=0,void this.sendNext();const h=null==i?void 0:i.toString().replace(/^([^_]+)_([^_]+).*/,\"$1_$2_$3\");this.busy=!0;const r={Authorization:`Bearer ${this.token}`,DataType:e};if(s&&(r[\"Content-Encoding\"]=\"gzip\"),null===this.token)return void setTimeout((()=>{this.sendBatch(t,s,`${null!=i?i:\"noBatchNum\"}_newToken`,e)}),500);const a=t.length<65536&&this.inflightKeepaliveBytes+t.length<=65536;a&&(this.inflightKeepaliveBytes+=t.length);const o=()=>{a&&(this.inflightKeepaliveBytes-=t.length)},u=`${null!==(n=this.pageNo)&&void 0!==n?n:\"noPageNum\"}_${null!=h?h:\"noBatchNum\"}`,c=\"analytics\"===e?this.analyticsIngestURL:this.ingestURL;fetch(`${c}?seq=${u}&keepalive=${a?\"yes\":\"no\"}`,{body:t,method:\"POST\",headers:r,keepalive:a}).then((n=>{var h;if(o(),null===(h=n.body)||void 0===h||h.cancel().catch((()=>{})),401===n.status)return this.busy=!1,void this.onUnauthorised();n.status>=400?this.retry(t,s,`${null!=i?i:\"noBatchNum\"}_network:${n.status}`,e):(this.attemptsCount=0,this.sendNext())})).catch((n=>{o(),console.warn(\"OxvoSessions:\",n),this.retry(t,s,`${null!=i?i:\"noBatchNum\"}_reject:${n.message}`,e)}))}sendCompressed(t,s=\"player\"){const i=++this.lastBatchNum;this.sendBatch(t,!0,i,s)}sendUncompressed(t,s=\"player\"){const i=++this.lastBatchNum;this.sendBatch(t,!1,i,s)}clean(){this.sendNext(),setTimeout((()=>{this.token=null,this.queue.length=0}),10)}}const s=new Set([60,61,71,73]),i=new Set([21,22,40,41,44,45,46,47,48,79,83,84,85,87,89,116,120,121,123]),e=new Set([17,23,24,27,28,29,30,42,63,64,78,112,115,124]),n=\"function\"==typeof TextEncoder?new TextEncoder:{encode(t){const s=t.length,i=new Uint8Array(3*s);let e=-1;for(let n=0,h=0,r=0;r!==s;){if(n=t.charCodeAt(r),r+=1,n>=55296&&n<=56319){if(r===s){i[e+=1]=239,i[e+=1]=191,i[e+=1]=189;break}if(h=t.charCodeAt(r),!(h>=56320&&h<=57343)){i[e+=1]=239,i[e+=1]=191,i[e+=1]=189;continue}if(n=1024*(n-55296)+h-56320+65536,r+=1,n>65535){i[e+=1]=240|n>>>18,i[e+=1]=128|n>>>12&63,i[e+=1]=128|n>>>6&63,i[e+=1]=128|63&n;continue}}n<=127?i[e+=1]=0|n:n<=2047?(i[e+=1]=192|n>>>6,i[e+=1]=128|63&n):(i[e+=1]=224|n>>>12,i[e+=1]=128|n>>>6&63,i[e+=1]=128|63&n)}return i.subarray(0,e+1)}};class h{constructor(t){this.size=t,this.offset=0,this.checkpointOffset=0,this.data=new Uint8Array(t)}getCurrentOffset(){return this.offset}getCurrentCheckpoint(){return this.checkpointOffset}checkpoint(){this.checkpointOffset=this.offset}get isEmpty(){return 0===this.offset}skip(t){return this.offset+=t,this.offset<=this.size}set(t,s){this.data.set(t,s)}boolean(t){return this.data[this.offset++]=+t,this.offset<=this.size}uint(t){for((t<0||t>Number.MAX_SAFE_INTEGER)&&(t=0);t>=128;)this.data[this.offset++]=t%256|128,t=Math.floor(t/128);return this.data[this.offset++]=t,this.offset<=this.size}int(t){return t=Math.round(t),this.uint(t>=0?2*t:-2*t-1)}string(t){const s=n.encode(t),i=s.byteLength;return!(!this.uint(i)||this.offset+i>this.size)&&(this.data.set(s,this.offset),this.offset+=i,!0)}reset(){this.offset=0,this.checkpointOffset=0}rewind(t,s){t>this.offset||s>this.checkpointOffset||(this.offset=t,this.checkpointOffset=s)}flush(){const t=this.data.slice(0,this.checkpointOffset);return this.reset(),t}}class r extends h{encode(t){switch(t[0]){case 0:case 11:case 114:case 115:return this.uint(t[1]);case 4:case 44:case 47:return this.string(t[1])&&this.string(t[2])&&this.uint(t[3]);case 5:case 20:case 65:case 70:case 75:case 76:case 77:return this.uint(t[1])&&this.uint(t[2]);case 6:return this.int(t[1])&&this.int(t[2]);case 7:return!0;case 8:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.string(t[4])&&this.boolean(t[5]);case 9:case 10:case 24:case 35:case 51:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3]);case 12:case 52:case 61:case 71:return this.uint(t[1])&&this.string(t[2])&&this.string(t[3]);case 13:case 14:case 17:case 34:case 36:case 50:case 54:return this.uint(t[1])&&this.string(t[2]);case 16:return this.uint(t[1])&&this.int(t[2])&&this.int(t[3]);case 18:return this.uint(t[1])&&this.string(t[2])&&this.int(t[3]);case 19:return this.uint(t[1])&&this.boolean(t[2]);case 21:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4])&&this.string(t[5])&&this.uint(t[6])&&this.uint(t[7])&&this.uint(t[8]);case 22:case 27:case 30:case 41:case 45:case 46:case 43:case 63:case 64:case 79:case 124:return this.string(t[1])&&this.string(t[2]);case 23:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.uint(t[4])&&this.uint(t[5])&&this.uint(t[6])&&this.uint(t[7])&&this.uint(t[8])&&this.uint(t[9]);case 28:case 29:case 42:case 117:case 118:return this.string(t[1]);case 40:return this.string(t[1])&&this.uint(t[2])&&this.string(t[3])&&this.string(t[4]);case 48:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4])&&this.int(t[5]);case 49:return this.int(t[1])&&this.int(t[2])&&this.uint(t[3])&&this.uint(t[4]);case 53:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.uint(t[4])&&this.uint(t[5])&&this.uint(t[6])&&this.string(t[7])&&this.string(t[8]);case 55:return this.boolean(t[1]);case 57:case 60:return this.uint(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4]);case 58:case 120:return this.int(t[1]);case 68:return this.uint(t[1])&&this.uint(t[2])&&this.string(t[3])&&this.string(t[4])&&this.uint(t[5])&&this.uint(t[6]);case 69:return this.uint(t[1])&&this.uint(t[2])&&this.string(t[3])&&this.string(t[4]);case 73:return this.uint(t[1])&&this.string(t[2])&&this.uint(t[3])&&this.string(t[4]);case 78:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4]);case 81:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.int(t[4])&&this.string(t[5]);case 83:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4])&&this.string(t[5])&&this.uint(t[6])&&this.uint(t[7])&&this.uint(t[8])&&this.uint(t[9]);case 84:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.uint(t[4])&&this.string(t[5])&&this.string(t[6]);case 85:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.uint(t[4])&&this.uint(t[5])&&this.uint(t[6])&&this.string(t[7])&&this.string(t[8])&&this.uint(t[9])&&this.boolean(t[10])&&this.uint(t[11])&&this.uint(t[12])&&this.uint(t[13])&&this.uint(t[14])&&this.uint(t[15])&&this.uint(t[16])&&this.uint(t[17]);case 87:return this.string(t[1])&&this.int(t[2])&&this.int(t[3]);case 89:return this.string(t[1])&&this.int(t[2])&&this.int(t[3])&&this.int(t[4])&&this.int(t[5])&&this.string(t[6]);case 112:return this.uint(t[1])&&this.string(t[2])&&this.boolean(t[3])&&this.string(t[4])&&this.int(t[5])&&this.int(t[6]);case 113:return this.uint(t[1])&&this.uint(t[2])&&this.string(t[3]);case 116:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.uint(t[4])&&this.uint(t[5])&&this.uint(t[6])&&this.string(t[7])&&this.string(t[8])&&this.uint(t[9])&&this.boolean(t[10]);case 119:return this.string(t[1])&&this.uint(t[2]);case 121:return this.string(t[1])&&this.string(t[2])&&this.uint(t[3])&&this.uint(t[4]);case 122:return this.string(t[1])&&this.string(t[2])&&this.uint(t[3])&&this.string(t[4]);case 123:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4])&&this.uint(t[5])}}}class a{constructor(t,s,i){this.bufferSize=t,this.version=s,this.dataType=i,this.sizeBuffer=new Uint8Array(3),this.snap=null,this.hasNonTimestamp=!1,this.lastPushedTs=0,this.encoder=new r(t)}push(t,s){const i=this.encoder,e=null===this.snap,n=i.getCurrentOffset(),h=i.getCurrentCheckpoint();if(e){const t={pageNo:s.pageNo,firstIndex:s.index,timestamp:s.timestamp,url:s.url,tabId:s.tabId};if(!this.writeHeader(t))return i.rewind(n,h),!1;this.snap=t,this.lastPushedTs=s.timestamp}return 0===t[0]||s.timestamp===this.lastPushedTs||this.writeMessageWithSize([0,s.timestamp])?this.writeMessageWithSize(t)?(this.lastPushedTs=s.timestamp,0!==t[0]&&(this.hasNonTimestamp=!0),!0):(i.rewind(n,h),e&&(this.snap=null),!1):(i.rewind(n,h),!1)}hasContent(){return null!==this.snap&&this.hasNonTimestamp}flush(){if(!this.hasContent())return this.reset(),null;const t=this.encoder.flush();return this.snap=null,this.hasNonTimestamp=!1,this.lastPushedTs=0,t}reset(){this.encoder.reset(),this.snap=null,this.hasNonTimestamp=!1,this.lastPushedTs=0}writeHeader(t){const s=this.encoder,i=[81,this.version,t.pageNo,t.firstIndex,t.timestamp,t.url];return!!s.uint(i[0])&&(!!s.encode(i)&&(!(s.getCurrentOffset()>this.bufferSize)&&(s.checkpoint(),!!this.writeMessageWithSize([0,t.timestamp])&&!!this.writeMessageWithSize([118,t.tabId]))))}writeMessageWithSize(t){const s=this.encoder;if(!s.uint(t[0])||!s.skip(3))return!1;const i=s.getCurrentOffset();if(!s.encode(t))return!1;const e=s.getCurrentOffset(),n=e-i;return n>16777215?(console.warn(\"OxvoSessions: max message size overflow.\"),!1):!(e>this.bufferSize)&&(this.writeSizeAt(n,i-3),s.checkpoint(),!0)}writeSizeAt(t,s){for(let s=0;s<3;s++)this.sizeBuffer[s]=t>>8*s;this.encoder.set(this.sizeBuffer,s)}}class o{constructor(t,s,i,e,n,h,r=!1,o){this.pageNo=t,this.timestamp=s,this.url=i,this.onBatch=e,this.tabId=n,this.onOfflineEnd=h,this.localDebug=r,this.onLocalSave=o,this.nextIndex=0,this.beaconSize=2e5,this.beaconSizeLimit=1e6,this.protocolVersion=1,this.playerBuilder=new a(this.beaconSize,this.playerVersion(),\"player\"),this.assetBuilder=new a(this.beaconSize,3,\"assets\"),this.devtoolsBuilder=new a(this.beaconSize,4,\"devtools\"),this.analyticsBuilder=new a(this.beaconSize,5,\"analytics\")}playerVersion(){return 2===this.protocolVersion?2:1}currentCtx(){return{pageNo:this.pageNo,index:this.nextIndex,timestamp:this.timestamp,url:this.url,tabId:this.tabId}}setBeaconSizeLimit(t){this.beaconSizeLimit=t}setProtocolVersion(t){this.protocolVersion!==t&&(this.protocolVersion=t,this.playerBuilder.reset(),this.playerBuilder=new a(this.beaconSize,this.playerVersion(),\"player\"))}writeMessage(t){if(-1===t[0])return this.finaliseBatch(),this.onOfflineEnd();0===t[0]&&(this.timestamp=t[1]),122===t[0]&&(this.url=t[1]);const s=this.routeMessage(t);this.pushTo(s,t)}routeMessage(t){if(2===this.protocolVersion){const n=t[0];if(s.has(n))return this.assetBuilder;if(i.has(n))return this.devtoolsBuilder;if(e.has(n))return this.analyticsBuilder}return this.playerBuilder}pushTo(t,s){const i=this.currentCtx();if(t.push(s,i))return void this.nextIndex++;if(this.flushBuilder(t),t.push(s,i))return void this.nextIndex++;const e=new a(this.beaconSizeLimit,t.version,t.dataType);if(!e.push(s,i))return void console.warn(\"OxvoSessions: beacon size overflow. Skipping large message.\",s);this.nextIndex++;const n=e.flush();n&&this.emitBatch(n,t.dataType,!1)}flushBuilder(t,s=!1){const i=t.flush();return!!i&&(this.emitBatch(i,t.dataType,s),!0)}emitBatch(t,s,i){this.localDebug&&this.onLocalSave&&this.onLocalSave(`${s}-${Date.now()}`,t.slice()),this.onBatch(t,i,s)}finaliseBatch(t=!1){this.flushBuilder(this.playerBuilder,t),this.flushBuilder(this.assetBuilder,t),this.flushBuilder(this.devtoolsBuilder,t),this.flushBuilder(this.analyticsBuilder,t)}clean(){this.playerBuilder.reset(),this.assetBuilder.reset(),this.devtoolsBuilder.reset(),this.analyticsBuilder.reset()}}var u;!function(t){t[t.NotActive=0]=\"NotActive\",t[t.Starting=1]=\"Starting\",t[t.Stopping=2]=\"Stopping\",t[t.Active=3]=\"Active\",t[t.Stopped=4]=\"Stopped\"}(u||(u={}));let c=null,l=null,d=u.NotActive;function p(t){l&&l.finaliseBatch(t)}function f(){return new Promise((t=>{d=u.Stopping,null!==y&&(clearInterval(y),y=null),l&&(l.clean(),l=null),c&&(c.clean(),setTimeout((()=>{c=null}),20)),setTimeout((()=>{d=u.NotActive,t(null)}),100)}))}function g(){[u.Stopped,u.Stopping].includes(d)||(postMessage(\"a_stop\"),f().then((()=>{postMessage(\"a_start\")})))}let m,y=null;self.onmessage=({data:s})=>{var i;if(\"stop\"===s)return p(),void f().then((()=>{d=u.Stopped}));if(\"forceFlushBatch\"!==s)if(\"closing\"!==s){if(!Array.isArray(s)){if(\"compressed\"===s.type){if(!c)return console.debug(\"OR WebWorker: sender not initialised. Compressed batch.\"),void g();s.batch&&c.sendCompressed(s.batch,s.dataType)}if(\"uncompressed\"===s.type){if(!c)return console.debug(\"OR WebWorker: sender not initialised. Uncompressed batch.\"),void g();s.batch&&c.sendUncompressed(s.batch,s.dataType)}return\"start\"===s.type?(d=u.Starting,c=new t(s.ingestPoint,(()=>{g()}),(t=>{!function(t){postMessage({type:\"failure\",reason:t}),f()}(t)}),s.connAttemptCount,s.connAttemptGap,((t,s)=>{postMessage({type:\"compress\",batch:t,dataType:s},[t.buffer])}),s.pageNo),l=new o(s.pageNo,s.timestamp,s.url,((t,s,i=\"player\")=>{c&&(s?c.sendUncompressed(t,i):c.push(t,i))}),s.tabId,(()=>postMessage({type:\"queue_empty\"})),null!==(i=s.localDebug)&&void 0!==i&&i,((t,s)=>{postMessage({type:\"local_save\",name:t,batch:s},[s.buffer])})),null===y&&(y=setInterval(p,3e4)),d=u.Active):\"auth\"===s.type?c?l?(c.authorise(s.token),s.beaconSizeLimit&&l.setBeaconSizeLimit(s.beaconSizeLimit),void(s.protocolVersion&&l.setProtocolVersion(s.protocolVersion))):(console.debug(\"OR WebWorker: writer not initialised. Received auth.\"),void g()):(console.debug(\"OR WebWorker: sender not initialised. Received auth.\"),void g()):void 0}if(l){const t=l;s.forEach((s=>{55===s[0]&&(s[1]?m=setTimeout((()=>g()),18e5):clearTimeout(m)),t.writeMessage(s)}))}else postMessage(\"not_init\"),g()}else p(!0);else p()}}();\n";
|
|
4112
4763
|
const CANCELED = 'canceled';
|
|
4113
4764
|
const bufferStorageKey = 'or_buffer_1';
|
|
4114
4765
|
const PROTO_VERSION = '2';
|
|
4766
|
+
const BOOTSTRAP_ATTEMPT_TTL_MS = 24 * 60 * 60 * 1000;
|
|
4767
|
+
const MAX_BOOTSTRAP_ATTEMPT_STORAGE_LENGTH = 1024 * 1024;
|
|
4768
|
+
const MAX_SESSION_TOKEN_LENGTH = 4096;
|
|
4769
|
+
const MAX_ASSIST_SESSION_GRANT_LENGTH = 4096;
|
|
4770
|
+
const LINKAGE_ASSERTION_METADATA_KEY = 'oxvo_linkage_assertion';
|
|
4771
|
+
const MESSENGER_LINKAGE_CHALLENGE_PATTERN = /^[a-f0-9]{64}$/;
|
|
4772
|
+
const MIN_MESSENGER_LINKAGE_CHALLENGE_TTL_SECONDS = 5;
|
|
4773
|
+
const MAX_MESSENGER_LINKAGE_CHALLENGE_TTL_SECONDS = 120;
|
|
4774
|
+
const asRecord = (value) => value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
4775
|
+
? value
|
|
4776
|
+
: {};
|
|
4777
|
+
const isValidBootstrapCredential = (value, maxLength) => typeof value === 'string' &&
|
|
4778
|
+
value.length > 0 &&
|
|
4779
|
+
value.length <= maxLength &&
|
|
4780
|
+
value.trim() === value;
|
|
4781
|
+
const parseMessengerLinkageChallenge = (value, expectedSessionId) => {
|
|
4782
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
|
4783
|
+
return null;
|
|
4784
|
+
}
|
|
4785
|
+
const record = value;
|
|
4786
|
+
const keys = Object.keys(record).sort();
|
|
4787
|
+
if (keys.length !== 3 ||
|
|
4788
|
+
keys[0] !== 'challenge' ||
|
|
4789
|
+
keys[1] !== 'expiresIn' ||
|
|
4790
|
+
keys[2] !== 'sessionId' ||
|
|
4791
|
+
typeof record.challenge !== 'string' ||
|
|
4792
|
+
!MESSENGER_LINKAGE_CHALLENGE_PATTERN.test(record.challenge) ||
|
|
4793
|
+
record.sessionId !== expectedSessionId ||
|
|
4794
|
+
!Number.isInteger(record.expiresIn) ||
|
|
4795
|
+
record.expiresIn < MIN_MESSENGER_LINKAGE_CHALLENGE_TTL_SECONDS ||
|
|
4796
|
+
record.expiresIn > MAX_MESSENGER_LINKAGE_CHALLENGE_TTL_SECONDS) {
|
|
4797
|
+
return null;
|
|
4798
|
+
}
|
|
4799
|
+
return {
|
|
4800
|
+
challenge: record.challenge,
|
|
4801
|
+
sessionId: expectedSessionId,
|
|
4802
|
+
expiresIn: record.expiresIn,
|
|
4803
|
+
};
|
|
4804
|
+
};
|
|
4805
|
+
const bootstrapHttpError = async (response, responseText, knownSecrets = []) => {
|
|
4806
|
+
let body;
|
|
4807
|
+
try {
|
|
4808
|
+
body = responseText ?? (await response.text());
|
|
4809
|
+
}
|
|
4810
|
+
catch (error) {
|
|
4811
|
+
return redactedErrorFrom(error, `Server error: ${response.status}.`, knownSecrets);
|
|
4812
|
+
}
|
|
4813
|
+
return redactedErrorFrom(`Server error: ${response.status}. ${body}`, `Server error: ${response.status}.`, knownSecrets);
|
|
4814
|
+
};
|
|
4815
|
+
const bootstrapJson = async (response, knownSecrets = []) => {
|
|
4816
|
+
try {
|
|
4817
|
+
return asRecord(await response.json());
|
|
4818
|
+
}
|
|
4819
|
+
catch (error) {
|
|
4820
|
+
throw redactedErrorFrom(error, 'Incorrect server response (invalid JSON)', knownSecrets);
|
|
4821
|
+
}
|
|
4822
|
+
};
|
|
4115
4823
|
const UnsuccessfulStart = (reason) => ({ reason, success: false });
|
|
4116
4824
|
const SuccessfulStart = (body) => ({ ...body, success: true });
|
|
4117
4825
|
var ActivityState;
|
|
@@ -4142,18 +4850,25 @@ const proto = {
|
|
|
4142
4850
|
resp: 'never-gonna-let-you-down',
|
|
4143
4851
|
// regenerating id (copied other tab)
|
|
4144
4852
|
reg: 'never-gonna-run-around-and-desert-you',
|
|
4145
|
-
iframeSignal: '
|
|
4146
|
-
iframeId: '
|
|
4147
|
-
iframeBatch: 'batch
|
|
4148
|
-
parentAlive: '
|
|
4149
|
-
killIframe: 'stop
|
|
4150
|
-
startIframe: 'start
|
|
4853
|
+
iframeSignal: 'oxvo:cross-frame:v1:handshake',
|
|
4854
|
+
iframeId: 'oxvo:cross-frame:v1:ready',
|
|
4855
|
+
iframeBatch: 'oxvo:cross-frame:v1:batch',
|
|
4856
|
+
parentAlive: 'oxvo:cross-frame:v1:pong',
|
|
4857
|
+
killIframe: 'oxvo:cross-frame:v1:stop',
|
|
4858
|
+
startIframe: 'oxvo:cross-frame:v1:start',
|
|
4151
4859
|
// checking updates
|
|
4152
|
-
polling: '
|
|
4860
|
+
polling: 'oxvo:cross-frame:v1:ping',
|
|
4153
4861
|
// happens if tab is old and has outdated token but
|
|
4154
4862
|
// not communicating with backend to update it (for whatever reason)
|
|
4155
4863
|
reset: 'reset-your-session-please',
|
|
4156
4864
|
};
|
|
4865
|
+
const FRAME_RATE_WINDOW_MS = 1000;
|
|
4866
|
+
const MAX_FRAME_ENVELOPES_PER_WINDOW = 120;
|
|
4867
|
+
const MAX_FRAME_MESSAGES_PER_WINDOW = 50000;
|
|
4868
|
+
const MAX_FRAME_BYTES_PER_WINDOW = 4 * 1024 * 1024;
|
|
4869
|
+
const FRAME_CAPABILITY_TTL_MS = 5 * 60 * 1000;
|
|
4870
|
+
const FRAME_CAPABILITY_REFRESH_LEAD_MS = 60 * 1000;
|
|
4871
|
+
const FRAME_PREVIOUS_CAPABILITY_GRACE_MS = 5 * 1000;
|
|
4157
4872
|
class App {
|
|
4158
4873
|
get tagMatcher() {
|
|
4159
4874
|
return this.markerWatcher.matcher;
|
|
@@ -4174,18 +4889,32 @@ class App {
|
|
|
4174
4889
|
this.stopCallbacks = [];
|
|
4175
4890
|
this.commitCallbacks = [];
|
|
4176
4891
|
this.activityState = ActivityState.NotActive;
|
|
4177
|
-
this.version = '7.
|
|
4892
|
+
this.version = '7.4.15'; // TODO: version compatability check inside each plugin.
|
|
4178
4893
|
this.socketMode = false;
|
|
4179
4894
|
this.compressionThreshold = 24 * 1000;
|
|
4180
4895
|
this.bc = null;
|
|
4181
4896
|
this.canvasRecorder = null;
|
|
4182
4897
|
this.conditionsManager = null;
|
|
4898
|
+
this.bootstrapAttempts = {};
|
|
4899
|
+
this.assistSessionGrant = null;
|
|
4183
4900
|
this.canStart = false;
|
|
4184
4901
|
this.rootId = null;
|
|
4185
4902
|
this.pageFrames = [];
|
|
4186
4903
|
this.frameOderNumber = 0;
|
|
4187
4904
|
this.frameLevel = 0;
|
|
4188
4905
|
this.emptyBatchCounter = 0;
|
|
4906
|
+
this.crossDomainFrameCapability = null;
|
|
4907
|
+
this.crossDomainFrameCapabilityExpiresAt = 0;
|
|
4908
|
+
this.crossDomainFrameSession = null;
|
|
4909
|
+
this.crossDomainFrameCanvas = null;
|
|
4910
|
+
this.crossDomainResumeAfterHandshake = false;
|
|
4911
|
+
this.crossDomainConnectionRequested = false;
|
|
4912
|
+
this.crossDomainFrames = new Map();
|
|
4913
|
+
this.crossDomainEnvelopeBudgets = new WeakMap();
|
|
4914
|
+
this.pendingCrossDomainFrames = new Map();
|
|
4915
|
+
this.crossDomainAuthorizationGeneration = 0;
|
|
4916
|
+
this.nextCrossDomainFrameOrder = 1;
|
|
4917
|
+
this.crossDomainCanvasConfig = null;
|
|
4189
4918
|
/** used by child iframes for crossdomain only */
|
|
4190
4919
|
this.parentActive = false;
|
|
4191
4920
|
this.checkStatus = () => {
|
|
@@ -4193,230 +4922,201 @@ class App {
|
|
|
4193
4922
|
};
|
|
4194
4923
|
this.parentCrossDomainFrameListener = (event) => {
|
|
4195
4924
|
const { data } = event;
|
|
4196
|
-
if (!data ||
|
|
4925
|
+
if (!data ||
|
|
4926
|
+
this.crossDomainParentOrigin === null ||
|
|
4927
|
+
this.crossDomainFrameNonce === null ||
|
|
4928
|
+
event.source !== window.parent ||
|
|
4929
|
+
event.origin !== this.crossDomainParentOrigin) {
|
|
4930
|
+
return;
|
|
4931
|
+
}
|
|
4932
|
+
if (data.line === proto.iframeId) {
|
|
4933
|
+
const init = parseCrossDomainFrameInit(data, proto.iframeId, {
|
|
4934
|
+
context: this.contextId,
|
|
4935
|
+
nonce: this.crossDomainFrameNonce,
|
|
4936
|
+
});
|
|
4937
|
+
if (init === null)
|
|
4938
|
+
return;
|
|
4939
|
+
const sessionChanged = this.crossDomainFrameSession !== null &&
|
|
4940
|
+
this.crossDomainFrameSession.sessionID !== init.session.sessionID;
|
|
4941
|
+
if (sessionChanged && this.activityState === ActivityState.Starting) {
|
|
4942
|
+
this.postHandshakeToParent();
|
|
4943
|
+
return;
|
|
4944
|
+
}
|
|
4945
|
+
const shouldResume = this.crossDomainResumeAfterHandshake || (sessionChanged && this.active());
|
|
4946
|
+
if (sessionChanged && this.active()) {
|
|
4947
|
+
this.stop();
|
|
4948
|
+
}
|
|
4949
|
+
this.applyCrossDomainFrameInit(init);
|
|
4950
|
+
if (shouldResume &&
|
|
4951
|
+
this.activityState !== ActivityState.Active &&
|
|
4952
|
+
this.activityState !== ActivityState.Starting) {
|
|
4953
|
+
this.crossDomainResumeAfterHandshake = false;
|
|
4954
|
+
this.startCrossDomainFrame(this.prevOpts);
|
|
4955
|
+
}
|
|
4956
|
+
this.debug.log('Starting secure cross-domain iframe tracking');
|
|
4957
|
+
return;
|
|
4958
|
+
}
|
|
4959
|
+
const expectedLine = data.line === proto.parentAlive
|
|
4960
|
+
? proto.parentAlive
|
|
4961
|
+
: data.line === proto.startIframe
|
|
4962
|
+
? proto.startIframe
|
|
4963
|
+
: data.line === proto.killIframe
|
|
4964
|
+
? proto.killIframe
|
|
4965
|
+
: null;
|
|
4966
|
+
if (this.crossDomainFrameCapability === null ||
|
|
4967
|
+
expectedLine === null ||
|
|
4968
|
+
(expectedLine !== proto.killIframe &&
|
|
4969
|
+
Date.now() >= this.crossDomainFrameCapabilityExpiresAt) ||
|
|
4970
|
+
!matchesCrossDomainEnvelope(data, expectedLine, {
|
|
4971
|
+
context: this.contextId,
|
|
4972
|
+
nonce: this.crossDomainFrameNonce,
|
|
4973
|
+
capability: this.crossDomainFrameCapability,
|
|
4974
|
+
})) {
|
|
4975
|
+
return;
|
|
4976
|
+
}
|
|
4977
|
+
if (data.line === proto.parentAlive) {
|
|
4978
|
+
this.parentActive = true;
|
|
4197
4979
|
return;
|
|
4980
|
+
}
|
|
4198
4981
|
if (data.line === proto.startIframe) {
|
|
4199
4982
|
// Avoid corrupting an in-flight start; let it complete.
|
|
4200
4983
|
if (this.activityState === ActivityState.Starting)
|
|
4201
4984
|
return;
|
|
4985
|
+
const init = parseCrossDomainFrameInit(data, proto.startIframe, {
|
|
4986
|
+
context: this.contextId,
|
|
4987
|
+
nonce: this.crossDomainFrameNonce,
|
|
4988
|
+
});
|
|
4989
|
+
if (init === null)
|
|
4990
|
+
return;
|
|
4202
4991
|
try {
|
|
4203
4992
|
if (this.active()) {
|
|
4204
4993
|
this.stop();
|
|
4205
4994
|
}
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
}
|
|
4209
|
-
if (data.id !== undefined) {
|
|
4210
|
-
this.rootId = data.id;
|
|
4211
|
-
}
|
|
4212
|
-
this.allowAppStart();
|
|
4213
|
-
void this.start();
|
|
4995
|
+
this.applyCrossDomainFrameInit(init);
|
|
4996
|
+
this.startCrossDomainFrame(this.prevOpts);
|
|
4214
4997
|
}
|
|
4215
4998
|
catch (e) {
|
|
4216
|
-
console.error('children frame restart failed:', e);
|
|
4999
|
+
console.error('children frame restart failed:', redactDebugValue(e));
|
|
4217
5000
|
}
|
|
4218
|
-
|
|
4219
|
-
if (data.line === proto.parentAlive) {
|
|
4220
|
-
this.parentActive = true;
|
|
4221
|
-
}
|
|
4222
|
-
if (data.line === proto.iframeId) {
|
|
4223
|
-
this.parentActive = true;
|
|
4224
|
-
this.rootId = data.id;
|
|
4225
|
-
this.session.setSessionSecret(data.token, this.projectKey);
|
|
4226
|
-
this.frameOderNumber = data.frameOrderNumber;
|
|
4227
|
-
this.frameLevel = data.frameLevel;
|
|
4228
|
-
this.debug.log('starting iframe tracking', data);
|
|
4229
|
-
this.allowAppStart();
|
|
5001
|
+
return;
|
|
4230
5002
|
}
|
|
4231
5003
|
if (data.line === proto.killIframe) {
|
|
4232
|
-
|
|
5004
|
+
const shouldResume = this.activityState === ActivityState.Active || this.activityState === ActivityState.Starting;
|
|
5005
|
+
if (this.activityState !== ActivityState.NotActive) {
|
|
4233
5006
|
this.stop();
|
|
4234
5007
|
}
|
|
5008
|
+
this.clearCrossDomainFrameAuthorization();
|
|
5009
|
+
this.crossDomainResumeAfterHandshake = shouldResume;
|
|
5010
|
+
this.crossDomainConnectionRequested = shouldResume;
|
|
4235
5011
|
}
|
|
4236
5012
|
};
|
|
4237
|
-
this.trackedFrames = [];
|
|
4238
|
-
this.frameLastSeen = new Map();
|
|
4239
|
-
this.FRAME_STALE_MS = 1500;
|
|
4240
5013
|
this.crossDomainIframeListener = (event) => {
|
|
4241
|
-
if (event.source === window)
|
|
4242
|
-
return;
|
|
4243
5014
|
const { data } = event;
|
|
4244
|
-
if (!data)
|
|
5015
|
+
if (!data || event.source === window)
|
|
5016
|
+
return;
|
|
5017
|
+
if (data.line === proto.iframeSignal) {
|
|
5018
|
+
const handshake = parseCrossDomainHandshake(data, proto.iframeSignal, this.projectKey);
|
|
5019
|
+
if (handshake !== null) {
|
|
5020
|
+
void this.establishCrossDomainFrame(event, handshake);
|
|
5021
|
+
}
|
|
4245
5022
|
return;
|
|
4246
|
-
// Record liveness regardless of our own active state so the queue can prune
|
|
4247
|
-
// stale contexts reliably once we resume dispatching commands after a cycle.
|
|
4248
|
-
if ((data.line === proto.polling || data.line === proto.iframeSignal) && data.context) {
|
|
4249
|
-
this.frameLastSeen.set(data.context, Date.now());
|
|
4250
5023
|
}
|
|
4251
5024
|
if (!this.active())
|
|
4252
5025
|
return;
|
|
4253
|
-
if (data.line === proto.
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
}
|
|
4260
|
-
const id = await this.checkNodeId(event.source);
|
|
4261
|
-
if (!id) {
|
|
4262
|
-
this.debug.log('Couldnt get node id for iframe', event.source);
|
|
4263
|
-
return;
|
|
4264
|
-
}
|
|
4265
|
-
try {
|
|
4266
|
-
if (this.trackedFrames.includes(data.context)) {
|
|
4267
|
-
this.debug.log('Trying to observe already added iframe; ignore if its a restart');
|
|
4268
|
-
}
|
|
4269
|
-
else {
|
|
4270
|
-
this.trackedFrames.push(data.context);
|
|
4271
|
-
}
|
|
4272
|
-
await this.waitStarted();
|
|
4273
|
-
const token = this.session.getSessionSecret(this.projectKey);
|
|
4274
|
-
const order = this.trackedFrames.findIndex((f) => f === data.context) + 1;
|
|
4275
|
-
if (order === 0) {
|
|
4276
|
-
this.debug.error('Couldnt get order number for iframe', data.context, this.trackedFrames);
|
|
4277
|
-
}
|
|
4278
|
-
const iframeData = {
|
|
4279
|
-
line: proto.iframeId,
|
|
4280
|
-
id,
|
|
4281
|
-
token,
|
|
4282
|
-
frameOrderNumber: order,
|
|
4283
|
-
frameLevel: this.frameLevel + 1,
|
|
4284
|
-
};
|
|
4285
|
-
this.debug.log('Got child frame signal; nodeId', id, event.source, iframeData);
|
|
4286
|
-
// @ts-ignore
|
|
4287
|
-
event.source?.postMessage(iframeData, '*');
|
|
4288
|
-
}
|
|
4289
|
-
catch (e) {
|
|
4290
|
-
console.error(e);
|
|
4291
|
-
}
|
|
4292
|
-
};
|
|
4293
|
-
void signalId();
|
|
5026
|
+
if (data.line === proto.polling) {
|
|
5027
|
+
const state = this.validatedCrossDomainFrame(event, proto.polling);
|
|
5028
|
+
if (state !== null && this.consumeCrossDomainEnvelopeBudget(state.source)) {
|
|
5029
|
+
this.postToCrossDomainFrame(state, proto.parentAlive);
|
|
5030
|
+
}
|
|
5031
|
+
return;
|
|
4294
5032
|
}
|
|
4295
5033
|
/**
|
|
4296
5034
|
* proxying messages from iframe to main body, so they can be in one batch (same indexes, etc)
|
|
4297
5035
|
* plus we rewrite some of the messages to be relative to the main context/window
|
|
4298
5036
|
* */
|
|
4299
5037
|
if (data.line === proto.iframeBatch) {
|
|
4300
|
-
const
|
|
5038
|
+
const state = this.validatedCrossDomainFrame(event, proto.iframeBatch);
|
|
5039
|
+
if (state === null || !this.consumeCrossDomainEnvelopeBudget(state.source)) {
|
|
5040
|
+
return;
|
|
5041
|
+
}
|
|
5042
|
+
const batch = parseCrossDomainIframeBatch(data.messages);
|
|
5043
|
+
if (batch === null ||
|
|
5044
|
+
!this.consumeCrossDomainFrameBudget(state, batch.messages.length, batch.bytes)) {
|
|
5045
|
+
return;
|
|
5046
|
+
}
|
|
4301
5047
|
const mappedMessages = [];
|
|
4302
|
-
|
|
5048
|
+
batch.messages.forEach((msg) => {
|
|
5049
|
+
let fixedMessage = msg;
|
|
4303
5050
|
if (msg[0] === 20 /* MType.MouseMove */) {
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
const [type, x, y] = msg;
|
|
4308
|
-
const { left, top } = frame.getBoundingClientRect();
|
|
4309
|
-
fixedMessage = [type, x + left, y + top];
|
|
4310
|
-
}
|
|
4311
|
-
});
|
|
4312
|
-
mappedMessages.push(fixedMessage);
|
|
5051
|
+
const [type, x, y] = msg;
|
|
5052
|
+
const { left, top } = state.frame.getBoundingClientRect();
|
|
5053
|
+
fixedMessage = [type, x + left, y + top];
|
|
4313
5054
|
}
|
|
4314
5055
|
if (msg[0] === 68 /* MType.MouseClick */) {
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
selector,
|
|
4333
|
-
Math.round(fixedX * 1e3) / 1e1,
|
|
4334
|
-
Math.round(fixedY * 1e3) / 1e1,
|
|
4335
|
-
];
|
|
4336
|
-
}
|
|
4337
|
-
});
|
|
4338
|
-
mappedMessages.push(fixedMessage);
|
|
4339
|
-
}
|
|
4340
|
-
if (![28 /* MType.UserID */, 29 /* MType.UserAnonymousID */, 30 /* MType.Metadata */].includes(msg[0])) {
|
|
4341
|
-
mappedMessages.push(msg);
|
|
5056
|
+
const [type, id, hesitationTime, label, selector, normX, normY] = msg;
|
|
5057
|
+
const { left, top, width, height } = state.frame.getBoundingClientRect();
|
|
5058
|
+
const contentWidth = document.documentElement.scrollWidth;
|
|
5059
|
+
const contentHeight = document.documentElement.scrollHeight;
|
|
5060
|
+
const fullX = (normX / 100) * width + left;
|
|
5061
|
+
const fullY = (normY / 100) * height + top;
|
|
5062
|
+
const fixedX = fullX / contentWidth;
|
|
5063
|
+
const fixedY = fullY / contentHeight;
|
|
5064
|
+
fixedMessage = [
|
|
5065
|
+
type,
|
|
5066
|
+
id,
|
|
5067
|
+
hesitationTime,
|
|
5068
|
+
label,
|
|
5069
|
+
selector,
|
|
5070
|
+
Math.round(fixedX * 1e3) / 1e1,
|
|
5071
|
+
Math.round(fixedY * 1e3) / 1e1,
|
|
5072
|
+
];
|
|
4342
5073
|
}
|
|
5074
|
+
mappedMessages.push(fixedMessage);
|
|
4343
5075
|
});
|
|
4344
5076
|
this.messages.push(...mappedMessages);
|
|
4345
5077
|
}
|
|
4346
|
-
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
const
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
break;
|
|
4361
|
-
}
|
|
5078
|
+
};
|
|
5079
|
+
this.bootChildrenFrames = async () => {
|
|
5080
|
+
await this.waitStarted();
|
|
5081
|
+
this.pruneDetachedCrossDomainFrames();
|
|
5082
|
+
for (const state of this.crossDomainFrames.values()) {
|
|
5083
|
+
const authorizationGeneration = this.crossDomainAuthorizationGeneration;
|
|
5084
|
+
const id = await this.checkNodeId(state.source);
|
|
5085
|
+
const session = this.currentCrossDomainSession();
|
|
5086
|
+
if (id === null ||
|
|
5087
|
+
session === null ||
|
|
5088
|
+
authorizationGeneration !== this.crossDomainAuthorizationGeneration ||
|
|
5089
|
+
this.crossDomainFrames.get(state.source) !== state ||
|
|
5090
|
+
!this.active()) {
|
|
5091
|
+
continue;
|
|
4362
5092
|
}
|
|
4363
|
-
|
|
4364
|
-
|
|
5093
|
+
const rotated = this.crossDomainFrameNeedsRotation(state, session.sessionID);
|
|
5094
|
+
if (rotated && !this.rotateCrossDomainFrameCapability(state, session.sessionID)) {
|
|
5095
|
+
continue;
|
|
4365
5096
|
}
|
|
4366
|
-
const
|
|
4367
|
-
if (
|
|
4368
|
-
this.
|
|
4369
|
-
const message = { line: nextCommand };
|
|
4370
|
-
if (nextCommand === proto.startIframe) {
|
|
4371
|
-
message.token = this.session.getSessionSecret(this.projectKey);
|
|
4372
|
-
const targetFrame = this.pageFrames.find((f) => f.contentWindow === event.source) ||
|
|
4373
|
-
Array.from(document.querySelectorAll('iframe')).find((f) => f.contentWindow === event.source);
|
|
4374
|
-
if (targetFrame) {
|
|
4375
|
-
const nodeId = targetFrame[this.options.node_id];
|
|
4376
|
-
if (nodeId !== undefined) {
|
|
4377
|
-
message.id = nodeId;
|
|
4378
|
-
}
|
|
4379
|
-
}
|
|
4380
|
-
}
|
|
4381
|
-
// @ts-ignore
|
|
4382
|
-
event.source?.postMessage(message, '*');
|
|
4383
|
-
if (this.pollingQueue[nextCommand].length === 0) {
|
|
4384
|
-
delete this.pollingQueue[nextCommand];
|
|
4385
|
-
this.pollingQueue.order.shift();
|
|
4386
|
-
}
|
|
5097
|
+
const payload = id === null ? null : this.frameInitPayload(state, id);
|
|
5098
|
+
if (payload) {
|
|
5099
|
+
this.postToCrossDomainFrame(state, rotated ? proto.iframeId : proto.startIframe, payload);
|
|
4387
5100
|
}
|
|
4388
5101
|
}
|
|
4389
5102
|
};
|
|
4390
|
-
/**
|
|
4391
|
-
* { command : [remaining iframes] }
|
|
4392
|
-
* + order of commands
|
|
4393
|
-
**/
|
|
4394
|
-
this.pollingQueue = {
|
|
4395
|
-
order: [],
|
|
4396
|
-
};
|
|
4397
|
-
this.addCommand = (cmd) => {
|
|
4398
|
-
this.pruneStaleFrames();
|
|
4399
|
-
if (this.pollingQueue[cmd]) {
|
|
4400
|
-
this.pollingQueue[cmd] = Array.from(new Set([...this.pollingQueue[cmd], ...this.trackedFrames]));
|
|
4401
|
-
return;
|
|
4402
|
-
}
|
|
4403
|
-
this.pollingQueue.order.push(cmd);
|
|
4404
|
-
this.pollingQueue[cmd] = [...this.trackedFrames];
|
|
4405
|
-
};
|
|
4406
|
-
this.bootChildrenFrames = async () => {
|
|
4407
|
-
await this.waitStarted();
|
|
4408
|
-
this.addCommand(proto.startIframe);
|
|
4409
|
-
};
|
|
4410
5103
|
this.killChildrenFrames = () => {
|
|
4411
|
-
this.
|
|
5104
|
+
this.pruneDetachedCrossDomainFrames();
|
|
5105
|
+
const states = Array.from(this.crossDomainFrames.values());
|
|
5106
|
+
this.crossDomainAuthorizationGeneration += 1;
|
|
5107
|
+
this.crossDomainFrames.clear();
|
|
5108
|
+
this.pendingCrossDomainFrames.clear();
|
|
5109
|
+
states.forEach((state) => {
|
|
5110
|
+
this.postToCrossDomainFrame(state, proto.killIframe);
|
|
5111
|
+
});
|
|
4412
5112
|
};
|
|
4413
5113
|
this.signalIframeTracker = () => {
|
|
4414
|
-
|
|
4415
|
-
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
|
|
4419
|
-
|
|
5114
|
+
if (this.crossDomainParentOrigin === null || this.crossDomainFrameNonce === null) {
|
|
5115
|
+
this.debug.error('Cross-domain iframe capture requires an explicit parentDomain and secure random support.');
|
|
5116
|
+
return;
|
|
5117
|
+
}
|
|
5118
|
+
this.crossDomainConnectionRequested = true;
|
|
5119
|
+
this.postHandshakeToParent();
|
|
4420
5120
|
/**
|
|
4421
5121
|
* since we need to wait uncertain amount of time
|
|
4422
5122
|
* and I don't want to have recursion going on,
|
|
@@ -4428,20 +5128,16 @@ class App {
|
|
|
4428
5128
|
let cumulativeDelay = 0;
|
|
4429
5129
|
let stopAttempts = false;
|
|
4430
5130
|
const checkAndSendMessage = () => {
|
|
4431
|
-
if (stopAttempts || this.checkStatus()) {
|
|
5131
|
+
if (stopAttempts || !this.crossDomainConnectionRequested || this.checkStatus()) {
|
|
4432
5132
|
stopAttempts = true;
|
|
4433
5133
|
return;
|
|
4434
5134
|
}
|
|
4435
|
-
|
|
4436
|
-
line: proto.iframeSignal,
|
|
4437
|
-
source: thisTab,
|
|
4438
|
-
context: this.contextId,
|
|
4439
|
-
}, this.options.crossdomain?.parentDomain ?? '*');
|
|
5135
|
+
this.postHandshakeToParent();
|
|
4440
5136
|
this.debug.info('Trying to signal to parent, attempt:', retries + 1);
|
|
4441
5137
|
retries++;
|
|
4442
5138
|
};
|
|
4443
5139
|
for (let i = 0; i < maxRetries; i++) {
|
|
4444
|
-
if (this.checkStatus()) {
|
|
5140
|
+
if (!this.crossDomainConnectionRequested || this.checkStatus()) {
|
|
4445
5141
|
stopAttempts = true;
|
|
4446
5142
|
break;
|
|
4447
5143
|
}
|
|
@@ -4459,10 +5155,10 @@ class App {
|
|
|
4459
5155
|
this.allowAppStart();
|
|
4460
5156
|
this.start(this.prevOpts, true)
|
|
4461
5157
|
.then((r) => {
|
|
4462
|
-
this.debug.info('Session restart', r);
|
|
5158
|
+
this.debug.info('Session restart', redactDebugValue(r));
|
|
4463
5159
|
})
|
|
4464
5160
|
.catch((e) => {
|
|
4465
|
-
this.debug.error('Session restart failed', e);
|
|
5161
|
+
this.debug.error('Session restart failed', redactDebugValue(e));
|
|
4466
5162
|
});
|
|
4467
5163
|
});
|
|
4468
5164
|
};
|
|
@@ -4598,9 +5294,7 @@ class App {
|
|
|
4598
5294
|
maskTextEmails: false,
|
|
4599
5295
|
maskTextNumbers: false,
|
|
4600
5296
|
disableStringDict: false,
|
|
4601
|
-
crossdomain: {
|
|
4602
|
-
parentDomain: '*',
|
|
4603
|
-
},
|
|
5297
|
+
crossdomain: {},
|
|
4604
5298
|
canvas: {
|
|
4605
5299
|
disableCanvas: false,
|
|
4606
5300
|
fixedCanvasScaling: false,
|
|
@@ -4613,6 +5307,9 @@ class App {
|
|
|
4613
5307
|
disableThrottling: false,
|
|
4614
5308
|
};
|
|
4615
5309
|
this.options = simpleMerge(defaultOptions, options);
|
|
5310
|
+
this.crossDomainChildOrigins = normalizeCrossDomainOrigins(this.options.crossdomain?.childOrigins);
|
|
5311
|
+
this.crossDomainParentOrigin = normalizeCrossDomainOrigin(this.options.crossdomain?.parentDomain);
|
|
5312
|
+
this.crossDomainFrameNonce = this.insideIframe ? createCrossDomainSecret() : null;
|
|
4616
5313
|
if (!this.insideIframe &&
|
|
4617
5314
|
!this.options.forceSingleTab &&
|
|
4618
5315
|
globalThis &&
|
|
@@ -4643,7 +5340,7 @@ class App {
|
|
|
4643
5340
|
});
|
|
4644
5341
|
this.markerWatcher = new MarkerWatcher({
|
|
4645
5342
|
sessionStorage: this.sessionStorage,
|
|
4646
|
-
errLog: this.debug.error,
|
|
5343
|
+
errLog: (...args) => this.debug.error(...args.map((arg) => redactDebugValue(arg))),
|
|
4647
5344
|
onMarkerHit: (markerId) => this.send(TagTrigger(markerId)),
|
|
4648
5345
|
});
|
|
4649
5346
|
this.session.attachUpdateCallback(({ userID, metadata }) => {
|
|
@@ -4659,22 +5356,33 @@ class App {
|
|
|
4659
5356
|
}
|
|
4660
5357
|
});
|
|
4661
5358
|
// @deprecated (use sessionHash on start instead)
|
|
4662
|
-
if (sessionToken != null) {
|
|
5359
|
+
if (sessionToken != null && !this.insideIframe) {
|
|
4663
5360
|
this.session.applySessionHash(sessionToken);
|
|
4664
5361
|
}
|
|
5362
|
+
if (this.insideIframe) {
|
|
5363
|
+
this.session.clearSessionSecret();
|
|
5364
|
+
}
|
|
4665
5365
|
const thisTab = this.session.getTabId();
|
|
4666
5366
|
if (this.insideIframe) {
|
|
4667
|
-
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
|
|
4673
|
-
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
|
|
4677
|
-
|
|
5367
|
+
if (this.crossDomainParentOrigin !== null && this.crossDomainFrameNonce !== null) {
|
|
5368
|
+
/**
|
|
5369
|
+
* listen for messages from parent window, so we can signal that we're alive
|
|
5370
|
+
* */
|
|
5371
|
+
window.addEventListener('message', this.parentCrossDomainFrameListener);
|
|
5372
|
+
setInterval(() => {
|
|
5373
|
+
if (!this.crossDomainConnectionRequested) {
|
|
5374
|
+
return;
|
|
5375
|
+
}
|
|
5376
|
+
if (this.crossDomainFrameCapability === null ||
|
|
5377
|
+
Date.now() >=
|
|
5378
|
+
this.crossDomainFrameCapabilityExpiresAt - FRAME_CAPABILITY_REFRESH_LEAD_MS) {
|
|
5379
|
+
this.postHandshakeToParent();
|
|
5380
|
+
}
|
|
5381
|
+
else {
|
|
5382
|
+
this.postToParent(proto.polling);
|
|
5383
|
+
}
|
|
5384
|
+
}, 1000);
|
|
5385
|
+
}
|
|
4678
5386
|
}
|
|
4679
5387
|
else {
|
|
4680
5388
|
this.initWorker();
|
|
@@ -4682,7 +5390,9 @@ class App {
|
|
|
4682
5390
|
* if we get a signal from child iframes, we check for their node_id and send it back,
|
|
4683
5391
|
* so they can act as if it was just a same-domain iframe
|
|
4684
5392
|
* */
|
|
4685
|
-
|
|
5393
|
+
if (this.options.crossdomain?.enabled && this.crossDomainChildOrigins.size > 0) {
|
|
5394
|
+
window.addEventListener('message', this.crossDomainIframeListener);
|
|
5395
|
+
}
|
|
4686
5396
|
}
|
|
4687
5397
|
if (this.bc !== null) {
|
|
4688
5398
|
this.bc.postMessage({
|
|
@@ -4696,10 +5406,10 @@ class App {
|
|
|
4696
5406
|
}, 250);
|
|
4697
5407
|
this.bc.onmessage = (ev) => {
|
|
4698
5408
|
if (ev.data.context === this.contextId || this.projectKey !== ev.data.projectKey) {
|
|
4699
|
-
this.debug.log('same ctx event', ev);
|
|
5409
|
+
this.debug.log('same ctx event', redactDebugValue(ev.data));
|
|
4700
5410
|
return;
|
|
4701
5411
|
}
|
|
4702
|
-
this.debug.log(ev);
|
|
5412
|
+
this.debug.log('broadcast event', redactDebugValue(ev.data));
|
|
4703
5413
|
if (ev.data.line === proto.resp) {
|
|
4704
5414
|
const sessionToken = ev.data.token;
|
|
4705
5415
|
this.session.setSessionSecret(sessionToken, this.projectKey);
|
|
@@ -4732,15 +5442,318 @@ class App {
|
|
|
4732
5442
|
};
|
|
4733
5443
|
}
|
|
4734
5444
|
}
|
|
4735
|
-
|
|
4736
|
-
|
|
4737
|
-
|
|
4738
|
-
|
|
4739
|
-
|
|
4740
|
-
|
|
4741
|
-
|
|
5445
|
+
postToParent(line, extra = {}) {
|
|
5446
|
+
if (this.crossDomainParentOrigin === null ||
|
|
5447
|
+
this.crossDomainFrameNonce === null ||
|
|
5448
|
+
this.crossDomainFrameCapability === null ||
|
|
5449
|
+
Date.now() >= this.crossDomainFrameCapabilityExpiresAt) {
|
|
5450
|
+
return;
|
|
5451
|
+
}
|
|
5452
|
+
window.parent.postMessage({
|
|
5453
|
+
line,
|
|
5454
|
+
protocolVersion: CROSS_DOMAIN_IFRAME_PROTOCOL_VERSION,
|
|
5455
|
+
context: this.contextId,
|
|
5456
|
+
nonce: this.crossDomainFrameNonce,
|
|
5457
|
+
capability: this.crossDomainFrameCapability,
|
|
5458
|
+
...extra,
|
|
5459
|
+
}, this.crossDomainParentOrigin);
|
|
5460
|
+
}
|
|
5461
|
+
postHandshakeToParent() {
|
|
5462
|
+
if (this.crossDomainParentOrigin === null || this.crossDomainFrameNonce === null) {
|
|
5463
|
+
return;
|
|
5464
|
+
}
|
|
5465
|
+
window.parent.postMessage({
|
|
5466
|
+
line: proto.iframeSignal,
|
|
5467
|
+
protocolVersion: CROSS_DOMAIN_IFRAME_PROTOCOL_VERSION,
|
|
5468
|
+
context: this.contextId,
|
|
5469
|
+
nonce: this.crossDomainFrameNonce,
|
|
5470
|
+
projectKey: this.projectKey,
|
|
5471
|
+
}, this.crossDomainParentOrigin);
|
|
5472
|
+
}
|
|
5473
|
+
applyCrossDomainFrameInit(init) {
|
|
5474
|
+
this.parentActive = true;
|
|
5475
|
+
this.crossDomainConnectionRequested = true;
|
|
5476
|
+
this.crossDomainFrameCapability = init.capability;
|
|
5477
|
+
this.crossDomainFrameCapabilityExpiresAt = init.capabilityExpiresAt;
|
|
5478
|
+
this.crossDomainFrameSession = init.session;
|
|
5479
|
+
this.crossDomainFrameCanvas = init.canvas ?? null;
|
|
5480
|
+
this.rootId = init.id;
|
|
5481
|
+
this.frameOderNumber = init.frameOrderNumber;
|
|
5482
|
+
this.frameLevel = init.frameLevel;
|
|
5483
|
+
this.allowAppStart();
|
|
5484
|
+
}
|
|
5485
|
+
clearCrossDomainFrameAuthorization() {
|
|
5486
|
+
this.parentActive = false;
|
|
5487
|
+
this.canStart = false;
|
|
5488
|
+
this.crossDomainFrameCapability = null;
|
|
5489
|
+
this.crossDomainFrameCapabilityExpiresAt = 0;
|
|
5490
|
+
this.crossDomainFrameSession = null;
|
|
5491
|
+
this.crossDomainFrameCanvas = null;
|
|
5492
|
+
this.rootId = null;
|
|
5493
|
+
}
|
|
5494
|
+
findCrossDomainFrame(source) {
|
|
5495
|
+
const frame = this.pageFrames.find((candidate) => candidate.contentWindow === source) ??
|
|
5496
|
+
Array.from(document.querySelectorAll('iframe')).find((candidate) => candidate.contentWindow === source);
|
|
5497
|
+
if (!frame)
|
|
5498
|
+
return null;
|
|
5499
|
+
if (!this.pageFrames.includes(frame)) {
|
|
5500
|
+
this.pageFrames.push(frame);
|
|
5501
|
+
}
|
|
5502
|
+
return frame;
|
|
5503
|
+
}
|
|
5504
|
+
revokeCrossDomainFrame(source, expectedState) {
|
|
5505
|
+
const state = this.crossDomainFrames.get(source);
|
|
5506
|
+
if (!state || (expectedState !== undefined && state !== expectedState)) {
|
|
5507
|
+
return;
|
|
5508
|
+
}
|
|
5509
|
+
this.crossDomainFrames.delete(source);
|
|
5510
|
+
}
|
|
5511
|
+
pruneDetachedCrossDomainFrames() {
|
|
5512
|
+
this.crossDomainFrames.forEach((state, source) => {
|
|
5513
|
+
if (!state.frame.isConnected || state.frame.contentWindow !== source) {
|
|
5514
|
+
this.revokeCrossDomainFrame(source);
|
|
5515
|
+
}
|
|
5516
|
+
});
|
|
5517
|
+
}
|
|
5518
|
+
currentCrossDomainSession() {
|
|
5519
|
+
const session = this.session.getInfo();
|
|
5520
|
+
if (!session.sessionID) {
|
|
5521
|
+
return null;
|
|
5522
|
+
}
|
|
5523
|
+
return {
|
|
5524
|
+
sessionID: session.sessionID,
|
|
5525
|
+
projectID: session.projectID,
|
|
5526
|
+
startedAt: session.timestamp,
|
|
5527
|
+
delayMs: this.delay,
|
|
5528
|
+
userUUID: this.localStorage.getItem(this.options.local_uuid_key) ?? '',
|
|
5529
|
+
};
|
|
5530
|
+
}
|
|
5531
|
+
frameInitPayload(state, id) {
|
|
5532
|
+
const session = this.currentCrossDomainSession();
|
|
5533
|
+
if (session === null ||
|
|
5534
|
+
session.sessionID !== state.sessionID ||
|
|
5535
|
+
Date.now() >= state.capabilityExpiresAt) {
|
|
5536
|
+
return null;
|
|
5537
|
+
}
|
|
5538
|
+
return {
|
|
5539
|
+
id,
|
|
5540
|
+
frameOrderNumber: state.frameOrderNumber,
|
|
5541
|
+
frameLevel: this.frameLevel + 1,
|
|
5542
|
+
capabilityExpiresAt: state.capabilityExpiresAt,
|
|
5543
|
+
session,
|
|
5544
|
+
...(this.crossDomainCanvasConfig ? { canvas: this.crossDomainCanvasConfig } : {}),
|
|
5545
|
+
};
|
|
5546
|
+
}
|
|
5547
|
+
rotateCrossDomainFrameCapability(state, sessionID, now = Date.now()) {
|
|
5548
|
+
const capability = createCrossDomainSecret();
|
|
5549
|
+
if (capability === null) {
|
|
5550
|
+
return false;
|
|
5551
|
+
}
|
|
5552
|
+
const preservePreviousCapability = state.sessionID === sessionID && now < state.capabilityExpiresAt;
|
|
5553
|
+
state.previousCapability = preservePreviousCapability ? state.capability : null;
|
|
5554
|
+
state.previousCapabilityExpiresAt = preservePreviousCapability
|
|
5555
|
+
? Math.min(state.capabilityExpiresAt, now + FRAME_PREVIOUS_CAPABILITY_GRACE_MS)
|
|
5556
|
+
: 0;
|
|
5557
|
+
state.capability = capability;
|
|
5558
|
+
state.sessionID = sessionID;
|
|
5559
|
+
state.capabilityExpiresAt = now + FRAME_CAPABILITY_TTL_MS;
|
|
5560
|
+
return true;
|
|
5561
|
+
}
|
|
5562
|
+
crossDomainFrameNeedsRotation(state, sessionID, now = Date.now()) {
|
|
5563
|
+
return (state.sessionID !== sessionID ||
|
|
5564
|
+
now >= state.capabilityExpiresAt - FRAME_CAPABILITY_REFRESH_LEAD_MS);
|
|
5565
|
+
}
|
|
5566
|
+
postToCrossDomainFrame(state, line, extra = {}) {
|
|
5567
|
+
state.source.postMessage({
|
|
5568
|
+
line,
|
|
5569
|
+
protocolVersion: CROSS_DOMAIN_IFRAME_PROTOCOL_VERSION,
|
|
5570
|
+
context: state.context,
|
|
5571
|
+
nonce: state.nonce,
|
|
5572
|
+
capability: state.capability,
|
|
5573
|
+
...extra,
|
|
5574
|
+
}, state.origin);
|
|
5575
|
+
}
|
|
5576
|
+
consumeCrossDomainEnvelopeBudget(source) {
|
|
5577
|
+
const now = Date.now();
|
|
5578
|
+
let budget = this.crossDomainEnvelopeBudgets.get(source);
|
|
5579
|
+
if (budget === undefined || now - budget.windowStartedAt >= FRAME_RATE_WINDOW_MS) {
|
|
5580
|
+
budget = {
|
|
5581
|
+
windowStartedAt: now,
|
|
5582
|
+
envelopesInWindow: 0,
|
|
5583
|
+
};
|
|
5584
|
+
this.crossDomainEnvelopeBudgets.set(source, budget);
|
|
5585
|
+
}
|
|
5586
|
+
if (budget.envelopesInWindow >= MAX_FRAME_ENVELOPES_PER_WINDOW) {
|
|
5587
|
+
return false;
|
|
5588
|
+
}
|
|
5589
|
+
budget.envelopesInWindow += 1;
|
|
5590
|
+
return true;
|
|
5591
|
+
}
|
|
5592
|
+
async establishCrossDomainFrame(event, handshake) {
|
|
5593
|
+
if (!this.active() ||
|
|
5594
|
+
event.source === null ||
|
|
5595
|
+
!this.crossDomainChildOrigins.has(event.origin)) {
|
|
5596
|
+
return;
|
|
5597
|
+
}
|
|
5598
|
+
const candidateSource = event.source;
|
|
5599
|
+
if (!this.consumeCrossDomainEnvelopeBudget(candidateSource)) {
|
|
5600
|
+
return;
|
|
5601
|
+
}
|
|
5602
|
+
const frame = this.findCrossDomainFrame(candidateSource);
|
|
5603
|
+
if (frame === null || !frame.isConnected || frame.contentWindow === null) {
|
|
5604
|
+
return;
|
|
5605
|
+
}
|
|
5606
|
+
const source = frame.contentWindow;
|
|
5607
|
+
const existing = this.crossDomainFrames.get(source);
|
|
5608
|
+
if (this.pendingCrossDomainFrames.has(source)) {
|
|
5609
|
+
return;
|
|
5610
|
+
}
|
|
5611
|
+
const existingMatches = existing !== undefined &&
|
|
5612
|
+
existing.origin === event.origin &&
|
|
5613
|
+
existing.context === handshake.context &&
|
|
5614
|
+
existing.nonce === handshake.nonce &&
|
|
5615
|
+
existing.frame === frame;
|
|
5616
|
+
if (existing !== undefined && !existingMatches) {
|
|
5617
|
+
return;
|
|
5618
|
+
}
|
|
5619
|
+
const authorizationGeneration = this.crossDomainAuthorizationGeneration;
|
|
5620
|
+
const pendingToken = Symbol();
|
|
5621
|
+
this.pendingCrossDomainFrames.set(source, pendingToken);
|
|
5622
|
+
let navigated = false;
|
|
5623
|
+
const markNavigated = () => {
|
|
5624
|
+
navigated = true;
|
|
5625
|
+
};
|
|
5626
|
+
frame.addEventListener('load', markNavigated, { once: true });
|
|
5627
|
+
try {
|
|
5628
|
+
const id = await this.checkNodeId(source);
|
|
5629
|
+
if (id === null ||
|
|
5630
|
+
navigated ||
|
|
5631
|
+
!this.active() ||
|
|
5632
|
+
authorizationGeneration !== this.crossDomainAuthorizationGeneration ||
|
|
5633
|
+
!frame.isConnected ||
|
|
5634
|
+
frame.contentWindow !== source) {
|
|
5635
|
+
return;
|
|
5636
|
+
}
|
|
5637
|
+
const session = this.currentCrossDomainSession();
|
|
5638
|
+
if (session === null) {
|
|
5639
|
+
return;
|
|
5640
|
+
}
|
|
5641
|
+
const now = Date.now();
|
|
5642
|
+
let state;
|
|
5643
|
+
if (existingMatches) {
|
|
5644
|
+
state = existing;
|
|
5645
|
+
if (this.crossDomainFrameNeedsRotation(state, session.sessionID, now) &&
|
|
5646
|
+
!this.rotateCrossDomainFrameCapability(state, session.sessionID, now)) {
|
|
5647
|
+
return;
|
|
5648
|
+
}
|
|
5649
|
+
state.lastSeenAt = now;
|
|
5650
|
+
}
|
|
5651
|
+
else {
|
|
5652
|
+
const capability = createCrossDomainSecret();
|
|
5653
|
+
if (capability === null) {
|
|
5654
|
+
return;
|
|
5655
|
+
}
|
|
5656
|
+
state = {
|
|
5657
|
+
source,
|
|
5658
|
+
frame,
|
|
5659
|
+
origin: event.origin,
|
|
5660
|
+
protocolVersion: CROSS_DOMAIN_IFRAME_PROTOCOL_VERSION,
|
|
5661
|
+
context: handshake.context,
|
|
5662
|
+
nonce: handshake.nonce,
|
|
5663
|
+
capability,
|
|
5664
|
+
frameOrderNumber: this.nextCrossDomainFrameOrder++,
|
|
5665
|
+
sessionID: session.sessionID,
|
|
5666
|
+
capabilityExpiresAt: now + FRAME_CAPABILITY_TTL_MS,
|
|
5667
|
+
previousCapability: null,
|
|
5668
|
+
previousCapabilityExpiresAt: 0,
|
|
5669
|
+
lastSeenAt: now,
|
|
5670
|
+
rateWindowStartedAt: now,
|
|
5671
|
+
messagesInRateWindow: 0,
|
|
5672
|
+
bytesInRateWindow: 0,
|
|
5673
|
+
};
|
|
5674
|
+
}
|
|
5675
|
+
const payload = this.frameInitPayload(state, id);
|
|
5676
|
+
if (payload === null) {
|
|
5677
|
+
return;
|
|
5678
|
+
}
|
|
5679
|
+
if (!existingMatches) {
|
|
5680
|
+
if (existing !== undefined) {
|
|
5681
|
+
this.revokeCrossDomainFrame(source, existing);
|
|
5682
|
+
}
|
|
5683
|
+
this.crossDomainFrames.set(source, state);
|
|
5684
|
+
frame.addEventListener('load', () => {
|
|
5685
|
+
this.revokeCrossDomainFrame(source, state);
|
|
5686
|
+
}, { once: true });
|
|
5687
|
+
}
|
|
5688
|
+
this.postToCrossDomainFrame(state, proto.iframeId, payload);
|
|
5689
|
+
}
|
|
5690
|
+
finally {
|
|
5691
|
+
frame.removeEventListener('load', markNavigated);
|
|
5692
|
+
if (this.pendingCrossDomainFrames.get(source) === pendingToken) {
|
|
5693
|
+
this.pendingCrossDomainFrames.delete(source);
|
|
5694
|
+
}
|
|
5695
|
+
}
|
|
5696
|
+
}
|
|
5697
|
+
validatedCrossDomainFrame(event, expectedLine) {
|
|
5698
|
+
if (event.source === null ||
|
|
5699
|
+
!this.crossDomainChildOrigins.has(event.origin) ||
|
|
5700
|
+
event.source === window) {
|
|
5701
|
+
return null;
|
|
5702
|
+
}
|
|
5703
|
+
const source = event.source;
|
|
5704
|
+
const state = this.crossDomainFrames.get(source);
|
|
5705
|
+
const now = Date.now();
|
|
5706
|
+
const currentSessionID = this.session.getInfo().sessionID;
|
|
5707
|
+
if (!state ||
|
|
5708
|
+
state.origin !== event.origin ||
|
|
5709
|
+
!state.frame.isConnected ||
|
|
5710
|
+
state.frame.contentWindow !== source) {
|
|
5711
|
+
if (state) {
|
|
5712
|
+
this.revokeCrossDomainFrame(source);
|
|
5713
|
+
}
|
|
5714
|
+
return null;
|
|
5715
|
+
}
|
|
5716
|
+
if (currentSessionID === undefined || state.sessionID !== currentSessionID) {
|
|
5717
|
+
this.revokeCrossDomainFrame(source);
|
|
5718
|
+
return null;
|
|
5719
|
+
}
|
|
5720
|
+
const matchesCurrentCapability = now < state.capabilityExpiresAt && matchesCrossDomainEnvelope(event.data, expectedLine, state);
|
|
5721
|
+
const matchesPreviousCapability = state.previousCapability !== null &&
|
|
5722
|
+
now < state.previousCapabilityExpiresAt &&
|
|
5723
|
+
matchesCrossDomainEnvelope(event.data, expectedLine, {
|
|
5724
|
+
protocolVersion: state.protocolVersion,
|
|
5725
|
+
context: state.context,
|
|
5726
|
+
nonce: state.nonce,
|
|
5727
|
+
capability: state.previousCapability,
|
|
5728
|
+
});
|
|
5729
|
+
if (!matchesCurrentCapability && !matchesPreviousCapability) {
|
|
5730
|
+
if (now >= state.capabilityExpiresAt && now >= state.previousCapabilityExpiresAt) {
|
|
5731
|
+
this.revokeCrossDomainFrame(source);
|
|
5732
|
+
}
|
|
5733
|
+
return null;
|
|
5734
|
+
}
|
|
5735
|
+
if ((matchesCurrentCapability && state.previousCapability !== null) ||
|
|
5736
|
+
now >= state.previousCapabilityExpiresAt) {
|
|
5737
|
+
state.previousCapability = null;
|
|
5738
|
+
state.previousCapabilityExpiresAt = 0;
|
|
5739
|
+
}
|
|
5740
|
+
state.lastSeenAt = now;
|
|
5741
|
+
return state;
|
|
5742
|
+
}
|
|
5743
|
+
consumeCrossDomainFrameBudget(state, messageCount, bytes) {
|
|
5744
|
+
const now = Date.now();
|
|
5745
|
+
if (now - state.rateWindowStartedAt >= FRAME_RATE_WINDOW_MS) {
|
|
5746
|
+
state.rateWindowStartedAt = now;
|
|
5747
|
+
state.messagesInRateWindow = 0;
|
|
5748
|
+
state.bytesInRateWindow = 0;
|
|
5749
|
+
}
|
|
5750
|
+
if (state.messagesInRateWindow + messageCount > MAX_FRAME_MESSAGES_PER_WINDOW ||
|
|
5751
|
+
state.bytesInRateWindow + bytes > MAX_FRAME_BYTES_PER_WINDOW) {
|
|
4742
5752
|
return false;
|
|
4743
|
-
}
|
|
5753
|
+
}
|
|
5754
|
+
state.messagesInRateWindow += messageCount;
|
|
5755
|
+
state.bytesInRateWindow += bytes;
|
|
5756
|
+
return true;
|
|
4744
5757
|
}
|
|
4745
5758
|
allowAppStart() {
|
|
4746
5759
|
this.canStart = true;
|
|
@@ -4801,6 +5814,7 @@ class App {
|
|
|
4801
5814
|
setTimeout(() => {
|
|
4802
5815
|
closing = false;
|
|
4803
5816
|
}, 500);
|
|
5817
|
+
this.canvasRecorder?.flushPendingSnapshots();
|
|
4804
5818
|
if (this.worker) {
|
|
4805
5819
|
this.worker.postMessage('closing');
|
|
4806
5820
|
}
|
|
@@ -4824,10 +5838,10 @@ class App {
|
|
|
4824
5838
|
this.allowAppStart();
|
|
4825
5839
|
this.start(this.prevOpts, true)
|
|
4826
5840
|
.then((r) => {
|
|
4827
|
-
this.debug.info('Worker restart, session too long', r);
|
|
5841
|
+
this.debug.info('Worker restart, session too long', redactDebugValue(r));
|
|
4828
5842
|
})
|
|
4829
5843
|
.catch((e) => {
|
|
4830
|
-
this.debug.error('Worker restart failed', e);
|
|
5844
|
+
this.debug.error('Worker restart failed', redactDebugValue(e));
|
|
4831
5845
|
});
|
|
4832
5846
|
});
|
|
4833
5847
|
}
|
|
@@ -4836,7 +5850,7 @@ class App {
|
|
|
4836
5850
|
}
|
|
4837
5851
|
else if (data.type === 'failure') {
|
|
4838
5852
|
this.stop(false);
|
|
4839
|
-
this.debug.error('worker_failed', data.reason);
|
|
5853
|
+
this.debug.error('worker_failed', redactDebugValue(data.reason));
|
|
4840
5854
|
this._debug('worker_failed', data.reason);
|
|
4841
5855
|
}
|
|
4842
5856
|
else if (data.type === 'compress') {
|
|
@@ -4857,7 +5871,7 @@ class App {
|
|
|
4857
5871
|
});
|
|
4858
5872
|
})
|
|
4859
5873
|
.catch((err) => {
|
|
4860
|
-
this.debug.error('OxvoSessions compression error:', err);
|
|
5874
|
+
this.debug.error('OxvoSessions compression error:', redactDebugValue(err));
|
|
4861
5875
|
this.worker?.postMessage({ type: 'uncompressed', batch: batch, dataType });
|
|
4862
5876
|
});
|
|
4863
5877
|
}
|
|
@@ -4879,18 +5893,18 @@ class App {
|
|
|
4879
5893
|
}
|
|
4880
5894
|
}
|
|
4881
5895
|
_debug(context, e) {
|
|
5896
|
+
const redactedError = redactDebugValue(e);
|
|
4882
5897
|
if (this.options.__debug_report_edp !== null) {
|
|
4883
5898
|
void fetch(this.options.__debug_report_edp, {
|
|
4884
5899
|
method: 'POST',
|
|
4885
5900
|
headers: { 'Content-Type': 'application/json' },
|
|
4886
5901
|
body: JSON.stringify({
|
|
4887
5902
|
context,
|
|
4888
|
-
|
|
4889
|
-
error: `${e}`,
|
|
5903
|
+
error: debugValueText(redactedError),
|
|
4890
5904
|
}),
|
|
4891
5905
|
});
|
|
4892
5906
|
}
|
|
4893
|
-
this.debug.error('OxvoSessions error: ', context,
|
|
5907
|
+
this.debug.error('OxvoSessions error: ', context, redactedError);
|
|
4894
5908
|
}
|
|
4895
5909
|
/**
|
|
4896
5910
|
* Normal workflow: add timestamp and tab data to batch, then commit it
|
|
@@ -4904,10 +5918,7 @@ class App {
|
|
|
4904
5918
|
return;
|
|
4905
5919
|
}
|
|
4906
5920
|
if (this.insideIframe) {
|
|
4907
|
-
|
|
4908
|
-
line: proto.iframeBatch,
|
|
4909
|
-
messages: this.messages,
|
|
4910
|
-
}, this.options.crossdomain?.parentDomain ?? '*');
|
|
5921
|
+
this.postToParent(proto.iframeBatch, { messages: this.messages });
|
|
4911
5922
|
this.commitCallbacks.forEach((cb) => cb(this.messages));
|
|
4912
5923
|
this.messages.length = 0;
|
|
4913
5924
|
return;
|
|
@@ -5063,12 +6074,230 @@ class App {
|
|
|
5063
6074
|
},
|
|
5064
6075
|
};
|
|
5065
6076
|
}
|
|
6077
|
+
getBootstrapAttemptStorageKey(kind) {
|
|
6078
|
+
return `${this.options.session_token_key}_bootstrap_idempotency_${kind}`;
|
|
6079
|
+
}
|
|
6080
|
+
isValidBootstrapAttempt(value, kind, currentTime) {
|
|
6081
|
+
if (typeof value !== 'object' || value === null) {
|
|
6082
|
+
return false;
|
|
6083
|
+
}
|
|
6084
|
+
const attempt = value;
|
|
6085
|
+
if (attempt.version !== 1 ||
|
|
6086
|
+
attempt.projectKey !== this.projectKey ||
|
|
6087
|
+
typeof attempt.createdAt !== 'number' ||
|
|
6088
|
+
!Number.isFinite(attempt.createdAt) ||
|
|
6089
|
+
currentTime - attempt.createdAt >= BOOTSTRAP_ATTEMPT_TTL_MS ||
|
|
6090
|
+
typeof attempt.idempotencyKey !== 'string' ||
|
|
6091
|
+
attempt.idempotencyKey.length < 8 ||
|
|
6092
|
+
attempt.idempotencyKey.length > 128 ||
|
|
6093
|
+
!/^[A-Za-z0-9._:-]+$/.test(attempt.idempotencyKey) ||
|
|
6094
|
+
typeof attempt.intentFingerprint !== 'string' ||
|
|
6095
|
+
attempt.intentFingerprint.length === 0 ||
|
|
6096
|
+
attempt.intentFingerprint.length > MAX_BOOTSTRAP_ATTEMPT_STORAGE_LENGTH ||
|
|
6097
|
+
typeof attempt.requestTimestamp !== 'number' ||
|
|
6098
|
+
!Number.isFinite(attempt.requestTimestamp) ||
|
|
6099
|
+
typeof attempt.workerTimestamp !== 'number' ||
|
|
6100
|
+
!Number.isFinite(attempt.workerTimestamp) ||
|
|
6101
|
+
typeof attempt.requestBody !== 'string' ||
|
|
6102
|
+
attempt.requestBody.length > MAX_BOOTSTRAP_ATTEMPT_STORAGE_LENGTH) {
|
|
6103
|
+
return false;
|
|
6104
|
+
}
|
|
6105
|
+
try {
|
|
6106
|
+
const body = JSON.parse(attempt.requestBody);
|
|
6107
|
+
const intent = this.parseBootstrapIntent(attempt.intentFingerprint);
|
|
6108
|
+
return (intent?.projectKey === this.projectKey &&
|
|
6109
|
+
intent.kind === kind &&
|
|
6110
|
+
body.app?.key === this.projectKey &&
|
|
6111
|
+
body.session?.dry === false &&
|
|
6112
|
+
body.session?.offline === (kind === 'offline') &&
|
|
6113
|
+
typeof body.session?.token === 'string' &&
|
|
6114
|
+
body.session.token === intent.token &&
|
|
6115
|
+
body.session?.rule === intent.rule &&
|
|
6116
|
+
(body.user?.id ?? '') === intent.userID &&
|
|
6117
|
+
(body.user?.uuid ?? '') === intent.userUUID &&
|
|
6118
|
+
body.meta?.ts === attempt.requestTimestamp &&
|
|
6119
|
+
attempt.idempotencyKey.startsWith(`bootstrap:${kind}:`));
|
|
6120
|
+
}
|
|
6121
|
+
catch {
|
|
6122
|
+
return false;
|
|
6123
|
+
}
|
|
6124
|
+
}
|
|
6125
|
+
removeStoredBootstrapAttempt(kind) {
|
|
6126
|
+
delete this.bootstrapAttempts[kind];
|
|
6127
|
+
try {
|
|
6128
|
+
this.sessionStorage.removeItem(this.getBootstrapAttemptStorageKey(kind));
|
|
6129
|
+
}
|
|
6130
|
+
catch {
|
|
6131
|
+
// The in-memory copy is still cleared when storage is unavailable.
|
|
6132
|
+
}
|
|
6133
|
+
}
|
|
6134
|
+
loadBootstrapAttempt(kind) {
|
|
6135
|
+
const currentTime = Date.now();
|
|
6136
|
+
const inMemoryAttempt = this.bootstrapAttempts[kind];
|
|
6137
|
+
if (this.isValidBootstrapAttempt(inMemoryAttempt, kind, currentTime)) {
|
|
6138
|
+
return inMemoryAttempt;
|
|
6139
|
+
}
|
|
6140
|
+
delete this.bootstrapAttempts[kind];
|
|
6141
|
+
try {
|
|
6142
|
+
const storedAttempt = this.sessionStorage.getItem(this.getBootstrapAttemptStorageKey(kind));
|
|
6143
|
+
if (storedAttempt === null || storedAttempt.length > MAX_BOOTSTRAP_ATTEMPT_STORAGE_LENGTH) {
|
|
6144
|
+
if (storedAttempt !== null) {
|
|
6145
|
+
this.removeStoredBootstrapAttempt(kind);
|
|
6146
|
+
}
|
|
6147
|
+
return undefined;
|
|
6148
|
+
}
|
|
6149
|
+
const parsedAttempt = JSON.parse(storedAttempt);
|
|
6150
|
+
if (this.isValidBootstrapAttempt(parsedAttempt, kind, currentTime)) {
|
|
6151
|
+
this.bootstrapAttempts[kind] = parsedAttempt;
|
|
6152
|
+
return parsedAttempt;
|
|
6153
|
+
}
|
|
6154
|
+
}
|
|
6155
|
+
catch {
|
|
6156
|
+
// Treat corrupt or inaccessible storage as an absent pending attempt.
|
|
6157
|
+
}
|
|
6158
|
+
this.removeStoredBootstrapAttempt(kind);
|
|
6159
|
+
return undefined;
|
|
6160
|
+
}
|
|
6161
|
+
getOrCreateBootstrapAttempt(kind, request) {
|
|
6162
|
+
const existingAttempt = this.loadBootstrapAttempt(kind);
|
|
6163
|
+
if (existingAttempt &&
|
|
6164
|
+
this.bootstrapIntentsMatch(existingAttempt.intentFingerprint, request.intentFingerprint)) {
|
|
6165
|
+
return existingAttempt;
|
|
6166
|
+
}
|
|
6167
|
+
if (existingAttempt) {
|
|
6168
|
+
this.removeStoredBootstrapAttempt(kind);
|
|
6169
|
+
}
|
|
6170
|
+
const attempt = {
|
|
6171
|
+
version: 1,
|
|
6172
|
+
projectKey: this.projectKey,
|
|
6173
|
+
createdAt: Date.now(),
|
|
6174
|
+
idempotencyKey: `bootstrap:${kind}:${generateRandomId(40)}`,
|
|
6175
|
+
...request,
|
|
6176
|
+
};
|
|
6177
|
+
this.bootstrapAttempts[kind] = attempt;
|
|
6178
|
+
try {
|
|
6179
|
+
this.sessionStorage.setItem(this.getBootstrapAttemptStorageKey(kind), JSON.stringify(attempt));
|
|
6180
|
+
}
|
|
6181
|
+
catch {
|
|
6182
|
+
// The in-memory copy still protects retries within this tracker instance.
|
|
6183
|
+
}
|
|
6184
|
+
return attempt;
|
|
6185
|
+
}
|
|
6186
|
+
clearBootstrapAttempt(kind) {
|
|
6187
|
+
this.removeStoredBootstrapAttempt(kind);
|
|
6188
|
+
}
|
|
6189
|
+
buildBootstrapIntentFingerprint(params) {
|
|
6190
|
+
const tracker = this.getTrackerInfo();
|
|
6191
|
+
return JSON.stringify({
|
|
6192
|
+
projectKey: this.projectKey,
|
|
6193
|
+
kind: params.kind,
|
|
6194
|
+
token: params.token ?? '',
|
|
6195
|
+
rule: params.rule ?? '',
|
|
6196
|
+
userID: this.session.getInfo().userID ?? '',
|
|
6197
|
+
userUUID: tracker.userUUID ?? '',
|
|
6198
|
+
});
|
|
6199
|
+
}
|
|
6200
|
+
parseBootstrapIntent(value) {
|
|
6201
|
+
try {
|
|
6202
|
+
const intent = JSON.parse(value);
|
|
6203
|
+
if (intent.projectKey !== this.projectKey ||
|
|
6204
|
+
(intent.kind !== 'recording' && intent.kind !== 'offline') ||
|
|
6205
|
+
typeof intent.token !== 'string' ||
|
|
6206
|
+
typeof intent.rule !== 'string' ||
|
|
6207
|
+
typeof intent.userID !== 'string' ||
|
|
6208
|
+
typeof intent.userUUID !== 'string') {
|
|
6209
|
+
return undefined;
|
|
6210
|
+
}
|
|
6211
|
+
return intent;
|
|
6212
|
+
}
|
|
6213
|
+
catch {
|
|
6214
|
+
return undefined;
|
|
6215
|
+
}
|
|
6216
|
+
}
|
|
6217
|
+
bootstrapIntentsMatch(existingFingerprint, currentFingerprint) {
|
|
6218
|
+
const existing = this.parseBootstrapIntent(existingFingerprint);
|
|
6219
|
+
const current = this.parseBootstrapIntent(currentFingerprint);
|
|
6220
|
+
if (!existing || !current) {
|
|
6221
|
+
return false;
|
|
6222
|
+
}
|
|
6223
|
+
return (existing.projectKey === current.projectKey &&
|
|
6224
|
+
existing.kind === current.kind &&
|
|
6225
|
+
existing.token === current.token &&
|
|
6226
|
+
(current.rule === '' || existing.rule === current.rule) &&
|
|
6227
|
+
(current.userID === '' || existing.userID === current.userID) &&
|
|
6228
|
+
(current.userUUID === '' || existing.userUUID === current.userUUID));
|
|
6229
|
+
}
|
|
5066
6230
|
getSessionMeta() {
|
|
6231
|
+
const sessionInfo = this.session.getInfo();
|
|
6232
|
+
const metadata = { ...sessionInfo.metadata };
|
|
6233
|
+
Object.keys(metadata).forEach((key) => {
|
|
6234
|
+
if (key.trim().toLowerCase() === LINKAGE_ASSERTION_METADATA_KEY) {
|
|
6235
|
+
delete metadata[key];
|
|
6236
|
+
}
|
|
6237
|
+
});
|
|
5067
6238
|
return {
|
|
5068
|
-
...
|
|
6239
|
+
...sessionInfo,
|
|
6240
|
+
metadata,
|
|
5069
6241
|
...this.getTrackerInfo(),
|
|
5070
6242
|
};
|
|
5071
6243
|
}
|
|
6244
|
+
async requestMessengerLinkageChallenge() {
|
|
6245
|
+
const sessionId = this.session.getInfo().sessionID;
|
|
6246
|
+
const grantLineage = this.assistSessionGrant;
|
|
6247
|
+
const grant = this.getAssistSessionGrant();
|
|
6248
|
+
if (!sessionId || !grantLineage || !grant || grant.sessionId !== sessionId) {
|
|
6249
|
+
return null;
|
|
6250
|
+
}
|
|
6251
|
+
const tabId = this.session.getTabId();
|
|
6252
|
+
try {
|
|
6253
|
+
const response = await fetch(`${normalizeEndpoint(this.options.endpoint)}/v1/linkage-challenge`, {
|
|
6254
|
+
method: 'POST',
|
|
6255
|
+
headers: {
|
|
6256
|
+
Authorization: `Bearer ${grant.token}`,
|
|
6257
|
+
'Content-Type': 'application/json',
|
|
6258
|
+
},
|
|
6259
|
+
body: JSON.stringify({ tabId }),
|
|
6260
|
+
cache: 'no-store',
|
|
6261
|
+
credentials: 'omit',
|
|
6262
|
+
referrerPolicy: 'no-referrer',
|
|
6263
|
+
});
|
|
6264
|
+
if (response.status !== 200) {
|
|
6265
|
+
return null;
|
|
6266
|
+
}
|
|
6267
|
+
const challenge = parseMessengerLinkageChallenge(await response.json(), sessionId);
|
|
6268
|
+
if (!challenge) {
|
|
6269
|
+
return null;
|
|
6270
|
+
}
|
|
6271
|
+
const currentGrant = this.getAssistSessionGrant();
|
|
6272
|
+
if (!currentGrant ||
|
|
6273
|
+
this.assistSessionGrant !== grantLineage ||
|
|
6274
|
+
currentGrant.sessionId !== grant.sessionId ||
|
|
6275
|
+
currentGrant.token !== grant.token ||
|
|
6276
|
+
this.session.getInfo().sessionID !== sessionId ||
|
|
6277
|
+
this.session.getTabId() !== tabId) {
|
|
6278
|
+
return null;
|
|
6279
|
+
}
|
|
6280
|
+
return challenge;
|
|
6281
|
+
}
|
|
6282
|
+
catch {
|
|
6283
|
+
return null;
|
|
6284
|
+
}
|
|
6285
|
+
}
|
|
6286
|
+
getAssistSessionGrant() {
|
|
6287
|
+
return this.assistSessionGrant ? { ...this.assistSessionGrant } : null;
|
|
6288
|
+
}
|
|
6289
|
+
setAssistSessionGrant(sessionId, token) {
|
|
6290
|
+
this.assistSessionGrant =
|
|
6291
|
+
typeof token === 'string' &&
|
|
6292
|
+
token.length > 0 &&
|
|
6293
|
+
token.length <= MAX_ASSIST_SESSION_GRANT_LENGTH &&
|
|
6294
|
+
token.trim() === token
|
|
6295
|
+
? { sessionId, token }
|
|
6296
|
+
: null;
|
|
6297
|
+
}
|
|
6298
|
+
clearAssistSessionGrant() {
|
|
6299
|
+
this.assistSessionGrant = null;
|
|
6300
|
+
}
|
|
5072
6301
|
getSessionSecret() {
|
|
5073
6302
|
return this.session.getSessionSecret(this.projectKey);
|
|
5074
6303
|
}
|
|
@@ -5133,12 +6362,19 @@ class App {
|
|
|
5133
6362
|
const needReset = this.sessionStorage.getItem(this.options.session_reset_key) !== null;
|
|
5134
6363
|
let needNewSessionID = startNewSession || needReset;
|
|
5135
6364
|
const sessionToken = this.session.getSessionSecret(this.projectKey);
|
|
6365
|
+
if (needReset) {
|
|
6366
|
+
this.sessionStorage.removeItem(this.options.session_reset_key);
|
|
6367
|
+
}
|
|
5136
6368
|
const storedVersion = this.sessionStorage.getItem(`${this.options.session_token_key}_version`);
|
|
5137
6369
|
if (!storedVersion || storedVersion !== PROTO_VERSION) {
|
|
5138
6370
|
needNewSessionID = true;
|
|
5139
6371
|
this.sessionStorage.setItem(`${this.options.session_token_key}_version`, PROTO_VERSION);
|
|
5140
6372
|
}
|
|
5141
|
-
|
|
6373
|
+
const isNewSession = needNewSessionID || !sessionToken;
|
|
6374
|
+
if (isNewSession && sessionToken) {
|
|
6375
|
+
this.session.clearSessionSecret();
|
|
6376
|
+
}
|
|
6377
|
+
return isNewSession;
|
|
5142
6378
|
}
|
|
5143
6379
|
/**
|
|
5144
6380
|
* start buffering messages without starting the actual session, which gives
|
|
@@ -5194,23 +6430,48 @@ class App {
|
|
|
5194
6430
|
async setupConditionalStart(startOpts) {
|
|
5195
6431
|
this.conditionsManager = new ConditionsManager(this, startOpts);
|
|
5196
6432
|
const ingestPoint = normalizeEndpoint(this.options.endpoint);
|
|
5197
|
-
|
|
5198
|
-
|
|
5199
|
-
|
|
5200
|
-
|
|
5201
|
-
|
|
5202
|
-
|
|
5203
|
-
|
|
5204
|
-
|
|
5205
|
-
|
|
5206
|
-
|
|
5207
|
-
|
|
5208
|
-
|
|
5209
|
-
|
|
5210
|
-
|
|
5211
|
-
|
|
5212
|
-
|
|
5213
|
-
|
|
6433
|
+
let r;
|
|
6434
|
+
try {
|
|
6435
|
+
r = await fetch(ingestPoint + '/v1/bootstrap', {
|
|
6436
|
+
method: 'POST',
|
|
6437
|
+
headers: {
|
|
6438
|
+
'Content-Type': 'application/json',
|
|
6439
|
+
},
|
|
6440
|
+
body: JSON.stringify(this.buildStartPayload({
|
|
6441
|
+
timestamp: now(),
|
|
6442
|
+
dry: true,
|
|
6443
|
+
bufferMs: 0,
|
|
6444
|
+
token: '',
|
|
6445
|
+
width: window.screen.width,
|
|
6446
|
+
height: window.screen.height,
|
|
6447
|
+
referrer: document.referrer,
|
|
6448
|
+
})),
|
|
6449
|
+
});
|
|
6450
|
+
}
|
|
6451
|
+
catch (error) {
|
|
6452
|
+
throw redactedErrorFrom(error, 'Conditional bootstrap request failed.');
|
|
6453
|
+
}
|
|
6454
|
+
if (r.status !== 200) {
|
|
6455
|
+
throw await bootstrapHttpError(r);
|
|
6456
|
+
}
|
|
6457
|
+
const response = await bootstrapJson(r);
|
|
6458
|
+
const session = asRecord(response.session);
|
|
6459
|
+
const client = asRecord(response.client);
|
|
6460
|
+
const device = asRecord(response.device);
|
|
6461
|
+
const { token, projectId, assistToken } = session;
|
|
6462
|
+
const invalidResponseFields = [
|
|
6463
|
+
!isValidBootstrapCredential(token, MAX_SESSION_TOKEN_LENGTH) ? 'session.token' : null,
|
|
6464
|
+
typeof assistToken !== 'undefined' &&
|
|
6465
|
+
!isValidBootstrapCredential(assistToken, MAX_ASSIST_SESSION_GRANT_LENGTH)
|
|
6466
|
+
? 'session.assistToken'
|
|
6467
|
+
: null,
|
|
6468
|
+
typeof session.id !== 'string' ? 'session.id' : null,
|
|
6469
|
+
typeof projectId !== 'string' ? 'session.projectId' : null,
|
|
6470
|
+
typeof device.id !== 'string' ? 'device.id' : null,
|
|
6471
|
+
].filter((field) => field !== null);
|
|
6472
|
+
if (invalidResponseFields.length > 0) {
|
|
6473
|
+
throw new Error(`Incorrect server response (status ${r.status}; invalid fields: ${invalidResponseFields.join(', ')})`);
|
|
6474
|
+
}
|
|
5214
6475
|
const { browser: userBrowser, city: userCity, country: userCountry, device: userDevice, os: userOS, state: userState, } = client;
|
|
5215
6476
|
this.session.assign({ projectID: projectId });
|
|
5216
6477
|
this.session.setUserInfo({
|
|
@@ -5223,12 +6484,18 @@ class App {
|
|
|
5223
6484
|
});
|
|
5224
6485
|
const onStartInfo = {
|
|
5225
6486
|
sessionToken: token,
|
|
5226
|
-
|
|
5227
|
-
|
|
6487
|
+
assistToken,
|
|
6488
|
+
userUUID: device.id,
|
|
6489
|
+
sessionID: session.id,
|
|
5228
6490
|
};
|
|
5229
|
-
|
|
5230
|
-
|
|
5231
|
-
|
|
6491
|
+
try {
|
|
6492
|
+
this.startCallbacks.forEach((cb) => cb(onStartInfo));
|
|
6493
|
+
await this.conditionsManager?.fetchConditions(projectId, token);
|
|
6494
|
+
await this.markerWatcher.fetchTags(normalizeEndpoint(this.options.endpoint), token);
|
|
6495
|
+
}
|
|
6496
|
+
catch (error) {
|
|
6497
|
+
throw redactedErrorFrom(error, 'Conditional bootstrap failed.', [token, assistToken]);
|
|
6498
|
+
}
|
|
5232
6499
|
}
|
|
5233
6500
|
/**
|
|
5234
6501
|
* Starts offline session recording
|
|
@@ -5236,9 +6503,9 @@ class App {
|
|
|
5236
6503
|
* @param {Function} onSessionSent - callback that will be called once session is fully sent
|
|
5237
6504
|
* */
|
|
5238
6505
|
offlineRecording(startOpts = {}, onSessionSent) {
|
|
6506
|
+
this.clearAssistSessionGrant();
|
|
5239
6507
|
this.onSessionSent = onSessionSent;
|
|
5240
6508
|
this.singleBuffer = true;
|
|
5241
|
-
const isNewSession = this.checkSessionToken(startOpts.startNewSession);
|
|
5242
6509
|
adjustTimeOrigin();
|
|
5243
6510
|
this.coldStartTs = now();
|
|
5244
6511
|
const saverBuffer = this.localStorage.getItem(bufferStorageKey);
|
|
@@ -5256,6 +6523,10 @@ class App {
|
|
|
5256
6523
|
if (startOpts.startNewSession) {
|
|
5257
6524
|
this.session.reset();
|
|
5258
6525
|
}
|
|
6526
|
+
const isNewSession = this.checkSessionToken(startOpts.startNewSession);
|
|
6527
|
+
this.offlineRecordingSessionToken = isNewSession
|
|
6528
|
+
? undefined
|
|
6529
|
+
: this.session.getSessionSecret(this.projectKey);
|
|
5259
6530
|
this.session.assign({
|
|
5260
6531
|
userID: startOpts.userID,
|
|
5261
6532
|
metadata: startOpts.metadata,
|
|
@@ -5305,12 +6576,29 @@ class App {
|
|
|
5305
6576
|
* */
|
|
5306
6577
|
async uploadOfflineRecording() {
|
|
5307
6578
|
this.stop(false);
|
|
5308
|
-
const
|
|
6579
|
+
const freshTimestamp = now();
|
|
6580
|
+
const bootstrapToken = this.offlineRecordingSessionToken ?? '';
|
|
6581
|
+
const freshRequestBody = () => JSON.stringify(this.buildStartPayload({
|
|
6582
|
+
timestamp: freshTimestamp,
|
|
6583
|
+
dry: false,
|
|
6584
|
+
offline: true,
|
|
6585
|
+
bufferMs: freshTimestamp - this.coldStartTs,
|
|
6586
|
+
token: bootstrapToken,
|
|
6587
|
+
}));
|
|
6588
|
+
const bootstrapAttempt = this.getOrCreateBootstrapAttempt('offline', {
|
|
6589
|
+
intentFingerprint: this.buildBootstrapIntentFingerprint({
|
|
6590
|
+
kind: 'offline',
|
|
6591
|
+
token: bootstrapToken,
|
|
6592
|
+
}),
|
|
6593
|
+
requestTimestamp: freshTimestamp,
|
|
6594
|
+
workerTimestamp: this.coldStartTs,
|
|
6595
|
+
requestBody: freshRequestBody(),
|
|
6596
|
+
});
|
|
5309
6597
|
this.worker?.postMessage({
|
|
5310
6598
|
type: 'start',
|
|
5311
6599
|
pageNo: this.session.incPageNo(),
|
|
5312
6600
|
ingestPoint: normalizeEndpoint(this.options.endpoint),
|
|
5313
|
-
timestamp:
|
|
6601
|
+
timestamp: bootstrapAttempt.workerTimestamp,
|
|
5314
6602
|
url: document.URL,
|
|
5315
6603
|
connAttemptCount: this.options.connAttemptCount,
|
|
5316
6604
|
connAttemptGap: this.options.connAttemptGap,
|
|
@@ -5318,28 +6606,45 @@ class App {
|
|
|
5318
6606
|
localDebug: this.options.__local_debug,
|
|
5319
6607
|
});
|
|
5320
6608
|
const ingestPoint = normalizeEndpoint(this.options.endpoint);
|
|
5321
|
-
const
|
|
5322
|
-
|
|
5323
|
-
|
|
5324
|
-
|
|
5325
|
-
|
|
5326
|
-
|
|
5327
|
-
|
|
5328
|
-
|
|
5329
|
-
|
|
5330
|
-
|
|
5331
|
-
})
|
|
5332
|
-
}
|
|
5333
|
-
|
|
6609
|
+
const headers = {
|
|
6610
|
+
'Content-Type': 'application/json',
|
|
6611
|
+
'Idempotency-Key': bootstrapAttempt.idempotencyKey,
|
|
6612
|
+
};
|
|
6613
|
+
let r;
|
|
6614
|
+
try {
|
|
6615
|
+
r = await fetch(ingestPoint + '/v1/bootstrap', {
|
|
6616
|
+
method: 'POST',
|
|
6617
|
+
headers,
|
|
6618
|
+
body: bootstrapAttempt.requestBody,
|
|
6619
|
+
});
|
|
6620
|
+
}
|
|
6621
|
+
catch (error) {
|
|
6622
|
+
throw redactedErrorFrom(error, 'Offline bootstrap request failed.', [bootstrapToken]);
|
|
6623
|
+
}
|
|
6624
|
+
if (r.status !== 200) {
|
|
6625
|
+
throw await bootstrapHttpError(r, undefined, [bootstrapToken]);
|
|
6626
|
+
}
|
|
6627
|
+
const response = await bootstrapJson(r, [bootstrapToken]);
|
|
6628
|
+
const session = asRecord(response.session);
|
|
6629
|
+
const client = asRecord(response.client);
|
|
6630
|
+
const limits = asRecord(response.limits);
|
|
6631
|
+
const offlineProtocolVersion = response.protocolVersion;
|
|
5334
6632
|
const { token, projectId } = session;
|
|
5335
6633
|
const { browser: userBrowser, city: userCity, country: userCountry, device: userDevice, os: userOS, state: userState, } = client;
|
|
5336
6634
|
const { beacon: beaconSizeLimit } = limits;
|
|
5337
|
-
|
|
5338
|
-
|
|
5339
|
-
|
|
5340
|
-
beaconSizeLimit
|
|
5341
|
-
|
|
5342
|
-
|
|
6635
|
+
const invalidResponseFields = [
|
|
6636
|
+
!isValidBootstrapCredential(token, MAX_SESSION_TOKEN_LENGTH) ? 'session.token' : null,
|
|
6637
|
+
typeof projectId !== 'string' ? 'session.projectId' : null,
|
|
6638
|
+
typeof beaconSizeLimit !== 'number' && typeof beaconSizeLimit !== 'undefined'
|
|
6639
|
+
? 'limits.beacon'
|
|
6640
|
+
: null,
|
|
6641
|
+
].filter((field) => field !== null);
|
|
6642
|
+
if (invalidResponseFields.length > 0) {
|
|
6643
|
+
throw new Error(`Incorrect server response (status ${r.status}; invalid fields: ${invalidResponseFields.join(', ')})`);
|
|
6644
|
+
}
|
|
6645
|
+
this.session.setSessionSecret(token, this.projectKey);
|
|
6646
|
+
this.clearBootstrapAttempt('offline');
|
|
6647
|
+
this.offlineRecordingSessionToken = token;
|
|
5343
6648
|
this.session.assign({ projectID: projectId });
|
|
5344
6649
|
this.session.setUserInfo({
|
|
5345
6650
|
userBrowser,
|
|
@@ -5349,12 +6654,83 @@ class App {
|
|
|
5349
6654
|
userOS,
|
|
5350
6655
|
userState,
|
|
5351
6656
|
});
|
|
5352
|
-
|
|
5353
|
-
|
|
6657
|
+
this.worker?.postMessage({
|
|
6658
|
+
type: 'auth',
|
|
6659
|
+
token,
|
|
6660
|
+
beaconSizeLimit,
|
|
6661
|
+
protocolVersion: offlineProtocolVersion,
|
|
6662
|
+
});
|
|
6663
|
+
try {
|
|
6664
|
+
while (this.bufferedMessages1.length > 0) {
|
|
6665
|
+
await this.flushBuffer(this.bufferedMessages1);
|
|
6666
|
+
}
|
|
6667
|
+
}
|
|
6668
|
+
catch (error) {
|
|
6669
|
+
throw redactedErrorFrom(error, 'Offline recording upload failed.', [token, bootstrapToken]);
|
|
5354
6670
|
}
|
|
5355
6671
|
this.postToWorker([[-1]]);
|
|
5356
6672
|
this.clearBuffers();
|
|
5357
6673
|
}
|
|
6674
|
+
startCrossDomainFrame(startOpts = {}) {
|
|
6675
|
+
if (this.activityState === ActivityState.Active ||
|
|
6676
|
+
this.activityState === ActivityState.Starting) {
|
|
6677
|
+
const reason = 'OxvoSessions: trying to call `start()` on the instance that has been started already.';
|
|
6678
|
+
return UnsuccessfulStart(reason);
|
|
6679
|
+
}
|
|
6680
|
+
if (this.crossDomainFrameSession === null ||
|
|
6681
|
+
this.crossDomainFrameCapability === null ||
|
|
6682
|
+
this.rootId === null) {
|
|
6683
|
+
return UnsuccessfulStart('Cross-domain iframe handshake was not established.');
|
|
6684
|
+
}
|
|
6685
|
+
this.clearAssistSessionGrant();
|
|
6686
|
+
if (Object.keys(startOpts).length !== 0) {
|
|
6687
|
+
this.prevOpts = startOpts;
|
|
6688
|
+
}
|
|
6689
|
+
if (startOpts.startCallback) {
|
|
6690
|
+
this.userStartCallback = startOpts.startCallback;
|
|
6691
|
+
}
|
|
6692
|
+
try {
|
|
6693
|
+
adjustTimeOrigin();
|
|
6694
|
+
this.activityState = ActivityState.Starting;
|
|
6695
|
+
this.delay = this.crossDomainFrameSession.delayMs;
|
|
6696
|
+
this.session.clearSessionSecret();
|
|
6697
|
+
this.session.assign({
|
|
6698
|
+
sessionID: this.crossDomainFrameSession.sessionID,
|
|
6699
|
+
projectID: this.crossDomainFrameSession.projectID,
|
|
6700
|
+
timestamp: this.crossDomainFrameSession.startedAt,
|
|
6701
|
+
});
|
|
6702
|
+
const onStartInfo = {
|
|
6703
|
+
sessionToken: '',
|
|
6704
|
+
userUUID: this.crossDomainFrameSession.userUUID,
|
|
6705
|
+
sessionID: this.crossDomainFrameSession.sessionID,
|
|
6706
|
+
};
|
|
6707
|
+
this.startCallbacks.forEach((callback) => callback(onStartInfo));
|
|
6708
|
+
this.userStartCallback?.(SuccessfulStart(onStartInfo));
|
|
6709
|
+
this.activityState = ActivityState.Active;
|
|
6710
|
+
if (this.crossDomainFrameCanvas?.enabled && !this.options.canvas.disableCanvas) {
|
|
6711
|
+
this.canvasRecorder =
|
|
6712
|
+
this.canvasRecorder ??
|
|
6713
|
+
new CanvasRecorder(this, {
|
|
6714
|
+
fps: this.crossDomainFrameCanvas.fps ?? 15,
|
|
6715
|
+
quality: this.crossDomainFrameCanvas.quality ?? 'medium',
|
|
6716
|
+
isDebug: this.options.canvas.__save_canvas_locally,
|
|
6717
|
+
fixedScaling: this.options.canvas.fixedCanvasScaling,
|
|
6718
|
+
useAnimationFrame: this.options.canvas.useAnimationFrame,
|
|
6719
|
+
framesSupport: this.crossDomainFrameCanvas.framesSupport,
|
|
6720
|
+
});
|
|
6721
|
+
}
|
|
6722
|
+
this.observer.crossdomainObserve(this.rootId, this.frameOderNumber, this.frameLevel);
|
|
6723
|
+
this.ticker.start();
|
|
6724
|
+
this.canvasRecorder?.startTracking();
|
|
6725
|
+
return SuccessfulStart(onStartInfo);
|
|
6726
|
+
}
|
|
6727
|
+
catch (reason) {
|
|
6728
|
+
this.stop();
|
|
6729
|
+
const message = reason instanceof Error ? reason.message : String(reason);
|
|
6730
|
+
this.signalError(message, []);
|
|
6731
|
+
return UnsuccessfulStart(message);
|
|
6732
|
+
}
|
|
6733
|
+
}
|
|
5358
6734
|
async _start(startOpts = {}, resetByWorker = false, conditionName) {
|
|
5359
6735
|
if (Object.keys(startOpts).length !== 0) {
|
|
5360
6736
|
this.prevOpts = startOpts;
|
|
@@ -5376,6 +6752,7 @@ class App {
|
|
|
5376
6752
|
const reason = 'OxvoSessions: trying to call `start()` on the instance that has been started already.';
|
|
5377
6753
|
return Promise.resolve(UnsuccessfulStart(reason));
|
|
5378
6754
|
}
|
|
6755
|
+
this.clearAssistSessionGrant();
|
|
5379
6756
|
this.activityState = ActivityState.Starting;
|
|
5380
6757
|
if (!isColdStart) {
|
|
5381
6758
|
adjustTimeOrigin();
|
|
@@ -5393,68 +6770,112 @@ class App {
|
|
|
5393
6770
|
userID: userId || undefined,
|
|
5394
6771
|
metadata: startOpts.metadata,
|
|
5395
6772
|
});
|
|
5396
|
-
const
|
|
6773
|
+
const sessionToken = this.session.getSessionSecret(this.projectKey);
|
|
6774
|
+
const isNewSession = this.checkSessionToken(startOpts.startNewSession);
|
|
6775
|
+
const freshTimestamp = now();
|
|
6776
|
+
const freshWorkerTimestamp = isColdStart ? this.coldStartTs : freshTimestamp;
|
|
6777
|
+
const bootstrapToken = isNewSession ? '' : sessionToken;
|
|
6778
|
+
const freshRequestBody = () => JSON.stringify(this.buildStartPayload({
|
|
6779
|
+
timestamp: freshTimestamp,
|
|
6780
|
+
dry: false,
|
|
6781
|
+
bufferMs: this.coldStartTs ? freshTimestamp - this.coldStartTs : 0,
|
|
6782
|
+
token: bootstrapToken,
|
|
6783
|
+
rule: conditionName,
|
|
6784
|
+
width: window.screen.width,
|
|
6785
|
+
height: window.screen.height,
|
|
6786
|
+
}));
|
|
6787
|
+
const bootstrapAttempt = this.getOrCreateBootstrapAttempt('recording', {
|
|
6788
|
+
intentFingerprint: this.buildBootstrapIntentFingerprint({
|
|
6789
|
+
kind: 'recording',
|
|
6790
|
+
token: bootstrapToken,
|
|
6791
|
+
rule: conditionName,
|
|
6792
|
+
}),
|
|
6793
|
+
requestTimestamp: freshTimestamp,
|
|
6794
|
+
workerTimestamp: freshWorkerTimestamp,
|
|
6795
|
+
requestBody: freshRequestBody(),
|
|
6796
|
+
});
|
|
6797
|
+
const timestamp = bootstrapAttempt.requestTimestamp;
|
|
5397
6798
|
this.worker?.postMessage({
|
|
5398
6799
|
type: 'start',
|
|
5399
6800
|
pageNo: this.session.incPageNo(),
|
|
5400
6801
|
ingestPoint: normalizeEndpoint(this.options.endpoint),
|
|
5401
|
-
timestamp:
|
|
6802
|
+
timestamp: bootstrapAttempt.workerTimestamp,
|
|
5402
6803
|
url: document.URL,
|
|
5403
6804
|
connAttemptCount: this.options.connAttemptCount,
|
|
5404
6805
|
connAttemptGap: this.options.connAttemptGap,
|
|
5405
6806
|
tabId: this.session.getTabId(),
|
|
5406
6807
|
localDebug: this.options.__local_debug,
|
|
5407
6808
|
});
|
|
5408
|
-
|
|
5409
|
-
const
|
|
5410
|
-
this.sessionStorage.removeItem(this.options.session_reset_key);
|
|
5411
|
-
this.debug.log('OxvoSessions: starting session; need new session id?', isNewSession, 'session token: ', sessionToken);
|
|
6809
|
+
this.debug.log('OxvoSessions: starting session; need new session id?', isNewSession, 'has existing session token:', Boolean(sessionToken));
|
|
6810
|
+
const bootstrapSecrets = [sessionToken, bootstrapToken];
|
|
5412
6811
|
try {
|
|
5413
6812
|
const ingestPoint = normalizeEndpoint(this.options.endpoint);
|
|
6813
|
+
const headers = {
|
|
6814
|
+
'Content-Type': 'application/json',
|
|
6815
|
+
'Idempotency-Key': bootstrapAttempt.idempotencyKey,
|
|
6816
|
+
};
|
|
5414
6817
|
const r = await window.fetch(ingestPoint + '/v1/bootstrap', {
|
|
5415
6818
|
method: 'POST',
|
|
5416
|
-
headers
|
|
5417
|
-
|
|
5418
|
-
},
|
|
5419
|
-
body: JSON.stringify(this.buildStartPayload({
|
|
5420
|
-
timestamp,
|
|
5421
|
-
dry: false,
|
|
5422
|
-
bufferMs: this.coldStartTs ? timestamp - this.coldStartTs : 0,
|
|
5423
|
-
token: isNewSession ? '' : sessionToken,
|
|
5424
|
-
rule: conditionName,
|
|
5425
|
-
width: window.screen.width,
|
|
5426
|
-
height: window.screen.height,
|
|
5427
|
-
})),
|
|
6819
|
+
headers,
|
|
6820
|
+
body: bootstrapAttempt.requestBody,
|
|
5428
6821
|
});
|
|
5429
6822
|
if (r.status !== 200) {
|
|
5430
6823
|
const error = await r.text();
|
|
5431
|
-
|
|
5432
|
-
|
|
6824
|
+
if (error === CANCELED) {
|
|
6825
|
+
this.stop();
|
|
6826
|
+
this.signalError(CANCELED, []);
|
|
6827
|
+
return UnsuccessfulStart(CANCELED);
|
|
6828
|
+
}
|
|
6829
|
+
throw await bootstrapHttpError(r, error, bootstrapSecrets);
|
|
5433
6830
|
}
|
|
5434
6831
|
if (!this.worker && !this.insideIframe) {
|
|
5435
6832
|
const reason = 'no worker found after start request (this should not happen in real world)';
|
|
5436
|
-
|
|
5437
|
-
|
|
5438
|
-
|
|
5439
|
-
const
|
|
5440
|
-
const
|
|
6833
|
+
throw new Error(reason);
|
|
6834
|
+
}
|
|
6835
|
+
const response = await bootstrapJson(r, bootstrapSecrets);
|
|
6836
|
+
const session = asRecord(response.session);
|
|
6837
|
+
const device = asRecord(response.device);
|
|
6838
|
+
const client = asRecord(response.client);
|
|
6839
|
+
const limits = asRecord(response.limits);
|
|
6840
|
+
const canvas = asRecord(response.canvas);
|
|
6841
|
+
const flags = asRecord(response.flags);
|
|
6842
|
+
const protocolVersion = response.protocolVersion;
|
|
6843
|
+
const { token, assistToken, id: sessionID, projectId: projectID, delayMs: delay, startedAt: startTimestamp, } = session;
|
|
5441
6844
|
const { id: userUUID } = device;
|
|
5442
6845
|
const { browser: userBrowser, city: userCity, country: userCountry, device: userDevice, os: userOS, state: userState, } = client;
|
|
5443
6846
|
const { beacon: beaconSizeLimit, compressAt: compressionThreshold } = limits;
|
|
5444
6847
|
const { enabled: canvasEnabled, quality: canvasQuality, fps: canvasFPS, framesSupport, } = canvas;
|
|
5445
6848
|
const socketOnly = flags?.socketOnly;
|
|
5446
|
-
|
|
5447
|
-
|
|
5448
|
-
|
|
5449
|
-
|
|
5450
|
-
|
|
5451
|
-
|
|
5452
|
-
|
|
5453
|
-
|
|
5454
|
-
|
|
5455
|
-
|
|
6849
|
+
const invalidResponseFields = [
|
|
6850
|
+
!isValidBootstrapCredential(token, MAX_SESSION_TOKEN_LENGTH) ? 'session.token' : null,
|
|
6851
|
+
typeof assistToken !== 'undefined' &&
|
|
6852
|
+
!isValidBootstrapCredential(assistToken, MAX_ASSIST_SESSION_GRANT_LENGTH)
|
|
6853
|
+
? 'session.assistToken'
|
|
6854
|
+
: null,
|
|
6855
|
+
typeof userUUID !== 'string' ? 'device.id' : null,
|
|
6856
|
+
typeof startTimestamp !== 'number' && typeof startTimestamp !== 'undefined'
|
|
6857
|
+
? 'session.startedAt'
|
|
6858
|
+
: null,
|
|
6859
|
+
typeof sessionID !== 'string' ? 'session.id' : null,
|
|
6860
|
+
typeof projectID !== 'string' ? 'session.projectId' : null,
|
|
6861
|
+
typeof delay !== 'number' ? 'session.delayMs' : null,
|
|
6862
|
+
typeof beaconSizeLimit !== 'number' && typeof beaconSizeLimit !== 'undefined'
|
|
6863
|
+
? 'limits.beacon'
|
|
6864
|
+
: null,
|
|
6865
|
+
].filter((field) => field !== null);
|
|
6866
|
+
if (invalidResponseFields.length > 0) {
|
|
6867
|
+
throw new Error(`Incorrect server response (status ${r.status}; invalid fields: ${invalidResponseFields.join(', ')})`);
|
|
6868
|
+
}
|
|
6869
|
+
bootstrapSecrets.push(token, assistToken);
|
|
6870
|
+
this.crossDomainCanvasConfig = {
|
|
6871
|
+
enabled: canvasEnabled === true,
|
|
6872
|
+
...(['low', 'medium', 'high'].includes(canvasQuality) ? { quality: canvasQuality } : {}),
|
|
6873
|
+
...(typeof canvasFPS === 'number' ? { fps: canvasFPS } : {}),
|
|
6874
|
+
...(typeof framesSupport === 'boolean' ? { framesSupport } : {}),
|
|
6875
|
+
};
|
|
5456
6876
|
this.delay = delay;
|
|
5457
6877
|
this.session.setSessionSecret(token, this.projectKey);
|
|
6878
|
+
this.clearBootstrapAttempt('recording');
|
|
5458
6879
|
if (sessionToken && sessionToken !== token) {
|
|
5459
6880
|
this.bc?.postMessage({
|
|
5460
6881
|
type: proto.reset,
|
|
@@ -5474,6 +6895,7 @@ class App {
|
|
|
5474
6895
|
timestamp: startTimestamp || timestamp,
|
|
5475
6896
|
projectID,
|
|
5476
6897
|
});
|
|
6898
|
+
this.setAssistSessionGrant(sessionID, assistToken);
|
|
5477
6899
|
if (socketOnly) {
|
|
5478
6900
|
this.socketMode = true;
|
|
5479
6901
|
this.worker?.postMessage('stop');
|
|
@@ -5495,7 +6917,7 @@ class App {
|
|
|
5495
6917
|
Object.entries(this.session.getInfo().metadata).forEach(([key, value]) => this.send(Metadata(key, value)));
|
|
5496
6918
|
this.localStorage.setItem(this.options.local_uuid_key, userUUID);
|
|
5497
6919
|
this.compressionThreshold = compressionThreshold;
|
|
5498
|
-
const onStartInfo = { sessionToken: token, userUUID, sessionID };
|
|
6920
|
+
const onStartInfo = { sessionToken: token, assistToken, userUUID, sessionID };
|
|
5499
6921
|
// TODO: start as early as possible (before receiving the token)
|
|
5500
6922
|
/** after start */
|
|
5501
6923
|
this.startCallbacks.forEach((cb) => cb(onStartInfo)); // MBTODO: callbacks after DOM "mounted" (observed)
|
|
@@ -5543,7 +6965,6 @@ class App {
|
|
|
5543
6965
|
}
|
|
5544
6966
|
catch (reason) {
|
|
5545
6967
|
this.stop();
|
|
5546
|
-
this.session.reset();
|
|
5547
6968
|
if (!reason) {
|
|
5548
6969
|
console.error('Unknown error during start');
|
|
5549
6970
|
this.signalError('Unknown error', []);
|
|
@@ -5553,8 +6974,9 @@ class App {
|
|
|
5553
6974
|
this.signalError(CANCELED, []);
|
|
5554
6975
|
return UnsuccessfulStart(CANCELED);
|
|
5555
6976
|
}
|
|
5556
|
-
|
|
5557
|
-
|
|
6977
|
+
const redactedError = redactedErrorFrom(reason, 'Session start failed.', bootstrapSecrets);
|
|
6978
|
+
this._debug('session_start', redactedError);
|
|
6979
|
+
const errorMessage = redactedError.message;
|
|
5558
6980
|
this.signalError(errorMessage, []);
|
|
5559
6981
|
return UnsuccessfulStart(errorMessage);
|
|
5560
6982
|
}
|
|
@@ -5569,6 +6991,23 @@ class App {
|
|
|
5569
6991
|
}, 100);
|
|
5570
6992
|
});
|
|
5571
6993
|
}
|
|
6994
|
+
async waitCrossDomainStart(timeoutMs = 15000) {
|
|
6995
|
+
if (this.canStart)
|
|
6996
|
+
return true;
|
|
6997
|
+
return new Promise((resolve) => {
|
|
6998
|
+
const interval = setInterval(() => {
|
|
6999
|
+
if (!this.canStart)
|
|
7000
|
+
return;
|
|
7001
|
+
clearInterval(interval);
|
|
7002
|
+
clearTimeout(timeout);
|
|
7003
|
+
resolve(true);
|
|
7004
|
+
}, 100);
|
|
7005
|
+
const timeout = setTimeout(() => {
|
|
7006
|
+
clearInterval(interval);
|
|
7007
|
+
resolve(false);
|
|
7008
|
+
}, timeoutMs);
|
|
7009
|
+
});
|
|
7010
|
+
}
|
|
5572
7011
|
async waitStarted() {
|
|
5573
7012
|
return this.waitStatus(ActivityState.Active);
|
|
5574
7013
|
}
|
|
@@ -5596,20 +7035,35 @@ class App {
|
|
|
5596
7035
|
return Promise.resolve(UnsuccessfulStart(reason));
|
|
5597
7036
|
}
|
|
5598
7037
|
if (this.insideIframe) {
|
|
7038
|
+
if (this.crossDomainParentOrigin === null || this.crossDomainFrameNonce === null) {
|
|
7039
|
+
return UnsuccessfulStart('Cross-domain iframe capture requires an explicit parentDomain and secure random support.');
|
|
7040
|
+
}
|
|
5599
7041
|
this.signalIframeTracker();
|
|
5600
7042
|
}
|
|
5601
|
-
|
|
7043
|
+
const startWhenReady = async () => {
|
|
7044
|
+
if (this.insideIframe) {
|
|
7045
|
+
const ready = await this.waitCrossDomainStart();
|
|
7046
|
+
if (!ready) {
|
|
7047
|
+
this.crossDomainConnectionRequested = false;
|
|
7048
|
+
this.crossDomainResumeAfterHandshake = false;
|
|
7049
|
+
this.clearCrossDomainFrameAuthorization();
|
|
7050
|
+
return UnsuccessfulStart('Cross-domain iframe handshake timed out.');
|
|
7051
|
+
}
|
|
7052
|
+
return this.startCrossDomainFrame(args[0]);
|
|
7053
|
+
}
|
|
5602
7054
|
await this.waitStart();
|
|
5603
7055
|
return this._start(...args);
|
|
7056
|
+
};
|
|
7057
|
+
if (!document.hidden) {
|
|
7058
|
+
return startWhenReady();
|
|
5604
7059
|
}
|
|
5605
7060
|
else {
|
|
5606
7061
|
return new Promise((resolve) => {
|
|
5607
7062
|
const onVisibilityChange = async () => {
|
|
5608
7063
|
if (!document.hidden) {
|
|
5609
|
-
await this.waitStart();
|
|
5610
7064
|
// eslint-disable-next-line
|
|
5611
7065
|
document.removeEventListener('visibilitychange', onVisibilityChange);
|
|
5612
|
-
resolve(
|
|
7066
|
+
resolve(startWhenReady());
|
|
5613
7067
|
}
|
|
5614
7068
|
};
|
|
5615
7069
|
// eslint-disable-next-line
|
|
@@ -5645,6 +7099,7 @@ class App {
|
|
|
5645
7099
|
};
|
|
5646
7100
|
}
|
|
5647
7101
|
stop(stopWorker = true) {
|
|
7102
|
+
this.clearAssistSessionGrant();
|
|
5648
7103
|
if (this.activityState !== ActivityState.NotActive) {
|
|
5649
7104
|
try {
|
|
5650
7105
|
if (this.options.crossdomain?.enabled) {
|
|
@@ -5670,6 +7125,11 @@ class App {
|
|
|
5670
7125
|
this.debug.log('OxvoSessions tracking stopped.');
|
|
5671
7126
|
}
|
|
5672
7127
|
}
|
|
7128
|
+
if (this.insideIframe) {
|
|
7129
|
+
this.crossDomainConnectionRequested = false;
|
|
7130
|
+
this.crossDomainResumeAfterHandshake = false;
|
|
7131
|
+
this.clearCrossDomainFrameAuthorization();
|
|
7132
|
+
}
|
|
5673
7133
|
}
|
|
5674
7134
|
}
|
|
5675
7135
|
|
|
@@ -7320,10 +8780,14 @@ function Fonts (app) {
|
|
|
7320
8780
|
}
|
|
7321
8781
|
|
|
7322
8782
|
function axiosSpy (app, instance, opts, sanitize, stringify) {
|
|
7323
|
-
app.debug.log('OxvoSessions: attaching axios spy to instance'
|
|
8783
|
+
app.debug.log('OxvoSessions: attaching axios spy to configured instance');
|
|
7324
8784
|
function captureResponseData(axiosResponseObj) {
|
|
7325
|
-
app.debug.log('OxvoSessions: capturing axios response data',
|
|
7326
|
-
|
|
8785
|
+
app.debug.log('OxvoSessions: capturing axios response data', {
|
|
8786
|
+
method: axiosResponseObj.config.method,
|
|
8787
|
+
status: axiosResponseObj.status,
|
|
8788
|
+
url: axiosResponseObj.config.url,
|
|
8789
|
+
});
|
|
8790
|
+
const { headers: reqHs, data: reqData, method, url } = axiosResponseObj.config;
|
|
7327
8791
|
const { data: rData, headers: rHs, status: globStatus, response } = axiosResponseObj;
|
|
7328
8792
|
const { data: resData, headers: resHs, status: resStatus } = response || {};
|
|
7329
8793
|
const ihOpt = opts.ignoreHeaders;
|
|
@@ -7382,11 +8846,18 @@ function axiosSpy (app, instance, opts, sanitize, stringify) {
|
|
|
7382
8846
|
}
|
|
7383
8847
|
const requestStart = axiosResponseObj.config.__oxvosessions_timing;
|
|
7384
8848
|
const duration = performance.now() - requestStart;
|
|
7385
|
-
app.debug.log('OxvoSessions:
|
|
8849
|
+
app.debug.log('OxvoSessions: sanitized axios request is ready', {
|
|
8850
|
+
method: reqResInfo.method,
|
|
8851
|
+
status: reqResInfo.status,
|
|
8852
|
+
url: reqResInfo.url,
|
|
8853
|
+
});
|
|
7386
8854
|
app.send(NetworkRequest('xhr', String(method), String(reqResInfo.url), stringify(reqResInfo.request), stringify(reqResInfo.response), reqResInfo.status, requestStart + getTimeOrigin(), duration, 0));
|
|
7387
8855
|
}
|
|
7388
8856
|
function getStartTime(config) {
|
|
7389
|
-
app.debug.log('OxvoSessions: capturing
|
|
8857
|
+
app.debug.log('OxvoSessions: capturing axios request', {
|
|
8858
|
+
method: config.method,
|
|
8859
|
+
url: config.url,
|
|
8860
|
+
});
|
|
7390
8861
|
config.__oxvosessions_timing = performance.now();
|
|
7391
8862
|
if (opts.sessionTokenHeader) {
|
|
7392
8863
|
const header = typeof opts.sessionTokenHeader === 'string'
|
|
@@ -7406,17 +8877,23 @@ function axiosSpy (app, instance, opts, sanitize, stringify) {
|
|
|
7406
8877
|
return response;
|
|
7407
8878
|
}
|
|
7408
8879
|
function captureNetworkError(error) {
|
|
7409
|
-
app.debug.log('OxvoSessions: capturing
|
|
8880
|
+
app.debug.log('OxvoSessions: capturing axios request error', {
|
|
8881
|
+
hasResponse: Boolean(error.response),
|
|
8882
|
+
isAxiosError: isAxiosError(error),
|
|
8883
|
+
message: typeof error.message === 'string' ? error.message : undefined,
|
|
8884
|
+
});
|
|
7410
8885
|
if (isAxiosError(error) && Boolean(error.response)) {
|
|
7411
8886
|
captureResponseData(error.response);
|
|
7412
8887
|
}
|
|
7413
8888
|
else if (error instanceof Error) {
|
|
7414
8889
|
app.send(getExceptionMessage(error, []));
|
|
7415
8890
|
}
|
|
8891
|
+
// Axios rejection handlers must preserve the original Axios error object for downstream code.
|
|
8892
|
+
// eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors
|
|
7416
8893
|
return Promise.reject(error);
|
|
7417
8894
|
}
|
|
7418
|
-
function logRequestError(
|
|
7419
|
-
app.debug.log('OxvoSessions: failed
|
|
8895
|
+
function logRequestError() {
|
|
8896
|
+
app.debug.log('OxvoSessions: failed axios request, skipping');
|
|
7420
8897
|
}
|
|
7421
8898
|
const reqInt = instance.interceptors.request.use(getStartTime, logRequestError, {
|
|
7422
8899
|
synchronous: true,
|
|
@@ -9229,7 +10706,7 @@ class ConstantProperties {
|
|
|
9229
10706
|
user_id: this.user_id,
|
|
9230
10707
|
distinct_id: this.deviceId,
|
|
9231
10708
|
sdk_edition: 'web',
|
|
9232
|
-
sdk_version: '7.
|
|
10709
|
+
sdk_version: '7.4.15',
|
|
9233
10710
|
timezone: getUTCOffsetString(),
|
|
9234
10711
|
search_engine: this.searchEngine,
|
|
9235
10712
|
};
|
|
@@ -9812,6 +11289,8 @@ class Batcher {
|
|
|
9812
11289
|
}
|
|
9813
11290
|
|
|
9814
11291
|
const STORAGEKEY = '__or_sdk_analytics_token';
|
|
11292
|
+
const START_ATTEMPT_STORAGE_KEY = '__or_sdk_analytics_start_attempt';
|
|
11293
|
+
const START_ATTEMPT_TTL_MS = 24 * 60 * 60 * 1000;
|
|
9815
11294
|
const normalizeIngestPoint = (ingest) => {
|
|
9816
11295
|
const base = ingest.replace(/\/$/, '');
|
|
9817
11296
|
return base.endsWith('/oxvo') ? base : `${base}/oxvo`;
|
|
@@ -9835,6 +11314,7 @@ class Analytics {
|
|
|
9835
11314
|
constructor(options) {
|
|
9836
11315
|
this.token = null;
|
|
9837
11316
|
this.standalone = false;
|
|
11317
|
+
this.pendingStartAttempt = null;
|
|
9838
11318
|
this._getToken = () => {
|
|
9839
11319
|
if (this.standalone) {
|
|
9840
11320
|
return this.token;
|
|
@@ -9878,9 +11358,14 @@ class Analytics {
|
|
|
9878
11358
|
timezone: defaultFields.timezone,
|
|
9879
11359
|
search_engine: defaultFields.search_engine,
|
|
9880
11360
|
};
|
|
11361
|
+
const attempt = this.startAttempt(JSON.stringify(data));
|
|
9881
11362
|
const resp = await fetch(apiEdp, {
|
|
9882
11363
|
method: 'POST',
|
|
9883
|
-
|
|
11364
|
+
headers: {
|
|
11365
|
+
'Content-Type': 'application/json',
|
|
11366
|
+
'Idempotency-Key': attempt.idempotencyKey,
|
|
11367
|
+
},
|
|
11368
|
+
body: attempt.requestBody,
|
|
9884
11369
|
});
|
|
9885
11370
|
if (!resp.ok) {
|
|
9886
11371
|
throw new Error(`HTTP error! status: ${resp.status}`);
|
|
@@ -9889,6 +11374,7 @@ class Analytics {
|
|
|
9889
11374
|
if (result.token) {
|
|
9890
11375
|
this.token = result.token;
|
|
9891
11376
|
this.sessionStorage.setItem(STORAGEKEY, result.token);
|
|
11377
|
+
this.clearStartAttempt();
|
|
9892
11378
|
}
|
|
9893
11379
|
else {
|
|
9894
11380
|
throw new Error('No token received from server');
|
|
@@ -9915,6 +11401,7 @@ class Analytics {
|
|
|
9915
11401
|
if (this.standalone) {
|
|
9916
11402
|
this.token = null;
|
|
9917
11403
|
this.sessionStorage.setItem(STORAGEKEY, '');
|
|
11404
|
+
this.clearStartAttempt();
|
|
9918
11405
|
}
|
|
9919
11406
|
};
|
|
9920
11407
|
/**
|
|
@@ -9953,6 +11440,91 @@ class Analytics {
|
|
|
9953
11440
|
this.init();
|
|
9954
11441
|
}
|
|
9955
11442
|
}
|
|
11443
|
+
loadStartAttempt() {
|
|
11444
|
+
if (this.isValidStartAttempt(this.pendingStartAttempt)) {
|
|
11445
|
+
return this.pendingStartAttempt;
|
|
11446
|
+
}
|
|
11447
|
+
this.pendingStartAttempt = null;
|
|
11448
|
+
let serialized;
|
|
11449
|
+
try {
|
|
11450
|
+
serialized = this.sessionStorage.getItem(START_ATTEMPT_STORAGE_KEY);
|
|
11451
|
+
}
|
|
11452
|
+
catch {
|
|
11453
|
+
return null;
|
|
11454
|
+
}
|
|
11455
|
+
if (!serialized || serialized.length > 1024 * 1024) {
|
|
11456
|
+
return null;
|
|
11457
|
+
}
|
|
11458
|
+
try {
|
|
11459
|
+
const attempt = JSON.parse(serialized);
|
|
11460
|
+
if (this.isValidStartAttempt(attempt)) {
|
|
11461
|
+
this.pendingStartAttempt = attempt;
|
|
11462
|
+
return attempt;
|
|
11463
|
+
}
|
|
11464
|
+
}
|
|
11465
|
+
catch {
|
|
11466
|
+
// Treat corrupt storage as an absent pending attempt.
|
|
11467
|
+
}
|
|
11468
|
+
return null;
|
|
11469
|
+
}
|
|
11470
|
+
isValidStartAttempt(value) {
|
|
11471
|
+
if (typeof value !== 'object' || value === null) {
|
|
11472
|
+
return false;
|
|
11473
|
+
}
|
|
11474
|
+
const attempt = value;
|
|
11475
|
+
const currentTime = Date.now();
|
|
11476
|
+
if (attempt.version !== 1 ||
|
|
11477
|
+
attempt.projectKey !== this.projectKey ||
|
|
11478
|
+
typeof attempt.createdAt !== 'number' ||
|
|
11479
|
+
!Number.isFinite(attempt.createdAt) ||
|
|
11480
|
+
attempt.createdAt > currentTime ||
|
|
11481
|
+
currentTime - attempt.createdAt >= START_ATTEMPT_TTL_MS ||
|
|
11482
|
+
typeof attempt.idempotencyKey !== 'string' ||
|
|
11483
|
+
attempt.idempotencyKey.length < 8 ||
|
|
11484
|
+
attempt.idempotencyKey.length > 128 ||
|
|
11485
|
+
!/^[A-Za-z0-9._:-]+$/.test(attempt.idempotencyKey) ||
|
|
11486
|
+
typeof attempt.requestBody !== 'string' ||
|
|
11487
|
+
attempt.requestBody.length > 1024 * 1024) {
|
|
11488
|
+
return false;
|
|
11489
|
+
}
|
|
11490
|
+
try {
|
|
11491
|
+
const request = JSON.parse(attempt.requestBody);
|
|
11492
|
+
return request.projectKey === this.projectKey;
|
|
11493
|
+
}
|
|
11494
|
+
catch {
|
|
11495
|
+
return false;
|
|
11496
|
+
}
|
|
11497
|
+
}
|
|
11498
|
+
startAttempt(requestBody) {
|
|
11499
|
+
const existing = this.loadStartAttempt();
|
|
11500
|
+
if (existing) {
|
|
11501
|
+
return existing;
|
|
11502
|
+
}
|
|
11503
|
+
const attempt = {
|
|
11504
|
+
version: 1,
|
|
11505
|
+
projectKey: this.projectKey,
|
|
11506
|
+
createdAt: Date.now(),
|
|
11507
|
+
idempotencyKey: `insights:start:${generateRandomId(40)}`,
|
|
11508
|
+
requestBody,
|
|
11509
|
+
};
|
|
11510
|
+
this.pendingStartAttempt = attempt;
|
|
11511
|
+
try {
|
|
11512
|
+
this.sessionStorage.setItem(START_ATTEMPT_STORAGE_KEY, JSON.stringify(attempt));
|
|
11513
|
+
}
|
|
11514
|
+
catch {
|
|
11515
|
+
// The in-memory copy still protects retries within this Analytics instance.
|
|
11516
|
+
}
|
|
11517
|
+
return attempt;
|
|
11518
|
+
}
|
|
11519
|
+
clearStartAttempt() {
|
|
11520
|
+
this.pendingStartAttempt = null;
|
|
11521
|
+
try {
|
|
11522
|
+
this.sessionStorage.setItem(START_ATTEMPT_STORAGE_KEY, '');
|
|
11523
|
+
}
|
|
11524
|
+
catch {
|
|
11525
|
+
// The in-memory copy is still cleared when storage is unavailable.
|
|
11526
|
+
}
|
|
11527
|
+
}
|
|
9956
11528
|
}
|
|
9957
11529
|
|
|
9958
11530
|
const Messages = _Messages;
|
|
@@ -9999,7 +11571,7 @@ class API {
|
|
|
9999
11571
|
this.signalStartIssue = (reason, missingApi) => {
|
|
10000
11572
|
const doNotTrack = this.checkDoNotTrack();
|
|
10001
11573
|
console.log("Tracker couldn't start due to:", JSON.stringify({
|
|
10002
|
-
trackerVersion: '7.
|
|
11574
|
+
trackerVersion: '7.4.15',
|
|
10003
11575
|
siteKey: this.options.siteKey,
|
|
10004
11576
|
doNotTrack,
|
|
10005
11577
|
reason: missingApi.length ? `missing api: ${missingApi.join(',')}` : reason,
|
|
@@ -10365,6 +11937,12 @@ class API {
|
|
|
10365
11937
|
}
|
|
10366
11938
|
return this.app.getSessionSecret();
|
|
10367
11939
|
}
|
|
11940
|
+
requestMessengerLinkageChallenge() {
|
|
11941
|
+
if (this.app === null) {
|
|
11942
|
+
return Promise.resolve(null);
|
|
11943
|
+
}
|
|
11944
|
+
return this.app.requestMessengerLinkageChallenge();
|
|
11945
|
+
}
|
|
10368
11946
|
getSessionMeta() {
|
|
10369
11947
|
if (this.app === null) {
|
|
10370
11948
|
return null;
|
|
@@ -10523,6 +12101,12 @@ class BrowserSingleton {
|
|
|
10523
12101
|
}
|
|
10524
12102
|
return this.instance.getSessionSecret();
|
|
10525
12103
|
}
|
|
12104
|
+
requestMessengerLinkageChallenge() {
|
|
12105
|
+
if (!IN_BROWSER || !this.ensureConfigured() || !this.instance) {
|
|
12106
|
+
return Promise.resolve(null);
|
|
12107
|
+
}
|
|
12108
|
+
return this.instance.requestMessengerLinkageChallenge();
|
|
12109
|
+
}
|
|
10526
12110
|
track(key, payload = null, issue = false) {
|
|
10527
12111
|
if (!IN_BROWSER || !this.ensureConfigured() || !this.instance) {
|
|
10528
12112
|
return;
|