@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/lib/entry.js CHANGED
@@ -1,716 +1,3 @@
1
- // DEFLATE is a complex format; to read this code, you should probably check the RFC first:
2
- // https://tools.ietf.org/html/rfc1951
3
- // You may also wish to take a look at the guide I made about this program:
4
- // https://gist.github.com/101arrowz/253f31eb5abc3d9275ab943003ffecad
5
- // Some of the following code is similar to that of UZIP.js:
6
- // https://github.com/photopea/UZIP.js
7
- // However, the vast majority of the codebase has diverged from UZIP.js to increase performance and reduce bundle size.
8
- // Sometimes 0 will appear where -1 would be more appropriate. This is because using a uint
9
- // is better for memory in most engines (I *think*).
10
- var ch2 = {};
11
- var wk = (function (c, id, msg, transfer, cb) {
12
- var w = new Worker(ch2[id] || (ch2[id] = URL.createObjectURL(new Blob([
13
- c + ';addEventListener("error",function(e){e=e.error;postMessage({$e$:[e.message,e.code,e.stack]})})'
14
- ], { type: 'text/javascript' }))));
15
- w.onmessage = function (e) {
16
- var d = e.data, ed = d.$e$;
17
- if (ed) {
18
- var err = new Error(ed[0]);
19
- err['code'] = ed[1];
20
- err.stack = ed[2];
21
- cb(err, null);
22
- }
23
- else
24
- cb(null, d);
25
- };
26
- w.postMessage(msg, transfer);
27
- return w;
28
- });
29
-
30
- // aliases for shorter compressed code (most minifers don't do this)
31
- var u8 = Uint8Array, u16 = Uint16Array, i32 = Int32Array;
32
- // fixed length extra bits
33
- var fleb = new u8([0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, /* unused */ 0, 0, /* impossible */ 0]);
34
- // fixed distance extra bits
35
- var fdeb = new u8([0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, /* unused */ 0, 0]);
36
- // code length index map
37
- var clim = new u8([16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]);
38
- // get base, reverse index map from extra bits
39
- var freb = function (eb, start) {
40
- var b = new u16(31);
41
- for (var i = 0; i < 31; ++i) {
42
- b[i] = start += 1 << eb[i - 1];
43
- }
44
- // numbers here are at max 18 bits
45
- var r = new i32(b[30]);
46
- for (var i = 1; i < 30; ++i) {
47
- for (var j = b[i]; j < b[i + 1]; ++j) {
48
- r[j] = ((j - b[i]) << 5) | i;
49
- }
50
- }
51
- return { b: b, r: r };
52
- };
53
- var _a = freb(fleb, 2), fl = _a.b, revfl = _a.r;
54
- // we can ignore the fact that the other numbers are wrong; they never happen anyway
55
- fl[28] = 258, revfl[258] = 28;
56
- var _b = freb(fdeb, 0), revfd = _b.r;
57
- // map of value to reverse (assuming 16 bits)
58
- var rev = new u16(32768);
59
- for (var i$1 = 0; i$1 < 32768; ++i$1) {
60
- // reverse table algorithm from SO
61
- var x$1 = ((i$1 & 0xAAAA) >> 1) | ((i$1 & 0x5555) << 1);
62
- x$1 = ((x$1 & 0xCCCC) >> 2) | ((x$1 & 0x3333) << 2);
63
- x$1 = ((x$1 & 0xF0F0) >> 4) | ((x$1 & 0x0F0F) << 4);
64
- rev[i$1] = (((x$1 & 0xFF00) >> 8) | ((x$1 & 0x00FF) << 8)) >> 1;
65
- }
66
- // create huffman tree from u8 "map": index -> code length for code index
67
- // mb (max bits) must be at most 15
68
- // TODO: optimize/split up?
69
- var hMap = (function (cd, mb, r) {
70
- var s = cd.length;
71
- // index
72
- var i = 0;
73
- // u16 "map": index -> # of codes with bit length = index
74
- var l = new u16(mb);
75
- // length of cd must be 288 (total # of codes)
76
- for (; i < s; ++i) {
77
- if (cd[i])
78
- ++l[cd[i] - 1];
79
- }
80
- // u16 "map": index -> minimum code for bit length = index
81
- var le = new u16(mb);
82
- for (i = 1; i < mb; ++i) {
83
- le[i] = (le[i - 1] + l[i - 1]) << 1;
84
- }
85
- var co;
86
- if (r) {
87
- // u16 "map": index -> number of actual bits, symbol for code
88
- co = new u16(1 << mb);
89
- // bits to remove for reverser
90
- var rvb = 15 - mb;
91
- for (i = 0; i < s; ++i) {
92
- // ignore 0 lengths
93
- if (cd[i]) {
94
- // num encoding both symbol and bits read
95
- var sv = (i << 4) | cd[i];
96
- // free bits
97
- var r_1 = mb - cd[i];
98
- // start value
99
- var v = le[cd[i] - 1]++ << r_1;
100
- // m is end value
101
- for (var m = v | ((1 << r_1) - 1); v <= m; ++v) {
102
- // every 16 bit value starting with the code yields the same result
103
- co[rev[v] >> rvb] = sv;
104
- }
105
- }
106
- }
107
- }
108
- else {
109
- co = new u16(s);
110
- for (i = 0; i < s; ++i) {
111
- if (cd[i]) {
112
- co[i] = rev[le[cd[i] - 1]++] >> (15 - cd[i]);
113
- }
114
- }
115
- }
116
- return co;
117
- });
118
- // fixed length tree
119
- var flt = new u8(288);
120
- for (var i$1 = 0; i$1 < 144; ++i$1)
121
- flt[i$1] = 8;
122
- for (var i$1 = 144; i$1 < 256; ++i$1)
123
- flt[i$1] = 9;
124
- for (var i$1 = 256; i$1 < 280; ++i$1)
125
- flt[i$1] = 7;
126
- for (var i$1 = 280; i$1 < 288; ++i$1)
127
- flt[i$1] = 8;
128
- // fixed distance tree
129
- var fdt = new u8(32);
130
- for (var i$1 = 0; i$1 < 32; ++i$1)
131
- fdt[i$1] = 5;
132
- // fixed length map
133
- var flm = /*#__PURE__*/ hMap(flt, 9, 0);
134
- // fixed distance map
135
- var fdm = /*#__PURE__*/ hMap(fdt, 5, 0);
136
- // get end of byte
137
- var shft = function (p) { return ((p + 7) / 8) | 0; };
138
- // typed array slice - allows garbage collector to free original reference,
139
- // while being more compatible than .slice
140
- var slc = function (v, s, e) {
141
- if (s == null || s < 0)
142
- s = 0;
143
- if (e == null || e > v.length)
144
- e = v.length;
145
- // can't use .constructor in case user-supplied
146
- return new u8(v.subarray(s, e));
147
- };
148
- // error codes
149
- var ec = [
150
- 'unexpected EOF',
151
- 'invalid block type',
152
- 'invalid length/literal',
153
- 'invalid distance',
154
- 'stream finished',
155
- 'no stream handler',
156
- ,
157
- 'no callback',
158
- 'invalid UTF-8 data',
159
- 'extra field too long',
160
- 'date not in range 1980-2099',
161
- 'filename too long',
162
- 'stream finishing',
163
- 'invalid zip data'
164
- // determined by unknown compression method
165
- ];
166
- var err = function (ind, msg, nt) {
167
- var e = new Error(msg || ec[ind]);
168
- e.code = ind;
169
- if (Error.captureStackTrace)
170
- Error.captureStackTrace(e, err);
171
- if (!nt)
172
- throw e;
173
- return e;
174
- };
175
- // starting at p, write the minimum number of bits that can hold v to d
176
- var wbits = function (d, p, v) {
177
- v <<= p & 7;
178
- var o = (p / 8) | 0;
179
- d[o] |= v;
180
- d[o + 1] |= v >> 8;
181
- };
182
- // starting at p, write the minimum number of bits (>8) that can hold v to d
183
- var wbits16 = function (d, p, v) {
184
- v <<= p & 7;
185
- var o = (p / 8) | 0;
186
- d[o] |= v;
187
- d[o + 1] |= v >> 8;
188
- d[o + 2] |= v >> 16;
189
- };
190
- // creates code lengths from a frequency table
191
- var hTree = function (d, mb) {
192
- // Need extra info to make a tree
193
- var t = [];
194
- for (var i = 0; i < d.length; ++i) {
195
- if (d[i])
196
- t.push({ s: i, f: d[i] });
197
- }
198
- var s = t.length;
199
- var t2 = t.slice();
200
- if (!s)
201
- return { t: et, l: 0 };
202
- if (s == 1) {
203
- var v = new u8(t[0].s + 1);
204
- v[t[0].s] = 1;
205
- return { t: v, l: 1 };
206
- }
207
- t.sort(function (a, b) { return a.f - b.f; });
208
- // after i2 reaches last ind, will be stopped
209
- // freq must be greater than largest possible number of symbols
210
- t.push({ s: -1, f: 25001 });
211
- var l = t[0], r = t[1], i0 = 0, i1 = 1, i2 = 2;
212
- t[0] = { s: -1, f: l.f + r.f, l: l, r: r };
213
- // efficient algorithm from UZIP.js
214
- // i0 is lookbehind, i2 is lookahead - after processing two low-freq
215
- // symbols that combined have high freq, will start processing i2 (high-freq,
216
- // non-composite) symbols instead
217
- // see https://reddit.com/r/photopea/comments/ikekht/uzipjs_questions/
218
- while (i1 != s - 1) {
219
- l = t[t[i0].f < t[i2].f ? i0++ : i2++];
220
- r = t[i0 != i1 && t[i0].f < t[i2].f ? i0++ : i2++];
221
- t[i1++] = { s: -1, f: l.f + r.f, l: l, r: r };
222
- }
223
- var maxSym = t2[0].s;
224
- for (var i = 1; i < s; ++i) {
225
- if (t2[i].s > maxSym)
226
- maxSym = t2[i].s;
227
- }
228
- // code lengths
229
- var tr = new u16(maxSym + 1);
230
- // max bits in tree
231
- var mbt = ln(t[i1 - 1], tr, 0);
232
- if (mbt > mb) {
233
- // more algorithms from UZIP.js
234
- // TODO: find out how this code works (debt)
235
- // ind debt
236
- var i = 0, dt = 0;
237
- // left cost
238
- var lft = mbt - mb, cst = 1 << lft;
239
- t2.sort(function (a, b) { return tr[b.s] - tr[a.s] || a.f - b.f; });
240
- for (; i < s; ++i) {
241
- var i2_1 = t2[i].s;
242
- if (tr[i2_1] > mb) {
243
- dt += cst - (1 << (mbt - tr[i2_1]));
244
- tr[i2_1] = mb;
245
- }
246
- else
247
- break;
248
- }
249
- dt >>= lft;
250
- while (dt > 0) {
251
- var i2_2 = t2[i].s;
252
- if (tr[i2_2] < mb)
253
- dt -= 1 << (mb - tr[i2_2]++ - 1);
254
- else
255
- ++i;
256
- }
257
- for (; i >= 0 && dt; --i) {
258
- var i2_3 = t2[i].s;
259
- if (tr[i2_3] == mb) {
260
- --tr[i2_3];
261
- ++dt;
262
- }
263
- }
264
- mbt = mb;
265
- }
266
- return { t: new u8(tr), l: mbt };
267
- };
268
- // get the max length and assign length codes
269
- var ln = function (n, l, d) {
270
- return n.s == -1
271
- ? Math.max(ln(n.l, l, d + 1), ln(n.r, l, d + 1))
272
- : (l[n.s] = d);
273
- };
274
- // length codes generation
275
- var lc = function (c) {
276
- var s = c.length;
277
- // Note that the semicolon was intentional
278
- while (s && !c[--s])
279
- ;
280
- var cl = new u16(++s);
281
- // ind num streak
282
- var cli = 0, cln = c[0], cls = 1;
283
- var w = function (v) { cl[cli++] = v; };
284
- for (var i = 1; i <= s; ++i) {
285
- if (c[i] == cln && i != s)
286
- ++cls;
287
- else {
288
- if (!cln && cls > 2) {
289
- for (; cls > 138; cls -= 138)
290
- w(32754);
291
- if (cls > 2) {
292
- w(cls > 10 ? ((cls - 11) << 5) | 28690 : ((cls - 3) << 5) | 12305);
293
- cls = 0;
294
- }
295
- }
296
- else if (cls > 3) {
297
- w(cln), --cls;
298
- for (; cls > 6; cls -= 6)
299
- w(8304);
300
- if (cls > 2)
301
- w(((cls - 3) << 5) | 8208), cls = 0;
302
- }
303
- while (cls--)
304
- w(cln);
305
- cls = 1;
306
- cln = c[i];
307
- }
308
- }
309
- return { c: cl.subarray(0, cli), n: s };
310
- };
311
- // calculate the length of output from tree, code lengths
312
- var clen = function (cf, cl) {
313
- var l = 0;
314
- for (var i = 0; i < cl.length; ++i)
315
- l += cf[i] * cl[i];
316
- return l;
317
- };
318
- // writes a fixed block
319
- // returns the new bit pos
320
- var wfblk = function (out, pos, dat) {
321
- // no need to write 00 as type: TypedArray defaults to 0
322
- var s = dat.length;
323
- var o = shft(pos + 2);
324
- out[o] = s & 255;
325
- out[o + 1] = s >> 8;
326
- out[o + 2] = out[o] ^ 255;
327
- out[o + 3] = out[o + 1] ^ 255;
328
- for (var i = 0; i < s; ++i)
329
- out[o + i + 4] = dat[i];
330
- return (o + 4 + s) * 8;
331
- };
332
- // writes a block
333
- var wblk = function (dat, out, final, syms, lf, df, eb, li, bs, bl, p) {
334
- wbits(out, p++, final);
335
- ++lf[256];
336
- var _a = hTree(lf, 15), dlt = _a.t, mlb = _a.l;
337
- var _b = hTree(df, 15), ddt = _b.t, mdb = _b.l;
338
- var _c = lc(dlt), lclt = _c.c, nlc = _c.n;
339
- var _d = lc(ddt), lcdt = _d.c, ndc = _d.n;
340
- var lcfreq = new u16(19);
341
- for (var i = 0; i < lclt.length; ++i)
342
- ++lcfreq[lclt[i] & 31];
343
- for (var i = 0; i < lcdt.length; ++i)
344
- ++lcfreq[lcdt[i] & 31];
345
- var _e = hTree(lcfreq, 7), lct = _e.t, mlcb = _e.l;
346
- var nlcc = 19;
347
- for (; nlcc > 4 && !lct[clim[nlcc - 1]]; --nlcc)
348
- ;
349
- var flen = (bl + 5) << 3;
350
- var ftlen = clen(lf, flt) + clen(df, fdt) + eb;
351
- var dtlen = clen(lf, dlt) + clen(df, ddt) + eb + 14 + 3 * nlcc + clen(lcfreq, lct) + 2 * lcfreq[16] + 3 * lcfreq[17] + 7 * lcfreq[18];
352
- if (bs >= 0 && flen <= ftlen && flen <= dtlen)
353
- return wfblk(out, p, dat.subarray(bs, bs + bl));
354
- var lm, ll, dm, dl;
355
- wbits(out, p, 1 + (dtlen < ftlen)), p += 2;
356
- if (dtlen < ftlen) {
357
- lm = hMap(dlt, mlb, 0), ll = dlt, dm = hMap(ddt, mdb, 0), dl = ddt;
358
- var llm = hMap(lct, mlcb, 0);
359
- wbits(out, p, nlc - 257);
360
- wbits(out, p + 5, ndc - 1);
361
- wbits(out, p + 10, nlcc - 4);
362
- p += 14;
363
- for (var i = 0; i < nlcc; ++i)
364
- wbits(out, p + 3 * i, lct[clim[i]]);
365
- p += 3 * nlcc;
366
- var lcts = [lclt, lcdt];
367
- for (var it = 0; it < 2; ++it) {
368
- var clct = lcts[it];
369
- for (var i = 0; i < clct.length; ++i) {
370
- var len = clct[i] & 31;
371
- wbits(out, p, llm[len]), p += lct[len];
372
- if (len > 15)
373
- wbits(out, p, (clct[i] >> 5) & 127), p += clct[i] >> 12;
374
- }
375
- }
376
- }
377
- else {
378
- lm = flm, ll = flt, dm = fdm, dl = fdt;
379
- }
380
- for (var i = 0; i < li; ++i) {
381
- var sym = syms[i];
382
- if (sym > 255) {
383
- var len = (sym >> 18) & 31;
384
- wbits16(out, p, lm[len + 257]), p += ll[len + 257];
385
- if (len > 7)
386
- wbits(out, p, (sym >> 23) & 31), p += fleb[len];
387
- var dst = sym & 31;
388
- wbits16(out, p, dm[dst]), p += dl[dst];
389
- if (dst > 3)
390
- wbits16(out, p, (sym >> 5) & 8191), p += fdeb[dst];
391
- }
392
- else {
393
- wbits16(out, p, lm[sym]), p += ll[sym];
394
- }
395
- }
396
- wbits16(out, p, lm[256]);
397
- return p + ll[256];
398
- };
399
- // deflate options (nice << 13) | chain
400
- var deo = /*#__PURE__*/ new i32([65540, 131080, 131088, 131104, 262176, 1048704, 1048832, 2114560, 2117632]);
401
- // empty
402
- var et = /*#__PURE__*/ new u8(0);
403
- // compresses data into a raw DEFLATE buffer
404
- var dflt = function (dat, lvl, plvl, pre, post, st) {
405
- var s = st.z || dat.length;
406
- var o = new u8(pre + s + 5 * (1 + Math.ceil(s / 7000)) + post);
407
- // writing to this writes to the output buffer
408
- var w = o.subarray(pre, o.length - post);
409
- var lst = st.l;
410
- var pos = (st.r || 0) & 7;
411
- if (lvl) {
412
- if (pos)
413
- w[0] = st.r >> 3;
414
- var opt = deo[lvl - 1];
415
- var n = opt >> 13, c = opt & 8191;
416
- var msk_1 = (1 << plvl) - 1;
417
- // prev 2-byte val map curr 2-byte val map
418
- var prev = st.p || new u16(32768), head = st.h || new u16(msk_1 + 1);
419
- var bs1_1 = Math.ceil(plvl / 3), bs2_1 = 2 * bs1_1;
420
- var hsh = function (i) { return (dat[i] ^ (dat[i + 1] << bs1_1) ^ (dat[i + 2] << bs2_1)) & msk_1; };
421
- // 24576 is an arbitrary number of maximum symbols per block
422
- // 424 buffer for last block
423
- var syms = new i32(25000);
424
- // length/literal freq distance freq
425
- var lf = new u16(288), df = new u16(32);
426
- // l/lcnt exbits index l/lind waitdx blkpos
427
- var lc_1 = 0, eb = 0, i = st.i || 0, li = 0, wi = st.w || 0, bs = 0;
428
- for (; i + 2 < s; ++i) {
429
- // hash value
430
- var hv = hsh(i);
431
- // index mod 32768 previous index mod
432
- var imod = i & 32767, pimod = head[hv];
433
- prev[imod] = pimod;
434
- head[hv] = imod;
435
- // We always should modify head and prev, but only add symbols if
436
- // this data is not yet processed ("wait" for wait index)
437
- if (wi <= i) {
438
- // bytes remaining
439
- var rem = s - i;
440
- if ((lc_1 > 7000 || li > 24576) && (rem > 423 || !lst)) {
441
- pos = wblk(dat, w, 0, syms, lf, df, eb, li, bs, i - bs, pos);
442
- li = lc_1 = eb = 0, bs = i;
443
- for (var j = 0; j < 286; ++j)
444
- lf[j] = 0;
445
- for (var j = 0; j < 30; ++j)
446
- df[j] = 0;
447
- }
448
- // len dist chain
449
- var l = 2, d = 0, ch_1 = c, dif = imod - pimod & 32767;
450
- if (rem > 2 && hv == hsh(i - dif)) {
451
- var maxn = Math.min(n, rem) - 1;
452
- var maxd = Math.min(32767, i);
453
- // max possible length
454
- // not capped at dif because decompressors implement "rolling" index population
455
- var ml = Math.min(258, rem);
456
- while (dif <= maxd && --ch_1 && imod != pimod) {
457
- if (dat[i + l] == dat[i + l - dif]) {
458
- var nl = 0;
459
- for (; nl < ml && dat[i + nl] == dat[i + nl - dif]; ++nl)
460
- ;
461
- if (nl > l) {
462
- l = nl, d = dif;
463
- // break out early when we reach "nice" (we are satisfied enough)
464
- if (nl > maxn)
465
- break;
466
- // now, find the rarest 2-byte sequence within this
467
- // length of literals and search for that instead.
468
- // Much faster than just using the start
469
- var mmd = Math.min(dif, nl - 2);
470
- var md = 0;
471
- for (var j = 0; j < mmd; ++j) {
472
- var ti = i - dif + j & 32767;
473
- var pti = prev[ti];
474
- var cd = ti - pti & 32767;
475
- if (cd > md)
476
- md = cd, pimod = ti;
477
- }
478
- }
479
- }
480
- // check the previous match
481
- imod = pimod, pimod = prev[imod];
482
- dif += imod - pimod & 32767;
483
- }
484
- }
485
- // d will be nonzero only when a match was found
486
- if (d) {
487
- // store both dist and len data in one int32
488
- // Make sure this is recognized as a len/dist with 28th bit (2^28)
489
- syms[li++] = 268435456 | (revfl[l] << 18) | revfd[d];
490
- var lin = revfl[l] & 31, din = revfd[d] & 31;
491
- eb += fleb[lin] + fdeb[din];
492
- ++lf[257 + lin];
493
- ++df[din];
494
- wi = i + l;
495
- ++lc_1;
496
- }
497
- else {
498
- syms[li++] = dat[i];
499
- ++lf[dat[i]];
500
- }
501
- }
502
- }
503
- for (i = Math.max(i, wi); i < s; ++i) {
504
- syms[li++] = dat[i];
505
- ++lf[dat[i]];
506
- }
507
- pos = wblk(dat, w, lst, syms, lf, df, eb, li, bs, i - bs, pos);
508
- if (!lst) {
509
- st.r = (pos & 7) | w[(pos / 8) | 0] << 3;
510
- // shft(pos) now 1 less if pos & 7 != 0
511
- pos -= 7;
512
- st.h = head, st.p = prev, st.i = i, st.w = wi;
513
- }
514
- }
515
- else {
516
- for (var i = st.w || 0; i < s + lst; i += 65535) {
517
- // end
518
- var e = i + 65535;
519
- if (e >= s) {
520
- // write final block
521
- w[(pos / 8) | 0] = lst;
522
- e = s;
523
- }
524
- pos = wfblk(w, pos + 1, dat.subarray(i, e));
525
- }
526
- st.i = s;
527
- }
528
- return slc(o, 0, pre + shft(pos) + post);
529
- };
530
- // CRC32 table
531
- var crct = /*#__PURE__*/ (function () {
532
- var t = new Int32Array(256);
533
- for (var i = 0; i < 256; ++i) {
534
- var c = i, k = 9;
535
- while (--k)
536
- c = ((c & 1) && -306674912) ^ (c >>> 1);
537
- t[i] = c;
538
- }
539
- return t;
540
- })();
541
- // CRC32
542
- var crc = function () {
543
- var c = -1;
544
- return {
545
- p: function (d) {
546
- // closures have awful performance
547
- var cr = c;
548
- for (var i = 0; i < d.length; ++i)
549
- cr = crct[(cr & 255) ^ d[i]] ^ (cr >>> 8);
550
- c = cr;
551
- },
552
- d: function () { return ~c; }
553
- };
554
- };
555
- // deflate with opts
556
- var dopt = function (dat, opt, pre, post, st) {
557
- if (!st) {
558
- st = { l: 1 };
559
- if (opt.dictionary) {
560
- var dict = opt.dictionary.subarray(-32768);
561
- var newDat = new u8(dict.length + dat.length);
562
- newDat.set(dict);
563
- newDat.set(dat, dict.length);
564
- dat = newDat;
565
- st.w = dict.length;
566
- }
567
- }
568
- return dflt(dat, opt.level == null ? 6 : opt.level, opt.mem == null ? (st.l ? Math.ceil(Math.max(8, Math.min(13, Math.log(dat.length))) * 1.5) : 20) : (12 + opt.mem), pre, post, st);
569
- };
570
- // Walmart object spread
571
- var mrg = function (a, b) {
572
- var o = {};
573
- for (var k in a)
574
- o[k] = a[k];
575
- for (var k in b)
576
- o[k] = b[k];
577
- return o;
578
- };
579
- // worker clone
580
- // This is possibly the craziest part of the entire codebase, despite how simple it may seem.
581
- // The only parameter to this function is a closure that returns an array of variables outside of the function scope.
582
- // We're going to try to figure out the variable names used in the closure as strings because that is crucial for workerization.
583
- // We will return an object mapping of true variable name to value (basically, the current scope as a JS object).
584
- // The reason we can't just use the original variable names is minifiers mangling the toplevel scope.
585
- // This took me three weeks to figure out how to do.
586
- var wcln = function (fn, fnStr, td) {
587
- var dt = fn();
588
- var st = fn.toString();
589
- var ks = st.slice(st.indexOf('[') + 1, st.lastIndexOf(']')).replace(/\s+/g, '').split(',');
590
- for (var i = 0; i < dt.length; ++i) {
591
- var v = dt[i], k = ks[i];
592
- if (typeof v == 'function') {
593
- fnStr += ';' + k + '=';
594
- var st_1 = v.toString();
595
- if (v.prototype) {
596
- // for global objects
597
- if (st_1.indexOf('[native code]') != -1) {
598
- var spInd = st_1.indexOf(' ', 8) + 1;
599
- fnStr += st_1.slice(spInd, st_1.indexOf('(', spInd));
600
- }
601
- else {
602
- fnStr += st_1;
603
- for (var t in v.prototype)
604
- fnStr += ';' + k + '.prototype.' + t + '=' + v.prototype[t].toString();
605
- }
606
- }
607
- else
608
- fnStr += st_1;
609
- }
610
- else
611
- td[k] = v;
612
- }
613
- return fnStr;
614
- };
615
- var ch = [];
616
- // clone bufs
617
- var cbfs = function (v) {
618
- var tl = [];
619
- for (var k in v) {
620
- if (v[k].buffer) {
621
- tl.push((v[k] = new v[k].constructor(v[k])).buffer);
622
- }
623
- }
624
- return tl;
625
- };
626
- // use a worker to execute code
627
- var wrkr = function (fns, init, id, cb) {
628
- if (!ch[id]) {
629
- var fnStr = '', td_1 = {}, m = fns.length - 1;
630
- for (var i = 0; i < m; ++i)
631
- fnStr = wcln(fns[i], fnStr, td_1);
632
- ch[id] = { c: wcln(fns[m], fnStr, td_1), e: td_1 };
633
- }
634
- var td = mrg({}, ch[id].e);
635
- return wk(ch[id].c + ';onmessage=function(e){for(var k in e.data)self[k]=e.data[k];onmessage=' + init.toString() + '}', id, td, cbfs(td), cb);
636
- };
637
- var bDflt = function () { return [u8, u16, i32, fleb, fdeb, clim, revfl, revfd, flm, flt, fdm, fdt, rev, deo, et, hMap, wbits, wbits16, hTree, ln, lc, clen, wfblk, wblk, shft, slc, dflt, dopt, deflateSync, pbf]; };
638
- // gzip extra
639
- var gze = function () { return [gzh, gzhl, wbytes, crc, crct]; };
640
- // post buf
641
- var pbf = function (msg) { return postMessage(msg, [msg.buffer]); };
642
- // async helper
643
- var cbify = function (dat, opts, fns, init, id, cb) {
644
- var w = wrkr(fns, init, id, function (err, dat) {
645
- w.terminate();
646
- cb(err, dat);
647
- });
648
- w.postMessage([dat, opts], opts.consume ? [dat.buffer] : []);
649
- return function () { w.terminate(); };
650
- };
651
- // write bytes
652
- var wbytes = function (d, b, v) {
653
- for (; v; ++b)
654
- d[b] = v, v >>>= 8;
655
- };
656
- // gzip header
657
- var gzh = function (c, o) {
658
- var fn = o.filename;
659
- c[0] = 31, c[1] = 139, c[2] = 8, c[8] = o.level < 2 ? 4 : o.level == 9 ? 2 : 0, c[9] = 3; // assume Unix
660
- if (o.mtime != 0)
661
- wbytes(c, 4, Math.floor(new Date(o.mtime || Date.now()) / 1000));
662
- if (fn) {
663
- c[3] = 8;
664
- for (var i = 0; i <= fn.length; ++i)
665
- c[i + 10] = fn.charCodeAt(i);
666
- }
667
- };
668
- // gzip header length
669
- var gzhl = function (o) { return 10 + (o.filename ? o.filename.length + 1 : 0); };
670
- /**
671
- * Compresses data with DEFLATE without any wrapper
672
- * @param data The data to compress
673
- * @param opts The compression options
674
- * @returns The deflated version of the data
675
- */
676
- function deflateSync(data, opts) {
677
- return dopt(data, opts || {}, 0, 0);
678
- }
679
- function gzip(data, opts, cb) {
680
- if (!cb)
681
- cb = opts, opts = {};
682
- if (typeof cb != 'function')
683
- err(7);
684
- return cbify(data, opts, [
685
- bDflt,
686
- gze,
687
- function () { return [gzipSync]; }
688
- ], function (ev) { return pbf(gzipSync(ev.data[0], ev.data[1])); }, 2, cb);
689
- }
690
- /**
691
- * Compresses data with GZIP
692
- * @param data The data to compress
693
- * @param opts The compression options
694
- * @returns The gzipped version of the data
695
- */
696
- function gzipSync(data, opts) {
697
- if (!opts)
698
- opts = {};
699
- var c = crc(), l = data.length;
700
- c.p(data);
701
- var d = dopt(data, opts, gzhl(opts), 8), s = d.length;
702
- return gzh(d, opts), wbytes(d, s - 8, c.d()), wbytes(d, s - 4, l), d;
703
- }
704
- // text decoder
705
- var td = typeof TextDecoder != 'undefined' && /*#__PURE__*/ new TextDecoder();
706
- // text decoder stream
707
- var tds = 0;
708
- try {
709
- td.decode(et, { stream: true });
710
- tds = 1;
711
- }
712
- catch (e) { }
713
-
714
1
  class StringDictionary {
715
2
  constructor() {
716
3
  this.lastTs = 0;
@@ -1161,6 +448,23 @@ function deprecationWarn(nameOfFeature, useInstead, docsPath = '/') {
1161
448
  console.warn(`OxvoSessions: ${nameOfFeature} is deprecated. ${useInstead ? `Please, use ${useInstead} instead.` : ''} Visit ${DOCS_HOST}${docsPath} for more information.`);
1162
449
  warnedFeatures[nameOfFeature] = true;
1163
450
  }
451
+ function getCustomAttributeLabel(e, customAttributes) {
452
+ if (!customAttributes || customAttributes.length === 0)
453
+ return '';
454
+ const parts = [];
455
+ for (const attr of customAttributes) {
456
+ const value = e.getAttribute(attr);
457
+ if (value !== null) {
458
+ parts.push(`[${attr}=${value}]`);
459
+ }
460
+ }
461
+ return parts.join('');
462
+ }
463
+ function getClassSelector(e) {
464
+ if (!e.classList || e.classList.length === 0)
465
+ return '';
466
+ return '.' + Array.from(e.classList).join('.');
467
+ }
1164
468
  function getLabelAttribute(e) {
1165
469
  let value = e.getAttribute('data-oxvosessions-label');
1166
470
  if (value !== null) {
@@ -1917,13 +1221,6 @@ function BatchMetadata(version, pageNo, firstIndex, timestamp, location) {
1917
1221
  location,
1918
1222
  ];
1919
1223
  }
1920
- function PartitionedMessage(partNo, partTotal) {
1921
- return [
1922
- 82 /* Messages.Type.PartitionedMessage */,
1923
- partNo,
1924
- partTotal,
1925
- ];
1926
- }
1927
1224
  function NetworkRequest(type, method, url, request, response, status, timestamp, duration, transferredBodySize) {
1928
1225
  return [
1929
1226
  83 /* Messages.Type.NetworkRequest */,
@@ -2135,7 +1432,6 @@ var _Messages = /*#__PURE__*/Object.freeze({
2135
1432
  OTable: OTable,
2136
1433
  PageLoadTiming: PageLoadTiming,
2137
1434
  PageRenderTiming: PageRenderTiming,
2138
- PartitionedMessage: PartitionedMessage,
2139
1435
  PerformanceTrack: PerformanceTrack,
2140
1436
  Profiler: Profiler,
2141
1437
  Redux: Redux,
@@ -2230,11 +1526,157 @@ function Performance (app, opts) {
2230
1526
  }
2231
1527
  }
2232
1528
 
1529
+ /**
1530
+ * Two-tier tag matching:
1531
+ * 1. Fast fingerprint lookup by id, data-attr,
1532
+ * or class from the selector's last segment
1533
+ * 2. Fallback iteration using native element.matches()
1534
+ */
1535
+ class TagMatcher {
1536
+ constructor() {
1537
+ this.tags = [];
1538
+ this.byId = new Map();
1539
+ this.byDataAttr = new Map();
1540
+ this.byClass = new Map();
1541
+ this.fallback = [];
1542
+ }
1543
+ setTags(tags) {
1544
+ this.tags = tags;
1545
+ this.byId.clear();
1546
+ this.byDataAttr.clear();
1547
+ this.byClass.clear();
1548
+ this.fallback = [];
1549
+ for (const tag of tags) {
1550
+ const last = lastSegment(tag.selector);
1551
+ if (!last) {
1552
+ this.fallback.push(tag);
1553
+ continue;
1554
+ }
1555
+ if (last.startsWith('#')) {
1556
+ this.byId.set(last.slice(1), tag);
1557
+ }
1558
+ else if (last.startsWith('[data-')) {
1559
+ this.byDataAttr.set(last, tag);
1560
+ }
1561
+ else {
1562
+ const cls = extractClass(last);
1563
+ if (cls) {
1564
+ this.byClass.set(cls, tag);
1565
+ }
1566
+ else {
1567
+ this.fallback.push(tag);
1568
+ }
1569
+ }
1570
+ }
1571
+ }
1572
+ getTags() {
1573
+ return this.tags;
1574
+ }
1575
+ /** Match element, its parent, or direct children against known tag selectors */
1576
+ match(el) {
1577
+ const direct = this.matchExact(el);
1578
+ if (direct)
1579
+ return direct;
1580
+ if (el.parentElement) {
1581
+ const parent = this.matchExact(el.parentElement);
1582
+ if (parent)
1583
+ return parent;
1584
+ }
1585
+ const children = el.children;
1586
+ for (let i = 0; i < children.length; i++) {
1587
+ const child = this.matchExact(children[i]);
1588
+ if (child)
1589
+ return child;
1590
+ }
1591
+ return null;
1592
+ }
1593
+ matchExact(el) {
1594
+ if (el.id && this.byId.has(el.id)) {
1595
+ const tag = this.byId.get(el.id);
1596
+ if (safeMatches(el, tag.selector) && matchesLocation(tag))
1597
+ return tag;
1598
+ }
1599
+ if (this.byDataAttr.size > 0) {
1600
+ const attrs = el.attributes;
1601
+ for (let i = 0; i < attrs.length; i++) {
1602
+ const attr = attrs[i];
1603
+ if (attr.name.startsWith('data-')) {
1604
+ const key = `[${attr.name}="${attr.value}"]`;
1605
+ if (this.byDataAttr.has(key)) {
1606
+ const tag = this.byDataAttr.get(key);
1607
+ if (safeMatches(el, tag.selector) && matchesLocation(tag))
1608
+ return tag;
1609
+ }
1610
+ }
1611
+ }
1612
+ }
1613
+ if (this.byClass.size > 0 && el.classList) {
1614
+ for (let i = 0; i < el.classList.length; i++) {
1615
+ const cls = el.classList[i];
1616
+ if (this.byClass.has(cls)) {
1617
+ const tag = this.byClass.get(cls);
1618
+ if (safeMatches(el, tag.selector) && matchesLocation(tag))
1619
+ return tag;
1620
+ }
1621
+ }
1622
+ }
1623
+ for (const tag of this.fallback) {
1624
+ if (safeMatches(el, tag.selector) && matchesLocation(tag))
1625
+ return tag;
1626
+ }
1627
+ return null;
1628
+ }
1629
+ clear() {
1630
+ this.tags = [];
1631
+ this.byId.clear();
1632
+ this.byDataAttr.clear();
1633
+ this.byClass.clear();
1634
+ this.fallback = [];
1635
+ }
1636
+ }
1637
+ /** Last combinator-separated segment of a CSS selector */
1638
+ function lastSegment(selector) {
1639
+ const trimmed = selector.trim();
1640
+ if (!trimmed)
1641
+ return null;
1642
+ const parts = trimmed.split(/\s*[>+~ ]\s*/);
1643
+ const last = parts[parts.length - 1]?.trim();
1644
+ return last || null;
1645
+ }
1646
+ /** First class name from a selector segment, e.g. "div.my-class" -> "my-class" */
1647
+ function extractClass(segment) {
1648
+ const match = segment.match(/\.([a-zA-Z_-][\w-]*)/);
1649
+ return match ? match[1] : null;
1650
+ }
1651
+ function safeMatches(el, selector) {
1652
+ try {
1653
+ return el.matches(selector);
1654
+ }
1655
+ catch {
1656
+ return false;
1657
+ }
1658
+ }
1659
+ function matchesLocation(tag) {
1660
+ if (!tag.location)
1661
+ return true;
1662
+ try {
1663
+ const loc = tag.location;
1664
+ if (loc.startsWith('/')) {
1665
+ return window.location.pathname === loc;
1666
+ }
1667
+ return window.location.href === loc;
1668
+ }
1669
+ catch {
1670
+ return true;
1671
+ }
1672
+ }
1673
+
2233
1674
  const WATCHED_MARKERS_KEY = '__or__watched_markers__';
2234
1675
  class MarkerWatcher {
2235
1676
  constructor(params) {
2236
1677
  this.interval = null;
2237
1678
  this.tags = [];
1679
+ this.matcher = new TagMatcher();
2238
1680
  this.sessionStorage = params.sessionStorage;
2239
1681
  this.errLog = params.errLog;
2240
1682
  this.onMarkerHit = params.onMarkerHit;
@@ -2277,12 +1719,15 @@ class MarkerWatcher {
2277
1719
  }
2278
1720
  setTags(tags) {
2279
1721
  this.tags = tags;
1722
+ this.matcher.setTags(tags);
2280
1723
  if (this.interval) {
2281
1724
  clearInterval(this.interval);
2282
1725
  this.interval = null;
2283
1726
  }
2284
1727
  this.interval = setInterval(() => {
2285
1728
  this.tags.forEach((tag) => {
1729
+ if (!matchesLocation(tag))
1730
+ return;
2286
1731
  const possibleEls = document.querySelectorAll(tag.selector);
2287
1732
  if (possibleEls.length > 0) {
2288
1733
  const el = possibleEls[0];
@@ -2294,13 +1739,17 @@ class MarkerWatcher {
2294
1739
  }, 500);
2295
1740
  }
2296
1741
  onMarkerRendered(markerId) {
2297
- if (this.tags.findIndex(t => t.id === markerId)) {
2298
- this.tags = this.tags.filter((tag) => tag.id !== markerId);
1742
+ const tag = this.tags.find((t) => t.id === markerId);
1743
+ if (tag) {
1744
+ this.tags = this.tags.filter((t) => t.id !== markerId);
1745
+ if (!matchesLocation(tag))
1746
+ return;
2299
1747
  }
2300
1748
  this.onMarkerHit(markerId);
2301
1749
  }
2302
1750
  clear() {
2303
1751
  this.tags = [];
1752
+ this.matcher.clear();
2304
1753
  if (this.interval) {
2305
1754
  clearInterval(this.interval);
2306
1755
  this.interval = null;
@@ -2351,6 +1800,30 @@ class CanvasRecorder {
2351
1800
  this.MAX_QUEUE_SIZE = 50; // ~500 images max (50 batches × 10 images)
2352
1801
  this.pendingBatches = [];
2353
1802
  this.isProcessingQueue = false;
1803
+ /**
1804
+ * Reacts to a runtime sanitization change on a canvas: stop capturing if it
1805
+ * just became masked, start if it just became visible. Already-sent frames
1806
+ * cannot be retracted, so escalation only stops future capture.
1807
+ */
1808
+ this.resanitizeCanvas = (node, id) => {
1809
+ if (!hasTag(node, 'canvas')) {
1810
+ return;
1811
+ }
1812
+ const isIgnored = this.app.sanitizer.isObscured(id) || this.app.sanitizer.isHidden(id);
1813
+ if (isIgnored) {
1814
+ if (this.snapshots[id] || this.observers.has(id)) {
1815
+ const observer = this.observers.get(id);
1816
+ if (observer) {
1817
+ observer.disconnect();
1818
+ this.observers.delete(id);
1819
+ }
1820
+ this.cleanupCanvas(id);
1821
+ }
1822
+ }
1823
+ else if (!this.snapshots[id] && !this.observers.has(id)) {
1824
+ this.captureCanvas(node);
1825
+ }
1826
+ };
2354
1827
  this.restartTracking = () => {
2355
1828
  this.clear();
2356
1829
  this.app.nodes.scanTree(this.captureCanvas);
@@ -2447,14 +1920,15 @@ class CanvasRecorder {
2447
1920
  }, this.interval);
2448
1921
  this.intervals.set(id, int);
2449
1922
  };
2450
- this.fileExt = options.fileExt ?? 'webp';
1923
+ this.fileExt = 'webp';
2451
1924
  this.interval = 1000 / options.fps;
2452
1925
  }
2453
1926
  startTracking() {
2454
1927
  setTimeout(() => {
2455
1928
  this.app.nodes.scanTree(this.captureCanvas);
2456
1929
  this.app.nodes.attachNodeCallback(this.captureCanvas);
2457
- }, 250);
1930
+ this.app.attachResanitizeCallback(this.resanitizeCanvas);
1931
+ }, 125);
2458
1932
  }
2459
1933
  sendSnaps(images, canvasId, createdAt) {
2460
1934
  if (Object.keys(this.snapshots).length === 0) {
@@ -2483,17 +1957,73 @@ class CanvasRecorder {
2483
1957
  }
2484
1958
  this.isProcessingQueue = false;
2485
1959
  }
2486
- uploadBatch(images, canvasId, createdAt) {
2487
- const formData = new FormData();
2488
- images.forEach((snapshot) => {
2489
- const blob = snapshot.data;
2490
- if (!blob)
2491
- return;
2492
- formData.append('snapshot', blob, `${createdAt}_${canvasId}_${snapshot.id}.${this.fileExt}`);
2493
- if (this.options.isDebug) {
2494
- saveImageData(blob, `${createdAt}_${canvasId}_${snapshot.id}.${this.fileExt}`);
1960
+ async uploadBatch(images, canvasId, createdAt) {
1961
+ if (this.options.isDebug) {
1962
+ const packed = await packFrames(images);
1963
+ if (packed) {
1964
+ const fileName = `${createdAt}_${canvasId}.${this.fileExt}.frames`;
1965
+ const url = URL.createObjectURL(new Blob([packed]));
1966
+ const link = document.createElement('a');
1967
+ link.href = url;
1968
+ link.download = fileName;
1969
+ link.style.display = 'none';
1970
+ document.body.appendChild(link);
1971
+ link.click();
1972
+ document.body.removeChild(link);
1973
+ URL.revokeObjectURL(url);
1974
+ }
1975
+ // fall through to also send to backend
1976
+ }
1977
+ let formData;
1978
+ if (this.options.framesSupport) {
1979
+ // Pack frames into binary format: [uint64 LE timestamp][uint32 LE size][data] per frame
1980
+ const buffers = [];
1981
+ let totalSize = 0;
1982
+ for (const snapshot of images) {
1983
+ if (!snapshot.data)
1984
+ continue;
1985
+ const ab = await snapshot.data.arrayBuffer();
1986
+ buffers.push(ab);
1987
+ totalSize += 8 + 4 + ab.byteLength; // uint64 ts + uint32 size + data
2495
1988
  }
2496
- });
1989
+ if (totalSize === 0)
1990
+ return;
1991
+ const packed = new ArrayBuffer(totalSize);
1992
+ const view = new DataView(packed);
1993
+ const bytes = new Uint8Array(packed);
1994
+ let offset = 0;
1995
+ for (let i = 0; i < images.length; i++) {
1996
+ if (!images[i].data)
1997
+ continue;
1998
+ const ab = buffers.shift();
1999
+ const ts = images[i].id;
2000
+ // uint64 LE as two uint32 LE writes -- timestamp
2001
+ view.setUint32(offset, ts % 0x100000000, true);
2002
+ view.setUint32(offset + 4, Math.floor(ts / 0x100000000), true);
2003
+ offset += 8;
2004
+ // uint32 LE -- size
2005
+ view.setUint32(offset, ab.byteLength, true);
2006
+ offset += 4;
2007
+ // image data
2008
+ bytes.set(new Uint8Array(ab), offset);
2009
+ offset += ab.byteLength;
2010
+ }
2011
+ formData = new FormData();
2012
+ formData.append('type', 'frames');
2013
+ const fileName = `${createdAt}_${canvasId}.${this.fileExt}.frames`;
2014
+ formData.append('frames', new Blob([packed]), fileName);
2015
+ }
2016
+ else {
2017
+ // Legacy: send individual image files
2018
+ formData = new FormData();
2019
+ images.forEach((snapshot) => {
2020
+ const blob = snapshot.data;
2021
+ if (!blob)
2022
+ return;
2023
+ const name = `${createdAt}_${canvasId}_${snapshot.id}.${this.fileExt}`;
2024
+ formData.append('snapshot', blob, name);
2025
+ });
2026
+ }
2497
2027
  const initRestart = () => {
2498
2028
  this.app.debug.log('Restarting tracker; token expired');
2499
2029
  this.app.stop(false);
@@ -2589,15 +2119,36 @@ function captureSnapshot(canvas, quality = 'medium', dummy, fixedScaling = false
2589
2119
  canvas.toBlob(onBlob, imageFormat, qualityInt[quality]);
2590
2120
  }
2591
2121
  }
2592
- function saveImageData(imageDataBlob, name) {
2593
- const imageDataUrl = URL.createObjectURL(imageDataBlob);
2594
- const link = document.createElement('a');
2595
- link.href = imageDataUrl;
2596
- link.download = name;
2597
- link.style.display = 'none';
2598
- document.body.appendChild(link);
2599
- link.click();
2600
- document.body.removeChild(link);
2122
+ async function packFrames(images) {
2123
+ const buffers = [];
2124
+ let totalSize = 0;
2125
+ for (const snapshot of images) {
2126
+ if (!snapshot.data)
2127
+ continue;
2128
+ const ab = await snapshot.data.arrayBuffer();
2129
+ buffers.push(ab);
2130
+ totalSize += 8 + 4 + ab.byteLength;
2131
+ }
2132
+ if (totalSize === 0)
2133
+ return null;
2134
+ const packed = new ArrayBuffer(totalSize);
2135
+ const view = new DataView(packed);
2136
+ const bytes = new Uint8Array(packed);
2137
+ let offset = 0;
2138
+ for (let i = 0; i < images.length; i++) {
2139
+ if (!images[i].data)
2140
+ continue;
2141
+ const ab = buffers.shift();
2142
+ const ts = images[i].id;
2143
+ view.setUint32(offset, ts % 0x100000000, true);
2144
+ view.setUint32(offset + 4, Math.floor(ts / 0x100000000), true);
2145
+ offset += 8;
2146
+ view.setUint32(offset, ab.byteLength, true);
2147
+ offset += 4;
2148
+ bytes.set(new Uint8Array(ab), offset);
2149
+ offset += ab.byteLength;
2150
+ }
2151
+ return packed;
2601
2152
  }
2602
2153
 
2603
2154
  const LogLevel = {
@@ -2878,8 +2429,44 @@ function inlineRemoteCss(node, id, baseHref, getNextID, insertRule, addOwner, fo
2878
2429
  })
2879
2430
  .catch(error => {
2880
2431
  console.error(`OxvoSessions: Failed to fetch CSS from ${node.href}:`, error);
2432
+ // Fetch failed, likely due to CORS. Retry via load event and sheet access.
2433
+ retryViaLoadEvent();
2881
2434
  });
2882
2435
  }
2436
+ else {
2437
+ retryViaLoadEvent();
2438
+ }
2439
+ function retryViaLoadEvent() {
2440
+ const trySheet = () => {
2441
+ const loadedSheet = node.sheet;
2442
+ if (loadedSheet) {
2443
+ try {
2444
+ const cssText = stringifyStylesheet(loadedSheet);
2445
+ if (cssText) {
2446
+ if (sendPlain && onPlain) {
2447
+ onPlain(cssText, fakeIdHolder++);
2448
+ }
2449
+ else {
2450
+ processCssText(cssText);
2451
+ }
2452
+ return;
2453
+ }
2454
+ }
2455
+ catch (e) {
2456
+ // Cross-origin sheet. Nothing more can be read safely here.
2457
+ }
2458
+ }
2459
+ };
2460
+ if (node.sheet) {
2461
+ trySheet();
2462
+ }
2463
+ else {
2464
+ node.addEventListener('load', function onLoad() {
2465
+ node.removeEventListener('load', onLoad);
2466
+ trySheet();
2467
+ });
2468
+ }
2469
+ }
2883
2470
  function processCssText(cssText) {
2884
2471
  // Remove comments
2885
2472
  cssText = cssText.replace(/\/\*[\s\S]*?\*\//g, '');
@@ -3101,7 +2688,6 @@ function ConstructedStyleSheets (app) {
3101
2688
  if (!hasAdoptedSS(document)) {
3102
2689
  return;
3103
2690
  }
3104
- const styleSheetIDMap = new Map();
3105
2691
  const adoptedStyleSheetsOwnings = new Map();
3106
2692
  const sendAdoptedStyleSheetsUpdate = (root) => setTimeout(() => {
3107
2693
  let nodeID = app.nodes.getID(root);
@@ -3215,6 +2801,119 @@ function ConstructedStyleSheets (app) {
3215
2801
  });
3216
2802
  }
3217
2803
 
2804
+ var SanitizeLevel;
2805
+ (function (SanitizeLevel) {
2806
+ SanitizeLevel[SanitizeLevel["Plain"] = 0] = "Plain";
2807
+ SanitizeLevel[SanitizeLevel["Obscured"] = 1] = "Obscured";
2808
+ SanitizeLevel[SanitizeLevel["Hidden"] = 2] = "Hidden";
2809
+ })(SanitizeLevel || (SanitizeLevel = {}));
2810
+ const stringWiper = (input) => input
2811
+ .trim()
2812
+ .replace(/[^\f\n\r\t\v\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff\s]/g, '*');
2813
+ class Sanitizer {
2814
+ constructor(params) {
2815
+ // Node id -> level. Plain (0) is never stored; a missing entry means Plain.
2816
+ // A map (not the old grow-only Sets) so levels can be raised and lowered.
2817
+ this.levels = new Map();
2818
+ this.app = params.app;
2819
+ const defaultOptions = {
2820
+ maskTextEmails: true,
2821
+ maskTextNumbers: false,
2822
+ maskAllByDefault: false,
2823
+ domSanitizer: undefined,
2824
+ };
2825
+ this.maskAllByDefault = params.options?.maskAllByDefault ?? false;
2826
+ this.options = Object.assign(defaultOptions, params.options);
2827
+ }
2828
+ // Pure recomputation of a node's level from the live DOM + parent level.
2829
+ // Reading current state on every call lets resanitize() pick up runtime changes.
2830
+ computeLevel(node, parentLevel) {
2831
+ if (this.options.maskAllByDefault) {
2832
+ if (isElementNode(node) && !hasOxvoSessionsAttribute(node, 'unmask')) {
2833
+ return SanitizeLevel.Obscured;
2834
+ }
2835
+ if (isTextNode(node) && !hasOxvoSessionsAttribute(node.parentNode, 'unmask')) {
2836
+ return SanitizeLevel.Obscured;
2837
+ }
2838
+ }
2839
+ let level = SanitizeLevel.Plain;
2840
+ if (parentLevel >= SanitizeLevel.Obscured ||
2841
+ (isElementNode(node) &&
2842
+ (hasOxvoSessionsAttribute(node, 'masked') || hasOxvoSessionsAttribute(node, 'obscured')))) {
2843
+ level = SanitizeLevel.Obscured;
2844
+ }
2845
+ if (parentLevel === SanitizeLevel.Hidden ||
2846
+ (isElementNode(node) &&
2847
+ (hasOxvoSessionsAttribute(node, 'htmlmasked') || hasOxvoSessionsAttribute(node, 'hidden')))) {
2848
+ level = SanitizeLevel.Hidden;
2849
+ }
2850
+ if (this.options.domSanitizer !== undefined && isElementNode(node)) {
2851
+ const sanitizeLevel = this.options.domSanitizer(node);
2852
+ if (sanitizeLevel === SanitizeLevel.Obscured && level < SanitizeLevel.Obscured) {
2853
+ level = SanitizeLevel.Obscured;
2854
+ }
2855
+ if (sanitizeLevel === SanitizeLevel.Hidden) {
2856
+ level = SanitizeLevel.Hidden;
2857
+ }
2858
+ }
2859
+ return level;
2860
+ }
2861
+ getLevel(id) {
2862
+ return this.levels.get(id) ?? SanitizeLevel.Plain;
2863
+ }
2864
+ // Sets a node's level in either direction and returns the previous level.
2865
+ setLevel(id, level) {
2866
+ const prev = this.getLevel(id);
2867
+ if (level === SanitizeLevel.Plain) {
2868
+ this.levels.delete(id);
2869
+ }
2870
+ else {
2871
+ this.levels.set(id, level);
2872
+ }
2873
+ return prev;
2874
+ }
2875
+ handleNode(id, parentID, node) {
2876
+ const level = this.computeLevel(node, this.getLevel(parentID));
2877
+ // Escalate-only: commits never lower a level, only resanitize/setLevel do.
2878
+ if (level > this.getLevel(id)) {
2879
+ this.setLevel(id, level);
2880
+ }
2881
+ }
2882
+ sanitize(id, data) {
2883
+ if (this.getLevel(id) >= SanitizeLevel.Obscured) {
2884
+ // TODO: is it the best place to put trim() ? Might trimmed spaces be considered in layout in certain cases?
2885
+ return stringWiper(data);
2886
+ }
2887
+ if (this.options.maskTextNumbers) {
2888
+ data = data.replace(/\d/g, '0');
2889
+ }
2890
+ if (this.options.maskTextEmails) {
2891
+ data = data.replace(/^\w+([+.-]\w+)*@\w+([.-]\w+)*\.\w{2,3}$/g, (email) => {
2892
+ const [name, domain] = email.split('@');
2893
+ const [domainName, host] = domain.split('.');
2894
+ return `${stars(name)}@${stars(domainName)}.${stars(host)}`;
2895
+ });
2896
+ }
2897
+ return data;
2898
+ }
2899
+ isObscured(id) {
2900
+ return this.getLevel(id) >= SanitizeLevel.Obscured;
2901
+ }
2902
+ isHidden(id) {
2903
+ return this.getLevel(id) === SanitizeLevel.Hidden;
2904
+ }
2905
+ getInnerTextSecure(el) {
2906
+ const id = this.app.nodes.getID(el);
2907
+ if (!id) {
2908
+ return '';
2909
+ }
2910
+ return this.sanitize(id, el.innerText);
2911
+ }
2912
+ clear() {
2913
+ this.levels.clear();
2914
+ }
2915
+ }
2916
+
3218
2917
  const iconCache = {};
3219
2918
  const svgUrlCache = {};
3220
2919
  async function parseUseEl(useElement, mode, domParser) {
@@ -3366,6 +3065,12 @@ class Observer {
3366
3065
  this.inlineRemoteCss = false;
3367
3066
  this.inlinerOptions = undefined;
3368
3067
  this.domParser = new DOMParser();
3068
+ /**
3069
+ * Bumped on every disconnect(). Stale async CSS-inlining callbacks from a
3070
+ * previous session (e.g. agent reload → tracker restart) compare against
3071
+ * this and bail instead of sending messages that reference vanished node ids.
3072
+ */
3073
+ this.generation = 0;
3369
3074
  this.throttling = true;
3370
3075
  this.throttledSetNodeData = throttleWithTrailing((id, parentElement, data) => this.sendNodeData(id, parentElement, data), 30);
3371
3076
  this.throttling = !Boolean(options.disableThrottling);
@@ -3512,18 +3217,23 @@ class Observer {
3512
3217
  }
3513
3218
  if (name === 'style' || (name === 'href' && hasTag(node, 'link'))) {
3514
3219
  if ('rel' in node && node.rel === 'stylesheet' && this.inlineRemoteCss) {
3220
+ const gen = this.generation;
3515
3221
  setTimeout(() => {
3516
3222
  inlineRemoteCss(
3517
3223
  // @ts-ignore
3518
3224
  node, id, this.app.getBaseHref(), nextID, (id, cssText, index, baseHref) => {
3225
+ if (this.generation !== gen)
3226
+ return;
3519
3227
  this.app.send(AdoptedSSInsertRuleURLBased(id, cssText, index, baseHref));
3520
3228
  }, (sheetId, ownerId) => {
3229
+ if (this.generation !== gen)
3230
+ return;
3521
3231
  this.app.send(AdoptedSSAddOwner(sheetId, ownerId));
3522
3232
  }, this.inlinerOptions?.forceFetch, this.inlinerOptions?.forcePlain, (cssText, fakeTextId) => {
3233
+ if (this.generation !== gen)
3234
+ return;
3523
3235
  this.app.send(CreateTextNode(fakeTextId, id, 0));
3524
- setTimeout(() => {
3525
- this.app.send(SetCSSDataURLBased(fakeTextId, cssText, this.app.getBaseHref()));
3526
- }, 10);
3236
+ this.app.send(SetCSSDataURLBased(fakeTextId, cssText, this.app.getBaseHref()));
3527
3237
  });
3528
3238
  }, 0);
3529
3239
  return;
@@ -3756,37 +3466,132 @@ class Observer {
3756
3466
  if (cmt !== undefined) {
3757
3467
  return cmt;
3758
3468
  }
3759
- return (this.commited[id] = this._commitNode(id, node));
3469
+ return (this.commited[id] = this._commitNode(id, node));
3470
+ }
3471
+ commitNodes(isStart = false) {
3472
+ let node;
3473
+ for (const [id, type] of this.recents.entries()) {
3474
+ this.commitNode(id);
3475
+ if (type === RecentsType.New && (node = this.app.nodes.getNode(id))) {
3476
+ this.app.nodes.callNodeCallbacks(node, isStart);
3477
+ }
3478
+ }
3479
+ this.clear();
3480
+ }
3481
+ // ISSSUE (nodeToBinde should be the same as node in all cases. Look at the comment about 0-node at the beginning of the file.)
3482
+ // TODO: use one observer instance for all iframes/shadowRoots (composition instiad of inheritance)
3483
+ observeRoot(node, beforeCommit, nodeToBind = node) {
3484
+ this.observer.observe(node, {
3485
+ childList: true,
3486
+ attributes: true,
3487
+ characterData: true,
3488
+ subtree: true,
3489
+ attributeOldValue: false,
3490
+ characterDataOldValue: false,
3491
+ });
3492
+ this.bindTree(nodeToBind);
3493
+ beforeCommit(this.app.nodes.getID(node));
3494
+ this.commitNodes(true);
3495
+ }
3496
+ /**
3497
+ * Re-evaluates sanitization for every tracked node in `root`'s subtree against
3498
+ * the current DOM and re-emits whatever changed. Pass the highest node you
3499
+ * changed (or the document root) so inherited levels propagate correctly.
3500
+ */
3501
+ resanitizeSubtree(root) {
3502
+ if (!isObservable(root)) {
3503
+ return;
3504
+ }
3505
+ const parent = root.parentNode;
3506
+ const parentId = parent !== null ? this.app.nodes.getID(parent) : undefined;
3507
+ const parentLevel = parentId !== undefined ? this.app.sanitizer.getLevel(parentId) : SanitizeLevel.Plain;
3508
+ this.resanitizeNode(root, parentLevel);
3509
+ }
3510
+ resanitizeNode(node, parentLevel) {
3511
+ if (isIgnored(node)) {
3512
+ return;
3513
+ }
3514
+ const id = this.app.nodes.getID(node);
3515
+ if (id === undefined) {
3516
+ // Untracked (new, or under a hidden ancestor): the live observer handles it.
3517
+ return;
3518
+ }
3519
+ const newLevel = this.app.sanitizer.computeLevel(node, parentLevel);
3520
+ const prevLevel = this.app.sanitizer.getLevel(id);
3521
+ const wasHidden = prevLevel === SanitizeLevel.Hidden;
3522
+ const willHidden = newLevel === SanitizeLevel.Hidden;
3523
+ // Crossing the hidden boundary changes the rendered structure (placeholder vs
3524
+ // real subtree), so rebuild rather than re-emit.
3525
+ if (wasHidden !== willHidden) {
3526
+ this.recreateSubtree(node);
3527
+ return;
3528
+ }
3529
+ if (willHidden) {
3530
+ return;
3531
+ }
3532
+ // Plain <-> Obscured: same structure, only leaf content changes.
3533
+ if (prevLevel !== newLevel) {
3534
+ this.app.sanitizer.setLevel(id, newLevel);
3535
+ this.reemitNode(id, node);
3536
+ }
3537
+ for (let child = node.firstChild; child !== null; child = child.nextSibling) {
3538
+ this.resanitizeNode(child, newLevel);
3539
+ }
3540
+ }
3541
+ // Destroys the node player-side and re-emits its subtree from scratch (new ids)
3542
+ // so it materializes at the freshly-computed level.
3543
+ recreateSubtree(node) {
3544
+ const id = this.app.nodes.getID(node);
3545
+ if (id === undefined) {
3546
+ return;
3547
+ }
3548
+ this.app.send(RemoveNode(id));
3549
+ this.clearSubtreeRegistration(node);
3550
+ this.bindTree(node);
3551
+ this.commitNodes();
3552
+ }
3553
+ clearSubtreeRegistration(node) {
3554
+ const clearOne = (n) => {
3555
+ const oldId = this.app.nodes.getID(n);
3556
+ if (oldId !== undefined) {
3557
+ this.app.sanitizer.setLevel(oldId, SanitizeLevel.Plain);
3558
+ }
3559
+ this.app.nodes.unregisterNode(n);
3560
+ };
3561
+ const walker = document.createTreeWalker(node, NodeFilter.SHOW_ELEMENT + NodeFilter.SHOW_TEXT, {
3562
+ acceptNode: (n) => isIgnored(n) || this.app.nodes.getID(n) === undefined
3563
+ ? NodeFilter.FILTER_REJECT
3564
+ : NodeFilter.FILTER_ACCEPT,
3565
+ },
3566
+ // @ts-ignore
3567
+ false);
3568
+ // Collect first, then clear: unregistering mutates the ids the walker reads.
3569
+ const subtree = [];
3570
+ while (walker.nextNode()) {
3571
+ subtree.push(walker.currentNode);
3572
+ }
3573
+ clearOne(node);
3574
+ subtree.forEach(clearOne);
3760
3575
  }
3761
- commitNodes(isStart = false) {
3762
- let node;
3763
- for (const [id, type] of this.recents.entries()) {
3764
- this.commitNode(id);
3765
- if (type === RecentsType.New && (node = this.app.nodes.getNode(id))) {
3766
- this.app.nodes.callNodeCallbacks(node, isStart);
3576
+ reemitNode(id, node) {
3577
+ if (isTextNode(node)) {
3578
+ const parent = node.parentNode;
3579
+ if (parent !== null && isElementNode(parent)) {
3580
+ // re-runs sanitize() at the level we just set
3581
+ this.sendNodeData(id, parent, node.data);
3767
3582
  }
3583
+ return;
3584
+ }
3585
+ if (isElementNode(node)) {
3586
+ // inputs/images/canvas re-emit their own payload via registered callbacks
3587
+ this.app.callResanitizeCallbacks(node, id);
3768
3588
  }
3769
- this.clear();
3770
- }
3771
- // ISSSUE (nodeToBinde should be the same as node in all cases. Look at the comment about 0-node at the beginning of the file.)
3772
- // TODO: use one observer instance for all iframes/shadowRoots (composition instiad of inheritance)
3773
- observeRoot(node, beforeCommit, nodeToBind = node) {
3774
- this.observer.observe(node, {
3775
- childList: true,
3776
- attributes: true,
3777
- characterData: true,
3778
- subtree: true,
3779
- attributeOldValue: false,
3780
- characterDataOldValue: false,
3781
- });
3782
- this.bindTree(nodeToBind);
3783
- beforeCommit(this.app.nodes.getID(node));
3784
- this.commitNodes(true);
3785
3589
  }
3786
3590
  disconnect() {
3787
3591
  this.observer.disconnect();
3788
3592
  this.clear();
3789
3593
  this.throttledSetNodeData.clear();
3594
+ this.generation++;
3790
3595
  }
3791
3596
  }
3792
3597
 
@@ -4021,7 +3826,10 @@ class TopObserver extends Observer {
4021
3826
  this.app.debug.info('doc already observed for', id);
4022
3827
  return;
4023
3828
  }
4024
- const observer = new IFrameObserver(this.app, false, {});
3829
+ const observer = new IFrameObserver(this.app, false, {
3830
+ disableSprites: this.options.disableSprites,
3831
+ ...getInlineOptions(this.options.inlineCss, console.warn),
3832
+ });
4025
3833
  this.iframeObservers.set(iframe, observer);
4026
3834
  this.docObservers.set(currentDoc, observer);
4027
3835
  this.iframeObserversArr.push(observer);
@@ -4043,7 +3851,10 @@ class TopObserver extends Observer {
4043
3851
  handle();
4044
3852
  }
4045
3853
  handleShadowRoot(shRoot) {
4046
- const observer = new ShadowRootObserver(this.app);
3854
+ const observer = new ShadowRootObserver(this.app, false, {
3855
+ disableSprites: this.options.disableSprites,
3856
+ ...getInlineOptions(this.options.inlineCss, console.warn),
3857
+ });
4047
3858
  this.shadowRootObservers.set(shRoot, observer);
4048
3859
  observer.observe(shRoot.host);
4049
3860
  }
@@ -4079,7 +3890,10 @@ class TopObserver extends Observer {
4079
3890
  };
4080
3891
  this.app.nodes.clear();
4081
3892
  this.app.nodes.crossdomainMode(frameLevel, frameOder);
4082
- const iframeObserver = new IFrameObserver(this.app);
3893
+ const iframeObserver = new IFrameObserver(this.app, false, {
3894
+ disableSprites: this.options.disableSprites,
3895
+ ...getInlineOptions(this.options.inlineCss, console.warn),
3896
+ });
4083
3897
  this.iframeObservers.set(window.document, iframeObserver);
4084
3898
  iframeObserver.syntheticObserve(rootNodeId, window.document);
4085
3899
  }
@@ -4095,94 +3909,6 @@ class TopObserver extends Observer {
4095
3909
  }
4096
3910
  }
4097
3911
 
4098
- var SanitizeLevel;
4099
- (function (SanitizeLevel) {
4100
- SanitizeLevel[SanitizeLevel["Plain"] = 0] = "Plain";
4101
- SanitizeLevel[SanitizeLevel["Obscured"] = 1] = "Obscured";
4102
- SanitizeLevel[SanitizeLevel["Hidden"] = 2] = "Hidden";
4103
- })(SanitizeLevel || (SanitizeLevel = {}));
4104
- const stringWiper = (input) => input
4105
- .trim()
4106
- .replace(/[^\f\n\r\t\v\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff\s]/g, '*');
4107
- class Sanitizer {
4108
- constructor(params) {
4109
- this.obscured = new Set();
4110
- this.hidden = new Set();
4111
- this.app = params.app;
4112
- const defaultOptions = {
4113
- maskTextEmails: true,
4114
- maskTextNumbers: false,
4115
- maskAllByDefault: false,
4116
- domSanitizer: undefined,
4117
- };
4118
- this.maskAllByDefault = params.options?.maskAllByDefault ?? false;
4119
- this.options = Object.assign(defaultOptions, params.options);
4120
- }
4121
- handleNode(id, parentID, node) {
4122
- if (this.options.maskAllByDefault) {
4123
- if (isElementNode(node) && !hasOxvoSessionsAttribute(node, 'unmask')) {
4124
- return this.obscured.add(id);
4125
- }
4126
- if (isTextNode(node) && !hasOxvoSessionsAttribute(node.parentNode, 'unmask')) {
4127
- return this.obscured.add(id);
4128
- }
4129
- }
4130
- if (this.obscured.has(parentID) ||
4131
- (isElementNode(node) &&
4132
- (hasOxvoSessionsAttribute(node, 'masked') || hasOxvoSessionsAttribute(node, 'obscured')))) {
4133
- this.obscured.add(id);
4134
- }
4135
- if (this.hidden.has(parentID) ||
4136
- (isElementNode(node) &&
4137
- (hasOxvoSessionsAttribute(node, 'htmlmasked') || hasOxvoSessionsAttribute(node, 'hidden')))) {
4138
- this.hidden.add(id);
4139
- }
4140
- if (this.options.domSanitizer !== undefined && isElementNode(node)) {
4141
- const sanitizeLevel = this.options.domSanitizer(node);
4142
- if (sanitizeLevel === SanitizeLevel.Obscured) {
4143
- this.obscured.add(id);
4144
- }
4145
- if (sanitizeLevel === SanitizeLevel.Hidden) {
4146
- this.hidden.add(id);
4147
- }
4148
- }
4149
- }
4150
- sanitize(id, data) {
4151
- if (this.obscured.has(id)) {
4152
- // TODO: is it the best place to put trim() ? Might trimmed spaces be considered in layout in certain cases?
4153
- return stringWiper(data);
4154
- }
4155
- if (this.options.maskTextNumbers) {
4156
- data = data.replace(/\d/g, '0');
4157
- }
4158
- if (this.options.maskTextEmails) {
4159
- data = data.replace(/^\w+([+.-]\w+)*@\w+([.-]\w+)*\.\w{2,3}$/g, (email) => {
4160
- const [name, domain] = email.split('@');
4161
- const [domainName, host] = domain.split('.');
4162
- return `${stars(name)}@${stars(domainName)}.${stars(host)}`;
4163
- });
4164
- }
4165
- return data;
4166
- }
4167
- isObscured(id) {
4168
- return this.obscured.has(id);
4169
- }
4170
- isHidden(id) {
4171
- return this.hidden.has(id);
4172
- }
4173
- getInnerTextSecure(el) {
4174
- const id = this.app.nodes.getID(el);
4175
- if (!id) {
4176
- return '';
4177
- }
4178
- return this.sanitize(id, el.innerText);
4179
- }
4180
- clear() {
4181
- this.obscured.clear();
4182
- this.hidden.clear();
4183
- }
4184
- }
4185
-
4186
3912
  const tokenSeparator = '_$_';
4187
3913
  class Session {
4188
3914
  constructor(params) {
@@ -4382,9 +4108,10 @@ class Ticker {
4382
4108
  * this value is injected during build time via rollup
4383
4109
  * */
4384
4110
  // @ts-ignore
4385
- const workerBodyFn = "!function(){\"use strict\";class t{constructor(t,s,i,e=10,n=250,h,r){this.onUnauthorised=s,this.onFailure=i,this.MAX_ATTEMPTS_COUNT=e,this.ATTEMPT_TIMEOUT=n,this.onCompress=h,this.pageNo=r,this.attemptsCount=0,this.busy=!1,this.queue=[],this.token=null,this.lastBatchNum=0,this.ingestURL=t+\"/v1/events\",this.isCompressing=void 0!==h}getQueueStatus(){return 0===this.queue.length&&!this.busy}authorise(t){this.token=t,this.busy||this.sendNext()}push(t){if(this.busy||!this.token)this.queue.push(t);else if(this.busy=!0,this.isCompressing&&this.onCompress)this.onCompress(t);else{const s=++this.lastBatchNum;this.sendBatch(t,!1,s)}}sendNext(){const t=this.queue.shift();if(t)if(this.busy=!0,this.isCompressing&&this.onCompress)this.onCompress(t);else{const s=++this.lastBatchNum;this.sendBatch(t,!1,s)}else this.busy=!1}retry(t,s,i){this.attemptsCount>=this.MAX_ATTEMPTS_COUNT?this.onFailure(`Failed to send batch after ${this.attemptsCount} attempts.`):(this.attemptsCount++,setTimeout((()=>this.sendBatch(t,s,i)),this.ATTEMPT_TIMEOUT*this.attemptsCount))}sendBatch(t,s,i){var e;const n=null==i?void 0:i.toString().replace(/^([^_]+)_([^_]+).*/,\"$1_$2_$3\");this.busy=!0;const h={Authorization:`Bearer ${this.token}`};if(s&&(h[\"Content-Encoding\"]=\"gzip\"),null===this.token)return void setTimeout((()=>{this.sendBatch(t,s,`${null!=i?i:\"noBatchNum\"}_newToken`)}),500);const r=`${null!==(e=this.pageNo)&&void 0!==e?e:\"noPageNum\"}_${null!=n?n:\"noBatchNum\"}`;fetch(`${this.ingestURL}?seq=${r}`,{body:t,method:\"POST\",headers:h,keepalive:t.length<65536}).then((e=>{if(401===e.status)return this.busy=!1,void this.onUnauthorised();e.status>=400?this.retry(t,s,`${null!=i?i:\"noBatchNum\"}_network:${e.status}`):(this.attemptsCount=0,this.sendNext())})).catch((e=>{console.warn(\"OxvoSessions:\",e),this.retry(t,s,`${null!=i?i:\"noBatchNum\"}_reject:${e.message}`)}))}sendCompressed(t){const s=++this.lastBatchNum;this.sendBatch(t,!0,s)}sendUncompressed(t){const s=++this.lastBatchNum;this.sendBatch(t,!1,s)}clean(){this.sendNext(),setTimeout((()=>{this.token=null,this.queue.length=0}),10)}}const s=\"function\"==typeof TextEncoder?new TextEncoder:{encode(t){const s=t.length,i=new Uint8Array(3*s);let e=-1;for(let n=0,h=0,r=0;r!==s;){if(n=t.charCodeAt(r),r+=1,n>=55296&&n<=56319){if(r===s){i[e+=1]=239,i[e+=1]=191,i[e+=1]=189;break}if(h=t.charCodeAt(r),!(h>=56320&&h<=57343)){i[e+=1]=239,i[e+=1]=191,i[e+=1]=189;continue}if(n=1024*(n-55296)+h-56320+65536,r+=1,n>65535){i[e+=1]=240|n>>>18,i[e+=1]=128|n>>>12&63,i[e+=1]=128|n>>>6&63,i[e+=1]=128|63&n;continue}}n<=127?i[e+=1]=0|n:n<=2047?(i[e+=1]=192|n>>>6,i[e+=1]=128|63&n):(i[e+=1]=224|n>>>12,i[e+=1]=128|n>>>6&63,i[e+=1]=128|63&n)}return i.subarray(0,e+1)}};class i{constructor(t){this.size=t,this.offset=0,this.checkpointOffset=0,this.data=new Uint8Array(t)}getCurrentOffset(){return this.offset}checkpoint(){this.checkpointOffset=this.offset}get isEmpty(){return 0===this.offset}skip(t){return this.offset+=t,this.offset<=this.size}set(t,s){this.data.set(t,s)}boolean(t){return this.data[this.offset++]=+t,this.offset<=this.size}uint(t){for((t<0||t>Number.MAX_SAFE_INTEGER)&&(t=0);t>=128;)this.data[this.offset++]=t%256|128,t=Math.floor(t/128);return this.data[this.offset++]=t,this.offset<=this.size}int(t){return t=Math.round(t),this.uint(t>=0?2*t:-2*t-1)}string(t){const i=s.encode(t),e=i.byteLength;return!(!this.uint(e)||this.offset+e>this.size)&&(this.data.set(i,this.offset),this.offset+=e,!0)}reset(){this.offset=0,this.checkpointOffset=0}flush(){const t=this.data.slice(0,this.checkpointOffset);return this.reset(),t}}class e extends i{encode(t){switch(t[0]){case 0:case 11:case 114:case 115:return this.uint(t[1]);case 4:case 44:case 47:return this.string(t[1])&&this.string(t[2])&&this.uint(t[3]);case 5:case 20:case 65:case 70:case 75:case 76:case 77:case 82:return this.uint(t[1])&&this.uint(t[2]);case 6:return this.int(t[1])&&this.int(t[2]);case 7:return!0;case 8:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.string(t[4])&&this.boolean(t[5]);case 9:case 10:case 24:case 35:case 51:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3]);case 12:case 52:case 61:case 71:return this.uint(t[1])&&this.string(t[2])&&this.string(t[3]);case 13:case 14:case 17:case 34:case 36:case 50:case 54:return this.uint(t[1])&&this.string(t[2]);case 16:return this.uint(t[1])&&this.int(t[2])&&this.int(t[3]);case 18:return this.uint(t[1])&&this.string(t[2])&&this.int(t[3]);case 19:return this.uint(t[1])&&this.boolean(t[2]);case 21:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4])&&this.string(t[5])&&this.uint(t[6])&&this.uint(t[7])&&this.uint(t[8]);case 22:case 27:case 30:case 41:case 45:case 46:case 43:case 63:case 64:case 79:case 124:return this.string(t[1])&&this.string(t[2]);case 23:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.uint(t[4])&&this.uint(t[5])&&this.uint(t[6])&&this.uint(t[7])&&this.uint(t[8])&&this.uint(t[9]);case 28:case 29:case 42:case 117:case 118:return this.string(t[1]);case 40:return this.string(t[1])&&this.uint(t[2])&&this.string(t[3])&&this.string(t[4]);case 48:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4])&&this.int(t[5]);case 49:return this.int(t[1])&&this.int(t[2])&&this.uint(t[3])&&this.uint(t[4]);case 53:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.uint(t[4])&&this.uint(t[5])&&this.uint(t[6])&&this.string(t[7])&&this.string(t[8]);case 55:return this.boolean(t[1]);case 57:case 60:return this.uint(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4]);case 58:case 120:return this.int(t[1]);case 68:return this.uint(t[1])&&this.uint(t[2])&&this.string(t[3])&&this.string(t[4])&&this.uint(t[5])&&this.uint(t[6]);case 69:return this.uint(t[1])&&this.uint(t[2])&&this.string(t[3])&&this.string(t[4]);case 73:return this.uint(t[1])&&this.string(t[2])&&this.uint(t[3])&&this.string(t[4]);case 78:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4]);case 81:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.int(t[4])&&this.string(t[5]);case 83:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4])&&this.string(t[5])&&this.uint(t[6])&&this.uint(t[7])&&this.uint(t[8])&&this.uint(t[9]);case 84:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.uint(t[4])&&this.string(t[5])&&this.string(t[6]);case 85:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.uint(t[4])&&this.uint(t[5])&&this.uint(t[6])&&this.string(t[7])&&this.string(t[8])&&this.uint(t[9])&&this.boolean(t[10])&&this.uint(t[11])&&this.uint(t[12])&&this.uint(t[13])&&this.uint(t[14])&&this.uint(t[15])&&this.uint(t[16])&&this.uint(t[17]);case 87:return this.string(t[1])&&this.int(t[2])&&this.int(t[3]);case 89:return this.string(t[1])&&this.int(t[2])&&this.int(t[3])&&this.int(t[4])&&this.int(t[5])&&this.string(t[6]);case 112:return this.uint(t[1])&&this.string(t[2])&&this.boolean(t[3])&&this.string(t[4])&&this.int(t[5])&&this.int(t[6]);case 113:return this.uint(t[1])&&this.uint(t[2])&&this.string(t[3]);case 116:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.uint(t[4])&&this.uint(t[5])&&this.uint(t[6])&&this.string(t[7])&&this.string(t[8])&&this.uint(t[9])&&this.boolean(t[10]);case 119:return this.string(t[1])&&this.uint(t[2]);case 121:return this.string(t[1])&&this.string(t[2])&&this.uint(t[3])&&this.uint(t[4]);case 122:return this.string(t[1])&&this.string(t[2])&&this.uint(t[3])&&this.string(t[4]);case 123:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4])&&this.uint(t[5])}}}class n{constructor(t,s,i,n,h,r){this.pageNo=t,this.timestamp=s,this.url=i,this.onBatch=n,this.tabId=h,this.onOfflineEnd=r,this.nextIndex=0,this.beaconSize=2e5,this.encoder=new e(this.beaconSize),this.sizeBuffer=new Uint8Array(3),this.isEmpty=!0,this.checkpoints=[],this.beaconSizeLimit=1e6,this.prepare()}writeType(t){return this.encoder.uint(t[0])}writeFields(t){return this.encoder.encode(t)}writeSizeAt(t,s){for(let s=0;s<3;s++)this.sizeBuffer[s]=t>>8*s;this.encoder.set(this.sizeBuffer,s)}prepare(){if(!this.encoder.isEmpty)return;this.checkpoints.length=0;const t=[81,1,this.pageNo,this.nextIndex,this.timestamp,this.url],s=[0,this.timestamp],i=[118,this.tabId];this.writeType(t),this.writeFields(t),this.writeWithSize(s),this.writeWithSize(i),this.isEmpty=!0}writeWithSize(t){const s=this.encoder;if(!this.writeType(t)||!s.skip(3))return!1;const i=s.getCurrentOffset(),e=this.writeFields(t);if(e){const e=s.getCurrentOffset()-i;if(e>16777215)return console.warn(\"OxvoSessions: max message size overflow.\"),!1;this.writeSizeAt(e,i-3),s.checkpoint(),this.checkpoints.push(s.getCurrentOffset()),this.isEmpty=this.isEmpty&&0===t[0],this.nextIndex++}return e}setBeaconSizeLimit(t){this.beaconSizeLimit=t}writeMessage(t){if(-1===t[0])return this.finaliseBatch(),this.onOfflineEnd();0===t[0]&&(this.timestamp=t[1]),122===t[0]&&(this.url=t[1]),this.writeWithSize(t)||(this.finaliseBatch(),this.writeWithSize(t)||(this.encoder=new e(this.beaconSizeLimit),this.prepare(),this.writeWithSize(t)?this.finaliseBatch():console.warn(\"OxvoSessions: beacon size overflow. Skipping large message.\",t,this),this.encoder=new e(this.beaconSize),this.prepare()))}finaliseBatch(t=!1){if(this.isEmpty)return;const s=this.encoder.flush();this.onBatch(s,t),this.prepare()}clean(){this.encoder.reset(),this.checkpoints.length=0}}var h;!function(t){t[t.NotActive=0]=\"NotActive\",t[t.Starting=1]=\"Starting\",t[t.Stopping=2]=\"Stopping\",t[t.Active=3]=\"Active\",t[t.Stopped=4]=\"Stopped\"}(h||(h={}));let r=null,u=null,a=h.NotActive;function o(t){u&&u.finaliseBatch(t)}function c(){return new Promise((t=>{a=h.Stopping,null!==p&&(clearInterval(p),p=null),u&&(u.clean(),u=null),r&&(r.clean(),setTimeout((()=>{r=null}),20)),setTimeout((()=>{a=h.NotActive,t(null)}),100)}))}function g(){[h.Stopped,h.Stopping].includes(a)||(postMessage(\"a_stop\"),c().then((()=>{postMessage(\"a_start\")})))}let l,p=null;self.onmessage=({data:s})=>{if(\"stop\"===s)return o(),void c().then((()=>{a=h.Stopped}));if(\"forceFlushBatch\"!==s)if(\"closing\"!==s){if(!Array.isArray(s)){if(\"compressed\"===s.type){if(!r)return console.debug(\"OR WebWorker: sender not initialised. Compressed batch.\"),void g();s.batch&&r.sendCompressed(s.batch)}if(\"uncompressed\"===s.type){if(!r)return console.debug(\"OR WebWorker: sender not initialised. Uncompressed batch.\"),void g();s.batch&&r.sendUncompressed(s.batch)}return\"start\"===s.type?(a=h.Starting,r=new t(s.ingestPoint,(()=>{g()}),(t=>{!function(t){postMessage({type:\"failure\",reason:t}),c()}(t)}),s.connAttemptCount,s.connAttemptGap,(t=>{postMessage({type:\"compress\",batch:t},[t.buffer])}),s.pageNo),u=new n(s.pageNo,s.timestamp,s.url,((t,s)=>{r&&(s?r.sendUncompressed(t):r.push(t))}),s.tabId,(()=>postMessage({type:\"queue_empty\"}))),null===p&&(p=setInterval(o,3e4)),a=h.Active):\"auth\"===s.type?r?u?(r.authorise(s.token),void(s.beaconSizeLimit&&u.setBeaconSizeLimit(s.beaconSizeLimit))):(console.debug(\"OR WebWorker: writer not initialised. Received auth.\"),void g()):(console.debug(\"OR WebWorker: sender not initialised. Received auth.\"),void g()):void 0}if(u){const t=u;s.forEach((s=>{55===s[0]&&(s[1]?l=setTimeout((()=>g()),18e5):clearTimeout(l)),t.writeMessage(s)}))}else postMessage(\"not_init\"),g()}else o(!0);else o()}}();\n";
4111
+ const workerBodyFn = "!function(){\"use strict\";class t{constructor(t,s,i,e=10,n=250,h,r){this.onUnauthorised=s,this.onFailure=i,this.MAX_ATTEMPTS_COUNT=e,this.ATTEMPT_TIMEOUT=n,this.onCompress=h,this.pageNo=r,this.attemptsCount=0,this.busy=!1,this.queue=[],this.token=null,this.lastBatchNum=0,this.inflightKeepaliveBytes=0,this.ingestURL=t+\"/v1/events\",this.isCompressing=void 0!==h}getQueueStatus(){return 0===this.queue.length&&!this.busy}authorise(t){this.token=t,this.busy||this.sendNext()}push(t,s=\"player\"){if(this.busy||!this.token)this.queue.push({batch:t,dataType:s});else if(this.busy=!0,this.isCompressing&&this.onCompress)this.onCompress(t,s);else{const i=++this.lastBatchNum;this.sendBatch(t,!1,i,s)}}sendNext(){const t=this.queue.shift();if(t)if(this.busy=!0,this.isCompressing&&this.onCompress)this.onCompress(t.batch,t.dataType);else{const s=++this.lastBatchNum;this.sendBatch(t.batch,!1,s,t.dataType)}else this.busy=!1}retry(t,s,i,e=\"player\"){if(this.attemptsCount>=this.MAX_ATTEMPTS_COUNT)return void this.onFailure(`Failed to send batch after ${this.attemptsCount} attempts.`);this.attemptsCount++;const n=new Uint8Array(t);setTimeout((()=>this.sendBatch(n,s,i,e)),this.ATTEMPT_TIMEOUT*this.attemptsCount)}sendBatch(t,s,i,e=\"player\"){var n;if(0===t.length)return console.error(\"OxvoSessions: refusing to send 0-byte batch.\",{batchNum:i,dataType:e,isCompressed:s,batch:t}),this.attemptsCount=0,void this.sendNext();const h=null==i?void 0:i.toString().replace(/^([^_]+)_([^_]+).*/,\"$1_$2_$3\");this.busy=!0;const r={Authorization:`Bearer ${this.token}`,DataType:e};if(s&&(r[\"Content-Encoding\"]=\"gzip\"),null===this.token)return void setTimeout((()=>{this.sendBatch(t,s,`${null!=i?i:\"noBatchNum\"}_newToken`,e)}),500);const a=t.length<65536&&this.inflightKeepaliveBytes+t.length<=65536;a&&(this.inflightKeepaliveBytes+=t.length);const o=()=>{a&&(this.inflightKeepaliveBytes-=t.length)},u=`${null!==(n=this.pageNo)&&void 0!==n?n:\"noPageNum\"}_${null!=h?h:\"noBatchNum\"}`;fetch(`${this.ingestURL}?seq=${u}&keepalive=${a?\"yes\":\"no\"}`,{body:t,method:\"POST\",headers:r,keepalive:a}).then((n=>{var h;if(o(),null===(h=n.body)||void 0===h||h.cancel().catch((()=>{})),401===n.status)return this.busy=!1,void this.onUnauthorised();n.status>=400?this.retry(t,s,`${null!=i?i:\"noBatchNum\"}_network:${n.status}`,e):(this.attemptsCount=0,this.sendNext())})).catch((n=>{o(),console.warn(\"OxvoSessions:\",n),this.retry(t,s,`${null!=i?i:\"noBatchNum\"}_reject:${n.message}`,e)}))}sendCompressed(t,s=\"player\"){const i=++this.lastBatchNum;this.sendBatch(t,!0,i,s)}sendUncompressed(t,s=\"player\"){const i=++this.lastBatchNum;this.sendBatch(t,!1,i,s)}clean(){this.sendNext(),setTimeout((()=>{this.token=null,this.queue.length=0}),10)}}const s=new Set([60,61,71,73]),i=new Set([21,22,40,41,44,45,46,47,48,79,83,84,85,87,89,116,120,121,123]),e=new Set([17,23,24,27,28,29,30,42,63,64,78,112,115,124]),n=\"function\"==typeof TextEncoder?new TextEncoder:{encode(t){const s=t.length,i=new Uint8Array(3*s);let e=-1;for(let n=0,h=0,r=0;r!==s;){if(n=t.charCodeAt(r),r+=1,n>=55296&&n<=56319){if(r===s){i[e+=1]=239,i[e+=1]=191,i[e+=1]=189;break}if(h=t.charCodeAt(r),!(h>=56320&&h<=57343)){i[e+=1]=239,i[e+=1]=191,i[e+=1]=189;continue}if(n=1024*(n-55296)+h-56320+65536,r+=1,n>65535){i[e+=1]=240|n>>>18,i[e+=1]=128|n>>>12&63,i[e+=1]=128|n>>>6&63,i[e+=1]=128|63&n;continue}}n<=127?i[e+=1]=0|n:n<=2047?(i[e+=1]=192|n>>>6,i[e+=1]=128|63&n):(i[e+=1]=224|n>>>12,i[e+=1]=128|n>>>6&63,i[e+=1]=128|63&n)}return i.subarray(0,e+1)}};class h{constructor(t){this.size=t,this.offset=0,this.checkpointOffset=0,this.data=new Uint8Array(t)}getCurrentOffset(){return this.offset}getCurrentCheckpoint(){return this.checkpointOffset}checkpoint(){this.checkpointOffset=this.offset}get isEmpty(){return 0===this.offset}skip(t){return this.offset+=t,this.offset<=this.size}set(t,s){this.data.set(t,s)}boolean(t){return this.data[this.offset++]=+t,this.offset<=this.size}uint(t){for((t<0||t>Number.MAX_SAFE_INTEGER)&&(t=0);t>=128;)this.data[this.offset++]=t%256|128,t=Math.floor(t/128);return this.data[this.offset++]=t,this.offset<=this.size}int(t){return t=Math.round(t),this.uint(t>=0?2*t:-2*t-1)}string(t){const s=n.encode(t),i=s.byteLength;return!(!this.uint(i)||this.offset+i>this.size)&&(this.data.set(s,this.offset),this.offset+=i,!0)}reset(){this.offset=0,this.checkpointOffset=0}rewind(t,s){t>this.offset||s>this.checkpointOffset||(this.offset=t,this.checkpointOffset=s)}flush(){const t=this.data.slice(0,this.checkpointOffset);return this.reset(),t}}class r extends h{encode(t){switch(t[0]){case 0:case 11:case 114:case 115:return this.uint(t[1]);case 4:case 44:case 47:return this.string(t[1])&&this.string(t[2])&&this.uint(t[3]);case 5:case 20:case 65:case 70:case 75:case 76:case 77:return this.uint(t[1])&&this.uint(t[2]);case 6:return this.int(t[1])&&this.int(t[2]);case 7:return!0;case 8:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.string(t[4])&&this.boolean(t[5]);case 9:case 10:case 24:case 35:case 51:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3]);case 12:case 52:case 61:case 71:return this.uint(t[1])&&this.string(t[2])&&this.string(t[3]);case 13:case 14:case 17:case 34:case 36:case 50:case 54:return this.uint(t[1])&&this.string(t[2]);case 16:return this.uint(t[1])&&this.int(t[2])&&this.int(t[3]);case 18:return this.uint(t[1])&&this.string(t[2])&&this.int(t[3]);case 19:return this.uint(t[1])&&this.boolean(t[2]);case 21:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4])&&this.string(t[5])&&this.uint(t[6])&&this.uint(t[7])&&this.uint(t[8]);case 22:case 27:case 30:case 41:case 45:case 46:case 43:case 63:case 64:case 79:case 124:return this.string(t[1])&&this.string(t[2]);case 23:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.uint(t[4])&&this.uint(t[5])&&this.uint(t[6])&&this.uint(t[7])&&this.uint(t[8])&&this.uint(t[9]);case 28:case 29:case 42:case 117:case 118:return this.string(t[1]);case 40:return this.string(t[1])&&this.uint(t[2])&&this.string(t[3])&&this.string(t[4]);case 48:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4])&&this.int(t[5]);case 49:return this.int(t[1])&&this.int(t[2])&&this.uint(t[3])&&this.uint(t[4]);case 53:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.uint(t[4])&&this.uint(t[5])&&this.uint(t[6])&&this.string(t[7])&&this.string(t[8]);case 55:return this.boolean(t[1]);case 57:case 60:return this.uint(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4]);case 58:case 120:return this.int(t[1]);case 68:return this.uint(t[1])&&this.uint(t[2])&&this.string(t[3])&&this.string(t[4])&&this.uint(t[5])&&this.uint(t[6]);case 69:return this.uint(t[1])&&this.uint(t[2])&&this.string(t[3])&&this.string(t[4]);case 73:return this.uint(t[1])&&this.string(t[2])&&this.uint(t[3])&&this.string(t[4]);case 78:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4]);case 81:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.int(t[4])&&this.string(t[5]);case 83:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4])&&this.string(t[5])&&this.uint(t[6])&&this.uint(t[7])&&this.uint(t[8])&&this.uint(t[9]);case 84:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.uint(t[4])&&this.string(t[5])&&this.string(t[6]);case 85:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.uint(t[4])&&this.uint(t[5])&&this.uint(t[6])&&this.string(t[7])&&this.string(t[8])&&this.uint(t[9])&&this.boolean(t[10])&&this.uint(t[11])&&this.uint(t[12])&&this.uint(t[13])&&this.uint(t[14])&&this.uint(t[15])&&this.uint(t[16])&&this.uint(t[17]);case 87:return this.string(t[1])&&this.int(t[2])&&this.int(t[3]);case 89:return this.string(t[1])&&this.int(t[2])&&this.int(t[3])&&this.int(t[4])&&this.int(t[5])&&this.string(t[6]);case 112:return this.uint(t[1])&&this.string(t[2])&&this.boolean(t[3])&&this.string(t[4])&&this.int(t[5])&&this.int(t[6]);case 113:return this.uint(t[1])&&this.uint(t[2])&&this.string(t[3]);case 116:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.uint(t[4])&&this.uint(t[5])&&this.uint(t[6])&&this.string(t[7])&&this.string(t[8])&&this.uint(t[9])&&this.boolean(t[10]);case 119:return this.string(t[1])&&this.uint(t[2]);case 121:return this.string(t[1])&&this.string(t[2])&&this.uint(t[3])&&this.uint(t[4]);case 122:return this.string(t[1])&&this.string(t[2])&&this.uint(t[3])&&this.string(t[4]);case 123:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4])&&this.uint(t[5])}}}class a{constructor(t,s,i){this.bufferSize=t,this.version=s,this.dataType=i,this.sizeBuffer=new Uint8Array(3),this.snap=null,this.hasNonTimestamp=!1,this.lastPushedTs=0,this.encoder=new r(t)}push(t,s){const i=this.encoder,e=null===this.snap,n=i.getCurrentOffset(),h=i.getCurrentCheckpoint();if(e){const t={pageNo:s.pageNo,firstIndex:s.index,timestamp:s.timestamp,url:s.url,tabId:s.tabId};if(!this.writeHeader(t))return i.rewind(n,h),!1;this.snap=t,this.lastPushedTs=s.timestamp}return 0===t[0]||s.timestamp===this.lastPushedTs||this.writeMessageWithSize([0,s.timestamp])?this.writeMessageWithSize(t)?(this.lastPushedTs=s.timestamp,0!==t[0]&&(this.hasNonTimestamp=!0),!0):(i.rewind(n,h),e&&(this.snap=null),!1):(i.rewind(n,h),!1)}hasContent(){return null!==this.snap&&this.hasNonTimestamp}flush(){if(!this.hasContent())return this.reset(),null;const t=this.encoder.flush();return this.snap=null,this.hasNonTimestamp=!1,this.lastPushedTs=0,t}reset(){this.encoder.reset(),this.snap=null,this.hasNonTimestamp=!1,this.lastPushedTs=0}writeHeader(t){const s=this.encoder,i=[81,this.version,t.pageNo,t.firstIndex,t.timestamp,t.url];return!!s.uint(i[0])&&(!!s.encode(i)&&(!(s.getCurrentOffset()>this.bufferSize)&&(s.checkpoint(),!!this.writeMessageWithSize([0,t.timestamp])&&!!this.writeMessageWithSize([118,t.tabId]))))}writeMessageWithSize(t){const s=this.encoder;if(!s.uint(t[0])||!s.skip(3))return!1;const i=s.getCurrentOffset();if(!s.encode(t))return!1;const e=s.getCurrentOffset(),n=e-i;return n>16777215?(console.warn(\"OxvoSessions: max message size overflow.\"),!1):!(e>this.bufferSize)&&(this.writeSizeAt(n,i-3),s.checkpoint(),!0)}writeSizeAt(t,s){for(let s=0;s<3;s++)this.sizeBuffer[s]=t>>8*s;this.encoder.set(this.sizeBuffer,s)}}class o{constructor(t,s,i,e,n,h,r=!1,o){this.pageNo=t,this.timestamp=s,this.url=i,this.onBatch=e,this.tabId=n,this.onOfflineEnd=h,this.localDebug=r,this.onLocalSave=o,this.nextIndex=0,this.beaconSize=2e5,this.beaconSizeLimit=1e6,this.protocolVersion=1,this.playerBuilder=new a(this.beaconSize,this.playerVersion(),\"player\"),this.assetBuilder=new a(this.beaconSize,3,\"assets\"),this.devtoolsBuilder=new a(this.beaconSize,4,\"devtools\"),this.analyticsBuilder=new a(this.beaconSize,5,\"analytics\")}playerVersion(){return 2===this.protocolVersion?2:1}currentCtx(){return{pageNo:this.pageNo,index:this.nextIndex,timestamp:this.timestamp,url:this.url,tabId:this.tabId}}setBeaconSizeLimit(t){this.beaconSizeLimit=t}setProtocolVersion(t){this.protocolVersion!==t&&(this.protocolVersion=t,this.playerBuilder.reset(),this.playerBuilder=new a(this.beaconSize,this.playerVersion(),\"player\"))}writeMessage(t){if(-1===t[0])return this.finaliseBatch(),this.onOfflineEnd();0===t[0]&&(this.timestamp=t[1]),122===t[0]&&(this.url=t[1]);const s=this.routeMessage(t);this.pushTo(s,t)}routeMessage(t){if(2===this.protocolVersion){const n=t[0];if(s.has(n))return this.assetBuilder;if(i.has(n))return this.devtoolsBuilder;if(e.has(n))return this.analyticsBuilder}return this.playerBuilder}pushTo(t,s){const i=this.currentCtx();if(t.push(s,i))return void this.nextIndex++;if(this.flushBuilder(t),t.push(s,i))return void this.nextIndex++;const e=new a(this.beaconSizeLimit,t.version,t.dataType);if(!e.push(s,i))return void console.warn(\"OxvoSessions: beacon size overflow. Skipping large message.\",s);this.nextIndex++;const n=e.flush();n&&this.emitBatch(n,t.dataType,!1)}flushBuilder(t,s=!1){const i=t.flush();return!!i&&(this.emitBatch(i,t.dataType,s),!0)}emitBatch(t,s,i){this.localDebug&&this.onLocalSave&&this.onLocalSave(`${s}-${Date.now()}`,t.slice()),this.onBatch(t,i,s)}finaliseBatch(t=!1){this.flushBuilder(this.playerBuilder,t),this.flushBuilder(this.assetBuilder,t),this.flushBuilder(this.devtoolsBuilder,t),this.flushBuilder(this.analyticsBuilder,t)}clean(){this.playerBuilder.reset(),this.assetBuilder.reset(),this.devtoolsBuilder.reset(),this.analyticsBuilder.reset()}}var u;!function(t){t[t.NotActive=0]=\"NotActive\",t[t.Starting=1]=\"Starting\",t[t.Stopping=2]=\"Stopping\",t[t.Active=3]=\"Active\",t[t.Stopped=4]=\"Stopped\"}(u||(u={}));let c=null,l=null,d=u.NotActive;function p(t){l&&l.finaliseBatch(t)}function f(){return new Promise((t=>{d=u.Stopping,null!==b&&(clearInterval(b),b=null),l&&(l.clean(),l=null),c&&(c.clean(),setTimeout((()=>{c=null}),20)),setTimeout((()=>{d=u.NotActive,t(null)}),100)}))}function g(){[u.Stopped,u.Stopping].includes(d)||(postMessage(\"a_stop\"),f().then((()=>{postMessage(\"a_start\")})))}let m,b=null;self.onmessage=({data:s})=>{var i;if(\"stop\"===s)return p(),void f().then((()=>{d=u.Stopped}));if(\"forceFlushBatch\"!==s)if(\"closing\"!==s){if(!Array.isArray(s)){if(\"compressed\"===s.type){if(!c)return console.debug(\"OR WebWorker: sender not initialised. Compressed batch.\"),void g();s.batch&&c.sendCompressed(s.batch,s.dataType)}if(\"uncompressed\"===s.type){if(!c)return console.debug(\"OR WebWorker: sender not initialised. Uncompressed batch.\"),void g();s.batch&&c.sendUncompressed(s.batch,s.dataType)}return\"start\"===s.type?(d=u.Starting,c=new t(s.ingestPoint,(()=>{g()}),(t=>{!function(t){postMessage({type:\"failure\",reason:t}),f()}(t)}),s.connAttemptCount,s.connAttemptGap,((t,s)=>{postMessage({type:\"compress\",batch:t,dataType:s},[t.buffer])}),s.pageNo),l=new o(s.pageNo,s.timestamp,s.url,((t,s,i=\"player\")=>{c&&(s?c.sendUncompressed(t,i):c.push(t,i))}),s.tabId,(()=>postMessage({type:\"queue_empty\"})),null!==(i=s.localDebug)&&void 0!==i&&i,((t,s)=>{postMessage({type:\"local_save\",name:t,batch:s},[s.buffer])})),null===b&&(b=setInterval(p,3e4)),d=u.Active):\"auth\"===s.type?c?l?(c.authorise(s.token),s.beaconSizeLimit&&l.setBeaconSizeLimit(s.beaconSizeLimit),void(s.protocolVersion&&l.setProtocolVersion(s.protocolVersion))):(console.debug(\"OR WebWorker: writer not initialised. Received auth.\"),void g()):(console.debug(\"OR WebWorker: sender not initialised. Received auth.\"),void g()):void 0}if(l){const t=l;s.forEach((s=>{55===s[0]&&(s[1]?m=setTimeout((()=>g()),18e5):clearTimeout(m)),t.writeMessage(s)}))}else postMessage(\"not_init\"),g()}else p(!0);else p()}}();\n";
4386
4112
  const CANCELED = 'canceled';
4387
4113
  const bufferStorageKey = 'or_buffer_1';
4114
+ const PROTO_VERSION = '2';
4388
4115
  const UnsuccessfulStart = (reason) => ({ reason, success: false });
4389
4116
  const SuccessfulStart = (body) => ({ ...body, success: true });
4390
4117
  var ActivityState;
@@ -4428,9 +4155,14 @@ const proto = {
4428
4155
  reset: 'reset-your-session-please',
4429
4156
  };
4430
4157
  class App {
4158
+ get tagMatcher() {
4159
+ return this.markerWatcher.matcher;
4160
+ }
4431
4161
  constructor(projectKey, sessionToken, options, signalError, insideIframe) {
4432
4162
  this.signalError = signalError;
4433
4163
  this.insideIframe = insideIframe;
4164
+ // Registered by input/img/canvas to re-emit a node when its level changes.
4165
+ this.resanitizeCallbacks = [];
4434
4166
  this.messages = [];
4435
4167
  /**
4436
4168
  * we need 2 buffers, so we don't lose anything
@@ -4442,7 +4174,7 @@ class App {
4442
4174
  this.stopCallbacks = [];
4443
4175
  this.commitCallbacks = [];
4444
4176
  this.activityState = ActivityState.NotActive;
4445
- this.version = '7.1.0'; // TODO: version compatability check inside each plugin.
4177
+ this.version = '7.3.0'; // TODO: version compatability check inside each plugin.
4446
4178
  this.socketMode = false;
4447
4179
  this.compressionThreshold = 24 * 1000;
4448
4180
  this.bc = null;
@@ -4464,9 +4196,19 @@ class App {
4464
4196
  if (!data || event.source === window)
4465
4197
  return;
4466
4198
  if (data.line === proto.startIframe) {
4467
- if (this.active())
4199
+ // Avoid corrupting an in-flight start; let it complete.
4200
+ if (this.activityState === ActivityState.Starting)
4468
4201
  return;
4469
4202
  try {
4203
+ if (this.active()) {
4204
+ this.stop();
4205
+ }
4206
+ if (data.token) {
4207
+ this.session.setSessionSecret(data.token, this.projectKey);
4208
+ }
4209
+ if (data.id !== undefined) {
4210
+ this.rootId = data.id;
4211
+ }
4470
4212
  this.allowAppStart();
4471
4213
  void this.start();
4472
4214
  }
@@ -4492,17 +4234,22 @@ class App {
4492
4234
  }
4493
4235
  }
4494
4236
  };
4495
- /**
4496
- * context ids for iframes,
4497
- * order is not so important as long as its consistent
4498
- * */
4499
4237
  this.trackedFrames = [];
4238
+ this.frameLastSeen = new Map();
4239
+ this.FRAME_STALE_MS = 1500;
4500
4240
  this.crossDomainIframeListener = (event) => {
4501
- if (!this.active() || event.source === window)
4241
+ if (event.source === window)
4502
4242
  return;
4503
4243
  const { data } = event;
4504
4244
  if (!data)
4505
4245
  return;
4246
+ // Record liveness regardless of our own active state so the queue can prune
4247
+ // stale contexts reliably once we resume dispatching commands after a cycle.
4248
+ if ((data.line === proto.polling || data.line === proto.iframeSignal) && data.context) {
4249
+ this.frameLastSeen.set(data.context, Date.now());
4250
+ }
4251
+ if (!this.active())
4252
+ return;
4506
4253
  if (data.line === proto.iframeSignal) {
4507
4254
  // @ts-ignore
4508
4255
  event.source?.postMessage({ ping: true, line: proto.parentAlive }, '*');
@@ -4600,16 +4347,41 @@ class App {
4600
4347
  if (!this.pollingQueue.order.length) {
4601
4348
  return;
4602
4349
  }
4603
- const nextCommand = this.pollingQueue.order[0];
4604
- if (nextCommand && this.pollingQueue[nextCommand].length === 0) {
4605
- this.pollingQueue.order = this.pollingQueue.order.filter((c) => c !== nextCommand);
4350
+ this.pruneStaleFrames();
4351
+ const liveSet = new Set(this.trackedFrames);
4352
+ while (this.pollingQueue.order.length > 0) {
4353
+ const head = this.pollingQueue.order[0];
4354
+ this.pollingQueue[head] = this.pollingQueue[head].filter((ctx) => liveSet.has(ctx));
4355
+ if (this.pollingQueue[head].length === 0) {
4356
+ delete this.pollingQueue[head];
4357
+ this.pollingQueue.order.shift();
4358
+ }
4359
+ else {
4360
+ break;
4361
+ }
4362
+ }
4363
+ if (!this.pollingQueue.order.length) {
4606
4364
  return;
4607
4365
  }
4366
+ const nextCommand = this.pollingQueue.order[0];
4608
4367
  if (this.pollingQueue[nextCommand].includes(data.context)) {
4609
4368
  this.pollingQueue[nextCommand] = this.pollingQueue[nextCommand].filter((c) => c !== data.context);
4369
+ const message = { line: nextCommand };
4370
+ if (nextCommand === proto.startIframe) {
4371
+ message.token = this.session.getSessionSecret(this.projectKey);
4372
+ const targetFrame = this.pageFrames.find((f) => f.contentWindow === event.source) ||
4373
+ Array.from(document.querySelectorAll('iframe')).find((f) => f.contentWindow === event.source);
4374
+ if (targetFrame) {
4375
+ const nodeId = targetFrame[this.options.node_id];
4376
+ if (nodeId !== undefined) {
4377
+ message.id = nodeId;
4378
+ }
4379
+ }
4380
+ }
4610
4381
  // @ts-ignore
4611
- event.source?.postMessage({ line: nextCommand }, '*');
4382
+ event.source?.postMessage(message, '*');
4612
4383
  if (this.pollingQueue[nextCommand].length === 0) {
4384
+ delete this.pollingQueue[nextCommand];
4613
4385
  this.pollingQueue.order.shift();
4614
4386
  }
4615
4387
  }
@@ -4623,6 +4395,11 @@ class App {
4623
4395
  order: [],
4624
4396
  };
4625
4397
  this.addCommand = (cmd) => {
4398
+ this.pruneStaleFrames();
4399
+ if (this.pollingQueue[cmd]) {
4400
+ this.pollingQueue[cmd] = Array.from(new Set([...this.pollingQueue[cmd], ...this.trackedFrames]));
4401
+ return;
4402
+ }
4626
4403
  this.pollingQueue.order.push(cmd);
4627
4404
  this.pollingQueue[cmd] = [...this.trackedFrames];
4628
4405
  };
@@ -4751,6 +4528,26 @@ class App {
4751
4528
  this.restartCanvasTracking = () => {
4752
4529
  this.canvasRecorder?.restartTracking();
4753
4530
  };
4531
+ this.attachResanitizeCallback = (cb) => {
4532
+ this.resanitizeCallbacks.push(cb);
4533
+ };
4534
+ this.callResanitizeCallbacks = (node, id) => {
4535
+ this.resanitizeCallbacks.forEach((cb) => cb(node, id));
4536
+ };
4537
+ this.resanitize = (el) => {
4538
+ const root = el ?? (IN_BROWSER ? document.documentElement : undefined);
4539
+ if (!root) {
4540
+ return;
4541
+ }
4542
+ this.observer.resanitizeSubtree(root);
4543
+ };
4544
+ this.checkSanitization = (el) => {
4545
+ const id = this.nodes.getID(el);
4546
+ if (id === undefined) {
4547
+ return undefined;
4548
+ }
4549
+ return this.sanitizer.getLevel(id);
4550
+ };
4754
4551
  this.flushBuffer = async (buffer) => {
4755
4552
  return new Promise((res, reject) => {
4756
4553
  if (buffer.length === 0) {
@@ -4792,6 +4589,7 @@ class App {
4792
4589
  __is_snippet: false,
4793
4590
  __debug_report_edp: null,
4794
4591
  __debug__: LogLevel.Silent,
4592
+ __local_debug: false,
4795
4593
  localStorage: null,
4796
4594
  sessionStorage: null,
4797
4595
  forceSingleTab: false,
@@ -4850,9 +4648,7 @@ class App {
4850
4648
  });
4851
4649
  this.session.attachUpdateCallback(({ userID, metadata }) => {
4852
4650
  if (userID != null) {
4853
- if (!userID ||
4854
- typeof userID !== 'string' ||
4855
- userID.trim().length === 0) {
4651
+ if (!userID || typeof userID !== 'string' || userID.trim().length === 0) {
4856
4652
  this.debug.warn('Invalid userID (must be type string), ignoring.');
4857
4653
  return;
4858
4654
  }
@@ -4874,9 +4670,6 @@ class App {
4874
4670
  window.addEventListener('message', this.parentCrossDomainFrameListener);
4875
4671
  window.addEventListener('message', this.crossDomainIframeListener);
4876
4672
  setInterval(() => {
4877
- if (document.hidden) {
4878
- return;
4879
- }
4880
4673
  window.parent.postMessage({
4881
4674
  line: proto.polling,
4882
4675
  context: this.contextId,
@@ -4939,6 +4732,16 @@ class App {
4939
4732
  };
4940
4733
  }
4941
4734
  }
4735
+ pruneStaleFrames() {
4736
+ const staleAfter = Date.now() - this.FRAME_STALE_MS;
4737
+ this.trackedFrames = this.trackedFrames.filter((ctx) => {
4738
+ const last = this.frameLastSeen.get(ctx);
4739
+ if (last !== undefined && last >= staleAfter)
4740
+ return true;
4741
+ this.frameLastSeen.delete(ctx);
4742
+ return false;
4743
+ });
4744
+ }
4942
4745
  allowAppStart() {
4943
4746
  this.canStart = true;
4944
4747
  if (this.startTimeout) {
@@ -5038,22 +4841,39 @@ class App {
5038
4841
  }
5039
4842
  else if (data.type === 'compress') {
5040
4843
  const batch = data.batch;
4844
+ const dataType = data.dataType;
5041
4845
  const batchSize = batch.byteLength;
5042
- if (batchSize > this.compressionThreshold) {
5043
- gzip(data.batch, { mtime: 0 }, (err, result) => {
5044
- if (err) {
5045
- this.debug.error('OxvoSessions compression error:', err);
5046
- this.worker?.postMessage({ type: 'uncompressed', batch: batch });
5047
- }
5048
- else {
5049
- this.worker?.postMessage({ type: 'compressed', batch: result });
5050
- }
4846
+ const hasCompressionAPI = 'CompressionStream' in globalThis;
4847
+ if (batchSize > this.compressionThreshold && hasCompressionAPI) {
4848
+ const blob = new Blob([batch]);
4849
+ const stream = blob.stream().pipeThrough(new CompressionStream('gzip'));
4850
+ new Response(stream)
4851
+ .arrayBuffer()
4852
+ .then((compressedBuffer) => {
4853
+ this.worker?.postMessage({
4854
+ type: 'compressed',
4855
+ batch: new Uint8Array(compressedBuffer),
4856
+ dataType,
4857
+ });
4858
+ })
4859
+ .catch((err) => {
4860
+ this.debug.error('OxvoSessions compression error:', err);
4861
+ this.worker?.postMessage({ type: 'uncompressed', batch: batch, dataType });
5051
4862
  });
5052
4863
  }
5053
4864
  else {
5054
- this.worker?.postMessage({ type: 'uncompressed', batch: batch });
4865
+ this.worker?.postMessage({ type: 'uncompressed', batch: batch, dataType });
5055
4866
  }
5056
4867
  }
4868
+ else if (data.type === 'local_save') {
4869
+ const blob = new Blob([data.batch], { type: 'application/octet-stream' });
4870
+ const url = URL.createObjectURL(blob);
4871
+ const a = document.createElement('a');
4872
+ a.href = url;
4873
+ a.download = data.name;
4874
+ a.click();
4875
+ URL.revokeObjectURL(url);
4876
+ }
5057
4877
  else if (data.type === 'queue_empty') {
5058
4878
  this.onSessionSent();
5059
4879
  }
@@ -5092,19 +4912,31 @@ class App {
5092
4912
  this.messages.length = 0;
5093
4913
  return;
5094
4914
  }
5095
- if (this.worker === undefined || !this.messages.length) {
4915
+ if (this.worker === undefined) {
5096
4916
  return;
5097
4917
  }
5098
4918
  if (!this.messages.length) {
5099
- // Release empty batches every 30 secs (1000 * 30ms)
4919
+ // Send a keepalive batch every ~30s (1000 * 30ms) to keep the session active
5100
4920
  if (this.emptyBatchCounter < 1000) {
5101
4921
  this.emptyBatchCounter++;
5102
4922
  return;
5103
4923
  }
4924
+ // Keepalive: send just Timestamp + TabData so the session stays alive
4925
+ this.emptyBatchCounter = 0;
4926
+ try {
4927
+ this.worker?.postMessage([Timestamp(this.timestamp()), TabData(this.session.getTabId())]);
4928
+ }
4929
+ catch (e) {
4930
+ this._debug('worker_keepalive', e);
4931
+ }
4932
+ return;
5104
4933
  }
5105
4934
  this.emptyBatchCounter = 0;
5106
4935
  try {
5107
4936
  requestIdleCb(() => {
4937
+ if (!this.messages.length) {
4938
+ return;
4939
+ }
5108
4940
  this.messages.unshift(Timestamp(this.timestamp()), TabData(this.session.getTabId()));
5109
4941
  this.worker?.postMessage(this.messages);
5110
4942
  this.commitCallbacks.forEach((cb) => cb(this.messages));
@@ -5298,9 +5130,14 @@ class App {
5298
5130
  }
5299
5131
  }
5300
5132
  checkSessionToken(startNewSession) {
5301
- const lsReset = this.sessionStorage.getItem(this.options.session_reset_key) !== null;
5302
- const needNewSessionID = startNewSession || lsReset;
5133
+ const needReset = this.sessionStorage.getItem(this.options.session_reset_key) !== null;
5134
+ let needNewSessionID = startNewSession || needReset;
5303
5135
  const sessionToken = this.session.getSessionSecret(this.projectKey);
5136
+ const storedVersion = this.sessionStorage.getItem(`${this.options.session_token_key}_version`);
5137
+ if (!storedVersion || storedVersion !== PROTO_VERSION) {
5138
+ needNewSessionID = true;
5139
+ this.sessionStorage.setItem(`${this.options.session_token_key}_version`, PROTO_VERSION);
5140
+ }
5304
5141
  return needNewSessionID || !sessionToken;
5305
5142
  }
5306
5143
  /**
@@ -5478,6 +5315,7 @@ class App {
5478
5315
  connAttemptCount: this.options.connAttemptCount,
5479
5316
  connAttemptGap: this.options.connAttemptGap,
5480
5317
  tabId: this.session.getTabId(),
5318
+ localDebug: this.options.__local_debug,
5481
5319
  });
5482
5320
  const ingestPoint = normalizeEndpoint(this.options.endpoint);
5483
5321
  const r = await fetch(ingestPoint + '/v1/bootstrap', {
@@ -5492,7 +5330,7 @@ class App {
5492
5330
  token: '',
5493
5331
  })),
5494
5332
  });
5495
- const { session = {}, client = {}, limits = {} } = await r.json();
5333
+ const { session = {}, client = {}, limits = {}, protocolVersion: offlineProtocolVersion } = await r.json();
5496
5334
  const { token, projectId } = session;
5497
5335
  const { browser: userBrowser, city: userCity, country: userCountry, device: userDevice, os: userOS, state: userState, } = client;
5498
5336
  const { beacon: beaconSizeLimit } = limits;
@@ -5500,6 +5338,7 @@ class App {
5500
5338
  type: 'auth',
5501
5339
  token,
5502
5340
  beaconSizeLimit,
5341
+ protocolVersion: offlineProtocolVersion,
5503
5342
  });
5504
5343
  this.session.assign({ projectID: projectId });
5505
5344
  this.session.setUserInfo({
@@ -5520,6 +5359,9 @@ class App {
5520
5359
  if (Object.keys(startOpts).length !== 0) {
5521
5360
  this.prevOpts = startOpts;
5522
5361
  }
5362
+ if (startOpts.startCallback) {
5363
+ this.userStartCallback = startOpts.startCallback;
5364
+ }
5523
5365
  const isColdStart = this.activityState === ActivityState.ColdStart;
5524
5366
  if (isColdStart && this.coldInterval) {
5525
5367
  clearInterval(this.coldInterval);
@@ -5561,6 +5403,7 @@ class App {
5561
5403
  connAttemptCount: this.options.connAttemptCount,
5562
5404
  connAttemptGap: this.options.connAttemptGap,
5563
5405
  tabId: this.session.getTabId(),
5406
+ localDebug: this.options.__local_debug,
5564
5407
  });
5565
5408
  const sessionToken = this.session.getSessionSecret(this.projectKey);
5566
5409
  const isNewSession = this.checkSessionToken(startOpts.startNewSession);
@@ -5576,7 +5419,7 @@ class App {
5576
5419
  body: JSON.stringify(this.buildStartPayload({
5577
5420
  timestamp,
5578
5421
  dry: false,
5579
- bufferMs: timestamp - this.coldStartTs,
5422
+ bufferMs: this.coldStartTs ? timestamp - this.coldStartTs : 0,
5580
5423
  token: isNewSession ? '' : sessionToken,
5581
5424
  rule: conditionName,
5582
5425
  width: window.screen.width,
@@ -5593,12 +5436,12 @@ class App {
5593
5436
  this.signalError(reason, []);
5594
5437
  return UnsuccessfulStart(reason);
5595
5438
  }
5596
- const { session = {}, device = {}, client = {}, limits = {}, canvas = {}, flags = {} } = await r.json();
5439
+ const { session = {}, device = {}, client = {}, limits = {}, canvas = {}, flags = {}, protocolVersion, } = await r.json();
5597
5440
  const { token, id: sessionID, projectId: projectID, delayMs: delay, startedAt: startTimestamp, } = session;
5598
5441
  const { id: userUUID } = device;
5599
5442
  const { browser: userBrowser, city: userCity, country: userCountry, device: userDevice, os: userOS, state: userState, } = client;
5600
5443
  const { beacon: beaconSizeLimit, compressAt: compressionThreshold } = limits;
5601
- const { enabled: canvasEnabled, quality: canvasQuality, fps: canvasFPS } = canvas;
5444
+ const { enabled: canvasEnabled, quality: canvasQuality, fps: canvasFPS, framesSupport, } = canvas;
5602
5445
  const socketOnly = flags?.socketOnly;
5603
5446
  if (typeof token !== 'string' ||
5604
5447
  typeof userUUID !== 'string' ||
@@ -5640,6 +5483,7 @@ class App {
5640
5483
  type: 'auth',
5641
5484
  token,
5642
5485
  beaconSizeLimit,
5486
+ protocolVersion,
5643
5487
  });
5644
5488
  }
5645
5489
  if (!isNewSession && token === sessionToken) {
@@ -5655,10 +5499,9 @@ class App {
5655
5499
  // TODO: start as early as possible (before receiving the token)
5656
5500
  /** after start */
5657
5501
  this.startCallbacks.forEach((cb) => cb(onStartInfo)); // MBTODO: callbacks after DOM "mounted" (observed)
5658
- if (startOpts.startCallback) {
5659
- startOpts.startCallback(SuccessfulStart(onStartInfo));
5502
+ if (this.userStartCallback) {
5503
+ this.userStartCallback(SuccessfulStart(onStartInfo));
5660
5504
  }
5661
- await this.markerWatcher.fetchTags(normalizeEndpoint(this.options.endpoint), token);
5662
5505
  this.activityState = ActivityState.Active;
5663
5506
  if (this.options.crossdomain?.enabled) {
5664
5507
  void this.bootChildrenFrames();
@@ -5672,6 +5515,7 @@ class App {
5672
5515
  isDebug: this.options.canvas.__save_canvas_locally,
5673
5516
  fixedScaling: this.options.canvas.fixedCanvasScaling,
5674
5517
  useAnimationFrame: this.options.canvas.useAnimationFrame,
5518
+ framesSupport: !!framesSupport,
5675
5519
  });
5676
5520
  }
5677
5521
  /** --------------- COLD START BUFFER ------------------*/
@@ -5694,6 +5538,7 @@ class App {
5694
5538
  }
5695
5539
  this.ticker.start();
5696
5540
  this.canvasRecorder?.startTracking();
5541
+ void this.markerWatcher.fetchTags(normalizeEndpoint(this.options.endpoint), token);
5697
5542
  return SuccessfulStart(onStartInfo);
5698
5543
  }
5699
5544
  catch (reason) {
@@ -5818,6 +5663,7 @@ class App {
5818
5663
  this.canvasRecorder?.clear();
5819
5664
  this.messages.length = 0;
5820
5665
  this.parentActive = false;
5666
+ this.pageFrames = [];
5821
5667
  }
5822
5668
  finally {
5823
5669
  this.activityState = ActivityState.NotActive;
@@ -6315,6 +6161,12 @@ function Img (app) {
6315
6161
  sendImgAttrs(node);
6316
6162
  observer.observe(node, { attributes: true, attributeFilter: ['src', 'srcset'] });
6317
6163
  });
6164
+ // On a runtime level change, re-evaluate placeholder vs real src for this image.
6165
+ app.attachResanitizeCallback((node) => {
6166
+ if (hasTag(node, 'img')) {
6167
+ sendImgAttrs(node);
6168
+ }
6169
+ });
6318
6170
  }
6319
6171
 
6320
6172
  const INPUT_TYPES = [
@@ -6372,19 +6224,24 @@ const labelElementFor = IN_BROWSER && 'labels' in HTMLInputElement.prototype
6372
6224
  }
6373
6225
  }
6374
6226
  };
6375
- function getInputLabel(node) {
6376
- let label = getLabelAttribute(node);
6377
- if (label === null) {
6378
- const labelElement = labelElementFor(node);
6379
- label =
6380
- (labelElement && labelElement.innerText) ||
6381
- node.placeholder ||
6382
- node.name ||
6383
- node.id ||
6384
- node.className ||
6385
- node.type;
6386
- }
6387
- return normSpaces(label).slice(0, 100);
6227
+ function getInputLabel(node, customAttributes) {
6228
+ const oxvosessionsLabel = getLabelAttribute(node);
6229
+ if (oxvosessionsLabel !== null)
6230
+ return normSpaces(oxvosessionsLabel).slice(0, 100);
6231
+ const customAttributeLabel = getCustomAttributeLabel(node, customAttributes);
6232
+ if (customAttributeLabel)
6233
+ return customAttributeLabel;
6234
+ if (node.id)
6235
+ return `#${node.id}`;
6236
+ const classLabel = getClassSelector(node);
6237
+ if (classLabel)
6238
+ return classLabel;
6239
+ const labelElement = labelElementFor(node);
6240
+ const label = node.name ||
6241
+ node.placeholder ||
6242
+ (labelElement && labelElement.innerText) ||
6243
+ node.type;
6244
+ return normSpaces(label || '').slice(0, 100);
6388
6245
  }
6389
6246
  const InputMode = {
6390
6247
  Plain: 0,
@@ -6406,9 +6263,12 @@ function Input (app, opts) {
6406
6263
  }
6407
6264
  else if (app.sanitizer.isObscured(id) ||
6408
6265
  (inputMode === InputMode.Plain &&
6409
- ((options.maskInputNumbers && node.type !== 'date' && /\d\d\d\d/.test(value)) ||
6266
+ ((options.maskInputNumbers &&
6267
+ node.type !== 'date' &&
6268
+ /\d\d\d\d/.test(value)) ||
6410
6269
  (options.maskInputDates && node.type === 'date') ||
6411
- (options.maskInputEmails && (node.type === 'email' || !!~value.indexOf('@')))))) {
6270
+ (options.maskInputEmails &&
6271
+ (node.type === 'email' || !!~value.indexOf('@')))))) {
6412
6272
  inputMode = InputMode.Obscured;
6413
6273
  }
6414
6274
  let mask = 0;
@@ -6428,11 +6288,24 @@ function Input (app, opts) {
6428
6288
  const { value, mask } = getInputValue(id, node);
6429
6289
  app.send(SetInputValue(id, value, mask));
6430
6290
  }
6291
+ const tagSelectorMap = new Map();
6431
6292
  const inputValues = new Map();
6432
6293
  const checkboxValues = new Map();
6294
+ function getTagSelector(id, node) {
6295
+ const cached = tagSelectorMap.get(id);
6296
+ if (cached !== undefined)
6297
+ return cached;
6298
+ const tagMatch = app.tagMatcher.match(node);
6299
+ if (tagMatch) {
6300
+ tagSelectorMap.set(id, tagMatch.selector);
6301
+ return tagMatch.selector;
6302
+ }
6303
+ return null;
6304
+ }
6433
6305
  app.attachStopCallback(() => {
6434
6306
  inputValues.clear();
6435
6307
  checkboxValues.clear();
6308
+ tagSelectorMap.clear();
6436
6309
  });
6437
6310
  function trackInputValue(id, node) {
6438
6311
  if (inputValues.get(id) === node.value) {
@@ -6465,12 +6338,19 @@ function Input (app, opts) {
6465
6338
  }, 3);
6466
6339
  function sendInputChange(id, node, hesitationTime, inputTime) {
6467
6340
  const { value, mask } = getInputValue(id, node);
6468
- let label = getInputLabel(node);
6341
+ let label = getTagSelector(id, node) || getInputLabel(node, options.customAttributes);
6469
6342
  if (app.sanitizer.maskAllByDefault) {
6470
6343
  label = label.replaceAll(/./g, '*');
6471
6344
  }
6472
6345
  app.send(InputChange(id, value, mask !== 0, label, hesitationTime, inputTime));
6473
6346
  }
6347
+ // Re-emit a field's value when its sanitization level changes at runtime.
6348
+ // getInputValue() reads the current level, so re-sending applies the new mask.
6349
+ app.attachResanitizeCallback((node, id) => {
6350
+ if (isTextFieldElement(node) || hasTag(node, 'select')) {
6351
+ sendInputValue(id, node);
6352
+ }
6353
+ });
6474
6354
  app.nodes.attachNodeCallback(app.safe((node) => {
6475
6355
  const id = app.nodes.getID(node);
6476
6356
  if (id === undefined) {
@@ -6574,21 +6454,28 @@ function _getTarget(target, document) {
6574
6454
  return target === document.documentElement ? null : target;
6575
6455
  }
6576
6456
  function Mouse (app, options) {
6577
- const { disableClickmaps = false } = options || {};
6457
+ const { disableClickmaps = false, customAttributes } = options || {};
6578
6458
  function getTargetLabel(target) {
6579
6459
  const dl = getLabelAttribute(target);
6580
6460
  if (dl !== null) {
6581
6461
  return dl;
6582
6462
  }
6583
6463
  if (hasTag(target, 'input')) {
6584
- return getInputLabel(target);
6585
- }
6464
+ return getInputLabel(target, customAttributes);
6465
+ }
6466
+ const customAttributeLabel = getCustomAttributeLabel(target, customAttributes);
6467
+ if (customAttributeLabel)
6468
+ return customAttributeLabel;
6469
+ if (target.id)
6470
+ return `#${target.id}`;
6471
+ const classLabel = getClassSelector(target);
6472
+ if (classLabel)
6473
+ return classLabel;
6586
6474
  if (isClickable(target)) {
6587
6475
  let label = '';
6588
6476
  if (target instanceof HTMLElement) {
6589
6477
  label = app.sanitizer.getInnerTextSecure(target);
6590
6478
  }
6591
- label = label || target.id || target.className;
6592
6479
  return normSpaces(label).slice(0, 100);
6593
6480
  }
6594
6481
  return '';
@@ -6641,7 +6528,13 @@ function Mouse (app, options) {
6641
6528
  };
6642
6529
  const patchDocument = (document, topframe = false) => {
6643
6530
  function getSelector(id, target) {
6644
- return (selectorMap[id] = selectorMap[id] || _getSelector(target));
6531
+ if (selectorMap[id])
6532
+ return selectorMap[id];
6533
+ const tagMatch = app.tagMatcher.match(target);
6534
+ if (tagMatch) {
6535
+ return (selectorMap[id] = tagMatch.selector);
6536
+ }
6537
+ return (selectorMap[id] = _getSelector(target));
6645
6538
  }
6646
6539
  const attachListener = topframe
6647
6540
  ? app.attachEventListener.bind(app) // attached/removed on start/stop
@@ -6732,6 +6625,11 @@ function getCSSPath(el) {
6732
6625
  return false;
6733
6626
  if (el.id)
6734
6627
  return `#${cssEscape(el.id)}`;
6628
+ // if has data attributes - use them as they are more likely to be stable and unique
6629
+ const dataAttr = Array.from(el.attributes).find(attr => attr.name.startsWith('data-'));
6630
+ if (dataAttr) {
6631
+ return `[${dataAttr.name}="${cssEscape(dataAttr.value)}"]`;
6632
+ }
6735
6633
  const parts = [];
6736
6634
  while (el && el.nodeType === 1 && el !== el.ownerDocument) {
6737
6635
  if (el.id) {
@@ -7072,11 +6970,27 @@ function Scroll (app, insideIframe) {
7072
6970
  }, 5, false);
7073
6971
  }
7074
6972
 
6973
+ const NativeURL = URL;
6974
+ const defaultUrlSanitizer = (url) => {
6975
+ const hiddenQueryParams = ['jwt', 'password', 'reset-password', 'invitation', 'secret', 'token'];
6976
+ try {
6977
+ const u = new NativeURL(url);
6978
+ u.searchParams.forEach((value, key) => {
6979
+ if (hiddenQueryParams.includes(key.toLowerCase())) {
6980
+ u.searchParams.set(key, '*'.repeat(value.length));
6981
+ }
6982
+ });
6983
+ return u.toString();
6984
+ }
6985
+ catch {
6986
+ return url;
6987
+ }
6988
+ };
7075
6989
  function Viewport (app, options) {
7076
6990
  let url, width, height;
7077
6991
  let navigationStart;
7078
6992
  let referrer = document.referrer;
7079
- const urlSanitizer = options?.urlSanitizer || ((u) => u);
6993
+ const urlSanitizer = options?.urlSanitizer || defaultUrlSanitizer;
7080
6994
  const titleSanitizer = options?.titleSanitizer || ((t) => t);
7081
6995
  const sendSetPageLocation = app.safe(() => {
7082
6996
  const currURL = document.URL;
@@ -8902,6 +8816,8 @@ function webAnimations(app, options = {}) {
8902
8816
  const lastKF = anim.effect.getKeyframes().at(-1);
8903
8817
  if (!lastKF)
8904
8818
  return;
8819
+ if (!el || !(el instanceof Element) || !el.isConnected)
8820
+ return;
8905
8821
  const computedStyle = getComputedStyle(el);
8906
8822
  const keys = Object.keys(lastKF).filter((p) => !toIgnore.includes(p));
8907
8823
  // @ts-ignore
@@ -9313,7 +9229,7 @@ class ConstantProperties {
9313
9229
  user_id: this.user_id,
9314
9230
  distinct_id: this.deviceId,
9315
9231
  sdk_edition: 'web',
9316
- sdk_version: '7.1.0',
9232
+ sdk_version: '7.3.0',
9317
9233
  timezone: getUTCOffsetString(),
9318
9234
  search_engine: this.searchEngine,
9319
9235
  };
@@ -9701,10 +9617,11 @@ class People {
9701
9617
  * Creates batches of events, then sends them at intervals.
9702
9618
  */
9703
9619
  class Batcher {
9704
- constructor(backendUrl, getToken, init) {
9620
+ constructor(backendUrl, getToken, init, standalone) {
9705
9621
  this.backendUrl = backendUrl;
9706
9622
  this.getToken = getToken;
9707
9623
  this.init = init;
9624
+ this.standalone = standalone;
9708
9625
  this.autosendInterval = 5 * 1000;
9709
9626
  this.retryTimeout = 3 * 1000;
9710
9627
  this.retryLimit = 3;
@@ -9714,10 +9631,20 @@ class Batcher {
9714
9631
  [categories.events]: [],
9715
9632
  };
9716
9633
  this.intervalId = null;
9634
+ this.stopped = false;
9635
+ this.paused = false;
9636
+ this.onVisibilityChange = () => {
9637
+ this.paused = document.hidden;
9638
+ };
9717
9639
  }
9718
9640
  getBatches() {
9719
9641
  this.batch[categories.people] = this.dedupePeopleEvents();
9720
- const finalData = { data: this.batch };
9642
+ const finalData = {
9643
+ data: {
9644
+ [categories.people]: [...this.batch[categories.people]],
9645
+ [categories.events]: [...this.batch[categories.events]],
9646
+ },
9647
+ };
9721
9648
  return finalData;
9722
9649
  }
9723
9650
  addEvent(event) {
@@ -9793,6 +9720,9 @@ class Batcher {
9793
9720
  return Array.from(uniqueEventsByType.values());
9794
9721
  }
9795
9722
  sendBatch(batch) {
9723
+ if (this.stopped) {
9724
+ return;
9725
+ }
9796
9726
  const sentBatch = batch;
9797
9727
  let attempts = 0;
9798
9728
  const send = () => {
@@ -9809,11 +9739,27 @@ class Batcher {
9809
9739
  },
9810
9740
  body: JSON.stringify(sentBatch),
9811
9741
  })
9812
- .then((response) => {
9742
+ .then(async (response) => {
9743
+ if (response.status === 401) {
9744
+ const body = await response.json().catch(() => null);
9745
+ if (!this.standalone && body?.error === 'token expired') {
9746
+ this.stop();
9747
+ return;
9748
+ }
9749
+ if (attempts < this.retryLimit) {
9750
+ return this.init().then(() => {
9751
+ send();
9752
+ });
9753
+ }
9754
+ return;
9755
+ }
9813
9756
  if (response.status === 403) {
9814
- this.init().then(() => {
9815
- send();
9816
- });
9757
+ if (attempts < this.retryLimit) {
9758
+ return this.init().then(() => {
9759
+ send();
9760
+ });
9761
+ }
9762
+ return;
9817
9763
  }
9818
9764
  if (!response.ok) {
9819
9765
  throw new Error(`HTTP error! status: ${response.status}`);
@@ -9828,8 +9774,15 @@ class Batcher {
9828
9774
  void send();
9829
9775
  }
9830
9776
  startAutosend() {
9777
+ if (this.intervalId) {
9778
+ clearInterval(this.intervalId);
9779
+ }
9780
+ this.paused = document.hidden;
9781
+ document.addEventListener('visibilitychange', this.onVisibilityChange);
9831
9782
  this.intervalId = setInterval(() => {
9832
- this.flush();
9783
+ if (!this.paused) {
9784
+ this.flush();
9785
+ }
9833
9786
  }, this.autosendInterval);
9834
9787
  }
9835
9788
  flush() {
@@ -9849,6 +9802,12 @@ class Batcher {
9849
9802
  clearInterval(this.intervalId);
9850
9803
  this.intervalId = null;
9851
9804
  }
9805
+ document.removeEventListener('visibilitychange', this.onVisibilityChange);
9806
+ this.stopped = true;
9807
+ }
9808
+ restart() {
9809
+ this.stopped = false;
9810
+ this.startAutosend();
9852
9811
  }
9853
9812
  }
9854
9813
 
@@ -9880,7 +9839,9 @@ class Analytics {
9880
9839
  if (this.standalone) {
9881
9840
  return this.token;
9882
9841
  }
9883
- return this.getToken();
9842
+ else {
9843
+ return this.getToken();
9844
+ }
9884
9845
  };
9885
9846
  this._getTimestamp = () => {
9886
9847
  if (this.standalone) {
@@ -9934,6 +9895,19 @@ class Analytics {
9934
9895
  }
9935
9896
  }
9936
9897
  };
9898
+ /**
9899
+ * Used by tracker when running in blundled mode
9900
+ */
9901
+ this.onStart = () => {
9902
+ if (!this.standalone) {
9903
+ this.batcher.restart();
9904
+ }
9905
+ };
9906
+ this.onStop = () => {
9907
+ if (!this.standalone) {
9908
+ this.batcher.stop();
9909
+ }
9910
+ };
9937
9911
  this.reset = () => {
9938
9912
  this.people.reset(true);
9939
9913
  this.events.reset();
@@ -9972,7 +9946,7 @@ class Analytics {
9972
9946
  this.standalone = !options.notStandalone;
9973
9947
  this.token = this.sessionStorage.getItem(STORAGEKEY);
9974
9948
  this.constantProperties = new ConstantProperties(this.localStorage, this.sessionStorage);
9975
- this.batcher = new Batcher(this.backendUrl, this._getToken, this.init);
9949
+ this.batcher = new Batcher(this.backendUrl, this._getToken, this.init, this.standalone);
9976
9950
  this.events = new Events(this.constantProperties, this._getTimestamp, this.batcher);
9977
9951
  this.people = new People(this.constantProperties, this._getTimestamp, this.setUserId, this.batcher);
9978
9952
  if (options.notStandalone) {
@@ -10025,7 +9999,7 @@ class API {
10025
9999
  this.signalStartIssue = (reason, missingApi) => {
10026
10000
  const doNotTrack = this.checkDoNotTrack();
10027
10001
  console.log("Tracker couldn't start due to:", JSON.stringify({
10028
- trackerVersion: '7.1.0',
10002
+ trackerVersion: '7.3.0',
10029
10003
  siteKey: this.options.siteKey,
10030
10004
  doNotTrack,
10031
10005
  reason: missingApi.length ? `missing api: ${missingApi.join(',')}` : reason,
@@ -10037,6 +10011,31 @@ class API {
10037
10011
  }
10038
10012
  this.app.restartCanvasTracking();
10039
10013
  };
10014
+ /**
10015
+ * Re-evaluates sanitization against the current DOM and re-emits whatever
10016
+ * changed, updating already-recorded nodes mid-session. Call after toggling
10017
+ * `data-oxvosessions-*` attributes or after changing whatever your `domSanitizer`
10018
+ * keys on (class/id/etc).
10019
+ *
10020
+ * @param el - the highest node you changed; omit to re-scan the whole document;
10021
+ * scanning the entire doc is O(dom size)
10022
+ * */
10023
+ this.resanitize = (el) => {
10024
+ if (this.app === null) {
10025
+ return;
10026
+ }
10027
+ this.app.resanitize(el);
10028
+ };
10029
+ /**
10030
+ * Returns the sanitization level the tracker currently has for a node
10031
+ * (0 = Plain, 1 = Obscured, 2 = Hidden), or undefined if it isn't tracked.
10032
+ * */
10033
+ this.checkSanitization = (el) => {
10034
+ if (this.app === null) {
10035
+ return undefined;
10036
+ }
10037
+ return this.app.checkSanitization(el);
10038
+ };
10040
10039
  this.getSessionLink = (options) => {
10041
10040
  if (this.app === null) {
10042
10041
  return undefined;
@@ -10179,6 +10178,12 @@ class API {
10179
10178
  : (options.analytics?.endpoint ?? options.endpoint ?? defaultEdp),
10180
10179
  projectKey: options.siteKey,
10181
10180
  });
10181
+ app.attachStartCallback(() => {
10182
+ this.analytics?.onStart();
10183
+ });
10184
+ app.attachStopCallback(() => {
10185
+ this.analytics?.onStop();
10186
+ });
10182
10187
  }
10183
10188
  if (!this.crossdomainMode) {
10184
10189
  // no need to send iframe viewport data since its a node for us
@@ -10190,7 +10195,7 @@ class API {
10190
10195
  // no tabs in iframes yet
10191
10196
  Tabs(app);
10192
10197
  }
10193
- Mouse(app, options.mouse);
10198
+ Mouse(app, { ...options.mouse, customAttributes: options.customAttributes });
10194
10199
  // inside iframe, we ignore viewport scroll
10195
10200
  Scroll(app, this.crossdomainMode);
10196
10201
  CSSRules(app, options.css);
@@ -10457,11 +10462,13 @@ class BrowserSingleton {
10457
10462
  if (!IN_BROWSER) {
10458
10463
  return Promise.resolve({ success: false, reason: 'Not in browser environment' });
10459
10464
  }
10460
- if (!this.ensureConfigured()) {
10465
+ if (!this.ensureConfigured() || !this.instance) {
10461
10466
  return Promise.resolve({ success: false, reason: 'Browser not configured' });
10462
10467
  }
10463
- return (this.instance?.start(startOpts) ||
10464
- Promise.resolve({ success: false, reason: 'Browser not initialized' }));
10468
+ return this.instance.start(startOpts).catch((reason) => ({
10469
+ success: false,
10470
+ reason: typeof reason === 'string' ? reason : String(reason),
10471
+ }));
10465
10472
  }
10466
10473
  /**
10467
10474
  * Stop the session and return sessionHash
@@ -10480,14 +10487,8 @@ class BrowserSingleton {
10480
10487
  this.instance.identify(id, properties);
10481
10488
  }
10482
10489
  get analytics() {
10483
- if (this.instance?.analytics) {
10484
- return this.instance.analytics;
10485
- }
10486
- else {
10487
- return null;
10488
- }
10490
+ return this.instance?.analytics ?? null;
10489
10491
  }
10490
- ;
10491
10492
  /**
10492
10493
  * Set metadata for the current session
10493
10494
  *
@@ -10662,6 +10663,51 @@ class BrowserSingleton {
10662
10663
  }
10663
10664
  return this.instance.getTabId();
10664
10665
  }
10666
+ /**
10667
+ * Re-evaluates sanitization against the current DOM and re-emits whatever
10668
+ * changed, updating already-recorded nodes mid-session. Call after toggling
10669
+ * `data-oxvosessions-*` attributes or after changing whatever your `domSanitizer`
10670
+ * keys on (class/id/etc).
10671
+ *
10672
+ * @param el - the highest node you changed; omit to re-scan the whole document.
10673
+ * */
10674
+ resanitize(el) {
10675
+ if (!IN_BROWSER || !this.ensureConfigured() || !this.instance) {
10676
+ return;
10677
+ }
10678
+ return this.instance.resanitize(el);
10679
+ }
10680
+ /**
10681
+ * Returns the sanitization level the tracker currently has for a node
10682
+ * (0 = Plain, 1 = Obscured, 2 = Hidden), or undefined if it isn't tracked.
10683
+ * */
10684
+ checkSanitization(el) {
10685
+ if (!IN_BROWSER || !this.ensureConfigured() || !this.instance) {
10686
+ return undefined;
10687
+ }
10688
+ return this.instance.checkSanitization(el);
10689
+ }
10690
+ incident(options) {
10691
+ if (!IN_BROWSER || !this.ensureConfigured() || !this.instance) {
10692
+ return;
10693
+ }
10694
+ this.instance.incident(options);
10695
+ }
10696
+ /**
10697
+ * Use custom token for analytics events without session recording.
10698
+ * */
10699
+ setAnalyticsToken(token) {
10700
+ if (!IN_BROWSER || !this.ensureConfigured() || !this.instance) {
10701
+ return;
10702
+ }
10703
+ this.instance.setAnalyticsToken(token);
10704
+ }
10705
+ getAnalyticsToken() {
10706
+ if (!IN_BROWSER || !this.ensureConfigured() || !this.instance) {
10707
+ return undefined;
10708
+ }
10709
+ return this.instance.getAnalyticsToken();
10710
+ }
10665
10711
  }
10666
10712
  const browser = new BrowserSingleton();
10667
10713