@oxvo/browser 7.1.0 → 7.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/dist/cjs/common/interaction.d.ts +12 -1
  2. package/dist/cjs/common/messages.gen.d.ts +4 -7
  3. package/dist/cjs/entry.js +991 -945
  4. package/dist/cjs/entry.js.map +1 -1
  5. package/dist/cjs/index.js +918 -913
  6. package/dist/cjs/index.js.map +1 -1
  7. package/dist/cjs/main/app/canvas.d.ts +8 -0
  8. package/dist/cjs/main/app/index.d.ts +13 -5
  9. package/dist/cjs/main/app/messages.gen.d.ts +0 -1
  10. package/dist/cjs/main/app/observer/observer.d.ts +16 -0
  11. package/dist/cjs/main/app/sanitizer.d.ts +5 -3
  12. package/dist/cjs/main/index.d.ts +16 -0
  13. package/dist/cjs/main/modules/analytics/batcher.d.ts +6 -1
  14. package/dist/cjs/main/modules/analytics/index.d.ts +5 -0
  15. package/dist/cjs/main/modules/input.d.ts +2 -1
  16. package/dist/cjs/main/modules/markerWatcher.d.ts +4 -8
  17. package/dist/cjs/main/modules/mouse.d.ts +1 -0
  18. package/dist/cjs/main/modules/tagMatcher.d.ts +28 -0
  19. package/dist/cjs/main/singleton.d.ts +24 -0
  20. package/dist/cjs/main/utils.d.ts +2 -0
  21. package/dist/lib/common/interaction.d.ts +12 -1
  22. package/dist/lib/common/messages.gen.d.ts +4 -7
  23. package/dist/lib/entry.js +991 -945
  24. package/dist/lib/entry.js.map +1 -1
  25. package/dist/lib/index.js +918 -913
  26. package/dist/lib/index.js.map +1 -1
  27. package/dist/lib/main/app/canvas.d.ts +8 -0
  28. package/dist/lib/main/app/index.d.ts +13 -5
  29. package/dist/lib/main/app/messages.gen.d.ts +0 -1
  30. package/dist/lib/main/app/observer/observer.d.ts +16 -0
  31. package/dist/lib/main/app/sanitizer.d.ts +5 -3
  32. package/dist/lib/main/index.d.ts +16 -0
  33. package/dist/lib/main/modules/analytics/batcher.d.ts +6 -1
  34. package/dist/lib/main/modules/analytics/index.d.ts +5 -0
  35. package/dist/lib/main/modules/input.d.ts +2 -1
  36. package/dist/lib/main/modules/markerWatcher.d.ts +4 -8
  37. package/dist/lib/main/modules/mouse.d.ts +1 -0
  38. package/dist/lib/main/modules/tagMatcher.d.ts +28 -0
  39. package/dist/lib/main/singleton.d.ts +24 -0
  40. package/dist/lib/main/utils.d.ts +2 -0
  41. package/dist/types/common/interaction.d.ts +12 -1
  42. package/dist/types/common/messages.gen.d.ts +4 -7
  43. package/dist/types/main/app/canvas.d.ts +8 -0
  44. package/dist/types/main/app/index.d.ts +13 -5
  45. package/dist/types/main/app/messages.gen.d.ts +0 -1
  46. package/dist/types/main/app/observer/observer.d.ts +16 -0
  47. package/dist/types/main/app/sanitizer.d.ts +5 -3
  48. package/dist/types/main/index.d.ts +16 -0
  49. package/dist/types/main/modules/analytics/batcher.d.ts +6 -1
  50. package/dist/types/main/modules/analytics/index.d.ts +5 -0
  51. package/dist/types/main/modules/input.d.ts +2 -1
  52. package/dist/types/main/modules/markerWatcher.d.ts +4 -8
  53. package/dist/types/main/modules/mouse.d.ts +1 -0
  54. package/dist/types/main/modules/tagMatcher.d.ts +28 -0
  55. package/dist/types/main/singleton.d.ts +24 -0
  56. package/dist/types/main/utils.d.ts +2 -0
  57. package/package.json +11 -12
