@oxvo/browser 7.1.0 → 7.3.4
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/common/interaction.d.ts +12 -1
- package/dist/cjs/common/messages.gen.d.ts +4 -7
- package/dist/cjs/entry.js +2382 -1191
- package/dist/cjs/entry.js.map +1 -1
- package/dist/cjs/index.js +2329 -1179
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/main/app/canvas.d.ts +13 -0
- package/dist/cjs/main/app/cross_domain_iframe.d.ts +44 -0
- package/dist/cjs/main/app/index.d.ts +63 -15
- package/dist/cjs/main/app/messages.gen.d.ts +2 -1
- package/dist/cjs/main/app/observer/observer.d.ts +16 -0
- package/dist/cjs/main/app/sanitizer.d.ts +5 -3
- 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 +17 -1
- package/dist/cjs/main/modules/analytics/batcher.d.ts +6 -1
- package/dist/cjs/main/modules/analytics/index.d.ts +10 -0
- package/dist/cjs/main/modules/input.d.ts +2 -1
- package/dist/cjs/main/modules/markerWatcher.d.ts +4 -8
- package/dist/cjs/main/modules/mouse.d.ts +1 -0
- package/dist/cjs/main/modules/tagMatcher.d.ts +28 -0
- package/dist/cjs/main/singleton.d.ts +24 -0
- package/dist/cjs/main/utils.d.ts +2 -0
- package/dist/cjs/package.json +1 -0
- package/dist/lib/common/interaction.d.ts +12 -1
- package/dist/lib/common/messages.gen.d.ts +4 -7
- package/dist/lib/entry.js +2382 -1191
- package/dist/lib/entry.js.map +1 -1
- package/dist/lib/index.js +2329 -1179
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/main/app/canvas.d.ts +13 -0
- package/dist/lib/main/app/cross_domain_iframe.d.ts +44 -0
- package/dist/lib/main/app/index.d.ts +63 -15
- package/dist/lib/main/app/messages.gen.d.ts +2 -1
- package/dist/lib/main/app/observer/observer.d.ts +16 -0
- package/dist/lib/main/app/sanitizer.d.ts +5 -3
- 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 +17 -1
- package/dist/lib/main/modules/analytics/batcher.d.ts +6 -1
- package/dist/lib/main/modules/analytics/index.d.ts +10 -0
- package/dist/lib/main/modules/input.d.ts +2 -1
- package/dist/lib/main/modules/markerWatcher.d.ts +4 -8
- package/dist/lib/main/modules/mouse.d.ts +1 -0
- package/dist/lib/main/modules/tagMatcher.d.ts +28 -0
- package/dist/lib/main/singleton.d.ts +24 -0
- package/dist/lib/main/utils.d.ts +2 -0
- package/dist/types/common/interaction.d.ts +12 -1
- package/dist/types/common/messages.gen.d.ts +4 -7
- package/dist/types/main/app/canvas.d.ts +13 -0
- package/dist/types/main/app/cross_domain_iframe.d.ts +44 -0
- package/dist/types/main/app/index.d.ts +63 -15
- package/dist/types/main/app/messages.gen.d.ts +2 -1
- package/dist/types/main/app/observer/observer.d.ts +16 -0
- package/dist/types/main/app/sanitizer.d.ts +5 -3
- 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 +17 -1
- package/dist/types/main/modules/analytics/batcher.d.ts +6 -1
- package/dist/types/main/modules/analytics/index.d.ts +10 -0
- package/dist/types/main/modules/input.d.ts +2 -1
- package/dist/types/main/modules/markerWatcher.d.ts +4 -8
- package/dist/types/main/modules/mouse.d.ts +1 -0
- package/dist/types/main/modules/tagMatcher.d.ts +28 -0
- package/dist/types/main/singleton.d.ts +24 -0
- package/dist/types/main/utils.d.ts +2 -0
- package/package.json +14 -14
package/dist/cjs/entry.js
CHANGED
|
@@ -2,719 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
4
|
|
|
5
|
-
// DEFLATE is a complex format; to read this code, you should probably check the RFC first:
|
|
6
|
-
// https://tools.ietf.org/html/rfc1951
|
|
7
|
-
// You may also wish to take a look at the guide I made about this program:
|
|
8
|
-
// https://gist.github.com/101arrowz/253f31eb5abc3d9275ab943003ffecad
|
|
9
|
-
// Some of the following code is similar to that of UZIP.js:
|
|
10
|
-
// https://github.com/photopea/UZIP.js
|
|
11
|
-
// However, the vast majority of the codebase has diverged from UZIP.js to increase performance and reduce bundle size.
|
|
12
|
-
// Sometimes 0 will appear where -1 would be more appropriate. This is because using a uint
|
|
13
|
-
// is better for memory in most engines (I *think*).
|
|
14
|
-
var ch2 = {};
|
|
15
|
-
var wk = (function (c, id, msg, transfer, cb) {
|
|
16
|
-
var w = new Worker(ch2[id] || (ch2[id] = URL.createObjectURL(new Blob([
|
|
17
|
-
c + ';addEventListener("error",function(e){e=e.error;postMessage({$e$:[e.message,e.code,e.stack]})})'
|
|
18
|
-
], { type: 'text/javascript' }))));
|
|
19
|
-
w.onmessage = function (e) {
|
|
20
|
-
var d = e.data, ed = d.$e$;
|
|
21
|
-
if (ed) {
|
|
22
|
-
var err = new Error(ed[0]);
|
|
23
|
-
err['code'] = ed[1];
|
|
24
|
-
err.stack = ed[2];
|
|
25
|
-
cb(err, null);
|
|
26
|
-
}
|
|
27
|
-
else
|
|
28
|
-
cb(null, d);
|
|
29
|
-
};
|
|
30
|
-
w.postMessage(msg, transfer);
|
|
31
|
-
return w;
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
// aliases for shorter compressed code (most minifers don't do this)
|
|
35
|
-
var u8 = Uint8Array, u16 = Uint16Array, i32 = Int32Array;
|
|
36
|
-
// fixed length extra bits
|
|
37
|
-
var fleb = new u8([0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, /* unused */ 0, 0, /* impossible */ 0]);
|
|
38
|
-
// fixed distance extra bits
|
|
39
|
-
var fdeb = new u8([0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, /* unused */ 0, 0]);
|
|
40
|
-
// code length index map
|
|
41
|
-
var clim = new u8([16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]);
|
|
42
|
-
// get base, reverse index map from extra bits
|
|
43
|
-
var freb = function (eb, start) {
|
|
44
|
-
var b = new u16(31);
|
|
45
|
-
for (var i = 0; i < 31; ++i) {
|
|
46
|
-
b[i] = start += 1 << eb[i - 1];
|
|
47
|
-
}
|
|
48
|
-
// numbers here are at max 18 bits
|
|
49
|
-
var r = new i32(b[30]);
|
|
50
|
-
for (var i = 1; i < 30; ++i) {
|
|
51
|
-
for (var j = b[i]; j < b[i + 1]; ++j) {
|
|
52
|
-
r[j] = ((j - b[i]) << 5) | i;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
return { b: b, r: r };
|
|
56
|
-
};
|
|
57
|
-
var _a = freb(fleb, 2), fl = _a.b, revfl = _a.r;
|
|
58
|
-
// we can ignore the fact that the other numbers are wrong; they never happen anyway
|
|
59
|
-
fl[28] = 258, revfl[258] = 28;
|
|
60
|
-
var _b = freb(fdeb, 0), revfd = _b.r;
|
|
61
|
-
// map of value to reverse (assuming 16 bits)
|
|
62
|
-
var rev = new u16(32768);
|
|
63
|
-
for (var i$1 = 0; i$1 < 32768; ++i$1) {
|
|
64
|
-
// reverse table algorithm from SO
|
|
65
|
-
var x$1 = ((i$1 & 0xAAAA) >> 1) | ((i$1 & 0x5555) << 1);
|
|
66
|
-
x$1 = ((x$1 & 0xCCCC) >> 2) | ((x$1 & 0x3333) << 2);
|
|
67
|
-
x$1 = ((x$1 & 0xF0F0) >> 4) | ((x$1 & 0x0F0F) << 4);
|
|
68
|
-
rev[i$1] = (((x$1 & 0xFF00) >> 8) | ((x$1 & 0x00FF) << 8)) >> 1;
|
|
69
|
-
}
|
|
70
|
-
// create huffman tree from u8 "map": index -> code length for code index
|
|
71
|
-
// mb (max bits) must be at most 15
|
|
72
|
-
// TODO: optimize/split up?
|
|
73
|
-
var hMap = (function (cd, mb, r) {
|
|
74
|
-
var s = cd.length;
|
|
75
|
-
// index
|
|
76
|
-
var i = 0;
|
|
77
|
-
// u16 "map": index -> # of codes with bit length = index
|
|
78
|
-
var l = new u16(mb);
|
|
79
|
-
// length of cd must be 288 (total # of codes)
|
|
80
|
-
for (; i < s; ++i) {
|
|
81
|
-
if (cd[i])
|
|
82
|
-
++l[cd[i] - 1];
|
|
83
|
-
}
|
|
84
|
-
// u16 "map": index -> minimum code for bit length = index
|
|
85
|
-
var le = new u16(mb);
|
|
86
|
-
for (i = 1; i < mb; ++i) {
|
|
87
|
-
le[i] = (le[i - 1] + l[i - 1]) << 1;
|
|
88
|
-
}
|
|
89
|
-
var co;
|
|
90
|
-
if (r) {
|
|
91
|
-
// u16 "map": index -> number of actual bits, symbol for code
|
|
92
|
-
co = new u16(1 << mb);
|
|
93
|
-
// bits to remove for reverser
|
|
94
|
-
var rvb = 15 - mb;
|
|
95
|
-
for (i = 0; i < s; ++i) {
|
|
96
|
-
// ignore 0 lengths
|
|
97
|
-
if (cd[i]) {
|
|
98
|
-
// num encoding both symbol and bits read
|
|
99
|
-
var sv = (i << 4) | cd[i];
|
|
100
|
-
// free bits
|
|
101
|
-
var r_1 = mb - cd[i];
|
|
102
|
-
// start value
|
|
103
|
-
var v = le[cd[i] - 1]++ << r_1;
|
|
104
|
-
// m is end value
|
|
105
|
-
for (var m = v | ((1 << r_1) - 1); v <= m; ++v) {
|
|
106
|
-
// every 16 bit value starting with the code yields the same result
|
|
107
|
-
co[rev[v] >> rvb] = sv;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
else {
|
|
113
|
-
co = new u16(s);
|
|
114
|
-
for (i = 0; i < s; ++i) {
|
|
115
|
-
if (cd[i]) {
|
|
116
|
-
co[i] = rev[le[cd[i] - 1]++] >> (15 - cd[i]);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
return co;
|
|
121
|
-
});
|
|
122
|
-
// fixed length tree
|
|
123
|
-
var flt = new u8(288);
|
|
124
|
-
for (var i$1 = 0; i$1 < 144; ++i$1)
|
|
125
|
-
flt[i$1] = 8;
|
|
126
|
-
for (var i$1 = 144; i$1 < 256; ++i$1)
|
|
127
|
-
flt[i$1] = 9;
|
|
128
|
-
for (var i$1 = 256; i$1 < 280; ++i$1)
|
|
129
|
-
flt[i$1] = 7;
|
|
130
|
-
for (var i$1 = 280; i$1 < 288; ++i$1)
|
|
131
|
-
flt[i$1] = 8;
|
|
132
|
-
// fixed distance tree
|
|
133
|
-
var fdt = new u8(32);
|
|
134
|
-
for (var i$1 = 0; i$1 < 32; ++i$1)
|
|
135
|
-
fdt[i$1] = 5;
|
|
136
|
-
// fixed length map
|
|
137
|
-
var flm = /*#__PURE__*/ hMap(flt, 9, 0);
|
|
138
|
-
// fixed distance map
|
|
139
|
-
var fdm = /*#__PURE__*/ hMap(fdt, 5, 0);
|
|
140
|
-
// get end of byte
|
|
141
|
-
var shft = function (p) { return ((p + 7) / 8) | 0; };
|
|
142
|
-
// typed array slice - allows garbage collector to free original reference,
|
|
143
|
-
// while being more compatible than .slice
|
|
144
|
-
var slc = function (v, s, e) {
|
|
145
|
-
if (s == null || s < 0)
|
|
146
|
-
s = 0;
|
|
147
|
-
if (e == null || e > v.length)
|
|
148
|
-
e = v.length;
|
|
149
|
-
// can't use .constructor in case user-supplied
|
|
150
|
-
return new u8(v.subarray(s, e));
|
|
151
|
-
};
|
|
152
|
-
// error codes
|
|
153
|
-
var ec = [
|
|
154
|
-
'unexpected EOF',
|
|
155
|
-
'invalid block type',
|
|
156
|
-
'invalid length/literal',
|
|
157
|
-
'invalid distance',
|
|
158
|
-
'stream finished',
|
|
159
|
-
'no stream handler',
|
|
160
|
-
,
|
|
161
|
-
'no callback',
|
|
162
|
-
'invalid UTF-8 data',
|
|
163
|
-
'extra field too long',
|
|
164
|
-
'date not in range 1980-2099',
|
|
165
|
-
'filename too long',
|
|
166
|
-
'stream finishing',
|
|
167
|
-
'invalid zip data'
|
|
168
|
-
// determined by unknown compression method
|
|
169
|
-
];
|
|
170
|
-
var err = function (ind, msg, nt) {
|
|
171
|
-
var e = new Error(msg || ec[ind]);
|
|
172
|
-
e.code = ind;
|
|
173
|
-
if (Error.captureStackTrace)
|
|
174
|
-
Error.captureStackTrace(e, err);
|
|
175
|
-
if (!nt)
|
|
176
|
-
throw e;
|
|
177
|
-
return e;
|
|
178
|
-
};
|
|
179
|
-
// starting at p, write the minimum number of bits that can hold v to d
|
|
180
|
-
var wbits = function (d, p, v) {
|
|
181
|
-
v <<= p & 7;
|
|
182
|
-
var o = (p / 8) | 0;
|
|
183
|
-
d[o] |= v;
|
|
184
|
-
d[o + 1] |= v >> 8;
|
|
185
|
-
};
|
|
186
|
-
// starting at p, write the minimum number of bits (>8) that can hold v to d
|
|
187
|
-
var wbits16 = function (d, p, v) {
|
|
188
|
-
v <<= p & 7;
|
|
189
|
-
var o = (p / 8) | 0;
|
|
190
|
-
d[o] |= v;
|
|
191
|
-
d[o + 1] |= v >> 8;
|
|
192
|
-
d[o + 2] |= v >> 16;
|
|
193
|
-
};
|
|
194
|
-
// creates code lengths from a frequency table
|
|
195
|
-
var hTree = function (d, mb) {
|
|
196
|
-
// Need extra info to make a tree
|
|
197
|
-
var t = [];
|
|
198
|
-
for (var i = 0; i < d.length; ++i) {
|
|
199
|
-
if (d[i])
|
|
200
|
-
t.push({ s: i, f: d[i] });
|
|
201
|
-
}
|
|
202
|
-
var s = t.length;
|
|
203
|
-
var t2 = t.slice();
|
|
204
|
-
if (!s)
|
|
205
|
-
return { t: et, l: 0 };
|
|
206
|
-
if (s == 1) {
|
|
207
|
-
var v = new u8(t[0].s + 1);
|
|
208
|
-
v[t[0].s] = 1;
|
|
209
|
-
return { t: v, l: 1 };
|
|
210
|
-
}
|
|
211
|
-
t.sort(function (a, b) { return a.f - b.f; });
|
|
212
|
-
// after i2 reaches last ind, will be stopped
|
|
213
|
-
// freq must be greater than largest possible number of symbols
|
|
214
|
-
t.push({ s: -1, f: 25001 });
|
|
215
|
-
var l = t[0], r = t[1], i0 = 0, i1 = 1, i2 = 2;
|
|
216
|
-
t[0] = { s: -1, f: l.f + r.f, l: l, r: r };
|
|
217
|
-
// efficient algorithm from UZIP.js
|
|
218
|
-
// i0 is lookbehind, i2 is lookahead - after processing two low-freq
|
|
219
|
-
// symbols that combined have high freq, will start processing i2 (high-freq,
|
|
220
|
-
// non-composite) symbols instead
|
|
221
|
-
// see https://reddit.com/r/photopea/comments/ikekht/uzipjs_questions/
|
|
222
|
-
while (i1 != s - 1) {
|
|
223
|
-
l = t[t[i0].f < t[i2].f ? i0++ : i2++];
|
|
224
|
-
r = t[i0 != i1 && t[i0].f < t[i2].f ? i0++ : i2++];
|
|
225
|
-
t[i1++] = { s: -1, f: l.f + r.f, l: l, r: r };
|
|
226
|
-
}
|
|
227
|
-
var maxSym = t2[0].s;
|
|
228
|
-
for (var i = 1; i < s; ++i) {
|
|
229
|
-
if (t2[i].s > maxSym)
|
|
230
|
-
maxSym = t2[i].s;
|
|
231
|
-
}
|
|
232
|
-
// code lengths
|
|
233
|
-
var tr = new u16(maxSym + 1);
|
|
234
|
-
// max bits in tree
|
|
235
|
-
var mbt = ln(t[i1 - 1], tr, 0);
|
|
236
|
-
if (mbt > mb) {
|
|
237
|
-
// more algorithms from UZIP.js
|
|
238
|
-
// TODO: find out how this code works (debt)
|
|
239
|
-
// ind debt
|
|
240
|
-
var i = 0, dt = 0;
|
|
241
|
-
// left cost
|
|
242
|
-
var lft = mbt - mb, cst = 1 << lft;
|
|
243
|
-
t2.sort(function (a, b) { return tr[b.s] - tr[a.s] || a.f - b.f; });
|
|
244
|
-
for (; i < s; ++i) {
|
|
245
|
-
var i2_1 = t2[i].s;
|
|
246
|
-
if (tr[i2_1] > mb) {
|
|
247
|
-
dt += cst - (1 << (mbt - tr[i2_1]));
|
|
248
|
-
tr[i2_1] = mb;
|
|
249
|
-
}
|
|
250
|
-
else
|
|
251
|
-
break;
|
|
252
|
-
}
|
|
253
|
-
dt >>= lft;
|
|
254
|
-
while (dt > 0) {
|
|
255
|
-
var i2_2 = t2[i].s;
|
|
256
|
-
if (tr[i2_2] < mb)
|
|
257
|
-
dt -= 1 << (mb - tr[i2_2]++ - 1);
|
|
258
|
-
else
|
|
259
|
-
++i;
|
|
260
|
-
}
|
|
261
|
-
for (; i >= 0 && dt; --i) {
|
|
262
|
-
var i2_3 = t2[i].s;
|
|
263
|
-
if (tr[i2_3] == mb) {
|
|
264
|
-
--tr[i2_3];
|
|
265
|
-
++dt;
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
mbt = mb;
|
|
269
|
-
}
|
|
270
|
-
return { t: new u8(tr), l: mbt };
|
|
271
|
-
};
|
|
272
|
-
// get the max length and assign length codes
|
|
273
|
-
var ln = function (n, l, d) {
|
|
274
|
-
return n.s == -1
|
|
275
|
-
? Math.max(ln(n.l, l, d + 1), ln(n.r, l, d + 1))
|
|
276
|
-
: (l[n.s] = d);
|
|
277
|
-
};
|
|
278
|
-
// length codes generation
|
|
279
|
-
var lc = function (c) {
|
|
280
|
-
var s = c.length;
|
|
281
|
-
// Note that the semicolon was intentional
|
|
282
|
-
while (s && !c[--s])
|
|
283
|
-
;
|
|
284
|
-
var cl = new u16(++s);
|
|
285
|
-
// ind num streak
|
|
286
|
-
var cli = 0, cln = c[0], cls = 1;
|
|
287
|
-
var w = function (v) { cl[cli++] = v; };
|
|
288
|
-
for (var i = 1; i <= s; ++i) {
|
|
289
|
-
if (c[i] == cln && i != s)
|
|
290
|
-
++cls;
|
|
291
|
-
else {
|
|
292
|
-
if (!cln && cls > 2) {
|
|
293
|
-
for (; cls > 138; cls -= 138)
|
|
294
|
-
w(32754);
|
|
295
|
-
if (cls > 2) {
|
|
296
|
-
w(cls > 10 ? ((cls - 11) << 5) | 28690 : ((cls - 3) << 5) | 12305);
|
|
297
|
-
cls = 0;
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
else if (cls > 3) {
|
|
301
|
-
w(cln), --cls;
|
|
302
|
-
for (; cls > 6; cls -= 6)
|
|
303
|
-
w(8304);
|
|
304
|
-
if (cls > 2)
|
|
305
|
-
w(((cls - 3) << 5) | 8208), cls = 0;
|
|
306
|
-
}
|
|
307
|
-
while (cls--)
|
|
308
|
-
w(cln);
|
|
309
|
-
cls = 1;
|
|
310
|
-
cln = c[i];
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
return { c: cl.subarray(0, cli), n: s };
|
|
314
|
-
};
|
|
315
|
-
// calculate the length of output from tree, code lengths
|
|
316
|
-
var clen = function (cf, cl) {
|
|
317
|
-
var l = 0;
|
|
318
|
-
for (var i = 0; i < cl.length; ++i)
|
|
319
|
-
l += cf[i] * cl[i];
|
|
320
|
-
return l;
|
|
321
|
-
};
|
|
322
|
-
// writes a fixed block
|
|
323
|
-
// returns the new bit pos
|
|
324
|
-
var wfblk = function (out, pos, dat) {
|
|
325
|
-
// no need to write 00 as type: TypedArray defaults to 0
|
|
326
|
-
var s = dat.length;
|
|
327
|
-
var o = shft(pos + 2);
|
|
328
|
-
out[o] = s & 255;
|
|
329
|
-
out[o + 1] = s >> 8;
|
|
330
|
-
out[o + 2] = out[o] ^ 255;
|
|
331
|
-
out[o + 3] = out[o + 1] ^ 255;
|
|
332
|
-
for (var i = 0; i < s; ++i)
|
|
333
|
-
out[o + i + 4] = dat[i];
|
|
334
|
-
return (o + 4 + s) * 8;
|
|
335
|
-
};
|
|
336
|
-
// writes a block
|
|
337
|
-
var wblk = function (dat, out, final, syms, lf, df, eb, li, bs, bl, p) {
|
|
338
|
-
wbits(out, p++, final);
|
|
339
|
-
++lf[256];
|
|
340
|
-
var _a = hTree(lf, 15), dlt = _a.t, mlb = _a.l;
|
|
341
|
-
var _b = hTree(df, 15), ddt = _b.t, mdb = _b.l;
|
|
342
|
-
var _c = lc(dlt), lclt = _c.c, nlc = _c.n;
|
|
343
|
-
var _d = lc(ddt), lcdt = _d.c, ndc = _d.n;
|
|
344
|
-
var lcfreq = new u16(19);
|
|
345
|
-
for (var i = 0; i < lclt.length; ++i)
|
|
346
|
-
++lcfreq[lclt[i] & 31];
|
|
347
|
-
for (var i = 0; i < lcdt.length; ++i)
|
|
348
|
-
++lcfreq[lcdt[i] & 31];
|
|
349
|
-
var _e = hTree(lcfreq, 7), lct = _e.t, mlcb = _e.l;
|
|
350
|
-
var nlcc = 19;
|
|
351
|
-
for (; nlcc > 4 && !lct[clim[nlcc - 1]]; --nlcc)
|
|
352
|
-
;
|
|
353
|
-
var flen = (bl + 5) << 3;
|
|
354
|
-
var ftlen = clen(lf, flt) + clen(df, fdt) + eb;
|
|
355
|
-
var dtlen = clen(lf, dlt) + clen(df, ddt) + eb + 14 + 3 * nlcc + clen(lcfreq, lct) + 2 * lcfreq[16] + 3 * lcfreq[17] + 7 * lcfreq[18];
|
|
356
|
-
if (bs >= 0 && flen <= ftlen && flen <= dtlen)
|
|
357
|
-
return wfblk(out, p, dat.subarray(bs, bs + bl));
|
|
358
|
-
var lm, ll, dm, dl;
|
|
359
|
-
wbits(out, p, 1 + (dtlen < ftlen)), p += 2;
|
|
360
|
-
if (dtlen < ftlen) {
|
|
361
|
-
lm = hMap(dlt, mlb, 0), ll = dlt, dm = hMap(ddt, mdb, 0), dl = ddt;
|
|
362
|
-
var llm = hMap(lct, mlcb, 0);
|
|
363
|
-
wbits(out, p, nlc - 257);
|
|
364
|
-
wbits(out, p + 5, ndc - 1);
|
|
365
|
-
wbits(out, p + 10, nlcc - 4);
|
|
366
|
-
p += 14;
|
|
367
|
-
for (var i = 0; i < nlcc; ++i)
|
|
368
|
-
wbits(out, p + 3 * i, lct[clim[i]]);
|
|
369
|
-
p += 3 * nlcc;
|
|
370
|
-
var lcts = [lclt, lcdt];
|
|
371
|
-
for (var it = 0; it < 2; ++it) {
|
|
372
|
-
var clct = lcts[it];
|
|
373
|
-
for (var i = 0; i < clct.length; ++i) {
|
|
374
|
-
var len = clct[i] & 31;
|
|
375
|
-
wbits(out, p, llm[len]), p += lct[len];
|
|
376
|
-
if (len > 15)
|
|
377
|
-
wbits(out, p, (clct[i] >> 5) & 127), p += clct[i] >> 12;
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
else {
|
|
382
|
-
lm = flm, ll = flt, dm = fdm, dl = fdt;
|
|
383
|
-
}
|
|
384
|
-
for (var i = 0; i < li; ++i) {
|
|
385
|
-
var sym = syms[i];
|
|
386
|
-
if (sym > 255) {
|
|
387
|
-
var len = (sym >> 18) & 31;
|
|
388
|
-
wbits16(out, p, lm[len + 257]), p += ll[len + 257];
|
|
389
|
-
if (len > 7)
|
|
390
|
-
wbits(out, p, (sym >> 23) & 31), p += fleb[len];
|
|
391
|
-
var dst = sym & 31;
|
|
392
|
-
wbits16(out, p, dm[dst]), p += dl[dst];
|
|
393
|
-
if (dst > 3)
|
|
394
|
-
wbits16(out, p, (sym >> 5) & 8191), p += fdeb[dst];
|
|
395
|
-
}
|
|
396
|
-
else {
|
|
397
|
-
wbits16(out, p, lm[sym]), p += ll[sym];
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
wbits16(out, p, lm[256]);
|
|
401
|
-
return p + ll[256];
|
|
402
|
-
};
|
|
403
|
-
// deflate options (nice << 13) | chain
|
|
404
|
-
var deo = /*#__PURE__*/ new i32([65540, 131080, 131088, 131104, 262176, 1048704, 1048832, 2114560, 2117632]);
|
|
405
|
-
// empty
|
|
406
|
-
var et = /*#__PURE__*/ new u8(0);
|
|
407
|
-
// compresses data into a raw DEFLATE buffer
|
|
408
|
-
var dflt = function (dat, lvl, plvl, pre, post, st) {
|
|
409
|
-
var s = st.z || dat.length;
|
|
410
|
-
var o = new u8(pre + s + 5 * (1 + Math.ceil(s / 7000)) + post);
|
|
411
|
-
// writing to this writes to the output buffer
|
|
412
|
-
var w = o.subarray(pre, o.length - post);
|
|
413
|
-
var lst = st.l;
|
|
414
|
-
var pos = (st.r || 0) & 7;
|
|
415
|
-
if (lvl) {
|
|
416
|
-
if (pos)
|
|
417
|
-
w[0] = st.r >> 3;
|
|
418
|
-
var opt = deo[lvl - 1];
|
|
419
|
-
var n = opt >> 13, c = opt & 8191;
|
|
420
|
-
var msk_1 = (1 << plvl) - 1;
|
|
421
|
-
// prev 2-byte val map curr 2-byte val map
|
|
422
|
-
var prev = st.p || new u16(32768), head = st.h || new u16(msk_1 + 1);
|
|
423
|
-
var bs1_1 = Math.ceil(plvl / 3), bs2_1 = 2 * bs1_1;
|
|
424
|
-
var hsh = function (i) { return (dat[i] ^ (dat[i + 1] << bs1_1) ^ (dat[i + 2] << bs2_1)) & msk_1; };
|
|
425
|
-
// 24576 is an arbitrary number of maximum symbols per block
|
|
426
|
-
// 424 buffer for last block
|
|
427
|
-
var syms = new i32(25000);
|
|
428
|
-
// length/literal freq distance freq
|
|
429
|
-
var lf = new u16(288), df = new u16(32);
|
|
430
|
-
// l/lcnt exbits index l/lind waitdx blkpos
|
|
431
|
-
var lc_1 = 0, eb = 0, i = st.i || 0, li = 0, wi = st.w || 0, bs = 0;
|
|
432
|
-
for (; i + 2 < s; ++i) {
|
|
433
|
-
// hash value
|
|
434
|
-
var hv = hsh(i);
|
|
435
|
-
// index mod 32768 previous index mod
|
|
436
|
-
var imod = i & 32767, pimod = head[hv];
|
|
437
|
-
prev[imod] = pimod;
|
|
438
|
-
head[hv] = imod;
|
|
439
|
-
// We always should modify head and prev, but only add symbols if
|
|
440
|
-
// this data is not yet processed ("wait" for wait index)
|
|
441
|
-
if (wi <= i) {
|
|
442
|
-
// bytes remaining
|
|
443
|
-
var rem = s - i;
|
|
444
|
-
if ((lc_1 > 7000 || li > 24576) && (rem > 423 || !lst)) {
|
|
445
|
-
pos = wblk(dat, w, 0, syms, lf, df, eb, li, bs, i - bs, pos);
|
|
446
|
-
li = lc_1 = eb = 0, bs = i;
|
|
447
|
-
for (var j = 0; j < 286; ++j)
|
|
448
|
-
lf[j] = 0;
|
|
449
|
-
for (var j = 0; j < 30; ++j)
|
|
450
|
-
df[j] = 0;
|
|
451
|
-
}
|
|
452
|
-
// len dist chain
|
|
453
|
-
var l = 2, d = 0, ch_1 = c, dif = imod - pimod & 32767;
|
|
454
|
-
if (rem > 2 && hv == hsh(i - dif)) {
|
|
455
|
-
var maxn = Math.min(n, rem) - 1;
|
|
456
|
-
var maxd = Math.min(32767, i);
|
|
457
|
-
// max possible length
|
|
458
|
-
// not capped at dif because decompressors implement "rolling" index population
|
|
459
|
-
var ml = Math.min(258, rem);
|
|
460
|
-
while (dif <= maxd && --ch_1 && imod != pimod) {
|
|
461
|
-
if (dat[i + l] == dat[i + l - dif]) {
|
|
462
|
-
var nl = 0;
|
|
463
|
-
for (; nl < ml && dat[i + nl] == dat[i + nl - dif]; ++nl)
|
|
464
|
-
;
|
|
465
|
-
if (nl > l) {
|
|
466
|
-
l = nl, d = dif;
|
|
467
|
-
// break out early when we reach "nice" (we are satisfied enough)
|
|
468
|
-
if (nl > maxn)
|
|
469
|
-
break;
|
|
470
|
-
// now, find the rarest 2-byte sequence within this
|
|
471
|
-
// length of literals and search for that instead.
|
|
472
|
-
// Much faster than just using the start
|
|
473
|
-
var mmd = Math.min(dif, nl - 2);
|
|
474
|
-
var md = 0;
|
|
475
|
-
for (var j = 0; j < mmd; ++j) {
|
|
476
|
-
var ti = i - dif + j & 32767;
|
|
477
|
-
var pti = prev[ti];
|
|
478
|
-
var cd = ti - pti & 32767;
|
|
479
|
-
if (cd > md)
|
|
480
|
-
md = cd, pimod = ti;
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
// check the previous match
|
|
485
|
-
imod = pimod, pimod = prev[imod];
|
|
486
|
-
dif += imod - pimod & 32767;
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
// d will be nonzero only when a match was found
|
|
490
|
-
if (d) {
|
|
491
|
-
// store both dist and len data in one int32
|
|
492
|
-
// Make sure this is recognized as a len/dist with 28th bit (2^28)
|
|
493
|
-
syms[li++] = 268435456 | (revfl[l] << 18) | revfd[d];
|
|
494
|
-
var lin = revfl[l] & 31, din = revfd[d] & 31;
|
|
495
|
-
eb += fleb[lin] + fdeb[din];
|
|
496
|
-
++lf[257 + lin];
|
|
497
|
-
++df[din];
|
|
498
|
-
wi = i + l;
|
|
499
|
-
++lc_1;
|
|
500
|
-
}
|
|
501
|
-
else {
|
|
502
|
-
syms[li++] = dat[i];
|
|
503
|
-
++lf[dat[i]];
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
for (i = Math.max(i, wi); i < s; ++i) {
|
|
508
|
-
syms[li++] = dat[i];
|
|
509
|
-
++lf[dat[i]];
|
|
510
|
-
}
|
|
511
|
-
pos = wblk(dat, w, lst, syms, lf, df, eb, li, bs, i - bs, pos);
|
|
512
|
-
if (!lst) {
|
|
513
|
-
st.r = (pos & 7) | w[(pos / 8) | 0] << 3;
|
|
514
|
-
// shft(pos) now 1 less if pos & 7 != 0
|
|
515
|
-
pos -= 7;
|
|
516
|
-
st.h = head, st.p = prev, st.i = i, st.w = wi;
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
else {
|
|
520
|
-
for (var i = st.w || 0; i < s + lst; i += 65535) {
|
|
521
|
-
// end
|
|
522
|
-
var e = i + 65535;
|
|
523
|
-
if (e >= s) {
|
|
524
|
-
// write final block
|
|
525
|
-
w[(pos / 8) | 0] = lst;
|
|
526
|
-
e = s;
|
|
527
|
-
}
|
|
528
|
-
pos = wfblk(w, pos + 1, dat.subarray(i, e));
|
|
529
|
-
}
|
|
530
|
-
st.i = s;
|
|
531
|
-
}
|
|
532
|
-
return slc(o, 0, pre + shft(pos) + post);
|
|
533
|
-
};
|
|
534
|
-
// CRC32 table
|
|
535
|
-
var crct = /*#__PURE__*/ (function () {
|
|
536
|
-
var t = new Int32Array(256);
|
|
537
|
-
for (var i = 0; i < 256; ++i) {
|
|
538
|
-
var c = i, k = 9;
|
|
539
|
-
while (--k)
|
|
540
|
-
c = ((c & 1) && -306674912) ^ (c >>> 1);
|
|
541
|
-
t[i] = c;
|
|
542
|
-
}
|
|
543
|
-
return t;
|
|
544
|
-
})();
|
|
545
|
-
// CRC32
|
|
546
|
-
var crc = function () {
|
|
547
|
-
var c = -1;
|
|
548
|
-
return {
|
|
549
|
-
p: function (d) {
|
|
550
|
-
// closures have awful performance
|
|
551
|
-
var cr = c;
|
|
552
|
-
for (var i = 0; i < d.length; ++i)
|
|
553
|
-
cr = crct[(cr & 255) ^ d[i]] ^ (cr >>> 8);
|
|
554
|
-
c = cr;
|
|
555
|
-
},
|
|
556
|
-
d: function () { return ~c; }
|
|
557
|
-
};
|
|
558
|
-
};
|
|
559
|
-
// deflate with opts
|
|
560
|
-
var dopt = function (dat, opt, pre, post, st) {
|
|
561
|
-
if (!st) {
|
|
562
|
-
st = { l: 1 };
|
|
563
|
-
if (opt.dictionary) {
|
|
564
|
-
var dict = opt.dictionary.subarray(-32768);
|
|
565
|
-
var newDat = new u8(dict.length + dat.length);
|
|
566
|
-
newDat.set(dict);
|
|
567
|
-
newDat.set(dat, dict.length);
|
|
568
|
-
dat = newDat;
|
|
569
|
-
st.w = dict.length;
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
return dflt(dat, opt.level == null ? 6 : opt.level, opt.mem == null ? (st.l ? Math.ceil(Math.max(8, Math.min(13, Math.log(dat.length))) * 1.5) : 20) : (12 + opt.mem), pre, post, st);
|
|
573
|
-
};
|
|
574
|
-
// Walmart object spread
|
|
575
|
-
var mrg = function (a, b) {
|
|
576
|
-
var o = {};
|
|
577
|
-
for (var k in a)
|
|
578
|
-
o[k] = a[k];
|
|
579
|
-
for (var k in b)
|
|
580
|
-
o[k] = b[k];
|
|
581
|
-
return o;
|
|
582
|
-
};
|
|
583
|
-
// worker clone
|
|
584
|
-
// This is possibly the craziest part of the entire codebase, despite how simple it may seem.
|
|
585
|
-
// The only parameter to this function is a closure that returns an array of variables outside of the function scope.
|
|
586
|
-
// We're going to try to figure out the variable names used in the closure as strings because that is crucial for workerization.
|
|
587
|
-
// We will return an object mapping of true variable name to value (basically, the current scope as a JS object).
|
|
588
|
-
// The reason we can't just use the original variable names is minifiers mangling the toplevel scope.
|
|
589
|
-
// This took me three weeks to figure out how to do.
|
|
590
|
-
var wcln = function (fn, fnStr, td) {
|
|
591
|
-
var dt = fn();
|
|
592
|
-
var st = fn.toString();
|
|
593
|
-
var ks = st.slice(st.indexOf('[') + 1, st.lastIndexOf(']')).replace(/\s+/g, '').split(',');
|
|
594
|
-
for (var i = 0; i < dt.length; ++i) {
|
|
595
|
-
var v = dt[i], k = ks[i];
|
|
596
|
-
if (typeof v == 'function') {
|
|
597
|
-
fnStr += ';' + k + '=';
|
|
598
|
-
var st_1 = v.toString();
|
|
599
|
-
if (v.prototype) {
|
|
600
|
-
// for global objects
|
|
601
|
-
if (st_1.indexOf('[native code]') != -1) {
|
|
602
|
-
var spInd = st_1.indexOf(' ', 8) + 1;
|
|
603
|
-
fnStr += st_1.slice(spInd, st_1.indexOf('(', spInd));
|
|
604
|
-
}
|
|
605
|
-
else {
|
|
606
|
-
fnStr += st_1;
|
|
607
|
-
for (var t in v.prototype)
|
|
608
|
-
fnStr += ';' + k + '.prototype.' + t + '=' + v.prototype[t].toString();
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
else
|
|
612
|
-
fnStr += st_1;
|
|
613
|
-
}
|
|
614
|
-
else
|
|
615
|
-
td[k] = v;
|
|
616
|
-
}
|
|
617
|
-
return fnStr;
|
|
618
|
-
};
|
|
619
|
-
var ch = [];
|
|
620
|
-
// clone bufs
|
|
621
|
-
var cbfs = function (v) {
|
|
622
|
-
var tl = [];
|
|
623
|
-
for (var k in v) {
|
|
624
|
-
if (v[k].buffer) {
|
|
625
|
-
tl.push((v[k] = new v[k].constructor(v[k])).buffer);
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
return tl;
|
|
629
|
-
};
|
|
630
|
-
// use a worker to execute code
|
|
631
|
-
var wrkr = function (fns, init, id, cb) {
|
|
632
|
-
if (!ch[id]) {
|
|
633
|
-
var fnStr = '', td_1 = {}, m = fns.length - 1;
|
|
634
|
-
for (var i = 0; i < m; ++i)
|
|
635
|
-
fnStr = wcln(fns[i], fnStr, td_1);
|
|
636
|
-
ch[id] = { c: wcln(fns[m], fnStr, td_1), e: td_1 };
|
|
637
|
-
}
|
|
638
|
-
var td = mrg({}, ch[id].e);
|
|
639
|
-
return wk(ch[id].c + ';onmessage=function(e){for(var k in e.data)self[k]=e.data[k];onmessage=' + init.toString() + '}', id, td, cbfs(td), cb);
|
|
640
|
-
};
|
|
641
|
-
var bDflt = function () { return [u8, u16, i32, fleb, fdeb, clim, revfl, revfd, flm, flt, fdm, fdt, rev, deo, et, hMap, wbits, wbits16, hTree, ln, lc, clen, wfblk, wblk, shft, slc, dflt, dopt, deflateSync, pbf]; };
|
|
642
|
-
// gzip extra
|
|
643
|
-
var gze = function () { return [gzh, gzhl, wbytes, crc, crct]; };
|
|
644
|
-
// post buf
|
|
645
|
-
var pbf = function (msg) { return postMessage(msg, [msg.buffer]); };
|
|
646
|
-
// async helper
|
|
647
|
-
var cbify = function (dat, opts, fns, init, id, cb) {
|
|
648
|
-
var w = wrkr(fns, init, id, function (err, dat) {
|
|
649
|
-
w.terminate();
|
|
650
|
-
cb(err, dat);
|
|
651
|
-
});
|
|
652
|
-
w.postMessage([dat, opts], opts.consume ? [dat.buffer] : []);
|
|
653
|
-
return function () { w.terminate(); };
|
|
654
|
-
};
|
|
655
|
-
// write bytes
|
|
656
|
-
var wbytes = function (d, b, v) {
|
|
657
|
-
for (; v; ++b)
|
|
658
|
-
d[b] = v, v >>>= 8;
|
|
659
|
-
};
|
|
660
|
-
// gzip header
|
|
661
|
-
var gzh = function (c, o) {
|
|
662
|
-
var fn = o.filename;
|
|
663
|
-
c[0] = 31, c[1] = 139, c[2] = 8, c[8] = o.level < 2 ? 4 : o.level == 9 ? 2 : 0, c[9] = 3; // assume Unix
|
|
664
|
-
if (o.mtime != 0)
|
|
665
|
-
wbytes(c, 4, Math.floor(new Date(o.mtime || Date.now()) / 1000));
|
|
666
|
-
if (fn) {
|
|
667
|
-
c[3] = 8;
|
|
668
|
-
for (var i = 0; i <= fn.length; ++i)
|
|
669
|
-
c[i + 10] = fn.charCodeAt(i);
|
|
670
|
-
}
|
|
671
|
-
};
|
|
672
|
-
// gzip header length
|
|
673
|
-
var gzhl = function (o) { return 10 + (o.filename ? o.filename.length + 1 : 0); };
|
|
674
|
-
/**
|
|
675
|
-
* Compresses data with DEFLATE without any wrapper
|
|
676
|
-
* @param data The data to compress
|
|
677
|
-
* @param opts The compression options
|
|
678
|
-
* @returns The deflated version of the data
|
|
679
|
-
*/
|
|
680
|
-
function deflateSync(data, opts) {
|
|
681
|
-
return dopt(data, opts || {}, 0, 0);
|
|
682
|
-
}
|
|
683
|
-
function gzip(data, opts, cb) {
|
|
684
|
-
if (!cb)
|
|
685
|
-
cb = opts, opts = {};
|
|
686
|
-
if (typeof cb != 'function')
|
|
687
|
-
err(7);
|
|
688
|
-
return cbify(data, opts, [
|
|
689
|
-
bDflt,
|
|
690
|
-
gze,
|
|
691
|
-
function () { return [gzipSync]; }
|
|
692
|
-
], function (ev) { return pbf(gzipSync(ev.data[0], ev.data[1])); }, 2, cb);
|
|
693
|
-
}
|
|
694
|
-
/**
|
|
695
|
-
* Compresses data with GZIP
|
|
696
|
-
* @param data The data to compress
|
|
697
|
-
* @param opts The compression options
|
|
698
|
-
* @returns The gzipped version of the data
|
|
699
|
-
*/
|
|
700
|
-
function gzipSync(data, opts) {
|
|
701
|
-
if (!opts)
|
|
702
|
-
opts = {};
|
|
703
|
-
var c = crc(), l = data.length;
|
|
704
|
-
c.p(data);
|
|
705
|
-
var d = dopt(data, opts, gzhl(opts), 8), s = d.length;
|
|
706
|
-
return gzh(d, opts), wbytes(d, s - 8, c.d()), wbytes(d, s - 4, l), d;
|
|
707
|
-
}
|
|
708
|
-
// text decoder
|
|
709
|
-
var td = typeof TextDecoder != 'undefined' && /*#__PURE__*/ new TextDecoder();
|
|
710
|
-
// text decoder stream
|
|
711
|
-
var tds = 0;
|
|
712
|
-
try {
|
|
713
|
-
td.decode(et, { stream: true });
|
|
714
|
-
tds = 1;
|
|
715
|
-
}
|
|
716
|
-
catch (e) { }
|
|
717
|
-
|
|
718
5
|
class StringDictionary {
|
|
719
6
|
constructor() {
|
|
720
7
|
this.lastTs = 0;
|
|
@@ -1165,6 +452,23 @@ function deprecationWarn(nameOfFeature, useInstead, docsPath = '/') {
|
|
|
1165
452
|
console.warn(`OxvoSessions: ${nameOfFeature} is deprecated. ${useInstead ? `Please, use ${useInstead} instead.` : ''} Visit ${DOCS_HOST}${docsPath} for more information.`);
|
|
1166
453
|
warnedFeatures[nameOfFeature] = true;
|
|
1167
454
|
}
|
|
455
|
+
function getCustomAttributeLabel(e, customAttributes) {
|
|
456
|
+
if (!customAttributes || customAttributes.length === 0)
|
|
457
|
+
return '';
|
|
458
|
+
const parts = [];
|
|
459
|
+
for (const attr of customAttributes) {
|
|
460
|
+
const value = e.getAttribute(attr);
|
|
461
|
+
if (value !== null) {
|
|
462
|
+
parts.push(`[${attr}=${value}]`);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
return parts.join('');
|
|
466
|
+
}
|
|
467
|
+
function getClassSelector(e) {
|
|
468
|
+
if (!e.classList || e.classList.length === 0)
|
|
469
|
+
return '';
|
|
470
|
+
return '.' + Array.from(e.classList).join('.');
|
|
471
|
+
}
|
|
1168
472
|
function getLabelAttribute(e) {
|
|
1169
473
|
let value = e.getAttribute('data-oxvosessions-label');
|
|
1170
474
|
if (value !== null) {
|
|
@@ -1230,7 +534,7 @@ function dec2hex(dec) {
|
|
|
1230
534
|
return dec.toString(16).padStart(2, '0');
|
|
1231
535
|
}
|
|
1232
536
|
function generateRandomId(len) {
|
|
1233
|
-
const arr = new Uint8Array((len) / 2);
|
|
537
|
+
const arr = new Uint8Array((len || 40) / 2);
|
|
1234
538
|
// msCrypto = IE11
|
|
1235
539
|
// @ts-ignore
|
|
1236
540
|
const safeCrypto = window.crypto || window.msCrypto;
|
|
@@ -1239,7 +543,7 @@ function generateRandomId(len) {
|
|
|
1239
543
|
return Array.from(arr, dec2hex).join('');
|
|
1240
544
|
}
|
|
1241
545
|
else {
|
|
1242
|
-
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('');
|
|
1243
547
|
}
|
|
1244
548
|
}
|
|
1245
549
|
function inIframe() {
|
|
@@ -1436,6 +740,88 @@ function throttleWithTrailing(fn, interval) {
|
|
|
1436
740
|
|
|
1437
741
|
// Auto-generated, do not edit
|
|
1438
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
|
+
};
|
|
1439
825
|
function Timestamp(timestamp) {
|
|
1440
826
|
return [
|
|
1441
827
|
0 /* Messages.Type.Timestamp */,
|
|
@@ -1921,13 +1307,6 @@ function BatchMetadata(version, pageNo, firstIndex, timestamp, location) {
|
|
|
1921
1307
|
location,
|
|
1922
1308
|
];
|
|
1923
1309
|
}
|
|
1924
|
-
function PartitionedMessage(partNo, partTotal) {
|
|
1925
|
-
return [
|
|
1926
|
-
82 /* Messages.Type.PartitionedMessage */,
|
|
1927
|
-
partNo,
|
|
1928
|
-
partTotal,
|
|
1929
|
-
];
|
|
1930
|
-
}
|
|
1931
1310
|
function NetworkRequest(type, method, url, request, response, status, timestamp, duration, transferredBodySize) {
|
|
1932
1311
|
return [
|
|
1933
1312
|
83 /* Messages.Type.NetworkRequest */,
|
|
@@ -2125,6 +1504,7 @@ var _Messages = /*#__PURE__*/Object.freeze({
|
|
|
2125
1504
|
JSException: JSException,
|
|
2126
1505
|
LoadFontFace: LoadFontFace,
|
|
2127
1506
|
LongAnimationTask: LongAnimationTask$1,
|
|
1507
|
+
MESSAGE_FIELD_TYPES: MESSAGE_FIELD_TYPES,
|
|
2128
1508
|
Metadata: Metadata,
|
|
2129
1509
|
MobX: MobX,
|
|
2130
1510
|
MouseClick: MouseClick,
|
|
@@ -2139,7 +1519,6 @@ var _Messages = /*#__PURE__*/Object.freeze({
|
|
|
2139
1519
|
OTable: OTable,
|
|
2140
1520
|
PageLoadTiming: PageLoadTiming,
|
|
2141
1521
|
PageRenderTiming: PageRenderTiming,
|
|
2142
|
-
PartitionedMessage: PartitionedMessage,
|
|
2143
1522
|
PerformanceTrack: PerformanceTrack,
|
|
2144
1523
|
Profiler: Profiler,
|
|
2145
1524
|
Redux: Redux,
|
|
@@ -2234,11 +1613,157 @@ function Performance (app, opts) {
|
|
|
2234
1613
|
}
|
|
2235
1614
|
}
|
|
2236
1615
|
|
|
1616
|
+
/**
|
|
1617
|
+
* Two-tier tag matching:
|
|
1618
|
+
* 1. Fast fingerprint lookup by id, data-attr,
|
|
1619
|
+
* or class from the selector's last segment
|
|
1620
|
+
* 2. Fallback iteration using native element.matches()
|
|
1621
|
+
*/
|
|
1622
|
+
class TagMatcher {
|
|
1623
|
+
constructor() {
|
|
1624
|
+
this.tags = [];
|
|
1625
|
+
this.byId = new Map();
|
|
1626
|
+
this.byDataAttr = new Map();
|
|
1627
|
+
this.byClass = new Map();
|
|
1628
|
+
this.fallback = [];
|
|
1629
|
+
}
|
|
1630
|
+
setTags(tags) {
|
|
1631
|
+
this.tags = tags;
|
|
1632
|
+
this.byId.clear();
|
|
1633
|
+
this.byDataAttr.clear();
|
|
1634
|
+
this.byClass.clear();
|
|
1635
|
+
this.fallback = [];
|
|
1636
|
+
for (const tag of tags) {
|
|
1637
|
+
const last = lastSegment(tag.selector);
|
|
1638
|
+
if (!last) {
|
|
1639
|
+
this.fallback.push(tag);
|
|
1640
|
+
continue;
|
|
1641
|
+
}
|
|
1642
|
+
if (last.startsWith('#')) {
|
|
1643
|
+
this.byId.set(last.slice(1), tag);
|
|
1644
|
+
}
|
|
1645
|
+
else if (last.startsWith('[data-')) {
|
|
1646
|
+
this.byDataAttr.set(last, tag);
|
|
1647
|
+
}
|
|
1648
|
+
else {
|
|
1649
|
+
const cls = extractClass(last);
|
|
1650
|
+
if (cls) {
|
|
1651
|
+
this.byClass.set(cls, tag);
|
|
1652
|
+
}
|
|
1653
|
+
else {
|
|
1654
|
+
this.fallback.push(tag);
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
getTags() {
|
|
1660
|
+
return this.tags;
|
|
1661
|
+
}
|
|
1662
|
+
/** Match element, its parent, or direct children against known tag selectors */
|
|
1663
|
+
match(el) {
|
|
1664
|
+
const direct = this.matchExact(el);
|
|
1665
|
+
if (direct)
|
|
1666
|
+
return direct;
|
|
1667
|
+
if (el.parentElement) {
|
|
1668
|
+
const parent = this.matchExact(el.parentElement);
|
|
1669
|
+
if (parent)
|
|
1670
|
+
return parent;
|
|
1671
|
+
}
|
|
1672
|
+
const children = el.children;
|
|
1673
|
+
for (let i = 0; i < children.length; i++) {
|
|
1674
|
+
const child = this.matchExact(children[i]);
|
|
1675
|
+
if (child)
|
|
1676
|
+
return child;
|
|
1677
|
+
}
|
|
1678
|
+
return null;
|
|
1679
|
+
}
|
|
1680
|
+
matchExact(el) {
|
|
1681
|
+
if (el.id && this.byId.has(el.id)) {
|
|
1682
|
+
const tag = this.byId.get(el.id);
|
|
1683
|
+
if (safeMatches(el, tag.selector) && matchesLocation(tag))
|
|
1684
|
+
return tag;
|
|
1685
|
+
}
|
|
1686
|
+
if (this.byDataAttr.size > 0) {
|
|
1687
|
+
const attrs = el.attributes;
|
|
1688
|
+
for (let i = 0; i < attrs.length; i++) {
|
|
1689
|
+
const attr = attrs[i];
|
|
1690
|
+
if (attr.name.startsWith('data-')) {
|
|
1691
|
+
const key = `[${attr.name}="${attr.value}"]`;
|
|
1692
|
+
if (this.byDataAttr.has(key)) {
|
|
1693
|
+
const tag = this.byDataAttr.get(key);
|
|
1694
|
+
if (safeMatches(el, tag.selector) && matchesLocation(tag))
|
|
1695
|
+
return tag;
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
if (this.byClass.size > 0 && el.classList) {
|
|
1701
|
+
for (let i = 0; i < el.classList.length; i++) {
|
|
1702
|
+
const cls = el.classList[i];
|
|
1703
|
+
if (this.byClass.has(cls)) {
|
|
1704
|
+
const tag = this.byClass.get(cls);
|
|
1705
|
+
if (safeMatches(el, tag.selector) && matchesLocation(tag))
|
|
1706
|
+
return tag;
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
for (const tag of this.fallback) {
|
|
1711
|
+
if (safeMatches(el, tag.selector) && matchesLocation(tag))
|
|
1712
|
+
return tag;
|
|
1713
|
+
}
|
|
1714
|
+
return null;
|
|
1715
|
+
}
|
|
1716
|
+
clear() {
|
|
1717
|
+
this.tags = [];
|
|
1718
|
+
this.byId.clear();
|
|
1719
|
+
this.byDataAttr.clear();
|
|
1720
|
+
this.byClass.clear();
|
|
1721
|
+
this.fallback = [];
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1724
|
+
/** Last combinator-separated segment of a CSS selector */
|
|
1725
|
+
function lastSegment(selector) {
|
|
1726
|
+
const trimmed = selector.trim();
|
|
1727
|
+
if (!trimmed)
|
|
1728
|
+
return null;
|
|
1729
|
+
const parts = trimmed.split(/\s*[>+~ ]\s*/);
|
|
1730
|
+
const last = parts[parts.length - 1]?.trim();
|
|
1731
|
+
return last || null;
|
|
1732
|
+
}
|
|
1733
|
+
/** First class name from a selector segment, e.g. "div.my-class" -> "my-class" */
|
|
1734
|
+
function extractClass(segment) {
|
|
1735
|
+
const match = segment.match(/\.([a-zA-Z_-][\w-]*)/);
|
|
1736
|
+
return match ? match[1] : null;
|
|
1737
|
+
}
|
|
1738
|
+
function safeMatches(el, selector) {
|
|
1739
|
+
try {
|
|
1740
|
+
return el.matches(selector);
|
|
1741
|
+
}
|
|
1742
|
+
catch {
|
|
1743
|
+
return false;
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
function matchesLocation(tag) {
|
|
1747
|
+
if (!tag.location)
|
|
1748
|
+
return true;
|
|
1749
|
+
try {
|
|
1750
|
+
const loc = tag.location;
|
|
1751
|
+
if (loc.startsWith('/')) {
|
|
1752
|
+
return window.location.pathname === loc;
|
|
1753
|
+
}
|
|
1754
|
+
return window.location.href === loc;
|
|
1755
|
+
}
|
|
1756
|
+
catch {
|
|
1757
|
+
return true;
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1760
|
+
|
|
2237
1761
|
const WATCHED_MARKERS_KEY = '__or__watched_markers__';
|
|
2238
1762
|
class MarkerWatcher {
|
|
2239
1763
|
constructor(params) {
|
|
2240
1764
|
this.interval = null;
|
|
2241
1765
|
this.tags = [];
|
|
1766
|
+
this.matcher = new TagMatcher();
|
|
2242
1767
|
this.sessionStorage = params.sessionStorage;
|
|
2243
1768
|
this.errLog = params.errLog;
|
|
2244
1769
|
this.onMarkerHit = params.onMarkerHit;
|
|
@@ -2281,12 +1806,15 @@ class MarkerWatcher {
|
|
|
2281
1806
|
}
|
|
2282
1807
|
setTags(tags) {
|
|
2283
1808
|
this.tags = tags;
|
|
1809
|
+
this.matcher.setTags(tags);
|
|
2284
1810
|
if (this.interval) {
|
|
2285
1811
|
clearInterval(this.interval);
|
|
2286
1812
|
this.interval = null;
|
|
2287
1813
|
}
|
|
2288
1814
|
this.interval = setInterval(() => {
|
|
2289
1815
|
this.tags.forEach((tag) => {
|
|
1816
|
+
if (!matchesLocation(tag))
|
|
1817
|
+
return;
|
|
2290
1818
|
const possibleEls = document.querySelectorAll(tag.selector);
|
|
2291
1819
|
if (possibleEls.length > 0) {
|
|
2292
1820
|
const el = possibleEls[0];
|
|
@@ -2298,13 +1826,17 @@ class MarkerWatcher {
|
|
|
2298
1826
|
}, 500);
|
|
2299
1827
|
}
|
|
2300
1828
|
onMarkerRendered(markerId) {
|
|
2301
|
-
|
|
2302
|
-
|
|
1829
|
+
const tag = this.tags.find((t) => t.id === markerId);
|
|
1830
|
+
if (tag) {
|
|
1831
|
+
this.tags = this.tags.filter((t) => t.id !== markerId);
|
|
1832
|
+
if (!matchesLocation(tag))
|
|
1833
|
+
return;
|
|
2303
1834
|
}
|
|
2304
1835
|
this.onMarkerHit(markerId);
|
|
2305
1836
|
}
|
|
2306
1837
|
clear() {
|
|
2307
1838
|
this.tags = [];
|
|
1839
|
+
this.matcher.clear();
|
|
2308
1840
|
if (this.interval) {
|
|
2309
1841
|
clearInterval(this.interval);
|
|
2310
1842
|
this.interval = null;
|
|
@@ -2353,8 +1885,34 @@ class CanvasRecorder {
|
|
|
2353
1885
|
this.uploadQueue = 0;
|
|
2354
1886
|
this.MAX_CONCURRENT_UPLOADS = 2;
|
|
2355
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;
|
|
2356
1890
|
this.pendingBatches = [];
|
|
2357
1891
|
this.isProcessingQueue = false;
|
|
1892
|
+
/**
|
|
1893
|
+
* Reacts to a runtime sanitization change on a canvas: stop capturing if it
|
|
1894
|
+
* just became masked, start if it just became visible. Already-sent frames
|
|
1895
|
+
* cannot be retracted, so escalation only stops future capture.
|
|
1896
|
+
*/
|
|
1897
|
+
this.resanitizeCanvas = (node, id) => {
|
|
1898
|
+
if (!hasTag(node, 'canvas')) {
|
|
1899
|
+
return;
|
|
1900
|
+
}
|
|
1901
|
+
const isIgnored = this.app.sanitizer.isObscured(id) || this.app.sanitizer.isHidden(id);
|
|
1902
|
+
if (isIgnored) {
|
|
1903
|
+
if (this.snapshots[id] || this.observers.has(id)) {
|
|
1904
|
+
const observer = this.observers.get(id);
|
|
1905
|
+
if (observer) {
|
|
1906
|
+
observer.disconnect();
|
|
1907
|
+
this.observers.delete(id);
|
|
1908
|
+
}
|
|
1909
|
+
this.cleanupCanvas(id);
|
|
1910
|
+
}
|
|
1911
|
+
}
|
|
1912
|
+
else if (!this.snapshots[id] && !this.observers.has(id)) {
|
|
1913
|
+
this.captureCanvas(node);
|
|
1914
|
+
}
|
|
1915
|
+
};
|
|
2358
1916
|
this.restartTracking = () => {
|
|
2359
1917
|
this.clear();
|
|
2360
1918
|
this.app.nodes.scanTree(this.captureCanvas);
|
|
@@ -2451,14 +2009,15 @@ class CanvasRecorder {
|
|
|
2451
2009
|
}, this.interval);
|
|
2452
2010
|
this.intervals.set(id, int);
|
|
2453
2011
|
};
|
|
2454
|
-
this.fileExt =
|
|
2012
|
+
this.fileExt = 'webp';
|
|
2455
2013
|
this.interval = 1000 / options.fps;
|
|
2456
2014
|
}
|
|
2457
2015
|
startTracking() {
|
|
2458
2016
|
setTimeout(() => {
|
|
2459
2017
|
this.app.nodes.scanTree(this.captureCanvas);
|
|
2460
2018
|
this.app.nodes.attachNodeCallback(this.captureCanvas);
|
|
2461
|
-
|
|
2019
|
+
this.app.attachResanitizeCallback(this.resanitizeCanvas);
|
|
2020
|
+
}, 125);
|
|
2462
2021
|
}
|
|
2463
2022
|
sendSnaps(images, canvasId, createdAt) {
|
|
2464
2023
|
if (Object.keys(this.snapshots).length === 0) {
|
|
@@ -2468,37 +2027,111 @@ class CanvasRecorder {
|
|
|
2468
2027
|
this.app.debug.warn('Upload queue full, dropping canvas batch');
|
|
2469
2028
|
return;
|
|
2470
2029
|
}
|
|
2471
|
-
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 });
|
|
2472
2036
|
if (!this.isProcessingQueue) {
|
|
2473
|
-
this.processUploadQueue();
|
|
2037
|
+
void this.processUploadQueue();
|
|
2474
2038
|
}
|
|
2475
2039
|
}
|
|
2476
2040
|
async processUploadQueue() {
|
|
2041
|
+
if (this.isProcessingQueue)
|
|
2042
|
+
return;
|
|
2477
2043
|
this.isProcessingQueue = true;
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
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);
|
|
2482
2050
|
}
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
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();
|
|
2061
|
+
}
|
|
2062
|
+
}
|
|
2063
|
+
}
|
|
2064
|
+
async uploadBatch(images, canvasId, createdAt, token) {
|
|
2065
|
+
if (this.options.isDebug) {
|
|
2066
|
+
const packed = await packFrames(images);
|
|
2067
|
+
if (packed) {
|
|
2068
|
+
const fileName = `${createdAt}_${canvasId}.${this.fileExt}.frames`;
|
|
2069
|
+
const url = URL.createObjectURL(new Blob([packed]));
|
|
2070
|
+
const link = document.createElement('a');
|
|
2071
|
+
link.href = url;
|
|
2072
|
+
link.download = fileName;
|
|
2073
|
+
link.style.display = 'none';
|
|
2074
|
+
document.body.appendChild(link);
|
|
2075
|
+
link.click();
|
|
2076
|
+
document.body.removeChild(link);
|
|
2077
|
+
URL.revokeObjectURL(url);
|
|
2078
|
+
}
|
|
2079
|
+
// fall through to also send to backend
|
|
2080
|
+
}
|
|
2081
|
+
let formData;
|
|
2082
|
+
if (this.options.framesSupport) {
|
|
2083
|
+
// Pack frames into binary format: [uint64 LE timestamp][uint32 LE size][data] per frame
|
|
2084
|
+
const buffers = [];
|
|
2085
|
+
let totalSize = 0;
|
|
2086
|
+
for (const snapshot of images) {
|
|
2087
|
+
if (!snapshot.data)
|
|
2088
|
+
continue;
|
|
2089
|
+
const ab = await snapshot.data.arrayBuffer();
|
|
2090
|
+
buffers.push(ab);
|
|
2091
|
+
totalSize += 8 + 4 + ab.byteLength; // uint64 ts + uint32 size + data
|
|
2092
|
+
}
|
|
2093
|
+
if (totalSize === 0)
|
|
2094
|
+
return;
|
|
2095
|
+
const packed = new ArrayBuffer(totalSize);
|
|
2096
|
+
const view = new DataView(packed);
|
|
2097
|
+
const bytes = new Uint8Array(packed);
|
|
2098
|
+
let offset = 0;
|
|
2099
|
+
for (let i = 0; i < images.length; i++) {
|
|
2100
|
+
if (!images[i].data)
|
|
2101
|
+
continue;
|
|
2102
|
+
const ab = buffers.shift();
|
|
2103
|
+
const ts = images[i].id;
|
|
2104
|
+
// uint64 LE as two uint32 LE writes -- timestamp
|
|
2105
|
+
view.setUint32(offset, ts % 0x100000000, true);
|
|
2106
|
+
view.setUint32(offset + 4, Math.floor(ts / 0x100000000), true);
|
|
2107
|
+
offset += 8;
|
|
2108
|
+
// uint32 LE -- size
|
|
2109
|
+
view.setUint32(offset, ab.byteLength, true);
|
|
2110
|
+
offset += 4;
|
|
2111
|
+
// image data
|
|
2112
|
+
bytes.set(new Uint8Array(ab), offset);
|
|
2113
|
+
offset += ab.byteLength;
|
|
2114
|
+
}
|
|
2115
|
+
formData = new FormData();
|
|
2116
|
+
formData.append('type', 'frames');
|
|
2117
|
+
const fileName = `${createdAt}_${canvasId}.${this.fileExt}.frames`;
|
|
2118
|
+
formData.append('frames', new Blob([packed]), fileName);
|
|
2487
2119
|
}
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2120
|
+
else {
|
|
2121
|
+
// Legacy: send individual image files
|
|
2122
|
+
formData = new FormData();
|
|
2123
|
+
images.forEach((snapshot) => {
|
|
2124
|
+
const blob = snapshot.data;
|
|
2125
|
+
if (!blob)
|
|
2126
|
+
return;
|
|
2127
|
+
const name = `${createdAt}_${canvasId}_${snapshot.id}.${this.fileExt}`;
|
|
2128
|
+
formData.append('snapshot', blob, name);
|
|
2129
|
+
});
|
|
2130
|
+
}
|
|
2131
|
+
const initRestart = () => {
|
|
2132
|
+
if (this.app.session.getSessionSecret() !== token) {
|
|
2495
2133
|
return;
|
|
2496
|
-
formData.append('snapshot', blob, `${createdAt}_${canvasId}_${snapshot.id}.${this.fileExt}`);
|
|
2497
|
-
if (this.options.isDebug) {
|
|
2498
|
-
saveImageData(blob, `${createdAt}_${canvasId}_${snapshot.id}.${this.fileExt}`);
|
|
2499
2134
|
}
|
|
2500
|
-
});
|
|
2501
|
-
const initRestart = () => {
|
|
2502
2135
|
this.app.debug.log('Restarting tracker; token expired');
|
|
2503
2136
|
this.app.stop(false);
|
|
2504
2137
|
setTimeout(() => {
|
|
@@ -2508,25 +2141,72 @@ class CanvasRecorder {
|
|
|
2508
2141
|
this.uploadQueue++;
|
|
2509
2142
|
const base = this.app.options.endpoint.replace(/\/$/, '');
|
|
2510
2143
|
const ingestPoint = base.endsWith('/oxvo') ? base : `${base}/oxvo`;
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
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
|
+
}
|
|
2521
2181
|
}
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
.catch((e) => {
|
|
2525
|
-
this.app.debug.error('error saving canvas', e);
|
|
2526
|
-
})
|
|
2527
|
-
.finally(() => {
|
|
2182
|
+
}
|
|
2183
|
+
finally {
|
|
2528
2184
|
this.uploadQueue--;
|
|
2529
|
-
}
|
|
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));
|
|
2530
2210
|
}
|
|
2531
2211
|
cleanupCanvas(id) {
|
|
2532
2212
|
if (this.snapshots[id]) {
|
|
@@ -2549,8 +2229,7 @@ class CanvasRecorder {
|
|
|
2549
2229
|
}
|
|
2550
2230
|
delete this.snapshots[id];
|
|
2551
2231
|
}
|
|
2552
|
-
|
|
2553
|
-
// Flush remaining images before cleanup
|
|
2232
|
+
flushPendingSnapshots() {
|
|
2554
2233
|
Object.keys(this.snapshots).forEach((idStr) => {
|
|
2555
2234
|
const id = parseInt(idStr, 10);
|
|
2556
2235
|
const snapshot = this.snapshots[id];
|
|
@@ -2559,6 +2238,9 @@ class CanvasRecorder {
|
|
|
2559
2238
|
snapshot.images = [];
|
|
2560
2239
|
}
|
|
2561
2240
|
});
|
|
2241
|
+
}
|
|
2242
|
+
clear() {
|
|
2243
|
+
this.flushPendingSnapshots();
|
|
2562
2244
|
Object.keys(this.snapshots).forEach((idStr) => {
|
|
2563
2245
|
const id = parseInt(idStr, 10);
|
|
2564
2246
|
this.cleanupCanvas(id);
|
|
@@ -2593,40 +2275,346 @@ function captureSnapshot(canvas, quality = 'medium', dummy, fixedScaling = false
|
|
|
2593
2275
|
canvas.toBlob(onBlob, imageFormat, qualityInt[quality]);
|
|
2594
2276
|
}
|
|
2595
2277
|
}
|
|
2596
|
-
function
|
|
2597
|
-
const
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2278
|
+
async function packFrames(images) {
|
|
2279
|
+
const buffers = [];
|
|
2280
|
+
let totalSize = 0;
|
|
2281
|
+
for (const snapshot of images) {
|
|
2282
|
+
if (!snapshot.data)
|
|
2283
|
+
continue;
|
|
2284
|
+
const ab = await snapshot.data.arrayBuffer();
|
|
2285
|
+
buffers.push(ab);
|
|
2286
|
+
totalSize += 8 + 4 + ab.byteLength;
|
|
2287
|
+
}
|
|
2288
|
+
if (totalSize === 0)
|
|
2289
|
+
return null;
|
|
2290
|
+
const packed = new ArrayBuffer(totalSize);
|
|
2291
|
+
const view = new DataView(packed);
|
|
2292
|
+
const bytes = new Uint8Array(packed);
|
|
2293
|
+
let offset = 0;
|
|
2294
|
+
for (let i = 0; i < images.length; i++) {
|
|
2295
|
+
if (!images[i].data)
|
|
2296
|
+
continue;
|
|
2297
|
+
const ab = buffers.shift();
|
|
2298
|
+
const ts = images[i].id;
|
|
2299
|
+
view.setUint32(offset, ts % 0x100000000, true);
|
|
2300
|
+
view.setUint32(offset + 4, Math.floor(ts / 0x100000000), true);
|
|
2301
|
+
offset += 8;
|
|
2302
|
+
view.setUint32(offset, ab.byteLength, true);
|
|
2303
|
+
offset += 4;
|
|
2304
|
+
bytes.set(new Uint8Array(ab), offset);
|
|
2305
|
+
offset += ab.byteLength;
|
|
2306
|
+
}
|
|
2307
|
+
return packed;
|
|
2605
2308
|
}
|
|
2606
2309
|
|
|
2607
|
-
const
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
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 LogLevel = {
|
|
2596
|
+
Verbose: 5,
|
|
2597
|
+
Log: 4,
|
|
2598
|
+
Warnings: 3,
|
|
2599
|
+
Errors: 2,
|
|
2600
|
+
Silent: 0,
|
|
2601
|
+
};
|
|
2602
|
+
class Logger {
|
|
2603
|
+
constructor(debugLevel = LogLevel.Silent) {
|
|
2604
|
+
this.shouldLog = (level) => {
|
|
2605
|
+
return this.level >= level;
|
|
2606
|
+
};
|
|
2607
|
+
this.info = (...args) => {
|
|
2608
|
+
if (this.shouldLog(LogLevel.Verbose)) {
|
|
2609
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
2610
|
+
console.info(...args);
|
|
2611
|
+
}
|
|
2612
|
+
};
|
|
2613
|
+
this.log = (...args) => {
|
|
2614
|
+
if (this.shouldLog(LogLevel.Log)) {
|
|
2615
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
2616
|
+
console.log(...args);
|
|
2617
|
+
}
|
|
2630
2618
|
};
|
|
2631
2619
|
this.warn = (...args) => {
|
|
2632
2620
|
if (this.shouldLog(LogLevel.Warnings)) {
|
|
@@ -2882,8 +2870,44 @@ function inlineRemoteCss(node, id, baseHref, getNextID, insertRule, addOwner, fo
|
|
|
2882
2870
|
})
|
|
2883
2871
|
.catch(error => {
|
|
2884
2872
|
console.error(`OxvoSessions: Failed to fetch CSS from ${node.href}:`, error);
|
|
2873
|
+
// Fetch failed, likely due to CORS. Retry via load event and sheet access.
|
|
2874
|
+
retryViaLoadEvent();
|
|
2885
2875
|
});
|
|
2886
2876
|
}
|
|
2877
|
+
else {
|
|
2878
|
+
retryViaLoadEvent();
|
|
2879
|
+
}
|
|
2880
|
+
function retryViaLoadEvent() {
|
|
2881
|
+
const trySheet = () => {
|
|
2882
|
+
const loadedSheet = node.sheet;
|
|
2883
|
+
if (loadedSheet) {
|
|
2884
|
+
try {
|
|
2885
|
+
const cssText = stringifyStylesheet(loadedSheet);
|
|
2886
|
+
if (cssText) {
|
|
2887
|
+
if (sendPlain && onPlain) {
|
|
2888
|
+
onPlain(cssText, fakeIdHolder++);
|
|
2889
|
+
}
|
|
2890
|
+
else {
|
|
2891
|
+
processCssText(cssText);
|
|
2892
|
+
}
|
|
2893
|
+
return;
|
|
2894
|
+
}
|
|
2895
|
+
}
|
|
2896
|
+
catch (e) {
|
|
2897
|
+
// Cross-origin sheet. Nothing more can be read safely here.
|
|
2898
|
+
}
|
|
2899
|
+
}
|
|
2900
|
+
};
|
|
2901
|
+
if (node.sheet) {
|
|
2902
|
+
trySheet();
|
|
2903
|
+
}
|
|
2904
|
+
else {
|
|
2905
|
+
node.addEventListener('load', function onLoad() {
|
|
2906
|
+
node.removeEventListener('load', onLoad);
|
|
2907
|
+
trySheet();
|
|
2908
|
+
});
|
|
2909
|
+
}
|
|
2910
|
+
}
|
|
2887
2911
|
function processCssText(cssText) {
|
|
2888
2912
|
// Remove comments
|
|
2889
2913
|
cssText = cssText.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
@@ -3105,7 +3129,6 @@ function ConstructedStyleSheets (app) {
|
|
|
3105
3129
|
if (!hasAdoptedSS(document)) {
|
|
3106
3130
|
return;
|
|
3107
3131
|
}
|
|
3108
|
-
const styleSheetIDMap = new Map();
|
|
3109
3132
|
const adoptedStyleSheetsOwnings = new Map();
|
|
3110
3133
|
const sendAdoptedStyleSheetsUpdate = (root) => setTimeout(() => {
|
|
3111
3134
|
let nodeID = app.nodes.getID(root);
|
|
@@ -3219,6 +3242,119 @@ function ConstructedStyleSheets (app) {
|
|
|
3219
3242
|
});
|
|
3220
3243
|
}
|
|
3221
3244
|
|
|
3245
|
+
exports.SanitizeLevel = void 0;
|
|
3246
|
+
(function (SanitizeLevel) {
|
|
3247
|
+
SanitizeLevel[SanitizeLevel["Plain"] = 0] = "Plain";
|
|
3248
|
+
SanitizeLevel[SanitizeLevel["Obscured"] = 1] = "Obscured";
|
|
3249
|
+
SanitizeLevel[SanitizeLevel["Hidden"] = 2] = "Hidden";
|
|
3250
|
+
})(exports.SanitizeLevel || (exports.SanitizeLevel = {}));
|
|
3251
|
+
const stringWiper = (input) => input
|
|
3252
|
+
.trim()
|
|
3253
|
+
.replace(/[^\f\n\r\t\v\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff\s]/g, '*');
|
|
3254
|
+
class Sanitizer {
|
|
3255
|
+
constructor(params) {
|
|
3256
|
+
// Node id -> level. Plain (0) is never stored; a missing entry means Plain.
|
|
3257
|
+
// A map (not the old grow-only Sets) so levels can be raised and lowered.
|
|
3258
|
+
this.levels = new Map();
|
|
3259
|
+
this.app = params.app;
|
|
3260
|
+
const defaultOptions = {
|
|
3261
|
+
maskTextEmails: true,
|
|
3262
|
+
maskTextNumbers: false,
|
|
3263
|
+
maskAllByDefault: false,
|
|
3264
|
+
domSanitizer: undefined,
|
|
3265
|
+
};
|
|
3266
|
+
this.maskAllByDefault = params.options?.maskAllByDefault ?? false;
|
|
3267
|
+
this.options = Object.assign(defaultOptions, params.options);
|
|
3268
|
+
}
|
|
3269
|
+
// Pure recomputation of a node's level from the live DOM + parent level.
|
|
3270
|
+
// Reading current state on every call lets resanitize() pick up runtime changes.
|
|
3271
|
+
computeLevel(node, parentLevel) {
|
|
3272
|
+
if (this.options.maskAllByDefault) {
|
|
3273
|
+
if (isElementNode(node) && !hasOxvoSessionsAttribute(node, 'unmask')) {
|
|
3274
|
+
return exports.SanitizeLevel.Obscured;
|
|
3275
|
+
}
|
|
3276
|
+
if (isTextNode(node) && !hasOxvoSessionsAttribute(node.parentNode, 'unmask')) {
|
|
3277
|
+
return exports.SanitizeLevel.Obscured;
|
|
3278
|
+
}
|
|
3279
|
+
}
|
|
3280
|
+
let level = exports.SanitizeLevel.Plain;
|
|
3281
|
+
if (parentLevel >= exports.SanitizeLevel.Obscured ||
|
|
3282
|
+
(isElementNode(node) &&
|
|
3283
|
+
(hasOxvoSessionsAttribute(node, 'masked') || hasOxvoSessionsAttribute(node, 'obscured')))) {
|
|
3284
|
+
level = exports.SanitizeLevel.Obscured;
|
|
3285
|
+
}
|
|
3286
|
+
if (parentLevel === exports.SanitizeLevel.Hidden ||
|
|
3287
|
+
(isElementNode(node) &&
|
|
3288
|
+
(hasOxvoSessionsAttribute(node, 'htmlmasked') || hasOxvoSessionsAttribute(node, 'hidden')))) {
|
|
3289
|
+
level = exports.SanitizeLevel.Hidden;
|
|
3290
|
+
}
|
|
3291
|
+
if (this.options.domSanitizer !== undefined && isElementNode(node)) {
|
|
3292
|
+
const sanitizeLevel = this.options.domSanitizer(node);
|
|
3293
|
+
if (sanitizeLevel === exports.SanitizeLevel.Obscured && level < exports.SanitizeLevel.Obscured) {
|
|
3294
|
+
level = exports.SanitizeLevel.Obscured;
|
|
3295
|
+
}
|
|
3296
|
+
if (sanitizeLevel === exports.SanitizeLevel.Hidden) {
|
|
3297
|
+
level = exports.SanitizeLevel.Hidden;
|
|
3298
|
+
}
|
|
3299
|
+
}
|
|
3300
|
+
return level;
|
|
3301
|
+
}
|
|
3302
|
+
getLevel(id) {
|
|
3303
|
+
return this.levels.get(id) ?? exports.SanitizeLevel.Plain;
|
|
3304
|
+
}
|
|
3305
|
+
// Sets a node's level in either direction and returns the previous level.
|
|
3306
|
+
setLevel(id, level) {
|
|
3307
|
+
const prev = this.getLevel(id);
|
|
3308
|
+
if (level === exports.SanitizeLevel.Plain) {
|
|
3309
|
+
this.levels.delete(id);
|
|
3310
|
+
}
|
|
3311
|
+
else {
|
|
3312
|
+
this.levels.set(id, level);
|
|
3313
|
+
}
|
|
3314
|
+
return prev;
|
|
3315
|
+
}
|
|
3316
|
+
handleNode(id, parentID, node) {
|
|
3317
|
+
const level = this.computeLevel(node, this.getLevel(parentID));
|
|
3318
|
+
// Escalate-only: commits never lower a level, only resanitize/setLevel do.
|
|
3319
|
+
if (level > this.getLevel(id)) {
|
|
3320
|
+
this.setLevel(id, level);
|
|
3321
|
+
}
|
|
3322
|
+
}
|
|
3323
|
+
sanitize(id, data) {
|
|
3324
|
+
if (this.getLevel(id) >= exports.SanitizeLevel.Obscured) {
|
|
3325
|
+
// TODO: is it the best place to put trim() ? Might trimmed spaces be considered in layout in certain cases?
|
|
3326
|
+
return stringWiper(data);
|
|
3327
|
+
}
|
|
3328
|
+
if (this.options.maskTextNumbers) {
|
|
3329
|
+
data = data.replace(/\d/g, '0');
|
|
3330
|
+
}
|
|
3331
|
+
if (this.options.maskTextEmails) {
|
|
3332
|
+
data = data.replace(/^\w+([+.-]\w+)*@\w+([.-]\w+)*\.\w{2,3}$/g, (email) => {
|
|
3333
|
+
const [name, domain] = email.split('@');
|
|
3334
|
+
const [domainName, host] = domain.split('.');
|
|
3335
|
+
return `${stars(name)}@${stars(domainName)}.${stars(host)}`;
|
|
3336
|
+
});
|
|
3337
|
+
}
|
|
3338
|
+
return data;
|
|
3339
|
+
}
|
|
3340
|
+
isObscured(id) {
|
|
3341
|
+
return this.getLevel(id) >= exports.SanitizeLevel.Obscured;
|
|
3342
|
+
}
|
|
3343
|
+
isHidden(id) {
|
|
3344
|
+
return this.getLevel(id) === exports.SanitizeLevel.Hidden;
|
|
3345
|
+
}
|
|
3346
|
+
getInnerTextSecure(el) {
|
|
3347
|
+
const id = this.app.nodes.getID(el);
|
|
3348
|
+
if (!id) {
|
|
3349
|
+
return '';
|
|
3350
|
+
}
|
|
3351
|
+
return this.sanitize(id, el.innerText);
|
|
3352
|
+
}
|
|
3353
|
+
clear() {
|
|
3354
|
+
this.levels.clear();
|
|
3355
|
+
}
|
|
3356
|
+
}
|
|
3357
|
+
|
|
3222
3358
|
const iconCache = {};
|
|
3223
3359
|
const svgUrlCache = {};
|
|
3224
3360
|
async function parseUseEl(useElement, mode, domParser) {
|
|
@@ -3370,6 +3506,12 @@ class Observer {
|
|
|
3370
3506
|
this.inlineRemoteCss = false;
|
|
3371
3507
|
this.inlinerOptions = undefined;
|
|
3372
3508
|
this.domParser = new DOMParser();
|
|
3509
|
+
/**
|
|
3510
|
+
* Bumped on every disconnect(). Stale async CSS-inlining callbacks from a
|
|
3511
|
+
* previous session (e.g. agent reload → tracker restart) compare against
|
|
3512
|
+
* this and bail instead of sending messages that reference vanished node ids.
|
|
3513
|
+
*/
|
|
3514
|
+
this.generation = 0;
|
|
3373
3515
|
this.throttling = true;
|
|
3374
3516
|
this.throttledSetNodeData = throttleWithTrailing((id, parentElement, data) => this.sendNodeData(id, parentElement, data), 30);
|
|
3375
3517
|
this.throttling = !Boolean(options.disableThrottling);
|
|
@@ -3516,18 +3658,23 @@ class Observer {
|
|
|
3516
3658
|
}
|
|
3517
3659
|
if (name === 'style' || (name === 'href' && hasTag(node, 'link'))) {
|
|
3518
3660
|
if ('rel' in node && node.rel === 'stylesheet' && this.inlineRemoteCss) {
|
|
3661
|
+
const gen = this.generation;
|
|
3519
3662
|
setTimeout(() => {
|
|
3520
3663
|
inlineRemoteCss(
|
|
3521
3664
|
// @ts-ignore
|
|
3522
3665
|
node, id, this.app.getBaseHref(), nextID, (id, cssText, index, baseHref) => {
|
|
3666
|
+
if (this.generation !== gen)
|
|
3667
|
+
return;
|
|
3523
3668
|
this.app.send(AdoptedSSInsertRuleURLBased(id, cssText, index, baseHref));
|
|
3524
3669
|
}, (sheetId, ownerId) => {
|
|
3670
|
+
if (this.generation !== gen)
|
|
3671
|
+
return;
|
|
3525
3672
|
this.app.send(AdoptedSSAddOwner(sheetId, ownerId));
|
|
3526
3673
|
}, this.inlinerOptions?.forceFetch, this.inlinerOptions?.forcePlain, (cssText, fakeTextId) => {
|
|
3674
|
+
if (this.generation !== gen)
|
|
3675
|
+
return;
|
|
3527
3676
|
this.app.send(CreateTextNode(fakeTextId, id, 0));
|
|
3528
|
-
|
|
3529
|
-
this.app.send(SetCSSDataURLBased(fakeTextId, cssText, this.app.getBaseHref()));
|
|
3530
|
-
}, 10);
|
|
3677
|
+
this.app.send(SetCSSDataURLBased(fakeTextId, cssText, this.app.getBaseHref()));
|
|
3531
3678
|
});
|
|
3532
3679
|
}, 0);
|
|
3533
3680
|
return;
|
|
@@ -3787,10 +3934,105 @@ class Observer {
|
|
|
3787
3934
|
beforeCommit(this.app.nodes.getID(node));
|
|
3788
3935
|
this.commitNodes(true);
|
|
3789
3936
|
}
|
|
3937
|
+
/**
|
|
3938
|
+
* Re-evaluates sanitization for every tracked node in `root`'s subtree against
|
|
3939
|
+
* the current DOM and re-emits whatever changed. Pass the highest node you
|
|
3940
|
+
* changed (or the document root) so inherited levels propagate correctly.
|
|
3941
|
+
*/
|
|
3942
|
+
resanitizeSubtree(root) {
|
|
3943
|
+
if (!isObservable(root)) {
|
|
3944
|
+
return;
|
|
3945
|
+
}
|
|
3946
|
+
const parent = root.parentNode;
|
|
3947
|
+
const parentId = parent !== null ? this.app.nodes.getID(parent) : undefined;
|
|
3948
|
+
const parentLevel = parentId !== undefined ? this.app.sanitizer.getLevel(parentId) : exports.SanitizeLevel.Plain;
|
|
3949
|
+
this.resanitizeNode(root, parentLevel);
|
|
3950
|
+
}
|
|
3951
|
+
resanitizeNode(node, parentLevel) {
|
|
3952
|
+
if (isIgnored(node)) {
|
|
3953
|
+
return;
|
|
3954
|
+
}
|
|
3955
|
+
const id = this.app.nodes.getID(node);
|
|
3956
|
+
if (id === undefined) {
|
|
3957
|
+
// Untracked (new, or under a hidden ancestor): the live observer handles it.
|
|
3958
|
+
return;
|
|
3959
|
+
}
|
|
3960
|
+
const newLevel = this.app.sanitizer.computeLevel(node, parentLevel);
|
|
3961
|
+
const prevLevel = this.app.sanitizer.getLevel(id);
|
|
3962
|
+
const wasHidden = prevLevel === exports.SanitizeLevel.Hidden;
|
|
3963
|
+
const willHidden = newLevel === exports.SanitizeLevel.Hidden;
|
|
3964
|
+
// Crossing the hidden boundary changes the rendered structure (placeholder vs
|
|
3965
|
+
// real subtree), so rebuild rather than re-emit.
|
|
3966
|
+
if (wasHidden !== willHidden) {
|
|
3967
|
+
this.recreateSubtree(node);
|
|
3968
|
+
return;
|
|
3969
|
+
}
|
|
3970
|
+
if (willHidden) {
|
|
3971
|
+
return;
|
|
3972
|
+
}
|
|
3973
|
+
// Plain <-> Obscured: same structure, only leaf content changes.
|
|
3974
|
+
if (prevLevel !== newLevel) {
|
|
3975
|
+
this.app.sanitizer.setLevel(id, newLevel);
|
|
3976
|
+
this.reemitNode(id, node);
|
|
3977
|
+
}
|
|
3978
|
+
for (let child = node.firstChild; child !== null; child = child.nextSibling) {
|
|
3979
|
+
this.resanitizeNode(child, newLevel);
|
|
3980
|
+
}
|
|
3981
|
+
}
|
|
3982
|
+
// Destroys the node player-side and re-emits its subtree from scratch (new ids)
|
|
3983
|
+
// so it materializes at the freshly-computed level.
|
|
3984
|
+
recreateSubtree(node) {
|
|
3985
|
+
const id = this.app.nodes.getID(node);
|
|
3986
|
+
if (id === undefined) {
|
|
3987
|
+
return;
|
|
3988
|
+
}
|
|
3989
|
+
this.app.send(RemoveNode(id));
|
|
3990
|
+
this.clearSubtreeRegistration(node);
|
|
3991
|
+
this.bindTree(node);
|
|
3992
|
+
this.commitNodes();
|
|
3993
|
+
}
|
|
3994
|
+
clearSubtreeRegistration(node) {
|
|
3995
|
+
const clearOne = (n) => {
|
|
3996
|
+
const oldId = this.app.nodes.getID(n);
|
|
3997
|
+
if (oldId !== undefined) {
|
|
3998
|
+
this.app.sanitizer.setLevel(oldId, exports.SanitizeLevel.Plain);
|
|
3999
|
+
}
|
|
4000
|
+
this.app.nodes.unregisterNode(n);
|
|
4001
|
+
};
|
|
4002
|
+
const walker = document.createTreeWalker(node, NodeFilter.SHOW_ELEMENT + NodeFilter.SHOW_TEXT, {
|
|
4003
|
+
acceptNode: (n) => isIgnored(n) || this.app.nodes.getID(n) === undefined
|
|
4004
|
+
? NodeFilter.FILTER_REJECT
|
|
4005
|
+
: NodeFilter.FILTER_ACCEPT,
|
|
4006
|
+
},
|
|
4007
|
+
// @ts-ignore
|
|
4008
|
+
false);
|
|
4009
|
+
// Collect first, then clear: unregistering mutates the ids the walker reads.
|
|
4010
|
+
const subtree = [];
|
|
4011
|
+
while (walker.nextNode()) {
|
|
4012
|
+
subtree.push(walker.currentNode);
|
|
4013
|
+
}
|
|
4014
|
+
clearOne(node);
|
|
4015
|
+
subtree.forEach(clearOne);
|
|
4016
|
+
}
|
|
4017
|
+
reemitNode(id, node) {
|
|
4018
|
+
if (isTextNode(node)) {
|
|
4019
|
+
const parent = node.parentNode;
|
|
4020
|
+
if (parent !== null && isElementNode(parent)) {
|
|
4021
|
+
// re-runs sanitize() at the level we just set
|
|
4022
|
+
this.sendNodeData(id, parent, node.data);
|
|
4023
|
+
}
|
|
4024
|
+
return;
|
|
4025
|
+
}
|
|
4026
|
+
if (isElementNode(node)) {
|
|
4027
|
+
// inputs/images/canvas re-emit their own payload via registered callbacks
|
|
4028
|
+
this.app.callResanitizeCallbacks(node, id);
|
|
4029
|
+
}
|
|
4030
|
+
}
|
|
3790
4031
|
disconnect() {
|
|
3791
4032
|
this.observer.disconnect();
|
|
3792
4033
|
this.clear();
|
|
3793
4034
|
this.throttledSetNodeData.clear();
|
|
4035
|
+
this.generation++;
|
|
3794
4036
|
}
|
|
3795
4037
|
}
|
|
3796
4038
|
|
|
@@ -4025,7 +4267,10 @@ class TopObserver extends Observer {
|
|
|
4025
4267
|
this.app.debug.info('doc already observed for', id);
|
|
4026
4268
|
return;
|
|
4027
4269
|
}
|
|
4028
|
-
const observer = new IFrameObserver(this.app, false, {
|
|
4270
|
+
const observer = new IFrameObserver(this.app, false, {
|
|
4271
|
+
disableSprites: this.options.disableSprites,
|
|
4272
|
+
...getInlineOptions(this.options.inlineCss, console.warn),
|
|
4273
|
+
});
|
|
4029
4274
|
this.iframeObservers.set(iframe, observer);
|
|
4030
4275
|
this.docObservers.set(currentDoc, observer);
|
|
4031
4276
|
this.iframeObserversArr.push(observer);
|
|
@@ -4047,7 +4292,10 @@ class TopObserver extends Observer {
|
|
|
4047
4292
|
handle();
|
|
4048
4293
|
}
|
|
4049
4294
|
handleShadowRoot(shRoot) {
|
|
4050
|
-
const observer = new ShadowRootObserver(this.app
|
|
4295
|
+
const observer = new ShadowRootObserver(this.app, false, {
|
|
4296
|
+
disableSprites: this.options.disableSprites,
|
|
4297
|
+
...getInlineOptions(this.options.inlineCss, console.warn),
|
|
4298
|
+
});
|
|
4051
4299
|
this.shadowRootObservers.set(shRoot, observer);
|
|
4052
4300
|
observer.observe(shRoot.host);
|
|
4053
4301
|
}
|
|
@@ -4083,7 +4331,10 @@ class TopObserver extends Observer {
|
|
|
4083
4331
|
};
|
|
4084
4332
|
this.app.nodes.clear();
|
|
4085
4333
|
this.app.nodes.crossdomainMode(frameLevel, frameOder);
|
|
4086
|
-
const iframeObserver = new IFrameObserver(this.app
|
|
4334
|
+
const iframeObserver = new IFrameObserver(this.app, false, {
|
|
4335
|
+
disableSprites: this.options.disableSprites,
|
|
4336
|
+
...getInlineOptions(this.options.inlineCss, console.warn),
|
|
4337
|
+
});
|
|
4087
4338
|
this.iframeObservers.set(window.document, iframeObserver);
|
|
4088
4339
|
iframeObserver.syntheticObserve(rootNodeId, window.document);
|
|
4089
4340
|
}
|
|
@@ -4099,96 +4350,8 @@ class TopObserver extends Observer {
|
|
|
4099
4350
|
}
|
|
4100
4351
|
}
|
|
4101
4352
|
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
SanitizeLevel[SanitizeLevel["Plain"] = 0] = "Plain";
|
|
4105
|
-
SanitizeLevel[SanitizeLevel["Obscured"] = 1] = "Obscured";
|
|
4106
|
-
SanitizeLevel[SanitizeLevel["Hidden"] = 2] = "Hidden";
|
|
4107
|
-
})(exports.SanitizeLevel || (exports.SanitizeLevel = {}));
|
|
4108
|
-
const stringWiper = (input) => input
|
|
4109
|
-
.trim()
|
|
4110
|
-
.replace(/[^\f\n\r\t\v\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff\s]/g, '*');
|
|
4111
|
-
class Sanitizer {
|
|
4112
|
-
constructor(params) {
|
|
4113
|
-
this.obscured = new Set();
|
|
4114
|
-
this.hidden = new Set();
|
|
4115
|
-
this.app = params.app;
|
|
4116
|
-
const defaultOptions = {
|
|
4117
|
-
maskTextEmails: true,
|
|
4118
|
-
maskTextNumbers: false,
|
|
4119
|
-
maskAllByDefault: false,
|
|
4120
|
-
domSanitizer: undefined,
|
|
4121
|
-
};
|
|
4122
|
-
this.maskAllByDefault = params.options?.maskAllByDefault ?? false;
|
|
4123
|
-
this.options = Object.assign(defaultOptions, params.options);
|
|
4124
|
-
}
|
|
4125
|
-
handleNode(id, parentID, node) {
|
|
4126
|
-
if (this.options.maskAllByDefault) {
|
|
4127
|
-
if (isElementNode(node) && !hasOxvoSessionsAttribute(node, 'unmask')) {
|
|
4128
|
-
return this.obscured.add(id);
|
|
4129
|
-
}
|
|
4130
|
-
if (isTextNode(node) && !hasOxvoSessionsAttribute(node.parentNode, 'unmask')) {
|
|
4131
|
-
return this.obscured.add(id);
|
|
4132
|
-
}
|
|
4133
|
-
}
|
|
4134
|
-
if (this.obscured.has(parentID) ||
|
|
4135
|
-
(isElementNode(node) &&
|
|
4136
|
-
(hasOxvoSessionsAttribute(node, 'masked') || hasOxvoSessionsAttribute(node, 'obscured')))) {
|
|
4137
|
-
this.obscured.add(id);
|
|
4138
|
-
}
|
|
4139
|
-
if (this.hidden.has(parentID) ||
|
|
4140
|
-
(isElementNode(node) &&
|
|
4141
|
-
(hasOxvoSessionsAttribute(node, 'htmlmasked') || hasOxvoSessionsAttribute(node, 'hidden')))) {
|
|
4142
|
-
this.hidden.add(id);
|
|
4143
|
-
}
|
|
4144
|
-
if (this.options.domSanitizer !== undefined && isElementNode(node)) {
|
|
4145
|
-
const sanitizeLevel = this.options.domSanitizer(node);
|
|
4146
|
-
if (sanitizeLevel === exports.SanitizeLevel.Obscured) {
|
|
4147
|
-
this.obscured.add(id);
|
|
4148
|
-
}
|
|
4149
|
-
if (sanitizeLevel === exports.SanitizeLevel.Hidden) {
|
|
4150
|
-
this.hidden.add(id);
|
|
4151
|
-
}
|
|
4152
|
-
}
|
|
4153
|
-
}
|
|
4154
|
-
sanitize(id, data) {
|
|
4155
|
-
if (this.obscured.has(id)) {
|
|
4156
|
-
// TODO: is it the best place to put trim() ? Might trimmed spaces be considered in layout in certain cases?
|
|
4157
|
-
return stringWiper(data);
|
|
4158
|
-
}
|
|
4159
|
-
if (this.options.maskTextNumbers) {
|
|
4160
|
-
data = data.replace(/\d/g, '0');
|
|
4161
|
-
}
|
|
4162
|
-
if (this.options.maskTextEmails) {
|
|
4163
|
-
data = data.replace(/^\w+([+.-]\w+)*@\w+([.-]\w+)*\.\w{2,3}$/g, (email) => {
|
|
4164
|
-
const [name, domain] = email.split('@');
|
|
4165
|
-
const [domainName, host] = domain.split('.');
|
|
4166
|
-
return `${stars(name)}@${stars(domainName)}.${stars(host)}`;
|
|
4167
|
-
});
|
|
4168
|
-
}
|
|
4169
|
-
return data;
|
|
4170
|
-
}
|
|
4171
|
-
isObscured(id) {
|
|
4172
|
-
return this.obscured.has(id);
|
|
4173
|
-
}
|
|
4174
|
-
isHidden(id) {
|
|
4175
|
-
return this.hidden.has(id);
|
|
4176
|
-
}
|
|
4177
|
-
getInnerTextSecure(el) {
|
|
4178
|
-
const id = this.app.nodes.getID(el);
|
|
4179
|
-
if (!id) {
|
|
4180
|
-
return '';
|
|
4181
|
-
}
|
|
4182
|
-
return this.sanitize(id, el.innerText);
|
|
4183
|
-
}
|
|
4184
|
-
clear() {
|
|
4185
|
-
this.obscured.clear();
|
|
4186
|
-
this.hidden.clear();
|
|
4187
|
-
}
|
|
4188
|
-
}
|
|
4189
|
-
|
|
4190
|
-
const tokenSeparator = '_$_';
|
|
4191
|
-
class Session {
|
|
4353
|
+
const tokenSeparator = '_$_';
|
|
4354
|
+
class Session {
|
|
4192
4355
|
constructor(params) {
|
|
4193
4356
|
this.metadata = {};
|
|
4194
4357
|
this.userID = null;
|
|
@@ -4232,6 +4395,10 @@ class Session {
|
|
|
4232
4395
|
this.token = `${token}${tokenSeparator}${projectKey}`;
|
|
4233
4396
|
this.app.sessionStorage.setItem(this.options.session_token_key, `${token}${tokenSeparator}${projectKey}`);
|
|
4234
4397
|
};
|
|
4398
|
+
this.clearSessionSecret = () => {
|
|
4399
|
+
this.token = undefined;
|
|
4400
|
+
this.app.sessionStorage.removeItem(this.options.session_token_key);
|
|
4401
|
+
};
|
|
4235
4402
|
this.app = params.app;
|
|
4236
4403
|
this.options = params.options;
|
|
4237
4404
|
this.createTabId();
|
|
@@ -4328,7 +4495,7 @@ class Session {
|
|
|
4328
4495
|
};
|
|
4329
4496
|
}
|
|
4330
4497
|
reset() {
|
|
4331
|
-
this.
|
|
4498
|
+
this.clearSessionSecret();
|
|
4332
4499
|
this.metadata = {};
|
|
4333
4500
|
this.userID = null;
|
|
4334
4501
|
this.sessionID = undefined;
|
|
@@ -4386,9 +4553,12 @@ class Ticker {
|
|
|
4386
4553
|
* this value is injected during build time via rollup
|
|
4387
4554
|
* */
|
|
4388
4555
|
// @ts-ignore
|
|
4389
|
-
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.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){if(this.busy||!this.token)this.queue.push(t);else if(this.busy=!0,this.isCompressing&&this.onCompress)this.onCompress(t);else{const s=++this.lastBatchNum;this.sendBatch(t,!1,s)}}sendNext(){const t=this.queue.shift();if(t)if(this.busy=!0,this.isCompressing&&this.onCompress)this.onCompress(t);else{const s=++this.lastBatchNum;this.sendBatch(t,!1,s)}else this.busy=!1}retry(t,s,i){this.attemptsCount>=this.MAX_ATTEMPTS_COUNT?this.onFailure(`Failed to send batch after ${this.attemptsCount} attempts.`):(this.attemptsCount++,setTimeout((()=>this.sendBatch(t,s,i)),this.ATTEMPT_TIMEOUT*this.attemptsCount))}sendBatch(t,s,i){var e;const n=null==i?void 0:i.toString().replace(/^([^_]+)_([^_]+).*/,\"$1_$2_$3\");this.busy=!0;const h={Authorization:`Bearer ${this.token}`};if(s&&(h[\"Content-Encoding\"]=\"gzip\"),null===this.token)return void setTimeout((()=>{this.sendBatch(t,s,`${null!=i?i:\"noBatchNum\"}_newToken`)}),500);const r=`${null!==(e=this.pageNo)&&void 0!==e?e:\"noPageNum\"}_${null!=n?n:\"noBatchNum\"}`;fetch(`${this.ingestURL}?seq=${r}`,{body:t,method:\"POST\",headers:h,keepalive:t.length<65536}).then((e=>{if(401===e.status)return this.busy=!1,void this.onUnauthorised();e.status>=400?this.retry(t,s,`${null!=i?i:\"noBatchNum\"}_network:${e.status}`):(this.attemptsCount=0,this.sendNext())})).catch((e=>{console.warn(\"OxvoSessions:\",e),this.retry(t,s,`${null!=i?i:\"noBatchNum\"}_reject:${e.message}`)}))}sendCompressed(t){const s=++this.lastBatchNum;this.sendBatch(t,!0,s)}sendUncompressed(t){const s=++this.lastBatchNum;this.sendBatch(t,!1,s)}clean(){this.sendNext(),setTimeout((()=>{this.token=null,this.queue.length=0}),10)}}const s=\"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 i{constructor(t){this.size=t,this.offset=0,this.checkpointOffset=0,this.data=new Uint8Array(t)}getCurrentOffset(){return this.offset}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 i=s.encode(t),e=i.byteLength;return!(!this.uint(e)||this.offset+e>this.size)&&(this.data.set(i,this.offset),this.offset+=e,!0)}reset(){this.offset=0,this.checkpointOffset=0}flush(){const t=this.data.slice(0,this.checkpointOffset);return this.reset(),t}}class e extends i{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:case 82: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 n{constructor(t,s,i,n,h,r){this.pageNo=t,this.timestamp=s,this.url=i,this.onBatch=n,this.tabId=h,this.onOfflineEnd=r,this.nextIndex=0,this.beaconSize=2e5,this.encoder=new e(this.beaconSize),this.sizeBuffer=new Uint8Array(3),this.isEmpty=!0,this.checkpoints=[],this.beaconSizeLimit=1e6,this.prepare()}writeType(t){return this.encoder.uint(t[0])}writeFields(t){return this.encoder.encode(t)}writeSizeAt(t,s){for(let s=0;s<3;s++)this.sizeBuffer[s]=t>>8*s;this.encoder.set(this.sizeBuffer,s)}prepare(){if(!this.encoder.isEmpty)return;this.checkpoints.length=0;const t=[81,1,this.pageNo,this.nextIndex,this.timestamp,this.url],s=[0,this.timestamp],i=[118,this.tabId];this.writeType(t),this.writeFields(t),this.writeWithSize(s),this.writeWithSize(i),this.isEmpty=!0}writeWithSize(t){const s=this.encoder;if(!this.writeType(t)||!s.skip(3))return!1;const i=s.getCurrentOffset(),e=this.writeFields(t);if(e){const e=s.getCurrentOffset()-i;if(e>16777215)return console.warn(\"OxvoSessions: max message size overflow.\"),!1;this.writeSizeAt(e,i-3),s.checkpoint(),this.checkpoints.push(s.getCurrentOffset()),this.isEmpty=this.isEmpty&&0===t[0],this.nextIndex++}return e}setBeaconSizeLimit(t){this.beaconSizeLimit=t}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]),this.writeWithSize(t)||(this.finaliseBatch(),this.writeWithSize(t)||(this.encoder=new e(this.beaconSizeLimit),this.prepare(),this.writeWithSize(t)?this.finaliseBatch():console.warn(\"OxvoSessions: beacon size overflow. Skipping large message.\",t,this),this.encoder=new e(this.beaconSize),this.prepare()))}finaliseBatch(t=!1){if(this.isEmpty)return;const s=this.encoder.flush();this.onBatch(s,t),this.prepare()}clean(){this.encoder.reset(),this.checkpoints.length=0}}var h;!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\"}(h||(h={}));let r=null,u=null,a=h.NotActive;function o(t){u&&u.finaliseBatch(t)}function c(){return new Promise((t=>{a=h.Stopping,null!==p&&(clearInterval(p),p=null),u&&(u.clean(),u=null),r&&(r.clean(),setTimeout((()=>{r=null}),20)),setTimeout((()=>{a=h.NotActive,t(null)}),100)}))}function g(){[h.Stopped,h.Stopping].includes(a)||(postMessage(\"a_stop\"),c().then((()=>{postMessage(\"a_start\")})))}let l,p=null;self.onmessage=({data:s})=>{if(\"stop\"===s)return o(),void c().then((()=>{a=h.Stopped}));if(\"forceFlushBatch\"!==s)if(\"closing\"!==s){if(!Array.isArray(s)){if(\"compressed\"===s.type){if(!r)return console.debug(\"OR WebWorker: sender not initialised. Compressed batch.\"),void g();s.batch&&r.sendCompressed(s.batch)}if(\"uncompressed\"===s.type){if(!r)return console.debug(\"OR WebWorker: sender not initialised. Uncompressed batch.\"),void g();s.batch&&r.sendUncompressed(s.batch)}return\"start\"===s.type?(a=h.Starting,r=new t(s.ingestPoint,(()=>{g()}),(t=>{!function(t){postMessage({type:\"failure\",reason:t}),c()}(t)}),s.connAttemptCount,s.connAttemptGap,(t=>{postMessage({type:\"compress\",batch:t},[t.buffer])}),s.pageNo),u=new n(s.pageNo,s.timestamp,s.url,((t,s)=>{r&&(s?r.sendUncompressed(t):r.push(t))}),s.tabId,(()=>postMessage({type:\"queue_empty\"}))),null===p&&(p=setInterval(o,3e4)),a=h.Active):\"auth\"===s.type?r?u?(r.authorise(s.token),void(s.beaconSizeLimit&&u.setBeaconSizeLimit(s.beaconSizeLimit))):(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(u){const t=u;s.forEach((s=>{55===s[0]&&(s[1]?l=setTimeout((()=>g()),18e5):clearTimeout(l)),t.writeMessage(s)}))}else postMessage(\"not_init\"),g()}else o(!0);else o()}}();\n";
|
|
4556
|
+
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";
|
|
4390
4557
|
const CANCELED = 'canceled';
|
|
4391
4558
|
const bufferStorageKey = 'or_buffer_1';
|
|
4559
|
+
const PROTO_VERSION = '2';
|
|
4560
|
+
const BOOTSTRAP_ATTEMPT_TTL_MS = 24 * 60 * 60 * 1000;
|
|
4561
|
+
const MAX_BOOTSTRAP_ATTEMPT_STORAGE_LENGTH = 1024 * 1024;
|
|
4392
4562
|
const UnsuccessfulStart = (reason) => ({ reason, success: false });
|
|
4393
4563
|
const SuccessfulStart = (body) => ({ ...body, success: true });
|
|
4394
4564
|
var ActivityState;
|
|
@@ -4419,22 +4589,34 @@ const proto = {
|
|
|
4419
4589
|
resp: 'never-gonna-let-you-down',
|
|
4420
4590
|
// regenerating id (copied other tab)
|
|
4421
4591
|
reg: 'never-gonna-run-around-and-desert-you',
|
|
4422
|
-
iframeSignal: '
|
|
4423
|
-
iframeId: '
|
|
4424
|
-
iframeBatch: 'batch
|
|
4425
|
-
parentAlive: '
|
|
4426
|
-
killIframe: 'stop
|
|
4427
|
-
startIframe: 'start
|
|
4592
|
+
iframeSignal: 'oxvo:cross-frame:v1:handshake',
|
|
4593
|
+
iframeId: 'oxvo:cross-frame:v1:ready',
|
|
4594
|
+
iframeBatch: 'oxvo:cross-frame:v1:batch',
|
|
4595
|
+
parentAlive: 'oxvo:cross-frame:v1:pong',
|
|
4596
|
+
killIframe: 'oxvo:cross-frame:v1:stop',
|
|
4597
|
+
startIframe: 'oxvo:cross-frame:v1:start',
|
|
4428
4598
|
// checking updates
|
|
4429
|
-
polling: '
|
|
4599
|
+
polling: 'oxvo:cross-frame:v1:ping',
|
|
4430
4600
|
// happens if tab is old and has outdated token but
|
|
4431
4601
|
// not communicating with backend to update it (for whatever reason)
|
|
4432
4602
|
reset: 'reset-your-session-please',
|
|
4433
4603
|
};
|
|
4604
|
+
const FRAME_RATE_WINDOW_MS = 1000;
|
|
4605
|
+
const MAX_FRAME_ENVELOPES_PER_WINDOW = 120;
|
|
4606
|
+
const MAX_FRAME_MESSAGES_PER_WINDOW = 50000;
|
|
4607
|
+
const MAX_FRAME_BYTES_PER_WINDOW = 4 * 1024 * 1024;
|
|
4608
|
+
const FRAME_CAPABILITY_TTL_MS = 5 * 60 * 1000;
|
|
4609
|
+
const FRAME_CAPABILITY_REFRESH_LEAD_MS = 60 * 1000;
|
|
4610
|
+
const FRAME_PREVIOUS_CAPABILITY_GRACE_MS = 5 * 1000;
|
|
4434
4611
|
class App {
|
|
4612
|
+
get tagMatcher() {
|
|
4613
|
+
return this.markerWatcher.matcher;
|
|
4614
|
+
}
|
|
4435
4615
|
constructor(projectKey, sessionToken, options, signalError, insideIframe) {
|
|
4436
4616
|
this.signalError = signalError;
|
|
4437
4617
|
this.insideIframe = insideIframe;
|
|
4618
|
+
// Registered by input/img/canvas to re-emit a node when its level changes.
|
|
4619
|
+
this.resanitizeCallbacks = [];
|
|
4438
4620
|
this.messages = [];
|
|
4439
4621
|
/**
|
|
4440
4622
|
* we need 2 buffers, so we don't lose anything
|
|
@@ -4446,18 +4628,31 @@ class App {
|
|
|
4446
4628
|
this.stopCallbacks = [];
|
|
4447
4629
|
this.commitCallbacks = [];
|
|
4448
4630
|
this.activityState = ActivityState.NotActive;
|
|
4449
|
-
this.version = '7.
|
|
4631
|
+
this.version = '7.3.4'; // TODO: version compatability check inside each plugin.
|
|
4450
4632
|
this.socketMode = false;
|
|
4451
4633
|
this.compressionThreshold = 24 * 1000;
|
|
4452
4634
|
this.bc = null;
|
|
4453
4635
|
this.canvasRecorder = null;
|
|
4454
4636
|
this.conditionsManager = null;
|
|
4637
|
+
this.bootstrapAttempts = {};
|
|
4455
4638
|
this.canStart = false;
|
|
4456
4639
|
this.rootId = null;
|
|
4457
4640
|
this.pageFrames = [];
|
|
4458
4641
|
this.frameOderNumber = 0;
|
|
4459
4642
|
this.frameLevel = 0;
|
|
4460
4643
|
this.emptyBatchCounter = 0;
|
|
4644
|
+
this.crossDomainFrameCapability = null;
|
|
4645
|
+
this.crossDomainFrameCapabilityExpiresAt = 0;
|
|
4646
|
+
this.crossDomainFrameSession = null;
|
|
4647
|
+
this.crossDomainFrameCanvas = null;
|
|
4648
|
+
this.crossDomainResumeAfterHandshake = false;
|
|
4649
|
+
this.crossDomainConnectionRequested = false;
|
|
4650
|
+
this.crossDomainFrames = new Map();
|
|
4651
|
+
this.crossDomainEnvelopeBudgets = new WeakMap();
|
|
4652
|
+
this.pendingCrossDomainFrames = new Map();
|
|
4653
|
+
this.crossDomainAuthorizationGeneration = 0;
|
|
4654
|
+
this.nextCrossDomainFrameOrder = 1;
|
|
4655
|
+
this.crossDomainCanvasConfig = null;
|
|
4461
4656
|
/** used by child iframes for crossdomain only */
|
|
4462
4657
|
this.parentActive = false;
|
|
4463
4658
|
this.checkStatus = () => {
|
|
@@ -4465,185 +4660,201 @@ class App {
|
|
|
4465
4660
|
};
|
|
4466
4661
|
this.parentCrossDomainFrameListener = (event) => {
|
|
4467
4662
|
const { data } = event;
|
|
4468
|
-
if (!data ||
|
|
4663
|
+
if (!data ||
|
|
4664
|
+
this.crossDomainParentOrigin === null ||
|
|
4665
|
+
this.crossDomainFrameNonce === null ||
|
|
4666
|
+
event.source !== window.parent ||
|
|
4667
|
+
event.origin !== this.crossDomainParentOrigin) {
|
|
4469
4668
|
return;
|
|
4470
|
-
|
|
4471
|
-
|
|
4669
|
+
}
|
|
4670
|
+
if (data.line === proto.iframeId) {
|
|
4671
|
+
const init = parseCrossDomainFrameInit(data, proto.iframeId, {
|
|
4672
|
+
context: this.contextId,
|
|
4673
|
+
nonce: this.crossDomainFrameNonce,
|
|
4674
|
+
});
|
|
4675
|
+
if (init === null)
|
|
4676
|
+
return;
|
|
4677
|
+
const sessionChanged = this.crossDomainFrameSession !== null &&
|
|
4678
|
+
this.crossDomainFrameSession.sessionID !== init.session.sessionID;
|
|
4679
|
+
if (sessionChanged && this.activityState === ActivityState.Starting) {
|
|
4680
|
+
this.postHandshakeToParent();
|
|
4472
4681
|
return;
|
|
4473
|
-
try {
|
|
4474
|
-
this.allowAppStart();
|
|
4475
|
-
void this.start();
|
|
4476
4682
|
}
|
|
4477
|
-
|
|
4478
|
-
|
|
4683
|
+
const shouldResume = this.crossDomainResumeAfterHandshake || (sessionChanged && this.active());
|
|
4684
|
+
if (sessionChanged && this.active()) {
|
|
4685
|
+
this.stop();
|
|
4479
4686
|
}
|
|
4687
|
+
this.applyCrossDomainFrameInit(init);
|
|
4688
|
+
if (shouldResume &&
|
|
4689
|
+
this.activityState !== ActivityState.Active &&
|
|
4690
|
+
this.activityState !== ActivityState.Starting) {
|
|
4691
|
+
this.crossDomainResumeAfterHandshake = false;
|
|
4692
|
+
this.startCrossDomainFrame(this.prevOpts);
|
|
4693
|
+
}
|
|
4694
|
+
this.debug.log('Starting secure cross-domain iframe tracking');
|
|
4695
|
+
return;
|
|
4696
|
+
}
|
|
4697
|
+
const expectedLine = data.line === proto.parentAlive
|
|
4698
|
+
? proto.parentAlive
|
|
4699
|
+
: data.line === proto.startIframe
|
|
4700
|
+
? proto.startIframe
|
|
4701
|
+
: data.line === proto.killIframe
|
|
4702
|
+
? proto.killIframe
|
|
4703
|
+
: null;
|
|
4704
|
+
if (this.crossDomainFrameCapability === null ||
|
|
4705
|
+
expectedLine === null ||
|
|
4706
|
+
(expectedLine !== proto.killIframe &&
|
|
4707
|
+
Date.now() >= this.crossDomainFrameCapabilityExpiresAt) ||
|
|
4708
|
+
!matchesCrossDomainEnvelope(data, expectedLine, {
|
|
4709
|
+
context: this.contextId,
|
|
4710
|
+
nonce: this.crossDomainFrameNonce,
|
|
4711
|
+
capability: this.crossDomainFrameCapability,
|
|
4712
|
+
})) {
|
|
4713
|
+
return;
|
|
4480
4714
|
}
|
|
4481
4715
|
if (data.line === proto.parentAlive) {
|
|
4482
4716
|
this.parentActive = true;
|
|
4717
|
+
return;
|
|
4483
4718
|
}
|
|
4484
|
-
if (data.line === proto.
|
|
4485
|
-
|
|
4486
|
-
this.
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
|
|
4490
|
-
|
|
4491
|
-
|
|
4719
|
+
if (data.line === proto.startIframe) {
|
|
4720
|
+
// Avoid corrupting an in-flight start; let it complete.
|
|
4721
|
+
if (this.activityState === ActivityState.Starting)
|
|
4722
|
+
return;
|
|
4723
|
+
const init = parseCrossDomainFrameInit(data, proto.startIframe, {
|
|
4724
|
+
context: this.contextId,
|
|
4725
|
+
nonce: this.crossDomainFrameNonce,
|
|
4726
|
+
});
|
|
4727
|
+
if (init === null)
|
|
4728
|
+
return;
|
|
4729
|
+
try {
|
|
4730
|
+
if (this.active()) {
|
|
4731
|
+
this.stop();
|
|
4732
|
+
}
|
|
4733
|
+
this.applyCrossDomainFrameInit(init);
|
|
4734
|
+
this.startCrossDomainFrame(this.prevOpts);
|
|
4735
|
+
}
|
|
4736
|
+
catch (e) {
|
|
4737
|
+
console.error('children frame restart failed:', e);
|
|
4738
|
+
}
|
|
4739
|
+
return;
|
|
4492
4740
|
}
|
|
4493
4741
|
if (data.line === proto.killIframe) {
|
|
4494
|
-
|
|
4742
|
+
const shouldResume = this.activityState === ActivityState.Active || this.activityState === ActivityState.Starting;
|
|
4743
|
+
if (this.activityState !== ActivityState.NotActive) {
|
|
4495
4744
|
this.stop();
|
|
4496
4745
|
}
|
|
4746
|
+
this.clearCrossDomainFrameAuthorization();
|
|
4747
|
+
this.crossDomainResumeAfterHandshake = shouldResume;
|
|
4748
|
+
this.crossDomainConnectionRequested = shouldResume;
|
|
4497
4749
|
}
|
|
4498
4750
|
};
|
|
4499
|
-
/**
|
|
4500
|
-
* context ids for iframes,
|
|
4501
|
-
* order is not so important as long as its consistent
|
|
4502
|
-
* */
|
|
4503
|
-
this.trackedFrames = [];
|
|
4504
4751
|
this.crossDomainIframeListener = (event) => {
|
|
4505
|
-
if (!this.active() || event.source === window)
|
|
4506
|
-
return;
|
|
4507
4752
|
const { data } = event;
|
|
4508
|
-
if (!data)
|
|
4753
|
+
if (!data || event.source === window)
|
|
4509
4754
|
return;
|
|
4510
4755
|
if (data.line === proto.iframeSignal) {
|
|
4511
|
-
|
|
4512
|
-
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
}
|
|
4526
|
-
else {
|
|
4527
|
-
this.trackedFrames.push(data.context);
|
|
4528
|
-
}
|
|
4529
|
-
await this.waitStarted();
|
|
4530
|
-
const token = this.session.getSessionSecret(this.projectKey);
|
|
4531
|
-
const order = this.trackedFrames.findIndex((f) => f === data.context) + 1;
|
|
4532
|
-
if (order === 0) {
|
|
4533
|
-
this.debug.error('Couldnt get order number for iframe', data.context, this.trackedFrames);
|
|
4534
|
-
}
|
|
4535
|
-
const iframeData = {
|
|
4536
|
-
line: proto.iframeId,
|
|
4537
|
-
id,
|
|
4538
|
-
token,
|
|
4539
|
-
frameOrderNumber: order,
|
|
4540
|
-
frameLevel: this.frameLevel + 1,
|
|
4541
|
-
};
|
|
4542
|
-
this.debug.log('Got child frame signal; nodeId', id, event.source, iframeData);
|
|
4543
|
-
// @ts-ignore
|
|
4544
|
-
event.source?.postMessage(iframeData, '*');
|
|
4545
|
-
}
|
|
4546
|
-
catch (e) {
|
|
4547
|
-
console.error(e);
|
|
4548
|
-
}
|
|
4549
|
-
};
|
|
4550
|
-
void signalId();
|
|
4756
|
+
const handshake = parseCrossDomainHandshake(data, proto.iframeSignal, this.projectKey);
|
|
4757
|
+
if (handshake !== null) {
|
|
4758
|
+
void this.establishCrossDomainFrame(event, handshake);
|
|
4759
|
+
}
|
|
4760
|
+
return;
|
|
4761
|
+
}
|
|
4762
|
+
if (!this.active())
|
|
4763
|
+
return;
|
|
4764
|
+
if (data.line === proto.polling) {
|
|
4765
|
+
const state = this.validatedCrossDomainFrame(event, proto.polling);
|
|
4766
|
+
if (state !== null && this.consumeCrossDomainEnvelopeBudget(state.source)) {
|
|
4767
|
+
this.postToCrossDomainFrame(state, proto.parentAlive);
|
|
4768
|
+
}
|
|
4769
|
+
return;
|
|
4551
4770
|
}
|
|
4552
4771
|
/**
|
|
4553
4772
|
* proxying messages from iframe to main body, so they can be in one batch (same indexes, etc)
|
|
4554
4773
|
* plus we rewrite some of the messages to be relative to the main context/window
|
|
4555
4774
|
* */
|
|
4556
4775
|
if (data.line === proto.iframeBatch) {
|
|
4557
|
-
const
|
|
4776
|
+
const state = this.validatedCrossDomainFrame(event, proto.iframeBatch);
|
|
4777
|
+
if (state === null || !this.consumeCrossDomainEnvelopeBudget(state.source)) {
|
|
4778
|
+
return;
|
|
4779
|
+
}
|
|
4780
|
+
const batch = parseCrossDomainIframeBatch(data.messages);
|
|
4781
|
+
if (batch === null ||
|
|
4782
|
+
!this.consumeCrossDomainFrameBudget(state, batch.messages.length, batch.bytes)) {
|
|
4783
|
+
return;
|
|
4784
|
+
}
|
|
4558
4785
|
const mappedMessages = [];
|
|
4559
|
-
|
|
4786
|
+
batch.messages.forEach((msg) => {
|
|
4787
|
+
let fixedMessage = msg;
|
|
4560
4788
|
if (msg[0] === 20 /* MType.MouseMove */) {
|
|
4561
|
-
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
const [type, x, y] = msg;
|
|
4565
|
-
const { left, top } = frame.getBoundingClientRect();
|
|
4566
|
-
fixedMessage = [type, x + left, y + top];
|
|
4567
|
-
}
|
|
4568
|
-
});
|
|
4569
|
-
mappedMessages.push(fixedMessage);
|
|
4789
|
+
const [type, x, y] = msg;
|
|
4790
|
+
const { left, top } = state.frame.getBoundingClientRect();
|
|
4791
|
+
fixedMessage = [type, x + left, y + top];
|
|
4570
4792
|
}
|
|
4571
4793
|
if (msg[0] === 68 /* MType.MouseClick */) {
|
|
4572
|
-
|
|
4573
|
-
|
|
4574
|
-
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
|
|
4586
|
-
|
|
4587
|
-
|
|
4588
|
-
|
|
4589
|
-
selector,
|
|
4590
|
-
Math.round(fixedX * 1e3) / 1e1,
|
|
4591
|
-
Math.round(fixedY * 1e3) / 1e1,
|
|
4592
|
-
];
|
|
4593
|
-
}
|
|
4594
|
-
});
|
|
4595
|
-
mappedMessages.push(fixedMessage);
|
|
4596
|
-
}
|
|
4597
|
-
if (![28 /* MType.UserID */, 29 /* MType.UserAnonymousID */, 30 /* MType.Metadata */].includes(msg[0])) {
|
|
4598
|
-
mappedMessages.push(msg);
|
|
4794
|
+
const [type, id, hesitationTime, label, selector, normX, normY] = msg;
|
|
4795
|
+
const { left, top, width, height } = state.frame.getBoundingClientRect();
|
|
4796
|
+
const contentWidth = document.documentElement.scrollWidth;
|
|
4797
|
+
const contentHeight = document.documentElement.scrollHeight;
|
|
4798
|
+
const fullX = (normX / 100) * width + left;
|
|
4799
|
+
const fullY = (normY / 100) * height + top;
|
|
4800
|
+
const fixedX = fullX / contentWidth;
|
|
4801
|
+
const fixedY = fullY / contentHeight;
|
|
4802
|
+
fixedMessage = [
|
|
4803
|
+
type,
|
|
4804
|
+
id,
|
|
4805
|
+
hesitationTime,
|
|
4806
|
+
label,
|
|
4807
|
+
selector,
|
|
4808
|
+
Math.round(fixedX * 1e3) / 1e1,
|
|
4809
|
+
Math.round(fixedY * 1e3) / 1e1,
|
|
4810
|
+
];
|
|
4599
4811
|
}
|
|
4812
|
+
mappedMessages.push(fixedMessage);
|
|
4600
4813
|
});
|
|
4601
4814
|
this.messages.push(...mappedMessages);
|
|
4602
4815
|
}
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
4816
|
+
};
|
|
4817
|
+
this.bootChildrenFrames = async () => {
|
|
4818
|
+
await this.waitStarted();
|
|
4819
|
+
this.pruneDetachedCrossDomainFrames();
|
|
4820
|
+
for (const state of this.crossDomainFrames.values()) {
|
|
4821
|
+
const authorizationGeneration = this.crossDomainAuthorizationGeneration;
|
|
4822
|
+
const id = await this.checkNodeId(state.source);
|
|
4823
|
+
const session = this.currentCrossDomainSession();
|
|
4824
|
+
if (id === null ||
|
|
4825
|
+
session === null ||
|
|
4826
|
+
authorizationGeneration !== this.crossDomainAuthorizationGeneration ||
|
|
4827
|
+
this.crossDomainFrames.get(state.source) !== state ||
|
|
4828
|
+
!this.active()) {
|
|
4829
|
+
continue;
|
|
4606
4830
|
}
|
|
4607
|
-
const
|
|
4608
|
-
if (
|
|
4609
|
-
|
|
4610
|
-
return;
|
|
4831
|
+
const rotated = this.crossDomainFrameNeedsRotation(state, session.sessionID);
|
|
4832
|
+
if (rotated && !this.rotateCrossDomainFrameCapability(state, session.sessionID)) {
|
|
4833
|
+
continue;
|
|
4611
4834
|
}
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
event.source?.postMessage({ line: nextCommand }, '*');
|
|
4616
|
-
if (this.pollingQueue[nextCommand].length === 0) {
|
|
4617
|
-
this.pollingQueue.order.shift();
|
|
4618
|
-
}
|
|
4835
|
+
const payload = id === null ? null : this.frameInitPayload(state, id);
|
|
4836
|
+
if (payload) {
|
|
4837
|
+
this.postToCrossDomainFrame(state, rotated ? proto.iframeId : proto.startIframe, payload);
|
|
4619
4838
|
}
|
|
4620
4839
|
}
|
|
4621
4840
|
};
|
|
4622
|
-
/**
|
|
4623
|
-
* { command : [remaining iframes] }
|
|
4624
|
-
* + order of commands
|
|
4625
|
-
**/
|
|
4626
|
-
this.pollingQueue = {
|
|
4627
|
-
order: [],
|
|
4628
|
-
};
|
|
4629
|
-
this.addCommand = (cmd) => {
|
|
4630
|
-
this.pollingQueue.order.push(cmd);
|
|
4631
|
-
this.pollingQueue[cmd] = [...this.trackedFrames];
|
|
4632
|
-
};
|
|
4633
|
-
this.bootChildrenFrames = async () => {
|
|
4634
|
-
await this.waitStarted();
|
|
4635
|
-
this.addCommand(proto.startIframe);
|
|
4636
|
-
};
|
|
4637
4841
|
this.killChildrenFrames = () => {
|
|
4638
|
-
this.
|
|
4842
|
+
this.pruneDetachedCrossDomainFrames();
|
|
4843
|
+
const states = Array.from(this.crossDomainFrames.values());
|
|
4844
|
+
this.crossDomainAuthorizationGeneration += 1;
|
|
4845
|
+
this.crossDomainFrames.clear();
|
|
4846
|
+
this.pendingCrossDomainFrames.clear();
|
|
4847
|
+
states.forEach((state) => {
|
|
4848
|
+
this.postToCrossDomainFrame(state, proto.killIframe);
|
|
4849
|
+
});
|
|
4639
4850
|
};
|
|
4640
4851
|
this.signalIframeTracker = () => {
|
|
4641
|
-
|
|
4642
|
-
|
|
4643
|
-
|
|
4644
|
-
|
|
4645
|
-
|
|
4646
|
-
|
|
4852
|
+
if (this.crossDomainParentOrigin === null || this.crossDomainFrameNonce === null) {
|
|
4853
|
+
this.debug.error('Cross-domain iframe capture requires an explicit parentDomain and secure random support.');
|
|
4854
|
+
return;
|
|
4855
|
+
}
|
|
4856
|
+
this.crossDomainConnectionRequested = true;
|
|
4857
|
+
this.postHandshakeToParent();
|
|
4647
4858
|
/**
|
|
4648
4859
|
* since we need to wait uncertain amount of time
|
|
4649
4860
|
* and I don't want to have recursion going on,
|
|
@@ -4655,20 +4866,16 @@ class App {
|
|
|
4655
4866
|
let cumulativeDelay = 0;
|
|
4656
4867
|
let stopAttempts = false;
|
|
4657
4868
|
const checkAndSendMessage = () => {
|
|
4658
|
-
if (stopAttempts || this.checkStatus()) {
|
|
4869
|
+
if (stopAttempts || !this.crossDomainConnectionRequested || this.checkStatus()) {
|
|
4659
4870
|
stopAttempts = true;
|
|
4660
4871
|
return;
|
|
4661
4872
|
}
|
|
4662
|
-
|
|
4663
|
-
line: proto.iframeSignal,
|
|
4664
|
-
source: thisTab,
|
|
4665
|
-
context: this.contextId,
|
|
4666
|
-
}, this.options.crossdomain?.parentDomain ?? '*');
|
|
4873
|
+
this.postHandshakeToParent();
|
|
4667
4874
|
this.debug.info('Trying to signal to parent, attempt:', retries + 1);
|
|
4668
4875
|
retries++;
|
|
4669
4876
|
};
|
|
4670
4877
|
for (let i = 0; i < maxRetries; i++) {
|
|
4671
|
-
if (this.checkStatus()) {
|
|
4878
|
+
if (!this.crossDomainConnectionRequested || this.checkStatus()) {
|
|
4672
4879
|
stopAttempts = true;
|
|
4673
4880
|
break;
|
|
4674
4881
|
}
|
|
@@ -4755,6 +4962,26 @@ class App {
|
|
|
4755
4962
|
this.restartCanvasTracking = () => {
|
|
4756
4963
|
this.canvasRecorder?.restartTracking();
|
|
4757
4964
|
};
|
|
4965
|
+
this.attachResanitizeCallback = (cb) => {
|
|
4966
|
+
this.resanitizeCallbacks.push(cb);
|
|
4967
|
+
};
|
|
4968
|
+
this.callResanitizeCallbacks = (node, id) => {
|
|
4969
|
+
this.resanitizeCallbacks.forEach((cb) => cb(node, id));
|
|
4970
|
+
};
|
|
4971
|
+
this.resanitize = (el) => {
|
|
4972
|
+
const root = el ?? (IN_BROWSER ? document.documentElement : undefined);
|
|
4973
|
+
if (!root) {
|
|
4974
|
+
return;
|
|
4975
|
+
}
|
|
4976
|
+
this.observer.resanitizeSubtree(root);
|
|
4977
|
+
};
|
|
4978
|
+
this.checkSanitization = (el) => {
|
|
4979
|
+
const id = this.nodes.getID(el);
|
|
4980
|
+
if (id === undefined) {
|
|
4981
|
+
return undefined;
|
|
4982
|
+
}
|
|
4983
|
+
return this.sanitizer.getLevel(id);
|
|
4984
|
+
};
|
|
4758
4985
|
this.flushBuffer = async (buffer) => {
|
|
4759
4986
|
return new Promise((res, reject) => {
|
|
4760
4987
|
if (buffer.length === 0) {
|
|
@@ -4796,6 +5023,7 @@ class App {
|
|
|
4796
5023
|
__is_snippet: false,
|
|
4797
5024
|
__debug_report_edp: null,
|
|
4798
5025
|
__debug__: LogLevel.Silent,
|
|
5026
|
+
__local_debug: false,
|
|
4799
5027
|
localStorage: null,
|
|
4800
5028
|
sessionStorage: null,
|
|
4801
5029
|
forceSingleTab: false,
|
|
@@ -4804,9 +5032,7 @@ class App {
|
|
|
4804
5032
|
maskTextEmails: false,
|
|
4805
5033
|
maskTextNumbers: false,
|
|
4806
5034
|
disableStringDict: false,
|
|
4807
|
-
crossdomain: {
|
|
4808
|
-
parentDomain: '*',
|
|
4809
|
-
},
|
|
5035
|
+
crossdomain: {},
|
|
4810
5036
|
canvas: {
|
|
4811
5037
|
disableCanvas: false,
|
|
4812
5038
|
fixedCanvasScaling: false,
|
|
@@ -4819,6 +5045,9 @@ class App {
|
|
|
4819
5045
|
disableThrottling: false,
|
|
4820
5046
|
};
|
|
4821
5047
|
this.options = simpleMerge(defaultOptions, options);
|
|
5048
|
+
this.crossDomainChildOrigins = normalizeCrossDomainOrigins(this.options.crossdomain?.childOrigins);
|
|
5049
|
+
this.crossDomainParentOrigin = normalizeCrossDomainOrigin(this.options.crossdomain?.parentDomain);
|
|
5050
|
+
this.crossDomainFrameNonce = this.insideIframe ? createCrossDomainSecret() : null;
|
|
4822
5051
|
if (!this.insideIframe &&
|
|
4823
5052
|
!this.options.forceSingleTab &&
|
|
4824
5053
|
globalThis &&
|
|
@@ -4854,9 +5083,7 @@ class App {
|
|
|
4854
5083
|
});
|
|
4855
5084
|
this.session.attachUpdateCallback(({ userID, metadata }) => {
|
|
4856
5085
|
if (userID != null) {
|
|
4857
|
-
if (!userID ||
|
|
4858
|
-
typeof userID !== 'string' ||
|
|
4859
|
-
userID.trim().length === 0) {
|
|
5086
|
+
if (!userID || typeof userID !== 'string' || userID.trim().length === 0) {
|
|
4860
5087
|
this.debug.warn('Invalid userID (must be type string), ignoring.');
|
|
4861
5088
|
return;
|
|
4862
5089
|
}
|
|
@@ -4867,25 +5094,33 @@ class App {
|
|
|
4867
5094
|
}
|
|
4868
5095
|
});
|
|
4869
5096
|
// @deprecated (use sessionHash on start instead)
|
|
4870
|
-
if (sessionToken != null) {
|
|
5097
|
+
if (sessionToken != null && !this.insideIframe) {
|
|
4871
5098
|
this.session.applySessionHash(sessionToken);
|
|
4872
5099
|
}
|
|
5100
|
+
if (this.insideIframe) {
|
|
5101
|
+
this.session.clearSessionSecret();
|
|
5102
|
+
}
|
|
4873
5103
|
const thisTab = this.session.getTabId();
|
|
4874
5104
|
if (this.insideIframe) {
|
|
4875
|
-
|
|
4876
|
-
|
|
4877
|
-
|
|
4878
|
-
|
|
4879
|
-
|
|
4880
|
-
|
|
4881
|
-
|
|
4882
|
-
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
|
|
5105
|
+
if (this.crossDomainParentOrigin !== null && this.crossDomainFrameNonce !== null) {
|
|
5106
|
+
/**
|
|
5107
|
+
* listen for messages from parent window, so we can signal that we're alive
|
|
5108
|
+
* */
|
|
5109
|
+
window.addEventListener('message', this.parentCrossDomainFrameListener);
|
|
5110
|
+
setInterval(() => {
|
|
5111
|
+
if (!this.crossDomainConnectionRequested) {
|
|
5112
|
+
return;
|
|
5113
|
+
}
|
|
5114
|
+
if (this.crossDomainFrameCapability === null ||
|
|
5115
|
+
Date.now() >=
|
|
5116
|
+
this.crossDomainFrameCapabilityExpiresAt - FRAME_CAPABILITY_REFRESH_LEAD_MS) {
|
|
5117
|
+
this.postHandshakeToParent();
|
|
5118
|
+
}
|
|
5119
|
+
else {
|
|
5120
|
+
this.postToParent(proto.polling);
|
|
5121
|
+
}
|
|
5122
|
+
}, 1000);
|
|
5123
|
+
}
|
|
4889
5124
|
}
|
|
4890
5125
|
else {
|
|
4891
5126
|
this.initWorker();
|
|
@@ -4893,7 +5128,9 @@ class App {
|
|
|
4893
5128
|
* if we get a signal from child iframes, we check for their node_id and send it back,
|
|
4894
5129
|
* so they can act as if it was just a same-domain iframe
|
|
4895
5130
|
* */
|
|
4896
|
-
|
|
5131
|
+
if (this.options.crossdomain?.enabled && this.crossDomainChildOrigins.size > 0) {
|
|
5132
|
+
window.addEventListener('message', this.crossDomainIframeListener);
|
|
5133
|
+
}
|
|
4897
5134
|
}
|
|
4898
5135
|
if (this.bc !== null) {
|
|
4899
5136
|
this.bc.postMessage({
|
|
@@ -4943,6 +5180,319 @@ class App {
|
|
|
4943
5180
|
};
|
|
4944
5181
|
}
|
|
4945
5182
|
}
|
|
5183
|
+
postToParent(line, extra = {}) {
|
|
5184
|
+
if (this.crossDomainParentOrigin === null ||
|
|
5185
|
+
this.crossDomainFrameNonce === null ||
|
|
5186
|
+
this.crossDomainFrameCapability === null ||
|
|
5187
|
+
Date.now() >= this.crossDomainFrameCapabilityExpiresAt) {
|
|
5188
|
+
return;
|
|
5189
|
+
}
|
|
5190
|
+
window.parent.postMessage({
|
|
5191
|
+
line,
|
|
5192
|
+
protocolVersion: CROSS_DOMAIN_IFRAME_PROTOCOL_VERSION,
|
|
5193
|
+
context: this.contextId,
|
|
5194
|
+
nonce: this.crossDomainFrameNonce,
|
|
5195
|
+
capability: this.crossDomainFrameCapability,
|
|
5196
|
+
...extra,
|
|
5197
|
+
}, this.crossDomainParentOrigin);
|
|
5198
|
+
}
|
|
5199
|
+
postHandshakeToParent() {
|
|
5200
|
+
if (this.crossDomainParentOrigin === null || this.crossDomainFrameNonce === null) {
|
|
5201
|
+
return;
|
|
5202
|
+
}
|
|
5203
|
+
window.parent.postMessage({
|
|
5204
|
+
line: proto.iframeSignal,
|
|
5205
|
+
protocolVersion: CROSS_DOMAIN_IFRAME_PROTOCOL_VERSION,
|
|
5206
|
+
context: this.contextId,
|
|
5207
|
+
nonce: this.crossDomainFrameNonce,
|
|
5208
|
+
projectKey: this.projectKey,
|
|
5209
|
+
}, this.crossDomainParentOrigin);
|
|
5210
|
+
}
|
|
5211
|
+
applyCrossDomainFrameInit(init) {
|
|
5212
|
+
this.parentActive = true;
|
|
5213
|
+
this.crossDomainConnectionRequested = true;
|
|
5214
|
+
this.crossDomainFrameCapability = init.capability;
|
|
5215
|
+
this.crossDomainFrameCapabilityExpiresAt = init.capabilityExpiresAt;
|
|
5216
|
+
this.crossDomainFrameSession = init.session;
|
|
5217
|
+
this.crossDomainFrameCanvas = init.canvas ?? null;
|
|
5218
|
+
this.rootId = init.id;
|
|
5219
|
+
this.frameOderNumber = init.frameOrderNumber;
|
|
5220
|
+
this.frameLevel = init.frameLevel;
|
|
5221
|
+
this.allowAppStart();
|
|
5222
|
+
}
|
|
5223
|
+
clearCrossDomainFrameAuthorization() {
|
|
5224
|
+
this.parentActive = false;
|
|
5225
|
+
this.canStart = false;
|
|
5226
|
+
this.crossDomainFrameCapability = null;
|
|
5227
|
+
this.crossDomainFrameCapabilityExpiresAt = 0;
|
|
5228
|
+
this.crossDomainFrameSession = null;
|
|
5229
|
+
this.crossDomainFrameCanvas = null;
|
|
5230
|
+
this.rootId = null;
|
|
5231
|
+
}
|
|
5232
|
+
findCrossDomainFrame(source) {
|
|
5233
|
+
const frame = this.pageFrames.find((candidate) => candidate.contentWindow === source) ??
|
|
5234
|
+
Array.from(document.querySelectorAll('iframe')).find((candidate) => candidate.contentWindow === source);
|
|
5235
|
+
if (!frame)
|
|
5236
|
+
return null;
|
|
5237
|
+
if (!this.pageFrames.includes(frame)) {
|
|
5238
|
+
this.pageFrames.push(frame);
|
|
5239
|
+
}
|
|
5240
|
+
return frame;
|
|
5241
|
+
}
|
|
5242
|
+
revokeCrossDomainFrame(source, expectedState) {
|
|
5243
|
+
const state = this.crossDomainFrames.get(source);
|
|
5244
|
+
if (!state || (expectedState !== undefined && state !== expectedState)) {
|
|
5245
|
+
return;
|
|
5246
|
+
}
|
|
5247
|
+
this.crossDomainFrames.delete(source);
|
|
5248
|
+
}
|
|
5249
|
+
pruneDetachedCrossDomainFrames() {
|
|
5250
|
+
this.crossDomainFrames.forEach((state, source) => {
|
|
5251
|
+
if (!state.frame.isConnected || state.frame.contentWindow !== source) {
|
|
5252
|
+
this.revokeCrossDomainFrame(source);
|
|
5253
|
+
}
|
|
5254
|
+
});
|
|
5255
|
+
}
|
|
5256
|
+
currentCrossDomainSession() {
|
|
5257
|
+
const session = this.session.getInfo();
|
|
5258
|
+
if (!session.sessionID) {
|
|
5259
|
+
return null;
|
|
5260
|
+
}
|
|
5261
|
+
return {
|
|
5262
|
+
sessionID: session.sessionID,
|
|
5263
|
+
projectID: session.projectID,
|
|
5264
|
+
startedAt: session.timestamp,
|
|
5265
|
+
delayMs: this.delay,
|
|
5266
|
+
userUUID: this.localStorage.getItem(this.options.local_uuid_key) ?? '',
|
|
5267
|
+
};
|
|
5268
|
+
}
|
|
5269
|
+
frameInitPayload(state, id) {
|
|
5270
|
+
const session = this.currentCrossDomainSession();
|
|
5271
|
+
if (session === null ||
|
|
5272
|
+
session.sessionID !== state.sessionID ||
|
|
5273
|
+
Date.now() >= state.capabilityExpiresAt) {
|
|
5274
|
+
return null;
|
|
5275
|
+
}
|
|
5276
|
+
return {
|
|
5277
|
+
id,
|
|
5278
|
+
frameOrderNumber: state.frameOrderNumber,
|
|
5279
|
+
frameLevel: this.frameLevel + 1,
|
|
5280
|
+
capabilityExpiresAt: state.capabilityExpiresAt,
|
|
5281
|
+
session,
|
|
5282
|
+
...(this.crossDomainCanvasConfig ? { canvas: this.crossDomainCanvasConfig } : {}),
|
|
5283
|
+
};
|
|
5284
|
+
}
|
|
5285
|
+
rotateCrossDomainFrameCapability(state, sessionID, now = Date.now()) {
|
|
5286
|
+
const capability = createCrossDomainSecret();
|
|
5287
|
+
if (capability === null) {
|
|
5288
|
+
return false;
|
|
5289
|
+
}
|
|
5290
|
+
const preservePreviousCapability = state.sessionID === sessionID && now < state.capabilityExpiresAt;
|
|
5291
|
+
state.previousCapability = preservePreviousCapability ? state.capability : null;
|
|
5292
|
+
state.previousCapabilityExpiresAt = preservePreviousCapability
|
|
5293
|
+
? Math.min(state.capabilityExpiresAt, now + FRAME_PREVIOUS_CAPABILITY_GRACE_MS)
|
|
5294
|
+
: 0;
|
|
5295
|
+
state.capability = capability;
|
|
5296
|
+
state.sessionID = sessionID;
|
|
5297
|
+
state.capabilityExpiresAt = now + FRAME_CAPABILITY_TTL_MS;
|
|
5298
|
+
return true;
|
|
5299
|
+
}
|
|
5300
|
+
crossDomainFrameNeedsRotation(state, sessionID, now = Date.now()) {
|
|
5301
|
+
return (state.sessionID !== sessionID ||
|
|
5302
|
+
now >= state.capabilityExpiresAt - FRAME_CAPABILITY_REFRESH_LEAD_MS);
|
|
5303
|
+
}
|
|
5304
|
+
postToCrossDomainFrame(state, line, extra = {}) {
|
|
5305
|
+
state.source.postMessage({
|
|
5306
|
+
line,
|
|
5307
|
+
protocolVersion: CROSS_DOMAIN_IFRAME_PROTOCOL_VERSION,
|
|
5308
|
+
context: state.context,
|
|
5309
|
+
nonce: state.nonce,
|
|
5310
|
+
capability: state.capability,
|
|
5311
|
+
...extra,
|
|
5312
|
+
}, state.origin);
|
|
5313
|
+
}
|
|
5314
|
+
consumeCrossDomainEnvelopeBudget(source) {
|
|
5315
|
+
const now = Date.now();
|
|
5316
|
+
let budget = this.crossDomainEnvelopeBudgets.get(source);
|
|
5317
|
+
if (budget === undefined || now - budget.windowStartedAt >= FRAME_RATE_WINDOW_MS) {
|
|
5318
|
+
budget = {
|
|
5319
|
+
windowStartedAt: now,
|
|
5320
|
+
envelopesInWindow: 0,
|
|
5321
|
+
};
|
|
5322
|
+
this.crossDomainEnvelopeBudgets.set(source, budget);
|
|
5323
|
+
}
|
|
5324
|
+
if (budget.envelopesInWindow >= MAX_FRAME_ENVELOPES_PER_WINDOW) {
|
|
5325
|
+
return false;
|
|
5326
|
+
}
|
|
5327
|
+
budget.envelopesInWindow += 1;
|
|
5328
|
+
return true;
|
|
5329
|
+
}
|
|
5330
|
+
async establishCrossDomainFrame(event, handshake) {
|
|
5331
|
+
if (!this.active() ||
|
|
5332
|
+
event.source === null ||
|
|
5333
|
+
!this.crossDomainChildOrigins.has(event.origin)) {
|
|
5334
|
+
return;
|
|
5335
|
+
}
|
|
5336
|
+
const candidateSource = event.source;
|
|
5337
|
+
if (!this.consumeCrossDomainEnvelopeBudget(candidateSource)) {
|
|
5338
|
+
return;
|
|
5339
|
+
}
|
|
5340
|
+
const frame = this.findCrossDomainFrame(candidateSource);
|
|
5341
|
+
if (frame === null || !frame.isConnected || frame.contentWindow === null) {
|
|
5342
|
+
return;
|
|
5343
|
+
}
|
|
5344
|
+
const source = frame.contentWindow;
|
|
5345
|
+
const existing = this.crossDomainFrames.get(source);
|
|
5346
|
+
if (this.pendingCrossDomainFrames.has(source)) {
|
|
5347
|
+
return;
|
|
5348
|
+
}
|
|
5349
|
+
const existingMatches = existing !== undefined &&
|
|
5350
|
+
existing.origin === event.origin &&
|
|
5351
|
+
existing.context === handshake.context &&
|
|
5352
|
+
existing.nonce === handshake.nonce &&
|
|
5353
|
+
existing.frame === frame;
|
|
5354
|
+
if (existing !== undefined && !existingMatches) {
|
|
5355
|
+
return;
|
|
5356
|
+
}
|
|
5357
|
+
const authorizationGeneration = this.crossDomainAuthorizationGeneration;
|
|
5358
|
+
const pendingToken = Symbol();
|
|
5359
|
+
this.pendingCrossDomainFrames.set(source, pendingToken);
|
|
5360
|
+
let navigated = false;
|
|
5361
|
+
const markNavigated = () => {
|
|
5362
|
+
navigated = true;
|
|
5363
|
+
};
|
|
5364
|
+
frame.addEventListener('load', markNavigated, { once: true });
|
|
5365
|
+
try {
|
|
5366
|
+
const id = await this.checkNodeId(source);
|
|
5367
|
+
if (id === null ||
|
|
5368
|
+
navigated ||
|
|
5369
|
+
!this.active() ||
|
|
5370
|
+
authorizationGeneration !== this.crossDomainAuthorizationGeneration ||
|
|
5371
|
+
!frame.isConnected ||
|
|
5372
|
+
frame.contentWindow !== source) {
|
|
5373
|
+
return;
|
|
5374
|
+
}
|
|
5375
|
+
const session = this.currentCrossDomainSession();
|
|
5376
|
+
if (session === null) {
|
|
5377
|
+
return;
|
|
5378
|
+
}
|
|
5379
|
+
const now = Date.now();
|
|
5380
|
+
let state;
|
|
5381
|
+
if (existingMatches) {
|
|
5382
|
+
state = existing;
|
|
5383
|
+
if (this.crossDomainFrameNeedsRotation(state, session.sessionID, now) &&
|
|
5384
|
+
!this.rotateCrossDomainFrameCapability(state, session.sessionID, now)) {
|
|
5385
|
+
return;
|
|
5386
|
+
}
|
|
5387
|
+
state.lastSeenAt = now;
|
|
5388
|
+
}
|
|
5389
|
+
else {
|
|
5390
|
+
const capability = createCrossDomainSecret();
|
|
5391
|
+
if (capability === null) {
|
|
5392
|
+
return;
|
|
5393
|
+
}
|
|
5394
|
+
state = {
|
|
5395
|
+
source,
|
|
5396
|
+
frame,
|
|
5397
|
+
origin: event.origin,
|
|
5398
|
+
protocolVersion: CROSS_DOMAIN_IFRAME_PROTOCOL_VERSION,
|
|
5399
|
+
context: handshake.context,
|
|
5400
|
+
nonce: handshake.nonce,
|
|
5401
|
+
capability,
|
|
5402
|
+
frameOrderNumber: this.nextCrossDomainFrameOrder++,
|
|
5403
|
+
sessionID: session.sessionID,
|
|
5404
|
+
capabilityExpiresAt: now + FRAME_CAPABILITY_TTL_MS,
|
|
5405
|
+
previousCapability: null,
|
|
5406
|
+
previousCapabilityExpiresAt: 0,
|
|
5407
|
+
lastSeenAt: now,
|
|
5408
|
+
rateWindowStartedAt: now,
|
|
5409
|
+
messagesInRateWindow: 0,
|
|
5410
|
+
bytesInRateWindow: 0,
|
|
5411
|
+
};
|
|
5412
|
+
}
|
|
5413
|
+
const payload = this.frameInitPayload(state, id);
|
|
5414
|
+
if (payload === null) {
|
|
5415
|
+
return;
|
|
5416
|
+
}
|
|
5417
|
+
if (!existingMatches) {
|
|
5418
|
+
if (existing !== undefined) {
|
|
5419
|
+
this.revokeCrossDomainFrame(source, existing);
|
|
5420
|
+
}
|
|
5421
|
+
this.crossDomainFrames.set(source, state);
|
|
5422
|
+
frame.addEventListener('load', () => {
|
|
5423
|
+
this.revokeCrossDomainFrame(source, state);
|
|
5424
|
+
}, { once: true });
|
|
5425
|
+
}
|
|
5426
|
+
this.postToCrossDomainFrame(state, proto.iframeId, payload);
|
|
5427
|
+
}
|
|
5428
|
+
finally {
|
|
5429
|
+
frame.removeEventListener('load', markNavigated);
|
|
5430
|
+
if (this.pendingCrossDomainFrames.get(source) === pendingToken) {
|
|
5431
|
+
this.pendingCrossDomainFrames.delete(source);
|
|
5432
|
+
}
|
|
5433
|
+
}
|
|
5434
|
+
}
|
|
5435
|
+
validatedCrossDomainFrame(event, expectedLine) {
|
|
5436
|
+
if (event.source === null ||
|
|
5437
|
+
!this.crossDomainChildOrigins.has(event.origin) ||
|
|
5438
|
+
event.source === window) {
|
|
5439
|
+
return null;
|
|
5440
|
+
}
|
|
5441
|
+
const source = event.source;
|
|
5442
|
+
const state = this.crossDomainFrames.get(source);
|
|
5443
|
+
const now = Date.now();
|
|
5444
|
+
const currentSessionID = this.session.getInfo().sessionID;
|
|
5445
|
+
if (!state ||
|
|
5446
|
+
state.origin !== event.origin ||
|
|
5447
|
+
!state.frame.isConnected ||
|
|
5448
|
+
state.frame.contentWindow !== source) {
|
|
5449
|
+
if (state) {
|
|
5450
|
+
this.revokeCrossDomainFrame(source);
|
|
5451
|
+
}
|
|
5452
|
+
return null;
|
|
5453
|
+
}
|
|
5454
|
+
if (currentSessionID === undefined || state.sessionID !== currentSessionID) {
|
|
5455
|
+
this.revokeCrossDomainFrame(source);
|
|
5456
|
+
return null;
|
|
5457
|
+
}
|
|
5458
|
+
const matchesCurrentCapability = now < state.capabilityExpiresAt && matchesCrossDomainEnvelope(event.data, expectedLine, state);
|
|
5459
|
+
const matchesPreviousCapability = state.previousCapability !== null &&
|
|
5460
|
+
now < state.previousCapabilityExpiresAt &&
|
|
5461
|
+
matchesCrossDomainEnvelope(event.data, expectedLine, {
|
|
5462
|
+
protocolVersion: state.protocolVersion,
|
|
5463
|
+
context: state.context,
|
|
5464
|
+
nonce: state.nonce,
|
|
5465
|
+
capability: state.previousCapability,
|
|
5466
|
+
});
|
|
5467
|
+
if (!matchesCurrentCapability && !matchesPreviousCapability) {
|
|
5468
|
+
if (now >= state.capabilityExpiresAt && now >= state.previousCapabilityExpiresAt) {
|
|
5469
|
+
this.revokeCrossDomainFrame(source);
|
|
5470
|
+
}
|
|
5471
|
+
return null;
|
|
5472
|
+
}
|
|
5473
|
+
if ((matchesCurrentCapability && state.previousCapability !== null) ||
|
|
5474
|
+
now >= state.previousCapabilityExpiresAt) {
|
|
5475
|
+
state.previousCapability = null;
|
|
5476
|
+
state.previousCapabilityExpiresAt = 0;
|
|
5477
|
+
}
|
|
5478
|
+
state.lastSeenAt = now;
|
|
5479
|
+
return state;
|
|
5480
|
+
}
|
|
5481
|
+
consumeCrossDomainFrameBudget(state, messageCount, bytes) {
|
|
5482
|
+
const now = Date.now();
|
|
5483
|
+
if (now - state.rateWindowStartedAt >= FRAME_RATE_WINDOW_MS) {
|
|
5484
|
+
state.rateWindowStartedAt = now;
|
|
5485
|
+
state.messagesInRateWindow = 0;
|
|
5486
|
+
state.bytesInRateWindow = 0;
|
|
5487
|
+
}
|
|
5488
|
+
if (state.messagesInRateWindow + messageCount > MAX_FRAME_MESSAGES_PER_WINDOW ||
|
|
5489
|
+
state.bytesInRateWindow + bytes > MAX_FRAME_BYTES_PER_WINDOW) {
|
|
5490
|
+
return false;
|
|
5491
|
+
}
|
|
5492
|
+
state.messagesInRateWindow += messageCount;
|
|
5493
|
+
state.bytesInRateWindow += bytes;
|
|
5494
|
+
return true;
|
|
5495
|
+
}
|
|
4946
5496
|
allowAppStart() {
|
|
4947
5497
|
this.canStart = true;
|
|
4948
5498
|
if (this.startTimeout) {
|
|
@@ -5002,6 +5552,7 @@ class App {
|
|
|
5002
5552
|
setTimeout(() => {
|
|
5003
5553
|
closing = false;
|
|
5004
5554
|
}, 500);
|
|
5555
|
+
this.canvasRecorder?.flushPendingSnapshots();
|
|
5005
5556
|
if (this.worker) {
|
|
5006
5557
|
this.worker.postMessage('closing');
|
|
5007
5558
|
}
|
|
@@ -5042,22 +5593,39 @@ class App {
|
|
|
5042
5593
|
}
|
|
5043
5594
|
else if (data.type === 'compress') {
|
|
5044
5595
|
const batch = data.batch;
|
|
5596
|
+
const dataType = data.dataType;
|
|
5045
5597
|
const batchSize = batch.byteLength;
|
|
5046
|
-
|
|
5047
|
-
|
|
5048
|
-
|
|
5049
|
-
|
|
5050
|
-
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
|
|
5054
|
-
|
|
5598
|
+
const hasCompressionAPI = 'CompressionStream' in globalThis;
|
|
5599
|
+
if (batchSize > this.compressionThreshold && hasCompressionAPI) {
|
|
5600
|
+
const blob = new Blob([batch]);
|
|
5601
|
+
const stream = blob.stream().pipeThrough(new CompressionStream('gzip'));
|
|
5602
|
+
new Response(stream)
|
|
5603
|
+
.arrayBuffer()
|
|
5604
|
+
.then((compressedBuffer) => {
|
|
5605
|
+
this.worker?.postMessage({
|
|
5606
|
+
type: 'compressed',
|
|
5607
|
+
batch: new Uint8Array(compressedBuffer),
|
|
5608
|
+
dataType,
|
|
5609
|
+
});
|
|
5610
|
+
})
|
|
5611
|
+
.catch((err) => {
|
|
5612
|
+
this.debug.error('OxvoSessions compression error:', err);
|
|
5613
|
+
this.worker?.postMessage({ type: 'uncompressed', batch: batch, dataType });
|
|
5055
5614
|
});
|
|
5056
5615
|
}
|
|
5057
5616
|
else {
|
|
5058
|
-
this.worker?.postMessage({ type: 'uncompressed', batch: batch });
|
|
5617
|
+
this.worker?.postMessage({ type: 'uncompressed', batch: batch, dataType });
|
|
5059
5618
|
}
|
|
5060
5619
|
}
|
|
5620
|
+
else if (data.type === 'local_save') {
|
|
5621
|
+
const blob = new Blob([data.batch], { type: 'application/octet-stream' });
|
|
5622
|
+
const url = URL.createObjectURL(blob);
|
|
5623
|
+
const a = document.createElement('a');
|
|
5624
|
+
a.href = url;
|
|
5625
|
+
a.download = data.name;
|
|
5626
|
+
a.click();
|
|
5627
|
+
URL.revokeObjectURL(url);
|
|
5628
|
+
}
|
|
5061
5629
|
else if (data.type === 'queue_empty') {
|
|
5062
5630
|
this.onSessionSent();
|
|
5063
5631
|
}
|
|
@@ -5088,27 +5656,36 @@ class App {
|
|
|
5088
5656
|
return;
|
|
5089
5657
|
}
|
|
5090
5658
|
if (this.insideIframe) {
|
|
5091
|
-
|
|
5092
|
-
line: proto.iframeBatch,
|
|
5093
|
-
messages: this.messages,
|
|
5094
|
-
}, this.options.crossdomain?.parentDomain ?? '*');
|
|
5659
|
+
this.postToParent(proto.iframeBatch, { messages: this.messages });
|
|
5095
5660
|
this.commitCallbacks.forEach((cb) => cb(this.messages));
|
|
5096
5661
|
this.messages.length = 0;
|
|
5097
5662
|
return;
|
|
5098
5663
|
}
|
|
5099
|
-
if (this.worker === undefined
|
|
5664
|
+
if (this.worker === undefined) {
|
|
5100
5665
|
return;
|
|
5101
5666
|
}
|
|
5102
5667
|
if (!this.messages.length) {
|
|
5103
|
-
//
|
|
5668
|
+
// Send a keepalive batch every ~30s (1000 * 30ms) to keep the session active
|
|
5104
5669
|
if (this.emptyBatchCounter < 1000) {
|
|
5105
5670
|
this.emptyBatchCounter++;
|
|
5106
5671
|
return;
|
|
5107
5672
|
}
|
|
5673
|
+
// Keepalive: send just Timestamp + TabData so the session stays alive
|
|
5674
|
+
this.emptyBatchCounter = 0;
|
|
5675
|
+
try {
|
|
5676
|
+
this.worker?.postMessage([Timestamp(this.timestamp()), TabData(this.session.getTabId())]);
|
|
5677
|
+
}
|
|
5678
|
+
catch (e) {
|
|
5679
|
+
this._debug('worker_keepalive', e);
|
|
5680
|
+
}
|
|
5681
|
+
return;
|
|
5108
5682
|
}
|
|
5109
5683
|
this.emptyBatchCounter = 0;
|
|
5110
5684
|
try {
|
|
5111
5685
|
requestIdleCb(() => {
|
|
5686
|
+
if (!this.messages.length) {
|
|
5687
|
+
return;
|
|
5688
|
+
}
|
|
5112
5689
|
this.messages.unshift(Timestamp(this.timestamp()), TabData(this.session.getTabId()));
|
|
5113
5690
|
this.worker?.postMessage(this.messages);
|
|
5114
5691
|
this.commitCallbacks.forEach((cb) => cb(this.messages));
|
|
@@ -5235,6 +5812,159 @@ class App {
|
|
|
5235
5812
|
},
|
|
5236
5813
|
};
|
|
5237
5814
|
}
|
|
5815
|
+
getBootstrapAttemptStorageKey(kind) {
|
|
5816
|
+
return `${this.options.session_token_key}_bootstrap_idempotency_${kind}`;
|
|
5817
|
+
}
|
|
5818
|
+
isValidBootstrapAttempt(value, kind, currentTime) {
|
|
5819
|
+
if (typeof value !== 'object' || value === null) {
|
|
5820
|
+
return false;
|
|
5821
|
+
}
|
|
5822
|
+
const attempt = value;
|
|
5823
|
+
if (attempt.version !== 1 ||
|
|
5824
|
+
attempt.projectKey !== this.projectKey ||
|
|
5825
|
+
typeof attempt.createdAt !== 'number' ||
|
|
5826
|
+
!Number.isFinite(attempt.createdAt) ||
|
|
5827
|
+
currentTime - attempt.createdAt >= BOOTSTRAP_ATTEMPT_TTL_MS ||
|
|
5828
|
+
typeof attempt.idempotencyKey !== 'string' ||
|
|
5829
|
+
attempt.idempotencyKey.length < 8 ||
|
|
5830
|
+
attempt.idempotencyKey.length > 128 ||
|
|
5831
|
+
!/^[A-Za-z0-9._:-]+$/.test(attempt.idempotencyKey) ||
|
|
5832
|
+
typeof attempt.intentFingerprint !== 'string' ||
|
|
5833
|
+
attempt.intentFingerprint.length === 0 ||
|
|
5834
|
+
attempt.intentFingerprint.length > MAX_BOOTSTRAP_ATTEMPT_STORAGE_LENGTH ||
|
|
5835
|
+
typeof attempt.requestTimestamp !== 'number' ||
|
|
5836
|
+
!Number.isFinite(attempt.requestTimestamp) ||
|
|
5837
|
+
typeof attempt.workerTimestamp !== 'number' ||
|
|
5838
|
+
!Number.isFinite(attempt.workerTimestamp) ||
|
|
5839
|
+
typeof attempt.requestBody !== 'string' ||
|
|
5840
|
+
attempt.requestBody.length > MAX_BOOTSTRAP_ATTEMPT_STORAGE_LENGTH) {
|
|
5841
|
+
return false;
|
|
5842
|
+
}
|
|
5843
|
+
try {
|
|
5844
|
+
const body = JSON.parse(attempt.requestBody);
|
|
5845
|
+
const intent = this.parseBootstrapIntent(attempt.intentFingerprint);
|
|
5846
|
+
return (intent?.projectKey === this.projectKey &&
|
|
5847
|
+
intent.kind === kind &&
|
|
5848
|
+
body.app?.key === this.projectKey &&
|
|
5849
|
+
body.session?.dry === false &&
|
|
5850
|
+
body.session?.offline === (kind === 'offline') &&
|
|
5851
|
+
typeof body.session?.token === 'string' &&
|
|
5852
|
+
body.session.token === intent.token &&
|
|
5853
|
+
body.session?.rule === intent.rule &&
|
|
5854
|
+
(body.user?.id ?? '') === intent.userID &&
|
|
5855
|
+
(body.user?.uuid ?? '') === intent.userUUID &&
|
|
5856
|
+
body.meta?.ts === attempt.requestTimestamp &&
|
|
5857
|
+
attempt.idempotencyKey.startsWith(`bootstrap:${kind}:`));
|
|
5858
|
+
}
|
|
5859
|
+
catch {
|
|
5860
|
+
return false;
|
|
5861
|
+
}
|
|
5862
|
+
}
|
|
5863
|
+
removeStoredBootstrapAttempt(kind) {
|
|
5864
|
+
delete this.bootstrapAttempts[kind];
|
|
5865
|
+
try {
|
|
5866
|
+
this.sessionStorage.removeItem(this.getBootstrapAttemptStorageKey(kind));
|
|
5867
|
+
}
|
|
5868
|
+
catch {
|
|
5869
|
+
// The in-memory copy is still cleared when storage is unavailable.
|
|
5870
|
+
}
|
|
5871
|
+
}
|
|
5872
|
+
loadBootstrapAttempt(kind) {
|
|
5873
|
+
const currentTime = Date.now();
|
|
5874
|
+
const inMemoryAttempt = this.bootstrapAttempts[kind];
|
|
5875
|
+
if (this.isValidBootstrapAttempt(inMemoryAttempt, kind, currentTime)) {
|
|
5876
|
+
return inMemoryAttempt;
|
|
5877
|
+
}
|
|
5878
|
+
delete this.bootstrapAttempts[kind];
|
|
5879
|
+
try {
|
|
5880
|
+
const storedAttempt = this.sessionStorage.getItem(this.getBootstrapAttemptStorageKey(kind));
|
|
5881
|
+
if (storedAttempt === null || storedAttempt.length > MAX_BOOTSTRAP_ATTEMPT_STORAGE_LENGTH) {
|
|
5882
|
+
if (storedAttempt !== null) {
|
|
5883
|
+
this.removeStoredBootstrapAttempt(kind);
|
|
5884
|
+
}
|
|
5885
|
+
return undefined;
|
|
5886
|
+
}
|
|
5887
|
+
const parsedAttempt = JSON.parse(storedAttempt);
|
|
5888
|
+
if (this.isValidBootstrapAttempt(parsedAttempt, kind, currentTime)) {
|
|
5889
|
+
this.bootstrapAttempts[kind] = parsedAttempt;
|
|
5890
|
+
return parsedAttempt;
|
|
5891
|
+
}
|
|
5892
|
+
}
|
|
5893
|
+
catch {
|
|
5894
|
+
// Treat corrupt or inaccessible storage as an absent pending attempt.
|
|
5895
|
+
}
|
|
5896
|
+
this.removeStoredBootstrapAttempt(kind);
|
|
5897
|
+
return undefined;
|
|
5898
|
+
}
|
|
5899
|
+
getOrCreateBootstrapAttempt(kind, request) {
|
|
5900
|
+
const existingAttempt = this.loadBootstrapAttempt(kind);
|
|
5901
|
+
if (existingAttempt &&
|
|
5902
|
+
this.bootstrapIntentsMatch(existingAttempt.intentFingerprint, request.intentFingerprint)) {
|
|
5903
|
+
return existingAttempt;
|
|
5904
|
+
}
|
|
5905
|
+
if (existingAttempt) {
|
|
5906
|
+
this.removeStoredBootstrapAttempt(kind);
|
|
5907
|
+
}
|
|
5908
|
+
const attempt = {
|
|
5909
|
+
version: 1,
|
|
5910
|
+
projectKey: this.projectKey,
|
|
5911
|
+
createdAt: Date.now(),
|
|
5912
|
+
idempotencyKey: `bootstrap:${kind}:${generateRandomId(40)}`,
|
|
5913
|
+
...request,
|
|
5914
|
+
};
|
|
5915
|
+
this.bootstrapAttempts[kind] = attempt;
|
|
5916
|
+
try {
|
|
5917
|
+
this.sessionStorage.setItem(this.getBootstrapAttemptStorageKey(kind), JSON.stringify(attempt));
|
|
5918
|
+
}
|
|
5919
|
+
catch {
|
|
5920
|
+
// The in-memory copy still protects retries within this tracker instance.
|
|
5921
|
+
}
|
|
5922
|
+
return attempt;
|
|
5923
|
+
}
|
|
5924
|
+
clearBootstrapAttempt(kind) {
|
|
5925
|
+
this.removeStoredBootstrapAttempt(kind);
|
|
5926
|
+
}
|
|
5927
|
+
buildBootstrapIntentFingerprint(params) {
|
|
5928
|
+
const tracker = this.getTrackerInfo();
|
|
5929
|
+
return JSON.stringify({
|
|
5930
|
+
projectKey: this.projectKey,
|
|
5931
|
+
kind: params.kind,
|
|
5932
|
+
token: params.token ?? '',
|
|
5933
|
+
rule: params.rule ?? '',
|
|
5934
|
+
userID: this.session.getInfo().userID ?? '',
|
|
5935
|
+
userUUID: tracker.userUUID ?? '',
|
|
5936
|
+
});
|
|
5937
|
+
}
|
|
5938
|
+
parseBootstrapIntent(value) {
|
|
5939
|
+
try {
|
|
5940
|
+
const intent = JSON.parse(value);
|
|
5941
|
+
if (intent.projectKey !== this.projectKey ||
|
|
5942
|
+
(intent.kind !== 'recording' && intent.kind !== 'offline') ||
|
|
5943
|
+
typeof intent.token !== 'string' ||
|
|
5944
|
+
typeof intent.rule !== 'string' ||
|
|
5945
|
+
typeof intent.userID !== 'string' ||
|
|
5946
|
+
typeof intent.userUUID !== 'string') {
|
|
5947
|
+
return undefined;
|
|
5948
|
+
}
|
|
5949
|
+
return intent;
|
|
5950
|
+
}
|
|
5951
|
+
catch {
|
|
5952
|
+
return undefined;
|
|
5953
|
+
}
|
|
5954
|
+
}
|
|
5955
|
+
bootstrapIntentsMatch(existingFingerprint, currentFingerprint) {
|
|
5956
|
+
const existing = this.parseBootstrapIntent(existingFingerprint);
|
|
5957
|
+
const current = this.parseBootstrapIntent(currentFingerprint);
|
|
5958
|
+
if (!existing || !current) {
|
|
5959
|
+
return false;
|
|
5960
|
+
}
|
|
5961
|
+
return (existing.projectKey === current.projectKey &&
|
|
5962
|
+
existing.kind === current.kind &&
|
|
5963
|
+
existing.token === current.token &&
|
|
5964
|
+
(current.rule === '' || existing.rule === current.rule) &&
|
|
5965
|
+
(current.userID === '' || existing.userID === current.userID) &&
|
|
5966
|
+
(current.userUUID === '' || existing.userUUID === current.userUUID));
|
|
5967
|
+
}
|
|
5238
5968
|
getSessionMeta() {
|
|
5239
5969
|
return {
|
|
5240
5970
|
...this.session.getInfo(),
|
|
@@ -5302,10 +6032,22 @@ class App {
|
|
|
5302
6032
|
}
|
|
5303
6033
|
}
|
|
5304
6034
|
checkSessionToken(startNewSession) {
|
|
5305
|
-
const
|
|
5306
|
-
|
|
6035
|
+
const needReset = this.sessionStorage.getItem(this.options.session_reset_key) !== null;
|
|
6036
|
+
let needNewSessionID = startNewSession || needReset;
|
|
5307
6037
|
const sessionToken = this.session.getSessionSecret(this.projectKey);
|
|
5308
|
-
|
|
6038
|
+
if (needReset) {
|
|
6039
|
+
this.sessionStorage.removeItem(this.options.session_reset_key);
|
|
6040
|
+
}
|
|
6041
|
+
const storedVersion = this.sessionStorage.getItem(`${this.options.session_token_key}_version`);
|
|
6042
|
+
if (!storedVersion || storedVersion !== PROTO_VERSION) {
|
|
6043
|
+
needNewSessionID = true;
|
|
6044
|
+
this.sessionStorage.setItem(`${this.options.session_token_key}_version`, PROTO_VERSION);
|
|
6045
|
+
}
|
|
6046
|
+
const isNewSession = needNewSessionID || !sessionToken;
|
|
6047
|
+
if (isNewSession && sessionToken) {
|
|
6048
|
+
this.session.clearSessionSecret();
|
|
6049
|
+
}
|
|
6050
|
+
return isNewSession;
|
|
5309
6051
|
}
|
|
5310
6052
|
/**
|
|
5311
6053
|
* start buffering messages without starting the actual session, which gives
|
|
@@ -5377,7 +6119,7 @@ class App {
|
|
|
5377
6119
|
})),
|
|
5378
6120
|
});
|
|
5379
6121
|
const { session = {}, client = {}, device = {} } = await r.json();
|
|
5380
|
-
const { token, projectId } = session;
|
|
6122
|
+
const { token, projectId, assistToken } = session;
|
|
5381
6123
|
const { browser: userBrowser, city: userCity, country: userCountry, device: userDevice, os: userOS, state: userState, } = client;
|
|
5382
6124
|
this.session.assign({ projectID: projectId });
|
|
5383
6125
|
this.session.setUserInfo({
|
|
@@ -5390,6 +6132,7 @@ class App {
|
|
|
5390
6132
|
});
|
|
5391
6133
|
const onStartInfo = {
|
|
5392
6134
|
sessionToken: token,
|
|
6135
|
+
assistToken: typeof assistToken === 'string' ? assistToken : undefined,
|
|
5393
6136
|
userUUID: device?.id || '',
|
|
5394
6137
|
sessionID: session?.id || '',
|
|
5395
6138
|
};
|
|
@@ -5405,7 +6148,6 @@ class App {
|
|
|
5405
6148
|
offlineRecording(startOpts = {}, onSessionSent) {
|
|
5406
6149
|
this.onSessionSent = onSessionSent;
|
|
5407
6150
|
this.singleBuffer = true;
|
|
5408
|
-
const isNewSession = this.checkSessionToken(startOpts.startNewSession);
|
|
5409
6151
|
adjustTimeOrigin();
|
|
5410
6152
|
this.coldStartTs = now();
|
|
5411
6153
|
const saverBuffer = this.localStorage.getItem(bufferStorageKey);
|
|
@@ -5423,6 +6165,10 @@ class App {
|
|
|
5423
6165
|
if (startOpts.startNewSession) {
|
|
5424
6166
|
this.session.reset();
|
|
5425
6167
|
}
|
|
6168
|
+
const isNewSession = this.checkSessionToken(startOpts.startNewSession);
|
|
6169
|
+
this.offlineRecordingSessionToken = isNewSession
|
|
6170
|
+
? undefined
|
|
6171
|
+
: this.session.getSessionSecret(this.projectKey);
|
|
5426
6172
|
this.session.assign({
|
|
5427
6173
|
userID: startOpts.userID,
|
|
5428
6174
|
metadata: startOpts.metadata,
|
|
@@ -5472,39 +6218,60 @@ class App {
|
|
|
5472
6218
|
* */
|
|
5473
6219
|
async uploadOfflineRecording() {
|
|
5474
6220
|
this.stop(false);
|
|
5475
|
-
const
|
|
6221
|
+
const freshTimestamp = now();
|
|
6222
|
+
const bootstrapToken = this.offlineRecordingSessionToken ?? '';
|
|
6223
|
+
const freshRequestBody = () => JSON.stringify(this.buildStartPayload({
|
|
6224
|
+
timestamp: freshTimestamp,
|
|
6225
|
+
dry: false,
|
|
6226
|
+
offline: true,
|
|
6227
|
+
bufferMs: freshTimestamp - this.coldStartTs,
|
|
6228
|
+
token: bootstrapToken,
|
|
6229
|
+
}));
|
|
6230
|
+
const bootstrapAttempt = this.getOrCreateBootstrapAttempt('offline', {
|
|
6231
|
+
intentFingerprint: this.buildBootstrapIntentFingerprint({
|
|
6232
|
+
kind: 'offline',
|
|
6233
|
+
token: bootstrapToken,
|
|
6234
|
+
}),
|
|
6235
|
+
requestTimestamp: freshTimestamp,
|
|
6236
|
+
workerTimestamp: this.coldStartTs,
|
|
6237
|
+
requestBody: freshRequestBody(),
|
|
6238
|
+
});
|
|
5476
6239
|
this.worker?.postMessage({
|
|
5477
6240
|
type: 'start',
|
|
5478
6241
|
pageNo: this.session.incPageNo(),
|
|
5479
6242
|
ingestPoint: normalizeEndpoint(this.options.endpoint),
|
|
5480
|
-
timestamp:
|
|
6243
|
+
timestamp: bootstrapAttempt.workerTimestamp,
|
|
5481
6244
|
url: document.URL,
|
|
5482
6245
|
connAttemptCount: this.options.connAttemptCount,
|
|
5483
6246
|
connAttemptGap: this.options.connAttemptGap,
|
|
5484
6247
|
tabId: this.session.getTabId(),
|
|
6248
|
+
localDebug: this.options.__local_debug,
|
|
5485
6249
|
});
|
|
5486
6250
|
const ingestPoint = normalizeEndpoint(this.options.endpoint);
|
|
6251
|
+
const headers = {
|
|
6252
|
+
'Content-Type': 'application/json',
|
|
6253
|
+
'Idempotency-Key': bootstrapAttempt.idempotencyKey,
|
|
6254
|
+
};
|
|
5487
6255
|
const r = await fetch(ingestPoint + '/v1/bootstrap', {
|
|
5488
6256
|
method: 'POST',
|
|
5489
|
-
headers
|
|
5490
|
-
|
|
5491
|
-
},
|
|
5492
|
-
body: JSON.stringify(this.buildStartPayload({
|
|
5493
|
-
timestamp,
|
|
5494
|
-
dry: false,
|
|
5495
|
-
bufferMs: timestamp - this.coldStartTs,
|
|
5496
|
-
token: '',
|
|
5497
|
-
})),
|
|
6257
|
+
headers,
|
|
6258
|
+
body: bootstrapAttempt.requestBody,
|
|
5498
6259
|
});
|
|
5499
|
-
|
|
6260
|
+
if (r.status !== 200) {
|
|
6261
|
+
throw new Error(`Server error: ${r.status}. ${await r.text()}`);
|
|
6262
|
+
}
|
|
6263
|
+
const { session = {}, client = {}, limits = {}, protocolVersion: offlineProtocolVersion, } = await r.json();
|
|
5500
6264
|
const { token, projectId } = session;
|
|
5501
6265
|
const { browser: userBrowser, city: userCity, country: userCountry, device: userDevice, os: userOS, state: userState, } = client;
|
|
5502
6266
|
const { beacon: beaconSizeLimit } = limits;
|
|
5503
|
-
|
|
5504
|
-
|
|
5505
|
-
|
|
5506
|
-
|
|
5507
|
-
}
|
|
6267
|
+
if (typeof token !== 'string' ||
|
|
6268
|
+
token.length === 0 ||
|
|
6269
|
+
(typeof beaconSizeLimit !== 'number' && typeof beaconSizeLimit !== 'undefined')) {
|
|
6270
|
+
throw new Error('Incorrect server response for offline recording bootstrap');
|
|
6271
|
+
}
|
|
6272
|
+
this.session.setSessionSecret(token, this.projectKey);
|
|
6273
|
+
this.clearBootstrapAttempt('offline');
|
|
6274
|
+
this.offlineRecordingSessionToken = token;
|
|
5508
6275
|
this.session.assign({ projectID: projectId });
|
|
5509
6276
|
this.session.setUserInfo({
|
|
5510
6277
|
userBrowser,
|
|
@@ -5514,16 +6281,84 @@ class App {
|
|
|
5514
6281
|
userOS,
|
|
5515
6282
|
userState,
|
|
5516
6283
|
});
|
|
6284
|
+
this.worker?.postMessage({
|
|
6285
|
+
type: 'auth',
|
|
6286
|
+
token,
|
|
6287
|
+
beaconSizeLimit,
|
|
6288
|
+
protocolVersion: offlineProtocolVersion,
|
|
6289
|
+
});
|
|
5517
6290
|
while (this.bufferedMessages1.length > 0) {
|
|
5518
6291
|
await this.flushBuffer(this.bufferedMessages1);
|
|
5519
6292
|
}
|
|
5520
6293
|
this.postToWorker([[-1]]);
|
|
5521
6294
|
this.clearBuffers();
|
|
5522
6295
|
}
|
|
6296
|
+
startCrossDomainFrame(startOpts = {}) {
|
|
6297
|
+
if (this.activityState === ActivityState.Active ||
|
|
6298
|
+
this.activityState === ActivityState.Starting) {
|
|
6299
|
+
const reason = 'OxvoSessions: trying to call `start()` on the instance that has been started already.';
|
|
6300
|
+
return UnsuccessfulStart(reason);
|
|
6301
|
+
}
|
|
6302
|
+
if (this.crossDomainFrameSession === null ||
|
|
6303
|
+
this.crossDomainFrameCapability === null ||
|
|
6304
|
+
this.rootId === null) {
|
|
6305
|
+
return UnsuccessfulStart('Cross-domain iframe handshake was not established.');
|
|
6306
|
+
}
|
|
6307
|
+
if (Object.keys(startOpts).length !== 0) {
|
|
6308
|
+
this.prevOpts = startOpts;
|
|
6309
|
+
}
|
|
6310
|
+
if (startOpts.startCallback) {
|
|
6311
|
+
this.userStartCallback = startOpts.startCallback;
|
|
6312
|
+
}
|
|
6313
|
+
try {
|
|
6314
|
+
adjustTimeOrigin();
|
|
6315
|
+
this.activityState = ActivityState.Starting;
|
|
6316
|
+
this.delay = this.crossDomainFrameSession.delayMs;
|
|
6317
|
+
this.session.clearSessionSecret();
|
|
6318
|
+
this.session.assign({
|
|
6319
|
+
sessionID: this.crossDomainFrameSession.sessionID,
|
|
6320
|
+
projectID: this.crossDomainFrameSession.projectID,
|
|
6321
|
+
timestamp: this.crossDomainFrameSession.startedAt,
|
|
6322
|
+
});
|
|
6323
|
+
const onStartInfo = {
|
|
6324
|
+
sessionToken: '',
|
|
6325
|
+
userUUID: this.crossDomainFrameSession.userUUID,
|
|
6326
|
+
sessionID: this.crossDomainFrameSession.sessionID,
|
|
6327
|
+
};
|
|
6328
|
+
this.startCallbacks.forEach((callback) => callback(onStartInfo));
|
|
6329
|
+
this.userStartCallback?.(SuccessfulStart(onStartInfo));
|
|
6330
|
+
this.activityState = ActivityState.Active;
|
|
6331
|
+
if (this.crossDomainFrameCanvas?.enabled && !this.options.canvas.disableCanvas) {
|
|
6332
|
+
this.canvasRecorder =
|
|
6333
|
+
this.canvasRecorder ??
|
|
6334
|
+
new CanvasRecorder(this, {
|
|
6335
|
+
fps: this.crossDomainFrameCanvas.fps ?? 15,
|
|
6336
|
+
quality: this.crossDomainFrameCanvas.quality ?? 'medium',
|
|
6337
|
+
isDebug: this.options.canvas.__save_canvas_locally,
|
|
6338
|
+
fixedScaling: this.options.canvas.fixedCanvasScaling,
|
|
6339
|
+
useAnimationFrame: this.options.canvas.useAnimationFrame,
|
|
6340
|
+
framesSupport: this.crossDomainFrameCanvas.framesSupport,
|
|
6341
|
+
});
|
|
6342
|
+
}
|
|
6343
|
+
this.observer.crossdomainObserve(this.rootId, this.frameOderNumber, this.frameLevel);
|
|
6344
|
+
this.ticker.start();
|
|
6345
|
+
this.canvasRecorder?.startTracking();
|
|
6346
|
+
return SuccessfulStart(onStartInfo);
|
|
6347
|
+
}
|
|
6348
|
+
catch (reason) {
|
|
6349
|
+
this.stop();
|
|
6350
|
+
const message = reason instanceof Error ? reason.message : String(reason);
|
|
6351
|
+
this.signalError(message, []);
|
|
6352
|
+
return UnsuccessfulStart(message);
|
|
6353
|
+
}
|
|
6354
|
+
}
|
|
5523
6355
|
async _start(startOpts = {}, resetByWorker = false, conditionName) {
|
|
5524
6356
|
if (Object.keys(startOpts).length !== 0) {
|
|
5525
6357
|
this.prevOpts = startOpts;
|
|
5526
6358
|
}
|
|
6359
|
+
if (startOpts.startCallback) {
|
|
6360
|
+
this.userStartCallback = startOpts.startCallback;
|
|
6361
|
+
}
|
|
5527
6362
|
const isColdStart = this.activityState === ActivityState.ColdStart;
|
|
5528
6363
|
if (isColdStart && this.coldInterval) {
|
|
5529
6364
|
clearInterval(this.coldInterval);
|
|
@@ -5555,67 +6390,89 @@ class App {
|
|
|
5555
6390
|
userID: userId || undefined,
|
|
5556
6391
|
metadata: startOpts.metadata,
|
|
5557
6392
|
});
|
|
5558
|
-
const
|
|
6393
|
+
const sessionToken = this.session.getSessionSecret(this.projectKey);
|
|
6394
|
+
const isNewSession = this.checkSessionToken(startOpts.startNewSession);
|
|
6395
|
+
const freshTimestamp = now();
|
|
6396
|
+
const freshWorkerTimestamp = isColdStart ? this.coldStartTs : freshTimestamp;
|
|
6397
|
+
const bootstrapToken = isNewSession ? '' : sessionToken;
|
|
6398
|
+
const freshRequestBody = () => JSON.stringify(this.buildStartPayload({
|
|
6399
|
+
timestamp: freshTimestamp,
|
|
6400
|
+
dry: false,
|
|
6401
|
+
bufferMs: this.coldStartTs ? freshTimestamp - this.coldStartTs : 0,
|
|
6402
|
+
token: bootstrapToken,
|
|
6403
|
+
rule: conditionName,
|
|
6404
|
+
width: window.screen.width,
|
|
6405
|
+
height: window.screen.height,
|
|
6406
|
+
}));
|
|
6407
|
+
const bootstrapAttempt = this.getOrCreateBootstrapAttempt('recording', {
|
|
6408
|
+
intentFingerprint: this.buildBootstrapIntentFingerprint({
|
|
6409
|
+
kind: 'recording',
|
|
6410
|
+
token: bootstrapToken,
|
|
6411
|
+
rule: conditionName,
|
|
6412
|
+
}),
|
|
6413
|
+
requestTimestamp: freshTimestamp,
|
|
6414
|
+
workerTimestamp: freshWorkerTimestamp,
|
|
6415
|
+
requestBody: freshRequestBody(),
|
|
6416
|
+
});
|
|
6417
|
+
const timestamp = bootstrapAttempt.requestTimestamp;
|
|
5559
6418
|
this.worker?.postMessage({
|
|
5560
6419
|
type: 'start',
|
|
5561
6420
|
pageNo: this.session.incPageNo(),
|
|
5562
6421
|
ingestPoint: normalizeEndpoint(this.options.endpoint),
|
|
5563
|
-
timestamp:
|
|
6422
|
+
timestamp: bootstrapAttempt.workerTimestamp,
|
|
5564
6423
|
url: document.URL,
|
|
5565
6424
|
connAttemptCount: this.options.connAttemptCount,
|
|
5566
6425
|
connAttemptGap: this.options.connAttemptGap,
|
|
5567
6426
|
tabId: this.session.getTabId(),
|
|
6427
|
+
localDebug: this.options.__local_debug,
|
|
5568
6428
|
});
|
|
5569
|
-
const sessionToken = this.session.getSessionSecret(this.projectKey);
|
|
5570
|
-
const isNewSession = this.checkSessionToken(startOpts.startNewSession);
|
|
5571
|
-
this.sessionStorage.removeItem(this.options.session_reset_key);
|
|
5572
6429
|
this.debug.log('OxvoSessions: starting session; need new session id?', isNewSession, 'session token: ', sessionToken);
|
|
5573
6430
|
try {
|
|
5574
6431
|
const ingestPoint = normalizeEndpoint(this.options.endpoint);
|
|
6432
|
+
const headers = {
|
|
6433
|
+
'Content-Type': 'application/json',
|
|
6434
|
+
'Idempotency-Key': bootstrapAttempt.idempotencyKey,
|
|
6435
|
+
};
|
|
5575
6436
|
const r = await window.fetch(ingestPoint + '/v1/bootstrap', {
|
|
5576
6437
|
method: 'POST',
|
|
5577
|
-
headers
|
|
5578
|
-
|
|
5579
|
-
},
|
|
5580
|
-
body: JSON.stringify(this.buildStartPayload({
|
|
5581
|
-
timestamp,
|
|
5582
|
-
dry: false,
|
|
5583
|
-
bufferMs: timestamp - this.coldStartTs,
|
|
5584
|
-
token: isNewSession ? '' : sessionToken,
|
|
5585
|
-
rule: conditionName,
|
|
5586
|
-
width: window.screen.width,
|
|
5587
|
-
height: window.screen.height,
|
|
5588
|
-
})),
|
|
6438
|
+
headers,
|
|
6439
|
+
body: bootstrapAttempt.requestBody,
|
|
5589
6440
|
});
|
|
5590
6441
|
if (r.status !== 200) {
|
|
5591
6442
|
const error = await r.text();
|
|
5592
6443
|
const reason = error === CANCELED ? CANCELED : `Server error: ${r.status}. ${error}`;
|
|
5593
|
-
|
|
6444
|
+
throw reason;
|
|
5594
6445
|
}
|
|
5595
6446
|
if (!this.worker && !this.insideIframe) {
|
|
5596
6447
|
const reason = 'no worker found after start request (this should not happen in real world)';
|
|
5597
|
-
|
|
5598
|
-
return UnsuccessfulStart(reason);
|
|
6448
|
+
throw new Error(reason);
|
|
5599
6449
|
}
|
|
5600
|
-
const { session = {}, device = {}, client = {}, limits = {}, canvas = {}, flags = {} } = await r.json();
|
|
5601
|
-
const { token, id: sessionID, projectId: projectID, delayMs: delay, startedAt: startTimestamp, } = session;
|
|
6450
|
+
const { session = {}, device = {}, client = {}, limits = {}, canvas = {}, flags = {}, protocolVersion, } = await r.json();
|
|
6451
|
+
const { token, assistToken, id: sessionID, projectId: projectID, delayMs: delay, startedAt: startTimestamp, } = session;
|
|
5602
6452
|
const { id: userUUID } = device;
|
|
5603
6453
|
const { browser: userBrowser, city: userCity, country: userCountry, device: userDevice, os: userOS, state: userState, } = client;
|
|
5604
6454
|
const { beacon: beaconSizeLimit, compressAt: compressionThreshold } = limits;
|
|
5605
|
-
const { enabled: canvasEnabled, quality: canvasQuality, fps: canvasFPS } = canvas;
|
|
6455
|
+
const { enabled: canvasEnabled, quality: canvasQuality, fps: canvasFPS, framesSupport, } = canvas;
|
|
5606
6456
|
const socketOnly = flags?.socketOnly;
|
|
5607
6457
|
if (typeof token !== 'string' ||
|
|
6458
|
+
(typeof assistToken !== 'string' && typeof assistToken !== 'undefined') ||
|
|
5608
6459
|
typeof userUUID !== 'string' ||
|
|
5609
6460
|
(typeof startTimestamp !== 'number' && typeof startTimestamp !== 'undefined') ||
|
|
5610
6461
|
typeof sessionID !== 'string' ||
|
|
5611
6462
|
typeof delay !== 'number' ||
|
|
5612
6463
|
(typeof beaconSizeLimit !== 'number' && typeof beaconSizeLimit !== 'undefined')) {
|
|
5613
6464
|
const reason = `Incorrect server response: ${JSON.stringify(r)}`;
|
|
5614
|
-
|
|
5615
|
-
return UnsuccessfulStart(reason);
|
|
6465
|
+
throw new Error(reason);
|
|
5616
6466
|
}
|
|
6467
|
+
this.crossDomainCanvasConfig = {
|
|
6468
|
+
enabled: canvasEnabled === true,
|
|
6469
|
+
...(['low', 'medium', 'high'].includes(canvasQuality) ? { quality: canvasQuality } : {}),
|
|
6470
|
+
...(typeof canvasFPS === 'number' ? { fps: canvasFPS } : {}),
|
|
6471
|
+
...(typeof framesSupport === 'boolean' ? { framesSupport } : {}),
|
|
6472
|
+
};
|
|
5617
6473
|
this.delay = delay;
|
|
5618
6474
|
this.session.setSessionSecret(token, this.projectKey);
|
|
6475
|
+
this.clearBootstrapAttempt('recording');
|
|
5619
6476
|
if (sessionToken && sessionToken !== token) {
|
|
5620
6477
|
this.bc?.postMessage({
|
|
5621
6478
|
type: proto.reset,
|
|
@@ -5644,6 +6501,7 @@ class App {
|
|
|
5644
6501
|
type: 'auth',
|
|
5645
6502
|
token,
|
|
5646
6503
|
beaconSizeLimit,
|
|
6504
|
+
protocolVersion,
|
|
5647
6505
|
});
|
|
5648
6506
|
}
|
|
5649
6507
|
if (!isNewSession && token === sessionToken) {
|
|
@@ -5655,14 +6513,13 @@ class App {
|
|
|
5655
6513
|
Object.entries(this.session.getInfo().metadata).forEach(([key, value]) => this.send(Metadata(key, value)));
|
|
5656
6514
|
this.localStorage.setItem(this.options.local_uuid_key, userUUID);
|
|
5657
6515
|
this.compressionThreshold = compressionThreshold;
|
|
5658
|
-
const onStartInfo = { sessionToken: token, userUUID, sessionID };
|
|
6516
|
+
const onStartInfo = { sessionToken: token, assistToken, userUUID, sessionID };
|
|
5659
6517
|
// TODO: start as early as possible (before receiving the token)
|
|
5660
6518
|
/** after start */
|
|
5661
6519
|
this.startCallbacks.forEach((cb) => cb(onStartInfo)); // MBTODO: callbacks after DOM "mounted" (observed)
|
|
5662
|
-
if (
|
|
5663
|
-
|
|
6520
|
+
if (this.userStartCallback) {
|
|
6521
|
+
this.userStartCallback(SuccessfulStart(onStartInfo));
|
|
5664
6522
|
}
|
|
5665
|
-
await this.markerWatcher.fetchTags(normalizeEndpoint(this.options.endpoint), token);
|
|
5666
6523
|
this.activityState = ActivityState.Active;
|
|
5667
6524
|
if (this.options.crossdomain?.enabled) {
|
|
5668
6525
|
void this.bootChildrenFrames();
|
|
@@ -5676,6 +6533,7 @@ class App {
|
|
|
5676
6533
|
isDebug: this.options.canvas.__save_canvas_locally,
|
|
5677
6534
|
fixedScaling: this.options.canvas.fixedCanvasScaling,
|
|
5678
6535
|
useAnimationFrame: this.options.canvas.useAnimationFrame,
|
|
6536
|
+
framesSupport: !!framesSupport,
|
|
5679
6537
|
});
|
|
5680
6538
|
}
|
|
5681
6539
|
/** --------------- COLD START BUFFER ------------------*/
|
|
@@ -5698,11 +6556,11 @@ class App {
|
|
|
5698
6556
|
}
|
|
5699
6557
|
this.ticker.start();
|
|
5700
6558
|
this.canvasRecorder?.startTracking();
|
|
6559
|
+
void this.markerWatcher.fetchTags(normalizeEndpoint(this.options.endpoint), token);
|
|
5701
6560
|
return SuccessfulStart(onStartInfo);
|
|
5702
6561
|
}
|
|
5703
6562
|
catch (reason) {
|
|
5704
6563
|
this.stop();
|
|
5705
|
-
this.session.reset();
|
|
5706
6564
|
if (!reason) {
|
|
5707
6565
|
console.error('Unknown error during start');
|
|
5708
6566
|
this.signalError('Unknown error', []);
|
|
@@ -5728,6 +6586,23 @@ class App {
|
|
|
5728
6586
|
}, 100);
|
|
5729
6587
|
});
|
|
5730
6588
|
}
|
|
6589
|
+
async waitCrossDomainStart(timeoutMs = 15000) {
|
|
6590
|
+
if (this.canStart)
|
|
6591
|
+
return true;
|
|
6592
|
+
return new Promise((resolve) => {
|
|
6593
|
+
const interval = setInterval(() => {
|
|
6594
|
+
if (!this.canStart)
|
|
6595
|
+
return;
|
|
6596
|
+
clearInterval(interval);
|
|
6597
|
+
clearTimeout(timeout);
|
|
6598
|
+
resolve(true);
|
|
6599
|
+
}, 100);
|
|
6600
|
+
const timeout = setTimeout(() => {
|
|
6601
|
+
clearInterval(interval);
|
|
6602
|
+
resolve(false);
|
|
6603
|
+
}, timeoutMs);
|
|
6604
|
+
});
|
|
6605
|
+
}
|
|
5731
6606
|
async waitStarted() {
|
|
5732
6607
|
return this.waitStatus(ActivityState.Active);
|
|
5733
6608
|
}
|
|
@@ -5755,20 +6630,35 @@ class App {
|
|
|
5755
6630
|
return Promise.resolve(UnsuccessfulStart(reason));
|
|
5756
6631
|
}
|
|
5757
6632
|
if (this.insideIframe) {
|
|
6633
|
+
if (this.crossDomainParentOrigin === null || this.crossDomainFrameNonce === null) {
|
|
6634
|
+
return UnsuccessfulStart('Cross-domain iframe capture requires an explicit parentDomain and secure random support.');
|
|
6635
|
+
}
|
|
5758
6636
|
this.signalIframeTracker();
|
|
5759
6637
|
}
|
|
5760
|
-
|
|
6638
|
+
const startWhenReady = async () => {
|
|
6639
|
+
if (this.insideIframe) {
|
|
6640
|
+
const ready = await this.waitCrossDomainStart();
|
|
6641
|
+
if (!ready) {
|
|
6642
|
+
this.crossDomainConnectionRequested = false;
|
|
6643
|
+
this.crossDomainResumeAfterHandshake = false;
|
|
6644
|
+
this.clearCrossDomainFrameAuthorization();
|
|
6645
|
+
return UnsuccessfulStart('Cross-domain iframe handshake timed out.');
|
|
6646
|
+
}
|
|
6647
|
+
return this.startCrossDomainFrame(args[0]);
|
|
6648
|
+
}
|
|
5761
6649
|
await this.waitStart();
|
|
5762
6650
|
return this._start(...args);
|
|
6651
|
+
};
|
|
6652
|
+
if (!document.hidden) {
|
|
6653
|
+
return startWhenReady();
|
|
5763
6654
|
}
|
|
5764
6655
|
else {
|
|
5765
6656
|
return new Promise((resolve) => {
|
|
5766
6657
|
const onVisibilityChange = async () => {
|
|
5767
6658
|
if (!document.hidden) {
|
|
5768
|
-
await this.waitStart();
|
|
5769
6659
|
// eslint-disable-next-line
|
|
5770
6660
|
document.removeEventListener('visibilitychange', onVisibilityChange);
|
|
5771
|
-
resolve(
|
|
6661
|
+
resolve(startWhenReady());
|
|
5772
6662
|
}
|
|
5773
6663
|
};
|
|
5774
6664
|
// eslint-disable-next-line
|
|
@@ -5822,12 +6712,18 @@ class App {
|
|
|
5822
6712
|
this.canvasRecorder?.clear();
|
|
5823
6713
|
this.messages.length = 0;
|
|
5824
6714
|
this.parentActive = false;
|
|
6715
|
+
this.pageFrames = [];
|
|
5825
6716
|
}
|
|
5826
6717
|
finally {
|
|
5827
6718
|
this.activityState = ActivityState.NotActive;
|
|
5828
6719
|
this.debug.log('OxvoSessions tracking stopped.');
|
|
5829
6720
|
}
|
|
5830
6721
|
}
|
|
6722
|
+
if (this.insideIframe) {
|
|
6723
|
+
this.crossDomainConnectionRequested = false;
|
|
6724
|
+
this.crossDomainResumeAfterHandshake = false;
|
|
6725
|
+
this.clearCrossDomainFrameAuthorization();
|
|
6726
|
+
}
|
|
5831
6727
|
}
|
|
5832
6728
|
}
|
|
5833
6729
|
|
|
@@ -6319,6 +7215,12 @@ function Img (app) {
|
|
|
6319
7215
|
sendImgAttrs(node);
|
|
6320
7216
|
observer.observe(node, { attributes: true, attributeFilter: ['src', 'srcset'] });
|
|
6321
7217
|
});
|
|
7218
|
+
// On a runtime level change, re-evaluate placeholder vs real src for this image.
|
|
7219
|
+
app.attachResanitizeCallback((node) => {
|
|
7220
|
+
if (hasTag(node, 'img')) {
|
|
7221
|
+
sendImgAttrs(node);
|
|
7222
|
+
}
|
|
7223
|
+
});
|
|
6322
7224
|
}
|
|
6323
7225
|
|
|
6324
7226
|
const INPUT_TYPES = [
|
|
@@ -6376,19 +7278,24 @@ const labelElementFor = IN_BROWSER && 'labels' in HTMLInputElement.prototype
|
|
|
6376
7278
|
}
|
|
6377
7279
|
}
|
|
6378
7280
|
};
|
|
6379
|
-
function getInputLabel(node) {
|
|
6380
|
-
|
|
6381
|
-
if (
|
|
6382
|
-
|
|
6383
|
-
|
|
6384
|
-
|
|
6385
|
-
|
|
6386
|
-
|
|
6387
|
-
|
|
6388
|
-
|
|
6389
|
-
|
|
6390
|
-
|
|
6391
|
-
|
|
7281
|
+
function getInputLabel(node, customAttributes) {
|
|
7282
|
+
const oxvosessionsLabel = getLabelAttribute(node);
|
|
7283
|
+
if (oxvosessionsLabel !== null)
|
|
7284
|
+
return normSpaces(oxvosessionsLabel).slice(0, 100);
|
|
7285
|
+
const customAttributeLabel = getCustomAttributeLabel(node, customAttributes);
|
|
7286
|
+
if (customAttributeLabel)
|
|
7287
|
+
return customAttributeLabel;
|
|
7288
|
+
if (node.id)
|
|
7289
|
+
return `#${node.id}`;
|
|
7290
|
+
const classLabel = getClassSelector(node);
|
|
7291
|
+
if (classLabel)
|
|
7292
|
+
return classLabel;
|
|
7293
|
+
const labelElement = labelElementFor(node);
|
|
7294
|
+
const label = node.name ||
|
|
7295
|
+
node.placeholder ||
|
|
7296
|
+
(labelElement && labelElement.innerText) ||
|
|
7297
|
+
node.type;
|
|
7298
|
+
return normSpaces(label || '').slice(0, 100);
|
|
6392
7299
|
}
|
|
6393
7300
|
const InputMode = {
|
|
6394
7301
|
Plain: 0,
|
|
@@ -6410,9 +7317,12 @@ function Input (app, opts) {
|
|
|
6410
7317
|
}
|
|
6411
7318
|
else if (app.sanitizer.isObscured(id) ||
|
|
6412
7319
|
(inputMode === InputMode.Plain &&
|
|
6413
|
-
((options.maskInputNumbers &&
|
|
7320
|
+
((options.maskInputNumbers &&
|
|
7321
|
+
node.type !== 'date' &&
|
|
7322
|
+
/\d\d\d\d/.test(value)) ||
|
|
6414
7323
|
(options.maskInputDates && node.type === 'date') ||
|
|
6415
|
-
(options.maskInputEmails &&
|
|
7324
|
+
(options.maskInputEmails &&
|
|
7325
|
+
(node.type === 'email' || !!~value.indexOf('@')))))) {
|
|
6416
7326
|
inputMode = InputMode.Obscured;
|
|
6417
7327
|
}
|
|
6418
7328
|
let mask = 0;
|
|
@@ -6432,11 +7342,24 @@ function Input (app, opts) {
|
|
|
6432
7342
|
const { value, mask } = getInputValue(id, node);
|
|
6433
7343
|
app.send(SetInputValue(id, value, mask));
|
|
6434
7344
|
}
|
|
7345
|
+
const tagSelectorMap = new Map();
|
|
6435
7346
|
const inputValues = new Map();
|
|
6436
7347
|
const checkboxValues = new Map();
|
|
7348
|
+
function getTagSelector(id, node) {
|
|
7349
|
+
const cached = tagSelectorMap.get(id);
|
|
7350
|
+
if (cached !== undefined)
|
|
7351
|
+
return cached;
|
|
7352
|
+
const tagMatch = app.tagMatcher.match(node);
|
|
7353
|
+
if (tagMatch) {
|
|
7354
|
+
tagSelectorMap.set(id, tagMatch.selector);
|
|
7355
|
+
return tagMatch.selector;
|
|
7356
|
+
}
|
|
7357
|
+
return null;
|
|
7358
|
+
}
|
|
6437
7359
|
app.attachStopCallback(() => {
|
|
6438
7360
|
inputValues.clear();
|
|
6439
7361
|
checkboxValues.clear();
|
|
7362
|
+
tagSelectorMap.clear();
|
|
6440
7363
|
});
|
|
6441
7364
|
function trackInputValue(id, node) {
|
|
6442
7365
|
if (inputValues.get(id) === node.value) {
|
|
@@ -6469,12 +7392,19 @@ function Input (app, opts) {
|
|
|
6469
7392
|
}, 3);
|
|
6470
7393
|
function sendInputChange(id, node, hesitationTime, inputTime) {
|
|
6471
7394
|
const { value, mask } = getInputValue(id, node);
|
|
6472
|
-
let label = getInputLabel(node);
|
|
7395
|
+
let label = getTagSelector(id, node) || getInputLabel(node, options.customAttributes);
|
|
6473
7396
|
if (app.sanitizer.maskAllByDefault) {
|
|
6474
7397
|
label = label.replaceAll(/./g, '*');
|
|
6475
7398
|
}
|
|
6476
7399
|
app.send(InputChange(id, value, mask !== 0, label, hesitationTime, inputTime));
|
|
6477
7400
|
}
|
|
7401
|
+
// Re-emit a field's value when its sanitization level changes at runtime.
|
|
7402
|
+
// getInputValue() reads the current level, so re-sending applies the new mask.
|
|
7403
|
+
app.attachResanitizeCallback((node, id) => {
|
|
7404
|
+
if (isTextFieldElement(node) || hasTag(node, 'select')) {
|
|
7405
|
+
sendInputValue(id, node);
|
|
7406
|
+
}
|
|
7407
|
+
});
|
|
6478
7408
|
app.nodes.attachNodeCallback(app.safe((node) => {
|
|
6479
7409
|
const id = app.nodes.getID(node);
|
|
6480
7410
|
if (id === undefined) {
|
|
@@ -6578,21 +7508,28 @@ function _getTarget(target, document) {
|
|
|
6578
7508
|
return target === document.documentElement ? null : target;
|
|
6579
7509
|
}
|
|
6580
7510
|
function Mouse (app, options) {
|
|
6581
|
-
const { disableClickmaps = false } = options || {};
|
|
7511
|
+
const { disableClickmaps = false, customAttributes } = options || {};
|
|
6582
7512
|
function getTargetLabel(target) {
|
|
6583
7513
|
const dl = getLabelAttribute(target);
|
|
6584
7514
|
if (dl !== null) {
|
|
6585
7515
|
return dl;
|
|
6586
7516
|
}
|
|
6587
7517
|
if (hasTag(target, 'input')) {
|
|
6588
|
-
return getInputLabel(target);
|
|
6589
|
-
}
|
|
7518
|
+
return getInputLabel(target, customAttributes);
|
|
7519
|
+
}
|
|
7520
|
+
const customAttributeLabel = getCustomAttributeLabel(target, customAttributes);
|
|
7521
|
+
if (customAttributeLabel)
|
|
7522
|
+
return customAttributeLabel;
|
|
7523
|
+
if (target.id)
|
|
7524
|
+
return `#${target.id}`;
|
|
7525
|
+
const classLabel = getClassSelector(target);
|
|
7526
|
+
if (classLabel)
|
|
7527
|
+
return classLabel;
|
|
6590
7528
|
if (isClickable(target)) {
|
|
6591
7529
|
let label = '';
|
|
6592
7530
|
if (target instanceof HTMLElement) {
|
|
6593
7531
|
label = app.sanitizer.getInnerTextSecure(target);
|
|
6594
7532
|
}
|
|
6595
|
-
label = label || target.id || target.className;
|
|
6596
7533
|
return normSpaces(label).slice(0, 100);
|
|
6597
7534
|
}
|
|
6598
7535
|
return '';
|
|
@@ -6645,7 +7582,13 @@ function Mouse (app, options) {
|
|
|
6645
7582
|
};
|
|
6646
7583
|
const patchDocument = (document, topframe = false) => {
|
|
6647
7584
|
function getSelector(id, target) {
|
|
6648
|
-
|
|
7585
|
+
if (selectorMap[id])
|
|
7586
|
+
return selectorMap[id];
|
|
7587
|
+
const tagMatch = app.tagMatcher.match(target);
|
|
7588
|
+
if (tagMatch) {
|
|
7589
|
+
return (selectorMap[id] = tagMatch.selector);
|
|
7590
|
+
}
|
|
7591
|
+
return (selectorMap[id] = _getSelector(target));
|
|
6649
7592
|
}
|
|
6650
7593
|
const attachListener = topframe
|
|
6651
7594
|
? app.attachEventListener.bind(app) // attached/removed on start/stop
|
|
@@ -6736,6 +7679,11 @@ function getCSSPath(el) {
|
|
|
6736
7679
|
return false;
|
|
6737
7680
|
if (el.id)
|
|
6738
7681
|
return `#${cssEscape(el.id)}`;
|
|
7682
|
+
// if has data attributes - use them as they are more likely to be stable and unique
|
|
7683
|
+
const dataAttr = Array.from(el.attributes).find(attr => attr.name.startsWith('data-'));
|
|
7684
|
+
if (dataAttr) {
|
|
7685
|
+
return `[${dataAttr.name}="${cssEscape(dataAttr.value)}"]`;
|
|
7686
|
+
}
|
|
6739
7687
|
const parts = [];
|
|
6740
7688
|
while (el && el.nodeType === 1 && el !== el.ownerDocument) {
|
|
6741
7689
|
if (el.id) {
|
|
@@ -7076,11 +8024,27 @@ function Scroll (app, insideIframe) {
|
|
|
7076
8024
|
}, 5, false);
|
|
7077
8025
|
}
|
|
7078
8026
|
|
|
8027
|
+
const NativeURL = URL;
|
|
8028
|
+
const defaultUrlSanitizer = (url) => {
|
|
8029
|
+
const hiddenQueryParams = ['jwt', 'password', 'reset-password', 'invitation', 'secret', 'token'];
|
|
8030
|
+
try {
|
|
8031
|
+
const u = new NativeURL(url);
|
|
8032
|
+
u.searchParams.forEach((value, key) => {
|
|
8033
|
+
if (hiddenQueryParams.includes(key.toLowerCase())) {
|
|
8034
|
+
u.searchParams.set(key, '*'.repeat(value.length));
|
|
8035
|
+
}
|
|
8036
|
+
});
|
|
8037
|
+
return u.toString();
|
|
8038
|
+
}
|
|
8039
|
+
catch {
|
|
8040
|
+
return url;
|
|
8041
|
+
}
|
|
8042
|
+
};
|
|
7079
8043
|
function Viewport (app, options) {
|
|
7080
8044
|
let url, width, height;
|
|
7081
8045
|
let navigationStart;
|
|
7082
8046
|
let referrer = document.referrer;
|
|
7083
|
-
const urlSanitizer = options?.urlSanitizer ||
|
|
8047
|
+
const urlSanitizer = options?.urlSanitizer || defaultUrlSanitizer;
|
|
7084
8048
|
const titleSanitizer = options?.titleSanitizer || ((t) => t);
|
|
7085
8049
|
const sendSetPageLocation = app.safe(() => {
|
|
7086
8050
|
const currURL = document.URL;
|
|
@@ -8906,6 +9870,8 @@ function webAnimations(app, options = {}) {
|
|
|
8906
9870
|
const lastKF = anim.effect.getKeyframes().at(-1);
|
|
8907
9871
|
if (!lastKF)
|
|
8908
9872
|
return;
|
|
9873
|
+
if (!el || !(el instanceof Element) || !el.isConnected)
|
|
9874
|
+
return;
|
|
8909
9875
|
const computedStyle = getComputedStyle(el);
|
|
8910
9876
|
const keys = Object.keys(lastKF).filter((p) => !toIgnore.includes(p));
|
|
8911
9877
|
// @ts-ignore
|
|
@@ -9317,7 +10283,7 @@ class ConstantProperties {
|
|
|
9317
10283
|
user_id: this.user_id,
|
|
9318
10284
|
distinct_id: this.deviceId,
|
|
9319
10285
|
sdk_edition: 'web',
|
|
9320
|
-
sdk_version: '7.
|
|
10286
|
+
sdk_version: '7.3.4',
|
|
9321
10287
|
timezone: getUTCOffsetString(),
|
|
9322
10288
|
search_engine: this.searchEngine,
|
|
9323
10289
|
};
|
|
@@ -9705,10 +10671,11 @@ class People {
|
|
|
9705
10671
|
* Creates batches of events, then sends them at intervals.
|
|
9706
10672
|
*/
|
|
9707
10673
|
class Batcher {
|
|
9708
|
-
constructor(backendUrl, getToken, init) {
|
|
10674
|
+
constructor(backendUrl, getToken, init, standalone) {
|
|
9709
10675
|
this.backendUrl = backendUrl;
|
|
9710
10676
|
this.getToken = getToken;
|
|
9711
10677
|
this.init = init;
|
|
10678
|
+
this.standalone = standalone;
|
|
9712
10679
|
this.autosendInterval = 5 * 1000;
|
|
9713
10680
|
this.retryTimeout = 3 * 1000;
|
|
9714
10681
|
this.retryLimit = 3;
|
|
@@ -9718,10 +10685,20 @@ class Batcher {
|
|
|
9718
10685
|
[categories.events]: [],
|
|
9719
10686
|
};
|
|
9720
10687
|
this.intervalId = null;
|
|
10688
|
+
this.stopped = false;
|
|
10689
|
+
this.paused = false;
|
|
10690
|
+
this.onVisibilityChange = () => {
|
|
10691
|
+
this.paused = document.hidden;
|
|
10692
|
+
};
|
|
9721
10693
|
}
|
|
9722
10694
|
getBatches() {
|
|
9723
10695
|
this.batch[categories.people] = this.dedupePeopleEvents();
|
|
9724
|
-
const finalData = {
|
|
10696
|
+
const finalData = {
|
|
10697
|
+
data: {
|
|
10698
|
+
[categories.people]: [...this.batch[categories.people]],
|
|
10699
|
+
[categories.events]: [...this.batch[categories.events]],
|
|
10700
|
+
},
|
|
10701
|
+
};
|
|
9725
10702
|
return finalData;
|
|
9726
10703
|
}
|
|
9727
10704
|
addEvent(event) {
|
|
@@ -9797,6 +10774,9 @@ class Batcher {
|
|
|
9797
10774
|
return Array.from(uniqueEventsByType.values());
|
|
9798
10775
|
}
|
|
9799
10776
|
sendBatch(batch) {
|
|
10777
|
+
if (this.stopped) {
|
|
10778
|
+
return;
|
|
10779
|
+
}
|
|
9800
10780
|
const sentBatch = batch;
|
|
9801
10781
|
let attempts = 0;
|
|
9802
10782
|
const send = () => {
|
|
@@ -9813,11 +10793,27 @@ class Batcher {
|
|
|
9813
10793
|
},
|
|
9814
10794
|
body: JSON.stringify(sentBatch),
|
|
9815
10795
|
})
|
|
9816
|
-
.then((response) => {
|
|
10796
|
+
.then(async (response) => {
|
|
10797
|
+
if (response.status === 401) {
|
|
10798
|
+
const body = await response.json().catch(() => null);
|
|
10799
|
+
if (!this.standalone && body?.error === 'token expired') {
|
|
10800
|
+
this.stop();
|
|
10801
|
+
return;
|
|
10802
|
+
}
|
|
10803
|
+
if (attempts < this.retryLimit) {
|
|
10804
|
+
return this.init().then(() => {
|
|
10805
|
+
send();
|
|
10806
|
+
});
|
|
10807
|
+
}
|
|
10808
|
+
return;
|
|
10809
|
+
}
|
|
9817
10810
|
if (response.status === 403) {
|
|
9818
|
-
this.
|
|
9819
|
-
|
|
9820
|
-
|
|
10811
|
+
if (attempts < this.retryLimit) {
|
|
10812
|
+
return this.init().then(() => {
|
|
10813
|
+
send();
|
|
10814
|
+
});
|
|
10815
|
+
}
|
|
10816
|
+
return;
|
|
9821
10817
|
}
|
|
9822
10818
|
if (!response.ok) {
|
|
9823
10819
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
@@ -9832,8 +10828,15 @@ class Batcher {
|
|
|
9832
10828
|
void send();
|
|
9833
10829
|
}
|
|
9834
10830
|
startAutosend() {
|
|
10831
|
+
if (this.intervalId) {
|
|
10832
|
+
clearInterval(this.intervalId);
|
|
10833
|
+
}
|
|
10834
|
+
this.paused = document.hidden;
|
|
10835
|
+
document.addEventListener('visibilitychange', this.onVisibilityChange);
|
|
9835
10836
|
this.intervalId = setInterval(() => {
|
|
9836
|
-
this.
|
|
10837
|
+
if (!this.paused) {
|
|
10838
|
+
this.flush();
|
|
10839
|
+
}
|
|
9837
10840
|
}, this.autosendInterval);
|
|
9838
10841
|
}
|
|
9839
10842
|
flush() {
|
|
@@ -9853,10 +10856,18 @@ class Batcher {
|
|
|
9853
10856
|
clearInterval(this.intervalId);
|
|
9854
10857
|
this.intervalId = null;
|
|
9855
10858
|
}
|
|
10859
|
+
document.removeEventListener('visibilitychange', this.onVisibilityChange);
|
|
10860
|
+
this.stopped = true;
|
|
10861
|
+
}
|
|
10862
|
+
restart() {
|
|
10863
|
+
this.stopped = false;
|
|
10864
|
+
this.startAutosend();
|
|
9856
10865
|
}
|
|
9857
10866
|
}
|
|
9858
10867
|
|
|
9859
10868
|
const STORAGEKEY = '__or_sdk_analytics_token';
|
|
10869
|
+
const START_ATTEMPT_STORAGE_KEY = '__or_sdk_analytics_start_attempt';
|
|
10870
|
+
const START_ATTEMPT_TTL_MS = 24 * 60 * 60 * 1000;
|
|
9860
10871
|
const normalizeIngestPoint = (ingest) => {
|
|
9861
10872
|
const base = ingest.replace(/\/$/, '');
|
|
9862
10873
|
return base.endsWith('/oxvo') ? base : `${base}/oxvo`;
|
|
@@ -9880,11 +10891,14 @@ class Analytics {
|
|
|
9880
10891
|
constructor(options) {
|
|
9881
10892
|
this.token = null;
|
|
9882
10893
|
this.standalone = false;
|
|
10894
|
+
this.pendingStartAttempt = null;
|
|
9883
10895
|
this._getToken = () => {
|
|
9884
10896
|
if (this.standalone) {
|
|
9885
10897
|
return this.token;
|
|
9886
10898
|
}
|
|
9887
|
-
|
|
10899
|
+
else {
|
|
10900
|
+
return this.getToken();
|
|
10901
|
+
}
|
|
9888
10902
|
};
|
|
9889
10903
|
this._getTimestamp = () => {
|
|
9890
10904
|
if (this.standalone) {
|
|
@@ -9921,9 +10935,14 @@ class Analytics {
|
|
|
9921
10935
|
timezone: defaultFields.timezone,
|
|
9922
10936
|
search_engine: defaultFields.search_engine,
|
|
9923
10937
|
};
|
|
10938
|
+
const attempt = this.startAttempt(JSON.stringify(data));
|
|
9924
10939
|
const resp = await fetch(apiEdp, {
|
|
9925
10940
|
method: 'POST',
|
|
9926
|
-
|
|
10941
|
+
headers: {
|
|
10942
|
+
'Content-Type': 'application/json',
|
|
10943
|
+
'Idempotency-Key': attempt.idempotencyKey,
|
|
10944
|
+
},
|
|
10945
|
+
body: attempt.requestBody,
|
|
9927
10946
|
});
|
|
9928
10947
|
if (!resp.ok) {
|
|
9929
10948
|
throw new Error(`HTTP error! status: ${resp.status}`);
|
|
@@ -9932,12 +10951,26 @@ class Analytics {
|
|
|
9932
10951
|
if (result.token) {
|
|
9933
10952
|
this.token = result.token;
|
|
9934
10953
|
this.sessionStorage.setItem(STORAGEKEY, result.token);
|
|
10954
|
+
this.clearStartAttempt();
|
|
9935
10955
|
}
|
|
9936
10956
|
else {
|
|
9937
10957
|
throw new Error('No token received from server');
|
|
9938
10958
|
}
|
|
9939
10959
|
}
|
|
9940
10960
|
};
|
|
10961
|
+
/**
|
|
10962
|
+
* Used by tracker when running in blundled mode
|
|
10963
|
+
*/
|
|
10964
|
+
this.onStart = () => {
|
|
10965
|
+
if (!this.standalone) {
|
|
10966
|
+
this.batcher.restart();
|
|
10967
|
+
}
|
|
10968
|
+
};
|
|
10969
|
+
this.onStop = () => {
|
|
10970
|
+
if (!this.standalone) {
|
|
10971
|
+
this.batcher.stop();
|
|
10972
|
+
}
|
|
10973
|
+
};
|
|
9941
10974
|
this.reset = () => {
|
|
9942
10975
|
this.people.reset(true);
|
|
9943
10976
|
this.events.reset();
|
|
@@ -9945,6 +10978,7 @@ class Analytics {
|
|
|
9945
10978
|
if (this.standalone) {
|
|
9946
10979
|
this.token = null;
|
|
9947
10980
|
this.sessionStorage.setItem(STORAGEKEY, '');
|
|
10981
|
+
this.clearStartAttempt();
|
|
9948
10982
|
}
|
|
9949
10983
|
};
|
|
9950
10984
|
/**
|
|
@@ -9976,13 +11010,98 @@ class Analytics {
|
|
|
9976
11010
|
this.standalone = !options.notStandalone;
|
|
9977
11011
|
this.token = this.sessionStorage.getItem(STORAGEKEY);
|
|
9978
11012
|
this.constantProperties = new ConstantProperties(this.localStorage, this.sessionStorage);
|
|
9979
|
-
this.batcher = new Batcher(this.backendUrl, this._getToken, this.init);
|
|
11013
|
+
this.batcher = new Batcher(this.backendUrl, this._getToken, this.init, this.standalone);
|
|
9980
11014
|
this.events = new Events(this.constantProperties, this._getTimestamp, this.batcher);
|
|
9981
11015
|
this.people = new People(this.constantProperties, this._getTimestamp, this.setUserId, this.batcher);
|
|
9982
11016
|
if (options.notStandalone) {
|
|
9983
11017
|
this.init();
|
|
9984
11018
|
}
|
|
9985
11019
|
}
|
|
11020
|
+
loadStartAttempt() {
|
|
11021
|
+
if (this.isValidStartAttempt(this.pendingStartAttempt)) {
|
|
11022
|
+
return this.pendingStartAttempt;
|
|
11023
|
+
}
|
|
11024
|
+
this.pendingStartAttempt = null;
|
|
11025
|
+
let serialized;
|
|
11026
|
+
try {
|
|
11027
|
+
serialized = this.sessionStorage.getItem(START_ATTEMPT_STORAGE_KEY);
|
|
11028
|
+
}
|
|
11029
|
+
catch {
|
|
11030
|
+
return null;
|
|
11031
|
+
}
|
|
11032
|
+
if (!serialized || serialized.length > 1024 * 1024) {
|
|
11033
|
+
return null;
|
|
11034
|
+
}
|
|
11035
|
+
try {
|
|
11036
|
+
const attempt = JSON.parse(serialized);
|
|
11037
|
+
if (this.isValidStartAttempt(attempt)) {
|
|
11038
|
+
this.pendingStartAttempt = attempt;
|
|
11039
|
+
return attempt;
|
|
11040
|
+
}
|
|
11041
|
+
}
|
|
11042
|
+
catch {
|
|
11043
|
+
// Treat corrupt storage as an absent pending attempt.
|
|
11044
|
+
}
|
|
11045
|
+
return null;
|
|
11046
|
+
}
|
|
11047
|
+
isValidStartAttempt(value) {
|
|
11048
|
+
if (typeof value !== 'object' || value === null) {
|
|
11049
|
+
return false;
|
|
11050
|
+
}
|
|
11051
|
+
const attempt = value;
|
|
11052
|
+
const currentTime = Date.now();
|
|
11053
|
+
if (attempt.version !== 1 ||
|
|
11054
|
+
attempt.projectKey !== this.projectKey ||
|
|
11055
|
+
typeof attempt.createdAt !== 'number' ||
|
|
11056
|
+
!Number.isFinite(attempt.createdAt) ||
|
|
11057
|
+
attempt.createdAt > currentTime ||
|
|
11058
|
+
currentTime - attempt.createdAt >= START_ATTEMPT_TTL_MS ||
|
|
11059
|
+
typeof attempt.idempotencyKey !== 'string' ||
|
|
11060
|
+
attempt.idempotencyKey.length < 8 ||
|
|
11061
|
+
attempt.idempotencyKey.length > 128 ||
|
|
11062
|
+
!/^[A-Za-z0-9._:-]+$/.test(attempt.idempotencyKey) ||
|
|
11063
|
+
typeof attempt.requestBody !== 'string' ||
|
|
11064
|
+
attempt.requestBody.length > 1024 * 1024) {
|
|
11065
|
+
return false;
|
|
11066
|
+
}
|
|
11067
|
+
try {
|
|
11068
|
+
const request = JSON.parse(attempt.requestBody);
|
|
11069
|
+
return request.projectKey === this.projectKey;
|
|
11070
|
+
}
|
|
11071
|
+
catch {
|
|
11072
|
+
return false;
|
|
11073
|
+
}
|
|
11074
|
+
}
|
|
11075
|
+
startAttempt(requestBody) {
|
|
11076
|
+
const existing = this.loadStartAttempt();
|
|
11077
|
+
if (existing) {
|
|
11078
|
+
return existing;
|
|
11079
|
+
}
|
|
11080
|
+
const attempt = {
|
|
11081
|
+
version: 1,
|
|
11082
|
+
projectKey: this.projectKey,
|
|
11083
|
+
createdAt: Date.now(),
|
|
11084
|
+
idempotencyKey: `insights:start:${generateRandomId(40)}`,
|
|
11085
|
+
requestBody,
|
|
11086
|
+
};
|
|
11087
|
+
this.pendingStartAttempt = attempt;
|
|
11088
|
+
try {
|
|
11089
|
+
this.sessionStorage.setItem(START_ATTEMPT_STORAGE_KEY, JSON.stringify(attempt));
|
|
11090
|
+
}
|
|
11091
|
+
catch {
|
|
11092
|
+
// The in-memory copy still protects retries within this Analytics instance.
|
|
11093
|
+
}
|
|
11094
|
+
return attempt;
|
|
11095
|
+
}
|
|
11096
|
+
clearStartAttempt() {
|
|
11097
|
+
this.pendingStartAttempt = null;
|
|
11098
|
+
try {
|
|
11099
|
+
this.sessionStorage.setItem(START_ATTEMPT_STORAGE_KEY, '');
|
|
11100
|
+
}
|
|
11101
|
+
catch {
|
|
11102
|
+
// The in-memory copy is still cleared when storage is unavailable.
|
|
11103
|
+
}
|
|
11104
|
+
}
|
|
9986
11105
|
}
|
|
9987
11106
|
|
|
9988
11107
|
const Messages = _Messages;
|
|
@@ -10029,7 +11148,7 @@ class API {
|
|
|
10029
11148
|
this.signalStartIssue = (reason, missingApi) => {
|
|
10030
11149
|
const doNotTrack = this.checkDoNotTrack();
|
|
10031
11150
|
console.log("Tracker couldn't start due to:", JSON.stringify({
|
|
10032
|
-
trackerVersion: '7.
|
|
11151
|
+
trackerVersion: '7.3.4',
|
|
10033
11152
|
siteKey: this.options.siteKey,
|
|
10034
11153
|
doNotTrack,
|
|
10035
11154
|
reason: missingApi.length ? `missing api: ${missingApi.join(',')}` : reason,
|
|
@@ -10041,6 +11160,31 @@ class API {
|
|
|
10041
11160
|
}
|
|
10042
11161
|
this.app.restartCanvasTracking();
|
|
10043
11162
|
};
|
|
11163
|
+
/**
|
|
11164
|
+
* Re-evaluates sanitization against the current DOM and re-emits whatever
|
|
11165
|
+
* changed, updating already-recorded nodes mid-session. Call after toggling
|
|
11166
|
+
* `data-oxvosessions-*` attributes or after changing whatever your `domSanitizer`
|
|
11167
|
+
* keys on (class/id/etc).
|
|
11168
|
+
*
|
|
11169
|
+
* @param el - the highest node you changed; omit to re-scan the whole document;
|
|
11170
|
+
* scanning the entire doc is O(dom size)
|
|
11171
|
+
* */
|
|
11172
|
+
this.resanitize = (el) => {
|
|
11173
|
+
if (this.app === null) {
|
|
11174
|
+
return;
|
|
11175
|
+
}
|
|
11176
|
+
this.app.resanitize(el);
|
|
11177
|
+
};
|
|
11178
|
+
/**
|
|
11179
|
+
* Returns the sanitization level the tracker currently has for a node
|
|
11180
|
+
* (0 = Plain, 1 = Obscured, 2 = Hidden), or undefined if it isn't tracked.
|
|
11181
|
+
* */
|
|
11182
|
+
this.checkSanitization = (el) => {
|
|
11183
|
+
if (this.app === null) {
|
|
11184
|
+
return undefined;
|
|
11185
|
+
}
|
|
11186
|
+
return this.app.checkSanitization(el);
|
|
11187
|
+
};
|
|
10044
11188
|
this.getSessionLink = (options) => {
|
|
10045
11189
|
if (this.app === null) {
|
|
10046
11190
|
return undefined;
|
|
@@ -10183,6 +11327,12 @@ class API {
|
|
|
10183
11327
|
: (options.analytics?.endpoint ?? options.endpoint ?? defaultEdp),
|
|
10184
11328
|
projectKey: options.siteKey,
|
|
10185
11329
|
});
|
|
11330
|
+
app.attachStartCallback(() => {
|
|
11331
|
+
this.analytics?.onStart();
|
|
11332
|
+
});
|
|
11333
|
+
app.attachStopCallback(() => {
|
|
11334
|
+
this.analytics?.onStop();
|
|
11335
|
+
});
|
|
10186
11336
|
}
|
|
10187
11337
|
if (!this.crossdomainMode) {
|
|
10188
11338
|
// no need to send iframe viewport data since its a node for us
|
|
@@ -10194,7 +11344,7 @@ class API {
|
|
|
10194
11344
|
// no tabs in iframes yet
|
|
10195
11345
|
Tabs(app);
|
|
10196
11346
|
}
|
|
10197
|
-
Mouse(app, options.mouse);
|
|
11347
|
+
Mouse(app, { ...options.mouse, customAttributes: options.customAttributes });
|
|
10198
11348
|
// inside iframe, we ignore viewport scroll
|
|
10199
11349
|
Scroll(app, this.crossdomainMode);
|
|
10200
11350
|
CSSRules(app, options.css);
|
|
@@ -10461,11 +11611,13 @@ class BrowserSingleton {
|
|
|
10461
11611
|
if (!IN_BROWSER) {
|
|
10462
11612
|
return Promise.resolve({ success: false, reason: 'Not in browser environment' });
|
|
10463
11613
|
}
|
|
10464
|
-
if (!this.ensureConfigured()) {
|
|
11614
|
+
if (!this.ensureConfigured() || !this.instance) {
|
|
10465
11615
|
return Promise.resolve({ success: false, reason: 'Browser not configured' });
|
|
10466
11616
|
}
|
|
10467
|
-
return
|
|
10468
|
-
|
|
11617
|
+
return this.instance.start(startOpts).catch((reason) => ({
|
|
11618
|
+
success: false,
|
|
11619
|
+
reason: typeof reason === 'string' ? reason : String(reason),
|
|
11620
|
+
}));
|
|
10469
11621
|
}
|
|
10470
11622
|
/**
|
|
10471
11623
|
* Stop the session and return sessionHash
|
|
@@ -10484,14 +11636,8 @@ class BrowserSingleton {
|
|
|
10484
11636
|
this.instance.identify(id, properties);
|
|
10485
11637
|
}
|
|
10486
11638
|
get analytics() {
|
|
10487
|
-
|
|
10488
|
-
return this.instance.analytics;
|
|
10489
|
-
}
|
|
10490
|
-
else {
|
|
10491
|
-
return null;
|
|
10492
|
-
}
|
|
11639
|
+
return this.instance?.analytics ?? null;
|
|
10493
11640
|
}
|
|
10494
|
-
;
|
|
10495
11641
|
/**
|
|
10496
11642
|
* Set metadata for the current session
|
|
10497
11643
|
*
|
|
@@ -10666,6 +11812,51 @@ class BrowserSingleton {
|
|
|
10666
11812
|
}
|
|
10667
11813
|
return this.instance.getTabId();
|
|
10668
11814
|
}
|
|
11815
|
+
/**
|
|
11816
|
+
* Re-evaluates sanitization against the current DOM and re-emits whatever
|
|
11817
|
+
* changed, updating already-recorded nodes mid-session. Call after toggling
|
|
11818
|
+
* `data-oxvosessions-*` attributes or after changing whatever your `domSanitizer`
|
|
11819
|
+
* keys on (class/id/etc).
|
|
11820
|
+
*
|
|
11821
|
+
* @param el - the highest node you changed; omit to re-scan the whole document.
|
|
11822
|
+
* */
|
|
11823
|
+
resanitize(el) {
|
|
11824
|
+
if (!IN_BROWSER || !this.ensureConfigured() || !this.instance) {
|
|
11825
|
+
return;
|
|
11826
|
+
}
|
|
11827
|
+
return this.instance.resanitize(el);
|
|
11828
|
+
}
|
|
11829
|
+
/**
|
|
11830
|
+
* Returns the sanitization level the tracker currently has for a node
|
|
11831
|
+
* (0 = Plain, 1 = Obscured, 2 = Hidden), or undefined if it isn't tracked.
|
|
11832
|
+
* */
|
|
11833
|
+
checkSanitization(el) {
|
|
11834
|
+
if (!IN_BROWSER || !this.ensureConfigured() || !this.instance) {
|
|
11835
|
+
return undefined;
|
|
11836
|
+
}
|
|
11837
|
+
return this.instance.checkSanitization(el);
|
|
11838
|
+
}
|
|
11839
|
+
incident(options) {
|
|
11840
|
+
if (!IN_BROWSER || !this.ensureConfigured() || !this.instance) {
|
|
11841
|
+
return;
|
|
11842
|
+
}
|
|
11843
|
+
this.instance.incident(options);
|
|
11844
|
+
}
|
|
11845
|
+
/**
|
|
11846
|
+
* Use custom token for analytics events without session recording.
|
|
11847
|
+
* */
|
|
11848
|
+
setAnalyticsToken(token) {
|
|
11849
|
+
if (!IN_BROWSER || !this.ensureConfigured() || !this.instance) {
|
|
11850
|
+
return;
|
|
11851
|
+
}
|
|
11852
|
+
this.instance.setAnalyticsToken(token);
|
|
11853
|
+
}
|
|
11854
|
+
getAnalyticsToken() {
|
|
11855
|
+
if (!IN_BROWSER || !this.ensureConfigured() || !this.instance) {
|
|
11856
|
+
return undefined;
|
|
11857
|
+
}
|
|
11858
|
+
return this.instance.getAnalyticsToken();
|
|
11859
|
+
}
|
|
10669
11860
|
}
|
|
10670
11861
|
const browser = new BrowserSingleton();
|
|
10671
11862
|
|