@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/lib/index.js
CHANGED
|
@@ -1,716 +1,3 @@
|
|
|
1
|
-
// DEFLATE is a complex format; to read this code, you should probably check the RFC first:
|
|
2
|
-
// https://tools.ietf.org/html/rfc1951
|
|
3
|
-
// You may also wish to take a look at the guide I made about this program:
|
|
4
|
-
// https://gist.github.com/101arrowz/253f31eb5abc3d9275ab943003ffecad
|
|
5
|
-
// Some of the following code is similar to that of UZIP.js:
|
|
6
|
-
// https://github.com/photopea/UZIP.js
|
|
7
|
-
// However, the vast majority of the codebase has diverged from UZIP.js to increase performance and reduce bundle size.
|
|
8
|
-
// Sometimes 0 will appear where -1 would be more appropriate. This is because using a uint
|
|
9
|
-
// is better for memory in most engines (I *think*).
|
|
10
|
-
var ch2 = {};
|
|
11
|
-
var wk = (function (c, id, msg, transfer, cb) {
|
|
12
|
-
var w = new Worker(ch2[id] || (ch2[id] = URL.createObjectURL(new Blob([
|
|
13
|
-
c + ';addEventListener("error",function(e){e=e.error;postMessage({$e$:[e.message,e.code,e.stack]})})'
|
|
14
|
-
], { type: 'text/javascript' }))));
|
|
15
|
-
w.onmessage = function (e) {
|
|
16
|
-
var d = e.data, ed = d.$e$;
|
|
17
|
-
if (ed) {
|
|
18
|
-
var err = new Error(ed[0]);
|
|
19
|
-
err['code'] = ed[1];
|
|
20
|
-
err.stack = ed[2];
|
|
21
|
-
cb(err, null);
|
|
22
|
-
}
|
|
23
|
-
else
|
|
24
|
-
cb(null, d);
|
|
25
|
-
};
|
|
26
|
-
w.postMessage(msg, transfer);
|
|
27
|
-
return w;
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
// aliases for shorter compressed code (most minifers don't do this)
|
|
31
|
-
var u8 = Uint8Array, u16 = Uint16Array, i32 = Int32Array;
|
|
32
|
-
// fixed length extra bits
|
|
33
|
-
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]);
|
|
34
|
-
// fixed distance extra bits
|
|
35
|
-
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]);
|
|
36
|
-
// code length index map
|
|
37
|
-
var clim = new u8([16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]);
|
|
38
|
-
// get base, reverse index map from extra bits
|
|
39
|
-
var freb = function (eb, start) {
|
|
40
|
-
var b = new u16(31);
|
|
41
|
-
for (var i = 0; i < 31; ++i) {
|
|
42
|
-
b[i] = start += 1 << eb[i - 1];
|
|
43
|
-
}
|
|
44
|
-
// numbers here are at max 18 bits
|
|
45
|
-
var r = new i32(b[30]);
|
|
46
|
-
for (var i = 1; i < 30; ++i) {
|
|
47
|
-
for (var j = b[i]; j < b[i + 1]; ++j) {
|
|
48
|
-
r[j] = ((j - b[i]) << 5) | i;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
return { b: b, r: r };
|
|
52
|
-
};
|
|
53
|
-
var _a = freb(fleb, 2), fl = _a.b, revfl = _a.r;
|
|
54
|
-
// we can ignore the fact that the other numbers are wrong; they never happen anyway
|
|
55
|
-
fl[28] = 258, revfl[258] = 28;
|
|
56
|
-
var _b = freb(fdeb, 0), revfd = _b.r;
|
|
57
|
-
// map of value to reverse (assuming 16 bits)
|
|
58
|
-
var rev = new u16(32768);
|
|
59
|
-
for (var i$1 = 0; i$1 < 32768; ++i$1) {
|
|
60
|
-
// reverse table algorithm from SO
|
|
61
|
-
var x$1 = ((i$1 & 0xAAAA) >> 1) | ((i$1 & 0x5555) << 1);
|
|
62
|
-
x$1 = ((x$1 & 0xCCCC) >> 2) | ((x$1 & 0x3333) << 2);
|
|
63
|
-
x$1 = ((x$1 & 0xF0F0) >> 4) | ((x$1 & 0x0F0F) << 4);
|
|
64
|
-
rev[i$1] = (((x$1 & 0xFF00) >> 8) | ((x$1 & 0x00FF) << 8)) >> 1;
|
|
65
|
-
}
|
|
66
|
-
// create huffman tree from u8 "map": index -> code length for code index
|
|
67
|
-
// mb (max bits) must be at most 15
|
|
68
|
-
// TODO: optimize/split up?
|
|
69
|
-
var hMap = (function (cd, mb, r) {
|
|
70
|
-
var s = cd.length;
|
|
71
|
-
// index
|
|
72
|
-
var i = 0;
|
|
73
|
-
// u16 "map": index -> # of codes with bit length = index
|
|
74
|
-
var l = new u16(mb);
|
|
75
|
-
// length of cd must be 288 (total # of codes)
|
|
76
|
-
for (; i < s; ++i) {
|
|
77
|
-
if (cd[i])
|
|
78
|
-
++l[cd[i] - 1];
|
|
79
|
-
}
|
|
80
|
-
// u16 "map": index -> minimum code for bit length = index
|
|
81
|
-
var le = new u16(mb);
|
|
82
|
-
for (i = 1; i < mb; ++i) {
|
|
83
|
-
le[i] = (le[i - 1] + l[i - 1]) << 1;
|
|
84
|
-
}
|
|
85
|
-
var co;
|
|
86
|
-
if (r) {
|
|
87
|
-
// u16 "map": index -> number of actual bits, symbol for code
|
|
88
|
-
co = new u16(1 << mb);
|
|
89
|
-
// bits to remove for reverser
|
|
90
|
-
var rvb = 15 - mb;
|
|
91
|
-
for (i = 0; i < s; ++i) {
|
|
92
|
-
// ignore 0 lengths
|
|
93
|
-
if (cd[i]) {
|
|
94
|
-
// num encoding both symbol and bits read
|
|
95
|
-
var sv = (i << 4) | cd[i];
|
|
96
|
-
// free bits
|
|
97
|
-
var r_1 = mb - cd[i];
|
|
98
|
-
// start value
|
|
99
|
-
var v = le[cd[i] - 1]++ << r_1;
|
|
100
|
-
// m is end value
|
|
101
|
-
for (var m = v | ((1 << r_1) - 1); v <= m; ++v) {
|
|
102
|
-
// every 16 bit value starting with the code yields the same result
|
|
103
|
-
co[rev[v] >> rvb] = sv;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
else {
|
|
109
|
-
co = new u16(s);
|
|
110
|
-
for (i = 0; i < s; ++i) {
|
|
111
|
-
if (cd[i]) {
|
|
112
|
-
co[i] = rev[le[cd[i] - 1]++] >> (15 - cd[i]);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
return co;
|
|
117
|
-
});
|
|
118
|
-
// fixed length tree
|
|
119
|
-
var flt = new u8(288);
|
|
120
|
-
for (var i$1 = 0; i$1 < 144; ++i$1)
|
|
121
|
-
flt[i$1] = 8;
|
|
122
|
-
for (var i$1 = 144; i$1 < 256; ++i$1)
|
|
123
|
-
flt[i$1] = 9;
|
|
124
|
-
for (var i$1 = 256; i$1 < 280; ++i$1)
|
|
125
|
-
flt[i$1] = 7;
|
|
126
|
-
for (var i$1 = 280; i$1 < 288; ++i$1)
|
|
127
|
-
flt[i$1] = 8;
|
|
128
|
-
// fixed distance tree
|
|
129
|
-
var fdt = new u8(32);
|
|
130
|
-
for (var i$1 = 0; i$1 < 32; ++i$1)
|
|
131
|
-
fdt[i$1] = 5;
|
|
132
|
-
// fixed length map
|
|
133
|
-
var flm = /*#__PURE__*/ hMap(flt, 9, 0);
|
|
134
|
-
// fixed distance map
|
|
135
|
-
var fdm = /*#__PURE__*/ hMap(fdt, 5, 0);
|
|
136
|
-
// get end of byte
|
|
137
|
-
var shft = function (p) { return ((p + 7) / 8) | 0; };
|
|
138
|
-
// typed array slice - allows garbage collector to free original reference,
|
|
139
|
-
// while being more compatible than .slice
|
|
140
|
-
var slc = function (v, s, e) {
|
|
141
|
-
if (s == null || s < 0)
|
|
142
|
-
s = 0;
|
|
143
|
-
if (e == null || e > v.length)
|
|
144
|
-
e = v.length;
|
|
145
|
-
// can't use .constructor in case user-supplied
|
|
146
|
-
return new u8(v.subarray(s, e));
|
|
147
|
-
};
|
|
148
|
-
// error codes
|
|
149
|
-
var ec = [
|
|
150
|
-
'unexpected EOF',
|
|
151
|
-
'invalid block type',
|
|
152
|
-
'invalid length/literal',
|
|
153
|
-
'invalid distance',
|
|
154
|
-
'stream finished',
|
|
155
|
-
'no stream handler',
|
|
156
|
-
,
|
|
157
|
-
'no callback',
|
|
158
|
-
'invalid UTF-8 data',
|
|
159
|
-
'extra field too long',
|
|
160
|
-
'date not in range 1980-2099',
|
|
161
|
-
'filename too long',
|
|
162
|
-
'stream finishing',
|
|
163
|
-
'invalid zip data'
|
|
164
|
-
// determined by unknown compression method
|
|
165
|
-
];
|
|
166
|
-
var err = function (ind, msg, nt) {
|
|
167
|
-
var e = new Error(msg || ec[ind]);
|
|
168
|
-
e.code = ind;
|
|
169
|
-
if (Error.captureStackTrace)
|
|
170
|
-
Error.captureStackTrace(e, err);
|
|
171
|
-
if (!nt)
|
|
172
|
-
throw e;
|
|
173
|
-
return e;
|
|
174
|
-
};
|
|
175
|
-
// starting at p, write the minimum number of bits that can hold v to d
|
|
176
|
-
var wbits = function (d, p, v) {
|
|
177
|
-
v <<= p & 7;
|
|
178
|
-
var o = (p / 8) | 0;
|
|
179
|
-
d[o] |= v;
|
|
180
|
-
d[o + 1] |= v >> 8;
|
|
181
|
-
};
|
|
182
|
-
// starting at p, write the minimum number of bits (>8) that can hold v to d
|
|
183
|
-
var wbits16 = function (d, p, v) {
|
|
184
|
-
v <<= p & 7;
|
|
185
|
-
var o = (p / 8) | 0;
|
|
186
|
-
d[o] |= v;
|
|
187
|
-
d[o + 1] |= v >> 8;
|
|
188
|
-
d[o + 2] |= v >> 16;
|
|
189
|
-
};
|
|
190
|
-
// creates code lengths from a frequency table
|
|
191
|
-
var hTree = function (d, mb) {
|
|
192
|
-
// Need extra info to make a tree
|
|
193
|
-
var t = [];
|
|
194
|
-
for (var i = 0; i < d.length; ++i) {
|
|
195
|
-
if (d[i])
|
|
196
|
-
t.push({ s: i, f: d[i] });
|
|
197
|
-
}
|
|
198
|
-
var s = t.length;
|
|
199
|
-
var t2 = t.slice();
|
|
200
|
-
if (!s)
|
|
201
|
-
return { t: et, l: 0 };
|
|
202
|
-
if (s == 1) {
|
|
203
|
-
var v = new u8(t[0].s + 1);
|
|
204
|
-
v[t[0].s] = 1;
|
|
205
|
-
return { t: v, l: 1 };
|
|
206
|
-
}
|
|
207
|
-
t.sort(function (a, b) { return a.f - b.f; });
|
|
208
|
-
// after i2 reaches last ind, will be stopped
|
|
209
|
-
// freq must be greater than largest possible number of symbols
|
|
210
|
-
t.push({ s: -1, f: 25001 });
|
|
211
|
-
var l = t[0], r = t[1], i0 = 0, i1 = 1, i2 = 2;
|
|
212
|
-
t[0] = { s: -1, f: l.f + r.f, l: l, r: r };
|
|
213
|
-
// efficient algorithm from UZIP.js
|
|
214
|
-
// i0 is lookbehind, i2 is lookahead - after processing two low-freq
|
|
215
|
-
// symbols that combined have high freq, will start processing i2 (high-freq,
|
|
216
|
-
// non-composite) symbols instead
|
|
217
|
-
// see https://reddit.com/r/photopea/comments/ikekht/uzipjs_questions/
|
|
218
|
-
while (i1 != s - 1) {
|
|
219
|
-
l = t[t[i0].f < t[i2].f ? i0++ : i2++];
|
|
220
|
-
r = t[i0 != i1 && t[i0].f < t[i2].f ? i0++ : i2++];
|
|
221
|
-
t[i1++] = { s: -1, f: l.f + r.f, l: l, r: r };
|
|
222
|
-
}
|
|
223
|
-
var maxSym = t2[0].s;
|
|
224
|
-
for (var i = 1; i < s; ++i) {
|
|
225
|
-
if (t2[i].s > maxSym)
|
|
226
|
-
maxSym = t2[i].s;
|
|
227
|
-
}
|
|
228
|
-
// code lengths
|
|
229
|
-
var tr = new u16(maxSym + 1);
|
|
230
|
-
// max bits in tree
|
|
231
|
-
var mbt = ln(t[i1 - 1], tr, 0);
|
|
232
|
-
if (mbt > mb) {
|
|
233
|
-
// more algorithms from UZIP.js
|
|
234
|
-
// TODO: find out how this code works (debt)
|
|
235
|
-
// ind debt
|
|
236
|
-
var i = 0, dt = 0;
|
|
237
|
-
// left cost
|
|
238
|
-
var lft = mbt - mb, cst = 1 << lft;
|
|
239
|
-
t2.sort(function (a, b) { return tr[b.s] - tr[a.s] || a.f - b.f; });
|
|
240
|
-
for (; i < s; ++i) {
|
|
241
|
-
var i2_1 = t2[i].s;
|
|
242
|
-
if (tr[i2_1] > mb) {
|
|
243
|
-
dt += cst - (1 << (mbt - tr[i2_1]));
|
|
244
|
-
tr[i2_1] = mb;
|
|
245
|
-
}
|
|
246
|
-
else
|
|
247
|
-
break;
|
|
248
|
-
}
|
|
249
|
-
dt >>= lft;
|
|
250
|
-
while (dt > 0) {
|
|
251
|
-
var i2_2 = t2[i].s;
|
|
252
|
-
if (tr[i2_2] < mb)
|
|
253
|
-
dt -= 1 << (mb - tr[i2_2]++ - 1);
|
|
254
|
-
else
|
|
255
|
-
++i;
|
|
256
|
-
}
|
|
257
|
-
for (; i >= 0 && dt; --i) {
|
|
258
|
-
var i2_3 = t2[i].s;
|
|
259
|
-
if (tr[i2_3] == mb) {
|
|
260
|
-
--tr[i2_3];
|
|
261
|
-
++dt;
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
mbt = mb;
|
|
265
|
-
}
|
|
266
|
-
return { t: new u8(tr), l: mbt };
|
|
267
|
-
};
|
|
268
|
-
// get the max length and assign length codes
|
|
269
|
-
var ln = function (n, l, d) {
|
|
270
|
-
return n.s == -1
|
|
271
|
-
? Math.max(ln(n.l, l, d + 1), ln(n.r, l, d + 1))
|
|
272
|
-
: (l[n.s] = d);
|
|
273
|
-
};
|
|
274
|
-
// length codes generation
|
|
275
|
-
var lc = function (c) {
|
|
276
|
-
var s = c.length;
|
|
277
|
-
// Note that the semicolon was intentional
|
|
278
|
-
while (s && !c[--s])
|
|
279
|
-
;
|
|
280
|
-
var cl = new u16(++s);
|
|
281
|
-
// ind num streak
|
|
282
|
-
var cli = 0, cln = c[0], cls = 1;
|
|
283
|
-
var w = function (v) { cl[cli++] = v; };
|
|
284
|
-
for (var i = 1; i <= s; ++i) {
|
|
285
|
-
if (c[i] == cln && i != s)
|
|
286
|
-
++cls;
|
|
287
|
-
else {
|
|
288
|
-
if (!cln && cls > 2) {
|
|
289
|
-
for (; cls > 138; cls -= 138)
|
|
290
|
-
w(32754);
|
|
291
|
-
if (cls > 2) {
|
|
292
|
-
w(cls > 10 ? ((cls - 11) << 5) | 28690 : ((cls - 3) << 5) | 12305);
|
|
293
|
-
cls = 0;
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
else if (cls > 3) {
|
|
297
|
-
w(cln), --cls;
|
|
298
|
-
for (; cls > 6; cls -= 6)
|
|
299
|
-
w(8304);
|
|
300
|
-
if (cls > 2)
|
|
301
|
-
w(((cls - 3) << 5) | 8208), cls = 0;
|
|
302
|
-
}
|
|
303
|
-
while (cls--)
|
|
304
|
-
w(cln);
|
|
305
|
-
cls = 1;
|
|
306
|
-
cln = c[i];
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
return { c: cl.subarray(0, cli), n: s };
|
|
310
|
-
};
|
|
311
|
-
// calculate the length of output from tree, code lengths
|
|
312
|
-
var clen = function (cf, cl) {
|
|
313
|
-
var l = 0;
|
|
314
|
-
for (var i = 0; i < cl.length; ++i)
|
|
315
|
-
l += cf[i] * cl[i];
|
|
316
|
-
return l;
|
|
317
|
-
};
|
|
318
|
-
// writes a fixed block
|
|
319
|
-
// returns the new bit pos
|
|
320
|
-
var wfblk = function (out, pos, dat) {
|
|
321
|
-
// no need to write 00 as type: TypedArray defaults to 0
|
|
322
|
-
var s = dat.length;
|
|
323
|
-
var o = shft(pos + 2);
|
|
324
|
-
out[o] = s & 255;
|
|
325
|
-
out[o + 1] = s >> 8;
|
|
326
|
-
out[o + 2] = out[o] ^ 255;
|
|
327
|
-
out[o + 3] = out[o + 1] ^ 255;
|
|
328
|
-
for (var i = 0; i < s; ++i)
|
|
329
|
-
out[o + i + 4] = dat[i];
|
|
330
|
-
return (o + 4 + s) * 8;
|
|
331
|
-
};
|
|
332
|
-
// writes a block
|
|
333
|
-
var wblk = function (dat, out, final, syms, lf, df, eb, li, bs, bl, p) {
|
|
334
|
-
wbits(out, p++, final);
|
|
335
|
-
++lf[256];
|
|
336
|
-
var _a = hTree(lf, 15), dlt = _a.t, mlb = _a.l;
|
|
337
|
-
var _b = hTree(df, 15), ddt = _b.t, mdb = _b.l;
|
|
338
|
-
var _c = lc(dlt), lclt = _c.c, nlc = _c.n;
|
|
339
|
-
var _d = lc(ddt), lcdt = _d.c, ndc = _d.n;
|
|
340
|
-
var lcfreq = new u16(19);
|
|
341
|
-
for (var i = 0; i < lclt.length; ++i)
|
|
342
|
-
++lcfreq[lclt[i] & 31];
|
|
343
|
-
for (var i = 0; i < lcdt.length; ++i)
|
|
344
|
-
++lcfreq[lcdt[i] & 31];
|
|
345
|
-
var _e = hTree(lcfreq, 7), lct = _e.t, mlcb = _e.l;
|
|
346
|
-
var nlcc = 19;
|
|
347
|
-
for (; nlcc > 4 && !lct[clim[nlcc - 1]]; --nlcc)
|
|
348
|
-
;
|
|
349
|
-
var flen = (bl + 5) << 3;
|
|
350
|
-
var ftlen = clen(lf, flt) + clen(df, fdt) + eb;
|
|
351
|
-
var dtlen = clen(lf, dlt) + clen(df, ddt) + eb + 14 + 3 * nlcc + clen(lcfreq, lct) + 2 * lcfreq[16] + 3 * lcfreq[17] + 7 * lcfreq[18];
|
|
352
|
-
if (bs >= 0 && flen <= ftlen && flen <= dtlen)
|
|
353
|
-
return wfblk(out, p, dat.subarray(bs, bs + bl));
|
|
354
|
-
var lm, ll, dm, dl;
|
|
355
|
-
wbits(out, p, 1 + (dtlen < ftlen)), p += 2;
|
|
356
|
-
if (dtlen < ftlen) {
|
|
357
|
-
lm = hMap(dlt, mlb, 0), ll = dlt, dm = hMap(ddt, mdb, 0), dl = ddt;
|
|
358
|
-
var llm = hMap(lct, mlcb, 0);
|
|
359
|
-
wbits(out, p, nlc - 257);
|
|
360
|
-
wbits(out, p + 5, ndc - 1);
|
|
361
|
-
wbits(out, p + 10, nlcc - 4);
|
|
362
|
-
p += 14;
|
|
363
|
-
for (var i = 0; i < nlcc; ++i)
|
|
364
|
-
wbits(out, p + 3 * i, lct[clim[i]]);
|
|
365
|
-
p += 3 * nlcc;
|
|
366
|
-
var lcts = [lclt, lcdt];
|
|
367
|
-
for (var it = 0; it < 2; ++it) {
|
|
368
|
-
var clct = lcts[it];
|
|
369
|
-
for (var i = 0; i < clct.length; ++i) {
|
|
370
|
-
var len = clct[i] & 31;
|
|
371
|
-
wbits(out, p, llm[len]), p += lct[len];
|
|
372
|
-
if (len > 15)
|
|
373
|
-
wbits(out, p, (clct[i] >> 5) & 127), p += clct[i] >> 12;
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
else {
|
|
378
|
-
lm = flm, ll = flt, dm = fdm, dl = fdt;
|
|
379
|
-
}
|
|
380
|
-
for (var i = 0; i < li; ++i) {
|
|
381
|
-
var sym = syms[i];
|
|
382
|
-
if (sym > 255) {
|
|
383
|
-
var len = (sym >> 18) & 31;
|
|
384
|
-
wbits16(out, p, lm[len + 257]), p += ll[len + 257];
|
|
385
|
-
if (len > 7)
|
|
386
|
-
wbits(out, p, (sym >> 23) & 31), p += fleb[len];
|
|
387
|
-
var dst = sym & 31;
|
|
388
|
-
wbits16(out, p, dm[dst]), p += dl[dst];
|
|
389
|
-
if (dst > 3)
|
|
390
|
-
wbits16(out, p, (sym >> 5) & 8191), p += fdeb[dst];
|
|
391
|
-
}
|
|
392
|
-
else {
|
|
393
|
-
wbits16(out, p, lm[sym]), p += ll[sym];
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
wbits16(out, p, lm[256]);
|
|
397
|
-
return p + ll[256];
|
|
398
|
-
};
|
|
399
|
-
// deflate options (nice << 13) | chain
|
|
400
|
-
var deo = /*#__PURE__*/ new i32([65540, 131080, 131088, 131104, 262176, 1048704, 1048832, 2114560, 2117632]);
|
|
401
|
-
// empty
|
|
402
|
-
var et = /*#__PURE__*/ new u8(0);
|
|
403
|
-
// compresses data into a raw DEFLATE buffer
|
|
404
|
-
var dflt = function (dat, lvl, plvl, pre, post, st) {
|
|
405
|
-
var s = st.z || dat.length;
|
|
406
|
-
var o = new u8(pre + s + 5 * (1 + Math.ceil(s / 7000)) + post);
|
|
407
|
-
// writing to this writes to the output buffer
|
|
408
|
-
var w = o.subarray(pre, o.length - post);
|
|
409
|
-
var lst = st.l;
|
|
410
|
-
var pos = (st.r || 0) & 7;
|
|
411
|
-
if (lvl) {
|
|
412
|
-
if (pos)
|
|
413
|
-
w[0] = st.r >> 3;
|
|
414
|
-
var opt = deo[lvl - 1];
|
|
415
|
-
var n = opt >> 13, c = opt & 8191;
|
|
416
|
-
var msk_1 = (1 << plvl) - 1;
|
|
417
|
-
// prev 2-byte val map curr 2-byte val map
|
|
418
|
-
var prev = st.p || new u16(32768), head = st.h || new u16(msk_1 + 1);
|
|
419
|
-
var bs1_1 = Math.ceil(plvl / 3), bs2_1 = 2 * bs1_1;
|
|
420
|
-
var hsh = function (i) { return (dat[i] ^ (dat[i + 1] << bs1_1) ^ (dat[i + 2] << bs2_1)) & msk_1; };
|
|
421
|
-
// 24576 is an arbitrary number of maximum symbols per block
|
|
422
|
-
// 424 buffer for last block
|
|
423
|
-
var syms = new i32(25000);
|
|
424
|
-
// length/literal freq distance freq
|
|
425
|
-
var lf = new u16(288), df = new u16(32);
|
|
426
|
-
// l/lcnt exbits index l/lind waitdx blkpos
|
|
427
|
-
var lc_1 = 0, eb = 0, i = st.i || 0, li = 0, wi = st.w || 0, bs = 0;
|
|
428
|
-
for (; i + 2 < s; ++i) {
|
|
429
|
-
// hash value
|
|
430
|
-
var hv = hsh(i);
|
|
431
|
-
// index mod 32768 previous index mod
|
|
432
|
-
var imod = i & 32767, pimod = head[hv];
|
|
433
|
-
prev[imod] = pimod;
|
|
434
|
-
head[hv] = imod;
|
|
435
|
-
// We always should modify head and prev, but only add symbols if
|
|
436
|
-
// this data is not yet processed ("wait" for wait index)
|
|
437
|
-
if (wi <= i) {
|
|
438
|
-
// bytes remaining
|
|
439
|
-
var rem = s - i;
|
|
440
|
-
if ((lc_1 > 7000 || li > 24576) && (rem > 423 || !lst)) {
|
|
441
|
-
pos = wblk(dat, w, 0, syms, lf, df, eb, li, bs, i - bs, pos);
|
|
442
|
-
li = lc_1 = eb = 0, bs = i;
|
|
443
|
-
for (var j = 0; j < 286; ++j)
|
|
444
|
-
lf[j] = 0;
|
|
445
|
-
for (var j = 0; j < 30; ++j)
|
|
446
|
-
df[j] = 0;
|
|
447
|
-
}
|
|
448
|
-
// len dist chain
|
|
449
|
-
var l = 2, d = 0, ch_1 = c, dif = imod - pimod & 32767;
|
|
450
|
-
if (rem > 2 && hv == hsh(i - dif)) {
|
|
451
|
-
var maxn = Math.min(n, rem) - 1;
|
|
452
|
-
var maxd = Math.min(32767, i);
|
|
453
|
-
// max possible length
|
|
454
|
-
// not capped at dif because decompressors implement "rolling" index population
|
|
455
|
-
var ml = Math.min(258, rem);
|
|
456
|
-
while (dif <= maxd && --ch_1 && imod != pimod) {
|
|
457
|
-
if (dat[i + l] == dat[i + l - dif]) {
|
|
458
|
-
var nl = 0;
|
|
459
|
-
for (; nl < ml && dat[i + nl] == dat[i + nl - dif]; ++nl)
|
|
460
|
-
;
|
|
461
|
-
if (nl > l) {
|
|
462
|
-
l = nl, d = dif;
|
|
463
|
-
// break out early when we reach "nice" (we are satisfied enough)
|
|
464
|
-
if (nl > maxn)
|
|
465
|
-
break;
|
|
466
|
-
// now, find the rarest 2-byte sequence within this
|
|
467
|
-
// length of literals and search for that instead.
|
|
468
|
-
// Much faster than just using the start
|
|
469
|
-
var mmd = Math.min(dif, nl - 2);
|
|
470
|
-
var md = 0;
|
|
471
|
-
for (var j = 0; j < mmd; ++j) {
|
|
472
|
-
var ti = i - dif + j & 32767;
|
|
473
|
-
var pti = prev[ti];
|
|
474
|
-
var cd = ti - pti & 32767;
|
|
475
|
-
if (cd > md)
|
|
476
|
-
md = cd, pimod = ti;
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
// check the previous match
|
|
481
|
-
imod = pimod, pimod = prev[imod];
|
|
482
|
-
dif += imod - pimod & 32767;
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
// d will be nonzero only when a match was found
|
|
486
|
-
if (d) {
|
|
487
|
-
// store both dist and len data in one int32
|
|
488
|
-
// Make sure this is recognized as a len/dist with 28th bit (2^28)
|
|
489
|
-
syms[li++] = 268435456 | (revfl[l] << 18) | revfd[d];
|
|
490
|
-
var lin = revfl[l] & 31, din = revfd[d] & 31;
|
|
491
|
-
eb += fleb[lin] + fdeb[din];
|
|
492
|
-
++lf[257 + lin];
|
|
493
|
-
++df[din];
|
|
494
|
-
wi = i + l;
|
|
495
|
-
++lc_1;
|
|
496
|
-
}
|
|
497
|
-
else {
|
|
498
|
-
syms[li++] = dat[i];
|
|
499
|
-
++lf[dat[i]];
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
for (i = Math.max(i, wi); i < s; ++i) {
|
|
504
|
-
syms[li++] = dat[i];
|
|
505
|
-
++lf[dat[i]];
|
|
506
|
-
}
|
|
507
|
-
pos = wblk(dat, w, lst, syms, lf, df, eb, li, bs, i - bs, pos);
|
|
508
|
-
if (!lst) {
|
|
509
|
-
st.r = (pos & 7) | w[(pos / 8) | 0] << 3;
|
|
510
|
-
// shft(pos) now 1 less if pos & 7 != 0
|
|
511
|
-
pos -= 7;
|
|
512
|
-
st.h = head, st.p = prev, st.i = i, st.w = wi;
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
else {
|
|
516
|
-
for (var i = st.w || 0; i < s + lst; i += 65535) {
|
|
517
|
-
// end
|
|
518
|
-
var e = i + 65535;
|
|
519
|
-
if (e >= s) {
|
|
520
|
-
// write final block
|
|
521
|
-
w[(pos / 8) | 0] = lst;
|
|
522
|
-
e = s;
|
|
523
|
-
}
|
|
524
|
-
pos = wfblk(w, pos + 1, dat.subarray(i, e));
|
|
525
|
-
}
|
|
526
|
-
st.i = s;
|
|
527
|
-
}
|
|
528
|
-
return slc(o, 0, pre + shft(pos) + post);
|
|
529
|
-
};
|
|
530
|
-
// CRC32 table
|
|
531
|
-
var crct = /*#__PURE__*/ (function () {
|
|
532
|
-
var t = new Int32Array(256);
|
|
533
|
-
for (var i = 0; i < 256; ++i) {
|
|
534
|
-
var c = i, k = 9;
|
|
535
|
-
while (--k)
|
|
536
|
-
c = ((c & 1) && -306674912) ^ (c >>> 1);
|
|
537
|
-
t[i] = c;
|
|
538
|
-
}
|
|
539
|
-
return t;
|
|
540
|
-
})();
|
|
541
|
-
// CRC32
|
|
542
|
-
var crc = function () {
|
|
543
|
-
var c = -1;
|
|
544
|
-
return {
|
|
545
|
-
p: function (d) {
|
|
546
|
-
// closures have awful performance
|
|
547
|
-
var cr = c;
|
|
548
|
-
for (var i = 0; i < d.length; ++i)
|
|
549
|
-
cr = crct[(cr & 255) ^ d[i]] ^ (cr >>> 8);
|
|
550
|
-
c = cr;
|
|
551
|
-
},
|
|
552
|
-
d: function () { return ~c; }
|
|
553
|
-
};
|
|
554
|
-
};
|
|
555
|
-
// deflate with opts
|
|
556
|
-
var dopt = function (dat, opt, pre, post, st) {
|
|
557
|
-
if (!st) {
|
|
558
|
-
st = { l: 1 };
|
|
559
|
-
if (opt.dictionary) {
|
|
560
|
-
var dict = opt.dictionary.subarray(-32768);
|
|
561
|
-
var newDat = new u8(dict.length + dat.length);
|
|
562
|
-
newDat.set(dict);
|
|
563
|
-
newDat.set(dat, dict.length);
|
|
564
|
-
dat = newDat;
|
|
565
|
-
st.w = dict.length;
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
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);
|
|
569
|
-
};
|
|
570
|
-
// Walmart object spread
|
|
571
|
-
var mrg = function (a, b) {
|
|
572
|
-
var o = {};
|
|
573
|
-
for (var k in a)
|
|
574
|
-
o[k] = a[k];
|
|
575
|
-
for (var k in b)
|
|
576
|
-
o[k] = b[k];
|
|
577
|
-
return o;
|
|
578
|
-
};
|
|
579
|
-
// worker clone
|
|
580
|
-
// This is possibly the craziest part of the entire codebase, despite how simple it may seem.
|
|
581
|
-
// The only parameter to this function is a closure that returns an array of variables outside of the function scope.
|
|
582
|
-
// We're going to try to figure out the variable names used in the closure as strings because that is crucial for workerization.
|
|
583
|
-
// We will return an object mapping of true variable name to value (basically, the current scope as a JS object).
|
|
584
|
-
// The reason we can't just use the original variable names is minifiers mangling the toplevel scope.
|
|
585
|
-
// This took me three weeks to figure out how to do.
|
|
586
|
-
var wcln = function (fn, fnStr, td) {
|
|
587
|
-
var dt = fn();
|
|
588
|
-
var st = fn.toString();
|
|
589
|
-
var ks = st.slice(st.indexOf('[') + 1, st.lastIndexOf(']')).replace(/\s+/g, '').split(',');
|
|
590
|
-
for (var i = 0; i < dt.length; ++i) {
|
|
591
|
-
var v = dt[i], k = ks[i];
|
|
592
|
-
if (typeof v == 'function') {
|
|
593
|
-
fnStr += ';' + k + '=';
|
|
594
|
-
var st_1 = v.toString();
|
|
595
|
-
if (v.prototype) {
|
|
596
|
-
// for global objects
|
|
597
|
-
if (st_1.indexOf('[native code]') != -1) {
|
|
598
|
-
var spInd = st_1.indexOf(' ', 8) + 1;
|
|
599
|
-
fnStr += st_1.slice(spInd, st_1.indexOf('(', spInd));
|
|
600
|
-
}
|
|
601
|
-
else {
|
|
602
|
-
fnStr += st_1;
|
|
603
|
-
for (var t in v.prototype)
|
|
604
|
-
fnStr += ';' + k + '.prototype.' + t + '=' + v.prototype[t].toString();
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
else
|
|
608
|
-
fnStr += st_1;
|
|
609
|
-
}
|
|
610
|
-
else
|
|
611
|
-
td[k] = v;
|
|
612
|
-
}
|
|
613
|
-
return fnStr;
|
|
614
|
-
};
|
|
615
|
-
var ch = [];
|
|
616
|
-
// clone bufs
|
|
617
|
-
var cbfs = function (v) {
|
|
618
|
-
var tl = [];
|
|
619
|
-
for (var k in v) {
|
|
620
|
-
if (v[k].buffer) {
|
|
621
|
-
tl.push((v[k] = new v[k].constructor(v[k])).buffer);
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
return tl;
|
|
625
|
-
};
|
|
626
|
-
// use a worker to execute code
|
|
627
|
-
var wrkr = function (fns, init, id, cb) {
|
|
628
|
-
if (!ch[id]) {
|
|
629
|
-
var fnStr = '', td_1 = {}, m = fns.length - 1;
|
|
630
|
-
for (var i = 0; i < m; ++i)
|
|
631
|
-
fnStr = wcln(fns[i], fnStr, td_1);
|
|
632
|
-
ch[id] = { c: wcln(fns[m], fnStr, td_1), e: td_1 };
|
|
633
|
-
}
|
|
634
|
-
var td = mrg({}, ch[id].e);
|
|
635
|
-
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);
|
|
636
|
-
};
|
|
637
|
-
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]; };
|
|
638
|
-
// gzip extra
|
|
639
|
-
var gze = function () { return [gzh, gzhl, wbytes, crc, crct]; };
|
|
640
|
-
// post buf
|
|
641
|
-
var pbf = function (msg) { return postMessage(msg, [msg.buffer]); };
|
|
642
|
-
// async helper
|
|
643
|
-
var cbify = function (dat, opts, fns, init, id, cb) {
|
|
644
|
-
var w = wrkr(fns, init, id, function (err, dat) {
|
|
645
|
-
w.terminate();
|
|
646
|
-
cb(err, dat);
|
|
647
|
-
});
|
|
648
|
-
w.postMessage([dat, opts], opts.consume ? [dat.buffer] : []);
|
|
649
|
-
return function () { w.terminate(); };
|
|
650
|
-
};
|
|
651
|
-
// write bytes
|
|
652
|
-
var wbytes = function (d, b, v) {
|
|
653
|
-
for (; v; ++b)
|
|
654
|
-
d[b] = v, v >>>= 8;
|
|
655
|
-
};
|
|
656
|
-
// gzip header
|
|
657
|
-
var gzh = function (c, o) {
|
|
658
|
-
var fn = o.filename;
|
|
659
|
-
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
|
|
660
|
-
if (o.mtime != 0)
|
|
661
|
-
wbytes(c, 4, Math.floor(new Date(o.mtime || Date.now()) / 1000));
|
|
662
|
-
if (fn) {
|
|
663
|
-
c[3] = 8;
|
|
664
|
-
for (var i = 0; i <= fn.length; ++i)
|
|
665
|
-
c[i + 10] = fn.charCodeAt(i);
|
|
666
|
-
}
|
|
667
|
-
};
|
|
668
|
-
// gzip header length
|
|
669
|
-
var gzhl = function (o) { return 10 + (o.filename ? o.filename.length + 1 : 0); };
|
|
670
|
-
/**
|
|
671
|
-
* Compresses data with DEFLATE without any wrapper
|
|
672
|
-
* @param data The data to compress
|
|
673
|
-
* @param opts The compression options
|
|
674
|
-
* @returns The deflated version of the data
|
|
675
|
-
*/
|
|
676
|
-
function deflateSync(data, opts) {
|
|
677
|
-
return dopt(data, opts || {}, 0, 0);
|
|
678
|
-
}
|
|
679
|
-
function gzip(data, opts, cb) {
|
|
680
|
-
if (!cb)
|
|
681
|
-
cb = opts, opts = {};
|
|
682
|
-
if (typeof cb != 'function')
|
|
683
|
-
err(7);
|
|
684
|
-
return cbify(data, opts, [
|
|
685
|
-
bDflt,
|
|
686
|
-
gze,
|
|
687
|
-
function () { return [gzipSync]; }
|
|
688
|
-
], function (ev) { return pbf(gzipSync(ev.data[0], ev.data[1])); }, 2, cb);
|
|
689
|
-
}
|
|
690
|
-
/**
|
|
691
|
-
* Compresses data with GZIP
|
|
692
|
-
* @param data The data to compress
|
|
693
|
-
* @param opts The compression options
|
|
694
|
-
* @returns The gzipped version of the data
|
|
695
|
-
*/
|
|
696
|
-
function gzipSync(data, opts) {
|
|
697
|
-
if (!opts)
|
|
698
|
-
opts = {};
|
|
699
|
-
var c = crc(), l = data.length;
|
|
700
|
-
c.p(data);
|
|
701
|
-
var d = dopt(data, opts, gzhl(opts), 8), s = d.length;
|
|
702
|
-
return gzh(d, opts), wbytes(d, s - 8, c.d()), wbytes(d, s - 4, l), d;
|
|
703
|
-
}
|
|
704
|
-
// text decoder
|
|
705
|
-
var td = typeof TextDecoder != 'undefined' && /*#__PURE__*/ new TextDecoder();
|
|
706
|
-
// text decoder stream
|
|
707
|
-
var tds = 0;
|
|
708
|
-
try {
|
|
709
|
-
td.decode(et, { stream: true });
|
|
710
|
-
tds = 1;
|
|
711
|
-
}
|
|
712
|
-
catch (e) { }
|
|
713
|
-
|
|
714
1
|
class StringDictionary {
|
|
715
2
|
constructor() {
|
|
716
3
|
this.lastTs = 0;
|
|
@@ -1161,6 +448,23 @@ function deprecationWarn(nameOfFeature, useInstead, docsPath = '/') {
|
|
|
1161
448
|
console.warn(`OxvoSessions: ${nameOfFeature} is deprecated. ${useInstead ? `Please, use ${useInstead} instead.` : ''} Visit ${DOCS_HOST}${docsPath} for more information.`);
|
|
1162
449
|
warnedFeatures[nameOfFeature] = true;
|
|
1163
450
|
}
|
|
451
|
+
function getCustomAttributeLabel(e, customAttributes) {
|
|
452
|
+
if (!customAttributes || customAttributes.length === 0)
|
|
453
|
+
return '';
|
|
454
|
+
const parts = [];
|
|
455
|
+
for (const attr of customAttributes) {
|
|
456
|
+
const value = e.getAttribute(attr);
|
|
457
|
+
if (value !== null) {
|
|
458
|
+
parts.push(`[${attr}=${value}]`);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
return parts.join('');
|
|
462
|
+
}
|
|
463
|
+
function getClassSelector(e) {
|
|
464
|
+
if (!e.classList || e.classList.length === 0)
|
|
465
|
+
return '';
|
|
466
|
+
return '.' + Array.from(e.classList).join('.');
|
|
467
|
+
}
|
|
1164
468
|
function getLabelAttribute(e) {
|
|
1165
469
|
let value = e.getAttribute('data-oxvosessions-label');
|
|
1166
470
|
if (value !== null) {
|
|
@@ -1226,7 +530,7 @@ function dec2hex(dec) {
|
|
|
1226
530
|
return dec.toString(16).padStart(2, '0');
|
|
1227
531
|
}
|
|
1228
532
|
function generateRandomId(len) {
|
|
1229
|
-
const arr = new Uint8Array((len) / 2);
|
|
533
|
+
const arr = new Uint8Array((len || 40) / 2);
|
|
1230
534
|
// msCrypto = IE11
|
|
1231
535
|
// @ts-ignore
|
|
1232
536
|
const safeCrypto = window.crypto || window.msCrypto;
|
|
@@ -1235,7 +539,7 @@ function generateRandomId(len) {
|
|
|
1235
539
|
return Array.from(arr, dec2hex).join('');
|
|
1236
540
|
}
|
|
1237
541
|
else {
|
|
1238
|
-
return Array.from({ length: len }, () => dec2hex(Math.floor(Math.random() * 16))).join('');
|
|
542
|
+
return Array.from({ length: len || 40 }, () => dec2hex(Math.floor(Math.random() * 16))).join('');
|
|
1239
543
|
}
|
|
1240
544
|
}
|
|
1241
545
|
function inIframe() {
|
|
@@ -1432,6 +736,88 @@ function throttleWithTrailing(fn, interval) {
|
|
|
1432
736
|
|
|
1433
737
|
// Auto-generated, do not edit
|
|
1434
738
|
/* eslint-disable */
|
|
739
|
+
const MESSAGE_FIELD_TYPES = {
|
|
740
|
+
0: ['number'],
|
|
741
|
+
4: ['string', 'string', 'number'],
|
|
742
|
+
5: ['number', 'number'],
|
|
743
|
+
6: ['number', 'number'],
|
|
744
|
+
7: [],
|
|
745
|
+
8: ['number', 'number', 'number', 'string', 'boolean'],
|
|
746
|
+
9: ['number', 'number', 'number'],
|
|
747
|
+
10: ['number', 'number', 'number'],
|
|
748
|
+
11: ['number'],
|
|
749
|
+
12: ['number', 'string', 'string'],
|
|
750
|
+
13: ['number', 'string'],
|
|
751
|
+
14: ['number', 'string'],
|
|
752
|
+
16: ['number', 'number', 'number'],
|
|
753
|
+
17: ['number', 'string'],
|
|
754
|
+
18: ['number', 'string', 'number'],
|
|
755
|
+
19: ['number', 'boolean'],
|
|
756
|
+
20: ['number', 'number'],
|
|
757
|
+
21: ['string', 'string', 'string', 'string', 'string', 'number', 'number', 'number'],
|
|
758
|
+
22: ['string', 'string'],
|
|
759
|
+
23: ['number', 'number', 'number', 'number', 'number', 'number', 'number', 'number', 'number'],
|
|
760
|
+
24: ['number', 'number', 'number'],
|
|
761
|
+
27: ['string', 'string'],
|
|
762
|
+
28: ['string'],
|
|
763
|
+
29: ['string'],
|
|
764
|
+
30: ['string', 'string'],
|
|
765
|
+
34: ['number', 'string'],
|
|
766
|
+
35: ['number', 'number', 'number'],
|
|
767
|
+
36: ['number', 'string'],
|
|
768
|
+
40: ['string', 'number', 'string', 'string'],
|
|
769
|
+
41: ['string', 'string'],
|
|
770
|
+
42: ['string'],
|
|
771
|
+
44: ['string', 'string', 'number'],
|
|
772
|
+
45: ['string', 'string'],
|
|
773
|
+
46: ['string', 'string'],
|
|
774
|
+
47: ['string', 'string', 'number'],
|
|
775
|
+
48: ['string', 'string', 'string', 'string', 'number'],
|
|
776
|
+
49: ['number', 'number', 'number', 'number'],
|
|
777
|
+
50: ['number', 'string'],
|
|
778
|
+
51: ['number', 'number', 'number'],
|
|
779
|
+
43: ['string', 'string'],
|
|
780
|
+
52: ['number', 'string', 'string'],
|
|
781
|
+
53: ['number', 'number', 'number', 'number', 'number', 'number', 'string', 'string'],
|
|
782
|
+
54: ['number', 'string'],
|
|
783
|
+
55: ['boolean'],
|
|
784
|
+
57: ['number', 'string', 'string', 'string'],
|
|
785
|
+
58: ['number'],
|
|
786
|
+
60: ['number', 'string', 'string', 'string'],
|
|
787
|
+
61: ['number', 'string', 'string'],
|
|
788
|
+
63: ['string', 'string'],
|
|
789
|
+
64: ['string', 'string'],
|
|
790
|
+
65: ['number', 'number'],
|
|
791
|
+
68: ['number', 'number', 'string', 'string', 'number', 'number'],
|
|
792
|
+
69: ['number', 'number', 'string', 'string'],
|
|
793
|
+
70: ['number', 'number'],
|
|
794
|
+
71: ['number', 'string', 'string'],
|
|
795
|
+
73: ['number', 'string', 'number', 'string'],
|
|
796
|
+
75: ['number', 'number'],
|
|
797
|
+
76: ['number', 'number'],
|
|
798
|
+
77: ['number', 'number'],
|
|
799
|
+
78: ['string', 'string', 'string', 'string'],
|
|
800
|
+
79: ['string', 'string'],
|
|
801
|
+
81: ['number', 'number', 'number', 'number', 'string'],
|
|
802
|
+
83: ['string', 'string', 'string', 'string', 'string', 'number', 'number', 'number', 'number'],
|
|
803
|
+
84: ['string', 'string', 'string', 'number', 'string', 'string'],
|
|
804
|
+
85: ['number', 'number', 'number', 'number', 'number', 'number', 'string', 'string', 'number', 'boolean', 'number', 'number', 'number', 'number', 'number', 'number', 'number'],
|
|
805
|
+
87: ['string', 'number', 'number'],
|
|
806
|
+
89: ['string', 'number', 'number', 'number', 'number', 'string'],
|
|
807
|
+
112: ['number', 'string', 'boolean', 'string', 'number', 'number'],
|
|
808
|
+
113: ['number', 'number', 'string'],
|
|
809
|
+
114: ['number'],
|
|
810
|
+
115: ['number'],
|
|
811
|
+
116: ['number', 'number', 'number', 'number', 'number', 'number', 'string', 'string', 'number', 'boolean'],
|
|
812
|
+
117: ['string'],
|
|
813
|
+
118: ['string'],
|
|
814
|
+
119: ['string', 'number'],
|
|
815
|
+
120: ['number'],
|
|
816
|
+
121: ['string', 'string', 'number', 'number'],
|
|
817
|
+
122: ['string', 'string', 'number', 'string'],
|
|
818
|
+
123: ['string', 'string', 'string', 'string', 'number'],
|
|
819
|
+
124: ['string', 'string'],
|
|
820
|
+
};
|
|
1435
821
|
function Timestamp(timestamp) {
|
|
1436
822
|
return [
|
|
1437
823
|
0 /* Messages.Type.Timestamp */,
|
|
@@ -1917,13 +1303,6 @@ function BatchMetadata(version, pageNo, firstIndex, timestamp, location) {
|
|
|
1917
1303
|
location,
|
|
1918
1304
|
];
|
|
1919
1305
|
}
|
|
1920
|
-
function PartitionedMessage(partNo, partTotal) {
|
|
1921
|
-
return [
|
|
1922
|
-
82 /* Messages.Type.PartitionedMessage */,
|
|
1923
|
-
partNo,
|
|
1924
|
-
partTotal,
|
|
1925
|
-
];
|
|
1926
|
-
}
|
|
1927
1306
|
function NetworkRequest(type, method, url, request, response, status, timestamp, duration, transferredBodySize) {
|
|
1928
1307
|
return [
|
|
1929
1308
|
83 /* Messages.Type.NetworkRequest */,
|
|
@@ -2121,6 +1500,7 @@ var _Messages = /*#__PURE__*/Object.freeze({
|
|
|
2121
1500
|
JSException: JSException,
|
|
2122
1501
|
LoadFontFace: LoadFontFace,
|
|
2123
1502
|
LongAnimationTask: LongAnimationTask$1,
|
|
1503
|
+
MESSAGE_FIELD_TYPES: MESSAGE_FIELD_TYPES,
|
|
2124
1504
|
Metadata: Metadata,
|
|
2125
1505
|
MobX: MobX,
|
|
2126
1506
|
MouseClick: MouseClick,
|
|
@@ -2135,7 +1515,6 @@ var _Messages = /*#__PURE__*/Object.freeze({
|
|
|
2135
1515
|
OTable: OTable,
|
|
2136
1516
|
PageLoadTiming: PageLoadTiming,
|
|
2137
1517
|
PageRenderTiming: PageRenderTiming,
|
|
2138
|
-
PartitionedMessage: PartitionedMessage,
|
|
2139
1518
|
PerformanceTrack: PerformanceTrack,
|
|
2140
1519
|
Profiler: Profiler,
|
|
2141
1520
|
Redux: Redux,
|
|
@@ -2230,11 +1609,157 @@ function Performance (app, opts) {
|
|
|
2230
1609
|
}
|
|
2231
1610
|
}
|
|
2232
1611
|
|
|
1612
|
+
/**
|
|
1613
|
+
* Two-tier tag matching:
|
|
1614
|
+
* 1. Fast fingerprint lookup by id, data-attr,
|
|
1615
|
+
* or class from the selector's last segment
|
|
1616
|
+
* 2. Fallback iteration using native element.matches()
|
|
1617
|
+
*/
|
|
1618
|
+
class TagMatcher {
|
|
1619
|
+
constructor() {
|
|
1620
|
+
this.tags = [];
|
|
1621
|
+
this.byId = new Map();
|
|
1622
|
+
this.byDataAttr = new Map();
|
|
1623
|
+
this.byClass = new Map();
|
|
1624
|
+
this.fallback = [];
|
|
1625
|
+
}
|
|
1626
|
+
setTags(tags) {
|
|
1627
|
+
this.tags = tags;
|
|
1628
|
+
this.byId.clear();
|
|
1629
|
+
this.byDataAttr.clear();
|
|
1630
|
+
this.byClass.clear();
|
|
1631
|
+
this.fallback = [];
|
|
1632
|
+
for (const tag of tags) {
|
|
1633
|
+
const last = lastSegment(tag.selector);
|
|
1634
|
+
if (!last) {
|
|
1635
|
+
this.fallback.push(tag);
|
|
1636
|
+
continue;
|
|
1637
|
+
}
|
|
1638
|
+
if (last.startsWith('#')) {
|
|
1639
|
+
this.byId.set(last.slice(1), tag);
|
|
1640
|
+
}
|
|
1641
|
+
else if (last.startsWith('[data-')) {
|
|
1642
|
+
this.byDataAttr.set(last, tag);
|
|
1643
|
+
}
|
|
1644
|
+
else {
|
|
1645
|
+
const cls = extractClass(last);
|
|
1646
|
+
if (cls) {
|
|
1647
|
+
this.byClass.set(cls, tag);
|
|
1648
|
+
}
|
|
1649
|
+
else {
|
|
1650
|
+
this.fallback.push(tag);
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
getTags() {
|
|
1656
|
+
return this.tags;
|
|
1657
|
+
}
|
|
1658
|
+
/** Match element, its parent, or direct children against known tag selectors */
|
|
1659
|
+
match(el) {
|
|
1660
|
+
const direct = this.matchExact(el);
|
|
1661
|
+
if (direct)
|
|
1662
|
+
return direct;
|
|
1663
|
+
if (el.parentElement) {
|
|
1664
|
+
const parent = this.matchExact(el.parentElement);
|
|
1665
|
+
if (parent)
|
|
1666
|
+
return parent;
|
|
1667
|
+
}
|
|
1668
|
+
const children = el.children;
|
|
1669
|
+
for (let i = 0; i < children.length; i++) {
|
|
1670
|
+
const child = this.matchExact(children[i]);
|
|
1671
|
+
if (child)
|
|
1672
|
+
return child;
|
|
1673
|
+
}
|
|
1674
|
+
return null;
|
|
1675
|
+
}
|
|
1676
|
+
matchExact(el) {
|
|
1677
|
+
if (el.id && this.byId.has(el.id)) {
|
|
1678
|
+
const tag = this.byId.get(el.id);
|
|
1679
|
+
if (safeMatches(el, tag.selector) && matchesLocation(tag))
|
|
1680
|
+
return tag;
|
|
1681
|
+
}
|
|
1682
|
+
if (this.byDataAttr.size > 0) {
|
|
1683
|
+
const attrs = el.attributes;
|
|
1684
|
+
for (let i = 0; i < attrs.length; i++) {
|
|
1685
|
+
const attr = attrs[i];
|
|
1686
|
+
if (attr.name.startsWith('data-')) {
|
|
1687
|
+
const key = `[${attr.name}="${attr.value}"]`;
|
|
1688
|
+
if (this.byDataAttr.has(key)) {
|
|
1689
|
+
const tag = this.byDataAttr.get(key);
|
|
1690
|
+
if (safeMatches(el, tag.selector) && matchesLocation(tag))
|
|
1691
|
+
return tag;
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
if (this.byClass.size > 0 && el.classList) {
|
|
1697
|
+
for (let i = 0; i < el.classList.length; i++) {
|
|
1698
|
+
const cls = el.classList[i];
|
|
1699
|
+
if (this.byClass.has(cls)) {
|
|
1700
|
+
const tag = this.byClass.get(cls);
|
|
1701
|
+
if (safeMatches(el, tag.selector) && matchesLocation(tag))
|
|
1702
|
+
return tag;
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
for (const tag of this.fallback) {
|
|
1707
|
+
if (safeMatches(el, tag.selector) && matchesLocation(tag))
|
|
1708
|
+
return tag;
|
|
1709
|
+
}
|
|
1710
|
+
return null;
|
|
1711
|
+
}
|
|
1712
|
+
clear() {
|
|
1713
|
+
this.tags = [];
|
|
1714
|
+
this.byId.clear();
|
|
1715
|
+
this.byDataAttr.clear();
|
|
1716
|
+
this.byClass.clear();
|
|
1717
|
+
this.fallback = [];
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1720
|
+
/** Last combinator-separated segment of a CSS selector */
|
|
1721
|
+
function lastSegment(selector) {
|
|
1722
|
+
const trimmed = selector.trim();
|
|
1723
|
+
if (!trimmed)
|
|
1724
|
+
return null;
|
|
1725
|
+
const parts = trimmed.split(/\s*[>+~ ]\s*/);
|
|
1726
|
+
const last = parts[parts.length - 1]?.trim();
|
|
1727
|
+
return last || null;
|
|
1728
|
+
}
|
|
1729
|
+
/** First class name from a selector segment, e.g. "div.my-class" -> "my-class" */
|
|
1730
|
+
function extractClass(segment) {
|
|
1731
|
+
const match = segment.match(/\.([a-zA-Z_-][\w-]*)/);
|
|
1732
|
+
return match ? match[1] : null;
|
|
1733
|
+
}
|
|
1734
|
+
function safeMatches(el, selector) {
|
|
1735
|
+
try {
|
|
1736
|
+
return el.matches(selector);
|
|
1737
|
+
}
|
|
1738
|
+
catch {
|
|
1739
|
+
return false;
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
function matchesLocation(tag) {
|
|
1743
|
+
if (!tag.location)
|
|
1744
|
+
return true;
|
|
1745
|
+
try {
|
|
1746
|
+
const loc = tag.location;
|
|
1747
|
+
if (loc.startsWith('/')) {
|
|
1748
|
+
return window.location.pathname === loc;
|
|
1749
|
+
}
|
|
1750
|
+
return window.location.href === loc;
|
|
1751
|
+
}
|
|
1752
|
+
catch {
|
|
1753
|
+
return true;
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
|
|
2233
1757
|
const WATCHED_MARKERS_KEY = '__or__watched_markers__';
|
|
2234
1758
|
class MarkerWatcher {
|
|
2235
1759
|
constructor(params) {
|
|
2236
1760
|
this.interval = null;
|
|
2237
1761
|
this.tags = [];
|
|
1762
|
+
this.matcher = new TagMatcher();
|
|
2238
1763
|
this.sessionStorage = params.sessionStorage;
|
|
2239
1764
|
this.errLog = params.errLog;
|
|
2240
1765
|
this.onMarkerHit = params.onMarkerHit;
|
|
@@ -2277,12 +1802,15 @@ class MarkerWatcher {
|
|
|
2277
1802
|
}
|
|
2278
1803
|
setTags(tags) {
|
|
2279
1804
|
this.tags = tags;
|
|
1805
|
+
this.matcher.setTags(tags);
|
|
2280
1806
|
if (this.interval) {
|
|
2281
1807
|
clearInterval(this.interval);
|
|
2282
1808
|
this.interval = null;
|
|
2283
1809
|
}
|
|
2284
1810
|
this.interval = setInterval(() => {
|
|
2285
1811
|
this.tags.forEach((tag) => {
|
|
1812
|
+
if (!matchesLocation(tag))
|
|
1813
|
+
return;
|
|
2286
1814
|
const possibleEls = document.querySelectorAll(tag.selector);
|
|
2287
1815
|
if (possibleEls.length > 0) {
|
|
2288
1816
|
const el = possibleEls[0];
|
|
@@ -2294,13 +1822,17 @@ class MarkerWatcher {
|
|
|
2294
1822
|
}, 500);
|
|
2295
1823
|
}
|
|
2296
1824
|
onMarkerRendered(markerId) {
|
|
2297
|
-
|
|
2298
|
-
|
|
1825
|
+
const tag = this.tags.find((t) => t.id === markerId);
|
|
1826
|
+
if (tag) {
|
|
1827
|
+
this.tags = this.tags.filter((t) => t.id !== markerId);
|
|
1828
|
+
if (!matchesLocation(tag))
|
|
1829
|
+
return;
|
|
2299
1830
|
}
|
|
2300
1831
|
this.onMarkerHit(markerId);
|
|
2301
1832
|
}
|
|
2302
1833
|
clear() {
|
|
2303
1834
|
this.tags = [];
|
|
1835
|
+
this.matcher.clear();
|
|
2304
1836
|
if (this.interval) {
|
|
2305
1837
|
clearInterval(this.interval);
|
|
2306
1838
|
this.interval = null;
|
|
@@ -2349,8 +1881,34 @@ class CanvasRecorder {
|
|
|
2349
1881
|
this.uploadQueue = 0;
|
|
2350
1882
|
this.MAX_CONCURRENT_UPLOADS = 2;
|
|
2351
1883
|
this.MAX_QUEUE_SIZE = 50; // ~500 images max (50 batches × 10 images)
|
|
1884
|
+
this.MAX_UPLOAD_ATTEMPTS = 5;
|
|
1885
|
+
this.MAX_RETRY_DELAY_MS = 10000;
|
|
2352
1886
|
this.pendingBatches = [];
|
|
2353
1887
|
this.isProcessingQueue = false;
|
|
1888
|
+
/**
|
|
1889
|
+
* Reacts to a runtime sanitization change on a canvas: stop capturing if it
|
|
1890
|
+
* just became masked, start if it just became visible. Already-sent frames
|
|
1891
|
+
* cannot be retracted, so escalation only stops future capture.
|
|
1892
|
+
*/
|
|
1893
|
+
this.resanitizeCanvas = (node, id) => {
|
|
1894
|
+
if (!hasTag(node, 'canvas')) {
|
|
1895
|
+
return;
|
|
1896
|
+
}
|
|
1897
|
+
const isIgnored = this.app.sanitizer.isObscured(id) || this.app.sanitizer.isHidden(id);
|
|
1898
|
+
if (isIgnored) {
|
|
1899
|
+
if (this.snapshots[id] || this.observers.has(id)) {
|
|
1900
|
+
const observer = this.observers.get(id);
|
|
1901
|
+
if (observer) {
|
|
1902
|
+
observer.disconnect();
|
|
1903
|
+
this.observers.delete(id);
|
|
1904
|
+
}
|
|
1905
|
+
this.cleanupCanvas(id);
|
|
1906
|
+
}
|
|
1907
|
+
}
|
|
1908
|
+
else if (!this.snapshots[id] && !this.observers.has(id)) {
|
|
1909
|
+
this.captureCanvas(node);
|
|
1910
|
+
}
|
|
1911
|
+
};
|
|
2354
1912
|
this.restartTracking = () => {
|
|
2355
1913
|
this.clear();
|
|
2356
1914
|
this.app.nodes.scanTree(this.captureCanvas);
|
|
@@ -2447,14 +2005,15 @@ class CanvasRecorder {
|
|
|
2447
2005
|
}, this.interval);
|
|
2448
2006
|
this.intervals.set(id, int);
|
|
2449
2007
|
};
|
|
2450
|
-
this.fileExt =
|
|
2008
|
+
this.fileExt = 'webp';
|
|
2451
2009
|
this.interval = 1000 / options.fps;
|
|
2452
2010
|
}
|
|
2453
2011
|
startTracking() {
|
|
2454
2012
|
setTimeout(() => {
|
|
2455
2013
|
this.app.nodes.scanTree(this.captureCanvas);
|
|
2456
2014
|
this.app.nodes.attachNodeCallback(this.captureCanvas);
|
|
2457
|
-
|
|
2015
|
+
this.app.attachResanitizeCallback(this.resanitizeCanvas);
|
|
2016
|
+
}, 125);
|
|
2458
2017
|
}
|
|
2459
2018
|
sendSnaps(images, canvasId, createdAt) {
|
|
2460
2019
|
if (Object.keys(this.snapshots).length === 0) {
|
|
@@ -2464,37 +2023,111 @@ class CanvasRecorder {
|
|
|
2464
2023
|
this.app.debug.warn('Upload queue full, dropping canvas batch');
|
|
2465
2024
|
return;
|
|
2466
2025
|
}
|
|
2467
|
-
this.
|
|
2026
|
+
const token = this.app.session.getSessionSecret();
|
|
2027
|
+
if (!token) {
|
|
2028
|
+
this.app.debug.warn('Canvas batch has no session token');
|
|
2029
|
+
return;
|
|
2030
|
+
}
|
|
2031
|
+
this.pendingBatches.push({ images, canvasId, createdAt, token });
|
|
2468
2032
|
if (!this.isProcessingQueue) {
|
|
2469
|
-
this.processUploadQueue();
|
|
2033
|
+
void this.processUploadQueue();
|
|
2470
2034
|
}
|
|
2471
2035
|
}
|
|
2472
2036
|
async processUploadQueue() {
|
|
2037
|
+
if (this.isProcessingQueue)
|
|
2038
|
+
return;
|
|
2473
2039
|
this.isProcessingQueue = true;
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2040
|
+
const worker = async () => {
|
|
2041
|
+
while (true) {
|
|
2042
|
+
const batch = this.pendingBatches.shift();
|
|
2043
|
+
if (!batch)
|
|
2044
|
+
return;
|
|
2045
|
+
await this.uploadBatch(batch.images, batch.canvasId, batch.createdAt, batch.token);
|
|
2478
2046
|
}
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2047
|
+
};
|
|
2048
|
+
try {
|
|
2049
|
+
await Promise.all(Array.from({ length: this.MAX_CONCURRENT_UPLOADS }, () => worker()));
|
|
2050
|
+
}
|
|
2051
|
+
finally {
|
|
2052
|
+
this.isProcessingQueue = false;
|
|
2053
|
+
// A batch can be appended between the final worker check and the flag
|
|
2054
|
+
// reset. Restart once so that race cannot strand queued frames.
|
|
2055
|
+
if (this.pendingBatches.length > 0) {
|
|
2056
|
+
void this.processUploadQueue();
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
2060
|
+
async uploadBatch(images, canvasId, createdAt, token) {
|
|
2061
|
+
if (this.options.isDebug) {
|
|
2062
|
+
const packed = await packFrames(images);
|
|
2063
|
+
if (packed) {
|
|
2064
|
+
const fileName = `${createdAt}_${canvasId}.${this.fileExt}.frames`;
|
|
2065
|
+
const url = URL.createObjectURL(new Blob([packed]));
|
|
2066
|
+
const link = document.createElement('a');
|
|
2067
|
+
link.href = url;
|
|
2068
|
+
link.download = fileName;
|
|
2069
|
+
link.style.display = 'none';
|
|
2070
|
+
document.body.appendChild(link);
|
|
2071
|
+
link.click();
|
|
2072
|
+
document.body.removeChild(link);
|
|
2073
|
+
URL.revokeObjectURL(url);
|
|
2074
|
+
}
|
|
2075
|
+
// fall through to also send to backend
|
|
2076
|
+
}
|
|
2077
|
+
let formData;
|
|
2078
|
+
if (this.options.framesSupport) {
|
|
2079
|
+
// Pack frames into binary format: [uint64 LE timestamp][uint32 LE size][data] per frame
|
|
2080
|
+
const buffers = [];
|
|
2081
|
+
let totalSize = 0;
|
|
2082
|
+
for (const snapshot of images) {
|
|
2083
|
+
if (!snapshot.data)
|
|
2084
|
+
continue;
|
|
2085
|
+
const ab = await snapshot.data.arrayBuffer();
|
|
2086
|
+
buffers.push(ab);
|
|
2087
|
+
totalSize += 8 + 4 + ab.byteLength; // uint64 ts + uint32 size + data
|
|
2088
|
+
}
|
|
2089
|
+
if (totalSize === 0)
|
|
2090
|
+
return;
|
|
2091
|
+
const packed = new ArrayBuffer(totalSize);
|
|
2092
|
+
const view = new DataView(packed);
|
|
2093
|
+
const bytes = new Uint8Array(packed);
|
|
2094
|
+
let offset = 0;
|
|
2095
|
+
for (let i = 0; i < images.length; i++) {
|
|
2096
|
+
if (!images[i].data)
|
|
2097
|
+
continue;
|
|
2098
|
+
const ab = buffers.shift();
|
|
2099
|
+
const ts = images[i].id;
|
|
2100
|
+
// uint64 LE as two uint32 LE writes -- timestamp
|
|
2101
|
+
view.setUint32(offset, ts % 0x100000000, true);
|
|
2102
|
+
view.setUint32(offset + 4, Math.floor(ts / 0x100000000), true);
|
|
2103
|
+
offset += 8;
|
|
2104
|
+
// uint32 LE -- size
|
|
2105
|
+
view.setUint32(offset, ab.byteLength, true);
|
|
2106
|
+
offset += 4;
|
|
2107
|
+
// image data
|
|
2108
|
+
bytes.set(new Uint8Array(ab), offset);
|
|
2109
|
+
offset += ab.byteLength;
|
|
2110
|
+
}
|
|
2111
|
+
formData = new FormData();
|
|
2112
|
+
formData.append('type', 'frames');
|
|
2113
|
+
const fileName = `${createdAt}_${canvasId}.${this.fileExt}.frames`;
|
|
2114
|
+
formData.append('frames', new Blob([packed]), fileName);
|
|
2483
2115
|
}
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2116
|
+
else {
|
|
2117
|
+
// Legacy: send individual image files
|
|
2118
|
+
formData = new FormData();
|
|
2119
|
+
images.forEach((snapshot) => {
|
|
2120
|
+
const blob = snapshot.data;
|
|
2121
|
+
if (!blob)
|
|
2122
|
+
return;
|
|
2123
|
+
const name = `${createdAt}_${canvasId}_${snapshot.id}.${this.fileExt}`;
|
|
2124
|
+
formData.append('snapshot', blob, name);
|
|
2125
|
+
});
|
|
2126
|
+
}
|
|
2127
|
+
const initRestart = () => {
|
|
2128
|
+
if (this.app.session.getSessionSecret() !== token) {
|
|
2491
2129
|
return;
|
|
2492
|
-
formData.append('snapshot', blob, `${createdAt}_${canvasId}_${snapshot.id}.${this.fileExt}`);
|
|
2493
|
-
if (this.options.isDebug) {
|
|
2494
|
-
saveImageData(blob, `${createdAt}_${canvasId}_${snapshot.id}.${this.fileExt}`);
|
|
2495
2130
|
}
|
|
2496
|
-
});
|
|
2497
|
-
const initRestart = () => {
|
|
2498
2131
|
this.app.debug.log('Restarting tracker; token expired');
|
|
2499
2132
|
this.app.stop(false);
|
|
2500
2133
|
setTimeout(() => {
|
|
@@ -2504,25 +2137,72 @@ class CanvasRecorder {
|
|
|
2504
2137
|
this.uploadQueue++;
|
|
2505
2138
|
const base = this.app.options.endpoint.replace(/\/$/, '');
|
|
2506
2139
|
const ingestPoint = base.endsWith('/oxvo') ? base : `${base}/oxvo`;
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2140
|
+
try {
|
|
2141
|
+
for (let attempt = 1; attempt <= this.MAX_UPLOAD_ATTEMPTS; attempt++) {
|
|
2142
|
+
try {
|
|
2143
|
+
const response = await fetch(ingestPoint + '/v1/assets', {
|
|
2144
|
+
method: 'POST',
|
|
2145
|
+
headers: {
|
|
2146
|
+
Authorization: `Bearer ${token}`,
|
|
2147
|
+
},
|
|
2148
|
+
body: formData,
|
|
2149
|
+
});
|
|
2150
|
+
if (response.ok)
|
|
2151
|
+
return;
|
|
2152
|
+
if (response.status === 401) {
|
|
2153
|
+
initRestart();
|
|
2154
|
+
return;
|
|
2155
|
+
}
|
|
2156
|
+
if (response.status === 413) {
|
|
2157
|
+
this.app.debug.warn('Canvas batch exceeds the upload size limit');
|
|
2158
|
+
return;
|
|
2159
|
+
}
|
|
2160
|
+
if (!this.isRetryableUploadStatus(response.status)) {
|
|
2161
|
+
this.app.debug.error(`Canvas upload failed with status ${response.status}`);
|
|
2162
|
+
return;
|
|
2163
|
+
}
|
|
2164
|
+
if (attempt === this.MAX_UPLOAD_ATTEMPTS) {
|
|
2165
|
+
this.app.debug.error(`Canvas upload failed after ${attempt} attempts (status ${response.status})`);
|
|
2166
|
+
return;
|
|
2167
|
+
}
|
|
2168
|
+
await this.waitBeforeUploadRetry(response, attempt);
|
|
2169
|
+
}
|
|
2170
|
+
catch (error) {
|
|
2171
|
+
if (attempt === this.MAX_UPLOAD_ATTEMPTS) {
|
|
2172
|
+
this.app.debug.error('Canvas upload failed after retrying', error);
|
|
2173
|
+
return;
|
|
2174
|
+
}
|
|
2175
|
+
await this.waitBeforeUploadRetry(undefined, attempt);
|
|
2176
|
+
}
|
|
2517
2177
|
}
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
.catch((e) => {
|
|
2521
|
-
this.app.debug.error('error saving canvas', e);
|
|
2522
|
-
})
|
|
2523
|
-
.finally(() => {
|
|
2178
|
+
}
|
|
2179
|
+
finally {
|
|
2524
2180
|
this.uploadQueue--;
|
|
2525
|
-
}
|
|
2181
|
+
}
|
|
2182
|
+
}
|
|
2183
|
+
isRetryableUploadStatus(status) {
|
|
2184
|
+
return status === 429 || status === 408 || status >= 500;
|
|
2185
|
+
}
|
|
2186
|
+
async waitBeforeUploadRetry(response, attempt) {
|
|
2187
|
+
const retryAfter = response?.headers.get('Retry-After');
|
|
2188
|
+
let retryDelayMs = 0;
|
|
2189
|
+
if (retryAfter) {
|
|
2190
|
+
const seconds = Number(retryAfter);
|
|
2191
|
+
if (Number.isFinite(seconds) && seconds >= 0) {
|
|
2192
|
+
retryDelayMs = seconds * 1000;
|
|
2193
|
+
}
|
|
2194
|
+
else {
|
|
2195
|
+
const retryAt = Date.parse(retryAfter);
|
|
2196
|
+
if (Number.isFinite(retryAt)) {
|
|
2197
|
+
retryDelayMs = Math.max(0, retryAt - Date.now());
|
|
2198
|
+
}
|
|
2199
|
+
}
|
|
2200
|
+
}
|
|
2201
|
+
if (retryDelayMs === 0) {
|
|
2202
|
+
retryDelayMs = 250 * 2 ** (attempt - 1);
|
|
2203
|
+
}
|
|
2204
|
+
retryDelayMs = Math.min(retryDelayMs, this.MAX_RETRY_DELAY_MS);
|
|
2205
|
+
await new Promise((resolve) => setTimeout(resolve, retryDelayMs));
|
|
2526
2206
|
}
|
|
2527
2207
|
cleanupCanvas(id) {
|
|
2528
2208
|
if (this.snapshots[id]) {
|
|
@@ -2545,8 +2225,7 @@ class CanvasRecorder {
|
|
|
2545
2225
|
}
|
|
2546
2226
|
delete this.snapshots[id];
|
|
2547
2227
|
}
|
|
2548
|
-
|
|
2549
|
-
// Flush remaining images before cleanup
|
|
2228
|
+
flushPendingSnapshots() {
|
|
2550
2229
|
Object.keys(this.snapshots).forEach((idStr) => {
|
|
2551
2230
|
const id = parseInt(idStr, 10);
|
|
2552
2231
|
const snapshot = this.snapshots[id];
|
|
@@ -2555,6 +2234,9 @@ class CanvasRecorder {
|
|
|
2555
2234
|
snapshot.images = [];
|
|
2556
2235
|
}
|
|
2557
2236
|
});
|
|
2237
|
+
}
|
|
2238
|
+
clear() {
|
|
2239
|
+
this.flushPendingSnapshots();
|
|
2558
2240
|
Object.keys(this.snapshots).forEach((idStr) => {
|
|
2559
2241
|
const id = parseInt(idStr, 10);
|
|
2560
2242
|
this.cleanupCanvas(id);
|
|
@@ -2589,40 +2271,346 @@ function captureSnapshot(canvas, quality = 'medium', dummy, fixedScaling = false
|
|
|
2589
2271
|
canvas.toBlob(onBlob, imageFormat, qualityInt[quality]);
|
|
2590
2272
|
}
|
|
2591
2273
|
}
|
|
2592
|
-
function
|
|
2593
|
-
const
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2274
|
+
async function packFrames(images) {
|
|
2275
|
+
const buffers = [];
|
|
2276
|
+
let totalSize = 0;
|
|
2277
|
+
for (const snapshot of images) {
|
|
2278
|
+
if (!snapshot.data)
|
|
2279
|
+
continue;
|
|
2280
|
+
const ab = await snapshot.data.arrayBuffer();
|
|
2281
|
+
buffers.push(ab);
|
|
2282
|
+
totalSize += 8 + 4 + ab.byteLength;
|
|
2283
|
+
}
|
|
2284
|
+
if (totalSize === 0)
|
|
2285
|
+
return null;
|
|
2286
|
+
const packed = new ArrayBuffer(totalSize);
|
|
2287
|
+
const view = new DataView(packed);
|
|
2288
|
+
const bytes = new Uint8Array(packed);
|
|
2289
|
+
let offset = 0;
|
|
2290
|
+
for (let i = 0; i < images.length; i++) {
|
|
2291
|
+
if (!images[i].data)
|
|
2292
|
+
continue;
|
|
2293
|
+
const ab = buffers.shift();
|
|
2294
|
+
const ts = images[i].id;
|
|
2295
|
+
view.setUint32(offset, ts % 0x100000000, true);
|
|
2296
|
+
view.setUint32(offset + 4, Math.floor(ts / 0x100000000), true);
|
|
2297
|
+
offset += 8;
|
|
2298
|
+
view.setUint32(offset, ab.byteLength, true);
|
|
2299
|
+
offset += 4;
|
|
2300
|
+
bytes.set(new Uint8Array(ab), offset);
|
|
2301
|
+
offset += ab.byteLength;
|
|
2302
|
+
}
|
|
2303
|
+
return packed;
|
|
2601
2304
|
}
|
|
2602
2305
|
|
|
2603
|
-
const
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2306
|
+
const CROSS_DOMAIN_IFRAME_PROTOCOL_VERSION = 1;
|
|
2307
|
+
const SECRET_PATTERN = /^[A-Za-z0-9_-]{32,128}$/;
|
|
2308
|
+
const CONTEXT_PATTERN = /^[A-Za-z0-9_-]{1,128}$/;
|
|
2309
|
+
const MAX_MESSAGE_FIELDS = 24;
|
|
2310
|
+
const MAX_MESSAGE_STRING_LENGTH = 100000;
|
|
2311
|
+
const MAX_BATCH_MESSAGES = 25000;
|
|
2312
|
+
const MAX_BATCH_BYTES = 2 * 1024 * 1024;
|
|
2313
|
+
// Cross-frame batches may contain only recording events. Identity, dictionary,
|
|
2314
|
+
// batch-header, and tab/session control opcodes remain owned by the top-level
|
|
2315
|
+
// tracker.
|
|
2316
|
+
const CROSS_DOMAIN_IFRAME_ALLOWED_MESSAGE_TYPES = new Set([
|
|
2317
|
+
4 /* MType.SetPageLocationDeprecated */,
|
|
2318
|
+
5 /* MType.SetViewportSize */,
|
|
2319
|
+
6 /* MType.SetViewportScroll */,
|
|
2320
|
+
7 /* MType.CreateDocument */,
|
|
2321
|
+
8 /* MType.CreateElementNode */,
|
|
2322
|
+
9 /* MType.CreateTextNode */,
|
|
2323
|
+
10 /* MType.MoveNode */,
|
|
2324
|
+
11 /* MType.RemoveNode */,
|
|
2325
|
+
12 /* MType.SetNodeAttribute */,
|
|
2326
|
+
13 /* MType.RemoveNodeAttribute */,
|
|
2327
|
+
14 /* MType.SetNodeData */,
|
|
2328
|
+
16 /* MType.SetNodeScroll */,
|
|
2329
|
+
17 /* MType.SetInputTarget */,
|
|
2330
|
+
18 /* MType.SetInputValue */,
|
|
2331
|
+
19 /* MType.SetInputChecked */,
|
|
2332
|
+
20 /* MType.MouseMove */,
|
|
2333
|
+
21 /* MType.NetworkRequestDeprecated */,
|
|
2334
|
+
22 /* MType.ConsoleLog */,
|
|
2335
|
+
23 /* MType.PageLoadTiming */,
|
|
2336
|
+
24 /* MType.PageRenderTiming */,
|
|
2337
|
+
27 /* MType.CustomEvent */,
|
|
2338
|
+
36 /* MType.NodeAnimationResult */,
|
|
2339
|
+
40 /* MType.Profiler */,
|
|
2340
|
+
41 /* MType.OTable */,
|
|
2341
|
+
42 /* MType.StateAction */,
|
|
2342
|
+
44 /* MType.ReduxDeprecated */,
|
|
2343
|
+
45 /* MType.Vuex */,
|
|
2344
|
+
46 /* MType.MobX */,
|
|
2345
|
+
47 /* MType.NgRx */,
|
|
2346
|
+
48 /* MType.GraphQLDeprecated */,
|
|
2347
|
+
49 /* MType.PerformanceTrack */,
|
|
2348
|
+
53 /* MType.ResourceTimingDeprecatedDeprecated */,
|
|
2349
|
+
54 /* MType.ConnectionInformation */,
|
|
2350
|
+
55 /* MType.SetPageVisibility */,
|
|
2351
|
+
57 /* MType.LoadFontFace */,
|
|
2352
|
+
58 /* MType.SetNodeFocus */,
|
|
2353
|
+
60 /* MType.SetNodeAttributeURLBased */,
|
|
2354
|
+
61 /* MType.SetCSSDataURLBased */,
|
|
2355
|
+
63 /* MType.TechnicalInfo */,
|
|
2356
|
+
64 /* MType.CustomIssue */,
|
|
2357
|
+
65 /* MType.SetNodeSlot */,
|
|
2358
|
+
68 /* MType.MouseClick */,
|
|
2359
|
+
69 /* MType.MouseClickDeprecated */,
|
|
2360
|
+
70 /* MType.CreateIFrameDocument */,
|
|
2361
|
+
71 /* MType.AdoptedSSReplaceURLBased */,
|
|
2362
|
+
73 /* MType.AdoptedSSInsertRuleURLBased */,
|
|
2363
|
+
75 /* MType.AdoptedSSDeleteRule */,
|
|
2364
|
+
76 /* MType.AdoptedSSAddOwner */,
|
|
2365
|
+
77 /* MType.AdoptedSSRemoveOwner */,
|
|
2366
|
+
78 /* MType.JSException */,
|
|
2367
|
+
79 /* MType.Zustand */,
|
|
2368
|
+
83 /* MType.NetworkRequest */,
|
|
2369
|
+
84 /* MType.WSChannel */,
|
|
2370
|
+
85 /* MType.ResourceTiming */,
|
|
2371
|
+
87 /* MType.Incident */,
|
|
2372
|
+
89 /* MType.LongAnimationTask */,
|
|
2373
|
+
112 /* MType.InputChange */,
|
|
2374
|
+
113 /* MType.SelectionChange */,
|
|
2375
|
+
114 /* MType.MouseThrashing */,
|
|
2376
|
+
115 /* MType.UnbindNodes */,
|
|
2377
|
+
116 /* MType.ResourceTimingDeprecated */,
|
|
2378
|
+
119 /* MType.CanvasNode */,
|
|
2379
|
+
120 /* MType.TagTrigger */,
|
|
2380
|
+
121 /* MType.Redux */,
|
|
2381
|
+
122 /* MType.SetPageLocation */,
|
|
2382
|
+
123 /* MType.GraphQL */,
|
|
2383
|
+
124 /* MType.WebVitals */,
|
|
2384
|
+
]);
|
|
2385
|
+
const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
2386
|
+
const isFiniteNumber = (value) => typeof value === 'number' && Number.isFinite(value);
|
|
2387
|
+
const isSafeIntegerAtLeast = (value, minimum) => typeof value === 'number' && Number.isSafeInteger(value) && value >= minimum;
|
|
2388
|
+
const isBoundedString = (value, maxLength) => typeof value === 'string' && value.length > 0 && value.length <= maxLength;
|
|
2389
|
+
const isCanvasQuality = (value) => value === 'low' || value === 'medium' || value === 'high';
|
|
2390
|
+
function normalizeCrossDomainOrigin(value) {
|
|
2391
|
+
if (typeof value !== 'string' || value.trim() === '' || value.trim() === '*') {
|
|
2392
|
+
return null;
|
|
2393
|
+
}
|
|
2394
|
+
try {
|
|
2395
|
+
const url = new URL(value.trim());
|
|
2396
|
+
if (!['http:', 'https:'].includes(url.protocol) ||
|
|
2397
|
+
url.origin === 'null' ||
|
|
2398
|
+
url.username !== '' ||
|
|
2399
|
+
url.password !== '' ||
|
|
2400
|
+
(url.pathname !== '' && url.pathname !== '/') ||
|
|
2401
|
+
url.search !== '' ||
|
|
2402
|
+
url.hash !== '') {
|
|
2403
|
+
return null;
|
|
2404
|
+
}
|
|
2405
|
+
return url.origin;
|
|
2406
|
+
}
|
|
2407
|
+
catch {
|
|
2408
|
+
return null;
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
2411
|
+
function normalizeCrossDomainOrigins(values) {
|
|
2412
|
+
if (!Array.isArray(values)) {
|
|
2413
|
+
return new Set();
|
|
2414
|
+
}
|
|
2415
|
+
return new Set(values.map(normalizeCrossDomainOrigin).filter((origin) => origin !== null));
|
|
2416
|
+
}
|
|
2417
|
+
function createCrossDomainSecret() {
|
|
2418
|
+
if (!globalThis.crypto?.getRandomValues) {
|
|
2419
|
+
return null;
|
|
2420
|
+
}
|
|
2421
|
+
const bytes = new Uint8Array(24);
|
|
2422
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
2423
|
+
return Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0')).join('');
|
|
2424
|
+
}
|
|
2425
|
+
function parseCrossDomainHandshake(data, expectedLine, expectedProjectKey) {
|
|
2426
|
+
if (!isRecord(data) ||
|
|
2427
|
+
data.line !== expectedLine ||
|
|
2428
|
+
data.protocolVersion !== CROSS_DOMAIN_IFRAME_PROTOCOL_VERSION ||
|
|
2429
|
+
data.projectKey !== expectedProjectKey ||
|
|
2430
|
+
!isBoundedString(data.context, 128) ||
|
|
2431
|
+
!CONTEXT_PATTERN.test(data.context) ||
|
|
2432
|
+
!isBoundedString(data.nonce, 128) ||
|
|
2433
|
+
!SECRET_PATTERN.test(data.nonce)) {
|
|
2434
|
+
return null;
|
|
2435
|
+
}
|
|
2436
|
+
return {
|
|
2437
|
+
protocolVersion: CROSS_DOMAIN_IFRAME_PROTOCOL_VERSION,
|
|
2438
|
+
context: data.context,
|
|
2439
|
+
nonce: data.nonce,
|
|
2440
|
+
projectKey: expectedProjectKey,
|
|
2441
|
+
};
|
|
2442
|
+
}
|
|
2443
|
+
function matchesCrossDomainEnvelope(data, expectedLine, expected) {
|
|
2444
|
+
return (isRecord(data) &&
|
|
2445
|
+
data.line === expectedLine &&
|
|
2446
|
+
data.protocolVersion === CROSS_DOMAIN_IFRAME_PROTOCOL_VERSION &&
|
|
2447
|
+
data.context === expected.context &&
|
|
2448
|
+
data.nonce === expected.nonce &&
|
|
2449
|
+
data.capability === expected.capability);
|
|
2450
|
+
}
|
|
2451
|
+
function parseCrossDomainFrameInit(data, expectedLine, expected) {
|
|
2452
|
+
if (!isRecord(data) ||
|
|
2453
|
+
data.line !== expectedLine ||
|
|
2454
|
+
data.protocolVersion !== CROSS_DOMAIN_IFRAME_PROTOCOL_VERSION ||
|
|
2455
|
+
data.context !== expected.context ||
|
|
2456
|
+
data.nonce !== expected.nonce ||
|
|
2457
|
+
!isBoundedString(data.capability, 128) ||
|
|
2458
|
+
!SECRET_PATTERN.test(data.capability) ||
|
|
2459
|
+
!isSafeIntegerAtLeast(data.id, 0) ||
|
|
2460
|
+
!isSafeIntegerAtLeast(data.frameOrderNumber, 1) ||
|
|
2461
|
+
!isSafeIntegerAtLeast(data.frameLevel, 0) ||
|
|
2462
|
+
!isSafeIntegerAtLeast(data.capabilityExpiresAt, 1) ||
|
|
2463
|
+
data.capabilityExpiresAt <= Date.now() ||
|
|
2464
|
+
!isRecord(data.session)) {
|
|
2465
|
+
return null;
|
|
2466
|
+
}
|
|
2467
|
+
const session = data.session;
|
|
2468
|
+
if (!isBoundedString(session.sessionID, 256) ||
|
|
2469
|
+
!isFiniteNumber(session.startedAt) ||
|
|
2470
|
+
!isFiniteNumber(session.delayMs) ||
|
|
2471
|
+
typeof session.userUUID !== 'string' ||
|
|
2472
|
+
session.userUUID.length > 256) {
|
|
2473
|
+
return null;
|
|
2474
|
+
}
|
|
2475
|
+
let projectID;
|
|
2476
|
+
if (session.projectID !== undefined) {
|
|
2477
|
+
if (!isBoundedString(session.projectID, 256)) {
|
|
2478
|
+
return null;
|
|
2479
|
+
}
|
|
2480
|
+
projectID = session.projectID;
|
|
2481
|
+
}
|
|
2482
|
+
const canvas = parseCanvasConfig(data.canvas);
|
|
2483
|
+
if (data.canvas !== undefined && canvas === null) {
|
|
2484
|
+
return null;
|
|
2485
|
+
}
|
|
2486
|
+
return {
|
|
2487
|
+
protocolVersion: CROSS_DOMAIN_IFRAME_PROTOCOL_VERSION,
|
|
2488
|
+
context: data.context,
|
|
2489
|
+
nonce: data.nonce,
|
|
2490
|
+
capability: data.capability,
|
|
2491
|
+
id: data.id,
|
|
2492
|
+
frameOrderNumber: data.frameOrderNumber,
|
|
2493
|
+
frameLevel: data.frameLevel,
|
|
2494
|
+
capabilityExpiresAt: data.capabilityExpiresAt,
|
|
2495
|
+
session: {
|
|
2496
|
+
sessionID: session.sessionID,
|
|
2497
|
+
projectID,
|
|
2498
|
+
startedAt: session.startedAt,
|
|
2499
|
+
delayMs: session.delayMs,
|
|
2500
|
+
userUUID: session.userUUID,
|
|
2501
|
+
},
|
|
2502
|
+
...(canvas ? { canvas } : {}),
|
|
2503
|
+
};
|
|
2504
|
+
}
|
|
2505
|
+
function parseCanvasConfig(value) {
|
|
2506
|
+
if (value === undefined) {
|
|
2507
|
+
return null;
|
|
2508
|
+
}
|
|
2509
|
+
if (!isRecord(value) || typeof value.enabled !== 'boolean') {
|
|
2510
|
+
return null;
|
|
2511
|
+
}
|
|
2512
|
+
let quality;
|
|
2513
|
+
if (value.quality !== undefined) {
|
|
2514
|
+
if (!isCanvasQuality(value.quality)) {
|
|
2515
|
+
return null;
|
|
2516
|
+
}
|
|
2517
|
+
quality = value.quality;
|
|
2518
|
+
}
|
|
2519
|
+
let fps;
|
|
2520
|
+
if (value.fps !== undefined) {
|
|
2521
|
+
if (!isFiniteNumber(value.fps)) {
|
|
2522
|
+
return null;
|
|
2523
|
+
}
|
|
2524
|
+
fps = value.fps;
|
|
2525
|
+
}
|
|
2526
|
+
let framesSupport;
|
|
2527
|
+
if (value.framesSupport !== undefined) {
|
|
2528
|
+
if (typeof value.framesSupport !== 'boolean') {
|
|
2529
|
+
return null;
|
|
2530
|
+
}
|
|
2531
|
+
framesSupport = value.framesSupport;
|
|
2532
|
+
}
|
|
2533
|
+
return {
|
|
2534
|
+
enabled: value.enabled,
|
|
2535
|
+
quality,
|
|
2536
|
+
fps,
|
|
2537
|
+
framesSupport,
|
|
2538
|
+
};
|
|
2539
|
+
}
|
|
2540
|
+
function parseCrossDomainIframeBatch(messages) {
|
|
2541
|
+
if (!Array.isArray(messages) || messages.length > MAX_BATCH_MESSAGES) {
|
|
2542
|
+
return null;
|
|
2543
|
+
}
|
|
2544
|
+
let bytes = 0;
|
|
2545
|
+
for (const message of messages) {
|
|
2546
|
+
if (!Array.isArray(message) || message.length < 1 || message.length > MAX_MESSAGE_FIELDS) {
|
|
2547
|
+
return null;
|
|
2548
|
+
}
|
|
2549
|
+
const messageType = message[0];
|
|
2550
|
+
if (typeof messageType !== 'number' ||
|
|
2551
|
+
!Number.isInteger(messageType) ||
|
|
2552
|
+
!CROSS_DOMAIN_IFRAME_ALLOWED_MESSAGE_TYPES.has(messageType)) {
|
|
2553
|
+
return null;
|
|
2554
|
+
}
|
|
2555
|
+
const expectedFieldTypes = MESSAGE_FIELD_TYPES[messageType];
|
|
2556
|
+
if (expectedFieldTypes === undefined || message.length !== expectedFieldTypes.length + 1) {
|
|
2557
|
+
return null;
|
|
2558
|
+
}
|
|
2559
|
+
bytes += 8;
|
|
2560
|
+
for (let fieldIndex = 0; fieldIndex < expectedFieldTypes.length; fieldIndex++) {
|
|
2561
|
+
const field = message[fieldIndex + 1];
|
|
2562
|
+
if (typeof field !== expectedFieldTypes[fieldIndex]) {
|
|
2563
|
+
return null;
|
|
2564
|
+
}
|
|
2565
|
+
if (typeof field === 'string') {
|
|
2566
|
+
if (field.length > MAX_MESSAGE_STRING_LENGTH) {
|
|
2567
|
+
return null;
|
|
2568
|
+
}
|
|
2569
|
+
bytes += field.length * 3;
|
|
2570
|
+
}
|
|
2571
|
+
else if (typeof field === 'number') {
|
|
2572
|
+
if (!Number.isFinite(field)) {
|
|
2573
|
+
return null;
|
|
2574
|
+
}
|
|
2575
|
+
bytes += 8;
|
|
2576
|
+
}
|
|
2577
|
+
else if (typeof field === 'boolean') {
|
|
2578
|
+
bytes += 1;
|
|
2579
|
+
}
|
|
2580
|
+
else {
|
|
2581
|
+
return null;
|
|
2582
|
+
}
|
|
2583
|
+
if (bytes > MAX_BATCH_BYTES) {
|
|
2584
|
+
return null;
|
|
2585
|
+
}
|
|
2586
|
+
}
|
|
2587
|
+
}
|
|
2588
|
+
return { messages: messages, bytes };
|
|
2589
|
+
}
|
|
2590
|
+
|
|
2591
|
+
const LogLevel = {
|
|
2592
|
+
Verbose: 5,
|
|
2593
|
+
Log: 4,
|
|
2594
|
+
Warnings: 3,
|
|
2595
|
+
Errors: 2,
|
|
2596
|
+
Silent: 0,
|
|
2597
|
+
};
|
|
2598
|
+
class Logger {
|
|
2599
|
+
constructor(debugLevel = LogLevel.Silent) {
|
|
2600
|
+
this.shouldLog = (level) => {
|
|
2601
|
+
return this.level >= level;
|
|
2602
|
+
};
|
|
2603
|
+
this.info = (...args) => {
|
|
2604
|
+
if (this.shouldLog(LogLevel.Verbose)) {
|
|
2605
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
2606
|
+
console.info(...args);
|
|
2607
|
+
}
|
|
2608
|
+
};
|
|
2609
|
+
this.log = (...args) => {
|
|
2610
|
+
if (this.shouldLog(LogLevel.Log)) {
|
|
2611
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
2612
|
+
console.log(...args);
|
|
2613
|
+
}
|
|
2626
2614
|
};
|
|
2627
2615
|
this.warn = (...args) => {
|
|
2628
2616
|
if (this.shouldLog(LogLevel.Warnings)) {
|
|
@@ -2878,8 +2866,44 @@ function inlineRemoteCss(node, id, baseHref, getNextID, insertRule, addOwner, fo
|
|
|
2878
2866
|
})
|
|
2879
2867
|
.catch(error => {
|
|
2880
2868
|
console.error(`OxvoSessions: Failed to fetch CSS from ${node.href}:`, error);
|
|
2869
|
+
// Fetch failed, likely due to CORS. Retry via load event and sheet access.
|
|
2870
|
+
retryViaLoadEvent();
|
|
2881
2871
|
});
|
|
2882
2872
|
}
|
|
2873
|
+
else {
|
|
2874
|
+
retryViaLoadEvent();
|
|
2875
|
+
}
|
|
2876
|
+
function retryViaLoadEvent() {
|
|
2877
|
+
const trySheet = () => {
|
|
2878
|
+
const loadedSheet = node.sheet;
|
|
2879
|
+
if (loadedSheet) {
|
|
2880
|
+
try {
|
|
2881
|
+
const cssText = stringifyStylesheet(loadedSheet);
|
|
2882
|
+
if (cssText) {
|
|
2883
|
+
if (sendPlain && onPlain) {
|
|
2884
|
+
onPlain(cssText, fakeIdHolder++);
|
|
2885
|
+
}
|
|
2886
|
+
else {
|
|
2887
|
+
processCssText(cssText);
|
|
2888
|
+
}
|
|
2889
|
+
return;
|
|
2890
|
+
}
|
|
2891
|
+
}
|
|
2892
|
+
catch (e) {
|
|
2893
|
+
// Cross-origin sheet. Nothing more can be read safely here.
|
|
2894
|
+
}
|
|
2895
|
+
}
|
|
2896
|
+
};
|
|
2897
|
+
if (node.sheet) {
|
|
2898
|
+
trySheet();
|
|
2899
|
+
}
|
|
2900
|
+
else {
|
|
2901
|
+
node.addEventListener('load', function onLoad() {
|
|
2902
|
+
node.removeEventListener('load', onLoad);
|
|
2903
|
+
trySheet();
|
|
2904
|
+
});
|
|
2905
|
+
}
|
|
2906
|
+
}
|
|
2883
2907
|
function processCssText(cssText) {
|
|
2884
2908
|
// Remove comments
|
|
2885
2909
|
cssText = cssText.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
@@ -3101,7 +3125,6 @@ function ConstructedStyleSheets (app) {
|
|
|
3101
3125
|
if (!hasAdoptedSS(document)) {
|
|
3102
3126
|
return;
|
|
3103
3127
|
}
|
|
3104
|
-
const styleSheetIDMap = new Map();
|
|
3105
3128
|
const adoptedStyleSheetsOwnings = new Map();
|
|
3106
3129
|
const sendAdoptedStyleSheetsUpdate = (root) => setTimeout(() => {
|
|
3107
3130
|
let nodeID = app.nodes.getID(root);
|
|
@@ -3215,6 +3238,119 @@ function ConstructedStyleSheets (app) {
|
|
|
3215
3238
|
});
|
|
3216
3239
|
}
|
|
3217
3240
|
|
|
3241
|
+
var SanitizeLevel;
|
|
3242
|
+
(function (SanitizeLevel) {
|
|
3243
|
+
SanitizeLevel[SanitizeLevel["Plain"] = 0] = "Plain";
|
|
3244
|
+
SanitizeLevel[SanitizeLevel["Obscured"] = 1] = "Obscured";
|
|
3245
|
+
SanitizeLevel[SanitizeLevel["Hidden"] = 2] = "Hidden";
|
|
3246
|
+
})(SanitizeLevel || (SanitizeLevel = {}));
|
|
3247
|
+
const stringWiper = (input) => input
|
|
3248
|
+
.trim()
|
|
3249
|
+
.replace(/[^\f\n\r\t\v\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff\s]/g, '*');
|
|
3250
|
+
class Sanitizer {
|
|
3251
|
+
constructor(params) {
|
|
3252
|
+
// Node id -> level. Plain (0) is never stored; a missing entry means Plain.
|
|
3253
|
+
// A map (not the old grow-only Sets) so levels can be raised and lowered.
|
|
3254
|
+
this.levels = new Map();
|
|
3255
|
+
this.app = params.app;
|
|
3256
|
+
const defaultOptions = {
|
|
3257
|
+
maskTextEmails: true,
|
|
3258
|
+
maskTextNumbers: false,
|
|
3259
|
+
maskAllByDefault: false,
|
|
3260
|
+
domSanitizer: undefined,
|
|
3261
|
+
};
|
|
3262
|
+
this.maskAllByDefault = params.options?.maskAllByDefault ?? false;
|
|
3263
|
+
this.options = Object.assign(defaultOptions, params.options);
|
|
3264
|
+
}
|
|
3265
|
+
// Pure recomputation of a node's level from the live DOM + parent level.
|
|
3266
|
+
// Reading current state on every call lets resanitize() pick up runtime changes.
|
|
3267
|
+
computeLevel(node, parentLevel) {
|
|
3268
|
+
if (this.options.maskAllByDefault) {
|
|
3269
|
+
if (isElementNode(node) && !hasOxvoSessionsAttribute(node, 'unmask')) {
|
|
3270
|
+
return SanitizeLevel.Obscured;
|
|
3271
|
+
}
|
|
3272
|
+
if (isTextNode(node) && !hasOxvoSessionsAttribute(node.parentNode, 'unmask')) {
|
|
3273
|
+
return SanitizeLevel.Obscured;
|
|
3274
|
+
}
|
|
3275
|
+
}
|
|
3276
|
+
let level = SanitizeLevel.Plain;
|
|
3277
|
+
if (parentLevel >= SanitizeLevel.Obscured ||
|
|
3278
|
+
(isElementNode(node) &&
|
|
3279
|
+
(hasOxvoSessionsAttribute(node, 'masked') || hasOxvoSessionsAttribute(node, 'obscured')))) {
|
|
3280
|
+
level = SanitizeLevel.Obscured;
|
|
3281
|
+
}
|
|
3282
|
+
if (parentLevel === SanitizeLevel.Hidden ||
|
|
3283
|
+
(isElementNode(node) &&
|
|
3284
|
+
(hasOxvoSessionsAttribute(node, 'htmlmasked') || hasOxvoSessionsAttribute(node, 'hidden')))) {
|
|
3285
|
+
level = SanitizeLevel.Hidden;
|
|
3286
|
+
}
|
|
3287
|
+
if (this.options.domSanitizer !== undefined && isElementNode(node)) {
|
|
3288
|
+
const sanitizeLevel = this.options.domSanitizer(node);
|
|
3289
|
+
if (sanitizeLevel === SanitizeLevel.Obscured && level < SanitizeLevel.Obscured) {
|
|
3290
|
+
level = SanitizeLevel.Obscured;
|
|
3291
|
+
}
|
|
3292
|
+
if (sanitizeLevel === SanitizeLevel.Hidden) {
|
|
3293
|
+
level = SanitizeLevel.Hidden;
|
|
3294
|
+
}
|
|
3295
|
+
}
|
|
3296
|
+
return level;
|
|
3297
|
+
}
|
|
3298
|
+
getLevel(id) {
|
|
3299
|
+
return this.levels.get(id) ?? SanitizeLevel.Plain;
|
|
3300
|
+
}
|
|
3301
|
+
// Sets a node's level in either direction and returns the previous level.
|
|
3302
|
+
setLevel(id, level) {
|
|
3303
|
+
const prev = this.getLevel(id);
|
|
3304
|
+
if (level === SanitizeLevel.Plain) {
|
|
3305
|
+
this.levels.delete(id);
|
|
3306
|
+
}
|
|
3307
|
+
else {
|
|
3308
|
+
this.levels.set(id, level);
|
|
3309
|
+
}
|
|
3310
|
+
return prev;
|
|
3311
|
+
}
|
|
3312
|
+
handleNode(id, parentID, node) {
|
|
3313
|
+
const level = this.computeLevel(node, this.getLevel(parentID));
|
|
3314
|
+
// Escalate-only: commits never lower a level, only resanitize/setLevel do.
|
|
3315
|
+
if (level > this.getLevel(id)) {
|
|
3316
|
+
this.setLevel(id, level);
|
|
3317
|
+
}
|
|
3318
|
+
}
|
|
3319
|
+
sanitize(id, data) {
|
|
3320
|
+
if (this.getLevel(id) >= SanitizeLevel.Obscured) {
|
|
3321
|
+
// TODO: is it the best place to put trim() ? Might trimmed spaces be considered in layout in certain cases?
|
|
3322
|
+
return stringWiper(data);
|
|
3323
|
+
}
|
|
3324
|
+
if (this.options.maskTextNumbers) {
|
|
3325
|
+
data = data.replace(/\d/g, '0');
|
|
3326
|
+
}
|
|
3327
|
+
if (this.options.maskTextEmails) {
|
|
3328
|
+
data = data.replace(/^\w+([+.-]\w+)*@\w+([.-]\w+)*\.\w{2,3}$/g, (email) => {
|
|
3329
|
+
const [name, domain] = email.split('@');
|
|
3330
|
+
const [domainName, host] = domain.split('.');
|
|
3331
|
+
return `${stars(name)}@${stars(domainName)}.${stars(host)}`;
|
|
3332
|
+
});
|
|
3333
|
+
}
|
|
3334
|
+
return data;
|
|
3335
|
+
}
|
|
3336
|
+
isObscured(id) {
|
|
3337
|
+
return this.getLevel(id) >= SanitizeLevel.Obscured;
|
|
3338
|
+
}
|
|
3339
|
+
isHidden(id) {
|
|
3340
|
+
return this.getLevel(id) === SanitizeLevel.Hidden;
|
|
3341
|
+
}
|
|
3342
|
+
getInnerTextSecure(el) {
|
|
3343
|
+
const id = this.app.nodes.getID(el);
|
|
3344
|
+
if (!id) {
|
|
3345
|
+
return '';
|
|
3346
|
+
}
|
|
3347
|
+
return this.sanitize(id, el.innerText);
|
|
3348
|
+
}
|
|
3349
|
+
clear() {
|
|
3350
|
+
this.levels.clear();
|
|
3351
|
+
}
|
|
3352
|
+
}
|
|
3353
|
+
|
|
3218
3354
|
const iconCache = {};
|
|
3219
3355
|
const svgUrlCache = {};
|
|
3220
3356
|
async function parseUseEl(useElement, mode, domParser) {
|
|
@@ -3366,6 +3502,12 @@ class Observer {
|
|
|
3366
3502
|
this.inlineRemoteCss = false;
|
|
3367
3503
|
this.inlinerOptions = undefined;
|
|
3368
3504
|
this.domParser = new DOMParser();
|
|
3505
|
+
/**
|
|
3506
|
+
* Bumped on every disconnect(). Stale async CSS-inlining callbacks from a
|
|
3507
|
+
* previous session (e.g. agent reload → tracker restart) compare against
|
|
3508
|
+
* this and bail instead of sending messages that reference vanished node ids.
|
|
3509
|
+
*/
|
|
3510
|
+
this.generation = 0;
|
|
3369
3511
|
this.throttling = true;
|
|
3370
3512
|
this.throttledSetNodeData = throttleWithTrailing((id, parentElement, data) => this.sendNodeData(id, parentElement, data), 30);
|
|
3371
3513
|
this.throttling = !Boolean(options.disableThrottling);
|
|
@@ -3512,18 +3654,23 @@ class Observer {
|
|
|
3512
3654
|
}
|
|
3513
3655
|
if (name === 'style' || (name === 'href' && hasTag(node, 'link'))) {
|
|
3514
3656
|
if ('rel' in node && node.rel === 'stylesheet' && this.inlineRemoteCss) {
|
|
3657
|
+
const gen = this.generation;
|
|
3515
3658
|
setTimeout(() => {
|
|
3516
3659
|
inlineRemoteCss(
|
|
3517
3660
|
// @ts-ignore
|
|
3518
3661
|
node, id, this.app.getBaseHref(), nextID, (id, cssText, index, baseHref) => {
|
|
3662
|
+
if (this.generation !== gen)
|
|
3663
|
+
return;
|
|
3519
3664
|
this.app.send(AdoptedSSInsertRuleURLBased(id, cssText, index, baseHref));
|
|
3520
3665
|
}, (sheetId, ownerId) => {
|
|
3666
|
+
if (this.generation !== gen)
|
|
3667
|
+
return;
|
|
3521
3668
|
this.app.send(AdoptedSSAddOwner(sheetId, ownerId));
|
|
3522
3669
|
}, this.inlinerOptions?.forceFetch, this.inlinerOptions?.forcePlain, (cssText, fakeTextId) => {
|
|
3670
|
+
if (this.generation !== gen)
|
|
3671
|
+
return;
|
|
3523
3672
|
this.app.send(CreateTextNode(fakeTextId, id, 0));
|
|
3524
|
-
|
|
3525
|
-
this.app.send(SetCSSDataURLBased(fakeTextId, cssText, this.app.getBaseHref()));
|
|
3526
|
-
}, 10);
|
|
3673
|
+
this.app.send(SetCSSDataURLBased(fakeTextId, cssText, this.app.getBaseHref()));
|
|
3527
3674
|
});
|
|
3528
3675
|
}, 0);
|
|
3529
3676
|
return;
|
|
@@ -3783,10 +3930,105 @@ class Observer {
|
|
|
3783
3930
|
beforeCommit(this.app.nodes.getID(node));
|
|
3784
3931
|
this.commitNodes(true);
|
|
3785
3932
|
}
|
|
3933
|
+
/**
|
|
3934
|
+
* Re-evaluates sanitization for every tracked node in `root`'s subtree against
|
|
3935
|
+
* the current DOM and re-emits whatever changed. Pass the highest node you
|
|
3936
|
+
* changed (or the document root) so inherited levels propagate correctly.
|
|
3937
|
+
*/
|
|
3938
|
+
resanitizeSubtree(root) {
|
|
3939
|
+
if (!isObservable(root)) {
|
|
3940
|
+
return;
|
|
3941
|
+
}
|
|
3942
|
+
const parent = root.parentNode;
|
|
3943
|
+
const parentId = parent !== null ? this.app.nodes.getID(parent) : undefined;
|
|
3944
|
+
const parentLevel = parentId !== undefined ? this.app.sanitizer.getLevel(parentId) : SanitizeLevel.Plain;
|
|
3945
|
+
this.resanitizeNode(root, parentLevel);
|
|
3946
|
+
}
|
|
3947
|
+
resanitizeNode(node, parentLevel) {
|
|
3948
|
+
if (isIgnored(node)) {
|
|
3949
|
+
return;
|
|
3950
|
+
}
|
|
3951
|
+
const id = this.app.nodes.getID(node);
|
|
3952
|
+
if (id === undefined) {
|
|
3953
|
+
// Untracked (new, or under a hidden ancestor): the live observer handles it.
|
|
3954
|
+
return;
|
|
3955
|
+
}
|
|
3956
|
+
const newLevel = this.app.sanitizer.computeLevel(node, parentLevel);
|
|
3957
|
+
const prevLevel = this.app.sanitizer.getLevel(id);
|
|
3958
|
+
const wasHidden = prevLevel === SanitizeLevel.Hidden;
|
|
3959
|
+
const willHidden = newLevel === SanitizeLevel.Hidden;
|
|
3960
|
+
// Crossing the hidden boundary changes the rendered structure (placeholder vs
|
|
3961
|
+
// real subtree), so rebuild rather than re-emit.
|
|
3962
|
+
if (wasHidden !== willHidden) {
|
|
3963
|
+
this.recreateSubtree(node);
|
|
3964
|
+
return;
|
|
3965
|
+
}
|
|
3966
|
+
if (willHidden) {
|
|
3967
|
+
return;
|
|
3968
|
+
}
|
|
3969
|
+
// Plain <-> Obscured: same structure, only leaf content changes.
|
|
3970
|
+
if (prevLevel !== newLevel) {
|
|
3971
|
+
this.app.sanitizer.setLevel(id, newLevel);
|
|
3972
|
+
this.reemitNode(id, node);
|
|
3973
|
+
}
|
|
3974
|
+
for (let child = node.firstChild; child !== null; child = child.nextSibling) {
|
|
3975
|
+
this.resanitizeNode(child, newLevel);
|
|
3976
|
+
}
|
|
3977
|
+
}
|
|
3978
|
+
// Destroys the node player-side and re-emits its subtree from scratch (new ids)
|
|
3979
|
+
// so it materializes at the freshly-computed level.
|
|
3980
|
+
recreateSubtree(node) {
|
|
3981
|
+
const id = this.app.nodes.getID(node);
|
|
3982
|
+
if (id === undefined) {
|
|
3983
|
+
return;
|
|
3984
|
+
}
|
|
3985
|
+
this.app.send(RemoveNode(id));
|
|
3986
|
+
this.clearSubtreeRegistration(node);
|
|
3987
|
+
this.bindTree(node);
|
|
3988
|
+
this.commitNodes();
|
|
3989
|
+
}
|
|
3990
|
+
clearSubtreeRegistration(node) {
|
|
3991
|
+
const clearOne = (n) => {
|
|
3992
|
+
const oldId = this.app.nodes.getID(n);
|
|
3993
|
+
if (oldId !== undefined) {
|
|
3994
|
+
this.app.sanitizer.setLevel(oldId, SanitizeLevel.Plain);
|
|
3995
|
+
}
|
|
3996
|
+
this.app.nodes.unregisterNode(n);
|
|
3997
|
+
};
|
|
3998
|
+
const walker = document.createTreeWalker(node, NodeFilter.SHOW_ELEMENT + NodeFilter.SHOW_TEXT, {
|
|
3999
|
+
acceptNode: (n) => isIgnored(n) || this.app.nodes.getID(n) === undefined
|
|
4000
|
+
? NodeFilter.FILTER_REJECT
|
|
4001
|
+
: NodeFilter.FILTER_ACCEPT,
|
|
4002
|
+
},
|
|
4003
|
+
// @ts-ignore
|
|
4004
|
+
false);
|
|
4005
|
+
// Collect first, then clear: unregistering mutates the ids the walker reads.
|
|
4006
|
+
const subtree = [];
|
|
4007
|
+
while (walker.nextNode()) {
|
|
4008
|
+
subtree.push(walker.currentNode);
|
|
4009
|
+
}
|
|
4010
|
+
clearOne(node);
|
|
4011
|
+
subtree.forEach(clearOne);
|
|
4012
|
+
}
|
|
4013
|
+
reemitNode(id, node) {
|
|
4014
|
+
if (isTextNode(node)) {
|
|
4015
|
+
const parent = node.parentNode;
|
|
4016
|
+
if (parent !== null && isElementNode(parent)) {
|
|
4017
|
+
// re-runs sanitize() at the level we just set
|
|
4018
|
+
this.sendNodeData(id, parent, node.data);
|
|
4019
|
+
}
|
|
4020
|
+
return;
|
|
4021
|
+
}
|
|
4022
|
+
if (isElementNode(node)) {
|
|
4023
|
+
// inputs/images/canvas re-emit their own payload via registered callbacks
|
|
4024
|
+
this.app.callResanitizeCallbacks(node, id);
|
|
4025
|
+
}
|
|
4026
|
+
}
|
|
3786
4027
|
disconnect() {
|
|
3787
4028
|
this.observer.disconnect();
|
|
3788
4029
|
this.clear();
|
|
3789
4030
|
this.throttledSetNodeData.clear();
|
|
4031
|
+
this.generation++;
|
|
3790
4032
|
}
|
|
3791
4033
|
}
|
|
3792
4034
|
|
|
@@ -4021,7 +4263,10 @@ class TopObserver extends Observer {
|
|
|
4021
4263
|
this.app.debug.info('doc already observed for', id);
|
|
4022
4264
|
return;
|
|
4023
4265
|
}
|
|
4024
|
-
const observer = new IFrameObserver(this.app, false, {
|
|
4266
|
+
const observer = new IFrameObserver(this.app, false, {
|
|
4267
|
+
disableSprites: this.options.disableSprites,
|
|
4268
|
+
...getInlineOptions(this.options.inlineCss, console.warn),
|
|
4269
|
+
});
|
|
4025
4270
|
this.iframeObservers.set(iframe, observer);
|
|
4026
4271
|
this.docObservers.set(currentDoc, observer);
|
|
4027
4272
|
this.iframeObserversArr.push(observer);
|
|
@@ -4043,7 +4288,10 @@ class TopObserver extends Observer {
|
|
|
4043
4288
|
handle();
|
|
4044
4289
|
}
|
|
4045
4290
|
handleShadowRoot(shRoot) {
|
|
4046
|
-
const observer = new ShadowRootObserver(this.app
|
|
4291
|
+
const observer = new ShadowRootObserver(this.app, false, {
|
|
4292
|
+
disableSprites: this.options.disableSprites,
|
|
4293
|
+
...getInlineOptions(this.options.inlineCss, console.warn),
|
|
4294
|
+
});
|
|
4047
4295
|
this.shadowRootObservers.set(shRoot, observer);
|
|
4048
4296
|
observer.observe(shRoot.host);
|
|
4049
4297
|
}
|
|
@@ -4079,7 +4327,10 @@ class TopObserver extends Observer {
|
|
|
4079
4327
|
};
|
|
4080
4328
|
this.app.nodes.clear();
|
|
4081
4329
|
this.app.nodes.crossdomainMode(frameLevel, frameOder);
|
|
4082
|
-
const iframeObserver = new IFrameObserver(this.app
|
|
4330
|
+
const iframeObserver = new IFrameObserver(this.app, false, {
|
|
4331
|
+
disableSprites: this.options.disableSprites,
|
|
4332
|
+
...getInlineOptions(this.options.inlineCss, console.warn),
|
|
4333
|
+
});
|
|
4083
4334
|
this.iframeObservers.set(window.document, iframeObserver);
|
|
4084
4335
|
iframeObserver.syntheticObserve(rootNodeId, window.document);
|
|
4085
4336
|
}
|
|
@@ -4095,94 +4346,6 @@ class TopObserver extends Observer {
|
|
|
4095
4346
|
}
|
|
4096
4347
|
}
|
|
4097
4348
|
|
|
4098
|
-
var SanitizeLevel;
|
|
4099
|
-
(function (SanitizeLevel) {
|
|
4100
|
-
SanitizeLevel[SanitizeLevel["Plain"] = 0] = "Plain";
|
|
4101
|
-
SanitizeLevel[SanitizeLevel["Obscured"] = 1] = "Obscured";
|
|
4102
|
-
SanitizeLevel[SanitizeLevel["Hidden"] = 2] = "Hidden";
|
|
4103
|
-
})(SanitizeLevel || (SanitizeLevel = {}));
|
|
4104
|
-
const stringWiper = (input) => input
|
|
4105
|
-
.trim()
|
|
4106
|
-
.replace(/[^\f\n\r\t\v\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff\s]/g, '*');
|
|
4107
|
-
class Sanitizer {
|
|
4108
|
-
constructor(params) {
|
|
4109
|
-
this.obscured = new Set();
|
|
4110
|
-
this.hidden = new Set();
|
|
4111
|
-
this.app = params.app;
|
|
4112
|
-
const defaultOptions = {
|
|
4113
|
-
maskTextEmails: true,
|
|
4114
|
-
maskTextNumbers: false,
|
|
4115
|
-
maskAllByDefault: false,
|
|
4116
|
-
domSanitizer: undefined,
|
|
4117
|
-
};
|
|
4118
|
-
this.maskAllByDefault = params.options?.maskAllByDefault ?? false;
|
|
4119
|
-
this.options = Object.assign(defaultOptions, params.options);
|
|
4120
|
-
}
|
|
4121
|
-
handleNode(id, parentID, node) {
|
|
4122
|
-
if (this.options.maskAllByDefault) {
|
|
4123
|
-
if (isElementNode(node) && !hasOxvoSessionsAttribute(node, 'unmask')) {
|
|
4124
|
-
return this.obscured.add(id);
|
|
4125
|
-
}
|
|
4126
|
-
if (isTextNode(node) && !hasOxvoSessionsAttribute(node.parentNode, 'unmask')) {
|
|
4127
|
-
return this.obscured.add(id);
|
|
4128
|
-
}
|
|
4129
|
-
}
|
|
4130
|
-
if (this.obscured.has(parentID) ||
|
|
4131
|
-
(isElementNode(node) &&
|
|
4132
|
-
(hasOxvoSessionsAttribute(node, 'masked') || hasOxvoSessionsAttribute(node, 'obscured')))) {
|
|
4133
|
-
this.obscured.add(id);
|
|
4134
|
-
}
|
|
4135
|
-
if (this.hidden.has(parentID) ||
|
|
4136
|
-
(isElementNode(node) &&
|
|
4137
|
-
(hasOxvoSessionsAttribute(node, 'htmlmasked') || hasOxvoSessionsAttribute(node, 'hidden')))) {
|
|
4138
|
-
this.hidden.add(id);
|
|
4139
|
-
}
|
|
4140
|
-
if (this.options.domSanitizer !== undefined && isElementNode(node)) {
|
|
4141
|
-
const sanitizeLevel = this.options.domSanitizer(node);
|
|
4142
|
-
if (sanitizeLevel === SanitizeLevel.Obscured) {
|
|
4143
|
-
this.obscured.add(id);
|
|
4144
|
-
}
|
|
4145
|
-
if (sanitizeLevel === SanitizeLevel.Hidden) {
|
|
4146
|
-
this.hidden.add(id);
|
|
4147
|
-
}
|
|
4148
|
-
}
|
|
4149
|
-
}
|
|
4150
|
-
sanitize(id, data) {
|
|
4151
|
-
if (this.obscured.has(id)) {
|
|
4152
|
-
// TODO: is it the best place to put trim() ? Might trimmed spaces be considered in layout in certain cases?
|
|
4153
|
-
return stringWiper(data);
|
|
4154
|
-
}
|
|
4155
|
-
if (this.options.maskTextNumbers) {
|
|
4156
|
-
data = data.replace(/\d/g, '0');
|
|
4157
|
-
}
|
|
4158
|
-
if (this.options.maskTextEmails) {
|
|
4159
|
-
data = data.replace(/^\w+([+.-]\w+)*@\w+([.-]\w+)*\.\w{2,3}$/g, (email) => {
|
|
4160
|
-
const [name, domain] = email.split('@');
|
|
4161
|
-
const [domainName, host] = domain.split('.');
|
|
4162
|
-
return `${stars(name)}@${stars(domainName)}.${stars(host)}`;
|
|
4163
|
-
});
|
|
4164
|
-
}
|
|
4165
|
-
return data;
|
|
4166
|
-
}
|
|
4167
|
-
isObscured(id) {
|
|
4168
|
-
return this.obscured.has(id);
|
|
4169
|
-
}
|
|
4170
|
-
isHidden(id) {
|
|
4171
|
-
return this.hidden.has(id);
|
|
4172
|
-
}
|
|
4173
|
-
getInnerTextSecure(el) {
|
|
4174
|
-
const id = this.app.nodes.getID(el);
|
|
4175
|
-
if (!id) {
|
|
4176
|
-
return '';
|
|
4177
|
-
}
|
|
4178
|
-
return this.sanitize(id, el.innerText);
|
|
4179
|
-
}
|
|
4180
|
-
clear() {
|
|
4181
|
-
this.obscured.clear();
|
|
4182
|
-
this.hidden.clear();
|
|
4183
|
-
}
|
|
4184
|
-
}
|
|
4185
|
-
|
|
4186
4349
|
const tokenSeparator = '_$_';
|
|
4187
4350
|
class Session {
|
|
4188
4351
|
constructor(params) {
|
|
@@ -4228,6 +4391,10 @@ class Session {
|
|
|
4228
4391
|
this.token = `${token}${tokenSeparator}${projectKey}`;
|
|
4229
4392
|
this.app.sessionStorage.setItem(this.options.session_token_key, `${token}${tokenSeparator}${projectKey}`);
|
|
4230
4393
|
};
|
|
4394
|
+
this.clearSessionSecret = () => {
|
|
4395
|
+
this.token = undefined;
|
|
4396
|
+
this.app.sessionStorage.removeItem(this.options.session_token_key);
|
|
4397
|
+
};
|
|
4231
4398
|
this.app = params.app;
|
|
4232
4399
|
this.options = params.options;
|
|
4233
4400
|
this.createTabId();
|
|
@@ -4324,7 +4491,7 @@ class Session {
|
|
|
4324
4491
|
};
|
|
4325
4492
|
}
|
|
4326
4493
|
reset() {
|
|
4327
|
-
this.
|
|
4494
|
+
this.clearSessionSecret();
|
|
4328
4495
|
this.metadata = {};
|
|
4329
4496
|
this.userID = null;
|
|
4330
4497
|
this.sessionID = undefined;
|
|
@@ -4382,9 +4549,12 @@ class Ticker {
|
|
|
4382
4549
|
* this value is injected during build time via rollup
|
|
4383
4550
|
* */
|
|
4384
4551
|
// @ts-ignore
|
|
4385
|
-
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";
|
|
4552
|
+
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";
|
|
4386
4553
|
const CANCELED = 'canceled';
|
|
4387
4554
|
const bufferStorageKey = 'or_buffer_1';
|
|
4555
|
+
const PROTO_VERSION = '2';
|
|
4556
|
+
const BOOTSTRAP_ATTEMPT_TTL_MS = 24 * 60 * 60 * 1000;
|
|
4557
|
+
const MAX_BOOTSTRAP_ATTEMPT_STORAGE_LENGTH = 1024 * 1024;
|
|
4388
4558
|
const UnsuccessfulStart = (reason) => ({ reason, success: false });
|
|
4389
4559
|
const SuccessfulStart = (body) => ({ ...body, success: true });
|
|
4390
4560
|
var ActivityState;
|
|
@@ -4415,22 +4585,34 @@ const proto = {
|
|
|
4415
4585
|
resp: 'never-gonna-let-you-down',
|
|
4416
4586
|
// regenerating id (copied other tab)
|
|
4417
4587
|
reg: 'never-gonna-run-around-and-desert-you',
|
|
4418
|
-
iframeSignal: '
|
|
4419
|
-
iframeId: '
|
|
4420
|
-
iframeBatch: 'batch
|
|
4421
|
-
parentAlive: '
|
|
4422
|
-
killIframe: 'stop
|
|
4423
|
-
startIframe: 'start
|
|
4588
|
+
iframeSignal: 'oxvo:cross-frame:v1:handshake',
|
|
4589
|
+
iframeId: 'oxvo:cross-frame:v1:ready',
|
|
4590
|
+
iframeBatch: 'oxvo:cross-frame:v1:batch',
|
|
4591
|
+
parentAlive: 'oxvo:cross-frame:v1:pong',
|
|
4592
|
+
killIframe: 'oxvo:cross-frame:v1:stop',
|
|
4593
|
+
startIframe: 'oxvo:cross-frame:v1:start',
|
|
4424
4594
|
// checking updates
|
|
4425
|
-
polling: '
|
|
4595
|
+
polling: 'oxvo:cross-frame:v1:ping',
|
|
4426
4596
|
// happens if tab is old and has outdated token but
|
|
4427
4597
|
// not communicating with backend to update it (for whatever reason)
|
|
4428
4598
|
reset: 'reset-your-session-please',
|
|
4429
4599
|
};
|
|
4600
|
+
const FRAME_RATE_WINDOW_MS = 1000;
|
|
4601
|
+
const MAX_FRAME_ENVELOPES_PER_WINDOW = 120;
|
|
4602
|
+
const MAX_FRAME_MESSAGES_PER_WINDOW = 50000;
|
|
4603
|
+
const MAX_FRAME_BYTES_PER_WINDOW = 4 * 1024 * 1024;
|
|
4604
|
+
const FRAME_CAPABILITY_TTL_MS = 5 * 60 * 1000;
|
|
4605
|
+
const FRAME_CAPABILITY_REFRESH_LEAD_MS = 60 * 1000;
|
|
4606
|
+
const FRAME_PREVIOUS_CAPABILITY_GRACE_MS = 5 * 1000;
|
|
4430
4607
|
class App {
|
|
4608
|
+
get tagMatcher() {
|
|
4609
|
+
return this.markerWatcher.matcher;
|
|
4610
|
+
}
|
|
4431
4611
|
constructor(projectKey, sessionToken, options, signalError, insideIframe) {
|
|
4432
4612
|
this.signalError = signalError;
|
|
4433
4613
|
this.insideIframe = insideIframe;
|
|
4614
|
+
// Registered by input/img/canvas to re-emit a node when its level changes.
|
|
4615
|
+
this.resanitizeCallbacks = [];
|
|
4434
4616
|
this.messages = [];
|
|
4435
4617
|
/**
|
|
4436
4618
|
* we need 2 buffers, so we don't lose anything
|
|
@@ -4442,18 +4624,31 @@ class App {
|
|
|
4442
4624
|
this.stopCallbacks = [];
|
|
4443
4625
|
this.commitCallbacks = [];
|
|
4444
4626
|
this.activityState = ActivityState.NotActive;
|
|
4445
|
-
this.version = '7.
|
|
4627
|
+
this.version = '7.3.4'; // TODO: version compatability check inside each plugin.
|
|
4446
4628
|
this.socketMode = false;
|
|
4447
4629
|
this.compressionThreshold = 24 * 1000;
|
|
4448
4630
|
this.bc = null;
|
|
4449
4631
|
this.canvasRecorder = null;
|
|
4450
4632
|
this.conditionsManager = null;
|
|
4633
|
+
this.bootstrapAttempts = {};
|
|
4451
4634
|
this.canStart = false;
|
|
4452
4635
|
this.rootId = null;
|
|
4453
4636
|
this.pageFrames = [];
|
|
4454
4637
|
this.frameOderNumber = 0;
|
|
4455
4638
|
this.frameLevel = 0;
|
|
4456
4639
|
this.emptyBatchCounter = 0;
|
|
4640
|
+
this.crossDomainFrameCapability = null;
|
|
4641
|
+
this.crossDomainFrameCapabilityExpiresAt = 0;
|
|
4642
|
+
this.crossDomainFrameSession = null;
|
|
4643
|
+
this.crossDomainFrameCanvas = null;
|
|
4644
|
+
this.crossDomainResumeAfterHandshake = false;
|
|
4645
|
+
this.crossDomainConnectionRequested = false;
|
|
4646
|
+
this.crossDomainFrames = new Map();
|
|
4647
|
+
this.crossDomainEnvelopeBudgets = new WeakMap();
|
|
4648
|
+
this.pendingCrossDomainFrames = new Map();
|
|
4649
|
+
this.crossDomainAuthorizationGeneration = 0;
|
|
4650
|
+
this.nextCrossDomainFrameOrder = 1;
|
|
4651
|
+
this.crossDomainCanvasConfig = null;
|
|
4457
4652
|
/** used by child iframes for crossdomain only */
|
|
4458
4653
|
this.parentActive = false;
|
|
4459
4654
|
this.checkStatus = () => {
|
|
@@ -4461,185 +4656,201 @@ class App {
|
|
|
4461
4656
|
};
|
|
4462
4657
|
this.parentCrossDomainFrameListener = (event) => {
|
|
4463
4658
|
const { data } = event;
|
|
4464
|
-
if (!data ||
|
|
4659
|
+
if (!data ||
|
|
4660
|
+
this.crossDomainParentOrigin === null ||
|
|
4661
|
+
this.crossDomainFrameNonce === null ||
|
|
4662
|
+
event.source !== window.parent ||
|
|
4663
|
+
event.origin !== this.crossDomainParentOrigin) {
|
|
4465
4664
|
return;
|
|
4466
|
-
|
|
4467
|
-
|
|
4665
|
+
}
|
|
4666
|
+
if (data.line === proto.iframeId) {
|
|
4667
|
+
const init = parseCrossDomainFrameInit(data, proto.iframeId, {
|
|
4668
|
+
context: this.contextId,
|
|
4669
|
+
nonce: this.crossDomainFrameNonce,
|
|
4670
|
+
});
|
|
4671
|
+
if (init === null)
|
|
4672
|
+
return;
|
|
4673
|
+
const sessionChanged = this.crossDomainFrameSession !== null &&
|
|
4674
|
+
this.crossDomainFrameSession.sessionID !== init.session.sessionID;
|
|
4675
|
+
if (sessionChanged && this.activityState === ActivityState.Starting) {
|
|
4676
|
+
this.postHandshakeToParent();
|
|
4468
4677
|
return;
|
|
4469
|
-
try {
|
|
4470
|
-
this.allowAppStart();
|
|
4471
|
-
void this.start();
|
|
4472
4678
|
}
|
|
4473
|
-
|
|
4474
|
-
|
|
4679
|
+
const shouldResume = this.crossDomainResumeAfterHandshake || (sessionChanged && this.active());
|
|
4680
|
+
if (sessionChanged && this.active()) {
|
|
4681
|
+
this.stop();
|
|
4682
|
+
}
|
|
4683
|
+
this.applyCrossDomainFrameInit(init);
|
|
4684
|
+
if (shouldResume &&
|
|
4685
|
+
this.activityState !== ActivityState.Active &&
|
|
4686
|
+
this.activityState !== ActivityState.Starting) {
|
|
4687
|
+
this.crossDomainResumeAfterHandshake = false;
|
|
4688
|
+
this.startCrossDomainFrame(this.prevOpts);
|
|
4475
4689
|
}
|
|
4690
|
+
this.debug.log('Starting secure cross-domain iframe tracking');
|
|
4691
|
+
return;
|
|
4692
|
+
}
|
|
4693
|
+
const expectedLine = data.line === proto.parentAlive
|
|
4694
|
+
? proto.parentAlive
|
|
4695
|
+
: data.line === proto.startIframe
|
|
4696
|
+
? proto.startIframe
|
|
4697
|
+
: data.line === proto.killIframe
|
|
4698
|
+
? proto.killIframe
|
|
4699
|
+
: null;
|
|
4700
|
+
if (this.crossDomainFrameCapability === null ||
|
|
4701
|
+
expectedLine === null ||
|
|
4702
|
+
(expectedLine !== proto.killIframe &&
|
|
4703
|
+
Date.now() >= this.crossDomainFrameCapabilityExpiresAt) ||
|
|
4704
|
+
!matchesCrossDomainEnvelope(data, expectedLine, {
|
|
4705
|
+
context: this.contextId,
|
|
4706
|
+
nonce: this.crossDomainFrameNonce,
|
|
4707
|
+
capability: this.crossDomainFrameCapability,
|
|
4708
|
+
})) {
|
|
4709
|
+
return;
|
|
4476
4710
|
}
|
|
4477
4711
|
if (data.line === proto.parentAlive) {
|
|
4478
4712
|
this.parentActive = true;
|
|
4713
|
+
return;
|
|
4479
4714
|
}
|
|
4480
|
-
if (data.line === proto.
|
|
4481
|
-
|
|
4482
|
-
this.
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
|
|
4715
|
+
if (data.line === proto.startIframe) {
|
|
4716
|
+
// Avoid corrupting an in-flight start; let it complete.
|
|
4717
|
+
if (this.activityState === ActivityState.Starting)
|
|
4718
|
+
return;
|
|
4719
|
+
const init = parseCrossDomainFrameInit(data, proto.startIframe, {
|
|
4720
|
+
context: this.contextId,
|
|
4721
|
+
nonce: this.crossDomainFrameNonce,
|
|
4722
|
+
});
|
|
4723
|
+
if (init === null)
|
|
4724
|
+
return;
|
|
4725
|
+
try {
|
|
4726
|
+
if (this.active()) {
|
|
4727
|
+
this.stop();
|
|
4728
|
+
}
|
|
4729
|
+
this.applyCrossDomainFrameInit(init);
|
|
4730
|
+
this.startCrossDomainFrame(this.prevOpts);
|
|
4731
|
+
}
|
|
4732
|
+
catch (e) {
|
|
4733
|
+
console.error('children frame restart failed:', e);
|
|
4734
|
+
}
|
|
4735
|
+
return;
|
|
4488
4736
|
}
|
|
4489
4737
|
if (data.line === proto.killIframe) {
|
|
4490
|
-
|
|
4738
|
+
const shouldResume = this.activityState === ActivityState.Active || this.activityState === ActivityState.Starting;
|
|
4739
|
+
if (this.activityState !== ActivityState.NotActive) {
|
|
4491
4740
|
this.stop();
|
|
4492
4741
|
}
|
|
4742
|
+
this.clearCrossDomainFrameAuthorization();
|
|
4743
|
+
this.crossDomainResumeAfterHandshake = shouldResume;
|
|
4744
|
+
this.crossDomainConnectionRequested = shouldResume;
|
|
4493
4745
|
}
|
|
4494
4746
|
};
|
|
4495
|
-
/**
|
|
4496
|
-
* context ids for iframes,
|
|
4497
|
-
* order is not so important as long as its consistent
|
|
4498
|
-
* */
|
|
4499
|
-
this.trackedFrames = [];
|
|
4500
4747
|
this.crossDomainIframeListener = (event) => {
|
|
4501
|
-
if (!this.active() || event.source === window)
|
|
4502
|
-
return;
|
|
4503
4748
|
const { data } = event;
|
|
4504
|
-
if (!data)
|
|
4749
|
+
if (!data || event.source === window)
|
|
4505
4750
|
return;
|
|
4506
4751
|
if (data.line === proto.iframeSignal) {
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
}
|
|
4522
|
-
else {
|
|
4523
|
-
this.trackedFrames.push(data.context);
|
|
4524
|
-
}
|
|
4525
|
-
await this.waitStarted();
|
|
4526
|
-
const token = this.session.getSessionSecret(this.projectKey);
|
|
4527
|
-
const order = this.trackedFrames.findIndex((f) => f === data.context) + 1;
|
|
4528
|
-
if (order === 0) {
|
|
4529
|
-
this.debug.error('Couldnt get order number for iframe', data.context, this.trackedFrames);
|
|
4530
|
-
}
|
|
4531
|
-
const iframeData = {
|
|
4532
|
-
line: proto.iframeId,
|
|
4533
|
-
id,
|
|
4534
|
-
token,
|
|
4535
|
-
frameOrderNumber: order,
|
|
4536
|
-
frameLevel: this.frameLevel + 1,
|
|
4537
|
-
};
|
|
4538
|
-
this.debug.log('Got child frame signal; nodeId', id, event.source, iframeData);
|
|
4539
|
-
// @ts-ignore
|
|
4540
|
-
event.source?.postMessage(iframeData, '*');
|
|
4541
|
-
}
|
|
4542
|
-
catch (e) {
|
|
4543
|
-
console.error(e);
|
|
4544
|
-
}
|
|
4545
|
-
};
|
|
4546
|
-
void signalId();
|
|
4752
|
+
const handshake = parseCrossDomainHandshake(data, proto.iframeSignal, this.projectKey);
|
|
4753
|
+
if (handshake !== null) {
|
|
4754
|
+
void this.establishCrossDomainFrame(event, handshake);
|
|
4755
|
+
}
|
|
4756
|
+
return;
|
|
4757
|
+
}
|
|
4758
|
+
if (!this.active())
|
|
4759
|
+
return;
|
|
4760
|
+
if (data.line === proto.polling) {
|
|
4761
|
+
const state = this.validatedCrossDomainFrame(event, proto.polling);
|
|
4762
|
+
if (state !== null && this.consumeCrossDomainEnvelopeBudget(state.source)) {
|
|
4763
|
+
this.postToCrossDomainFrame(state, proto.parentAlive);
|
|
4764
|
+
}
|
|
4765
|
+
return;
|
|
4547
4766
|
}
|
|
4548
4767
|
/**
|
|
4549
4768
|
* proxying messages from iframe to main body, so they can be in one batch (same indexes, etc)
|
|
4550
4769
|
* plus we rewrite some of the messages to be relative to the main context/window
|
|
4551
4770
|
* */
|
|
4552
4771
|
if (data.line === proto.iframeBatch) {
|
|
4553
|
-
const
|
|
4772
|
+
const state = this.validatedCrossDomainFrame(event, proto.iframeBatch);
|
|
4773
|
+
if (state === null || !this.consumeCrossDomainEnvelopeBudget(state.source)) {
|
|
4774
|
+
return;
|
|
4775
|
+
}
|
|
4776
|
+
const batch = parseCrossDomainIframeBatch(data.messages);
|
|
4777
|
+
if (batch === null ||
|
|
4778
|
+
!this.consumeCrossDomainFrameBudget(state, batch.messages.length, batch.bytes)) {
|
|
4779
|
+
return;
|
|
4780
|
+
}
|
|
4554
4781
|
const mappedMessages = [];
|
|
4555
|
-
|
|
4782
|
+
batch.messages.forEach((msg) => {
|
|
4783
|
+
let fixedMessage = msg;
|
|
4556
4784
|
if (msg[0] === 20 /* MType.MouseMove */) {
|
|
4557
|
-
|
|
4558
|
-
|
|
4559
|
-
|
|
4560
|
-
const [type, x, y] = msg;
|
|
4561
|
-
const { left, top } = frame.getBoundingClientRect();
|
|
4562
|
-
fixedMessage = [type, x + left, y + top];
|
|
4563
|
-
}
|
|
4564
|
-
});
|
|
4565
|
-
mappedMessages.push(fixedMessage);
|
|
4785
|
+
const [type, x, y] = msg;
|
|
4786
|
+
const { left, top } = state.frame.getBoundingClientRect();
|
|
4787
|
+
fixedMessage = [type, x + left, y + top];
|
|
4566
4788
|
}
|
|
4567
4789
|
if (msg[0] === 68 /* MType.MouseClick */) {
|
|
4568
|
-
|
|
4569
|
-
|
|
4570
|
-
|
|
4571
|
-
|
|
4572
|
-
|
|
4573
|
-
|
|
4574
|
-
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
selector,
|
|
4586
|
-
Math.round(fixedX * 1e3) / 1e1,
|
|
4587
|
-
Math.round(fixedY * 1e3) / 1e1,
|
|
4588
|
-
];
|
|
4589
|
-
}
|
|
4590
|
-
});
|
|
4591
|
-
mappedMessages.push(fixedMessage);
|
|
4592
|
-
}
|
|
4593
|
-
if (![28 /* MType.UserID */, 29 /* MType.UserAnonymousID */, 30 /* MType.Metadata */].includes(msg[0])) {
|
|
4594
|
-
mappedMessages.push(msg);
|
|
4790
|
+
const [type, id, hesitationTime, label, selector, normX, normY] = msg;
|
|
4791
|
+
const { left, top, width, height } = state.frame.getBoundingClientRect();
|
|
4792
|
+
const contentWidth = document.documentElement.scrollWidth;
|
|
4793
|
+
const contentHeight = document.documentElement.scrollHeight;
|
|
4794
|
+
const fullX = (normX / 100) * width + left;
|
|
4795
|
+
const fullY = (normY / 100) * height + top;
|
|
4796
|
+
const fixedX = fullX / contentWidth;
|
|
4797
|
+
const fixedY = fullY / contentHeight;
|
|
4798
|
+
fixedMessage = [
|
|
4799
|
+
type,
|
|
4800
|
+
id,
|
|
4801
|
+
hesitationTime,
|
|
4802
|
+
label,
|
|
4803
|
+
selector,
|
|
4804
|
+
Math.round(fixedX * 1e3) / 1e1,
|
|
4805
|
+
Math.round(fixedY * 1e3) / 1e1,
|
|
4806
|
+
];
|
|
4595
4807
|
}
|
|
4808
|
+
mappedMessages.push(fixedMessage);
|
|
4596
4809
|
});
|
|
4597
4810
|
this.messages.push(...mappedMessages);
|
|
4598
4811
|
}
|
|
4599
|
-
|
|
4600
|
-
|
|
4601
|
-
|
|
4812
|
+
};
|
|
4813
|
+
this.bootChildrenFrames = async () => {
|
|
4814
|
+
await this.waitStarted();
|
|
4815
|
+
this.pruneDetachedCrossDomainFrames();
|
|
4816
|
+
for (const state of this.crossDomainFrames.values()) {
|
|
4817
|
+
const authorizationGeneration = this.crossDomainAuthorizationGeneration;
|
|
4818
|
+
const id = await this.checkNodeId(state.source);
|
|
4819
|
+
const session = this.currentCrossDomainSession();
|
|
4820
|
+
if (id === null ||
|
|
4821
|
+
session === null ||
|
|
4822
|
+
authorizationGeneration !== this.crossDomainAuthorizationGeneration ||
|
|
4823
|
+
this.crossDomainFrames.get(state.source) !== state ||
|
|
4824
|
+
!this.active()) {
|
|
4825
|
+
continue;
|
|
4602
4826
|
}
|
|
4603
|
-
const
|
|
4604
|
-
if (
|
|
4605
|
-
|
|
4606
|
-
return;
|
|
4827
|
+
const rotated = this.crossDomainFrameNeedsRotation(state, session.sessionID);
|
|
4828
|
+
if (rotated && !this.rotateCrossDomainFrameCapability(state, session.sessionID)) {
|
|
4829
|
+
continue;
|
|
4607
4830
|
}
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
event.source?.postMessage({ line: nextCommand }, '*');
|
|
4612
|
-
if (this.pollingQueue[nextCommand].length === 0) {
|
|
4613
|
-
this.pollingQueue.order.shift();
|
|
4614
|
-
}
|
|
4831
|
+
const payload = id === null ? null : this.frameInitPayload(state, id);
|
|
4832
|
+
if (payload) {
|
|
4833
|
+
this.postToCrossDomainFrame(state, rotated ? proto.iframeId : proto.startIframe, payload);
|
|
4615
4834
|
}
|
|
4616
4835
|
}
|
|
4617
4836
|
};
|
|
4618
|
-
/**
|
|
4619
|
-
* { command : [remaining iframes] }
|
|
4620
|
-
* + order of commands
|
|
4621
|
-
**/
|
|
4622
|
-
this.pollingQueue = {
|
|
4623
|
-
order: [],
|
|
4624
|
-
};
|
|
4625
|
-
this.addCommand = (cmd) => {
|
|
4626
|
-
this.pollingQueue.order.push(cmd);
|
|
4627
|
-
this.pollingQueue[cmd] = [...this.trackedFrames];
|
|
4628
|
-
};
|
|
4629
|
-
this.bootChildrenFrames = async () => {
|
|
4630
|
-
await this.waitStarted();
|
|
4631
|
-
this.addCommand(proto.startIframe);
|
|
4632
|
-
};
|
|
4633
4837
|
this.killChildrenFrames = () => {
|
|
4634
|
-
this.
|
|
4838
|
+
this.pruneDetachedCrossDomainFrames();
|
|
4839
|
+
const states = Array.from(this.crossDomainFrames.values());
|
|
4840
|
+
this.crossDomainAuthorizationGeneration += 1;
|
|
4841
|
+
this.crossDomainFrames.clear();
|
|
4842
|
+
this.pendingCrossDomainFrames.clear();
|
|
4843
|
+
states.forEach((state) => {
|
|
4844
|
+
this.postToCrossDomainFrame(state, proto.killIframe);
|
|
4845
|
+
});
|
|
4635
4846
|
};
|
|
4636
4847
|
this.signalIframeTracker = () => {
|
|
4637
|
-
|
|
4638
|
-
|
|
4639
|
-
|
|
4640
|
-
|
|
4641
|
-
|
|
4642
|
-
|
|
4848
|
+
if (this.crossDomainParentOrigin === null || this.crossDomainFrameNonce === null) {
|
|
4849
|
+
this.debug.error('Cross-domain iframe capture requires an explicit parentDomain and secure random support.');
|
|
4850
|
+
return;
|
|
4851
|
+
}
|
|
4852
|
+
this.crossDomainConnectionRequested = true;
|
|
4853
|
+
this.postHandshakeToParent();
|
|
4643
4854
|
/**
|
|
4644
4855
|
* since we need to wait uncertain amount of time
|
|
4645
4856
|
* and I don't want to have recursion going on,
|
|
@@ -4651,20 +4862,16 @@ class App {
|
|
|
4651
4862
|
let cumulativeDelay = 0;
|
|
4652
4863
|
let stopAttempts = false;
|
|
4653
4864
|
const checkAndSendMessage = () => {
|
|
4654
|
-
if (stopAttempts || this.checkStatus()) {
|
|
4865
|
+
if (stopAttempts || !this.crossDomainConnectionRequested || this.checkStatus()) {
|
|
4655
4866
|
stopAttempts = true;
|
|
4656
4867
|
return;
|
|
4657
4868
|
}
|
|
4658
|
-
|
|
4659
|
-
line: proto.iframeSignal,
|
|
4660
|
-
source: thisTab,
|
|
4661
|
-
context: this.contextId,
|
|
4662
|
-
}, this.options.crossdomain?.parentDomain ?? '*');
|
|
4869
|
+
this.postHandshakeToParent();
|
|
4663
4870
|
this.debug.info('Trying to signal to parent, attempt:', retries + 1);
|
|
4664
4871
|
retries++;
|
|
4665
4872
|
};
|
|
4666
4873
|
for (let i = 0; i < maxRetries; i++) {
|
|
4667
|
-
if (this.checkStatus()) {
|
|
4874
|
+
if (!this.crossDomainConnectionRequested || this.checkStatus()) {
|
|
4668
4875
|
stopAttempts = true;
|
|
4669
4876
|
break;
|
|
4670
4877
|
}
|
|
@@ -4751,6 +4958,26 @@ class App {
|
|
|
4751
4958
|
this.restartCanvasTracking = () => {
|
|
4752
4959
|
this.canvasRecorder?.restartTracking();
|
|
4753
4960
|
};
|
|
4961
|
+
this.attachResanitizeCallback = (cb) => {
|
|
4962
|
+
this.resanitizeCallbacks.push(cb);
|
|
4963
|
+
};
|
|
4964
|
+
this.callResanitizeCallbacks = (node, id) => {
|
|
4965
|
+
this.resanitizeCallbacks.forEach((cb) => cb(node, id));
|
|
4966
|
+
};
|
|
4967
|
+
this.resanitize = (el) => {
|
|
4968
|
+
const root = el ?? (IN_BROWSER ? document.documentElement : undefined);
|
|
4969
|
+
if (!root) {
|
|
4970
|
+
return;
|
|
4971
|
+
}
|
|
4972
|
+
this.observer.resanitizeSubtree(root);
|
|
4973
|
+
};
|
|
4974
|
+
this.checkSanitization = (el) => {
|
|
4975
|
+
const id = this.nodes.getID(el);
|
|
4976
|
+
if (id === undefined) {
|
|
4977
|
+
return undefined;
|
|
4978
|
+
}
|
|
4979
|
+
return this.sanitizer.getLevel(id);
|
|
4980
|
+
};
|
|
4754
4981
|
this.flushBuffer = async (buffer) => {
|
|
4755
4982
|
return new Promise((res, reject) => {
|
|
4756
4983
|
if (buffer.length === 0) {
|
|
@@ -4792,6 +5019,7 @@ class App {
|
|
|
4792
5019
|
__is_snippet: false,
|
|
4793
5020
|
__debug_report_edp: null,
|
|
4794
5021
|
__debug__: LogLevel.Silent,
|
|
5022
|
+
__local_debug: false,
|
|
4795
5023
|
localStorage: null,
|
|
4796
5024
|
sessionStorage: null,
|
|
4797
5025
|
forceSingleTab: false,
|
|
@@ -4800,9 +5028,7 @@ class App {
|
|
|
4800
5028
|
maskTextEmails: false,
|
|
4801
5029
|
maskTextNumbers: false,
|
|
4802
5030
|
disableStringDict: false,
|
|
4803
|
-
crossdomain: {
|
|
4804
|
-
parentDomain: '*',
|
|
4805
|
-
},
|
|
5031
|
+
crossdomain: {},
|
|
4806
5032
|
canvas: {
|
|
4807
5033
|
disableCanvas: false,
|
|
4808
5034
|
fixedCanvasScaling: false,
|
|
@@ -4815,6 +5041,9 @@ class App {
|
|
|
4815
5041
|
disableThrottling: false,
|
|
4816
5042
|
};
|
|
4817
5043
|
this.options = simpleMerge(defaultOptions, options);
|
|
5044
|
+
this.crossDomainChildOrigins = normalizeCrossDomainOrigins(this.options.crossdomain?.childOrigins);
|
|
5045
|
+
this.crossDomainParentOrigin = normalizeCrossDomainOrigin(this.options.crossdomain?.parentDomain);
|
|
5046
|
+
this.crossDomainFrameNonce = this.insideIframe ? createCrossDomainSecret() : null;
|
|
4818
5047
|
if (!this.insideIframe &&
|
|
4819
5048
|
!this.options.forceSingleTab &&
|
|
4820
5049
|
globalThis &&
|
|
@@ -4850,9 +5079,7 @@ class App {
|
|
|
4850
5079
|
});
|
|
4851
5080
|
this.session.attachUpdateCallback(({ userID, metadata }) => {
|
|
4852
5081
|
if (userID != null) {
|
|
4853
|
-
if (!userID ||
|
|
4854
|
-
typeof userID !== 'string' ||
|
|
4855
|
-
userID.trim().length === 0) {
|
|
5082
|
+
if (!userID || typeof userID !== 'string' || userID.trim().length === 0) {
|
|
4856
5083
|
this.debug.warn('Invalid userID (must be type string), ignoring.');
|
|
4857
5084
|
return;
|
|
4858
5085
|
}
|
|
@@ -4863,25 +5090,33 @@ class App {
|
|
|
4863
5090
|
}
|
|
4864
5091
|
});
|
|
4865
5092
|
// @deprecated (use sessionHash on start instead)
|
|
4866
|
-
if (sessionToken != null) {
|
|
5093
|
+
if (sessionToken != null && !this.insideIframe) {
|
|
4867
5094
|
this.session.applySessionHash(sessionToken);
|
|
4868
5095
|
}
|
|
5096
|
+
if (this.insideIframe) {
|
|
5097
|
+
this.session.clearSessionSecret();
|
|
5098
|
+
}
|
|
4869
5099
|
const thisTab = this.session.getTabId();
|
|
4870
5100
|
if (this.insideIframe) {
|
|
4871
|
-
|
|
4872
|
-
|
|
4873
|
-
|
|
4874
|
-
|
|
4875
|
-
|
|
4876
|
-
|
|
4877
|
-
|
|
4878
|
-
|
|
4879
|
-
|
|
4880
|
-
|
|
4881
|
-
|
|
4882
|
-
|
|
4883
|
-
|
|
4884
|
-
|
|
5101
|
+
if (this.crossDomainParentOrigin !== null && this.crossDomainFrameNonce !== null) {
|
|
5102
|
+
/**
|
|
5103
|
+
* listen for messages from parent window, so we can signal that we're alive
|
|
5104
|
+
* */
|
|
5105
|
+
window.addEventListener('message', this.parentCrossDomainFrameListener);
|
|
5106
|
+
setInterval(() => {
|
|
5107
|
+
if (!this.crossDomainConnectionRequested) {
|
|
5108
|
+
return;
|
|
5109
|
+
}
|
|
5110
|
+
if (this.crossDomainFrameCapability === null ||
|
|
5111
|
+
Date.now() >=
|
|
5112
|
+
this.crossDomainFrameCapabilityExpiresAt - FRAME_CAPABILITY_REFRESH_LEAD_MS) {
|
|
5113
|
+
this.postHandshakeToParent();
|
|
5114
|
+
}
|
|
5115
|
+
else {
|
|
5116
|
+
this.postToParent(proto.polling);
|
|
5117
|
+
}
|
|
5118
|
+
}, 1000);
|
|
5119
|
+
}
|
|
4885
5120
|
}
|
|
4886
5121
|
else {
|
|
4887
5122
|
this.initWorker();
|
|
@@ -4889,7 +5124,9 @@ class App {
|
|
|
4889
5124
|
* if we get a signal from child iframes, we check for their node_id and send it back,
|
|
4890
5125
|
* so they can act as if it was just a same-domain iframe
|
|
4891
5126
|
* */
|
|
4892
|
-
|
|
5127
|
+
if (this.options.crossdomain?.enabled && this.crossDomainChildOrigins.size > 0) {
|
|
5128
|
+
window.addEventListener('message', this.crossDomainIframeListener);
|
|
5129
|
+
}
|
|
4893
5130
|
}
|
|
4894
5131
|
if (this.bc !== null) {
|
|
4895
5132
|
this.bc.postMessage({
|
|
@@ -4939,6 +5176,319 @@ class App {
|
|
|
4939
5176
|
};
|
|
4940
5177
|
}
|
|
4941
5178
|
}
|
|
5179
|
+
postToParent(line, extra = {}) {
|
|
5180
|
+
if (this.crossDomainParentOrigin === null ||
|
|
5181
|
+
this.crossDomainFrameNonce === null ||
|
|
5182
|
+
this.crossDomainFrameCapability === null ||
|
|
5183
|
+
Date.now() >= this.crossDomainFrameCapabilityExpiresAt) {
|
|
5184
|
+
return;
|
|
5185
|
+
}
|
|
5186
|
+
window.parent.postMessage({
|
|
5187
|
+
line,
|
|
5188
|
+
protocolVersion: CROSS_DOMAIN_IFRAME_PROTOCOL_VERSION,
|
|
5189
|
+
context: this.contextId,
|
|
5190
|
+
nonce: this.crossDomainFrameNonce,
|
|
5191
|
+
capability: this.crossDomainFrameCapability,
|
|
5192
|
+
...extra,
|
|
5193
|
+
}, this.crossDomainParentOrigin);
|
|
5194
|
+
}
|
|
5195
|
+
postHandshakeToParent() {
|
|
5196
|
+
if (this.crossDomainParentOrigin === null || this.crossDomainFrameNonce === null) {
|
|
5197
|
+
return;
|
|
5198
|
+
}
|
|
5199
|
+
window.parent.postMessage({
|
|
5200
|
+
line: proto.iframeSignal,
|
|
5201
|
+
protocolVersion: CROSS_DOMAIN_IFRAME_PROTOCOL_VERSION,
|
|
5202
|
+
context: this.contextId,
|
|
5203
|
+
nonce: this.crossDomainFrameNonce,
|
|
5204
|
+
projectKey: this.projectKey,
|
|
5205
|
+
}, this.crossDomainParentOrigin);
|
|
5206
|
+
}
|
|
5207
|
+
applyCrossDomainFrameInit(init) {
|
|
5208
|
+
this.parentActive = true;
|
|
5209
|
+
this.crossDomainConnectionRequested = true;
|
|
5210
|
+
this.crossDomainFrameCapability = init.capability;
|
|
5211
|
+
this.crossDomainFrameCapabilityExpiresAt = init.capabilityExpiresAt;
|
|
5212
|
+
this.crossDomainFrameSession = init.session;
|
|
5213
|
+
this.crossDomainFrameCanvas = init.canvas ?? null;
|
|
5214
|
+
this.rootId = init.id;
|
|
5215
|
+
this.frameOderNumber = init.frameOrderNumber;
|
|
5216
|
+
this.frameLevel = init.frameLevel;
|
|
5217
|
+
this.allowAppStart();
|
|
5218
|
+
}
|
|
5219
|
+
clearCrossDomainFrameAuthorization() {
|
|
5220
|
+
this.parentActive = false;
|
|
5221
|
+
this.canStart = false;
|
|
5222
|
+
this.crossDomainFrameCapability = null;
|
|
5223
|
+
this.crossDomainFrameCapabilityExpiresAt = 0;
|
|
5224
|
+
this.crossDomainFrameSession = null;
|
|
5225
|
+
this.crossDomainFrameCanvas = null;
|
|
5226
|
+
this.rootId = null;
|
|
5227
|
+
}
|
|
5228
|
+
findCrossDomainFrame(source) {
|
|
5229
|
+
const frame = this.pageFrames.find((candidate) => candidate.contentWindow === source) ??
|
|
5230
|
+
Array.from(document.querySelectorAll('iframe')).find((candidate) => candidate.contentWindow === source);
|
|
5231
|
+
if (!frame)
|
|
5232
|
+
return null;
|
|
5233
|
+
if (!this.pageFrames.includes(frame)) {
|
|
5234
|
+
this.pageFrames.push(frame);
|
|
5235
|
+
}
|
|
5236
|
+
return frame;
|
|
5237
|
+
}
|
|
5238
|
+
revokeCrossDomainFrame(source, expectedState) {
|
|
5239
|
+
const state = this.crossDomainFrames.get(source);
|
|
5240
|
+
if (!state || (expectedState !== undefined && state !== expectedState)) {
|
|
5241
|
+
return;
|
|
5242
|
+
}
|
|
5243
|
+
this.crossDomainFrames.delete(source);
|
|
5244
|
+
}
|
|
5245
|
+
pruneDetachedCrossDomainFrames() {
|
|
5246
|
+
this.crossDomainFrames.forEach((state, source) => {
|
|
5247
|
+
if (!state.frame.isConnected || state.frame.contentWindow !== source) {
|
|
5248
|
+
this.revokeCrossDomainFrame(source);
|
|
5249
|
+
}
|
|
5250
|
+
});
|
|
5251
|
+
}
|
|
5252
|
+
currentCrossDomainSession() {
|
|
5253
|
+
const session = this.session.getInfo();
|
|
5254
|
+
if (!session.sessionID) {
|
|
5255
|
+
return null;
|
|
5256
|
+
}
|
|
5257
|
+
return {
|
|
5258
|
+
sessionID: session.sessionID,
|
|
5259
|
+
projectID: session.projectID,
|
|
5260
|
+
startedAt: session.timestamp,
|
|
5261
|
+
delayMs: this.delay,
|
|
5262
|
+
userUUID: this.localStorage.getItem(this.options.local_uuid_key) ?? '',
|
|
5263
|
+
};
|
|
5264
|
+
}
|
|
5265
|
+
frameInitPayload(state, id) {
|
|
5266
|
+
const session = this.currentCrossDomainSession();
|
|
5267
|
+
if (session === null ||
|
|
5268
|
+
session.sessionID !== state.sessionID ||
|
|
5269
|
+
Date.now() >= state.capabilityExpiresAt) {
|
|
5270
|
+
return null;
|
|
5271
|
+
}
|
|
5272
|
+
return {
|
|
5273
|
+
id,
|
|
5274
|
+
frameOrderNumber: state.frameOrderNumber,
|
|
5275
|
+
frameLevel: this.frameLevel + 1,
|
|
5276
|
+
capabilityExpiresAt: state.capabilityExpiresAt,
|
|
5277
|
+
session,
|
|
5278
|
+
...(this.crossDomainCanvasConfig ? { canvas: this.crossDomainCanvasConfig } : {}),
|
|
5279
|
+
};
|
|
5280
|
+
}
|
|
5281
|
+
rotateCrossDomainFrameCapability(state, sessionID, now = Date.now()) {
|
|
5282
|
+
const capability = createCrossDomainSecret();
|
|
5283
|
+
if (capability === null) {
|
|
5284
|
+
return false;
|
|
5285
|
+
}
|
|
5286
|
+
const preservePreviousCapability = state.sessionID === sessionID && now < state.capabilityExpiresAt;
|
|
5287
|
+
state.previousCapability = preservePreviousCapability ? state.capability : null;
|
|
5288
|
+
state.previousCapabilityExpiresAt = preservePreviousCapability
|
|
5289
|
+
? Math.min(state.capabilityExpiresAt, now + FRAME_PREVIOUS_CAPABILITY_GRACE_MS)
|
|
5290
|
+
: 0;
|
|
5291
|
+
state.capability = capability;
|
|
5292
|
+
state.sessionID = sessionID;
|
|
5293
|
+
state.capabilityExpiresAt = now + FRAME_CAPABILITY_TTL_MS;
|
|
5294
|
+
return true;
|
|
5295
|
+
}
|
|
5296
|
+
crossDomainFrameNeedsRotation(state, sessionID, now = Date.now()) {
|
|
5297
|
+
return (state.sessionID !== sessionID ||
|
|
5298
|
+
now >= state.capabilityExpiresAt - FRAME_CAPABILITY_REFRESH_LEAD_MS);
|
|
5299
|
+
}
|
|
5300
|
+
postToCrossDomainFrame(state, line, extra = {}) {
|
|
5301
|
+
state.source.postMessage({
|
|
5302
|
+
line,
|
|
5303
|
+
protocolVersion: CROSS_DOMAIN_IFRAME_PROTOCOL_VERSION,
|
|
5304
|
+
context: state.context,
|
|
5305
|
+
nonce: state.nonce,
|
|
5306
|
+
capability: state.capability,
|
|
5307
|
+
...extra,
|
|
5308
|
+
}, state.origin);
|
|
5309
|
+
}
|
|
5310
|
+
consumeCrossDomainEnvelopeBudget(source) {
|
|
5311
|
+
const now = Date.now();
|
|
5312
|
+
let budget = this.crossDomainEnvelopeBudgets.get(source);
|
|
5313
|
+
if (budget === undefined || now - budget.windowStartedAt >= FRAME_RATE_WINDOW_MS) {
|
|
5314
|
+
budget = {
|
|
5315
|
+
windowStartedAt: now,
|
|
5316
|
+
envelopesInWindow: 0,
|
|
5317
|
+
};
|
|
5318
|
+
this.crossDomainEnvelopeBudgets.set(source, budget);
|
|
5319
|
+
}
|
|
5320
|
+
if (budget.envelopesInWindow >= MAX_FRAME_ENVELOPES_PER_WINDOW) {
|
|
5321
|
+
return false;
|
|
5322
|
+
}
|
|
5323
|
+
budget.envelopesInWindow += 1;
|
|
5324
|
+
return true;
|
|
5325
|
+
}
|
|
5326
|
+
async establishCrossDomainFrame(event, handshake) {
|
|
5327
|
+
if (!this.active() ||
|
|
5328
|
+
event.source === null ||
|
|
5329
|
+
!this.crossDomainChildOrigins.has(event.origin)) {
|
|
5330
|
+
return;
|
|
5331
|
+
}
|
|
5332
|
+
const candidateSource = event.source;
|
|
5333
|
+
if (!this.consumeCrossDomainEnvelopeBudget(candidateSource)) {
|
|
5334
|
+
return;
|
|
5335
|
+
}
|
|
5336
|
+
const frame = this.findCrossDomainFrame(candidateSource);
|
|
5337
|
+
if (frame === null || !frame.isConnected || frame.contentWindow === null) {
|
|
5338
|
+
return;
|
|
5339
|
+
}
|
|
5340
|
+
const source = frame.contentWindow;
|
|
5341
|
+
const existing = this.crossDomainFrames.get(source);
|
|
5342
|
+
if (this.pendingCrossDomainFrames.has(source)) {
|
|
5343
|
+
return;
|
|
5344
|
+
}
|
|
5345
|
+
const existingMatches = existing !== undefined &&
|
|
5346
|
+
existing.origin === event.origin &&
|
|
5347
|
+
existing.context === handshake.context &&
|
|
5348
|
+
existing.nonce === handshake.nonce &&
|
|
5349
|
+
existing.frame === frame;
|
|
5350
|
+
if (existing !== undefined && !existingMatches) {
|
|
5351
|
+
return;
|
|
5352
|
+
}
|
|
5353
|
+
const authorizationGeneration = this.crossDomainAuthorizationGeneration;
|
|
5354
|
+
const pendingToken = Symbol();
|
|
5355
|
+
this.pendingCrossDomainFrames.set(source, pendingToken);
|
|
5356
|
+
let navigated = false;
|
|
5357
|
+
const markNavigated = () => {
|
|
5358
|
+
navigated = true;
|
|
5359
|
+
};
|
|
5360
|
+
frame.addEventListener('load', markNavigated, { once: true });
|
|
5361
|
+
try {
|
|
5362
|
+
const id = await this.checkNodeId(source);
|
|
5363
|
+
if (id === null ||
|
|
5364
|
+
navigated ||
|
|
5365
|
+
!this.active() ||
|
|
5366
|
+
authorizationGeneration !== this.crossDomainAuthorizationGeneration ||
|
|
5367
|
+
!frame.isConnected ||
|
|
5368
|
+
frame.contentWindow !== source) {
|
|
5369
|
+
return;
|
|
5370
|
+
}
|
|
5371
|
+
const session = this.currentCrossDomainSession();
|
|
5372
|
+
if (session === null) {
|
|
5373
|
+
return;
|
|
5374
|
+
}
|
|
5375
|
+
const now = Date.now();
|
|
5376
|
+
let state;
|
|
5377
|
+
if (existingMatches) {
|
|
5378
|
+
state = existing;
|
|
5379
|
+
if (this.crossDomainFrameNeedsRotation(state, session.sessionID, now) &&
|
|
5380
|
+
!this.rotateCrossDomainFrameCapability(state, session.sessionID, now)) {
|
|
5381
|
+
return;
|
|
5382
|
+
}
|
|
5383
|
+
state.lastSeenAt = now;
|
|
5384
|
+
}
|
|
5385
|
+
else {
|
|
5386
|
+
const capability = createCrossDomainSecret();
|
|
5387
|
+
if (capability === null) {
|
|
5388
|
+
return;
|
|
5389
|
+
}
|
|
5390
|
+
state = {
|
|
5391
|
+
source,
|
|
5392
|
+
frame,
|
|
5393
|
+
origin: event.origin,
|
|
5394
|
+
protocolVersion: CROSS_DOMAIN_IFRAME_PROTOCOL_VERSION,
|
|
5395
|
+
context: handshake.context,
|
|
5396
|
+
nonce: handshake.nonce,
|
|
5397
|
+
capability,
|
|
5398
|
+
frameOrderNumber: this.nextCrossDomainFrameOrder++,
|
|
5399
|
+
sessionID: session.sessionID,
|
|
5400
|
+
capabilityExpiresAt: now + FRAME_CAPABILITY_TTL_MS,
|
|
5401
|
+
previousCapability: null,
|
|
5402
|
+
previousCapabilityExpiresAt: 0,
|
|
5403
|
+
lastSeenAt: now,
|
|
5404
|
+
rateWindowStartedAt: now,
|
|
5405
|
+
messagesInRateWindow: 0,
|
|
5406
|
+
bytesInRateWindow: 0,
|
|
5407
|
+
};
|
|
5408
|
+
}
|
|
5409
|
+
const payload = this.frameInitPayload(state, id);
|
|
5410
|
+
if (payload === null) {
|
|
5411
|
+
return;
|
|
5412
|
+
}
|
|
5413
|
+
if (!existingMatches) {
|
|
5414
|
+
if (existing !== undefined) {
|
|
5415
|
+
this.revokeCrossDomainFrame(source, existing);
|
|
5416
|
+
}
|
|
5417
|
+
this.crossDomainFrames.set(source, state);
|
|
5418
|
+
frame.addEventListener('load', () => {
|
|
5419
|
+
this.revokeCrossDomainFrame(source, state);
|
|
5420
|
+
}, { once: true });
|
|
5421
|
+
}
|
|
5422
|
+
this.postToCrossDomainFrame(state, proto.iframeId, payload);
|
|
5423
|
+
}
|
|
5424
|
+
finally {
|
|
5425
|
+
frame.removeEventListener('load', markNavigated);
|
|
5426
|
+
if (this.pendingCrossDomainFrames.get(source) === pendingToken) {
|
|
5427
|
+
this.pendingCrossDomainFrames.delete(source);
|
|
5428
|
+
}
|
|
5429
|
+
}
|
|
5430
|
+
}
|
|
5431
|
+
validatedCrossDomainFrame(event, expectedLine) {
|
|
5432
|
+
if (event.source === null ||
|
|
5433
|
+
!this.crossDomainChildOrigins.has(event.origin) ||
|
|
5434
|
+
event.source === window) {
|
|
5435
|
+
return null;
|
|
5436
|
+
}
|
|
5437
|
+
const source = event.source;
|
|
5438
|
+
const state = this.crossDomainFrames.get(source);
|
|
5439
|
+
const now = Date.now();
|
|
5440
|
+
const currentSessionID = this.session.getInfo().sessionID;
|
|
5441
|
+
if (!state ||
|
|
5442
|
+
state.origin !== event.origin ||
|
|
5443
|
+
!state.frame.isConnected ||
|
|
5444
|
+
state.frame.contentWindow !== source) {
|
|
5445
|
+
if (state) {
|
|
5446
|
+
this.revokeCrossDomainFrame(source);
|
|
5447
|
+
}
|
|
5448
|
+
return null;
|
|
5449
|
+
}
|
|
5450
|
+
if (currentSessionID === undefined || state.sessionID !== currentSessionID) {
|
|
5451
|
+
this.revokeCrossDomainFrame(source);
|
|
5452
|
+
return null;
|
|
5453
|
+
}
|
|
5454
|
+
const matchesCurrentCapability = now < state.capabilityExpiresAt && matchesCrossDomainEnvelope(event.data, expectedLine, state);
|
|
5455
|
+
const matchesPreviousCapability = state.previousCapability !== null &&
|
|
5456
|
+
now < state.previousCapabilityExpiresAt &&
|
|
5457
|
+
matchesCrossDomainEnvelope(event.data, expectedLine, {
|
|
5458
|
+
protocolVersion: state.protocolVersion,
|
|
5459
|
+
context: state.context,
|
|
5460
|
+
nonce: state.nonce,
|
|
5461
|
+
capability: state.previousCapability,
|
|
5462
|
+
});
|
|
5463
|
+
if (!matchesCurrentCapability && !matchesPreviousCapability) {
|
|
5464
|
+
if (now >= state.capabilityExpiresAt && now >= state.previousCapabilityExpiresAt) {
|
|
5465
|
+
this.revokeCrossDomainFrame(source);
|
|
5466
|
+
}
|
|
5467
|
+
return null;
|
|
5468
|
+
}
|
|
5469
|
+
if ((matchesCurrentCapability && state.previousCapability !== null) ||
|
|
5470
|
+
now >= state.previousCapabilityExpiresAt) {
|
|
5471
|
+
state.previousCapability = null;
|
|
5472
|
+
state.previousCapabilityExpiresAt = 0;
|
|
5473
|
+
}
|
|
5474
|
+
state.lastSeenAt = now;
|
|
5475
|
+
return state;
|
|
5476
|
+
}
|
|
5477
|
+
consumeCrossDomainFrameBudget(state, messageCount, bytes) {
|
|
5478
|
+
const now = Date.now();
|
|
5479
|
+
if (now - state.rateWindowStartedAt >= FRAME_RATE_WINDOW_MS) {
|
|
5480
|
+
state.rateWindowStartedAt = now;
|
|
5481
|
+
state.messagesInRateWindow = 0;
|
|
5482
|
+
state.bytesInRateWindow = 0;
|
|
5483
|
+
}
|
|
5484
|
+
if (state.messagesInRateWindow + messageCount > MAX_FRAME_MESSAGES_PER_WINDOW ||
|
|
5485
|
+
state.bytesInRateWindow + bytes > MAX_FRAME_BYTES_PER_WINDOW) {
|
|
5486
|
+
return false;
|
|
5487
|
+
}
|
|
5488
|
+
state.messagesInRateWindow += messageCount;
|
|
5489
|
+
state.bytesInRateWindow += bytes;
|
|
5490
|
+
return true;
|
|
5491
|
+
}
|
|
4942
5492
|
allowAppStart() {
|
|
4943
5493
|
this.canStart = true;
|
|
4944
5494
|
if (this.startTimeout) {
|
|
@@ -4998,6 +5548,7 @@ class App {
|
|
|
4998
5548
|
setTimeout(() => {
|
|
4999
5549
|
closing = false;
|
|
5000
5550
|
}, 500);
|
|
5551
|
+
this.canvasRecorder?.flushPendingSnapshots();
|
|
5001
5552
|
if (this.worker) {
|
|
5002
5553
|
this.worker.postMessage('closing');
|
|
5003
5554
|
}
|
|
@@ -5038,22 +5589,39 @@ class App {
|
|
|
5038
5589
|
}
|
|
5039
5590
|
else if (data.type === 'compress') {
|
|
5040
5591
|
const batch = data.batch;
|
|
5592
|
+
const dataType = data.dataType;
|
|
5041
5593
|
const batchSize = batch.byteLength;
|
|
5042
|
-
|
|
5043
|
-
|
|
5044
|
-
|
|
5045
|
-
|
|
5046
|
-
|
|
5047
|
-
|
|
5048
|
-
|
|
5049
|
-
|
|
5050
|
-
|
|
5594
|
+
const hasCompressionAPI = 'CompressionStream' in globalThis;
|
|
5595
|
+
if (batchSize > this.compressionThreshold && hasCompressionAPI) {
|
|
5596
|
+
const blob = new Blob([batch]);
|
|
5597
|
+
const stream = blob.stream().pipeThrough(new CompressionStream('gzip'));
|
|
5598
|
+
new Response(stream)
|
|
5599
|
+
.arrayBuffer()
|
|
5600
|
+
.then((compressedBuffer) => {
|
|
5601
|
+
this.worker?.postMessage({
|
|
5602
|
+
type: 'compressed',
|
|
5603
|
+
batch: new Uint8Array(compressedBuffer),
|
|
5604
|
+
dataType,
|
|
5605
|
+
});
|
|
5606
|
+
})
|
|
5607
|
+
.catch((err) => {
|
|
5608
|
+
this.debug.error('OxvoSessions compression error:', err);
|
|
5609
|
+
this.worker?.postMessage({ type: 'uncompressed', batch: batch, dataType });
|
|
5051
5610
|
});
|
|
5052
5611
|
}
|
|
5053
5612
|
else {
|
|
5054
|
-
this.worker?.postMessage({ type: 'uncompressed', batch: batch });
|
|
5613
|
+
this.worker?.postMessage({ type: 'uncompressed', batch: batch, dataType });
|
|
5055
5614
|
}
|
|
5056
5615
|
}
|
|
5616
|
+
else if (data.type === 'local_save') {
|
|
5617
|
+
const blob = new Blob([data.batch], { type: 'application/octet-stream' });
|
|
5618
|
+
const url = URL.createObjectURL(blob);
|
|
5619
|
+
const a = document.createElement('a');
|
|
5620
|
+
a.href = url;
|
|
5621
|
+
a.download = data.name;
|
|
5622
|
+
a.click();
|
|
5623
|
+
URL.revokeObjectURL(url);
|
|
5624
|
+
}
|
|
5057
5625
|
else if (data.type === 'queue_empty') {
|
|
5058
5626
|
this.onSessionSent();
|
|
5059
5627
|
}
|
|
@@ -5084,27 +5652,36 @@ class App {
|
|
|
5084
5652
|
return;
|
|
5085
5653
|
}
|
|
5086
5654
|
if (this.insideIframe) {
|
|
5087
|
-
|
|
5088
|
-
line: proto.iframeBatch,
|
|
5089
|
-
messages: this.messages,
|
|
5090
|
-
}, this.options.crossdomain?.parentDomain ?? '*');
|
|
5655
|
+
this.postToParent(proto.iframeBatch, { messages: this.messages });
|
|
5091
5656
|
this.commitCallbacks.forEach((cb) => cb(this.messages));
|
|
5092
5657
|
this.messages.length = 0;
|
|
5093
5658
|
return;
|
|
5094
5659
|
}
|
|
5095
|
-
if (this.worker === undefined
|
|
5660
|
+
if (this.worker === undefined) {
|
|
5096
5661
|
return;
|
|
5097
5662
|
}
|
|
5098
5663
|
if (!this.messages.length) {
|
|
5099
|
-
//
|
|
5664
|
+
// Send a keepalive batch every ~30s (1000 * 30ms) to keep the session active
|
|
5100
5665
|
if (this.emptyBatchCounter < 1000) {
|
|
5101
5666
|
this.emptyBatchCounter++;
|
|
5102
5667
|
return;
|
|
5103
5668
|
}
|
|
5669
|
+
// Keepalive: send just Timestamp + TabData so the session stays alive
|
|
5670
|
+
this.emptyBatchCounter = 0;
|
|
5671
|
+
try {
|
|
5672
|
+
this.worker?.postMessage([Timestamp(this.timestamp()), TabData(this.session.getTabId())]);
|
|
5673
|
+
}
|
|
5674
|
+
catch (e) {
|
|
5675
|
+
this._debug('worker_keepalive', e);
|
|
5676
|
+
}
|
|
5677
|
+
return;
|
|
5104
5678
|
}
|
|
5105
5679
|
this.emptyBatchCounter = 0;
|
|
5106
5680
|
try {
|
|
5107
5681
|
requestIdleCb(() => {
|
|
5682
|
+
if (!this.messages.length) {
|
|
5683
|
+
return;
|
|
5684
|
+
}
|
|
5108
5685
|
this.messages.unshift(Timestamp(this.timestamp()), TabData(this.session.getTabId()));
|
|
5109
5686
|
this.worker?.postMessage(this.messages);
|
|
5110
5687
|
this.commitCallbacks.forEach((cb) => cb(this.messages));
|
|
@@ -5231,6 +5808,159 @@ class App {
|
|
|
5231
5808
|
},
|
|
5232
5809
|
};
|
|
5233
5810
|
}
|
|
5811
|
+
getBootstrapAttemptStorageKey(kind) {
|
|
5812
|
+
return `${this.options.session_token_key}_bootstrap_idempotency_${kind}`;
|
|
5813
|
+
}
|
|
5814
|
+
isValidBootstrapAttempt(value, kind, currentTime) {
|
|
5815
|
+
if (typeof value !== 'object' || value === null) {
|
|
5816
|
+
return false;
|
|
5817
|
+
}
|
|
5818
|
+
const attempt = value;
|
|
5819
|
+
if (attempt.version !== 1 ||
|
|
5820
|
+
attempt.projectKey !== this.projectKey ||
|
|
5821
|
+
typeof attempt.createdAt !== 'number' ||
|
|
5822
|
+
!Number.isFinite(attempt.createdAt) ||
|
|
5823
|
+
currentTime - attempt.createdAt >= BOOTSTRAP_ATTEMPT_TTL_MS ||
|
|
5824
|
+
typeof attempt.idempotencyKey !== 'string' ||
|
|
5825
|
+
attempt.idempotencyKey.length < 8 ||
|
|
5826
|
+
attempt.idempotencyKey.length > 128 ||
|
|
5827
|
+
!/^[A-Za-z0-9._:-]+$/.test(attempt.idempotencyKey) ||
|
|
5828
|
+
typeof attempt.intentFingerprint !== 'string' ||
|
|
5829
|
+
attempt.intentFingerprint.length === 0 ||
|
|
5830
|
+
attempt.intentFingerprint.length > MAX_BOOTSTRAP_ATTEMPT_STORAGE_LENGTH ||
|
|
5831
|
+
typeof attempt.requestTimestamp !== 'number' ||
|
|
5832
|
+
!Number.isFinite(attempt.requestTimestamp) ||
|
|
5833
|
+
typeof attempt.workerTimestamp !== 'number' ||
|
|
5834
|
+
!Number.isFinite(attempt.workerTimestamp) ||
|
|
5835
|
+
typeof attempt.requestBody !== 'string' ||
|
|
5836
|
+
attempt.requestBody.length > MAX_BOOTSTRAP_ATTEMPT_STORAGE_LENGTH) {
|
|
5837
|
+
return false;
|
|
5838
|
+
}
|
|
5839
|
+
try {
|
|
5840
|
+
const body = JSON.parse(attempt.requestBody);
|
|
5841
|
+
const intent = this.parseBootstrapIntent(attempt.intentFingerprint);
|
|
5842
|
+
return (intent?.projectKey === this.projectKey &&
|
|
5843
|
+
intent.kind === kind &&
|
|
5844
|
+
body.app?.key === this.projectKey &&
|
|
5845
|
+
body.session?.dry === false &&
|
|
5846
|
+
body.session?.offline === (kind === 'offline') &&
|
|
5847
|
+
typeof body.session?.token === 'string' &&
|
|
5848
|
+
body.session.token === intent.token &&
|
|
5849
|
+
body.session?.rule === intent.rule &&
|
|
5850
|
+
(body.user?.id ?? '') === intent.userID &&
|
|
5851
|
+
(body.user?.uuid ?? '') === intent.userUUID &&
|
|
5852
|
+
body.meta?.ts === attempt.requestTimestamp &&
|
|
5853
|
+
attempt.idempotencyKey.startsWith(`bootstrap:${kind}:`));
|
|
5854
|
+
}
|
|
5855
|
+
catch {
|
|
5856
|
+
return false;
|
|
5857
|
+
}
|
|
5858
|
+
}
|
|
5859
|
+
removeStoredBootstrapAttempt(kind) {
|
|
5860
|
+
delete this.bootstrapAttempts[kind];
|
|
5861
|
+
try {
|
|
5862
|
+
this.sessionStorage.removeItem(this.getBootstrapAttemptStorageKey(kind));
|
|
5863
|
+
}
|
|
5864
|
+
catch {
|
|
5865
|
+
// The in-memory copy is still cleared when storage is unavailable.
|
|
5866
|
+
}
|
|
5867
|
+
}
|
|
5868
|
+
loadBootstrapAttempt(kind) {
|
|
5869
|
+
const currentTime = Date.now();
|
|
5870
|
+
const inMemoryAttempt = this.bootstrapAttempts[kind];
|
|
5871
|
+
if (this.isValidBootstrapAttempt(inMemoryAttempt, kind, currentTime)) {
|
|
5872
|
+
return inMemoryAttempt;
|
|
5873
|
+
}
|
|
5874
|
+
delete this.bootstrapAttempts[kind];
|
|
5875
|
+
try {
|
|
5876
|
+
const storedAttempt = this.sessionStorage.getItem(this.getBootstrapAttemptStorageKey(kind));
|
|
5877
|
+
if (storedAttempt === null || storedAttempt.length > MAX_BOOTSTRAP_ATTEMPT_STORAGE_LENGTH) {
|
|
5878
|
+
if (storedAttempt !== null) {
|
|
5879
|
+
this.removeStoredBootstrapAttempt(kind);
|
|
5880
|
+
}
|
|
5881
|
+
return undefined;
|
|
5882
|
+
}
|
|
5883
|
+
const parsedAttempt = JSON.parse(storedAttempt);
|
|
5884
|
+
if (this.isValidBootstrapAttempt(parsedAttempt, kind, currentTime)) {
|
|
5885
|
+
this.bootstrapAttempts[kind] = parsedAttempt;
|
|
5886
|
+
return parsedAttempt;
|
|
5887
|
+
}
|
|
5888
|
+
}
|
|
5889
|
+
catch {
|
|
5890
|
+
// Treat corrupt or inaccessible storage as an absent pending attempt.
|
|
5891
|
+
}
|
|
5892
|
+
this.removeStoredBootstrapAttempt(kind);
|
|
5893
|
+
return undefined;
|
|
5894
|
+
}
|
|
5895
|
+
getOrCreateBootstrapAttempt(kind, request) {
|
|
5896
|
+
const existingAttempt = this.loadBootstrapAttempt(kind);
|
|
5897
|
+
if (existingAttempt &&
|
|
5898
|
+
this.bootstrapIntentsMatch(existingAttempt.intentFingerprint, request.intentFingerprint)) {
|
|
5899
|
+
return existingAttempt;
|
|
5900
|
+
}
|
|
5901
|
+
if (existingAttempt) {
|
|
5902
|
+
this.removeStoredBootstrapAttempt(kind);
|
|
5903
|
+
}
|
|
5904
|
+
const attempt = {
|
|
5905
|
+
version: 1,
|
|
5906
|
+
projectKey: this.projectKey,
|
|
5907
|
+
createdAt: Date.now(),
|
|
5908
|
+
idempotencyKey: `bootstrap:${kind}:${generateRandomId(40)}`,
|
|
5909
|
+
...request,
|
|
5910
|
+
};
|
|
5911
|
+
this.bootstrapAttempts[kind] = attempt;
|
|
5912
|
+
try {
|
|
5913
|
+
this.sessionStorage.setItem(this.getBootstrapAttemptStorageKey(kind), JSON.stringify(attempt));
|
|
5914
|
+
}
|
|
5915
|
+
catch {
|
|
5916
|
+
// The in-memory copy still protects retries within this tracker instance.
|
|
5917
|
+
}
|
|
5918
|
+
return attempt;
|
|
5919
|
+
}
|
|
5920
|
+
clearBootstrapAttempt(kind) {
|
|
5921
|
+
this.removeStoredBootstrapAttempt(kind);
|
|
5922
|
+
}
|
|
5923
|
+
buildBootstrapIntentFingerprint(params) {
|
|
5924
|
+
const tracker = this.getTrackerInfo();
|
|
5925
|
+
return JSON.stringify({
|
|
5926
|
+
projectKey: this.projectKey,
|
|
5927
|
+
kind: params.kind,
|
|
5928
|
+
token: params.token ?? '',
|
|
5929
|
+
rule: params.rule ?? '',
|
|
5930
|
+
userID: this.session.getInfo().userID ?? '',
|
|
5931
|
+
userUUID: tracker.userUUID ?? '',
|
|
5932
|
+
});
|
|
5933
|
+
}
|
|
5934
|
+
parseBootstrapIntent(value) {
|
|
5935
|
+
try {
|
|
5936
|
+
const intent = JSON.parse(value);
|
|
5937
|
+
if (intent.projectKey !== this.projectKey ||
|
|
5938
|
+
(intent.kind !== 'recording' && intent.kind !== 'offline') ||
|
|
5939
|
+
typeof intent.token !== 'string' ||
|
|
5940
|
+
typeof intent.rule !== 'string' ||
|
|
5941
|
+
typeof intent.userID !== 'string' ||
|
|
5942
|
+
typeof intent.userUUID !== 'string') {
|
|
5943
|
+
return undefined;
|
|
5944
|
+
}
|
|
5945
|
+
return intent;
|
|
5946
|
+
}
|
|
5947
|
+
catch {
|
|
5948
|
+
return undefined;
|
|
5949
|
+
}
|
|
5950
|
+
}
|
|
5951
|
+
bootstrapIntentsMatch(existingFingerprint, currentFingerprint) {
|
|
5952
|
+
const existing = this.parseBootstrapIntent(existingFingerprint);
|
|
5953
|
+
const current = this.parseBootstrapIntent(currentFingerprint);
|
|
5954
|
+
if (!existing || !current) {
|
|
5955
|
+
return false;
|
|
5956
|
+
}
|
|
5957
|
+
return (existing.projectKey === current.projectKey &&
|
|
5958
|
+
existing.kind === current.kind &&
|
|
5959
|
+
existing.token === current.token &&
|
|
5960
|
+
(current.rule === '' || existing.rule === current.rule) &&
|
|
5961
|
+
(current.userID === '' || existing.userID === current.userID) &&
|
|
5962
|
+
(current.userUUID === '' || existing.userUUID === current.userUUID));
|
|
5963
|
+
}
|
|
5234
5964
|
getSessionMeta() {
|
|
5235
5965
|
return {
|
|
5236
5966
|
...this.session.getInfo(),
|
|
@@ -5298,10 +6028,22 @@ class App {
|
|
|
5298
6028
|
}
|
|
5299
6029
|
}
|
|
5300
6030
|
checkSessionToken(startNewSession) {
|
|
5301
|
-
const
|
|
5302
|
-
|
|
6031
|
+
const needReset = this.sessionStorage.getItem(this.options.session_reset_key) !== null;
|
|
6032
|
+
let needNewSessionID = startNewSession || needReset;
|
|
5303
6033
|
const sessionToken = this.session.getSessionSecret(this.projectKey);
|
|
5304
|
-
|
|
6034
|
+
if (needReset) {
|
|
6035
|
+
this.sessionStorage.removeItem(this.options.session_reset_key);
|
|
6036
|
+
}
|
|
6037
|
+
const storedVersion = this.sessionStorage.getItem(`${this.options.session_token_key}_version`);
|
|
6038
|
+
if (!storedVersion || storedVersion !== PROTO_VERSION) {
|
|
6039
|
+
needNewSessionID = true;
|
|
6040
|
+
this.sessionStorage.setItem(`${this.options.session_token_key}_version`, PROTO_VERSION);
|
|
6041
|
+
}
|
|
6042
|
+
const isNewSession = needNewSessionID || !sessionToken;
|
|
6043
|
+
if (isNewSession && sessionToken) {
|
|
6044
|
+
this.session.clearSessionSecret();
|
|
6045
|
+
}
|
|
6046
|
+
return isNewSession;
|
|
5305
6047
|
}
|
|
5306
6048
|
/**
|
|
5307
6049
|
* start buffering messages without starting the actual session, which gives
|
|
@@ -5373,7 +6115,7 @@ class App {
|
|
|
5373
6115
|
})),
|
|
5374
6116
|
});
|
|
5375
6117
|
const { session = {}, client = {}, device = {} } = await r.json();
|
|
5376
|
-
const { token, projectId } = session;
|
|
6118
|
+
const { token, projectId, assistToken } = session;
|
|
5377
6119
|
const { browser: userBrowser, city: userCity, country: userCountry, device: userDevice, os: userOS, state: userState, } = client;
|
|
5378
6120
|
this.session.assign({ projectID: projectId });
|
|
5379
6121
|
this.session.setUserInfo({
|
|
@@ -5386,6 +6128,7 @@ class App {
|
|
|
5386
6128
|
});
|
|
5387
6129
|
const onStartInfo = {
|
|
5388
6130
|
sessionToken: token,
|
|
6131
|
+
assistToken: typeof assistToken === 'string' ? assistToken : undefined,
|
|
5389
6132
|
userUUID: device?.id || '',
|
|
5390
6133
|
sessionID: session?.id || '',
|
|
5391
6134
|
};
|
|
@@ -5401,7 +6144,6 @@ class App {
|
|
|
5401
6144
|
offlineRecording(startOpts = {}, onSessionSent) {
|
|
5402
6145
|
this.onSessionSent = onSessionSent;
|
|
5403
6146
|
this.singleBuffer = true;
|
|
5404
|
-
const isNewSession = this.checkSessionToken(startOpts.startNewSession);
|
|
5405
6147
|
adjustTimeOrigin();
|
|
5406
6148
|
this.coldStartTs = now();
|
|
5407
6149
|
const saverBuffer = this.localStorage.getItem(bufferStorageKey);
|
|
@@ -5419,6 +6161,10 @@ class App {
|
|
|
5419
6161
|
if (startOpts.startNewSession) {
|
|
5420
6162
|
this.session.reset();
|
|
5421
6163
|
}
|
|
6164
|
+
const isNewSession = this.checkSessionToken(startOpts.startNewSession);
|
|
6165
|
+
this.offlineRecordingSessionToken = isNewSession
|
|
6166
|
+
? undefined
|
|
6167
|
+
: this.session.getSessionSecret(this.projectKey);
|
|
5422
6168
|
this.session.assign({
|
|
5423
6169
|
userID: startOpts.userID,
|
|
5424
6170
|
metadata: startOpts.metadata,
|
|
@@ -5468,39 +6214,60 @@ class App {
|
|
|
5468
6214
|
* */
|
|
5469
6215
|
async uploadOfflineRecording() {
|
|
5470
6216
|
this.stop(false);
|
|
5471
|
-
const
|
|
6217
|
+
const freshTimestamp = now();
|
|
6218
|
+
const bootstrapToken = this.offlineRecordingSessionToken ?? '';
|
|
6219
|
+
const freshRequestBody = () => JSON.stringify(this.buildStartPayload({
|
|
6220
|
+
timestamp: freshTimestamp,
|
|
6221
|
+
dry: false,
|
|
6222
|
+
offline: true,
|
|
6223
|
+
bufferMs: freshTimestamp - this.coldStartTs,
|
|
6224
|
+
token: bootstrapToken,
|
|
6225
|
+
}));
|
|
6226
|
+
const bootstrapAttempt = this.getOrCreateBootstrapAttempt('offline', {
|
|
6227
|
+
intentFingerprint: this.buildBootstrapIntentFingerprint({
|
|
6228
|
+
kind: 'offline',
|
|
6229
|
+
token: bootstrapToken,
|
|
6230
|
+
}),
|
|
6231
|
+
requestTimestamp: freshTimestamp,
|
|
6232
|
+
workerTimestamp: this.coldStartTs,
|
|
6233
|
+
requestBody: freshRequestBody(),
|
|
6234
|
+
});
|
|
5472
6235
|
this.worker?.postMessage({
|
|
5473
6236
|
type: 'start',
|
|
5474
6237
|
pageNo: this.session.incPageNo(),
|
|
5475
6238
|
ingestPoint: normalizeEndpoint(this.options.endpoint),
|
|
5476
|
-
timestamp:
|
|
6239
|
+
timestamp: bootstrapAttempt.workerTimestamp,
|
|
5477
6240
|
url: document.URL,
|
|
5478
6241
|
connAttemptCount: this.options.connAttemptCount,
|
|
5479
6242
|
connAttemptGap: this.options.connAttemptGap,
|
|
5480
6243
|
tabId: this.session.getTabId(),
|
|
6244
|
+
localDebug: this.options.__local_debug,
|
|
5481
6245
|
});
|
|
5482
6246
|
const ingestPoint = normalizeEndpoint(this.options.endpoint);
|
|
6247
|
+
const headers = {
|
|
6248
|
+
'Content-Type': 'application/json',
|
|
6249
|
+
'Idempotency-Key': bootstrapAttempt.idempotencyKey,
|
|
6250
|
+
};
|
|
5483
6251
|
const r = await fetch(ingestPoint + '/v1/bootstrap', {
|
|
5484
6252
|
method: 'POST',
|
|
5485
|
-
headers
|
|
5486
|
-
|
|
5487
|
-
},
|
|
5488
|
-
body: JSON.stringify(this.buildStartPayload({
|
|
5489
|
-
timestamp,
|
|
5490
|
-
dry: false,
|
|
5491
|
-
bufferMs: timestamp - this.coldStartTs,
|
|
5492
|
-
token: '',
|
|
5493
|
-
})),
|
|
6253
|
+
headers,
|
|
6254
|
+
body: bootstrapAttempt.requestBody,
|
|
5494
6255
|
});
|
|
5495
|
-
|
|
6256
|
+
if (r.status !== 200) {
|
|
6257
|
+
throw new Error(`Server error: ${r.status}. ${await r.text()}`);
|
|
6258
|
+
}
|
|
6259
|
+
const { session = {}, client = {}, limits = {}, protocolVersion: offlineProtocolVersion, } = await r.json();
|
|
5496
6260
|
const { token, projectId } = session;
|
|
5497
6261
|
const { browser: userBrowser, city: userCity, country: userCountry, device: userDevice, os: userOS, state: userState, } = client;
|
|
5498
6262
|
const { beacon: beaconSizeLimit } = limits;
|
|
5499
|
-
|
|
5500
|
-
|
|
5501
|
-
|
|
5502
|
-
|
|
5503
|
-
}
|
|
6263
|
+
if (typeof token !== 'string' ||
|
|
6264
|
+
token.length === 0 ||
|
|
6265
|
+
(typeof beaconSizeLimit !== 'number' && typeof beaconSizeLimit !== 'undefined')) {
|
|
6266
|
+
throw new Error('Incorrect server response for offline recording bootstrap');
|
|
6267
|
+
}
|
|
6268
|
+
this.session.setSessionSecret(token, this.projectKey);
|
|
6269
|
+
this.clearBootstrapAttempt('offline');
|
|
6270
|
+
this.offlineRecordingSessionToken = token;
|
|
5504
6271
|
this.session.assign({ projectID: projectId });
|
|
5505
6272
|
this.session.setUserInfo({
|
|
5506
6273
|
userBrowser,
|
|
@@ -5510,16 +6277,84 @@ class App {
|
|
|
5510
6277
|
userOS,
|
|
5511
6278
|
userState,
|
|
5512
6279
|
});
|
|
6280
|
+
this.worker?.postMessage({
|
|
6281
|
+
type: 'auth',
|
|
6282
|
+
token,
|
|
6283
|
+
beaconSizeLimit,
|
|
6284
|
+
protocolVersion: offlineProtocolVersion,
|
|
6285
|
+
});
|
|
5513
6286
|
while (this.bufferedMessages1.length > 0) {
|
|
5514
6287
|
await this.flushBuffer(this.bufferedMessages1);
|
|
5515
6288
|
}
|
|
5516
6289
|
this.postToWorker([[-1]]);
|
|
5517
6290
|
this.clearBuffers();
|
|
5518
6291
|
}
|
|
6292
|
+
startCrossDomainFrame(startOpts = {}) {
|
|
6293
|
+
if (this.activityState === ActivityState.Active ||
|
|
6294
|
+
this.activityState === ActivityState.Starting) {
|
|
6295
|
+
const reason = 'OxvoSessions: trying to call `start()` on the instance that has been started already.';
|
|
6296
|
+
return UnsuccessfulStart(reason);
|
|
6297
|
+
}
|
|
6298
|
+
if (this.crossDomainFrameSession === null ||
|
|
6299
|
+
this.crossDomainFrameCapability === null ||
|
|
6300
|
+
this.rootId === null) {
|
|
6301
|
+
return UnsuccessfulStart('Cross-domain iframe handshake was not established.');
|
|
6302
|
+
}
|
|
6303
|
+
if (Object.keys(startOpts).length !== 0) {
|
|
6304
|
+
this.prevOpts = startOpts;
|
|
6305
|
+
}
|
|
6306
|
+
if (startOpts.startCallback) {
|
|
6307
|
+
this.userStartCallback = startOpts.startCallback;
|
|
6308
|
+
}
|
|
6309
|
+
try {
|
|
6310
|
+
adjustTimeOrigin();
|
|
6311
|
+
this.activityState = ActivityState.Starting;
|
|
6312
|
+
this.delay = this.crossDomainFrameSession.delayMs;
|
|
6313
|
+
this.session.clearSessionSecret();
|
|
6314
|
+
this.session.assign({
|
|
6315
|
+
sessionID: this.crossDomainFrameSession.sessionID,
|
|
6316
|
+
projectID: this.crossDomainFrameSession.projectID,
|
|
6317
|
+
timestamp: this.crossDomainFrameSession.startedAt,
|
|
6318
|
+
});
|
|
6319
|
+
const onStartInfo = {
|
|
6320
|
+
sessionToken: '',
|
|
6321
|
+
userUUID: this.crossDomainFrameSession.userUUID,
|
|
6322
|
+
sessionID: this.crossDomainFrameSession.sessionID,
|
|
6323
|
+
};
|
|
6324
|
+
this.startCallbacks.forEach((callback) => callback(onStartInfo));
|
|
6325
|
+
this.userStartCallback?.(SuccessfulStart(onStartInfo));
|
|
6326
|
+
this.activityState = ActivityState.Active;
|
|
6327
|
+
if (this.crossDomainFrameCanvas?.enabled && !this.options.canvas.disableCanvas) {
|
|
6328
|
+
this.canvasRecorder =
|
|
6329
|
+
this.canvasRecorder ??
|
|
6330
|
+
new CanvasRecorder(this, {
|
|
6331
|
+
fps: this.crossDomainFrameCanvas.fps ?? 15,
|
|
6332
|
+
quality: this.crossDomainFrameCanvas.quality ?? 'medium',
|
|
6333
|
+
isDebug: this.options.canvas.__save_canvas_locally,
|
|
6334
|
+
fixedScaling: this.options.canvas.fixedCanvasScaling,
|
|
6335
|
+
useAnimationFrame: this.options.canvas.useAnimationFrame,
|
|
6336
|
+
framesSupport: this.crossDomainFrameCanvas.framesSupport,
|
|
6337
|
+
});
|
|
6338
|
+
}
|
|
6339
|
+
this.observer.crossdomainObserve(this.rootId, this.frameOderNumber, this.frameLevel);
|
|
6340
|
+
this.ticker.start();
|
|
6341
|
+
this.canvasRecorder?.startTracking();
|
|
6342
|
+
return SuccessfulStart(onStartInfo);
|
|
6343
|
+
}
|
|
6344
|
+
catch (reason) {
|
|
6345
|
+
this.stop();
|
|
6346
|
+
const message = reason instanceof Error ? reason.message : String(reason);
|
|
6347
|
+
this.signalError(message, []);
|
|
6348
|
+
return UnsuccessfulStart(message);
|
|
6349
|
+
}
|
|
6350
|
+
}
|
|
5519
6351
|
async _start(startOpts = {}, resetByWorker = false, conditionName) {
|
|
5520
6352
|
if (Object.keys(startOpts).length !== 0) {
|
|
5521
6353
|
this.prevOpts = startOpts;
|
|
5522
6354
|
}
|
|
6355
|
+
if (startOpts.startCallback) {
|
|
6356
|
+
this.userStartCallback = startOpts.startCallback;
|
|
6357
|
+
}
|
|
5523
6358
|
const isColdStart = this.activityState === ActivityState.ColdStart;
|
|
5524
6359
|
if (isColdStart && this.coldInterval) {
|
|
5525
6360
|
clearInterval(this.coldInterval);
|
|
@@ -5551,67 +6386,89 @@ class App {
|
|
|
5551
6386
|
userID: userId || undefined,
|
|
5552
6387
|
metadata: startOpts.metadata,
|
|
5553
6388
|
});
|
|
5554
|
-
const
|
|
6389
|
+
const sessionToken = this.session.getSessionSecret(this.projectKey);
|
|
6390
|
+
const isNewSession = this.checkSessionToken(startOpts.startNewSession);
|
|
6391
|
+
const freshTimestamp = now();
|
|
6392
|
+
const freshWorkerTimestamp = isColdStart ? this.coldStartTs : freshTimestamp;
|
|
6393
|
+
const bootstrapToken = isNewSession ? '' : sessionToken;
|
|
6394
|
+
const freshRequestBody = () => JSON.stringify(this.buildStartPayload({
|
|
6395
|
+
timestamp: freshTimestamp,
|
|
6396
|
+
dry: false,
|
|
6397
|
+
bufferMs: this.coldStartTs ? freshTimestamp - this.coldStartTs : 0,
|
|
6398
|
+
token: bootstrapToken,
|
|
6399
|
+
rule: conditionName,
|
|
6400
|
+
width: window.screen.width,
|
|
6401
|
+
height: window.screen.height,
|
|
6402
|
+
}));
|
|
6403
|
+
const bootstrapAttempt = this.getOrCreateBootstrapAttempt('recording', {
|
|
6404
|
+
intentFingerprint: this.buildBootstrapIntentFingerprint({
|
|
6405
|
+
kind: 'recording',
|
|
6406
|
+
token: bootstrapToken,
|
|
6407
|
+
rule: conditionName,
|
|
6408
|
+
}),
|
|
6409
|
+
requestTimestamp: freshTimestamp,
|
|
6410
|
+
workerTimestamp: freshWorkerTimestamp,
|
|
6411
|
+
requestBody: freshRequestBody(),
|
|
6412
|
+
});
|
|
6413
|
+
const timestamp = bootstrapAttempt.requestTimestamp;
|
|
5555
6414
|
this.worker?.postMessage({
|
|
5556
6415
|
type: 'start',
|
|
5557
6416
|
pageNo: this.session.incPageNo(),
|
|
5558
6417
|
ingestPoint: normalizeEndpoint(this.options.endpoint),
|
|
5559
|
-
timestamp:
|
|
6418
|
+
timestamp: bootstrapAttempt.workerTimestamp,
|
|
5560
6419
|
url: document.URL,
|
|
5561
6420
|
connAttemptCount: this.options.connAttemptCount,
|
|
5562
6421
|
connAttemptGap: this.options.connAttemptGap,
|
|
5563
6422
|
tabId: this.session.getTabId(),
|
|
6423
|
+
localDebug: this.options.__local_debug,
|
|
5564
6424
|
});
|
|
5565
|
-
const sessionToken = this.session.getSessionSecret(this.projectKey);
|
|
5566
|
-
const isNewSession = this.checkSessionToken(startOpts.startNewSession);
|
|
5567
|
-
this.sessionStorage.removeItem(this.options.session_reset_key);
|
|
5568
6425
|
this.debug.log('OxvoSessions: starting session; need new session id?', isNewSession, 'session token: ', sessionToken);
|
|
5569
6426
|
try {
|
|
5570
6427
|
const ingestPoint = normalizeEndpoint(this.options.endpoint);
|
|
6428
|
+
const headers = {
|
|
6429
|
+
'Content-Type': 'application/json',
|
|
6430
|
+
'Idempotency-Key': bootstrapAttempt.idempotencyKey,
|
|
6431
|
+
};
|
|
5571
6432
|
const r = await window.fetch(ingestPoint + '/v1/bootstrap', {
|
|
5572
6433
|
method: 'POST',
|
|
5573
|
-
headers
|
|
5574
|
-
|
|
5575
|
-
},
|
|
5576
|
-
body: JSON.stringify(this.buildStartPayload({
|
|
5577
|
-
timestamp,
|
|
5578
|
-
dry: false,
|
|
5579
|
-
bufferMs: timestamp - this.coldStartTs,
|
|
5580
|
-
token: isNewSession ? '' : sessionToken,
|
|
5581
|
-
rule: conditionName,
|
|
5582
|
-
width: window.screen.width,
|
|
5583
|
-
height: window.screen.height,
|
|
5584
|
-
})),
|
|
6434
|
+
headers,
|
|
6435
|
+
body: bootstrapAttempt.requestBody,
|
|
5585
6436
|
});
|
|
5586
6437
|
if (r.status !== 200) {
|
|
5587
6438
|
const error = await r.text();
|
|
5588
6439
|
const reason = error === CANCELED ? CANCELED : `Server error: ${r.status}. ${error}`;
|
|
5589
|
-
|
|
6440
|
+
throw reason;
|
|
5590
6441
|
}
|
|
5591
6442
|
if (!this.worker && !this.insideIframe) {
|
|
5592
6443
|
const reason = 'no worker found after start request (this should not happen in real world)';
|
|
5593
|
-
|
|
5594
|
-
return UnsuccessfulStart(reason);
|
|
6444
|
+
throw new Error(reason);
|
|
5595
6445
|
}
|
|
5596
|
-
const { session = {}, device = {}, client = {}, limits = {}, canvas = {}, flags = {} } = await r.json();
|
|
5597
|
-
const { token, id: sessionID, projectId: projectID, delayMs: delay, startedAt: startTimestamp, } = session;
|
|
6446
|
+
const { session = {}, device = {}, client = {}, limits = {}, canvas = {}, flags = {}, protocolVersion, } = await r.json();
|
|
6447
|
+
const { token, assistToken, id: sessionID, projectId: projectID, delayMs: delay, startedAt: startTimestamp, } = session;
|
|
5598
6448
|
const { id: userUUID } = device;
|
|
5599
6449
|
const { browser: userBrowser, city: userCity, country: userCountry, device: userDevice, os: userOS, state: userState, } = client;
|
|
5600
6450
|
const { beacon: beaconSizeLimit, compressAt: compressionThreshold } = limits;
|
|
5601
|
-
const { enabled: canvasEnabled, quality: canvasQuality, fps: canvasFPS } = canvas;
|
|
6451
|
+
const { enabled: canvasEnabled, quality: canvasQuality, fps: canvasFPS, framesSupport, } = canvas;
|
|
5602
6452
|
const socketOnly = flags?.socketOnly;
|
|
5603
6453
|
if (typeof token !== 'string' ||
|
|
6454
|
+
(typeof assistToken !== 'string' && typeof assistToken !== 'undefined') ||
|
|
5604
6455
|
typeof userUUID !== 'string' ||
|
|
5605
6456
|
(typeof startTimestamp !== 'number' && typeof startTimestamp !== 'undefined') ||
|
|
5606
6457
|
typeof sessionID !== 'string' ||
|
|
5607
6458
|
typeof delay !== 'number' ||
|
|
5608
6459
|
(typeof beaconSizeLimit !== 'number' && typeof beaconSizeLimit !== 'undefined')) {
|
|
5609
6460
|
const reason = `Incorrect server response: ${JSON.stringify(r)}`;
|
|
5610
|
-
|
|
5611
|
-
return UnsuccessfulStart(reason);
|
|
6461
|
+
throw new Error(reason);
|
|
5612
6462
|
}
|
|
6463
|
+
this.crossDomainCanvasConfig = {
|
|
6464
|
+
enabled: canvasEnabled === true,
|
|
6465
|
+
...(['low', 'medium', 'high'].includes(canvasQuality) ? { quality: canvasQuality } : {}),
|
|
6466
|
+
...(typeof canvasFPS === 'number' ? { fps: canvasFPS } : {}),
|
|
6467
|
+
...(typeof framesSupport === 'boolean' ? { framesSupport } : {}),
|
|
6468
|
+
};
|
|
5613
6469
|
this.delay = delay;
|
|
5614
6470
|
this.session.setSessionSecret(token, this.projectKey);
|
|
6471
|
+
this.clearBootstrapAttempt('recording');
|
|
5615
6472
|
if (sessionToken && sessionToken !== token) {
|
|
5616
6473
|
this.bc?.postMessage({
|
|
5617
6474
|
type: proto.reset,
|
|
@@ -5640,6 +6497,7 @@ class App {
|
|
|
5640
6497
|
type: 'auth',
|
|
5641
6498
|
token,
|
|
5642
6499
|
beaconSizeLimit,
|
|
6500
|
+
protocolVersion,
|
|
5643
6501
|
});
|
|
5644
6502
|
}
|
|
5645
6503
|
if (!isNewSession && token === sessionToken) {
|
|
@@ -5651,14 +6509,13 @@ class App {
|
|
|
5651
6509
|
Object.entries(this.session.getInfo().metadata).forEach(([key, value]) => this.send(Metadata(key, value)));
|
|
5652
6510
|
this.localStorage.setItem(this.options.local_uuid_key, userUUID);
|
|
5653
6511
|
this.compressionThreshold = compressionThreshold;
|
|
5654
|
-
const onStartInfo = { sessionToken: token, userUUID, sessionID };
|
|
6512
|
+
const onStartInfo = { sessionToken: token, assistToken, userUUID, sessionID };
|
|
5655
6513
|
// TODO: start as early as possible (before receiving the token)
|
|
5656
6514
|
/** after start */
|
|
5657
6515
|
this.startCallbacks.forEach((cb) => cb(onStartInfo)); // MBTODO: callbacks after DOM "mounted" (observed)
|
|
5658
|
-
if (
|
|
5659
|
-
|
|
6516
|
+
if (this.userStartCallback) {
|
|
6517
|
+
this.userStartCallback(SuccessfulStart(onStartInfo));
|
|
5660
6518
|
}
|
|
5661
|
-
await this.markerWatcher.fetchTags(normalizeEndpoint(this.options.endpoint), token);
|
|
5662
6519
|
this.activityState = ActivityState.Active;
|
|
5663
6520
|
if (this.options.crossdomain?.enabled) {
|
|
5664
6521
|
void this.bootChildrenFrames();
|
|
@@ -5672,6 +6529,7 @@ class App {
|
|
|
5672
6529
|
isDebug: this.options.canvas.__save_canvas_locally,
|
|
5673
6530
|
fixedScaling: this.options.canvas.fixedCanvasScaling,
|
|
5674
6531
|
useAnimationFrame: this.options.canvas.useAnimationFrame,
|
|
6532
|
+
framesSupport: !!framesSupport,
|
|
5675
6533
|
});
|
|
5676
6534
|
}
|
|
5677
6535
|
/** --------------- COLD START BUFFER ------------------*/
|
|
@@ -5694,11 +6552,11 @@ class App {
|
|
|
5694
6552
|
}
|
|
5695
6553
|
this.ticker.start();
|
|
5696
6554
|
this.canvasRecorder?.startTracking();
|
|
6555
|
+
void this.markerWatcher.fetchTags(normalizeEndpoint(this.options.endpoint), token);
|
|
5697
6556
|
return SuccessfulStart(onStartInfo);
|
|
5698
6557
|
}
|
|
5699
6558
|
catch (reason) {
|
|
5700
6559
|
this.stop();
|
|
5701
|
-
this.session.reset();
|
|
5702
6560
|
if (!reason) {
|
|
5703
6561
|
console.error('Unknown error during start');
|
|
5704
6562
|
this.signalError('Unknown error', []);
|
|
@@ -5724,6 +6582,23 @@ class App {
|
|
|
5724
6582
|
}, 100);
|
|
5725
6583
|
});
|
|
5726
6584
|
}
|
|
6585
|
+
async waitCrossDomainStart(timeoutMs = 15000) {
|
|
6586
|
+
if (this.canStart)
|
|
6587
|
+
return true;
|
|
6588
|
+
return new Promise((resolve) => {
|
|
6589
|
+
const interval = setInterval(() => {
|
|
6590
|
+
if (!this.canStart)
|
|
6591
|
+
return;
|
|
6592
|
+
clearInterval(interval);
|
|
6593
|
+
clearTimeout(timeout);
|
|
6594
|
+
resolve(true);
|
|
6595
|
+
}, 100);
|
|
6596
|
+
const timeout = setTimeout(() => {
|
|
6597
|
+
clearInterval(interval);
|
|
6598
|
+
resolve(false);
|
|
6599
|
+
}, timeoutMs);
|
|
6600
|
+
});
|
|
6601
|
+
}
|
|
5727
6602
|
async waitStarted() {
|
|
5728
6603
|
return this.waitStatus(ActivityState.Active);
|
|
5729
6604
|
}
|
|
@@ -5751,20 +6626,35 @@ class App {
|
|
|
5751
6626
|
return Promise.resolve(UnsuccessfulStart(reason));
|
|
5752
6627
|
}
|
|
5753
6628
|
if (this.insideIframe) {
|
|
6629
|
+
if (this.crossDomainParentOrigin === null || this.crossDomainFrameNonce === null) {
|
|
6630
|
+
return UnsuccessfulStart('Cross-domain iframe capture requires an explicit parentDomain and secure random support.');
|
|
6631
|
+
}
|
|
5754
6632
|
this.signalIframeTracker();
|
|
5755
6633
|
}
|
|
5756
|
-
|
|
6634
|
+
const startWhenReady = async () => {
|
|
6635
|
+
if (this.insideIframe) {
|
|
6636
|
+
const ready = await this.waitCrossDomainStart();
|
|
6637
|
+
if (!ready) {
|
|
6638
|
+
this.crossDomainConnectionRequested = false;
|
|
6639
|
+
this.crossDomainResumeAfterHandshake = false;
|
|
6640
|
+
this.clearCrossDomainFrameAuthorization();
|
|
6641
|
+
return UnsuccessfulStart('Cross-domain iframe handshake timed out.');
|
|
6642
|
+
}
|
|
6643
|
+
return this.startCrossDomainFrame(args[0]);
|
|
6644
|
+
}
|
|
5757
6645
|
await this.waitStart();
|
|
5758
6646
|
return this._start(...args);
|
|
6647
|
+
};
|
|
6648
|
+
if (!document.hidden) {
|
|
6649
|
+
return startWhenReady();
|
|
5759
6650
|
}
|
|
5760
6651
|
else {
|
|
5761
6652
|
return new Promise((resolve) => {
|
|
5762
6653
|
const onVisibilityChange = async () => {
|
|
5763
6654
|
if (!document.hidden) {
|
|
5764
|
-
await this.waitStart();
|
|
5765
6655
|
// eslint-disable-next-line
|
|
5766
6656
|
document.removeEventListener('visibilitychange', onVisibilityChange);
|
|
5767
|
-
resolve(
|
|
6657
|
+
resolve(startWhenReady());
|
|
5768
6658
|
}
|
|
5769
6659
|
};
|
|
5770
6660
|
// eslint-disable-next-line
|
|
@@ -5818,12 +6708,18 @@ class App {
|
|
|
5818
6708
|
this.canvasRecorder?.clear();
|
|
5819
6709
|
this.messages.length = 0;
|
|
5820
6710
|
this.parentActive = false;
|
|
6711
|
+
this.pageFrames = [];
|
|
5821
6712
|
}
|
|
5822
6713
|
finally {
|
|
5823
6714
|
this.activityState = ActivityState.NotActive;
|
|
5824
6715
|
this.debug.log('OxvoSessions tracking stopped.');
|
|
5825
6716
|
}
|
|
5826
6717
|
}
|
|
6718
|
+
if (this.insideIframe) {
|
|
6719
|
+
this.crossDomainConnectionRequested = false;
|
|
6720
|
+
this.crossDomainResumeAfterHandshake = false;
|
|
6721
|
+
this.clearCrossDomainFrameAuthorization();
|
|
6722
|
+
}
|
|
5827
6723
|
}
|
|
5828
6724
|
}
|
|
5829
6725
|
|
|
@@ -6315,6 +7211,12 @@ function Img (app) {
|
|
|
6315
7211
|
sendImgAttrs(node);
|
|
6316
7212
|
observer.observe(node, { attributes: true, attributeFilter: ['src', 'srcset'] });
|
|
6317
7213
|
});
|
|
7214
|
+
// On a runtime level change, re-evaluate placeholder vs real src for this image.
|
|
7215
|
+
app.attachResanitizeCallback((node) => {
|
|
7216
|
+
if (hasTag(node, 'img')) {
|
|
7217
|
+
sendImgAttrs(node);
|
|
7218
|
+
}
|
|
7219
|
+
});
|
|
6318
7220
|
}
|
|
6319
7221
|
|
|
6320
7222
|
const INPUT_TYPES = [
|
|
@@ -6372,19 +7274,24 @@ const labelElementFor = IN_BROWSER && 'labels' in HTMLInputElement.prototype
|
|
|
6372
7274
|
}
|
|
6373
7275
|
}
|
|
6374
7276
|
};
|
|
6375
|
-
function getInputLabel(node) {
|
|
6376
|
-
|
|
6377
|
-
if (
|
|
6378
|
-
|
|
6379
|
-
|
|
6380
|
-
|
|
6381
|
-
|
|
6382
|
-
|
|
6383
|
-
|
|
6384
|
-
|
|
6385
|
-
|
|
6386
|
-
|
|
6387
|
-
|
|
7277
|
+
function getInputLabel(node, customAttributes) {
|
|
7278
|
+
const oxvosessionsLabel = getLabelAttribute(node);
|
|
7279
|
+
if (oxvosessionsLabel !== null)
|
|
7280
|
+
return normSpaces(oxvosessionsLabel).slice(0, 100);
|
|
7281
|
+
const customAttributeLabel = getCustomAttributeLabel(node, customAttributes);
|
|
7282
|
+
if (customAttributeLabel)
|
|
7283
|
+
return customAttributeLabel;
|
|
7284
|
+
if (node.id)
|
|
7285
|
+
return `#${node.id}`;
|
|
7286
|
+
const classLabel = getClassSelector(node);
|
|
7287
|
+
if (classLabel)
|
|
7288
|
+
return classLabel;
|
|
7289
|
+
const labelElement = labelElementFor(node);
|
|
7290
|
+
const label = node.name ||
|
|
7291
|
+
node.placeholder ||
|
|
7292
|
+
(labelElement && labelElement.innerText) ||
|
|
7293
|
+
node.type;
|
|
7294
|
+
return normSpaces(label || '').slice(0, 100);
|
|
6388
7295
|
}
|
|
6389
7296
|
const InputMode = {
|
|
6390
7297
|
Plain: 0,
|
|
@@ -6406,9 +7313,12 @@ function Input (app, opts) {
|
|
|
6406
7313
|
}
|
|
6407
7314
|
else if (app.sanitizer.isObscured(id) ||
|
|
6408
7315
|
(inputMode === InputMode.Plain &&
|
|
6409
|
-
((options.maskInputNumbers &&
|
|
7316
|
+
((options.maskInputNumbers &&
|
|
7317
|
+
node.type !== 'date' &&
|
|
7318
|
+
/\d\d\d\d/.test(value)) ||
|
|
6410
7319
|
(options.maskInputDates && node.type === 'date') ||
|
|
6411
|
-
(options.maskInputEmails &&
|
|
7320
|
+
(options.maskInputEmails &&
|
|
7321
|
+
(node.type === 'email' || !!~value.indexOf('@')))))) {
|
|
6412
7322
|
inputMode = InputMode.Obscured;
|
|
6413
7323
|
}
|
|
6414
7324
|
let mask = 0;
|
|
@@ -6428,11 +7338,24 @@ function Input (app, opts) {
|
|
|
6428
7338
|
const { value, mask } = getInputValue(id, node);
|
|
6429
7339
|
app.send(SetInputValue(id, value, mask));
|
|
6430
7340
|
}
|
|
7341
|
+
const tagSelectorMap = new Map();
|
|
6431
7342
|
const inputValues = new Map();
|
|
6432
7343
|
const checkboxValues = new Map();
|
|
7344
|
+
function getTagSelector(id, node) {
|
|
7345
|
+
const cached = tagSelectorMap.get(id);
|
|
7346
|
+
if (cached !== undefined)
|
|
7347
|
+
return cached;
|
|
7348
|
+
const tagMatch = app.tagMatcher.match(node);
|
|
7349
|
+
if (tagMatch) {
|
|
7350
|
+
tagSelectorMap.set(id, tagMatch.selector);
|
|
7351
|
+
return tagMatch.selector;
|
|
7352
|
+
}
|
|
7353
|
+
return null;
|
|
7354
|
+
}
|
|
6433
7355
|
app.attachStopCallback(() => {
|
|
6434
7356
|
inputValues.clear();
|
|
6435
7357
|
checkboxValues.clear();
|
|
7358
|
+
tagSelectorMap.clear();
|
|
6436
7359
|
});
|
|
6437
7360
|
function trackInputValue(id, node) {
|
|
6438
7361
|
if (inputValues.get(id) === node.value) {
|
|
@@ -6465,12 +7388,19 @@ function Input (app, opts) {
|
|
|
6465
7388
|
}, 3);
|
|
6466
7389
|
function sendInputChange(id, node, hesitationTime, inputTime) {
|
|
6467
7390
|
const { value, mask } = getInputValue(id, node);
|
|
6468
|
-
let label = getInputLabel(node);
|
|
7391
|
+
let label = getTagSelector(id, node) || getInputLabel(node, options.customAttributes);
|
|
6469
7392
|
if (app.sanitizer.maskAllByDefault) {
|
|
6470
7393
|
label = label.replaceAll(/./g, '*');
|
|
6471
7394
|
}
|
|
6472
7395
|
app.send(InputChange(id, value, mask !== 0, label, hesitationTime, inputTime));
|
|
6473
7396
|
}
|
|
7397
|
+
// Re-emit a field's value when its sanitization level changes at runtime.
|
|
7398
|
+
// getInputValue() reads the current level, so re-sending applies the new mask.
|
|
7399
|
+
app.attachResanitizeCallback((node, id) => {
|
|
7400
|
+
if (isTextFieldElement(node) || hasTag(node, 'select')) {
|
|
7401
|
+
sendInputValue(id, node);
|
|
7402
|
+
}
|
|
7403
|
+
});
|
|
6474
7404
|
app.nodes.attachNodeCallback(app.safe((node) => {
|
|
6475
7405
|
const id = app.nodes.getID(node);
|
|
6476
7406
|
if (id === undefined) {
|
|
@@ -6574,21 +7504,28 @@ function _getTarget(target, document) {
|
|
|
6574
7504
|
return target === document.documentElement ? null : target;
|
|
6575
7505
|
}
|
|
6576
7506
|
function Mouse (app, options) {
|
|
6577
|
-
const { disableClickmaps = false } = options || {};
|
|
7507
|
+
const { disableClickmaps = false, customAttributes } = options || {};
|
|
6578
7508
|
function getTargetLabel(target) {
|
|
6579
7509
|
const dl = getLabelAttribute(target);
|
|
6580
7510
|
if (dl !== null) {
|
|
6581
7511
|
return dl;
|
|
6582
7512
|
}
|
|
6583
7513
|
if (hasTag(target, 'input')) {
|
|
6584
|
-
return getInputLabel(target);
|
|
6585
|
-
}
|
|
7514
|
+
return getInputLabel(target, customAttributes);
|
|
7515
|
+
}
|
|
7516
|
+
const customAttributeLabel = getCustomAttributeLabel(target, customAttributes);
|
|
7517
|
+
if (customAttributeLabel)
|
|
7518
|
+
return customAttributeLabel;
|
|
7519
|
+
if (target.id)
|
|
7520
|
+
return `#${target.id}`;
|
|
7521
|
+
const classLabel = getClassSelector(target);
|
|
7522
|
+
if (classLabel)
|
|
7523
|
+
return classLabel;
|
|
6586
7524
|
if (isClickable(target)) {
|
|
6587
7525
|
let label = '';
|
|
6588
7526
|
if (target instanceof HTMLElement) {
|
|
6589
7527
|
label = app.sanitizer.getInnerTextSecure(target);
|
|
6590
7528
|
}
|
|
6591
|
-
label = label || target.id || target.className;
|
|
6592
7529
|
return normSpaces(label).slice(0, 100);
|
|
6593
7530
|
}
|
|
6594
7531
|
return '';
|
|
@@ -6641,7 +7578,13 @@ function Mouse (app, options) {
|
|
|
6641
7578
|
};
|
|
6642
7579
|
const patchDocument = (document, topframe = false) => {
|
|
6643
7580
|
function getSelector(id, target) {
|
|
6644
|
-
|
|
7581
|
+
if (selectorMap[id])
|
|
7582
|
+
return selectorMap[id];
|
|
7583
|
+
const tagMatch = app.tagMatcher.match(target);
|
|
7584
|
+
if (tagMatch) {
|
|
7585
|
+
return (selectorMap[id] = tagMatch.selector);
|
|
7586
|
+
}
|
|
7587
|
+
return (selectorMap[id] = _getSelector(target));
|
|
6645
7588
|
}
|
|
6646
7589
|
const attachListener = topframe
|
|
6647
7590
|
? app.attachEventListener.bind(app) // attached/removed on start/stop
|
|
@@ -6732,6 +7675,11 @@ function getCSSPath(el) {
|
|
|
6732
7675
|
return false;
|
|
6733
7676
|
if (el.id)
|
|
6734
7677
|
return `#${cssEscape(el.id)}`;
|
|
7678
|
+
// if has data attributes - use them as they are more likely to be stable and unique
|
|
7679
|
+
const dataAttr = Array.from(el.attributes).find(attr => attr.name.startsWith('data-'));
|
|
7680
|
+
if (dataAttr) {
|
|
7681
|
+
return `[${dataAttr.name}="${cssEscape(dataAttr.value)}"]`;
|
|
7682
|
+
}
|
|
6735
7683
|
const parts = [];
|
|
6736
7684
|
while (el && el.nodeType === 1 && el !== el.ownerDocument) {
|
|
6737
7685
|
if (el.id) {
|
|
@@ -7072,11 +8020,27 @@ function Scroll (app, insideIframe) {
|
|
|
7072
8020
|
}, 5, false);
|
|
7073
8021
|
}
|
|
7074
8022
|
|
|
8023
|
+
const NativeURL = URL;
|
|
8024
|
+
const defaultUrlSanitizer = (url) => {
|
|
8025
|
+
const hiddenQueryParams = ['jwt', 'password', 'reset-password', 'invitation', 'secret', 'token'];
|
|
8026
|
+
try {
|
|
8027
|
+
const u = new NativeURL(url);
|
|
8028
|
+
u.searchParams.forEach((value, key) => {
|
|
8029
|
+
if (hiddenQueryParams.includes(key.toLowerCase())) {
|
|
8030
|
+
u.searchParams.set(key, '*'.repeat(value.length));
|
|
8031
|
+
}
|
|
8032
|
+
});
|
|
8033
|
+
return u.toString();
|
|
8034
|
+
}
|
|
8035
|
+
catch {
|
|
8036
|
+
return url;
|
|
8037
|
+
}
|
|
8038
|
+
};
|
|
7075
8039
|
function Viewport (app, options) {
|
|
7076
8040
|
let url, width, height;
|
|
7077
8041
|
let navigationStart;
|
|
7078
8042
|
let referrer = document.referrer;
|
|
7079
|
-
const urlSanitizer = options?.urlSanitizer ||
|
|
8043
|
+
const urlSanitizer = options?.urlSanitizer || defaultUrlSanitizer;
|
|
7080
8044
|
const titleSanitizer = options?.titleSanitizer || ((t) => t);
|
|
7081
8045
|
const sendSetPageLocation = app.safe(() => {
|
|
7082
8046
|
const currURL = document.URL;
|
|
@@ -8902,6 +9866,8 @@ function webAnimations(app, options = {}) {
|
|
|
8902
9866
|
const lastKF = anim.effect.getKeyframes().at(-1);
|
|
8903
9867
|
if (!lastKF)
|
|
8904
9868
|
return;
|
|
9869
|
+
if (!el || !(el instanceof Element) || !el.isConnected)
|
|
9870
|
+
return;
|
|
8905
9871
|
const computedStyle = getComputedStyle(el);
|
|
8906
9872
|
const keys = Object.keys(lastKF).filter((p) => !toIgnore.includes(p));
|
|
8907
9873
|
// @ts-ignore
|
|
@@ -9313,7 +10279,7 @@ class ConstantProperties {
|
|
|
9313
10279
|
user_id: this.user_id,
|
|
9314
10280
|
distinct_id: this.deviceId,
|
|
9315
10281
|
sdk_edition: 'web',
|
|
9316
|
-
sdk_version: '7.
|
|
10282
|
+
sdk_version: '7.3.4',
|
|
9317
10283
|
timezone: getUTCOffsetString(),
|
|
9318
10284
|
search_engine: this.searchEngine,
|
|
9319
10285
|
};
|
|
@@ -9701,10 +10667,11 @@ class People {
|
|
|
9701
10667
|
* Creates batches of events, then sends them at intervals.
|
|
9702
10668
|
*/
|
|
9703
10669
|
class Batcher {
|
|
9704
|
-
constructor(backendUrl, getToken, init) {
|
|
10670
|
+
constructor(backendUrl, getToken, init, standalone) {
|
|
9705
10671
|
this.backendUrl = backendUrl;
|
|
9706
10672
|
this.getToken = getToken;
|
|
9707
10673
|
this.init = init;
|
|
10674
|
+
this.standalone = standalone;
|
|
9708
10675
|
this.autosendInterval = 5 * 1000;
|
|
9709
10676
|
this.retryTimeout = 3 * 1000;
|
|
9710
10677
|
this.retryLimit = 3;
|
|
@@ -9714,10 +10681,20 @@ class Batcher {
|
|
|
9714
10681
|
[categories.events]: [],
|
|
9715
10682
|
};
|
|
9716
10683
|
this.intervalId = null;
|
|
10684
|
+
this.stopped = false;
|
|
10685
|
+
this.paused = false;
|
|
10686
|
+
this.onVisibilityChange = () => {
|
|
10687
|
+
this.paused = document.hidden;
|
|
10688
|
+
};
|
|
9717
10689
|
}
|
|
9718
10690
|
getBatches() {
|
|
9719
10691
|
this.batch[categories.people] = this.dedupePeopleEvents();
|
|
9720
|
-
const finalData = {
|
|
10692
|
+
const finalData = {
|
|
10693
|
+
data: {
|
|
10694
|
+
[categories.people]: [...this.batch[categories.people]],
|
|
10695
|
+
[categories.events]: [...this.batch[categories.events]],
|
|
10696
|
+
},
|
|
10697
|
+
};
|
|
9721
10698
|
return finalData;
|
|
9722
10699
|
}
|
|
9723
10700
|
addEvent(event) {
|
|
@@ -9793,6 +10770,9 @@ class Batcher {
|
|
|
9793
10770
|
return Array.from(uniqueEventsByType.values());
|
|
9794
10771
|
}
|
|
9795
10772
|
sendBatch(batch) {
|
|
10773
|
+
if (this.stopped) {
|
|
10774
|
+
return;
|
|
10775
|
+
}
|
|
9796
10776
|
const sentBatch = batch;
|
|
9797
10777
|
let attempts = 0;
|
|
9798
10778
|
const send = () => {
|
|
@@ -9809,11 +10789,27 @@ class Batcher {
|
|
|
9809
10789
|
},
|
|
9810
10790
|
body: JSON.stringify(sentBatch),
|
|
9811
10791
|
})
|
|
9812
|
-
.then((response) => {
|
|
10792
|
+
.then(async (response) => {
|
|
10793
|
+
if (response.status === 401) {
|
|
10794
|
+
const body = await response.json().catch(() => null);
|
|
10795
|
+
if (!this.standalone && body?.error === 'token expired') {
|
|
10796
|
+
this.stop();
|
|
10797
|
+
return;
|
|
10798
|
+
}
|
|
10799
|
+
if (attempts < this.retryLimit) {
|
|
10800
|
+
return this.init().then(() => {
|
|
10801
|
+
send();
|
|
10802
|
+
});
|
|
10803
|
+
}
|
|
10804
|
+
return;
|
|
10805
|
+
}
|
|
9813
10806
|
if (response.status === 403) {
|
|
9814
|
-
this.
|
|
9815
|
-
|
|
9816
|
-
|
|
10807
|
+
if (attempts < this.retryLimit) {
|
|
10808
|
+
return this.init().then(() => {
|
|
10809
|
+
send();
|
|
10810
|
+
});
|
|
10811
|
+
}
|
|
10812
|
+
return;
|
|
9817
10813
|
}
|
|
9818
10814
|
if (!response.ok) {
|
|
9819
10815
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
@@ -9828,8 +10824,15 @@ class Batcher {
|
|
|
9828
10824
|
void send();
|
|
9829
10825
|
}
|
|
9830
10826
|
startAutosend() {
|
|
10827
|
+
if (this.intervalId) {
|
|
10828
|
+
clearInterval(this.intervalId);
|
|
10829
|
+
}
|
|
10830
|
+
this.paused = document.hidden;
|
|
10831
|
+
document.addEventListener('visibilitychange', this.onVisibilityChange);
|
|
9831
10832
|
this.intervalId = setInterval(() => {
|
|
9832
|
-
this.
|
|
10833
|
+
if (!this.paused) {
|
|
10834
|
+
this.flush();
|
|
10835
|
+
}
|
|
9833
10836
|
}, this.autosendInterval);
|
|
9834
10837
|
}
|
|
9835
10838
|
flush() {
|
|
@@ -9849,10 +10852,18 @@ class Batcher {
|
|
|
9849
10852
|
clearInterval(this.intervalId);
|
|
9850
10853
|
this.intervalId = null;
|
|
9851
10854
|
}
|
|
10855
|
+
document.removeEventListener('visibilitychange', this.onVisibilityChange);
|
|
10856
|
+
this.stopped = true;
|
|
10857
|
+
}
|
|
10858
|
+
restart() {
|
|
10859
|
+
this.stopped = false;
|
|
10860
|
+
this.startAutosend();
|
|
9852
10861
|
}
|
|
9853
10862
|
}
|
|
9854
10863
|
|
|
9855
10864
|
const STORAGEKEY = '__or_sdk_analytics_token';
|
|
10865
|
+
const START_ATTEMPT_STORAGE_KEY = '__or_sdk_analytics_start_attempt';
|
|
10866
|
+
const START_ATTEMPT_TTL_MS = 24 * 60 * 60 * 1000;
|
|
9856
10867
|
const normalizeIngestPoint = (ingest) => {
|
|
9857
10868
|
const base = ingest.replace(/\/$/, '');
|
|
9858
10869
|
return base.endsWith('/oxvo') ? base : `${base}/oxvo`;
|
|
@@ -9876,11 +10887,14 @@ class Analytics {
|
|
|
9876
10887
|
constructor(options) {
|
|
9877
10888
|
this.token = null;
|
|
9878
10889
|
this.standalone = false;
|
|
10890
|
+
this.pendingStartAttempt = null;
|
|
9879
10891
|
this._getToken = () => {
|
|
9880
10892
|
if (this.standalone) {
|
|
9881
10893
|
return this.token;
|
|
9882
10894
|
}
|
|
9883
|
-
|
|
10895
|
+
else {
|
|
10896
|
+
return this.getToken();
|
|
10897
|
+
}
|
|
9884
10898
|
};
|
|
9885
10899
|
this._getTimestamp = () => {
|
|
9886
10900
|
if (this.standalone) {
|
|
@@ -9917,9 +10931,14 @@ class Analytics {
|
|
|
9917
10931
|
timezone: defaultFields.timezone,
|
|
9918
10932
|
search_engine: defaultFields.search_engine,
|
|
9919
10933
|
};
|
|
10934
|
+
const attempt = this.startAttempt(JSON.stringify(data));
|
|
9920
10935
|
const resp = await fetch(apiEdp, {
|
|
9921
10936
|
method: 'POST',
|
|
9922
|
-
|
|
10937
|
+
headers: {
|
|
10938
|
+
'Content-Type': 'application/json',
|
|
10939
|
+
'Idempotency-Key': attempt.idempotencyKey,
|
|
10940
|
+
},
|
|
10941
|
+
body: attempt.requestBody,
|
|
9923
10942
|
});
|
|
9924
10943
|
if (!resp.ok) {
|
|
9925
10944
|
throw new Error(`HTTP error! status: ${resp.status}`);
|
|
@@ -9928,12 +10947,26 @@ class Analytics {
|
|
|
9928
10947
|
if (result.token) {
|
|
9929
10948
|
this.token = result.token;
|
|
9930
10949
|
this.sessionStorage.setItem(STORAGEKEY, result.token);
|
|
10950
|
+
this.clearStartAttempt();
|
|
9931
10951
|
}
|
|
9932
10952
|
else {
|
|
9933
10953
|
throw new Error('No token received from server');
|
|
9934
10954
|
}
|
|
9935
10955
|
}
|
|
9936
10956
|
};
|
|
10957
|
+
/**
|
|
10958
|
+
* Used by tracker when running in blundled mode
|
|
10959
|
+
*/
|
|
10960
|
+
this.onStart = () => {
|
|
10961
|
+
if (!this.standalone) {
|
|
10962
|
+
this.batcher.restart();
|
|
10963
|
+
}
|
|
10964
|
+
};
|
|
10965
|
+
this.onStop = () => {
|
|
10966
|
+
if (!this.standalone) {
|
|
10967
|
+
this.batcher.stop();
|
|
10968
|
+
}
|
|
10969
|
+
};
|
|
9937
10970
|
this.reset = () => {
|
|
9938
10971
|
this.people.reset(true);
|
|
9939
10972
|
this.events.reset();
|
|
@@ -9941,6 +10974,7 @@ class Analytics {
|
|
|
9941
10974
|
if (this.standalone) {
|
|
9942
10975
|
this.token = null;
|
|
9943
10976
|
this.sessionStorage.setItem(STORAGEKEY, '');
|
|
10977
|
+
this.clearStartAttempt();
|
|
9944
10978
|
}
|
|
9945
10979
|
};
|
|
9946
10980
|
/**
|
|
@@ -9972,13 +11006,98 @@ class Analytics {
|
|
|
9972
11006
|
this.standalone = !options.notStandalone;
|
|
9973
11007
|
this.token = this.sessionStorage.getItem(STORAGEKEY);
|
|
9974
11008
|
this.constantProperties = new ConstantProperties(this.localStorage, this.sessionStorage);
|
|
9975
|
-
this.batcher = new Batcher(this.backendUrl, this._getToken, this.init);
|
|
11009
|
+
this.batcher = new Batcher(this.backendUrl, this._getToken, this.init, this.standalone);
|
|
9976
11010
|
this.events = new Events(this.constantProperties, this._getTimestamp, this.batcher);
|
|
9977
11011
|
this.people = new People(this.constantProperties, this._getTimestamp, this.setUserId, this.batcher);
|
|
9978
11012
|
if (options.notStandalone) {
|
|
9979
11013
|
this.init();
|
|
9980
11014
|
}
|
|
9981
11015
|
}
|
|
11016
|
+
loadStartAttempt() {
|
|
11017
|
+
if (this.isValidStartAttempt(this.pendingStartAttempt)) {
|
|
11018
|
+
return this.pendingStartAttempt;
|
|
11019
|
+
}
|
|
11020
|
+
this.pendingStartAttempt = null;
|
|
11021
|
+
let serialized;
|
|
11022
|
+
try {
|
|
11023
|
+
serialized = this.sessionStorage.getItem(START_ATTEMPT_STORAGE_KEY);
|
|
11024
|
+
}
|
|
11025
|
+
catch {
|
|
11026
|
+
return null;
|
|
11027
|
+
}
|
|
11028
|
+
if (!serialized || serialized.length > 1024 * 1024) {
|
|
11029
|
+
return null;
|
|
11030
|
+
}
|
|
11031
|
+
try {
|
|
11032
|
+
const attempt = JSON.parse(serialized);
|
|
11033
|
+
if (this.isValidStartAttempt(attempt)) {
|
|
11034
|
+
this.pendingStartAttempt = attempt;
|
|
11035
|
+
return attempt;
|
|
11036
|
+
}
|
|
11037
|
+
}
|
|
11038
|
+
catch {
|
|
11039
|
+
// Treat corrupt storage as an absent pending attempt.
|
|
11040
|
+
}
|
|
11041
|
+
return null;
|
|
11042
|
+
}
|
|
11043
|
+
isValidStartAttempt(value) {
|
|
11044
|
+
if (typeof value !== 'object' || value === null) {
|
|
11045
|
+
return false;
|
|
11046
|
+
}
|
|
11047
|
+
const attempt = value;
|
|
11048
|
+
const currentTime = Date.now();
|
|
11049
|
+
if (attempt.version !== 1 ||
|
|
11050
|
+
attempt.projectKey !== this.projectKey ||
|
|
11051
|
+
typeof attempt.createdAt !== 'number' ||
|
|
11052
|
+
!Number.isFinite(attempt.createdAt) ||
|
|
11053
|
+
attempt.createdAt > currentTime ||
|
|
11054
|
+
currentTime - attempt.createdAt >= START_ATTEMPT_TTL_MS ||
|
|
11055
|
+
typeof attempt.idempotencyKey !== 'string' ||
|
|
11056
|
+
attempt.idempotencyKey.length < 8 ||
|
|
11057
|
+
attempt.idempotencyKey.length > 128 ||
|
|
11058
|
+
!/^[A-Za-z0-9._:-]+$/.test(attempt.idempotencyKey) ||
|
|
11059
|
+
typeof attempt.requestBody !== 'string' ||
|
|
11060
|
+
attempt.requestBody.length > 1024 * 1024) {
|
|
11061
|
+
return false;
|
|
11062
|
+
}
|
|
11063
|
+
try {
|
|
11064
|
+
const request = JSON.parse(attempt.requestBody);
|
|
11065
|
+
return request.projectKey === this.projectKey;
|
|
11066
|
+
}
|
|
11067
|
+
catch {
|
|
11068
|
+
return false;
|
|
11069
|
+
}
|
|
11070
|
+
}
|
|
11071
|
+
startAttempt(requestBody) {
|
|
11072
|
+
const existing = this.loadStartAttempt();
|
|
11073
|
+
if (existing) {
|
|
11074
|
+
return existing;
|
|
11075
|
+
}
|
|
11076
|
+
const attempt = {
|
|
11077
|
+
version: 1,
|
|
11078
|
+
projectKey: this.projectKey,
|
|
11079
|
+
createdAt: Date.now(),
|
|
11080
|
+
idempotencyKey: `insights:start:${generateRandomId(40)}`,
|
|
11081
|
+
requestBody,
|
|
11082
|
+
};
|
|
11083
|
+
this.pendingStartAttempt = attempt;
|
|
11084
|
+
try {
|
|
11085
|
+
this.sessionStorage.setItem(START_ATTEMPT_STORAGE_KEY, JSON.stringify(attempt));
|
|
11086
|
+
}
|
|
11087
|
+
catch {
|
|
11088
|
+
// The in-memory copy still protects retries within this Analytics instance.
|
|
11089
|
+
}
|
|
11090
|
+
return attempt;
|
|
11091
|
+
}
|
|
11092
|
+
clearStartAttempt() {
|
|
11093
|
+
this.pendingStartAttempt = null;
|
|
11094
|
+
try {
|
|
11095
|
+
this.sessionStorage.setItem(START_ATTEMPT_STORAGE_KEY, '');
|
|
11096
|
+
}
|
|
11097
|
+
catch {
|
|
11098
|
+
// The in-memory copy is still cleared when storage is unavailable.
|
|
11099
|
+
}
|
|
11100
|
+
}
|
|
9982
11101
|
}
|
|
9983
11102
|
|
|
9984
11103
|
const Messages = _Messages;
|
|
@@ -10025,7 +11144,7 @@ class API {
|
|
|
10025
11144
|
this.signalStartIssue = (reason, missingApi) => {
|
|
10026
11145
|
const doNotTrack = this.checkDoNotTrack();
|
|
10027
11146
|
console.log("Tracker couldn't start due to:", JSON.stringify({
|
|
10028
|
-
trackerVersion: '7.
|
|
11147
|
+
trackerVersion: '7.3.4',
|
|
10029
11148
|
siteKey: this.options.siteKey,
|
|
10030
11149
|
doNotTrack,
|
|
10031
11150
|
reason: missingApi.length ? `missing api: ${missingApi.join(',')}` : reason,
|
|
@@ -10037,6 +11156,31 @@ class API {
|
|
|
10037
11156
|
}
|
|
10038
11157
|
this.app.restartCanvasTracking();
|
|
10039
11158
|
};
|
|
11159
|
+
/**
|
|
11160
|
+
* Re-evaluates sanitization against the current DOM and re-emits whatever
|
|
11161
|
+
* changed, updating already-recorded nodes mid-session. Call after toggling
|
|
11162
|
+
* `data-oxvosessions-*` attributes or after changing whatever your `domSanitizer`
|
|
11163
|
+
* keys on (class/id/etc).
|
|
11164
|
+
*
|
|
11165
|
+
* @param el - the highest node you changed; omit to re-scan the whole document;
|
|
11166
|
+
* scanning the entire doc is O(dom size)
|
|
11167
|
+
* */
|
|
11168
|
+
this.resanitize = (el) => {
|
|
11169
|
+
if (this.app === null) {
|
|
11170
|
+
return;
|
|
11171
|
+
}
|
|
11172
|
+
this.app.resanitize(el);
|
|
11173
|
+
};
|
|
11174
|
+
/**
|
|
11175
|
+
* Returns the sanitization level the tracker currently has for a node
|
|
11176
|
+
* (0 = Plain, 1 = Obscured, 2 = Hidden), or undefined if it isn't tracked.
|
|
11177
|
+
* */
|
|
11178
|
+
this.checkSanitization = (el) => {
|
|
11179
|
+
if (this.app === null) {
|
|
11180
|
+
return undefined;
|
|
11181
|
+
}
|
|
11182
|
+
return this.app.checkSanitization(el);
|
|
11183
|
+
};
|
|
10040
11184
|
this.getSessionLink = (options) => {
|
|
10041
11185
|
if (this.app === null) {
|
|
10042
11186
|
return undefined;
|
|
@@ -10179,6 +11323,12 @@ class API {
|
|
|
10179
11323
|
: (options.analytics?.endpoint ?? options.endpoint ?? defaultEdp),
|
|
10180
11324
|
projectKey: options.siteKey,
|
|
10181
11325
|
});
|
|
11326
|
+
app.attachStartCallback(() => {
|
|
11327
|
+
this.analytics?.onStart();
|
|
11328
|
+
});
|
|
11329
|
+
app.attachStopCallback(() => {
|
|
11330
|
+
this.analytics?.onStop();
|
|
11331
|
+
});
|
|
10182
11332
|
}
|
|
10183
11333
|
if (!this.crossdomainMode) {
|
|
10184
11334
|
// no need to send iframe viewport data since its a node for us
|
|
@@ -10190,7 +11340,7 @@ class API {
|
|
|
10190
11340
|
// no tabs in iframes yet
|
|
10191
11341
|
Tabs(app);
|
|
10192
11342
|
}
|
|
10193
|
-
Mouse(app, options.mouse);
|
|
11343
|
+
Mouse(app, { ...options.mouse, customAttributes: options.customAttributes });
|
|
10194
11344
|
// inside iframe, we ignore viewport scroll
|
|
10195
11345
|
Scroll(app, this.crossdomainMode);
|
|
10196
11346
|
CSSRules(app, options.css);
|