package/dist/cjs/index.js CHANGED
@@ -2,719 +2,6 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
- // DEFLATE is a complex format; to read this code, you should probably check the RFC first:
6
- // https://tools.ietf.org/html/rfc1951
7
- // You may also wish to take a look at the guide I made about this program:
8
- // https://gist.github.com/101arrowz/253f31eb5abc3d9275ab943003ffecad
9
- // Some of the following code is similar to that of UZIP.js:
10
- // https://github.com/photopea/UZIP.js
11
- // However, the vast majority of the codebase has diverged from UZIP.js to increase performance and reduce bundle size.
12
- // Sometimes 0 will appear where -1 would be more appropriate. This is because using a uint
13
- // is better for memory in most engines (I *think*).
14
- var ch2 = {};
15
- var wk = (function (c, id, msg, transfer, cb) {
16
- var w = new Worker(ch2[id] || (ch2[id] = URL.createObjectURL(new Blob([
17
- c + ';addEventListener("error",function(e){e=e.error;postMessage({$e$:[e.message,e.code,e.stack]})})'
18
- ], { type: 'text/javascript' }))));
19
- w.onmessage = function (e) {
20
- var d = e.data, ed = d.$e$;
21
- if (ed) {
22
- var err = new Error(ed[0]);
23
- err['code'] = ed[1];
24
- err.stack = ed[2];
25
- cb(err, null);
26
- }
27
- else
28
- cb(null, d);
29
- };
30
- w.postMessage(msg, transfer);
31
- return w;
32
- });
33
-
34
- // aliases for shorter compressed code (most minifers don't do this)
35
- var u8 = Uint8Array, u16 = Uint16Array, i32 = Int32Array;
36
- // fixed length extra bits
37
- var fleb = new u8([0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, /* unused */ 0, 0, /* impossible */ 0]);
38
- // fixed distance extra bits
39
- var fdeb = new u8([0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, /* unused */ 0, 0]);
40
- // code length index map
41
- var clim = new u8([16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]);
42
- // get base, reverse index map from extra bits
43
- var freb = function (eb, start) {
44
- var b = new u16(31);
45
- for (var i = 0; i < 31; ++i) {
46
- b[i] = start += 1 << eb[i - 1];
47
- }
48
- // numbers here are at max 18 bits
49
- var r = new i32(b[30]);
50
- for (var i = 1; i < 30; ++i) {
51
- for (var j = b[i]; j < b[i + 1]; ++j) {
52
- r[j] = ((j - b[i]) << 5) | i;
53
- }
54
- }
55
- return { b: b, r: r };
56
- };
57
- var _a = freb(fleb, 2), fl = _a.b, revfl = _a.r;
58
- // we can ignore the fact that the other numbers are wrong; they never happen anyway
59
- fl[28] = 258, revfl[258] = 28;
60
- var _b = freb(fdeb, 0), revfd = _b.r;
61
- // map of value to reverse (assuming 16 bits)
62
- var rev = new u16(32768);
63
- for (var i$1 = 0; i$1 < 32768; ++i$1) {
64
- // reverse table algorithm from SO
65
- var x$1 = ((i$1 & 0xAAAA) >> 1) | ((i$1 & 0x5555) << 1);
66
- x$1 = ((x$1 & 0xCCCC) >> 2) | ((x$1 & 0x3333) << 2);
67
- x$1 = ((x$1 & 0xF0F0) >> 4) | ((x$1 & 0x0F0F) << 4);
68
- rev[i$1] = (((x$1 & 0xFF00) >> 8) | ((x$1 & 0x00FF) << 8)) >> 1;
69
- }
70
- // create huffman tree from u8 "map": index -> code length for code index
71
- // mb (max bits) must be at most 15
72
- // TODO: optimize/split up?
73
- var hMap = (function (cd, mb, r) {
74
- var s = cd.length;
75
- // index
76
- var i = 0;
77
- // u16 "map": index -> # of codes with bit length = index
78
- var l = new u16(mb);
79
- // length of cd must be 288 (total # of codes)
80
- for (; i < s; ++i) {
81
- if (cd[i])
82
- ++l[cd[i] - 1];
83
- }
84
- // u16 "map": index -> minimum code for bit length = index
85
- var le = new u16(mb);
86
- for (i = 1; i < mb; ++i) {
87
- le[i] = (le[i - 1] + l[i - 1]) << 1;
88
- }
89
- var co;
90
- if (r) {
91
- // u16 "map": index -> number of actual bits, symbol for code
92
- co = new u16(1 << mb);
93
- // bits to remove for reverser
94
- var rvb = 15 - mb;
95
- for (i = 0; i < s; ++i) {
96
- // ignore 0 lengths
97
- if (cd[i]) {
98
- // num encoding both symbol and bits read
99
- var sv = (i << 4) | cd[i];
100
- // free bits
101
- var r_1 = mb - cd[i];
102
- // start value
103
- var v = le[cd[i] - 1]++ << r_1;
104
- // m is end value
105
- for (var m = v | ((1 << r_1) - 1); v <= m; ++v) {
106
- // every 16 bit value starting with the code yields the same result
107
- co[rev[v] >> rvb] = sv;
108
- }
109
- }
110
- }
111
- }
112
- else {
113
- co = new u16(s);
114
- for (i = 0; i < s; ++i) {
115
- if (cd[i]) {
116
- co[i] = rev[le[cd[i] - 1]++] >> (15 - cd[i]);
117
- }
118
- }
119
- }
120
- return co;
121
- });
122
- // fixed length tree
123
- var flt = new u8(288);
124
- for (var i$1 = 0; i$1 < 144; ++i$1)
125
- flt[i$1] = 8;
126
- for (var i$1 = 144; i$1 < 256; ++i$1)
127
- flt[i$1] = 9;
128
- for (var i$1 = 256; i$1 < 280; ++i$1)
129
- flt[i$1] = 7;
130
- for (var i$1 = 280; i$1 < 288; ++i$1)
131
- flt[i$1] = 8;
132
- // fixed distance tree
133
- var fdt = new u8(32);
134
- for (var i$1 = 0; i$1 < 32; ++i$1)
135
- fdt[i$1] = 5;
136
- // fixed length map
137
- var flm = /*#__PURE__*/ hMap(flt, 9, 0);
138
- // fixed distance map
139
- var fdm = /*#__PURE__*/ hMap(fdt, 5, 0);
140
- // get end of byte
141
- var shft = function (p) { return ((p + 7) / 8) | 0; };
142
- // typed array slice - allows garbage collector to free original reference,
143
- // while being more compatible than .slice
144
- var slc = function (v, s, e) {
145
- if (s == null || s < 0)
146
- s = 0;
147
- if (e == null || e > v.length)
148
- e = v.length;
149
- // can't use .constructor in case user-supplied
150
- return new u8(v.subarray(s, e));
151
- };
152
- // error codes
153
- var ec = [
154
- 'unexpected EOF',
155
- 'invalid block type',
156
- 'invalid length/literal',
157
- 'invalid distance',
158
- 'stream finished',
159
- 'no stream handler',
160
- ,
161
- 'no callback',
162
- 'invalid UTF-8 data',
163
- 'extra field too long',
164
- 'date not in range 1980-2099',
165
- 'filename too long',
166
- 'stream finishing',
167
- 'invalid zip data'
168
- // determined by unknown compression method
169
- ];
170
- var err = function (ind, msg, nt) {
171
- var e = new Error(msg || ec[ind]);
172
- e.code = ind;
173
- if (Error.captureStackTrace)
174
- Error.captureStackTrace(e, err);
175
- if (!nt)
176
- throw e;
177
- return e;
178
- };
179
- // starting at p, write the minimum number of bits that can hold v to d
180
- var wbits = function (d, p, v) {
181
- v <<= p & 7;
182
- var o = (p / 8) | 0;
183
- d[o] |= v;
184
- d[o + 1] |= v >> 8;
185
- };
186
- // starting at p, write the minimum number of bits (>8) that can hold v to d
187
- var wbits16 = function (d, p, v) {
188
- v <<= p & 7;
189
- var o = (p / 8) | 0;
190
- d[o] |= v;
191
- d[o + 1] |= v >> 8;
192
- d[o + 2] |= v >> 16;
193
- };
194
- // creates code lengths from a frequency table
195
- var hTree = function (d, mb) {
196
- // Need extra info to make a tree
197
- var t = [];
198
- for (var i = 0; i < d.length; ++i) {
199
- if (d[i])
200
- t.push({ s: i, f: d[i] });
201
- }
202
- var s = t.length;
203
- var t2 = t.slice();
204
- if (!s)
205
- return { t: et, l: 0 };
206
- if (s == 1) {
207
- var v = new u8(t[0].s + 1);
208
- v[t[0].s] = 1;
209
- return { t: v, l: 1 };
210
- }
211
- t.sort(function (a, b) { return a.f - b.f; });
212
- // after i2 reaches last ind, will be stopped
213
- // freq must be greater than largest possible number of symbols
214
- t.push({ s: -1, f: 25001 });
215
- var l = t[0], r = t[1], i0 = 0, i1 = 1, i2 = 2;
216
- t[0] = { s: -1, f: l.f + r.f, l: l, r: r };
217
- // efficient algorithm from UZIP.js
218
- // i0 is lookbehind, i2 is lookahead - after processing two low-freq
219
- // symbols that combined have high freq, will start processing i2 (high-freq,
220
- // non-composite) symbols instead
221
- // see https://reddit.com/r/photopea/comments/ikekht/uzipjs_questions/
222
- while (i1 != s - 1) {
223
- l = t[t[i0].f < t[i2].f ? i0++ : i2++];
224
- r = t[i0 != i1 && t[i0].f < t[i2].f ? i0++ : i2++];
225
- t[i1++] = { s: -1, f: l.f + r.f, l: l, r: r };
226
- }
227
- var maxSym = t2[0].s;
228
- for (var i = 1; i < s; ++i) {
229
- if (t2[i].s > maxSym)
230
- maxSym = t2[i].s;
231
- }
232
- // code lengths
233
- var tr = new u16(maxSym + 1);
234
- // max bits in tree
235
- var mbt = ln(t[i1 - 1], tr, 0);
236
- if (mbt > mb) {
237
- // more algorithms from UZIP.js
238
- // TODO: find out how this code works (debt)
239
- // ind debt
240
- var i = 0, dt = 0;
241
- // left cost
242
- var lft = mbt - mb, cst = 1 << lft;
243
- t2.sort(function (a, b) { return tr[b.s] - tr[a.s] || a.f - b.f; });
244
- for (; i < s; ++i) {
245
- var i2_1 = t2[i].s;
246
- if (tr[i2_1] > mb) {
247
- dt += cst - (1 << (mbt - tr[i2_1]));
248
- tr[i2_1] = mb;
249
- }
250
- else
251
- break;
252
- }
253
- dt >>= lft;
254
- while (dt > 0) {
255
- var i2_2 = t2[i].s;
256
- if (tr[i2_2] < mb)
257
- dt -= 1 << (mb - tr[i2_2]++ - 1);
258
- else
259
- ++i;
260
- }
261
- for (; i >= 0 && dt; --i) {
262
- var i2_3 = t2[i].s;
263
- if (tr[i2_3] == mb) {
264
- --tr[i2_3];
265
- ++dt;
266
- }
267
- }
268
- mbt = mb;
269
- }
270
- return { t: new u8(tr), l: mbt };
271
- };
272
- // get the max length and assign length codes
273
- var ln = function (n, l, d) {
274
- return n.s == -1
275
- ? Math.max(ln(n.l, l, d + 1), ln(n.r, l, d + 1))
276
- : (l[n.s] = d);
277
- };
278
- // length codes generation
279
- var lc = function (c) {
280
- var s = c.length;
281
- // Note that the semicolon was intentional
282
- while (s && !c[--s])
283
- ;
284
- var cl = new u16(++s);
285
- // ind num streak
286
- var cli = 0, cln = c[0], cls = 1;
287
- var w = function (v) { cl[cli++] = v; };
288
- for (var i = 1; i <= s; ++i) {
289
- if (c[i] == cln && i != s)
290
- ++cls;
291
- else {
292
- if (!cln && cls > 2) {
293
- for (; cls > 138; cls -= 138)
294
- w(32754);
295
- if (cls > 2) {
296
- w(cls > 10 ? ((cls - 11) << 5) | 28690 : ((cls - 3) << 5) | 12305);
297
- cls = 0;
298
- }
299
- }
300
- else if (cls > 3) {
301
- w(cln), --cls;
302
- for (; cls > 6; cls -= 6)
303
- w(8304);
304
- if (cls > 2)
305
- w(((cls - 3) << 5) | 8208), cls = 0;
306
- }
307
- while (cls--)
308
- w(cln);
309
- cls = 1;
310
- cln = c[i];
311
- }
312
- }
313
- return { c: cl.subarray(0, cli), n: s };
314
- };
315
- // calculate the length of output from tree, code lengths
316
- var clen = function (cf, cl) {
317
- var l = 0;
318
- for (var i = 0; i < cl.length; ++i)
319
- l += cf[i] * cl[i];
320
- return l;
321
- };
322
- // writes a fixed block
323
- // returns the new bit pos
324
- var wfblk = function (out, pos, dat) {
325
- // no need to write 00 as type: TypedArray defaults to 0
326
- var s = dat.length;
327
- var o = shft(pos + 2);
328
- out[o] = s & 255;
329
- out[o + 1] = s >> 8;
330
- out[o + 2] = out[o] ^ 255;
331
- out[o + 3] = out[o + 1] ^ 255;
332
- for (var i = 0; i < s; ++i)
333
- out[o + i + 4] = dat[i];
334
- return (o + 4 + s) * 8;
335
- };
336
- // writes a block
337
- var wblk = function (dat, out, final, syms, lf, df, eb, li, bs, bl, p) {
338
- wbits(out, p++, final);
339
- ++lf[256];
340
- var _a = hTree(lf, 15), dlt = _a.t, mlb = _a.l;
341
- var _b = hTree(df, 15), ddt = _b.t, mdb = _b.l;
342
- var _c = lc(dlt), lclt = _c.c, nlc = _c.n;
343
- var _d = lc(ddt), lcdt = _d.c, ndc = _d.n;
344
- var lcfreq = new u16(19);
345
- for (var i = 0; i < lclt.length; ++i)
346
- ++lcfreq[lclt[i] & 31];
347
- for (var i = 0; i < lcdt.length; ++i)
348
- ++lcfreq[lcdt[i] & 31];
349
- var _e = hTree(lcfreq, 7), lct = _e.t, mlcb = _e.l;
350
- var nlcc = 19;
351
- for (; nlcc > 4 && !lct[clim[nlcc - 1]]; --nlcc)
352
- ;
353
- var flen = (bl + 5) << 3;
354
- var ftlen = clen(lf, flt) + clen(df, fdt) + eb;
355
- var dtlen = clen(lf, dlt) + clen(df, ddt) + eb + 14 + 3 * nlcc + clen(lcfreq, lct) + 2 * lcfreq[16] + 3 * lcfreq[17] + 7 * lcfreq[18];
356
- if (bs >= 0 && flen <= ftlen && flen <= dtlen)
357
- return wfblk(out, p, dat.subarray(bs, bs + bl));
358
- var lm, ll, dm, dl;
359
- wbits(out, p, 1 + (dtlen < ftlen)), p += 2;
360
- if (dtlen < ftlen) {
361
- lm = hMap(dlt, mlb, 0), ll = dlt, dm = hMap(ddt, mdb, 0), dl = ddt;
362
- var llm = hMap(lct, mlcb, 0);
363
- wbits(out, p, nlc - 257);
364
- wbits(out, p + 5, ndc - 1);
365
- wbits(out, p + 10, nlcc - 4);
366
- p += 14;
367
- for (var i = 0; i < nlcc; ++i)
368
- wbits(out, p + 3 * i, lct[clim[i]]);
369
- p += 3 * nlcc;
370
- var lcts = [lclt, lcdt];
371
- for (var it = 0; it < 2; ++it) {
372
- var clct = lcts[it];
373
- for (var i = 0; i < clct.length; ++i) {
374
- var len = clct[i] & 31;
375
- wbits(out, p, llm[len]), p += lct[len];
376
- if (len > 15)
377
- wbits(out, p, (clct[i] >> 5) & 127), p += clct[i] >> 12;
378
- }
379
- }
380
- }
381
- else {
382
- lm = flm, ll = flt, dm = fdm, dl = fdt;
383
- }
384
- for (var i = 0; i < li; ++i) {
385
- var sym = syms[i];
386
- if (sym > 255) {
387
- var len = (sym >> 18) & 31;
388
- wbits16(out, p, lm[len + 257]), p += ll[len + 257];
389
- if (len > 7)
390
- wbits(out, p, (sym >> 23) & 31), p += fleb[len];
391
- var dst = sym & 31;
392
- wbits16(out, p, dm[dst]), p += dl[dst];
393
- if (dst > 3)
394
- wbits16(out, p, (sym >> 5) & 8191), p += fdeb[dst];
395
- }
396
- else {
397
- wbits16(out, p, lm[sym]), p += ll[sym];
398
- }
399
- }
400
- wbits16(out, p, lm[256]);
401
- return p + ll[256];
402
- };
403
- // deflate options (nice << 13) | chain
404
- var deo = /*#__PURE__*/ new i32([65540, 131080, 131088, 131104, 262176, 1048704, 1048832, 2114560, 2117632]);
405
- // empty
406
- var et = /*#__PURE__*/ new u8(0);
407
- // compresses data into a raw DEFLATE buffer
408
- var dflt = function (dat, lvl, plvl, pre, post, st) {
409
- var s = st.z || dat.length;
410
- var o = new u8(pre + s + 5 * (1 + Math.ceil(s / 7000)) + post);
411
- // writing to this writes to the output buffer
412
- var w = o.subarray(pre, o.length - post);
413
- var lst = st.l;
414
- var pos = (st.r || 0) & 7;
415
- if (lvl) {
416
- if (pos)
417
- w[0] = st.r >> 3;
418
- var opt = deo[lvl - 1];
419
- var n = opt >> 13, c = opt & 8191;
420
- var msk_1 = (1 << plvl) - 1;
421
- // prev 2-byte val map curr 2-byte val map
422
- var prev = st.p || new u16(32768), head = st.h || new u16(msk_1 + 1);
423
- var bs1_1 = Math.ceil(plvl / 3), bs2_1 = 2 * bs1_1;
424
- var hsh = function (i) { return (dat[i] ^ (dat[i + 1] << bs1_1) ^ (dat[i + 2] << bs2_1)) & msk_1; };
425
- // 24576 is an arbitrary number of maximum symbols per block
426
- // 424 buffer for last block
427
- var syms = new i32(25000);
428
- // length/literal freq distance freq
429
- var lf = new u16(288), df = new u16(32);
430
- // l/lcnt exbits index l/lind waitdx blkpos
431
- var lc_1 = 0, eb = 0, i = st.i || 0, li = 0, wi = st.w || 0, bs = 0;
432
- for (; i + 2 < s; ++i) {
433
- // hash value
434
- var hv = hsh(i);
435
- // index mod 32768 previous index mod
436
- var imod = i & 32767, pimod = head[hv];
437
- prev[imod] = pimod;
438
- head[hv] = imod;
439
- // We always should modify head and prev, but only add symbols if
440
- // this data is not yet processed ("wait" for wait index)
441
- if (wi <= i) {
442
- // bytes remaining
443
- var rem = s - i;
444
- if ((lc_1 > 7000 || li > 24576) && (rem > 423 || !lst)) {
445
- pos = wblk(dat, w, 0, syms, lf, df, eb, li, bs, i - bs, pos);
446
- li = lc_1 = eb = 0, bs = i;
447
- for (var j = 0; j < 286; ++j)
448
- lf[j] = 0;
449
- for (var j = 0; j < 30; ++j)
450
- df[j] = 0;
451
- }
452
- // len dist chain
453
- var l = 2, d = 0, ch_1 = c, dif = imod - pimod & 32767;
454
- if (rem > 2 && hv == hsh(i - dif)) {
455
- var maxn = Math.min(n, rem) - 1;
456
- var maxd = Math.min(32767, i);
457
- // max possible length
458
- // not capped at dif because decompressors implement "rolling" index population
459
- var ml = Math.min(258, rem);
460
- while (dif <= maxd && --ch_1 && imod != pimod) {
461
- if (dat[i + l] == dat[i + l - dif]) {
462
- var nl = 0;
463
- for (; nl < ml && dat[i + nl] == dat[i + nl - dif]; ++nl)
464
- ;
465
- if (nl > l) {
466
- l = nl, d = dif;
467
- // break out early when we reach "nice" (we are satisfied enough)
468
- if (nl > maxn)
469
- break;
470
- // now, find the rarest 2-byte sequence within this
471
- // length of literals and search for that instead.
472
- // Much faster than just using the start
473
- var mmd = Math.min(dif, nl - 2);
474
- var md = 0;
475
- for (var j = 0; j < mmd; ++j) {
476
- var ti = i - dif + j & 32767;
477
- var pti = prev[ti];
478
- var cd = ti - pti & 32767;
479
- if (cd > md)
480
- md = cd, pimod = ti;
481
- }
482
- }
483
- }
484
- // check the previous match
485
- imod = pimod, pimod = prev[imod];
486
- dif += imod - pimod & 32767;
487
- }
488
- }
489
- // d will be nonzero only when a match was found
490
- if (d) {
491
- // store both dist and len data in one int32
492
- // Make sure this is recognized as a len/dist with 28th bit (2^28)
493
- syms[li++] = 268435456 | (revfl[l] << 18) | revfd[d];
494
- var lin = revfl[l] & 31, din = revfd[d] & 31;
495
- eb += fleb[lin] + fdeb[din];
496
- ++lf[257 + lin];
497
- ++df[din];
498
- wi = i + l;
499
- ++lc_1;
500
- }
501
- else {
502
- syms[li++] = dat[i];
503
- ++lf[dat[i]];
504
- }
505
- }
506
- }
507
- for (i = Math.max(i, wi); i < s; ++i) {
508
- syms[li++] = dat[i];
509
- ++lf[dat[i]];
510
- }
511
- pos = wblk(dat, w, lst, syms, lf, df, eb, li, bs, i - bs, pos);
512
- if (!lst) {
513
- st.r = (pos & 7) | w[(pos / 8) | 0] << 3;
514
- // shft(pos) now 1 less if pos & 7 != 0
515
- pos -= 7;
516
- st.h = head, st.p = prev, st.i = i, st.w = wi;
517
- }
518
- }
519
- else {
520
- for (var i = st.w || 0; i < s + lst; i += 65535) {
521
- // end
522
- var e = i + 65535;
523
- if (e >= s) {
524
- // write final block
525
- w[(pos / 8) | 0] = lst;
526
- e = s;
527
- }
528
- pos = wfblk(w, pos + 1, dat.subarray(i, e));
529
- }
530
- st.i = s;
531
- }
532
- return slc(o, 0, pre + shft(pos) + post);
533
- };
534
- // CRC32 table
535
- var crct = /*#__PURE__*/ (function () {
536
- var t = new Int32Array(256);
537
- for (var i = 0; i < 256; ++i) {
538
- var c = i, k = 9;
539
- while (--k)
540
- c = ((c & 1) && -306674912) ^ (c >>> 1);
541
- t[i] = c;
542
- }
543
- return t;
544
- })();
545
- // CRC32
546
- var crc = function () {
547
- var c = -1;
548
- return {
549
- p: function (d) {
550
- // closures have awful performance
551
- var cr = c;
552
- for (var i = 0; i < d.length; ++i)
553
- cr = crct[(cr & 255) ^ d[i]] ^ (cr >>> 8);
554
- c = cr;
555
- },
556
- d: function () { return ~c; }
557
- };
558
- };
559
- // deflate with opts
560
- var dopt = function (dat, opt, pre, post, st) {
561
- if (!st) {
562
- st = { l: 1 };
563
- if (opt.dictionary) {
564
- var dict = opt.dictionary.subarray(-32768);
565
- var newDat = new u8(dict.length + dat.length);
566
- newDat.set(dict);
567
- newDat.set(dat, dict.length);
568
- dat = newDat;
569
- st.w = dict.length;
570
- }
571
- }
572
- return dflt(dat, opt.level == null ? 6 : opt.level, opt.mem == null ? (st.l ? Math.ceil(Math.max(8, Math.min(13, Math.log(dat.length))) * 1.5) : 20) : (12 + opt.mem), pre, post, st);
573
- };
574
- // Walmart object spread
575
- var mrg = function (a, b) {
576
- var o = {};
577
- for (var k in a)
578
- o[k] = a[k];
579
- for (var k in b)
580
- o[k] = b[k];
581
- return o;
582
- };
583
- // worker clone
584
- // This is possibly the craziest part of the entire codebase, despite how simple it may seem.
585
- // The only parameter to this function is a closure that returns an array of variables outside of the function scope.
586
- // We're going to try to figure out the variable names used in the closure as strings because that is crucial for workerization.
587
- // We will return an object mapping of true variable name to value (basically, the current scope as a JS object).
588
- // The reason we can't just use the original variable names is minifiers mangling the toplevel scope.
589
- // This took me three weeks to figure out how to do.
590
- var wcln = function (fn, fnStr, td) {
591
- var dt = fn();
592
- var st = fn.toString();
593
- var ks = st.slice(st.indexOf('[') + 1, st.lastIndexOf(']')).replace(/\s+/g, '').split(',');
594
- for (var i = 0; i < dt.length; ++i) {
595
- var v = dt[i], k = ks[i];
596
- if (typeof v == 'function') {
597
- fnStr += ';' + k + '=';
598
- var st_1 = v.toString();
599
- if (v.prototype) {
600
- // for global objects
601
- if (st_1.indexOf('[native code]') != -1) {
602
- var spInd = st_1.indexOf(' ', 8) + 1;
603
- fnStr += st_1.slice(spInd, st_1.indexOf('(', spInd));
604
- }
605
- else {
606
- fnStr += st_1;
607
- for (var t in v.prototype)
608
- fnStr += ';' + k + '.prototype.' + t + '=' + v.prototype[t].toString();
609
- }
610
- }
611
- else
612
- fnStr += st_1;
613
- }
614
- else
615
- td[k] = v;
616
- }
617
- return fnStr;
618
- };
619
- var ch = [];
620
- // clone bufs
621
- var cbfs = function (v) {
622
- var tl = [];
623
- for (var k in v) {
624
- if (v[k].buffer) {
625
- tl.push((v[k] = new v[k].constructor(v[k])).buffer);
626
- }
627
- }
628
- return tl;
629
- };
630
- // use a worker to execute code
631
- var wrkr = function (fns, init, id, cb) {
632
- if (!ch[id]) {
633
- var fnStr = '', td_1 = {}, m = fns.length - 1;
634
- for (var i = 0; i < m; ++i)
635
- fnStr = wcln(fns[i], fnStr, td_1);
636
- ch[id] = { c: wcln(fns[m], fnStr, td_1), e: td_1 };
637
- }
638
- var td = mrg({}, ch[id].e);
639
- return wk(ch[id].c + ';onmessage=function(e){for(var k in e.data)self[k]=e.data[k];onmessage=' + init.toString() + '}', id, td, cbfs(td), cb);
640
- };
641
- var bDflt = function () { return [u8, u16, i32, fleb, fdeb, clim, revfl, revfd, flm, flt, fdm, fdt, rev, deo, et, hMap, wbits, wbits16, hTree, ln, lc, clen, wfblk, wblk, shft, slc, dflt, dopt, deflateSync, pbf]; };
642
- // gzip extra
643
- var gze = function () { return [gzh, gzhl, wbytes, crc, crct]; };
644
- // post buf
645
- var pbf = function (msg) { return postMessage(msg, [msg.buffer]); };
646
- // async helper
647
- var cbify = function (dat, opts, fns, init, id, cb) {
648
- var w = wrkr(fns, init, id, function (err, dat) {
649
- w.terminate();
650
- cb(err, dat);
651
- });
652
- w.postMessage([dat, opts], opts.consume ? [dat.buffer] : []);
653
- return function () { w.terminate(); };
654
- };
655
- // write bytes
656
- var wbytes = function (d, b, v) {
657
- for (; v; ++b)
658
- d[b] = v, v >>>= 8;
659
- };
660
- // gzip header
661
- var gzh = function (c, o) {
662
- var fn = o.filename;
663
- c[0] = 31, c[1] = 139, c[2] = 8, c[8] = o.level < 2 ? 4 : o.level == 9 ? 2 : 0, c[9] = 3; // assume Unix
664
- if (o.mtime != 0)
665
- wbytes(c, 4, Math.floor(new Date(o.mtime || Date.now()) / 1000));
666
- if (fn) {
667
- c[3] = 8;
668
- for (var i = 0; i <= fn.length; ++i)
669
- c[i + 10] = fn.charCodeAt(i);
670
- }
671
- };
672
- // gzip header length
673
- var gzhl = function (o) { return 10 + (o.filename ? o.filename.length + 1 : 0); };
674
- /**
675
- * Compresses data with DEFLATE without any wrapper
676
- * @param data The data to compress
677
- * @param opts The compression options
678
- * @returns The deflated version of the data
679
- */
680
- function deflateSync(data, opts) {
681
- return dopt(data, opts || {}, 0, 0);
682
- }
683
- function gzip(data, opts, cb) {
684
- if (!cb)
685
- cb = opts, opts = {};
686
- if (typeof cb != 'function')
687
- err(7);
688
- return cbify(data, opts, [
689
- bDflt,
690
- gze,
691
- function () { return [gzipSync]; }
692
- ], function (ev) { return pbf(gzipSync(ev.data[0], ev.data[1])); }, 2, cb);
693
- }
694
- /**
695
- * Compresses data with GZIP
696
- * @param data The data to compress
697
- * @param opts The compression options
698
- * @returns The gzipped version of the data
699
- */
700
- function gzipSync(data, opts) {
701
- if (!opts)
702
- opts = {};
703
- var c = crc(), l = data.length;
704
- c.p(data);
705
- var d = dopt(data, opts, gzhl(opts), 8), s = d.length;
706
- return gzh(d, opts), wbytes(d, s - 8, c.d()), wbytes(d, s - 4, l), d;
707
- }
708
- // text decoder
709
- var td = typeof TextDecoder != 'undefined' && /*#__PURE__*/ new TextDecoder();
710
- // text decoder stream
711
- var tds = 0;
712
- try {
713
- td.decode(et, { stream: true });
714
- tds = 1;
715
- }
716
- catch (e) { }
717
-
718
5
  class StringDictionary {
719
6
  constructor() {
720
7
  this.lastTs = 0;
@@ -1165,6 +452,23 @@ function deprecationWarn(nameOfFeature, useInstead, docsPath = '/') {
1165
452
  console.warn(`OxvoSessions: ${nameOfFeature} is deprecated. ${useInstead ? `Please, use ${useInstead} instead.` : ''} Visit ${DOCS_HOST}${docsPath} for more information.`);
1166
453
  warnedFeatures[nameOfFeature] = true;
1167
454
  }
455
+ function getCustomAttributeLabel(e, customAttributes) {
456
+ if (!customAttributes || customAttributes.length === 0)
457
+ return '';
458
+ const parts = [];
459
+ for (const attr of customAttributes) {
460
+ const value = e.getAttribute(attr);
461
+ if (value !== null) {
462
+ parts.push(`[${attr}=${value}]`);
463
+ }
464
+ }
465
+ return parts.join('');
466
+ }
467
+ function getClassSelector(e) {
468
+ if (!e.classList || e.classList.length === 0)
469
+ return '';
470
+ return '.' + Array.from(e.classList).join('.');
471
+ }
1168
472
  function getLabelAttribute(e) {
1169
473
  let value = e.getAttribute('data-oxvosessions-label');
1170
474
  if (value !== null) {
@@ -1921,13 +1225,6 @@ function BatchMetadata(version, pageNo, firstIndex, timestamp, location) {
1921
1225
  location,
1922
1226
  ];
1923
1227
  }
1924
- function PartitionedMessage(partNo, partTotal) {
1925
- return [
1926
- 82 /* Messages.Type.PartitionedMessage */,
1927
- partNo,
1928
- partTotal,
1929
- ];
1930
- }
1931
1228
  function NetworkRequest(type, method, url, request, response, status, timestamp, duration, transferredBodySize) {
1932
1229
  return [
1933
1230
  83 /* Messages.Type.NetworkRequest */,
@@ -2139,7 +1436,6 @@ var _Messages = /*#__PURE__*/Object.freeze({
2139
1436
  OTable: OTable,
2140
1437
  PageLoadTiming: PageLoadTiming,
2141
1438
  PageRenderTiming: PageRenderTiming,
2142
- PartitionedMessage: PartitionedMessage,
2143
1439
  PerformanceTrack: PerformanceTrack,
2144
1440
  Profiler: Profiler,
2145
1441
  Redux: Redux,
@@ -2234,11 +1530,157 @@ function Performance (app, opts) {
2234
1530
  }
2235
1531
  }
2236
1532
 
1533
+ /**
1534
+ * Two-tier tag matching:
1535
+ * 1. Fast fingerprint lookup by id, data-attr,
1536
+ * or class from the selector's last segment
1537
+ * 2. Fallback iteration using native element.matches()
1538
+ */
1539
+ class TagMatcher {
1540
+ constructor() {
1541
+ this.tags = [];
1542
+ this.byId = new Map();
1543
+ this.byDataAttr = new Map();
1544
+ this.byClass = new Map();
1545
+ this.fallback = [];
1546
+ }
1547
+ setTags(tags) {
1548
+ this.tags = tags;
1549
+ this.byId.clear();
1550
+ this.byDataAttr.clear();
1551
+ this.byClass.clear();
1552
+ this.fallback = [];
1553
+ for (const tag of tags) {
1554
+ const last = lastSegment(tag.selector);
1555
+ if (!last) {
1556
+ this.fallback.push(tag);
1557
+ continue;
1558
+ }
1559
+ if (last.startsWith('#')) {
1560
+ this.byId.set(last.slice(1), tag);
1561
+ }
1562
+ else if (last.startsWith('[data-')) {
1563
+ this.byDataAttr.set(last, tag);
1564
+ }
1565
+ else {
1566
+ const cls = extractClass(last);
1567
+ if (cls) {
1568
+ this.byClass.set(cls, tag);
1569
+ }
1570
+ else {
1571
+ this.fallback.push(tag);
1572
+ }
1573
+ }
1574
+ }
1575
+ }
1576
+ getTags() {
1577
+ return this.tags;
1578
+ }
1579
+ /** Match element, its parent, or direct children against known tag selectors */
1580
+ match(el) {
1581
+ const direct = this.matchExact(el);
1582
+ if (direct)
1583
+ return direct;
1584
+ if (el.parentElement) {
1585
+ const parent = this.matchExact(el.parentElement);
1586
+ if (parent)
1587
+ return parent;
1588
+ }
1589
+ const children = el.children;
1590
+ for (let i = 0; i < children.length; i++) {
1591
+ const child = this.matchExact(children[i]);
1592
+ if (child)
1593
+ return child;
1594
+ }
1595
+ return null;
1596
+ }
1597
+ matchExact(el) {
1598
+ if (el.id && this.byId.has(el.id)) {
1599
+ const tag = this.byId.get(el.id);
1600
+ if (safeMatches(el, tag.selector) && matchesLocation(tag))
1601
+ return tag;
1602
+ }
1603
+ if (this.byDataAttr.size > 0) {
1604
+ const attrs = el.attributes;
1605
+ for (let i = 0; i < attrs.length; i++) {
1606
+ const attr = attrs[i];
1607
+ if (attr.name.startsWith('data-')) {
1608
+ const key = `[${attr.name}="${attr.value}"]`;
1609
+ if (this.byDataAttr.has(key)) {
1610
+ const tag = this.byDataAttr.get(key);
1611
+ if (safeMatches(el, tag.selector) && matchesLocation(tag))
1612
+ return tag;
1613
+ }
1614
+ }
1615
+ }
1616
+ }
1617
+ if (this.byClass.size > 0 && el.classList) {
1618
+ for (let i = 0; i < el.classList.length; i++) {
1619
+ const cls = el.classList[i];
1620
+ if (this.byClass.has(cls)) {
1621
+ const tag = this.byClass.get(cls);
1622
+ if (safeMatches(el, tag.selector) && matchesLocation(tag))
1623
+ return tag;
1624
+ }
1625
+ }
1626
+ }
1627
+ for (const tag of this.fallback) {
1628
+ if (safeMatches(el, tag.selector) && matchesLocation(tag))
1629
+ return tag;
1630
+ }
1631
+ return null;
1632
+ }
1633
+ clear() {
1634
+ this.tags = [];
1635
+ this.byId.clear();
1636
+ this.byDataAttr.clear();
1637
+ this.byClass.clear();
1638
+ this.fallback = [];
1639
+ }
1640
+ }
1641
+ /** Last combinator-separated segment of a CSS selector */
1642
+ function lastSegment(selector) {
1643
+ const trimmed = selector.trim();
1644
+ if (!trimmed)
1645
+ return null;
1646
+ const parts = trimmed.split(/\s*[>+~ ]\s*/);
1647
+ const last = parts[parts.length - 1]?.trim();
1648
+ return last || null;
1649
+ }
1650
+ /** First class name from a selector segment, e.g. "div.my-class" -> "my-class" */
1651
+ function extractClass(segment) {
1652
+ const match = segment.match(/\.([a-zA-Z_-][\w-]*)/);
1653
+ return match ? match[1] : null;
1654
+ }
1655
+ function safeMatches(el, selector) {
1656
+ try {
1657
+ return el.matches(selector);
1658
+ }
1659
+ catch {
1660
+ return false;
1661
+ }
1662
+ }
1663
+ function matchesLocation(tag) {
1664
+ if (!tag.location)
1665
+ return true;
1666
+ try {
1667
+ const loc = tag.location;
1668
+ if (loc.startsWith('/')) {
1669
+ return window.location.pathname === loc;
1670
+ }
1671
+ return window.location.href === loc;
1672
+ }
1673
+ catch {
1674
+ return true;
1675
+ }
1676
+ }
1677
+
2237
1678
  const WATCHED_MARKERS_KEY = '__or__watched_markers__';
2238
1679
  class MarkerWatcher {
2239
1680
  constructor(params) {
2240
1681
  this.interval = null;
2241
1682
  this.tags = [];
1683
+ this.matcher = new TagMatcher();
2242
1684
  this.sessionStorage = params.sessionStorage;
2243
1685
  this.errLog = params.errLog;
2244
1686
  this.onMarkerHit = params.onMarkerHit;
@@ -2281,12 +1723,15 @@ class MarkerWatcher {
2281
1723
  }
2282
1724
  setTags(tags) {
2283
1725
  this.tags = tags;
1726
+ this.matcher.setTags(tags);
2284
1727
  if (this.interval) {
2285
1728
  clearInterval(this.interval);
2286
1729
  this.interval = null;
2287
1730
  }
2288
1731
  this.interval = setInterval(() => {
2289
1732
  this.tags.forEach((tag) => {
1733
+ if (!matchesLocation(tag))
1734
+ return;
2290
1735
  const possibleEls = document.querySelectorAll(tag.selector);
2291
1736
  if (possibleEls.length > 0) {
2292
1737
  const el = possibleEls[0];
@@ -2298,13 +1743,17 @@ class MarkerWatcher {
2298
1743
  }, 500);
2299
1744
  }
2300
1745
  onMarkerRendered(markerId) {
2301
- if (this.tags.findIndex(t => t.id === markerId)) {
2302
- this.tags = this.tags.filter((tag) => tag.id !== markerId);
1746
+ const tag = this.tags.find((t) => t.id === markerId);
1747
+ if (tag) {
1748
+ this.tags = this.tags.filter((t) => t.id !== markerId);
1749
+ if (!matchesLocation(tag))
1750
+ return;
2303
1751
  }
2304
1752
  this.onMarkerHit(markerId);
2305
1753
  }
2306
1754
  clear() {
2307
1755
  this.tags = [];
1756
+ this.matcher.clear();
2308
1757
  if (this.interval) {
2309
1758
  clearInterval(this.interval);
2310
1759
  this.interval = null;
@@ -2355,6 +1804,30 @@ class CanvasRecorder {
2355
1804
  this.MAX_QUEUE_SIZE = 50; // ~500 images max (50 batches × 10 images)
2356
1805
  this.pendingBatches = [];
2357
1806
  this.isProcessingQueue = false;
1807
+ /**
1808
+ * Reacts to a runtime sanitization change on a canvas: stop capturing if it
1809
+ * just became masked, start if it just became visible. Already-sent frames
1810
+ * cannot be retracted, so escalation only stops future capture.
1811
+ */
1812
+ this.resanitizeCanvas = (node, id) => {
1813
+ if (!hasTag(node, 'canvas')) {
1814
+ return;
1815
+ }
1816
+ const isIgnored = this.app.sanitizer.isObscured(id) || this.app.sanitizer.isHidden(id);
1817
+ if (isIgnored) {
1818
+ if (this.snapshots[id] || this.observers.has(id)) {
1819
+ const observer = this.observers.get(id);
1820
+ if (observer) {
1821
+ observer.disconnect();
1822
+ this.observers.delete(id);
1823
+ }
1824
+ this.cleanupCanvas(id);
1825
+ }
1826
+ }
1827
+ else if (!this.snapshots[id] && !this.observers.has(id)) {
1828
+ this.captureCanvas(node);
1829
+ }
1830
+ };
2358
1831
  this.restartTracking = () => {
2359
1832
  this.clear();
2360
1833
  this.app.nodes.scanTree(this.captureCanvas);
@@ -2451,14 +1924,15 @@ class CanvasRecorder {
2451
1924
  }, this.interval);
2452
1925
  this.intervals.set(id, int);
2453
1926
  };
2454
- this.fileExt = options.fileExt ?? 'webp';
1927
+ this.fileExt = 'webp';
2455
1928
  this.interval = 1000 / options.fps;
2456
1929
  }
2457
1930
  startTracking() {
2458
1931
  setTimeout(() => {
2459
1932
  this.app.nodes.scanTree(this.captureCanvas);
2460
1933
  this.app.nodes.attachNodeCallback(this.captureCanvas);
2461
- }, 250);
1934
+ this.app.attachResanitizeCallback(this.resanitizeCanvas);
1935
+ }, 125);
2462
1936
  }
2463
1937
  sendSnaps(images, canvasId, createdAt) {
2464
1938
  if (Object.keys(this.snapshots).length === 0) {
@@ -2487,17 +1961,73 @@ class CanvasRecorder {
2487
1961
  }
2488
1962
  this.isProcessingQueue = false;
2489
1963
  }
2490
- uploadBatch(images, canvasId, createdAt) {
2491
- const formData = new FormData();
2492
- images.forEach((snapshot) => {
2493
- const blob = snapshot.data;
2494
- if (!blob)
2495
- return;
2496
- formData.append('snapshot', blob, `${createdAt}_${canvasId}_${snapshot.id}.${this.fileExt}`);
2497
- if (this.options.isDebug) {
2498
- saveImageData(blob, `${createdAt}_${canvasId}_${snapshot.id}.${this.fileExt}`);
1964
+ async uploadBatch(images, canvasId, createdAt) {
1965
+ if (this.options.isDebug) {
1966
+ const packed = await packFrames(images);
1967
+ if (packed) {
1968
+ const fileName = `${createdAt}_${canvasId}.${this.fileExt}.frames`;
1969
+ const url = URL.createObjectURL(new Blob([packed]));
1970
+ const link = document.createElement('a');
1971
+ link.href = url;
1972
+ link.download = fileName;
1973
+ link.style.display = 'none';
1974
+ document.body.appendChild(link);
1975
+ link.click();
1976
+ document.body.removeChild(link);
1977
+ URL.revokeObjectURL(url);
1978
+ }
1979
+ // fall through to also send to backend
1980
+ }
1981
+ let formData;
1982
+ if (this.options.framesSupport) {
1983
+ // Pack frames into binary format: [uint64 LE timestamp][uint32 LE size][data] per frame
1984
+ const buffers = [];
1985
+ let totalSize = 0;
1986
+ for (const snapshot of images) {
1987
+ if (!snapshot.data)
1988
+ continue;
1989
+ const ab = await snapshot.data.arrayBuffer();
1990
+ buffers.push(ab);
1991
+ totalSize += 8 + 4 + ab.byteLength; // uint64 ts + uint32 size + data
2499
1992
  }
2500
- });
1993
+ if (totalSize === 0)
1994
+ return;
1995
+ const packed = new ArrayBuffer(totalSize);
1996
+ const view = new DataView(packed);
1997
+ const bytes = new Uint8Array(packed);
1998
+ let offset = 0;
1999
+ for (let i = 0; i < images.length; i++) {
2000
+ if (!images[i].data)
2001
+ continue;
2002
+ const ab = buffers.shift();
2003
+ const ts = images[i].id;
2004
+ // uint64 LE as two uint32 LE writes -- timestamp
2005
+ view.setUint32(offset, ts % 0x100000000, true);
2006
+ view.setUint32(offset + 4, Math.floor(ts / 0x100000000), true);
2007
+ offset += 8;
2008
+ // uint32 LE -- size
2009
+ view.setUint32(offset, ab.byteLength, true);
2010
+ offset += 4;
2011
+ // image data
2012
+ bytes.set(new Uint8Array(ab), offset);
2013
+ offset += ab.byteLength;
2014
+ }
2015
+ formData = new FormData();
2016
+ formData.append('type', 'frames');
2017
+ const fileName = `${createdAt}_${canvasId}.${this.fileExt}.frames`;
2018
+ formData.append('frames', new Blob([packed]), fileName);
2019
+ }
2020
+ else {
2021
+ // Legacy: send individual image files
2022
+ formData = new FormData();
2023
+ images.forEach((snapshot) => {
2024
+ const blob = snapshot.data;
2025
+ if (!blob)
2026
+ return;
2027
+ const name = `${createdAt}_${canvasId}_${snapshot.id}.${this.fileExt}`;
2028
+ formData.append('snapshot', blob, name);
2029
+ });
2030
+ }
2501
2031
  const initRestart = () => {
2502
2032
  this.app.debug.log('Restarting tracker; token expired');
2503
2033
  this.app.stop(false);
@@ -2593,15 +2123,36 @@ function captureSnapshot(canvas, quality = 'medium', dummy, fixedScaling = false
2593
2123
  canvas.toBlob(onBlob, imageFormat, qualityInt[quality]);
2594
2124
  }
2595
2125
  }
2596
- function saveImageData(imageDataBlob, name) {
2597
- const imageDataUrl = URL.createObjectURL(imageDataBlob);
2598
- const link = document.createElement('a');
2599
- link.href = imageDataUrl;
2600
- link.download = name;
2601
- link.style.display = 'none';
2602
- document.body.appendChild(link);
2603
- link.click();
2604
- document.body.removeChild(link);
2126
+ async function packFrames(images) {
2127
+ const buffers = [];
2128
+ let totalSize = 0;
2129
+ for (const snapshot of images) {
2130
+ if (!snapshot.data)
2131
+ continue;
2132
+ const ab = await snapshot.data.arrayBuffer();
2133
+ buffers.push(ab);
2134
+ totalSize += 8 + 4 + ab.byteLength;
2135
+ }
2136
+ if (totalSize === 0)
2137
+ return null;
2138
+ const packed = new ArrayBuffer(totalSize);
2139
+ const view = new DataView(packed);
2140
+ const bytes = new Uint8Array(packed);
2141
+ let offset = 0;
2142
+ for (let i = 0; i < images.length; i++) {
2143
+ if (!images[i].data)
2144
+ continue;
2145
+ const ab = buffers.shift();
2146
+ const ts = images[i].id;
2147
+ view.setUint32(offset, ts % 0x100000000, true);
2148
+ view.setUint32(offset + 4, Math.floor(ts / 0x100000000), true);
2149
+ offset += 8;
2150
+ view.setUint32(offset, ab.byteLength, true);
2151
+ offset += 4;
2152
+ bytes.set(new Uint8Array(ab), offset);
2153
+ offset += ab.byteLength;
2154
+ }
2155
+ return packed;
2605
2156
  }
2606
2157
 
2607
2158
  const LogLevel = {
@@ -2882,8 +2433,44 @@ function inlineRemoteCss(node, id, baseHref, getNextID, insertRule, addOwner, fo
2882
2433
  })
2883
2434
  .catch(error => {
2884
2435
  console.error(`OxvoSessions: Failed to fetch CSS from ${node.href}:`, error);
2436
+ // Fetch failed, likely due to CORS. Retry via load event and sheet access.
2437
+ retryViaLoadEvent();
2885
2438
  });
2886
2439
  }
2440
+ else {
2441
+ retryViaLoadEvent();
2442
+ }
2443
+ function retryViaLoadEvent() {
2444
+ const trySheet = () => {
2445
+ const loadedSheet = node.sheet;
2446
+ if (loadedSheet) {
2447
+ try {
2448
+ const cssText = stringifyStylesheet(loadedSheet);
2449
+ if (cssText) {
2450
+ if (sendPlain && onPlain) {
2451
+ onPlain(cssText, fakeIdHolder++);
2452
+ }
2453
+ else {
2454
+ processCssText(cssText);
2455
+ }
2456
+ return;
2457
+ }
2458
+ }
2459
+ catch (e) {
2460
+ // Cross-origin sheet. Nothing more can be read safely here.
2461
+ }
2462
+ }
2463
+ };
2464
+ if (node.sheet) {
2465
+ trySheet();
2466
+ }
2467
+ else {
2468
+ node.addEventListener('load', function onLoad() {
2469
+ node.removeEventListener('load', onLoad);
2470
+ trySheet();
2471
+ });
2472
+ }
2473
+ }
2887
2474
  function processCssText(cssText) {
2888
2475
  // Remove comments
2889
2476
  cssText = cssText.replace(/\/\*[\s\S]*?\*\//g, '');
@@ -3105,7 +2692,6 @@ function ConstructedStyleSheets (app) {
3105
2692
  if (!hasAdoptedSS(document)) {
3106
2693
  return;
3107
2694
  }
3108
- const styleSheetIDMap = new Map();
3109
2695
  const adoptedStyleSheetsOwnings = new Map();
3110
2696
  const sendAdoptedStyleSheetsUpdate = (root) => setTimeout(() => {
3111
2697
  let nodeID = app.nodes.getID(root);
@@ -3216,7 +2802,120 @@ function ConstructedStyleSheets (app) {
3216
2802
  if (hasAdoptedSS(node)) {
3217
2803
  sendAdoptedStyleSheetsUpdate(node);
3218
2804
  }
3219
- });
2805
+ });
2806
+ }
2807
+
2808
+ exports.SanitizeLevel = void 0;
2809
+ (function (SanitizeLevel) {
2810
+ SanitizeLevel[SanitizeLevel["Plain"] = 0] = "Plain";
2811
+ SanitizeLevel[SanitizeLevel["Obscured"] = 1] = "Obscured";
2812
+ SanitizeLevel[SanitizeLevel["Hidden"] = 2] = "Hidden";
2813
+ })(exports.SanitizeLevel || (exports.SanitizeLevel = {}));
2814
+ const stringWiper = (input) => input
2815
+ .trim()
2816
+ .replace(/[^\f\n\r\t\v\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff\s]/g, '*');
2817
+ class Sanitizer {
2818
+ constructor(params) {
2819
+ // Node id -> level. Plain (0) is never stored; a missing entry means Plain.
2820
+ // A map (not the old grow-only Sets) so levels can be raised and lowered.
2821
+ this.levels = new Map();
2822
+ this.app = params.app;
2823
+ const defaultOptions = {
2824
+ maskTextEmails: true,
2825
+ maskTextNumbers: false,
2826
+ maskAllByDefault: false,
2827
+ domSanitizer: undefined,
2828
+ };
2829
+ this.maskAllByDefault = params.options?.maskAllByDefault ?? false;
2830
+ this.options = Object.assign(defaultOptions, params.options);
2831
+ }
2832
+ // Pure recomputation of a node's level from the live DOM + parent level.
2833
+ // Reading current state on every call lets resanitize() pick up runtime changes.
2834
+ computeLevel(node, parentLevel) {
2835
+ if (this.options.maskAllByDefault) {
2836
+ if (isElementNode(node) && !hasOxvoSessionsAttribute(node, 'unmask')) {
2837
+ return exports.SanitizeLevel.Obscured;
2838
+ }
2839
+ if (isTextNode(node) && !hasOxvoSessionsAttribute(node.parentNode, 'unmask')) {
2840
+ return exports.SanitizeLevel.Obscured;
2841
+ }
2842
+ }
2843
+ let level = exports.SanitizeLevel.Plain;
2844
+ if (parentLevel >= exports.SanitizeLevel.Obscured ||
2845
+ (isElementNode(node) &&
2846
+ (hasOxvoSessionsAttribute(node, 'masked') || hasOxvoSessionsAttribute(node, 'obscured')))) {
2847
+ level = exports.SanitizeLevel.Obscured;
2848
+ }
2849
+ if (parentLevel === exports.SanitizeLevel.Hidden ||
2850
+ (isElementNode(node) &&
2851
+ (hasOxvoSessionsAttribute(node, 'htmlmasked') || hasOxvoSessionsAttribute(node, 'hidden')))) {
2852
+ level = exports.SanitizeLevel.Hidden;
2853
+ }
2854
+ if (this.options.domSanitizer !== undefined && isElementNode(node)) {
2855
+ const sanitizeLevel = this.options.domSanitizer(node);
2856
+ if (sanitizeLevel === exports.SanitizeLevel.Obscured && level < exports.SanitizeLevel.Obscured) {
2857
+ level = exports.SanitizeLevel.Obscured;
2858
+ }
2859
+ if (sanitizeLevel === exports.SanitizeLevel.Hidden) {
2860
+ level = exports.SanitizeLevel.Hidden;
2861
+ }
2862
+ }
2863
+ return level;
2864
+ }
2865
+ getLevel(id) {
2866
+ return this.levels.get(id) ?? exports.SanitizeLevel.Plain;
2867
+ }
2868
+ // Sets a node's level in either direction and returns the previous level.
2869
+ setLevel(id, level) {
2870
+ const prev = this.getLevel(id);
2871
+ if (level === exports.SanitizeLevel.Plain) {
2872
+ this.levels.delete(id);
2873
+ }
2874
+ else {
2875
+ this.levels.set(id, level);
2876
+ }
2877
+ return prev;
2878
+ }
2879
+ handleNode(id, parentID, node) {
2880
+ const level = this.computeLevel(node, this.getLevel(parentID));
2881
+ // Escalate-only: commits never lower a level, only resanitize/setLevel do.
2882
+ if (level > this.getLevel(id)) {
2883
+ this.setLevel(id, level);
2884
+ }
2885
+ }
2886
+ sanitize(id, data) {
2887
+ if (this.getLevel(id) >= exports.SanitizeLevel.Obscured) {
2888
+ // TODO: is it the best place to put trim() ? Might trimmed spaces be considered in layout in certain cases?
2889
+ return stringWiper(data);
2890
+ }
2891
+ if (this.options.maskTextNumbers) {
2892
+ data = data.replace(/\d/g, '0');
2893
+ }
2894
+ if (this.options.maskTextEmails) {
2895
+ data = data.replace(/^\w+([+.-]\w+)*@\w+([.-]\w+)*\.\w{2,3}$/g, (email) => {
2896
+ const [name, domain] = email.split('@');
2897
+ const [domainName, host] = domain.split('.');
2898
+ return `${stars(name)}@${stars(domainName)}.${stars(host)}`;
2899
+ });
2900
+ }
2901
+ return data;
2902
+ }
2903
+ isObscured(id) {
2904
+ return this.getLevel(id) >= exports.SanitizeLevel.Obscured;
2905
+ }
2906
+ isHidden(id) {
2907
+ return this.getLevel(id) === exports.SanitizeLevel.Hidden;
2908
+ }
2909
+ getInnerTextSecure(el) {
2910
+ const id = this.app.nodes.getID(el);
2911
+ if (!id) {
2912
+ return '';
2913
+ }
2914
+ return this.sanitize(id, el.innerText);
2915
+ }
2916
+ clear() {
2917
+ this.levels.clear();
2918
+ }
3220
2919
  }
3221
2920
 
3222
2921
  const iconCache = {};
@@ -3370,6 +3069,12 @@ class Observer {
3370
3069
  this.inlineRemoteCss = false;
3371
3070
  this.inlinerOptions = undefined;
3372
3071
  this.domParser = new DOMParser();
3072
+ /**
3073
+ * Bumped on every disconnect(). Stale async CSS-inlining callbacks from a
3074
+ * previous session (e.g. agent reload → tracker restart) compare against
3075
+ * this and bail instead of sending messages that reference vanished node ids.
3076
+ */
3077
+ this.generation = 0;
3373
3078
  this.throttling = true;
3374
3079
  this.throttledSetNodeData = throttleWithTrailing((id, parentElement, data) => this.sendNodeData(id, parentElement, data), 30);
3375
3080
  this.throttling = !Boolean(options.disableThrottling);
@@ -3516,18 +3221,23 @@ class Observer {
3516
3221
  }
3517
3222
  if (name === 'style' || (name === 'href' && hasTag(node, 'link'))) {
3518
3223
  if ('rel' in node && node.rel === 'stylesheet' && this.inlineRemoteCss) {
3224
+ const gen = this.generation;
3519
3225
  setTimeout(() => {
3520
3226
  inlineRemoteCss(
3521
3227
  // @ts-ignore
3522
3228
  node, id, this.app.getBaseHref(), nextID, (id, cssText, index, baseHref) => {
3229
+ if (this.generation !== gen)
3230
+ return;
3523
3231
  this.app.send(AdoptedSSInsertRuleURLBased(id, cssText, index, baseHref));
3524
3232
  }, (sheetId, ownerId) => {
3233
+ if (this.generation !== gen)
3234
+ return;
3525
3235
  this.app.send(AdoptedSSAddOwner(sheetId, ownerId));
3526
3236
  }, this.inlinerOptions?.forceFetch, this.inlinerOptions?.forcePlain, (cssText, fakeTextId) => {
3237
+ if (this.generation !== gen)
3238
+ return;
3527
3239
  this.app.send(CreateTextNode(fakeTextId, id, 0));
3528
- setTimeout(() => {
3529
- this.app.send(SetCSSDataURLBased(fakeTextId, cssText, this.app.getBaseHref()));
3530
- }, 10);
3240
+ this.app.send(SetCSSDataURLBased(fakeTextId, cssText, this.app.getBaseHref()));
3531
3241
  });
3532
3242
  }, 0);
3533
3243
  return;
@@ -3787,10 +3497,105 @@ class Observer {
3787
3497
  beforeCommit(this.app.nodes.getID(node));
3788
3498
  this.commitNodes(true);
3789
3499
  }
3500
+ /**
3501
+ * Re-evaluates sanitization for every tracked node in `root`'s subtree against
3502
+ * the current DOM and re-emits whatever changed. Pass the highest node you
3503
+ * changed (or the document root) so inherited levels propagate correctly.
3504
+ */
3505
+ resanitizeSubtree(root) {
3506
+ if (!isObservable(root)) {
3507
+ return;
3508
+ }
3509
+ const parent = root.parentNode;
3510
+ const parentId = parent !== null ? this.app.nodes.getID(parent) : undefined;
3511
+ const parentLevel = parentId !== undefined ? this.app.sanitizer.getLevel(parentId) : exports.SanitizeLevel.Plain;
3512
+ this.resanitizeNode(root, parentLevel);
3513
+ }
3514
+ resanitizeNode(node, parentLevel) {
3515
+ if (isIgnored(node)) {
3516
+ return;
3517
+ }
3518
+ const id = this.app.nodes.getID(node);
3519
+ if (id === undefined) {
3520
+ // Untracked (new, or under a hidden ancestor): the live observer handles it.
3521
+ return;
3522
+ }
3523
+ const newLevel = this.app.sanitizer.computeLevel(node, parentLevel);
3524
+ const prevLevel = this.app.sanitizer.getLevel(id);
3525
+ const wasHidden = prevLevel === exports.SanitizeLevel.Hidden;
3526
+ const willHidden = newLevel === exports.SanitizeLevel.Hidden;
3527
+ // Crossing the hidden boundary changes the rendered structure (placeholder vs
3528
+ // real subtree), so rebuild rather than re-emit.
3529
+ if (wasHidden !== willHidden) {
3530
+ this.recreateSubtree(node);
3531
+ return;
3532
+ }
3533
+ if (willHidden) {
3534
+ return;
3535
+ }
3536
+ // Plain <-> Obscured: same structure, only leaf content changes.
3537
+ if (prevLevel !== newLevel) {
3538
+ this.app.sanitizer.setLevel(id, newLevel);
3539
+ this.reemitNode(id, node);
3540
+ }
3541
+ for (let child = node.firstChild; child !== null; child = child.nextSibling) {
3542
+ this.resanitizeNode(child, newLevel);
3543
+ }
3544
+ }
3545
+ // Destroys the node player-side and re-emits its subtree from scratch (new ids)
3546
+ // so it materializes at the freshly-computed level.
3547
+ recreateSubtree(node) {
3548
+ const id = this.app.nodes.getID(node);
3549
+ if (id === undefined) {
3550
+ return;
3551
+ }
3552
+ this.app.send(RemoveNode(id));
3553
+ this.clearSubtreeRegistration(node);
3554
+ this.bindTree(node);
3555
+ this.commitNodes();
3556
+ }
3557
+ clearSubtreeRegistration(node) {
3558
+ const clearOne = (n) => {
3559
+ const oldId = this.app.nodes.getID(n);
3560
+ if (oldId !== undefined) {
3561
+ this.app.sanitizer.setLevel(oldId, exports.SanitizeLevel.Plain);
3562
+ }
3563
+ this.app.nodes.unregisterNode(n);
3564
+ };
3565
+ const walker = document.createTreeWalker(node, NodeFilter.SHOW_ELEMENT + NodeFilter.SHOW_TEXT, {
3566
+ acceptNode: (n) => isIgnored(n) || this.app.nodes.getID(n) === undefined
3567
+ ? NodeFilter.FILTER_REJECT
3568
+ : NodeFilter.FILTER_ACCEPT,
3569
+ },
3570
+ // @ts-ignore
3571
+ false);
3572
+ // Collect first, then clear: unregistering mutates the ids the walker reads.
3573
+ const subtree = [];
3574
+ while (walker.nextNode()) {
3575
+ subtree.push(walker.currentNode);
3576
+ }
3577
+ clearOne(node);
3578
+ subtree.forEach(clearOne);
3579
+ }
3580
+ reemitNode(id, node) {
3581
+ if (isTextNode(node)) {
3582
+ const parent = node.parentNode;
3583
+ if (parent !== null && isElementNode(parent)) {
3584
+ // re-runs sanitize() at the level we just set
3585
+ this.sendNodeData(id, parent, node.data);
3586
+ }
3587
+ return;
3588
+ }
3589
+ if (isElementNode(node)) {
3590
+ // inputs/images/canvas re-emit their own payload via registered callbacks
3591
+ this.app.callResanitizeCallbacks(node, id);
3592
+ }
3593
+ }
3790
3594
  disconnect() {
3791
3595
  this.observer.disconnect();
3792
3596
  this.clear();
3793
3597
  this.throttledSetNodeData.clear();
3598
+ this.generation++;
3794
3599
  }
3795
3600
  }
3796
3601
 
@@ -4025,7 +3830,10 @@ class TopObserver extends Observer {
4025
3830
  this.app.debug.info('doc already observed for', id);
4026
3831
  return;
4027
3832
  }
4028
- const observer = new IFrameObserver(this.app, false, {});
3833
+ const observer = new IFrameObserver(this.app, false, {
3834
+ disableSprites: this.options.disableSprites,
3835
+ ...getInlineOptions(this.options.inlineCss, console.warn),
3836
+ });
4029
3837
  this.iframeObservers.set(iframe, observer);
4030
3838
  this.docObservers.set(currentDoc, observer);
4031
3839
  this.iframeObserversArr.push(observer);
@@ -4047,7 +3855,10 @@ class TopObserver extends Observer {
4047
3855
  handle();
4048
3856
  }
4049
3857
  handleShadowRoot(shRoot) {
4050
- const observer = new ShadowRootObserver(this.app);
3858
+ const observer = new ShadowRootObserver(this.app, false, {
3859
+ disableSprites: this.options.disableSprites,
3860
+ ...getInlineOptions(this.options.inlineCss, console.warn),
3861
+ });
4051
3862
  this.shadowRootObservers.set(shRoot, observer);
4052
3863
  observer.observe(shRoot.host);
4053
3864
  }
@@ -4083,7 +3894,10 @@ class TopObserver extends Observer {
4083
3894
  };
4084
3895
  this.app.nodes.clear();
4085
3896
  this.app.nodes.crossdomainMode(frameLevel, frameOder);
4086
- const iframeObserver = new IFrameObserver(this.app);
3897
+ const iframeObserver = new IFrameObserver(this.app, false, {
3898
+ disableSprites: this.options.disableSprites,
3899
+ ...getInlineOptions(this.options.inlineCss, console.warn),
3900
+ });
4087
3901
  this.iframeObservers.set(window.document, iframeObserver);
4088
3902
  iframeObserver.syntheticObserve(rootNodeId, window.document);
4089
3903
  }
@@ -4099,94 +3913,6 @@ class TopObserver extends Observer {
4099
3913
  }
4100
3914
  }
4101
3915
 
4102
- exports.SanitizeLevel = void 0;
4103
- (function (SanitizeLevel) {
4104
- SanitizeLevel[SanitizeLevel["Plain"] = 0] = "Plain";
4105
- SanitizeLevel[SanitizeLevel["Obscured"] = 1] = "Obscured";
4106
- SanitizeLevel[SanitizeLevel["Hidden"] = 2] = "Hidden";
4107
- })(exports.SanitizeLevel || (exports.SanitizeLevel = {}));
4108
- const stringWiper = (input) => input
4109
- .trim()
4110
- .replace(/[^\f\n\r\t\v\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff\s]/g, '*');
4111
- class Sanitizer {
4112
- constructor(params) {
4113
- this.obscured = new Set();
4114
- this.hidden = new Set();
4115
- this.app = params.app;
4116
- const defaultOptions = {
4117
- maskTextEmails: true,
4118
- maskTextNumbers: false,
4119
- maskAllByDefault: false,
4120
- domSanitizer: undefined,
4121
- };
4122
- this.maskAllByDefault = params.options?.maskAllByDefault ?? false;
4123
- this.options = Object.assign(defaultOptions, params.options);
4124
- }
4125
- handleNode(id, parentID, node) {
4126
- if (this.options.maskAllByDefault) {
4127
- if (isElementNode(node) && !hasOxvoSessionsAttribute(node, 'unmask')) {
4128
- return this.obscured.add(id);
4129
- }
4130
- if (isTextNode(node) && !hasOxvoSessionsAttribute(node.parentNode, 'unmask')) {
4131
- return this.obscured.add(id);
4132
- }
4133
- }
4134
- if (this.obscured.has(parentID) ||
4135
- (isElementNode(node) &&
4136
- (hasOxvoSessionsAttribute(node, 'masked') || hasOxvoSessionsAttribute(node, 'obscured')))) {
4137
- this.obscured.add(id);
4138
- }
4139
- if (this.hidden.has(parentID) ||
4140
- (isElementNode(node) &&
4141
- (hasOxvoSessionsAttribute(node, 'htmlmasked') || hasOxvoSessionsAttribute(node, 'hidden')))) {
4142
- this.hidden.add(id);
4143
- }
4144
- if (this.options.domSanitizer !== undefined && isElementNode(node)) {
4145
- const sanitizeLevel = this.options.domSanitizer(node);
4146
- if (sanitizeLevel === exports.SanitizeLevel.Obscured) {
4147
- this.obscured.add(id);
4148
- }
4149
- if (sanitizeLevel === exports.SanitizeLevel.Hidden) {
4150
- this.hidden.add(id);
4151
- }
4152
- }
4153
- }
4154
- sanitize(id, data) {
4155
- if (this.obscured.has(id)) {
4156
- // TODO: is it the best place to put trim() ? Might trimmed spaces be considered in layout in certain cases?
4157
- return stringWiper(data);
4158
- }
4159
- if (this.options.maskTextNumbers) {
4160
- data = data.replace(/\d/g, '0');
4161
- }
4162
- if (this.options.maskTextEmails) {
4163
- data = data.replace(/^\w+([+.-]\w+)*@\w+([.-]\w+)*\.\w{2,3}$/g, (email) => {
4164
- const [name, domain] = email.split('@');
4165
- const [domainName, host] = domain.split('.');
4166
- return `${stars(name)}@${stars(domainName)}.${stars(host)}`;
4167
- });
4168
- }
4169
- return data;
4170
- }
4171
- isObscured(id) {
4172
- return this.obscured.has(id);
4173
- }
4174
- isHidden(id) {
4175
- return this.hidden.has(id);
4176
- }
4177
- getInnerTextSecure(el) {
4178
- const id = this.app.nodes.getID(el);
4179
- if (!id) {
4180
- return '';
4181
- }
4182
- return this.sanitize(id, el.innerText);
4183
- }
4184
- clear() {
4185
- this.obscured.clear();
4186
- this.hidden.clear();
4187
- }
4188
- }
4189
-
4190
3916
  const tokenSeparator = '_$_';
4191
3917
  class Session {
4192
3918
  constructor(params) {
@@ -4386,9 +4112,10 @@ class Ticker {
4386
4112
  * this value is injected during build time via rollup
4387
4113
  * */
4388
4114
  // @ts-ignore
4389
- const workerBodyFn = "!function(){\"use strict\";class t{constructor(t,s,i,e=10,n=250,h,r){this.onUnauthorised=s,this.onFailure=i,this.MAX_ATTEMPTS_COUNT=e,this.ATTEMPT_TIMEOUT=n,this.onCompress=h,this.pageNo=r,this.attemptsCount=0,this.busy=!1,this.queue=[],this.token=null,this.lastBatchNum=0,this.ingestURL=t+\"/v1/events\",this.isCompressing=void 0!==h}getQueueStatus(){return 0===this.queue.length&&!this.busy}authorise(t){this.token=t,this.busy||this.sendNext()}push(t){if(this.busy||!this.token)this.queue.push(t);else if(this.busy=!0,this.isCompressing&&this.onCompress)this.onCompress(t);else{const s=++this.lastBatchNum;this.sendBatch(t,!1,s)}}sendNext(){const t=this.queue.shift();if(t)if(this.busy=!0,this.isCompressing&&this.onCompress)this.onCompress(t);else{const s=++this.lastBatchNum;this.sendBatch(t,!1,s)}else this.busy=!1}retry(t,s,i){this.attemptsCount>=this.MAX_ATTEMPTS_COUNT?this.onFailure(`Failed to send batch after ${this.attemptsCount} attempts.`):(this.attemptsCount++,setTimeout((()=>this.sendBatch(t,s,i)),this.ATTEMPT_TIMEOUT*this.attemptsCount))}sendBatch(t,s,i){var e;const n=null==i?void 0:i.toString().replace(/^([^_]+)_([^_]+).*/,\"$1_$2_$3\");this.busy=!0;const h={Authorization:`Bearer ${this.token}`};if(s&&(h[\"Content-Encoding\"]=\"gzip\"),null===this.token)return void setTimeout((()=>{this.sendBatch(t,s,`${null!=i?i:\"noBatchNum\"}_newToken`)}),500);const r=`${null!==(e=this.pageNo)&&void 0!==e?e:\"noPageNum\"}_${null!=n?n:\"noBatchNum\"}`;fetch(`${this.ingestURL}?seq=${r}`,{body:t,method:\"POST\",headers:h,keepalive:t.length<65536}).then((e=>{if(401===e.status)return this.busy=!1,void this.onUnauthorised();e.status>=400?this.retry(t,s,`${null!=i?i:\"noBatchNum\"}_network:${e.status}`):(this.attemptsCount=0,this.sendNext())})).catch((e=>{console.warn(\"OxvoSessions:\",e),this.retry(t,s,`${null!=i?i:\"noBatchNum\"}_reject:${e.message}`)}))}sendCompressed(t){const s=++this.lastBatchNum;this.sendBatch(t,!0,s)}sendUncompressed(t){const s=++this.lastBatchNum;this.sendBatch(t,!1,s)}clean(){this.sendNext(),setTimeout((()=>{this.token=null,this.queue.length=0}),10)}}const s=\"function\"==typeof TextEncoder?new TextEncoder:{encode(t){const s=t.length,i=new Uint8Array(3*s);let e=-1;for(let n=0,h=0,r=0;r!==s;){if(n=t.charCodeAt(r),r+=1,n>=55296&&n<=56319){if(r===s){i[e+=1]=239,i[e+=1]=191,i[e+=1]=189;break}if(h=t.charCodeAt(r),!(h>=56320&&h<=57343)){i[e+=1]=239,i[e+=1]=191,i[e+=1]=189;continue}if(n=1024*(n-55296)+h-56320+65536,r+=1,n>65535){i[e+=1]=240|n>>>18,i[e+=1]=128|n>>>12&63,i[e+=1]=128|n>>>6&63,i[e+=1]=128|63&n;continue}}n<=127?i[e+=1]=0|n:n<=2047?(i[e+=1]=192|n>>>6,i[e+=1]=128|63&n):(i[e+=1]=224|n>>>12,i[e+=1]=128|n>>>6&63,i[e+=1]=128|63&n)}return i.subarray(0,e+1)}};class i{constructor(t){this.size=t,this.offset=0,this.checkpointOffset=0,this.data=new Uint8Array(t)}getCurrentOffset(){return this.offset}checkpoint(){this.checkpointOffset=this.offset}get isEmpty(){return 0===this.offset}skip(t){return this.offset+=t,this.offset<=this.size}set(t,s){this.data.set(t,s)}boolean(t){return this.data[this.offset++]=+t,this.offset<=this.size}uint(t){for((t<0||t>Number.MAX_SAFE_INTEGER)&&(t=0);t>=128;)this.data[this.offset++]=t%256|128,t=Math.floor(t/128);return this.data[this.offset++]=t,this.offset<=this.size}int(t){return t=Math.round(t),this.uint(t>=0?2*t:-2*t-1)}string(t){const i=s.encode(t),e=i.byteLength;return!(!this.uint(e)||this.offset+e>this.size)&&(this.data.set(i,this.offset),this.offset+=e,!0)}reset(){this.offset=0,this.checkpointOffset=0}flush(){const t=this.data.slice(0,this.checkpointOffset);return this.reset(),t}}class e extends i{encode(t){switch(t[0]){case 0:case 11:case 114:case 115:return this.uint(t[1]);case 4:case 44:case 47:return this.string(t[1])&&this.string(t[2])&&this.uint(t[3]);case 5:case 20:case 65:case 70:case 75:case 76:case 77:case 82:return this.uint(t[1])&&this.uint(t[2]);case 6:return this.int(t[1])&&this.int(t[2]);case 7:return!0;case 8:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.string(t[4])&&this.boolean(t[5]);case 9:case 10:case 24:case 35:case 51:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3]);case 12:case 52:case 61:case 71:return this.uint(t[1])&&this.string(t[2])&&this.string(t[3]);case 13:case 14:case 17:case 34:case 36:case 50:case 54:return this.uint(t[1])&&this.string(t[2]);case 16:return this.uint(t[1])&&this.int(t[2])&&this.int(t[3]);case 18:return this.uint(t[1])&&this.string(t[2])&&this.int(t[3]);case 19:return this.uint(t[1])&&this.boolean(t[2]);case 21:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4])&&this.string(t[5])&&this.uint(t[6])&&this.uint(t[7])&&this.uint(t[8]);case 22:case 27:case 30:case 41:case 45:case 46:case 43:case 63:case 64:case 79:case 124:return this.string(t[1])&&this.string(t[2]);case 23:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.uint(t[4])&&this.uint(t[5])&&this.uint(t[6])&&this.uint(t[7])&&this.uint(t[8])&&this.uint(t[9]);case 28:case 29:case 42:case 117:case 118:return this.string(t[1]);case 40:return this.string(t[1])&&this.uint(t[2])&&this.string(t[3])&&this.string(t[4]);case 48:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4])&&this.int(t[5]);case 49:return this.int(t[1])&&this.int(t[2])&&this.uint(t[3])&&this.uint(t[4]);case 53:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.uint(t[4])&&this.uint(t[5])&&this.uint(t[6])&&this.string(t[7])&&this.string(t[8]);case 55:return this.boolean(t[1]);case 57:case 60:return this.uint(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4]);case 58:case 120:return this.int(t[1]);case 68:return this.uint(t[1])&&this.uint(t[2])&&this.string(t[3])&&this.string(t[4])&&this.uint(t[5])&&this.uint(t[6]);case 69:return this.uint(t[1])&&this.uint(t[2])&&this.string(t[3])&&this.string(t[4]);case 73:return this.uint(t[1])&&this.string(t[2])&&this.uint(t[3])&&this.string(t[4]);case 78:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4]);case 81:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.int(t[4])&&this.string(t[5]);case 83:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4])&&this.string(t[5])&&this.uint(t[6])&&this.uint(t[7])&&this.uint(t[8])&&this.uint(t[9]);case 84:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.uint(t[4])&&this.string(t[5])&&this.string(t[6]);case 85:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.uint(t[4])&&this.uint(t[5])&&this.uint(t[6])&&this.string(t[7])&&this.string(t[8])&&this.uint(t[9])&&this.boolean(t[10])&&this.uint(t[11])&&this.uint(t[12])&&this.uint(t[13])&&this.uint(t[14])&&this.uint(t[15])&&this.uint(t[16])&&this.uint(t[17]);case 87:return this.string(t[1])&&this.int(t[2])&&this.int(t[3]);case 89:return this.string(t[1])&&this.int(t[2])&&this.int(t[3])&&this.int(t[4])&&this.int(t[5])&&this.string(t[6]);case 112:return this.uint(t[1])&&this.string(t[2])&&this.boolean(t[3])&&this.string(t[4])&&this.int(t[5])&&this.int(t[6]);case 113:return this.uint(t[1])&&this.uint(t[2])&&this.string(t[3]);case 116:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.uint(t[4])&&this.uint(t[5])&&this.uint(t[6])&&this.string(t[7])&&this.string(t[8])&&this.uint(t[9])&&this.boolean(t[10]);case 119:return this.string(t[1])&&this.uint(t[2]);case 121:return this.string(t[1])&&this.string(t[2])&&this.uint(t[3])&&this.uint(t[4]);case 122:return this.string(t[1])&&this.string(t[2])&&this.uint(t[3])&&this.string(t[4]);case 123:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4])&&this.uint(t[5])}}}class n{constructor(t,s,i,n,h,r){this.pageNo=t,this.timestamp=s,this.url=i,this.onBatch=n,this.tabId=h,this.onOfflineEnd=r,this.nextIndex=0,this.beaconSize=2e5,this.encoder=new e(this.beaconSize),this.sizeBuffer=new Uint8Array(3),this.isEmpty=!0,this.checkpoints=[],this.beaconSizeLimit=1e6,this.prepare()}writeType(t){return this.encoder.uint(t[0])}writeFields(t){return this.encoder.encode(t)}writeSizeAt(t,s){for(let s=0;s<3;s++)this.sizeBuffer[s]=t>>8*s;this.encoder.set(this.sizeBuffer,s)}prepare(){if(!this.encoder.isEmpty)return;this.checkpoints.length=0;const t=[81,1,this.pageNo,this.nextIndex,this.timestamp,this.url],s=[0,this.timestamp],i=[118,this.tabId];this.writeType(t),this.writeFields(t),this.writeWithSize(s),this.writeWithSize(i),this.isEmpty=!0}writeWithSize(t){const s=this.encoder;if(!this.writeType(t)||!s.skip(3))return!1;const i=s.getCurrentOffset(),e=this.writeFields(t);if(e){const e=s.getCurrentOffset()-i;if(e>16777215)return console.warn(\"OxvoSessions: max message size overflow.\"),!1;this.writeSizeAt(e,i-3),s.checkpoint(),this.checkpoints.push(s.getCurrentOffset()),this.isEmpty=this.isEmpty&&0===t[0],this.nextIndex++}return e}setBeaconSizeLimit(t){this.beaconSizeLimit=t}writeMessage(t){if(-1===t[0])return this.finaliseBatch(),this.onOfflineEnd();0===t[0]&&(this.timestamp=t[1]),122===t[0]&&(this.url=t[1]),this.writeWithSize(t)||(this.finaliseBatch(),this.writeWithSize(t)||(this.encoder=new e(this.beaconSizeLimit),this.prepare(),this.writeWithSize(t)?this.finaliseBatch():console.warn(\"OxvoSessions: beacon size overflow. Skipping large message.\",t,this),this.encoder=new e(this.beaconSize),this.prepare()))}finaliseBatch(t=!1){if(this.isEmpty)return;const s=this.encoder.flush();this.onBatch(s,t),this.prepare()}clean(){this.encoder.reset(),this.checkpoints.length=0}}var h;!function(t){t[t.NotActive=0]=\"NotActive\",t[t.Starting=1]=\"Starting\",t[t.Stopping=2]=\"Stopping\",t[t.Active=3]=\"Active\",t[t.Stopped=4]=\"Stopped\"}(h||(h={}));let r=null,u=null,a=h.NotActive;function o(t){u&&u.finaliseBatch(t)}function c(){return new Promise((t=>{a=h.Stopping,null!==p&&(clearInterval(p),p=null),u&&(u.clean(),u=null),r&&(r.clean(),setTimeout((()=>{r=null}),20)),setTimeout((()=>{a=h.NotActive,t(null)}),100)}))}function g(){[h.Stopped,h.Stopping].includes(a)||(postMessage(\"a_stop\"),c().then((()=>{postMessage(\"a_start\")})))}let l,p=null;self.onmessage=({data:s})=>{if(\"stop\"===s)return o(),void c().then((()=>{a=h.Stopped}));if(\"forceFlushBatch\"!==s)if(\"closing\"!==s){if(!Array.isArray(s)){if(\"compressed\"===s.type){if(!r)return console.debug(\"OR WebWorker: sender not initialised. Compressed batch.\"),void g();s.batch&&r.sendCompressed(s.batch)}if(\"uncompressed\"===s.type){if(!r)return console.debug(\"OR WebWorker: sender not initialised. Uncompressed batch.\"),void g();s.batch&&r.sendUncompressed(s.batch)}return\"start\"===s.type?(a=h.Starting,r=new t(s.ingestPoint,(()=>{g()}),(t=>{!function(t){postMessage({type:\"failure\",reason:t}),c()}(t)}),s.connAttemptCount,s.connAttemptGap,(t=>{postMessage({type:\"compress\",batch:t},[t.buffer])}),s.pageNo),u=new n(s.pageNo,s.timestamp,s.url,((t,s)=>{r&&(s?r.sendUncompressed(t):r.push(t))}),s.tabId,(()=>postMessage({type:\"queue_empty\"}))),null===p&&(p=setInterval(o,3e4)),a=h.Active):\"auth\"===s.type?r?u?(r.authorise(s.token),void(s.beaconSizeLimit&&u.setBeaconSizeLimit(s.beaconSizeLimit))):(console.debug(\"OR WebWorker: writer not initialised. Received auth.\"),void g()):(console.debug(\"OR WebWorker: sender not initialised. Received auth.\"),void g()):void 0}if(u){const t=u;s.forEach((s=>{55===s[0]&&(s[1]?l=setTimeout((()=>g()),18e5):clearTimeout(l)),t.writeMessage(s)}))}else postMessage(\"not_init\"),g()}else o(!0);else o()}}();\n";
4115
+ const workerBodyFn = "!function(){\"use strict\";class t{constructor(t,s,i,e=10,n=250,h,r){this.onUnauthorised=s,this.onFailure=i,this.MAX_ATTEMPTS_COUNT=e,this.ATTEMPT_TIMEOUT=n,this.onCompress=h,this.pageNo=r,this.attemptsCount=0,this.busy=!1,this.queue=[],this.token=null,this.lastBatchNum=0,this.inflightKeepaliveBytes=0,this.ingestURL=t+\"/v1/events\",this.isCompressing=void 0!==h}getQueueStatus(){return 0===this.queue.length&&!this.busy}authorise(t){this.token=t,this.busy||this.sendNext()}push(t,s=\"player\"){if(this.busy||!this.token)this.queue.push({batch:t,dataType:s});else if(this.busy=!0,this.isCompressing&&this.onCompress)this.onCompress(t,s);else{const i=++this.lastBatchNum;this.sendBatch(t,!1,i,s)}}sendNext(){const t=this.queue.shift();if(t)if(this.busy=!0,this.isCompressing&&this.onCompress)this.onCompress(t.batch,t.dataType);else{const s=++this.lastBatchNum;this.sendBatch(t.batch,!1,s,t.dataType)}else this.busy=!1}retry(t,s,i,e=\"player\"){if(this.attemptsCount>=this.MAX_ATTEMPTS_COUNT)return void this.onFailure(`Failed to send batch after ${this.attemptsCount} attempts.`);this.attemptsCount++;const n=new Uint8Array(t);setTimeout((()=>this.sendBatch(n,s,i,e)),this.ATTEMPT_TIMEOUT*this.attemptsCount)}sendBatch(t,s,i,e=\"player\"){var n;if(0===t.length)return console.error(\"OxvoSessions: refusing to send 0-byte batch.\",{batchNum:i,dataType:e,isCompressed:s,batch:t}),this.attemptsCount=0,void this.sendNext();const h=null==i?void 0:i.toString().replace(/^([^_]+)_([^_]+).*/,\"$1_$2_$3\");this.busy=!0;const r={Authorization:`Bearer ${this.token}`,DataType:e};if(s&&(r[\"Content-Encoding\"]=\"gzip\"),null===this.token)return void setTimeout((()=>{this.sendBatch(t,s,`${null!=i?i:\"noBatchNum\"}_newToken`,e)}),500);const a=t.length<65536&&this.inflightKeepaliveBytes+t.length<=65536;a&&(this.inflightKeepaliveBytes+=t.length);const o=()=>{a&&(this.inflightKeepaliveBytes-=t.length)},u=`${null!==(n=this.pageNo)&&void 0!==n?n:\"noPageNum\"}_${null!=h?h:\"noBatchNum\"}`;fetch(`${this.ingestURL}?seq=${u}&keepalive=${a?\"yes\":\"no\"}`,{body:t,method:\"POST\",headers:r,keepalive:a}).then((n=>{var h;if(o(),null===(h=n.body)||void 0===h||h.cancel().catch((()=>{})),401===n.status)return this.busy=!1,void this.onUnauthorised();n.status>=400?this.retry(t,s,`${null!=i?i:\"noBatchNum\"}_network:${n.status}`,e):(this.attemptsCount=0,this.sendNext())})).catch((n=>{o(),console.warn(\"OxvoSessions:\",n),this.retry(t,s,`${null!=i?i:\"noBatchNum\"}_reject:${n.message}`,e)}))}sendCompressed(t,s=\"player\"){const i=++this.lastBatchNum;this.sendBatch(t,!0,i,s)}sendUncompressed(t,s=\"player\"){const i=++this.lastBatchNum;this.sendBatch(t,!1,i,s)}clean(){this.sendNext(),setTimeout((()=>{this.token=null,this.queue.length=0}),10)}}const s=new Set([60,61,71,73]),i=new Set([21,22,40,41,44,45,46,47,48,79,83,84,85,87,89,116,120,121,123]),e=new Set([17,23,24,27,28,29,30,42,63,64,78,112,115,124]),n=\"function\"==typeof TextEncoder?new TextEncoder:{encode(t){const s=t.length,i=new Uint8Array(3*s);let e=-1;for(let n=0,h=0,r=0;r!==s;){if(n=t.charCodeAt(r),r+=1,n>=55296&&n<=56319){if(r===s){i[e+=1]=239,i[e+=1]=191,i[e+=1]=189;break}if(h=t.charCodeAt(r),!(h>=56320&&h<=57343)){i[e+=1]=239,i[e+=1]=191,i[e+=1]=189;continue}if(n=1024*(n-55296)+h-56320+65536,r+=1,n>65535){i[e+=1]=240|n>>>18,i[e+=1]=128|n>>>12&63,i[e+=1]=128|n>>>6&63,i[e+=1]=128|63&n;continue}}n<=127?i[e+=1]=0|n:n<=2047?(i[e+=1]=192|n>>>6,i[e+=1]=128|63&n):(i[e+=1]=224|n>>>12,i[e+=1]=128|n>>>6&63,i[e+=1]=128|63&n)}return i.subarray(0,e+1)}};class h{constructor(t){this.size=t,this.offset=0,this.checkpointOffset=0,this.data=new Uint8Array(t)}getCurrentOffset(){return this.offset}getCurrentCheckpoint(){return this.checkpointOffset}checkpoint(){this.checkpointOffset=this.offset}get isEmpty(){return 0===this.offset}skip(t){return this.offset+=t,this.offset<=this.size}set(t,s){this.data.set(t,s)}boolean(t){return this.data[this.offset++]=+t,this.offset<=this.size}uint(t){for((t<0||t>Number.MAX_SAFE_INTEGER)&&(t=0);t>=128;)this.data[this.offset++]=t%256|128,t=Math.floor(t/128);return this.data[this.offset++]=t,this.offset<=this.size}int(t){return t=Math.round(t),this.uint(t>=0?2*t:-2*t-1)}string(t){const s=n.encode(t),i=s.byteLength;return!(!this.uint(i)||this.offset+i>this.size)&&(this.data.set(s,this.offset),this.offset+=i,!0)}reset(){this.offset=0,this.checkpointOffset=0}rewind(t,s){t>this.offset||s>this.checkpointOffset||(this.offset=t,this.checkpointOffset=s)}flush(){const t=this.data.slice(0,this.checkpointOffset);return this.reset(),t}}class r extends h{encode(t){switch(t[0]){case 0:case 11:case 114:case 115:return this.uint(t[1]);case 4:case 44:case 47:return this.string(t[1])&&this.string(t[2])&&this.uint(t[3]);case 5:case 20:case 65:case 70:case 75:case 76:case 77:return this.uint(t[1])&&this.uint(t[2]);case 6:return this.int(t[1])&&this.int(t[2]);case 7:return!0;case 8:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.string(t[4])&&this.boolean(t[5]);case 9:case 10:case 24:case 35:case 51:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3]);case 12:case 52:case 61:case 71:return this.uint(t[1])&&this.string(t[2])&&this.string(t[3]);case 13:case 14:case 17:case 34:case 36:case 50:case 54:return this.uint(t[1])&&this.string(t[2]);case 16:return this.uint(t[1])&&this.int(t[2])&&this.int(t[3]);case 18:return this.uint(t[1])&&this.string(t[2])&&this.int(t[3]);case 19:return this.uint(t[1])&&this.boolean(t[2]);case 21:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4])&&this.string(t[5])&&this.uint(t[6])&&this.uint(t[7])&&this.uint(t[8]);case 22:case 27:case 30:case 41:case 45:case 46:case 43:case 63:case 64:case 79:case 124:return this.string(t[1])&&this.string(t[2]);case 23:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.uint(t[4])&&this.uint(t[5])&&this.uint(t[6])&&this.uint(t[7])&&this.uint(t[8])&&this.uint(t[9]);case 28:case 29:case 42:case 117:case 118:return this.string(t[1]);case 40:return this.string(t[1])&&this.uint(t[2])&&this.string(t[3])&&this.string(t[4]);case 48:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4])&&this.int(t[5]);case 49:return this.int(t[1])&&this.int(t[2])&&this.uint(t[3])&&this.uint(t[4]);case 53:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.uint(t[4])&&this.uint(t[5])&&this.uint(t[6])&&this.string(t[7])&&this.string(t[8]);case 55:return this.boolean(t[1]);case 57:case 60:return this.uint(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4]);case 58:case 120:return this.int(t[1]);case 68:return this.uint(t[1])&&this.uint(t[2])&&this.string(t[3])&&this.string(t[4])&&this.uint(t[5])&&this.uint(t[6]);case 69:return this.uint(t[1])&&this.uint(t[2])&&this.string(t[3])&&this.string(t[4]);case 73:return this.uint(t[1])&&this.string(t[2])&&this.uint(t[3])&&this.string(t[4]);case 78:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4]);case 81:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.int(t[4])&&this.string(t[5]);case 83:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4])&&this.string(t[5])&&this.uint(t[6])&&this.uint(t[7])&&this.uint(t[8])&&this.uint(t[9]);case 84:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.uint(t[4])&&this.string(t[5])&&this.string(t[6]);case 85:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.uint(t[4])&&this.uint(t[5])&&this.uint(t[6])&&this.string(t[7])&&this.string(t[8])&&this.uint(t[9])&&this.boolean(t[10])&&this.uint(t[11])&&this.uint(t[12])&&this.uint(t[13])&&this.uint(t[14])&&this.uint(t[15])&&this.uint(t[16])&&this.uint(t[17]);case 87:return this.string(t[1])&&this.int(t[2])&&this.int(t[3]);case 89:return this.string(t[1])&&this.int(t[2])&&this.int(t[3])&&this.int(t[4])&&this.int(t[5])&&this.string(t[6]);case 112:return this.uint(t[1])&&this.string(t[2])&&this.boolean(t[3])&&this.string(t[4])&&this.int(t[5])&&this.int(t[6]);case 113:return this.uint(t[1])&&this.uint(t[2])&&this.string(t[3]);case 116:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.uint(t[4])&&this.uint(t[5])&&this.uint(t[6])&&this.string(t[7])&&this.string(t[8])&&this.uint(t[9])&&this.boolean(t[10]);case 119:return this.string(t[1])&&this.uint(t[2]);case 121:return this.string(t[1])&&this.string(t[2])&&this.uint(t[3])&&this.uint(t[4]);case 122:return this.string(t[1])&&this.string(t[2])&&this.uint(t[3])&&this.string(t[4]);case 123:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4])&&this.uint(t[5])}}}class a{constructor(t,s,i){this.bufferSize=t,this.version=s,this.dataType=i,this.sizeBuffer=new Uint8Array(3),this.snap=null,this.hasNonTimestamp=!1,this.lastPushedTs=0,this.encoder=new r(t)}push(t,s){const i=this.encoder,e=null===this.snap,n=i.getCurrentOffset(),h=i.getCurrentCheckpoint();if(e){const t={pageNo:s.pageNo,firstIndex:s.index,timestamp:s.timestamp,url:s.url,tabId:s.tabId};if(!this.writeHeader(t))return i.rewind(n,h),!1;this.snap=t,this.lastPushedTs=s.timestamp}return 0===t[0]||s.timestamp===this.lastPushedTs||this.writeMessageWithSize([0,s.timestamp])?this.writeMessageWithSize(t)?(this.lastPushedTs=s.timestamp,0!==t[0]&&(this.hasNonTimestamp=!0),!0):(i.rewind(n,h),e&&(this.snap=null),!1):(i.rewind(n,h),!1)}hasContent(){return null!==this.snap&&this.hasNonTimestamp}flush(){if(!this.hasContent())return this.reset(),null;const t=this.encoder.flush();return this.snap=null,this.hasNonTimestamp=!1,this.lastPushedTs=0,t}reset(){this.encoder.reset(),this.snap=null,this.hasNonTimestamp=!1,this.lastPushedTs=0}writeHeader(t){const s=this.encoder,i=[81,this.version,t.pageNo,t.firstIndex,t.timestamp,t.url];return!!s.uint(i[0])&&(!!s.encode(i)&&(!(s.getCurrentOffset()>this.bufferSize)&&(s.checkpoint(),!!this.writeMessageWithSize([0,t.timestamp])&&!!this.writeMessageWithSize([118,t.tabId]))))}writeMessageWithSize(t){const s=this.encoder;if(!s.uint(t[0])||!s.skip(3))return!1;const i=s.getCurrentOffset();if(!s.encode(t))return!1;const e=s.getCurrentOffset(),n=e-i;return n>16777215?(console.warn(\"OxvoSessions: max message size overflow.\"),!1):!(e>this.bufferSize)&&(this.writeSizeAt(n,i-3),s.checkpoint(),!0)}writeSizeAt(t,s){for(let s=0;s<3;s++)this.sizeBuffer[s]=t>>8*s;this.encoder.set(this.sizeBuffer,s)}}class o{constructor(t,s,i,e,n,h,r=!1,o){this.pageNo=t,this.timestamp=s,this.url=i,this.onBatch=e,this.tabId=n,this.onOfflineEnd=h,this.localDebug=r,this.onLocalSave=o,this.nextIndex=0,this.beaconSize=2e5,this.beaconSizeLimit=1e6,this.protocolVersion=1,this.playerBuilder=new a(this.beaconSize,this.playerVersion(),\"player\"),this.assetBuilder=new a(this.beaconSize,3,\"assets\"),this.devtoolsBuilder=new a(this.beaconSize,4,\"devtools\"),this.analyticsBuilder=new a(this.beaconSize,5,\"analytics\")}playerVersion(){return 2===this.protocolVersion?2:1}currentCtx(){return{pageNo:this.pageNo,index:this.nextIndex,timestamp:this.timestamp,url:this.url,tabId:this.tabId}}setBeaconSizeLimit(t){this.beaconSizeLimit=t}setProtocolVersion(t){this.protocolVersion!==t&&(this.protocolVersion=t,this.playerBuilder.reset(),this.playerBuilder=new a(this.beaconSize,this.playerVersion(),\"player\"))}writeMessage(t){if(-1===t[0])return this.finaliseBatch(),this.onOfflineEnd();0===t[0]&&(this.timestamp=t[1]),122===t[0]&&(this.url=t[1]);const s=this.routeMessage(t);this.pushTo(s,t)}routeMessage(t){if(2===this.protocolVersion){const n=t[0];if(s.has(n))return this.assetBuilder;if(i.has(n))return this.devtoolsBuilder;if(e.has(n))return this.analyticsBuilder}return this.playerBuilder}pushTo(t,s){const i=this.currentCtx();if(t.push(s,i))return void this.nextIndex++;if(this.flushBuilder(t),t.push(s,i))return void this.nextIndex++;const e=new a(this.beaconSizeLimit,t.version,t.dataType);if(!e.push(s,i))return void console.warn(\"OxvoSessions: beacon size overflow. Skipping large message.\",s);this.nextIndex++;const n=e.flush();n&&this.emitBatch(n,t.dataType,!1)}flushBuilder(t,s=!1){const i=t.flush();return!!i&&(this.emitBatch(i,t.dataType,s),!0)}emitBatch(t,s,i){this.localDebug&&this.onLocalSave&&this.onLocalSave(`${s}-${Date.now()}`,t.slice()),this.onBatch(t,i,s)}finaliseBatch(t=!1){this.flushBuilder(this.playerBuilder,t),this.flushBuilder(this.assetBuilder,t),this.flushBuilder(this.devtoolsBuilder,t),this.flushBuilder(this.analyticsBuilder,t)}clean(){this.playerBuilder.reset(),this.assetBuilder.reset(),this.devtoolsBuilder.reset(),this.analyticsBuilder.reset()}}var u;!function(t){t[t.NotActive=0]=\"NotActive\",t[t.Starting=1]=\"Starting\",t[t.Stopping=2]=\"Stopping\",t[t.Active=3]=\"Active\",t[t.Stopped=4]=\"Stopped\"}(u||(u={}));let c=null,l=null,d=u.NotActive;function p(t){l&&l.finaliseBatch(t)}function f(){return new Promise((t=>{d=u.Stopping,null!==b&&(clearInterval(b),b=null),l&&(l.clean(),l=null),c&&(c.clean(),setTimeout((()=>{c=null}),20)),setTimeout((()=>{d=u.NotActive,t(null)}),100)}))}function g(){[u.Stopped,u.Stopping].includes(d)||(postMessage(\"a_stop\"),f().then((()=>{postMessage(\"a_start\")})))}let m,b=null;self.onmessage=({data:s})=>{var i;if(\"stop\"===s)return p(),void f().then((()=>{d=u.Stopped}));if(\"forceFlushBatch\"!==s)if(\"closing\"!==s){if(!Array.isArray(s)){if(\"compressed\"===s.type){if(!c)return console.debug(\"OR WebWorker: sender not initialised. Compressed batch.\"),void g();s.batch&&c.sendCompressed(s.batch,s.dataType)}if(\"uncompressed\"===s.type){if(!c)return console.debug(\"OR WebWorker: sender not initialised. Uncompressed batch.\"),void g();s.batch&&c.sendUncompressed(s.batch,s.dataType)}return\"start\"===s.type?(d=u.Starting,c=new t(s.ingestPoint,(()=>{g()}),(t=>{!function(t){postMessage({type:\"failure\",reason:t}),f()}(t)}),s.connAttemptCount,s.connAttemptGap,((t,s)=>{postMessage({type:\"compress\",batch:t,dataType:s},[t.buffer])}),s.pageNo),l=new o(s.pageNo,s.timestamp,s.url,((t,s,i=\"player\")=>{c&&(s?c.sendUncompressed(t,i):c.push(t,i))}),s.tabId,(()=>postMessage({type:\"queue_empty\"})),null!==(i=s.localDebug)&&void 0!==i&&i,((t,s)=>{postMessage({type:\"local_save\",name:t,batch:s},[s.buffer])})),null===b&&(b=setInterval(p,3e4)),d=u.Active):\"auth\"===s.type?c?l?(c.authorise(s.token),s.beaconSizeLimit&&l.setBeaconSizeLimit(s.beaconSizeLimit),void(s.protocolVersion&&l.setProtocolVersion(s.protocolVersion))):(console.debug(\"OR WebWorker: writer not initialised. Received auth.\"),void g()):(console.debug(\"OR WebWorker: sender not initialised. Received auth.\"),void g()):void 0}if(l){const t=l;s.forEach((s=>{55===s[0]&&(s[1]?m=setTimeout((()=>g()),18e5):clearTimeout(m)),t.writeMessage(s)}))}else postMessage(\"not_init\"),g()}else p(!0);else p()}}();\n";
4390
4116
  const CANCELED = 'canceled';
4391
4117
  const bufferStorageKey = 'or_buffer_1';
4118
+ const PROTO_VERSION = '2';
4392
4119
  const UnsuccessfulStart = (reason) => ({ reason, success: false });
4393
4120
  const SuccessfulStart = (body) => ({ ...body, success: true });
4394
4121
  var ActivityState;
@@ -4432,9 +4159,14 @@ const proto = {
4432
4159
  reset: 'reset-your-session-please',
4433
4160
  };
4434
4161
  class App {
4162
+ get tagMatcher() {
4163
+ return this.markerWatcher.matcher;
4164
+ }
4435
4165
  constructor(projectKey, sessionToken, options, signalError, insideIframe) {
4436
4166
  this.signalError = signalError;
4437
4167
  this.insideIframe = insideIframe;
4168
+ // Registered by input/img/canvas to re-emit a node when its level changes.
4169
+ this.resanitizeCallbacks = [];
4438
4170
  this.messages = [];
4439
4171
  /**
4440
4172
  * we need 2 buffers, so we don't lose anything
@@ -4446,7 +4178,7 @@ class App {
4446
4178
  this.stopCallbacks = [];
4447
4179
  this.commitCallbacks = [];
4448
4180
  this.activityState = ActivityState.NotActive;
4449
- this.version = '7.1.0'; // TODO: version compatability check inside each plugin.
4181
+ this.version = '7.3.0'; // TODO: version compatability check inside each plugin.
4450
4182
  this.socketMode = false;
4451
4183
  this.compressionThreshold = 24 * 1000;
4452
4184
  this.bc = null;
@@ -4468,9 +4200,19 @@ class App {
4468
4200
  if (!data || event.source === window)
4469
4201
  return;
4470
4202
  if (data.line === proto.startIframe) {
4471
- if (this.active())
4203
+ // Avoid corrupting an in-flight start; let it complete.
4204
+ if (this.activityState === ActivityState.Starting)
4472
4205
  return;
4473
4206
  try {
4207
+ if (this.active()) {
4208
+ this.stop();
4209
+ }
4210
+ if (data.token) {
4211
+ this.session.setSessionSecret(data.token, this.projectKey);
4212
+ }
4213
+ if (data.id !== undefined) {
4214
+ this.rootId = data.id;
4215
+ }
4474
4216
  this.allowAppStart();
4475
4217
  void this.start();
4476
4218
  }
@@ -4496,17 +4238,22 @@ class App {
4496
4238
  }
4497
4239
  }
4498
4240
  };
4499
- /**
4500
- * context ids for iframes,
4501
- * order is not so important as long as its consistent
4502
- * */
4503
4241
  this.trackedFrames = [];
4242
+ this.frameLastSeen = new Map();
4243
+ this.FRAME_STALE_MS = 1500;
4504
4244
  this.crossDomainIframeListener = (event) => {
4505
- if (!this.active() || event.source === window)
4245
+ if (event.source === window)
4506
4246
  return;
4507
4247
  const { data } = event;
4508
4248
  if (!data)
4509
4249
  return;
4250
+ // Record liveness regardless of our own active state so the queue can prune
4251
+ // stale contexts reliably once we resume dispatching commands after a cycle.
4252
+ if ((data.line === proto.polling || data.line === proto.iframeSignal) && data.context) {
4253
+ this.frameLastSeen.set(data.context, Date.now());
4254
+ }
4255
+ if (!this.active())
4256
+ return;
4510
4257
  if (data.line === proto.iframeSignal) {
4511
4258
  // @ts-ignore
4512
4259
  event.source?.postMessage({ ping: true, line: proto.parentAlive }, '*');
@@ -4604,16 +4351,41 @@ class App {
4604
4351
  if (!this.pollingQueue.order.length) {
4605
4352
  return;
4606
4353
  }
4607
- const nextCommand = this.pollingQueue.order[0];
4608
- if (nextCommand && this.pollingQueue[nextCommand].length === 0) {
4609
- this.pollingQueue.order = this.pollingQueue.order.filter((c) => c !== nextCommand);
4354
+ this.pruneStaleFrames();
4355
+ const liveSet = new Set(this.trackedFrames);
4356
+ while (this.pollingQueue.order.length > 0) {
4357
+ const head = this.pollingQueue.order[0];
4358
+ this.pollingQueue[head] = this.pollingQueue[head].filter((ctx) => liveSet.has(ctx));
4359
+ if (this.pollingQueue[head].length === 0) {
4360
+ delete this.pollingQueue[head];
4361
+ this.pollingQueue.order.shift();
4362
+ }
4363
+ else {
4364
+ break;
4365
+ }
4366
+ }
4367
+ if (!this.pollingQueue.order.length) {
4610
4368
  return;
4611
4369
  }
4370
+ const nextCommand = this.pollingQueue.order[0];
4612
4371
  if (this.pollingQueue[nextCommand].includes(data.context)) {
4613
4372
  this.pollingQueue[nextCommand] = this.pollingQueue[nextCommand].filter((c) => c !== data.context);
4373
+ const message = { line: nextCommand };
4374
+ if (nextCommand === proto.startIframe) {
4375
+ message.token = this.session.getSessionSecret(this.projectKey);
4376
+ const targetFrame = this.pageFrames.find((f) => f.contentWindow === event.source) ||
4377
+ Array.from(document.querySelectorAll('iframe')).find((f) => f.contentWindow === event.source);
4378
+ if (targetFrame) {
4379
+ const nodeId = targetFrame[this.options.node_id];
4380
+ if (nodeId !== undefined) {
4381
+ message.id = nodeId;
4382
+ }
4383
+ }
4384
+ }
4614
4385
  // @ts-ignore
4615
- event.source?.postMessage({ line: nextCommand }, '*');
4386
+ event.source?.postMessage(message, '*');
4616
4387
  if (this.pollingQueue[nextCommand].length === 0) {
4388
+ delete this.pollingQueue[nextCommand];
4617
4389
  this.pollingQueue.order.shift();
4618
4390
  }
4619
4391
  }
@@ -4627,6 +4399,11 @@ class App {
4627
4399
  order: [],
4628
4400
  };
4629
4401
  this.addCommand = (cmd) => {
4402
+ this.pruneStaleFrames();
4403
+ if (this.pollingQueue[cmd]) {
4404
+ this.pollingQueue[cmd] = Array.from(new Set([...this.pollingQueue[cmd], ...this.trackedFrames]));
4405
+ return;
4406
+ }
4630
4407
  this.pollingQueue.order.push(cmd);
4631
4408
  this.pollingQueue[cmd] = [...this.trackedFrames];
4632
4409
  };
@@ -4755,6 +4532,26 @@ class App {
4755
4532
  this.restartCanvasTracking = () => {
4756
4533
  this.canvasRecorder?.restartTracking();
4757
4534
  };
4535
+ this.attachResanitizeCallback = (cb) => {
4536
+ this.resanitizeCallbacks.push(cb);
4537
+ };
4538
+ this.callResanitizeCallbacks = (node, id) => {
4539
+ this.resanitizeCallbacks.forEach((cb) => cb(node, id));
4540
+ };
4541
+ this.resanitize = (el) => {
4542
+ const root = el ?? (IN_BROWSER ? document.documentElement : undefined);
4543
+ if (!root) {
4544
+ return;
4545
+ }
4546
+ this.observer.resanitizeSubtree(root);
4547
+ };
4548
+ this.checkSanitization = (el) => {
4549
+ const id = this.nodes.getID(el);
4550
+ if (id === undefined) {
4551
+ return undefined;
4552
+ }
4553
+ return this.sanitizer.getLevel(id);
4554
+ };
4758
4555
  this.flushBuffer = async (buffer) => {
4759
4556
  return new Promise((res, reject) => {
4760
4557
  if (buffer.length === 0) {
@@ -4796,6 +4593,7 @@ class App {
4796
4593
  __is_snippet: false,
4797
4594
  __debug_report_edp: null,
4798
4595
  __debug__: LogLevel.Silent,
4596
+ __local_debug: false,
4799
4597
  localStorage: null,
4800
4598
  sessionStorage: null,
4801
4599
  forceSingleTab: false,
@@ -4854,9 +4652,7 @@ class App {
4854
4652
  });
4855
4653
  this.session.attachUpdateCallback(({ userID, metadata }) => {
4856
4654
  if (userID != null) {
4857
- if (!userID ||
4858
- typeof userID !== 'string' ||
4859
- userID.trim().length === 0) {
4655
+ if (!userID || typeof userID !== 'string' || userID.trim().length === 0) {
4860
4656
  this.debug.warn('Invalid userID (must be type string), ignoring.');
4861
4657
  return;
4862
4658
  }
@@ -4878,9 +4674,6 @@ class App {
4878
4674
  window.addEventListener('message', this.parentCrossDomainFrameListener);
4879
4675
  window.addEventListener('message', this.crossDomainIframeListener);
4880
4676
  setInterval(() => {
4881
- if (document.hidden) {
4882
- return;
4883
- }
4884
4677
  window.parent.postMessage({
4885
4678
  line: proto.polling,
4886
4679
  context: this.contextId,
@@ -4943,6 +4736,16 @@ class App {
4943
4736
  };
4944
4737
  }
4945
4738
  }
4739
+ pruneStaleFrames() {
4740
+ const staleAfter = Date.now() - this.FRAME_STALE_MS;
4741
+ this.trackedFrames = this.trackedFrames.filter((ctx) => {
4742
+ const last = this.frameLastSeen.get(ctx);
4743
+ if (last !== undefined && last >= staleAfter)
4744
+ return true;
4745
+ this.frameLastSeen.delete(ctx);
4746
+ return false;
4747
+ });
4748
+ }
4946
4749
  allowAppStart() {
4947
4750
  this.canStart = true;
4948
4751
  if (this.startTimeout) {
@@ -5042,22 +4845,39 @@ class App {
5042
4845
  }
5043
4846
  else if (data.type === 'compress') {
5044
4847
  const batch = data.batch;
4848
+ const dataType = data.dataType;
5045
4849
  const batchSize = batch.byteLength;
5046
- if (batchSize > this.compressionThreshold) {
5047
- gzip(data.batch, { mtime: 0 }, (err, result) => {
5048
- if (err) {
5049
- this.debug.error('OxvoSessions compression error:', err);
5050
- this.worker?.postMessage({ type: 'uncompressed', batch: batch });
5051
- }
5052
- else {
5053
- this.worker?.postMessage({ type: 'compressed', batch: result });
5054
- }
4850
+ const hasCompressionAPI = 'CompressionStream' in globalThis;
4851
+ if (batchSize > this.compressionThreshold && hasCompressionAPI) {
4852
+ const blob = new Blob([batch]);
4853
+ const stream = blob.stream().pipeThrough(new CompressionStream('gzip'));
4854
+ new Response(stream)
4855
+ .arrayBuffer()
4856
+ .then((compressedBuffer) => {
4857
+ this.worker?.postMessage({
4858
+ type: 'compressed',
4859
+ batch: new Uint8Array(compressedBuffer),
4860
+ dataType,
4861
+ });
4862
+ })
4863
+ .catch((err) => {
4864
+ this.debug.error('OxvoSessions compression error:', err);
4865
+ this.worker?.postMessage({ type: 'uncompressed', batch: batch, dataType });
5055
4866
  });
5056
4867
  }
5057
4868
  else {
5058
- this.worker?.postMessage({ type: 'uncompressed', batch: batch });
4869
+ this.worker?.postMessage({ type: 'uncompressed', batch: batch, dataType });
5059
4870
  }
5060
4871
  }
4872
+ else if (data.type === 'local_save') {
4873
+ const blob = new Blob([data.batch], { type: 'application/octet-stream' });
4874
+ const url = URL.createObjectURL(blob);
4875
+ const a = document.createElement('a');
4876
+ a.href = url;
4877
+ a.download = data.name;
4878
+ a.click();
4879
+ URL.revokeObjectURL(url);
4880
+ }
5061
4881
  else if (data.type === 'queue_empty') {
5062
4882
  this.onSessionSent();
5063
4883
  }
@@ -5096,19 +4916,31 @@ class App {
5096
4916
  this.messages.length = 0;
5097
4917
  return;
5098
4918
  }
5099
- if (this.worker === undefined || !this.messages.length) {
4919
+ if (this.worker === undefined) {
5100
4920
  return;
5101
4921
  }
5102
4922
  if (!this.messages.length) {
5103
- // Release empty batches every 30 secs (1000 * 30ms)
4923
+ // Send a keepalive batch every ~30s (1000 * 30ms) to keep the session active
5104
4924
  if (this.emptyBatchCounter < 1000) {
5105
4925
  this.emptyBatchCounter++;
5106
4926
  return;
5107
4927
  }
4928
+ // Keepalive: send just Timestamp + TabData so the session stays alive
4929
+ this.emptyBatchCounter = 0;
4930
+ try {
4931
+ this.worker?.postMessage([Timestamp(this.timestamp()), TabData(this.session.getTabId())]);
4932
+ }
4933
+ catch (e) {
4934
+ this._debug('worker_keepalive', e);
4935
+ }
4936
+ return;
5108
4937
  }
5109
4938
  this.emptyBatchCounter = 0;
5110
4939
  try {
5111
4940
  requestIdleCb(() => {
4941
+ if (!this.messages.length) {
4942
+ return;
4943
+ }
5112
4944
  this.messages.unshift(Timestamp(this.timestamp()), TabData(this.session.getTabId()));
5113
4945
  this.worker?.postMessage(this.messages);
5114
4946
  this.commitCallbacks.forEach((cb) => cb(this.messages));
@@ -5302,9 +5134,14 @@ class App {
5302
5134
  }
5303
5135
  }
5304
5136
  checkSessionToken(startNewSession) {
5305
- const lsReset = this.sessionStorage.getItem(this.options.session_reset_key) !== null;
5306
- const needNewSessionID = startNewSession || lsReset;
5137
+ const needReset = this.sessionStorage.getItem(this.options.session_reset_key) !== null;
5138
+ let needNewSessionID = startNewSession || needReset;
5307
5139
  const sessionToken = this.session.getSessionSecret(this.projectKey);
5140
+ const storedVersion = this.sessionStorage.getItem(`${this.options.session_token_key}_version`);
5141
+ if (!storedVersion || storedVersion !== PROTO_VERSION) {
5142
+ needNewSessionID = true;
5143
+ this.sessionStorage.setItem(`${this.options.session_token_key}_version`, PROTO_VERSION);
5144
+ }
5308
5145
  return needNewSessionID || !sessionToken;
5309
5146
  }
5310
5147
  /**
@@ -5482,6 +5319,7 @@ class App {
5482
5319
  connAttemptCount: this.options.connAttemptCount,
5483
5320
  connAttemptGap: this.options.connAttemptGap,
5484
5321
  tabId: this.session.getTabId(),
5322
+ localDebug: this.options.__local_debug,
5485
5323
  });
5486
5324
  const ingestPoint = normalizeEndpoint(this.options.endpoint);
5487
5325
  const r = await fetch(ingestPoint + '/v1/bootstrap', {
@@ -5496,7 +5334,7 @@ class App {
5496
5334
  token: '',
5497
5335
  })),
5498
5336
  });
5499
- const { session = {}, client = {}, limits = {} } = await r.json();
5337
+ const { session = {}, client = {}, limits = {}, protocolVersion: offlineProtocolVersion } = await r.json();
5500
5338
  const { token, projectId } = session;
5501
5339
  const { browser: userBrowser, city: userCity, country: userCountry, device: userDevice, os: userOS, state: userState, } = client;
5502
5340
  const { beacon: beaconSizeLimit } = limits;
@@ -5504,6 +5342,7 @@ class App {
5504
5342
  type: 'auth',
5505
5343
  token,
5506
5344
  beaconSizeLimit,
5345
+ protocolVersion: offlineProtocolVersion,
5507
5346
  });
5508
5347
  this.session.assign({ projectID: projectId });
5509
5348
  this.session.setUserInfo({
@@ -5524,6 +5363,9 @@ class App {
5524
5363
  if (Object.keys(startOpts).length !== 0) {
5525
5364
  this.prevOpts = startOpts;
5526
5365
  }
5366
+ if (startOpts.startCallback) {
5367
+ this.userStartCallback = startOpts.startCallback;
5368
+ }
5527
5369
  const isColdStart = this.activityState === ActivityState.ColdStart;
5528
5370
  if (isColdStart && this.coldInterval) {
5529
5371
  clearInterval(this.coldInterval);
@@ -5565,6 +5407,7 @@ class App {
5565
5407
  connAttemptCount: this.options.connAttemptCount,
5566
5408
  connAttemptGap: this.options.connAttemptGap,
5567
5409
  tabId: this.session.getTabId(),
5410
+ localDebug: this.options.__local_debug,
5568
5411
  });
5569
5412
  const sessionToken = this.session.getSessionSecret(this.projectKey);
5570
5413
  const isNewSession = this.checkSessionToken(startOpts.startNewSession);
@@ -5580,7 +5423,7 @@ class App {
5580
5423
  body: JSON.stringify(this.buildStartPayload({
5581
5424
  timestamp,
5582
5425
  dry: false,
5583
- bufferMs: timestamp - this.coldStartTs,
5426
+ bufferMs: this.coldStartTs ? timestamp - this.coldStartTs : 0,
5584
5427
  token: isNewSession ? '' : sessionToken,
5585
5428
  rule: conditionName,
5586
5429
  width: window.screen.width,
@@ -5597,12 +5440,12 @@ class App {
5597
5440
  this.signalError(reason, []);
5598
5441
  return UnsuccessfulStart(reason);
5599
5442
  }
5600
- const { session = {}, device = {}, client = {}, limits = {}, canvas = {}, flags = {} } = await r.json();
5443
+ const { session = {}, device = {}, client = {}, limits = {}, canvas = {}, flags = {}, protocolVersion, } = await r.json();
5601
5444
  const { token, id: sessionID, projectId: projectID, delayMs: delay, startedAt: startTimestamp, } = session;
5602
5445
  const { id: userUUID } = device;
5603
5446
  const { browser: userBrowser, city: userCity, country: userCountry, device: userDevice, os: userOS, state: userState, } = client;
5604
5447
  const { beacon: beaconSizeLimit, compressAt: compressionThreshold } = limits;
5605
- const { enabled: canvasEnabled, quality: canvasQuality, fps: canvasFPS } = canvas;
5448
+ const { enabled: canvasEnabled, quality: canvasQuality, fps: canvasFPS, framesSupport, } = canvas;
5606
5449
  const socketOnly = flags?.socketOnly;
5607
5450
  if (typeof token !== 'string' ||
5608
5451
  typeof userUUID !== 'string' ||
@@ -5644,6 +5487,7 @@ class App {
5644
5487
  type: 'auth',
5645
5488
  token,
5646
5489
  beaconSizeLimit,
5490
+ protocolVersion,
5647
5491
  });
5648
5492
  }
5649
5493
  if (!isNewSession && token === sessionToken) {
@@ -5659,10 +5503,9 @@ class App {
5659
5503
  // TODO: start as early as possible (before receiving the token)
5660
5504
  /** after start */
5661
5505
  this.startCallbacks.forEach((cb) => cb(onStartInfo)); // MBTODO: callbacks after DOM "mounted" (observed)
5662
- if (startOpts.startCallback) {
5663
- startOpts.startCallback(SuccessfulStart(onStartInfo));
5506
+ if (this.userStartCallback) {
5507
+ this.userStartCallback(SuccessfulStart(onStartInfo));
5664
5508
  }
5665
- await this.markerWatcher.fetchTags(normalizeEndpoint(this.options.endpoint), token);
5666
5509
  this.activityState = ActivityState.Active;
5667
5510
  if (this.options.crossdomain?.enabled) {
5668
5511
  void this.bootChildrenFrames();
@@ -5676,6 +5519,7 @@ class App {
5676
5519
  isDebug: this.options.canvas.__save_canvas_locally,
5677
5520
  fixedScaling: this.options.canvas.fixedCanvasScaling,
5678
5521
  useAnimationFrame: this.options.canvas.useAnimationFrame,
5522
+ framesSupport: !!framesSupport,
5679
5523
  });
5680
5524
  }
5681
5525
  /** --------------- COLD START BUFFER ------------------*/
@@ -5698,6 +5542,7 @@ class App {
5698
5542
  }
5699
5543
  this.ticker.start();
5700
5544
  this.canvasRecorder?.startTracking();
5545
+ void this.markerWatcher.fetchTags(normalizeEndpoint(this.options.endpoint), token);
5701
5546
  return SuccessfulStart(onStartInfo);
5702
5547
  }
5703
5548
  catch (reason) {
@@ -5822,6 +5667,7 @@ class App {
5822
5667
  this.canvasRecorder?.clear();
5823
5668
  this.messages.length = 0;
5824
5669
  this.parentActive = false;
5670
+ this.pageFrames = [];
5825
5671
  }
5826
5672
  finally {
5827
5673
  this.activityState = ActivityState.NotActive;
@@ -6319,6 +6165,12 @@ function Img (app) {
6319
6165
  sendImgAttrs(node);
6320
6166
  observer.observe(node, { attributes: true, attributeFilter: ['src', 'srcset'] });
6321
6167
  });
6168
+ // On a runtime level change, re-evaluate placeholder vs real src for this image.
6169
+ app.attachResanitizeCallback((node) => {
6170
+ if (hasTag(node, 'img')) {
6171
+ sendImgAttrs(node);
6172
+ }
6173
+ });
6322
6174
  }
6323
6175
 
6324
6176
  const INPUT_TYPES = [
@@ -6376,19 +6228,24 @@ const labelElementFor = IN_BROWSER && 'labels' in HTMLInputElement.prototype
6376
6228
  }
6377
6229
  }
6378
6230
  };
6379
- function getInputLabel(node) {
6380
- let label = getLabelAttribute(node);
6381
- if (label === null) {
6382
- const labelElement = labelElementFor(node);
6383
- label =
6384
- (labelElement && labelElement.innerText) ||
6385
- node.placeholder ||
6386
- node.name ||
6387
- node.id ||
6388
- node.className ||
6389
- node.type;
6390
- }
6391
- return normSpaces(label).slice(0, 100);
6231
+ function getInputLabel(node, customAttributes) {
6232
+ const oxvosessionsLabel = getLabelAttribute(node);
6233
+ if (oxvosessionsLabel !== null)
6234
+ return normSpaces(oxvosessionsLabel).slice(0, 100);
6235
+ const customAttributeLabel = getCustomAttributeLabel(node, customAttributes);
6236
+ if (customAttributeLabel)
6237
+ return customAttributeLabel;
6238
+ if (node.id)
6239
+ return `#${node.id}`;
6240
+ const classLabel = getClassSelector(node);
6241
+ if (classLabel)
6242
+ return classLabel;
6243
+ const labelElement = labelElementFor(node);
6244
+ const label = node.name ||
6245
+ node.placeholder ||
6246
+ (labelElement && labelElement.innerText) ||
6247
+ node.type;
6248
+ return normSpaces(label || '').slice(0, 100);
6392
6249
  }
6393
6250
  const InputMode = {
6394
6251
  Plain: 0,
@@ -6410,9 +6267,12 @@ function Input (app, opts) {
6410
6267
  }
6411
6268
  else if (app.sanitizer.isObscured(id) ||
6412
6269
  (inputMode === InputMode.Plain &&
6413
- ((options.maskInputNumbers && node.type !== 'date' && /\d\d\d\d/.test(value)) ||
6270
+ ((options.maskInputNumbers &&
6271
+ node.type !== 'date' &&
6272
+ /\d\d\d\d/.test(value)) ||
6414
6273
  (options.maskInputDates && node.type === 'date') ||
6415
- (options.maskInputEmails && (node.type === 'email' || !!~value.indexOf('@')))))) {
6274
+ (options.maskInputEmails &&
6275
+ (node.type === 'email' || !!~value.indexOf('@')))))) {
6416
6276
  inputMode = InputMode.Obscured;
6417
6277
  }
6418
6278
  let mask = 0;
@@ -6432,11 +6292,24 @@ function Input (app, opts) {
6432
6292
  const { value, mask } = getInputValue(id, node);
6433
6293
  app.send(SetInputValue(id, value, mask));
6434
6294
  }
6295
+ const tagSelectorMap = new Map();
6435
6296
  const inputValues = new Map();
6436
6297
  const checkboxValues = new Map();
6298
+ function getTagSelector(id, node) {
6299
+ const cached = tagSelectorMap.get(id);
6300
+ if (cached !== undefined)
6301
+ return cached;
6302
+ const tagMatch = app.tagMatcher.match(node);
6303
+ if (tagMatch) {
6304
+ tagSelectorMap.set(id, tagMatch.selector);
6305
+ return tagMatch.selector;
6306
+ }
6307
+ return null;
6308
+ }
6437
6309
  app.attachStopCallback(() => {
6438
6310
  inputValues.clear();
6439
6311
  checkboxValues.clear();
6312
+ tagSelectorMap.clear();
6440
6313
  });
6441
6314
  function trackInputValue(id, node) {
6442
6315
  if (inputValues.get(id) === node.value) {
@@ -6469,12 +6342,19 @@ function Input (app, opts) {
6469
6342
  }, 3);
6470
6343
  function sendInputChange(id, node, hesitationTime, inputTime) {
6471
6344
  const { value, mask } = getInputValue(id, node);
6472
- let label = getInputLabel(node);
6345
+ let label = getTagSelector(id, node) || getInputLabel(node, options.customAttributes);
6473
6346
  if (app.sanitizer.maskAllByDefault) {
6474
6347
  label = label.replaceAll(/./g, '*');
6475
6348
  }
6476
6349
  app.send(InputChange(id, value, mask !== 0, label, hesitationTime, inputTime));
6477
6350
  }
6351
+ // Re-emit a field's value when its sanitization level changes at runtime.
6352
+ // getInputValue() reads the current level, so re-sending applies the new mask.
6353
+ app.attachResanitizeCallback((node, id) => {
6354
+ if (isTextFieldElement(node) || hasTag(node, 'select')) {
6355
+ sendInputValue(id, node);
6356
+ }
6357
+ });
6478
6358
  app.nodes.attachNodeCallback(app.safe((node) => {
6479
6359
  const id = app.nodes.getID(node);
6480
6360
  if (id === undefined) {
@@ -6578,21 +6458,28 @@ function _getTarget(target, document) {
6578
6458
  return target === document.documentElement ? null : target;
6579
6459
  }
6580
6460
  function Mouse (app, options) {
6581
- const { disableClickmaps = false } = options || {};
6461
+ const { disableClickmaps = false, customAttributes } = options || {};
6582
6462
  function getTargetLabel(target) {
6583
6463
  const dl = getLabelAttribute(target);
6584
6464
  if (dl !== null) {
6585
6465
  return dl;
6586
6466
  }
6587
6467
  if (hasTag(target, 'input')) {
6588
- return getInputLabel(target);
6589
- }
6468
+ return getInputLabel(target, customAttributes);
6469
+ }
6470
+ const customAttributeLabel = getCustomAttributeLabel(target, customAttributes);
6471
+ if (customAttributeLabel)
6472
+ return customAttributeLabel;
6473
+ if (target.id)
6474
+ return `#${target.id}`;
6475
+ const classLabel = getClassSelector(target);
6476
+ if (classLabel)
6477
+ return classLabel;
6590
6478
  if (isClickable(target)) {
6591
6479
  let label = '';
6592
6480
  if (target instanceof HTMLElement) {
6593
6481
  label = app.sanitizer.getInnerTextSecure(target);
6594
6482
  }
6595
- label = label || target.id || target.className;
6596
6483
  return normSpaces(label).slice(0, 100);
6597
6484
  }
6598
6485
  return '';
@@ -6645,7 +6532,13 @@ function Mouse (app, options) {
6645
6532
  };
6646
6533
  const patchDocument = (document, topframe = false) => {
6647
6534
  function getSelector(id, target) {
6648
- return (selectorMap[id] = selectorMap[id] || _getSelector(target));
6535
+ if (selectorMap[id])
6536
+ return selectorMap[id];
6537
+ const tagMatch = app.tagMatcher.match(target);
6538
+ if (tagMatch) {
6539
+ return (selectorMap[id] = tagMatch.selector);
6540
+ }
6541
+ return (selectorMap[id] = _getSelector(target));
6649
6542
  }
6650
6543
  const attachListener = topframe
6651
6544
  ? app.attachEventListener.bind(app) // attached/removed on start/stop
@@ -6736,6 +6629,11 @@ function getCSSPath(el) {
6736
6629
  return false;
6737
6630
  if (el.id)
6738
6631
  return `#${cssEscape(el.id)}`;
6632
+ // if has data attributes - use them as they are more likely to be stable and unique
6633
+ const dataAttr = Array.from(el.attributes).find(attr => attr.name.startsWith('data-'));
6634
+ if (dataAttr) {
6635
+ return `[${dataAttr.name}="${cssEscape(dataAttr.value)}"]`;
6636
+ }
6739
6637
  const parts = [];
6740
6638
  while (el && el.nodeType === 1 && el !== el.ownerDocument) {
6741
6639
  if (el.id) {
@@ -7076,11 +6974,27 @@ function Scroll (app, insideIframe) {
7076
6974
  }, 5, false);
7077
6975
  }
7078
6976
 
6977
+ const NativeURL = URL;
6978
+ const defaultUrlSanitizer = (url) => {
6979
+ const hiddenQueryParams = ['jwt', 'password', 'reset-password', 'invitation', 'secret', 'token'];
6980
+ try {
6981
+ const u = new NativeURL(url);
6982
+ u.searchParams.forEach((value, key) => {
6983
+ if (hiddenQueryParams.includes(key.toLowerCase())) {
6984
+ u.searchParams.set(key, '*'.repeat(value.length));
6985
+ }
6986
+ });
6987
+ return u.toString();
6988
+ }
6989
+ catch {
6990
+ return url;
6991
+ }
6992
+ };
7079
6993
  function Viewport (app, options) {
7080
6994
  let url, width, height;
7081
6995
  let navigationStart;
7082
6996
  let referrer = document.referrer;
7083
- const urlSanitizer = options?.urlSanitizer || ((u) => u);
6997
+ const urlSanitizer = options?.urlSanitizer || defaultUrlSanitizer;
7084
6998
  const titleSanitizer = options?.titleSanitizer || ((t) => t);
7085
6999
  const sendSetPageLocation = app.safe(() => {
7086
7000
  const currURL = document.URL;
@@ -8906,6 +8820,8 @@ function webAnimations(app, options = {}) {
8906
8820
  const lastKF = anim.effect.getKeyframes().at(-1);
8907
8821
  if (!lastKF)
8908
8822
  return;
8823
+ if (!el || !(el instanceof Element) || !el.isConnected)
8824
+ return;
8909
8825
  const computedStyle = getComputedStyle(el);
8910
8826
  const keys = Object.keys(lastKF).filter((p) => !toIgnore.includes(p));
8911
8827
  // @ts-ignore
@@ -9317,7 +9233,7 @@ class ConstantProperties {
9317
9233
  user_id: this.user_id,
9318
9234
  distinct_id: this.deviceId,
9319
9235
  sdk_edition: 'web',
9320
- sdk_version: '7.1.0',
9236
+ sdk_version: '7.3.0',
9321
9237
  timezone: getUTCOffsetString(),
9322
9238
  search_engine: this.searchEngine,
9323
9239
  };
@@ -9705,10 +9621,11 @@ class People {
9705
9621
  * Creates batches of events, then sends them at intervals.
9706
9622
  */
9707
9623
  class Batcher {
9708
- constructor(backendUrl, getToken, init) {
9624
+ constructor(backendUrl, getToken, init, standalone) {
9709
9625
  this.backendUrl = backendUrl;
9710
9626
  this.getToken = getToken;
9711
9627
  this.init = init;
9628
+ this.standalone = standalone;
9712
9629
  this.autosendInterval = 5 * 1000;
9713
9630
  this.retryTimeout = 3 * 1000;
9714
9631
  this.retryLimit = 3;
@@ -9718,10 +9635,20 @@ class Batcher {
9718
9635
  [categories.events]: [],
9719
9636
  };
9720
9637
  this.intervalId = null;
9638
+ this.stopped = false;
9639
+ this.paused = false;
9640
+ this.onVisibilityChange = () => {
9641
+ this.paused = document.hidden;
9642
+ };
9721
9643
  }
9722
9644
  getBatches() {
9723
9645
  this.batch[categories.people] = this.dedupePeopleEvents();
9724
- const finalData = { data: this.batch };
9646
+ const finalData = {
9647
+ data: {
9648
+ [categories.people]: [...this.batch[categories.people]],
9649
+ [categories.events]: [...this.batch[categories.events]],
9650
+ },
9651
+ };
9725
9652
  return finalData;
9726
9653
  }
9727
9654
  addEvent(event) {
@@ -9797,6 +9724,9 @@ class Batcher {
9797
9724
  return Array.from(uniqueEventsByType.values());
9798
9725
  }
9799
9726
  sendBatch(batch) {
9727
+ if (this.stopped) {
9728
+ return;
9729
+ }
9800
9730
  const sentBatch = batch;
9801
9731
  let attempts = 0;
9802
9732
  const send = () => {
@@ -9813,11 +9743,27 @@ class Batcher {
9813
9743
  },
9814
9744
  body: JSON.stringify(sentBatch),
9815
9745
  })
9816
- .then((response) => {
9746
+ .then(async (response) => {
9747
+ if (response.status === 401) {
9748
+ const body = await response.json().catch(() => null);
9749
+ if (!this.standalone && body?.error === 'token expired') {
9750
+ this.stop();
9751
+ return;
9752
+ }
9753
+ if (attempts < this.retryLimit) {
9754
+ return this.init().then(() => {
9755
+ send();
9756
+ });
9757
+ }
9758
+ return;
9759
+ }
9817
9760
  if (response.status === 403) {
9818
- this.init().then(() => {
9819
- send();
9820
- });
9761
+ if (attempts < this.retryLimit) {
9762
+ return this.init().then(() => {
9763
+ send();
9764
+ });
9765
+ }
9766
+ return;
9821
9767
  }
9822
9768
  if (!response.ok) {
9823
9769
  throw new Error(`HTTP error! status: ${response.status}`);
@@ -9832,8 +9778,15 @@ class Batcher {
9832
9778
  void send();
9833
9779
  }
9834
9780
  startAutosend() {
9781
+ if (this.intervalId) {
9782
+ clearInterval(this.intervalId);
9783
+ }
9784
+ this.paused = document.hidden;
9785
+ document.addEventListener('visibilitychange', this.onVisibilityChange);
9835
9786
  this.intervalId = setInterval(() => {
9836
- this.flush();
9787
+ if (!this.paused) {
9788
+ this.flush();
9789
+ }
9837
9790
  }, this.autosendInterval);
9838
9791
  }
9839
9792
  flush() {
@@ -9853,6 +9806,12 @@ class Batcher {
9853
9806
  clearInterval(this.intervalId);
9854
9807
  this.intervalId = null;
9855
9808
  }
9809
+ document.removeEventListener('visibilitychange', this.onVisibilityChange);
9810
+ this.stopped = true;
9811
+ }
9812
+ restart() {
9813
+ this.stopped = false;
9814
+ this.startAutosend();
9856
9815
  }
9857
9816
  }
9858
9817
 
@@ -9884,7 +9843,9 @@ class Analytics {
9884
9843
  if (this.standalone) {
9885
9844
  return this.token;
9886
9845
  }
9887
- return this.getToken();
9846
+ else {
9847
+ return this.getToken();
9848
+ }
9888
9849
  };
9889
9850
  this._getTimestamp = () => {
9890
9851
  if (this.standalone) {
@@ -9938,6 +9899,19 @@ class Analytics {
9938
9899
  }
9939
9900
  }
9940
9901
  };
9902
+ /**
9903
+ * Used by tracker when running in blundled mode
9904
+ */
9905
+ this.onStart = () => {
9906
+ if (!this.standalone) {
9907
+ this.batcher.restart();
9908
+ }
9909
+ };
9910
+ this.onStop = () => {
9911
+ if (!this.standalone) {
9912
+ this.batcher.stop();
9913
+ }
9914
+ };
9941
9915
  this.reset = () => {
9942
9916
  this.people.reset(true);
9943
9917
  this.events.reset();
@@ -9976,7 +9950,7 @@ class Analytics {
9976
9950
  this.standalone = !options.notStandalone;
9977
9951
  this.token = this.sessionStorage.getItem(STORAGEKEY);
9978
9952
  this.constantProperties = new ConstantProperties(this.localStorage, this.sessionStorage);
9979
- this.batcher = new Batcher(this.backendUrl, this._getToken, this.init);
9953
+ this.batcher = new Batcher(this.backendUrl, this._getToken, this.init, this.standalone);
9980
9954
  this.events = new Events(this.constantProperties, this._getTimestamp, this.batcher);
9981
9955
  this.people = new People(this.constantProperties, this._getTimestamp, this.setUserId, this.batcher);
9982
9956
  if (options.notStandalone) {
@@ -10029,7 +10003,7 @@ class API {
10029
10003
  this.signalStartIssue = (reason, missingApi) => {
10030
10004
  const doNotTrack = this.checkDoNotTrack();
10031
10005
  console.log("Tracker couldn't start due to:", JSON.stringify({
10032
- trackerVersion: '7.1.0',
10006
+ trackerVersion: '7.3.0',
10033
10007
  siteKey: this.options.siteKey,
10034
10008
  doNotTrack,
10035
10009
  reason: missingApi.length ? `missing api: ${missingApi.join(',')}` : reason,
@@ -10041,6 +10015,31 @@ class API {
10041
10015
  }
10042
10016
  this.app.restartCanvasTracking();
10043
10017
  };
10018
+ /**
10019
+ * Re-evaluates sanitization against the current DOM and re-emits whatever
10020
+ * changed, updating already-recorded nodes mid-session. Call after toggling
10021
+ * `data-oxvosessions-*` attributes or after changing whatever your `domSanitizer`
10022
+ * keys on (class/id/etc).
10023
+ *
10024
+ * @param el - the highest node you changed; omit to re-scan the whole document;
10025
+ * scanning the entire doc is O(dom size)
10026
+ * */
10027
+ this.resanitize = (el) => {
10028
+ if (this.app === null) {
10029
+ return;
10030
+ }
10031
+ this.app.resanitize(el);
10032
+ };
10033
+ /**
10034
+ * Returns the sanitization level the tracker currently has for a node
10035
+ * (0 = Plain, 1 = Obscured, 2 = Hidden), or undefined if it isn't tracked.
10036
+ * */
10037
+ this.checkSanitization = (el) => {
10038
+ if (this.app === null) {
10039
+ return undefined;
10040
+ }
10041
+ return this.app.checkSanitization(el);
10042
+ };
10044
10043
  this.getSessionLink = (options) => {
10045
10044
  if (this.app === null) {
10046
10045
  return undefined;
@@ -10183,6 +10182,12 @@ class API {
10183
10182
  : (options.analytics?.endpoint ?? options.endpoint ?? defaultEdp),
10184
10183
  projectKey: options.siteKey,
10185
10184
  });
10185
+ app.attachStartCallback(() => {
10186
+ this.analytics?.onStart();
10187
+ });
10188
+ app.attachStopCallback(() => {
10189
+ this.analytics?.onStop();
10190
+ });
10186
10191
  }
10187
10192
  if (!this.crossdomainMode) {
10188
10193
  // no need to send iframe viewport data since its a node for us
@@ -10194,7 +10199,7 @@ class API {
10194
10199
  // no tabs in iframes yet
10195
10200
  Tabs(app);
10196
10201
  }
10197
- Mouse(app, options.mouse);
10202
+ Mouse(app, { ...options.mouse, customAttributes: options.customAttributes });
10198
10203
  // inside iframe, we ignore viewport scroll
10199
10204
  Scroll(app, this.crossdomainMode);
10200
10205
  CSSRules(app, options.css);