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