@openreplay/tracker 16.4.11-beta.0 → 16.4.11-beta.1

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 (102) hide show
  1. package/dist/cjs/common/messages.gen.d.ts +1 -44
  2. package/dist/cjs/entry.js +1584 -2177
  3. package/dist/cjs/entry.js.map +1 -1
  4. package/dist/cjs/index.js +1566 -2135
  5. package/dist/cjs/index.js.map +1 -1
  6. package/dist/cjs/main/app/canvas.d.ts +11 -0
  7. package/dist/cjs/main/app/index.d.ts +2 -7
  8. package/dist/cjs/main/app/messages.gen.d.ts +0 -5
  9. package/dist/cjs/main/app/nodes/idSeq.d.ts +14 -0
  10. package/dist/cjs/main/app/nodes/index.d.ts +2 -1
  11. package/dist/cjs/main/app/observer/top_observer.d.ts +2 -1
  12. package/dist/cjs/main/app/session.d.ts +1 -0
  13. package/dist/cjs/main/entry.d.ts +5 -4
  14. package/dist/cjs/main/index.d.ts +22 -17
  15. package/dist/cjs/main/modules/analytics/batcher.d.ts +45 -0
  16. package/dist/cjs/main/modules/analytics/constantProperties.d.ts +53 -0
  17. package/dist/cjs/main/modules/analytics/demo.d.ts +0 -0
  18. package/dist/cjs/main/modules/analytics/events.d.ts +37 -0
  19. package/dist/cjs/main/modules/analytics/index.d.ts +73 -0
  20. package/dist/cjs/main/modules/analytics/people.d.ts +51 -0
  21. package/dist/cjs/main/modules/analytics/types.d.ts +32 -0
  22. package/dist/cjs/main/modules/analytics/utils.d.ts +19 -0
  23. package/dist/cjs/main/modules/conditionsManager.d.ts +6 -1
  24. package/dist/cjs/main/modules/input.d.ts +2 -1
  25. package/dist/cjs/main/modules/mouse.d.ts +1 -0
  26. package/dist/cjs/main/modules/viewport.d.ts +7 -0
  27. package/dist/cjs/main/singleton.d.ts +5 -7
  28. package/dist/cjs/main/utils.d.ts +2 -0
  29. package/dist/lib/common/messages.gen.d.ts +1 -44
  30. package/dist/lib/entry.js +1583 -2178
  31. package/dist/lib/entry.js.map +1 -1
  32. package/dist/lib/index.js +1566 -2135
  33. package/dist/lib/index.js.map +1 -1
  34. package/dist/lib/main/app/canvas.d.ts +11 -0
  35. package/dist/lib/main/app/index.d.ts +2 -7
  36. package/dist/lib/main/app/messages.gen.d.ts +0 -5
  37. package/dist/lib/main/app/nodes/idSeq.d.ts +14 -0
  38. package/dist/lib/main/app/nodes/index.d.ts +2 -1
  39. package/dist/lib/main/app/observer/top_observer.d.ts +2 -1
  40. package/dist/lib/main/app/session.d.ts +1 -0
  41. package/dist/lib/main/entry.d.ts +5 -4
  42. package/dist/lib/main/index.d.ts +22 -17
  43. package/dist/lib/main/modules/analytics/batcher.d.ts +45 -0
  44. package/dist/lib/main/modules/analytics/constantProperties.d.ts +53 -0
  45. package/dist/lib/main/modules/analytics/demo.d.ts +0 -0
  46. package/dist/lib/main/modules/analytics/events.d.ts +37 -0
  47. package/dist/lib/main/modules/analytics/index.d.ts +73 -0
  48. package/dist/lib/main/modules/analytics/people.d.ts +51 -0
  49. package/dist/lib/main/modules/analytics/types.d.ts +32 -0
  50. package/dist/lib/main/modules/analytics/utils.d.ts +19 -0
  51. package/dist/lib/main/modules/conditionsManager.d.ts +6 -1
  52. package/dist/lib/main/modules/input.d.ts +2 -1
  53. package/dist/lib/main/modules/mouse.d.ts +1 -0
  54. package/dist/lib/main/modules/viewport.d.ts +7 -0
  55. package/dist/lib/main/singleton.d.ts +5 -7
  56. package/dist/lib/main/utils.d.ts +2 -0
  57. package/dist/types/common/messages.gen.d.ts +1 -44
  58. package/dist/types/main/app/canvas.d.ts +11 -0
  59. package/dist/types/main/app/index.d.ts +2 -7
  60. package/dist/types/main/app/messages.gen.d.ts +0 -5
  61. package/dist/types/main/app/nodes/idSeq.d.ts +14 -0
  62. package/dist/types/main/app/nodes/index.d.ts +2 -1
  63. package/dist/types/main/app/observer/top_observer.d.ts +2 -1
  64. package/dist/types/main/app/session.d.ts +1 -0
  65. package/dist/types/main/entry.d.ts +5 -4
  66. package/dist/types/main/index.d.ts +22 -17
  67. package/dist/types/main/modules/analytics/batcher.d.ts +45 -0
  68. package/dist/types/main/modules/analytics/constantProperties.d.ts +53 -0
  69. package/dist/types/main/modules/analytics/demo.d.ts +0 -0
  70. package/dist/types/main/modules/analytics/events.d.ts +37 -0
  71. package/dist/types/main/modules/analytics/index.d.ts +73 -0
  72. package/dist/types/main/modules/analytics/people.d.ts +51 -0
  73. package/dist/types/main/modules/analytics/types.d.ts +32 -0
  74. package/dist/types/main/modules/analytics/utils.d.ts +19 -0
  75. package/dist/types/main/modules/conditionsManager.d.ts +6 -1
  76. package/dist/types/main/modules/input.d.ts +2 -1
  77. package/dist/types/main/modules/mouse.d.ts +1 -0
  78. package/dist/types/main/modules/viewport.d.ts +7 -0
  79. package/dist/types/main/singleton.d.ts +5 -7
  80. package/dist/types/main/utils.d.ts +2 -0
  81. package/package.json +1 -1
  82. package/dist/cjs/main/modules/featureFlags.d.ts +0 -25
  83. package/dist/cjs/main/modules/userTesting/SignalManager.d.ts +0 -29
  84. package/dist/cjs/main/modules/userTesting/dnd.d.ts +0 -1
  85. package/dist/cjs/main/modules/userTesting/index.d.ts +0 -45
  86. package/dist/cjs/main/modules/userTesting/recorder.d.ts +0 -24
  87. package/dist/cjs/main/modules/userTesting/styles.d.ts +0 -277
  88. package/dist/cjs/main/modules/userTesting/utils.d.ts +0 -9
  89. package/dist/lib/main/modules/featureFlags.d.ts +0 -25
  90. package/dist/lib/main/modules/userTesting/SignalManager.d.ts +0 -29
  91. package/dist/lib/main/modules/userTesting/dnd.d.ts +0 -1
  92. package/dist/lib/main/modules/userTesting/index.d.ts +0 -45
  93. package/dist/lib/main/modules/userTesting/recorder.d.ts +0 -24
  94. package/dist/lib/main/modules/userTesting/styles.d.ts +0 -277
  95. package/dist/lib/main/modules/userTesting/utils.d.ts +0 -9
  96. package/dist/types/main/modules/featureFlags.d.ts +0 -25
  97. package/dist/types/main/modules/userTesting/SignalManager.d.ts +0 -29
  98. package/dist/types/main/modules/userTesting/dnd.d.ts +0 -1
  99. package/dist/types/main/modules/userTesting/index.d.ts +0 -45
  100. package/dist/types/main/modules/userTesting/recorder.d.ts +0 -24
  101. package/dist/types/main/modules/userTesting/styles.d.ts +0 -277
  102. package/dist/types/main/modules/userTesting/utils.d.ts +0 -9
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;
@@ -1110,93 +397,6 @@ const mapCondition = (condition) => {
1110
397
  return con;
1111
398
  };
1112
399
 
1113
- class FeatureFlags {
1114
- constructor(app) {
1115
- this.app = app;
1116
- this.flags = [];
1117
- this.storageKey = '__openreplay_flags';
1118
- const persistFlags = this.app.sessionStorage.getItem(this.storageKey);
1119
- if (persistFlags) {
1120
- const persistFlagsStrArr = persistFlags.split(';').filter(Boolean);
1121
- this.flags = persistFlagsStrArr.map((flag) => JSON.parse(flag));
1122
- }
1123
- }
1124
- getFeatureFlag(flagName) {
1125
- return this.flags.find((flag) => flag.key === flagName);
1126
- }
1127
- isFlagEnabled(flagName) {
1128
- return this.flags.findIndex((flag) => flag.key === flagName) !== -1;
1129
- }
1130
- onFlagsLoad(cb) {
1131
- this.onFlagsCb = cb;
1132
- }
1133
- async reloadFlags(token) {
1134
- const persistFlagsStr = this.app.sessionStorage.getItem(this.storageKey);
1135
- const persistFlags = {};
1136
- if (persistFlagsStr) {
1137
- const persistArray = persistFlagsStr.split(';').filter(Boolean);
1138
- persistArray.forEach((flag) => {
1139
- const flagObj = JSON.parse(flag);
1140
- persistFlags[flagObj.key] = { key: flagObj.key, value: flagObj.value };
1141
- });
1142
- }
1143
- const sessionInfo = this.app.session.getInfo();
1144
- const userInfo = this.app.session.userInfo;
1145
- const requestObject = {
1146
- projectID: sessionInfo.projectID,
1147
- userID: sessionInfo.userID,
1148
- metadata: sessionInfo.metadata,
1149
- referrer: document.referrer,
1150
- os: userInfo.userOS,
1151
- device: userInfo.userDevice,
1152
- country: userInfo.userCountry,
1153
- state: userInfo.userState,
1154
- city: userInfo.userCity,
1155
- browser: userInfo.userBrowser,
1156
- persistFlags: persistFlags,
1157
- };
1158
- const authToken = token ?? this.app.session.getSessionToken();
1159
- const resp = await fetch(this.app.options.ingestPoint + '/v1/web/feature-flags', {
1160
- method: 'POST',
1161
- headers: {
1162
- 'Content-Type': 'application/json',
1163
- Authorization: `Bearer ${authToken}`,
1164
- },
1165
- body: JSON.stringify(requestObject),
1166
- });
1167
- if (resp.status === 200) {
1168
- const data = await resp.json();
1169
- return this.handleFlags(data.flags);
1170
- }
1171
- }
1172
- handleFlags(flags) {
1173
- const persistFlags = [];
1174
- flags.forEach((flag) => {
1175
- if (flag.is_persist)
1176
- persistFlags.push(flag);
1177
- });
1178
- let str = '';
1179
- const uniquePersistFlags = this.diffPersist(persistFlags);
1180
- uniquePersistFlags.forEach((flag) => {
1181
- str += `${JSON.stringify(flag)};`;
1182
- });
1183
- this.app.sessionStorage.setItem(this.storageKey, str);
1184
- this.flags = flags;
1185
- return this.onFlagsCb?.(flags);
1186
- }
1187
- clearPersistFlags() {
1188
- this.app.sessionStorage.removeItem(this.storageKey);
1189
- }
1190
- diffPersist(flags) {
1191
- const persistFlags = this.app.sessionStorage.getItem(this.storageKey);
1192
- if (!persistFlags)
1193
- return flags;
1194
- const persistFlagsStrArr = persistFlags.split(';').filter(Boolean);
1195
- const persistFlagsArr = persistFlagsStrArr.map((flag) => JSON.parse(flag));
1196
- return flags.filter((flag) => persistFlagsArr.findIndex((pf) => pf.key === flag.key) === -1);
1197
- }
1198
- }
1199
-
1200
400
  const DEPRECATED_ATTRS = { htmlmasked: 'hidden', masked: 'obscured' };
1201
401
  const IN_BROWSER = !(typeof window === 'undefined');
1202
402
  const IS_FIREFOX = IN_BROWSER && navigator.userAgent.match(/firefox|fxios/i);
@@ -1218,7 +418,7 @@ const stars = 'repeat' in String.prototype
1218
418
  ? (str) => '*'.repeat(str.length)
1219
419
  : (str) => str.replace(/./g, '*');
1220
420
  function normSpaces(str) {
1221
- return str.trim().replace(/\s+/g, ' ');
421
+ return str ? str.trim().replace(/\s+/g, ' ') : '';
1222
422
  }
1223
423
  // isAbsoluteUrl regexp: /^([a-z][a-z\d\+\-\.]*:)?\/\//i.test(url)
1224
424
  function isURL(s) {
@@ -1234,6 +434,23 @@ function deprecationWarn(nameOfFeature, useInstead, docsPath = '/') {
1234
434
  console.warn(`OpenReplay: ${nameOfFeature} is deprecated. ${useInstead ? `Please, use ${useInstead} instead.` : ''} Visit ${DOCS_HOST}${docsPath} for more information.`);
1235
435
  warnedFeatures[nameOfFeature] = true;
1236
436
  }
437
+ function getCustomAttributeLabel(e, customAttributes) {
438
+ if (!customAttributes || customAttributes.length === 0)
439
+ return '';
440
+ const parts = [];
441
+ for (const attr of customAttributes) {
442
+ const value = e.getAttribute(attr);
443
+ if (value !== null) {
444
+ parts.push(`[${attr}=${value}]`);
445
+ }
446
+ }
447
+ return parts.join('');
448
+ }
449
+ function getClassSelector(e) {
450
+ if (!e.classList || e.classList.length === 0)
451
+ return '';
452
+ return '.' + Array.from(e.classList).join('.');
453
+ }
1237
454
  function getLabelAttribute(e) {
1238
455
  let value = e.getAttribute('data-openreplay-label');
1239
456
  if (value !== null) {
@@ -1719,33 +936,6 @@ function NodeAnimationResult(id, styles) {
1719
936
  styles,
1720
937
  ];
1721
938
  }
1722
- function CSSInsertRule(id, rule, index) {
1723
- return [
1724
- 37 /* Messages.Type.CSSInsertRule */,
1725
- id,
1726
- rule,
1727
- index,
1728
- ];
1729
- }
1730
- function CSSDeleteRule(id, index) {
1731
- return [
1732
- 38 /* Messages.Type.CSSDeleteRule */,
1733
- id,
1734
- index,
1735
- ];
1736
- }
1737
- function Fetch(method, url, request, response, status, timestamp, duration) {
1738
- return [
1739
- 39 /* Messages.Type.Fetch */,
1740
- method,
1741
- url,
1742
- request,
1743
- response,
1744
- status,
1745
- timestamp,
1746
- duration,
1747
- ];
1748
- }
1749
939
  function Profiler(name, duration, args, result) {
1750
940
  return [
1751
941
  40 /* Messages.Type.Profiler */,
@@ -1888,18 +1078,6 @@ function SetNodeFocus(id) {
1888
1078
  id,
1889
1079
  ];
1890
1080
  }
1891
- function LongTask(timestamp, duration, context, containerType, containerSrc, containerId, containerName) {
1892
- return [
1893
- 59 /* Messages.Type.LongTask */,
1894
- timestamp,
1895
- duration,
1896
- context,
1897
- containerType,
1898
- containerSrc,
1899
- containerId,
1900
- containerName,
1901
- ];
1902
- }
1903
1081
  function SetNodeAttributeURLBased(id, name, value, baseURL) {
1904
1082
  return [
1905
1083
  60 /* Messages.Type.SetNodeAttributeURLBased */,
@@ -1938,15 +1116,6 @@ function SetNodeSlot(id, slotID) {
1938
1116
  slotID,
1939
1117
  ];
1940
1118
  }
1941
- function CSSInsertRuleURLBased(id, rule, index, baseURL) {
1942
- return [
1943
- 67 /* Messages.Type.CSSInsertRuleURLBased */,
1944
- id,
1945
- rule,
1946
- index,
1947
- baseURL,
1948
- ];
1949
- }
1950
1119
  function MouseClick(id, hesitationTime, label, selector, normalizedX, normalizedY) {
1951
1120
  return [
1952
1121
  68 /* Messages.Type.MouseClick */,
@@ -2226,9 +1395,6 @@ var _Messages = /*#__PURE__*/Object.freeze({
2226
1395
  AdoptedSSRemoveOwner: AdoptedSSRemoveOwner,
2227
1396
  AdoptedSSReplaceURLBased: AdoptedSSReplaceURLBased,
2228
1397
  BatchMetadata: BatchMetadata,
2229
- CSSDeleteRule: CSSDeleteRule,
2230
- CSSInsertRule: CSSInsertRule,
2231
- CSSInsertRuleURLBased: CSSInsertRuleURLBased,
2232
1398
  CanvasNode: CanvasNode,
2233
1399
  ConnectionInformation: ConnectionInformation,
2234
1400
  ConsoleLog: ConsoleLog,
@@ -2238,7 +1404,6 @@ var _Messages = /*#__PURE__*/Object.freeze({
2238
1404
  CreateTextNode: CreateTextNode,
2239
1405
  CustomEvent: CustomEvent,
2240
1406
  CustomIssue: CustomIssue,
2241
- Fetch: Fetch,
2242
1407
  GraphQL: GraphQL,
2243
1408
  GraphQLDeprecated: GraphQLDeprecated,
2244
1409
  Incident: Incident,
@@ -2246,7 +1411,6 @@ var _Messages = /*#__PURE__*/Object.freeze({
2246
1411
  JSException: JSException,
2247
1412
  LoadFontFace: LoadFontFace,
2248
1413
  LongAnimationTask: LongAnimationTask$1,
2249
- LongTask: LongTask,
2250
1414
  Metadata: Metadata,
2251
1415
  MobX: MobX,
2252
1416
  MouseClick: MouseClick,
@@ -2433,1060 +1597,85 @@ class TagWatcher {
2433
1597
  }
2434
1598
  }
2435
1599
 
2436
- const bgStyle = {
2437
- position: 'fixed',
2438
- top: 0,
2439
- left: 0,
2440
- width: '100vw',
2441
- height: '100vh',
2442
- background: 'rgba(0, 0, 0, 0.40)',
2443
- display: 'flex',
2444
- alignItems: 'center',
2445
- justifyContent: 'center',
2446
- zIndex: 999999,
2447
- fontFamily: `-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"`,
2448
- };
2449
- const containerStyle = {
2450
- display: 'flex',
2451
- flexDirection: 'column',
2452
- gap: '2rem',
2453
- alignItems: 'center',
2454
- padding: '1.5rem',
2455
- borderRadius: '2px',
2456
- border: '1px solid rgb(255 255 255 / var(--tw-bg-opacity, 1))',
2457
- background: '#FFF',
2458
- width: '22rem',
2459
- };
2460
- const containerWidgetStyle = {
2461
- display: 'flex',
2462
- 'flex-direction': 'column',
2463
- gap: 'unset',
2464
- 'align-items': 'center',
2465
- padding: 'unset',
2466
- fontFamily: `-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"`,
2467
- 'border-radius': '2px',
2468
- border: '1px solid rgb(255 255 255 / var(--tw-bg-opacity, 1))',
2469
- background: 'rgba(255, 255, 255, 0.75)',
2470
- width: '22rem',
2471
- };
2472
- const titleStyle = {
2473
- fontFamily: 'Verdana, sans-serif',
2474
- fontSize: '1.25rem',
2475
- fontStyle: 'normal',
2476
- fontWeight: '500',
2477
- lineHeight: '1.75rem',
2478
- color: 'rgba(0, 0, 0, 0.85)',
2479
- };
2480
- const descriptionStyle = {
2481
- borderTop: '1px solid rgba(0, 0, 0, 0.06)',
2482
- borderBottom: '1px solid rgba(0, 0, 0, 0.06)',
2483
- padding: '1.25rem 0rem',
2484
- color: 'rgba(0, 0, 0, 0.85)',
2485
- fontFamily: 'Verdana, sans-serif',
2486
- fontSize: '13px',
2487
- fontStyle: 'normal',
2488
- fontWeight: '400',
2489
- lineHeight: 'auto',
2490
- whiteSpace: 'pre-wrap',
2491
- };
2492
- const buttonStyle = {
2493
- display: 'flex',
2494
- padding: '0.4rem 0.9375rem',
2495
- justifyContent: 'center',
2496
- alignItems: 'center',
2497
- gap: '0.625rem',
2498
- borderRadius: '0.25rem',
2499
- border: '1px solid #394EFF',
2500
- background: '#394EFF',
2501
- boxShadow: '0px 2px 0px 0px rgba(0, 0, 0, 0.04)',
2502
- color: '#FFF',
2503
- textAlign: 'center',
2504
- fontFamily: 'Verdana, sans-serif',
2505
- fontSize: '1rem',
2506
- fontStyle: 'normal',
2507
- fontWeight: '500',
2508
- lineHeight: '1.5rem',
2509
- cursor: 'pointer',
2510
- };
2511
- const sectionTitleStyle = {
2512
- fontFamily: 'Verdana, sans-serif',
2513
- fontSize: '13px',
2514
- fontWeight: '500',
2515
- lineHeight: 'auto',
2516
- display: 'flex',
2517
- justifyContent: 'space-between',
2518
- width: '100%',
2519
- cursor: 'pointer',
2520
- };
2521
- const contentStyle = {
2522
- display: 'flex',
2523
- flexDirection: 'column',
2524
- alignItems: 'flex-start',
2525
- gap: '0.625rem',
2526
- fontSize: '13px',
2527
- lineHeight: 'auto',
2528
- };
2529
- // New widget styles
2530
- const titleWidgetStyle = {
2531
- padding: '0.5rem',
2532
- gap: '0.5rem',
2533
- fontFamily: 'Verdana, sans-serif',
2534
- fontSize: '16px',
2535
- fontStyle: 'normal',
2536
- fontWeight: '500',
2537
- lineHeight: 'auto',
2538
- color: 'white',
2539
- display: 'flex',
2540
- alignItems: 'center',
2541
- width: '100%',
2542
- borderRadius: '2px',
2543
- background: 'rgba(0, 0, 0, 0.75)',
2544
- boxSizing: 'border-box',
2545
- };
2546
- const descriptionWidgetStyle = {
2547
- boxSizing: 'border-box',
2548
- display: 'block',
2549
- width: '100%',
2550
- borderBottom: '1px solid rgb(255 255 255 / var(--tw-bg-opacity, 1))',
2551
- background: '#FFF',
2552
- padding: '0.65rem',
2553
- alignSelf: 'stretch',
2554
- color: '#000',
2555
- fontFamily: 'Verdana, sans-serif',
2556
- // fontSize: '0.875rem',
2557
- fontStyle: 'normal',
2558
- fontWeight: '400',
2559
- // lineHeight: '1.375rem',
2560
- };
2561
- const endSectionStyle = {
2562
- ...descriptionWidgetStyle,
2563
- display: 'flex',
2564
- flexDirection: 'column',
2565
- alignItems: 'center',
2566
- gap: '0.625rem',
2567
- };
2568
- const symbolIcon = {
2569
- fontSize: '1.25rem',
2570
- fontWeight: '500',
2571
- cursor: 'pointer',
2572
- color: '#394EFF',
2573
- };
2574
- const buttonWidgetStyle = {
2575
- display: 'flex',
2576
- padding: '0.4rem 0.9375rem',
2577
- justifyContent: 'center',
2578
- alignItems: 'center',
2579
- gap: '0.625rem',
2580
- borderRadius: '0.25rem',
2581
- border: '1px solid #394EFF',
2582
- background: '#394EFF',
2583
- boxShadow: '0px 2px 0px 0px rgba(0, 0, 0, 0.04)',
2584
- color: '#FFF',
2585
- textAlign: 'center',
2586
- fontFamily: 'Verdana, sans-serif',
2587
- fontSize: '1rem',
2588
- fontStyle: 'normal',
2589
- fontWeight: '500',
2590
- lineHeight: '1.5rem',
2591
- width: '100%',
2592
- boxSizing: 'border-box',
2593
- cursor: 'pointer',
2594
- };
2595
- const stopWidgetStyle = {
2596
- marginTop: '1rem',
2597
- marginBottom: '1rem',
2598
- cursor: 'pointer',
2599
- display: 'block',
2600
- fontWeight: '500',
2601
- fontSize: '13px!important',
2602
- lineHeight: 'auto',
2603
- };
2604
- const paginationStyle = {
2605
- display: 'flex',
2606
- justifyContent: 'space-between',
2607
- alignItems: 'center',
2608
- gap: '1rem',
2609
- padding: '0.5rem',
2610
- width: '100%',
2611
- boxSizing: 'border-box',
2612
- };
2613
- const taskNumberActive = {
2614
- display: 'flex',
2615
- flexDirection: 'column',
2616
- alignItems: 'center',
2617
- justifyContent: 'center',
2618
- borderRadius: '6.25em',
2619
- outline: '1px solid #394EFF',
2620
- fontSize: '13px',
2621
- height: '24px',
2622
- width: '24px',
2623
- };
2624
- const taskNumberDone = {
2625
- display: 'flex',
2626
- flexDirection: 'column',
2627
- alignItems: 'center',
2628
- justifyContent: 'center',
2629
- borderRadius: '6.25em',
2630
- outline: '1px solid #D2DFFF',
2631
- boxShadow: '0px 2px 0px 0px rgba(0, 0, 0, 0.04)',
2632
- background: '#D2DFFF',
2633
- fontSize: '13px',
2634
- height: '24px',
2635
- width: '24px',
2636
- };
2637
- const taskDescriptionCard = {
2638
- borderRadius: '0.375rem',
2639
- border: '1px solid rgba(0, 0, 0, 0.06)',
2640
- background: '#F5F7FF',
2641
- boxShadow: '0px 2px 0px 0px rgba(0, 0, 0, 0.04)',
2642
- display: 'flex',
2643
- flexDirection: 'column',
2644
- padding: '0.625rem 0.9375rem',
2645
- gap: '0.5rem',
2646
- alignSelf: 'stretch',
2647
- };
2648
- const taskTextStyle = {
2649
- fontWeight: 'bold',
2650
- };
2651
- const taskDescriptionStyle = {
2652
- fontSize: '13px',
2653
- lineHeight: 'auto',
2654
- };
2655
- const taskButtonStyle = {
2656
- marginRight: '0.5rem',
2657
- cursor: 'pointer',
2658
- color: '#394EFF',
2659
- textAlign: 'center',
2660
- fontFamily: 'Verdana, sans-serif',
2661
- fontSize: '13px',
2662
- fontStyle: 'normal',
2663
- fontWeight: '500',
2664
- lineHeight: 'auto',
2665
- };
2666
- const taskButtonBorderedStyle = {
2667
- ...taskButtonStyle,
2668
- display: 'flex',
2669
- padding: '0.25rem 0.9375rem',
2670
- justifyContent: 'center',
2671
- alignItems: 'center',
2672
- gap: '0.5rem',
2673
- borderRadius: '0.25rem',
2674
- border: '1px solid #394EFF',
2675
- };
2676
- const taskButtonsRow = {
2677
- display: 'flex',
2678
- justifyContent: 'space-between',
2679
- alignItems: 'center',
2680
- width: '100%',
2681
- boxSizing: 'border-box',
2682
- };
2683
- const spinnerStyles = {
2684
- border: '4px solid rgba(255, 255, 255, 0.4)',
2685
- width: '16px',
2686
- height: '16px',
2687
- borderRadius: '50%',
2688
- borderLeftColor: '#fff',
2689
- animation: 'spin 0.5s linear infinite',
2690
- };
1600
+ //@ts-ignore
1601
+ function isNode(sth) {
1602
+ return !!sth && sth.nodeType != null;
1603
+ }
1604
+ function isSVGElement(node) {
1605
+ return (node.namespaceURI === 'http://www.w3.org/2000/svg' || node.localName === 'svg');
1606
+ }
1607
+ function isUseElement(node) {
1608
+ return node.localName === 'use';
1609
+ }
1610
+ function isElementNode(node) {
1611
+ return node.nodeType === Node.ELEMENT_NODE;
1612
+ }
1613
+ function isCommentNode(node) {
1614
+ return node.nodeType === Node.COMMENT_NODE;
1615
+ }
1616
+ function isTextNode(node) {
1617
+ return node.nodeType === Node.TEXT_NODE;
1618
+ }
1619
+ function isDocument(node) {
1620
+ return node.nodeType === Node.DOCUMENT_NODE;
1621
+ }
1622
+ function isRootNode(node) {
1623
+ return node.nodeType === Node.DOCUMENT_NODE || node.nodeType === Node.DOCUMENT_FRAGMENT_NODE;
1624
+ }
1625
+ function hasTag(el, tagName) {
1626
+ // @ts-ignore
1627
+ return el.localName === tagName;
1628
+ }
2691
1629
 
2692
- const Quality = {
2693
- Standard: { width: 1280, height: 720 }};
2694
- class Recorder {
2695
- constructor(app) {
1630
+ class CanvasRecorder {
1631
+ constructor(app, options) {
2696
1632
  this.app = app;
2697
- this.mediaRecorder = null;
2698
- this.recordedChunks = [];
2699
- this.stream = null;
2700
- this.recStartTs = null;
2701
- }
2702
- async startRecording(fps, quality, micReq, camReq) {
2703
- this.recStartTs = this.app.timestamp();
2704
- const videoConstraints = quality;
2705
- try {
2706
- this.stream = await navigator.mediaDevices.getUserMedia({
2707
- video: camReq ? { ...videoConstraints, frameRate: { ideal: fps } } : false,
2708
- audio: micReq,
2709
- });
2710
- this.mediaRecorder = new MediaRecorder(this.stream, {
2711
- mimeType: 'video/webm;codecs=vp9',
2712
- });
2713
- this.recordedChunks = [];
2714
- this.mediaRecorder.ondataavailable = (event) => {
2715
- if (event.data.size > 0) {
2716
- this.recordedChunks.push(event.data);
2717
- }
2718
- };
2719
- this.mediaRecorder.start();
2720
- }
2721
- catch (error) {
2722
- console.error(error);
2723
- }
2724
- }
2725
- async stopRecording() {
2726
- return new Promise((resolve) => {
2727
- if (!this.mediaRecorder)
1633
+ this.options = options;
1634
+ this.snapshots = {};
1635
+ this.intervals = new Map();
1636
+ this.observers = new Map();
1637
+ this.uploadQueue = 0;
1638
+ this.MAX_CONCURRENT_UPLOADS = 2;
1639
+ this.MAX_QUEUE_SIZE = 50; // ~500 images max (50 batches × 10 images)
1640
+ this.pendingBatches = [];
1641
+ this.isProcessingQueue = false;
1642
+ this.restartTracking = () => {
1643
+ this.clear();
1644
+ this.app.nodes.scanTree(this.captureCanvas);
1645
+ };
1646
+ this.captureCanvas = (node) => {
1647
+ const id = this.app.nodes.getID(node);
1648
+ if (!id || !hasTag(node, 'canvas')) {
2728
1649
  return;
2729
- this.mediaRecorder.onstop = () => {
2730
- const blob = new Blob(this.recordedChunks, {
2731
- type: 'video/webm',
2732
- });
2733
- resolve(blob);
2734
- };
2735
- this.mediaRecorder.stop();
2736
- });
2737
- }
2738
- async sendToAPI() {
2739
- const blob = await this.stopRecording();
2740
- // const formData = new FormData()
2741
- // formData.append('file', blob, 'record.webm')
2742
- // formData.append('start', this.recStartTs?.toString() ?? '')
2743
- return fetch(`${this.app.options.ingestPoint}/v1/web/uxt/upload-url`, {
2744
- headers: {
2745
- Authorization: `Bearer ${this.app.session.getSessionToken()}`,
2746
- },
2747
- })
2748
- .then((r) => {
2749
- if (r.ok) {
2750
- return r.json();
2751
1650
  }
2752
- else {
2753
- throw new Error('Failed to get upload url');
1651
+ const isIgnored = this.app.sanitizer.isObscured(id) || this.app.sanitizer.isHidden(id);
1652
+ if (isIgnored || this.snapshots[id]) {
1653
+ return;
2754
1654
  }
2755
- })
2756
- .then(({ url }) => {
2757
- return fetch(url, {
2758
- method: 'PUT',
2759
- headers: {
2760
- 'Content-Type': 'video/webm',
2761
- },
2762
- body: blob,
2763
- });
2764
- })
2765
- .catch(console.error)
2766
- .finally(() => {
2767
- this.discard();
2768
- });
2769
- }
2770
- async saveToFile(fileName = 'recorded-video.webm') {
2771
- const blob = await this.stopRecording();
2772
- const url = URL.createObjectURL(blob);
2773
- const a = document.createElement('a');
2774
- a.style.display = 'none';
2775
- a.href = url;
2776
- a.download = fileName;
2777
- document.body.appendChild(a);
2778
- a.click();
2779
- window.URL.revokeObjectURL(url);
2780
- document.body.removeChild(a);
2781
- }
2782
- discard() {
2783
- this.mediaRecorder?.stop();
2784
- this.stream?.getTracks().forEach((track) => track.stop());
2785
- }
2786
- }
2787
-
2788
- // @ts-nocheck
2789
- function attachDND(element, dragTarget) {
2790
- dragTarget.onmousedown = function (event) {
2791
- const clientRect = element.getBoundingClientRect();
2792
- const shiftX = event.clientX - clientRect.left;
2793
- const shiftY = event.clientY - clientRect.top;
2794
- element.style.position = 'fixed';
2795
- element.style.zIndex = 99999999999999;
2796
- moveAt(event.pageX, event.pageY);
2797
- function moveAt(pageX, pageY) {
2798
- let leftC = pageX - shiftX;
2799
- let topC = pageY - shiftY;
2800
- if (leftC <= 5)
2801
- leftC = 5;
2802
- if (topC <= 5)
2803
- topC = 5;
2804
- if (leftC >= window.innerWidth - clientRect.width)
2805
- leftC = window.innerWidth - clientRect.width;
2806
- if (topC >= window.innerHeight - clientRect.height)
2807
- topC = window.innerHeight - clientRect.height;
2808
- element.style.left = `${leftC}px`;
2809
- element.style.top = `${topC}px`;
2810
- }
2811
- function onMouseMove(event) {
2812
- moveAt(event.pageX, event.pageY);
2813
- }
2814
- document.addEventListener('mousemove', onMouseMove);
2815
- const clearAll = () => {
2816
- document.removeEventListener('mousemove', onMouseMove);
2817
- document.removeEventListener('mouseup', clearAll);
2818
- };
2819
- document.addEventListener('mouseup', clearAll);
2820
- };
2821
- dragTarget.ondragstart = function () {
2822
- return false;
2823
- };
2824
- }
2825
-
2826
- function generateGrid() {
2827
- const grid = document.createElement('div');
2828
- grid.className = 'grid';
2829
- for (let i = 0; i < 16; i++) {
2830
- const cell = document.createElement('div');
2831
- Object.assign(cell.style, {
2832
- width: '2px',
2833
- height: '2px',
2834
- borderRadius: '10px',
2835
- background: 'white',
2836
- });
2837
- cell.className = 'cell';
2838
- grid.appendChild(cell);
2839
- }
2840
- Object.assign(grid.style, {
2841
- display: 'grid',
2842
- gridTemplateColumns: 'repeat(4, 1fr)',
2843
- gridTemplateRows: 'repeat(4, 1fr)',
2844
- gap: '2px',
2845
- cursor: 'grab',
2846
- });
2847
- return grid;
2848
- }
2849
- function generateChevron() {
2850
- const triangle = document.createElement('div');
2851
- Object.assign(triangle.style, {
2852
- width: '0',
2853
- height: '0',
2854
- borderLeft: '7px solid transparent',
2855
- borderRight: '7px solid transparent',
2856
- borderBottom: '7px solid white',
2857
- });
2858
- const container = document.createElement('div');
2859
- container.appendChild(triangle);
2860
- Object.assign(container.style, {
2861
- display: 'flex',
2862
- alignItems: 'center',
2863
- justifyContent: 'center',
2864
- width: '16px',
2865
- height: '16px',
2866
- cursor: 'pointer',
2867
- marginLeft: 'auto',
2868
- transform: 'rotate(180deg)',
2869
- });
2870
- return container;
2871
- }
2872
- function addKeyframes() {
2873
- const styleSheet = document.createElement('style');
2874
- styleSheet.type = 'text/css';
2875
- styleSheet.innerText = `@keyframes spin {
2876
- 0% { transform: rotate(0deg); }
2877
- 100% { transform: rotate(360deg); }
2878
- }`;
2879
- document.head.appendChild(styleSheet);
2880
- }
2881
- function createSpinner() {
2882
- addKeyframes();
2883
- const spinner = document.createElement('div');
2884
- spinner.classList.add('spinner');
2885
- Object.assign(spinner.style, spinnerStyles);
2886
- return spinner;
2887
- }
2888
- function createElement(tag, className, styles, textContent, id) {
2889
- const element = document.createElement(tag);
2890
- element.className = className;
2891
- Object.assign(element.style, styles);
2892
- if (textContent) {
2893
- element.textContent = textContent;
2894
- }
2895
- if (id) {
2896
- element.id = id;
2897
- }
2898
- return element;
2899
- }
2900
- const TEST_START = 'or_uxt_test_start';
2901
- const TASK_IND = 'or_uxt_task_index';
2902
- const SESSION_ID = 'or_uxt_session_id';
2903
- const TEST_ID = 'or_uxt_test_id';
2904
-
2905
- class SignalManager {
2906
- constructor(ingestPoint, getTimestamp, token, testId, storageKey, setStorageKey, removeStorageKey, getStorageKey, getSessionId) {
2907
- this.ingestPoint = ingestPoint;
2908
- this.getTimestamp = getTimestamp;
2909
- this.token = token;
2910
- this.testId = testId;
2911
- this.storageKey = storageKey;
2912
- this.setStorageKey = setStorageKey;
2913
- this.removeStorageKey = removeStorageKey;
2914
- this.getStorageKey = getStorageKey;
2915
- this.getSessionId = getSessionId;
2916
- this.durations = {
2917
- testStart: 0,
2918
- tasks: [],
2919
- };
2920
- this.getDurations = () => {
2921
- return this.durations;
2922
- };
2923
- this.setDurations = (durations) => {
2924
- this.durations.testStart = durations.testStart;
2925
- this.durations.tasks = durations.tasks;
2926
- };
2927
- this.signalTask = (taskId, status, taskAnswer) => {
2928
- if (!taskId)
2929
- return console.error('User Testing: No Task ID Given');
2930
- const taskStart = this.durations.tasks.find((t) => t.taskId === taskId);
2931
- const timestamp = this.getTimestamp();
2932
- const duration = taskStart ? timestamp - taskStart.started : 0;
2933
- return fetch(`${this.ingestPoint}/v1/web/uxt/signals/task`, {
2934
- method: 'POST',
2935
- headers: {
2936
- // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
2937
- Authorization: `Bearer ${this.token}`,
2938
- },
2939
- body: JSON.stringify({
2940
- testId: this.testId,
2941
- taskId,
2942
- status,
2943
- duration,
2944
- timestamp,
2945
- taskAnswer,
2946
- }),
2947
- });
2948
- };
2949
- this.signalTest = (status) => {
2950
- const timestamp = this.getTimestamp();
2951
- if (status === 'begin' && this.testId) {
2952
- const sessionId = this.getSessionId();
2953
- this.setStorageKey(SESSION_ID, sessionId);
2954
- this.setStorageKey(this.storageKey, this.testId.toString());
2955
- this.setStorageKey(TEST_START, timestamp.toString());
2956
- }
2957
- else {
2958
- this.removeStorageKey(this.storageKey);
2959
- this.removeStorageKey(TASK_IND);
2960
- this.removeStorageKey(TEST_START);
2961
- }
2962
- const start = this.durations.testStart || timestamp;
2963
- const duration = timestamp - start;
2964
- return fetch(`${this.ingestPoint}/v1/web/uxt/signals/test`, {
2965
- method: 'POST',
2966
- headers: {
2967
- // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
2968
- Authorization: `Bearer ${this.token}`,
2969
- },
2970
- body: JSON.stringify({
2971
- testId: this.testId,
2972
- status,
2973
- duration,
2974
- timestamp,
2975
- }),
2976
- });
2977
- };
2978
- const possibleStart = this.getStorageKey(TEST_START);
2979
- if (possibleStart) {
2980
- this.durations.testStart = parseInt(possibleStart, 10);
2981
- }
2982
- }
2983
- }
2984
-
2985
- class UserTestManager {
2986
- constructor(app, storageKey) {
2987
- this.app = app;
2988
- this.storageKey = storageKey;
2989
- this.bg = createElement('div', 'bg', bgStyle, undefined, '__or_ut_bg');
2990
- this.container = createElement('div', 'container', containerStyle, undefined, '__or_ut_ct');
2991
- this.widgetGuidelinesVisible = true;
2992
- this.widgetTasksVisible = false;
2993
- this.widgetVisible = true;
2994
- this.isActive = false;
2995
- this.descriptionSection = null;
2996
- this.taskSection = null;
2997
- this.endSection = null;
2998
- this.stopButton = null;
2999
- this.stopButtonContainer = null;
3000
- this.test = null;
3001
- this.testId = null;
3002
- this.signalManager = null;
3003
- this.getTest = (id, token, inProgress) => {
3004
- this.testId = id;
3005
- const ingest = this.app.options.ingestPoint;
3006
- return fetch(`${ingest}/v1/web/uxt/test/${id}`, {
3007
- headers: {
3008
- Authorization: `Bearer ${token}`,
3009
- },
3010
- })
3011
- .then((res) => res.json())
3012
- .then(({ test }) => {
3013
- this.isActive = true;
3014
- this.test = test;
3015
- this.signalManager = new SignalManager(this.app.options.ingestPoint, () => this.app.timestamp(), token, id, this.storageKey, (k, v) => this.app.localStorage.setItem(k, v), (k) => this.app.localStorage.removeItem(k), (k) => this.app.localStorage.getItem(k), () => this.app.getSessionID());
3016
- this.createGreeting(test.title, test.reqMic, test.reqCamera);
3017
- if (inProgress) {
3018
- if (test.reqMic || test.reqCamera) {
3019
- void this.userRecorder.startRecording(30, Quality.Standard, test.reqMic, test.reqCamera);
3020
- }
3021
- this.showWidget(test.description, test.tasks, true);
3022
- this.showTaskSection();
3023
- }
3024
- })
3025
- .then(() => id)
3026
- .catch((err) => {
3027
- console.log('OR: Error fetching test', err);
3028
- });
3029
- };
3030
- this.hideTaskSection = () => false;
3031
- this.showTaskSection = () => true;
3032
- this.collapseWidget = () => false;
3033
- this.removeGreeting = () => false;
3034
- // eslint-disable-next-line @typescript-eslint/no-empty-function
3035
- this.toggleDescriptionVisibility = () => { };
3036
- this.currentTaskIndex = 0;
3037
- this.userRecorder = new Recorder(app);
3038
- const sessionId = this.app.getSessionID();
3039
- const savedSessionId = this.app.localStorage.getItem(SESSION_ID);
3040
- if (sessionId !== savedSessionId) {
3041
- this.app.localStorage.removeItem(this.storageKey);
3042
- this.app.localStorage.removeItem(SESSION_ID);
3043
- this.app.localStorage.removeItem(TEST_ID);
3044
- this.app.localStorage.removeItem(TASK_IND);
3045
- this.app.localStorage.removeItem(TEST_START);
3046
- }
3047
- const taskIndex = this.app.localStorage.getItem(TASK_IND);
3048
- if (taskIndex) {
3049
- this.currentTaskIndex = parseInt(taskIndex, 10);
3050
- }
3051
- }
3052
- getTestId() {
3053
- return this.testId;
3054
- }
3055
- createGreeting(title, micRequired, cameraRequired) {
3056
- const titleElement = createElement('div', 'title', titleStyle, title);
3057
- const descriptionElement = createElement('div', 'description', descriptionStyle, `Welcome, you're here to help us improve, not to be judged. Your insights matter!\n
3058
- 📹 We're recording this browser tab to learn from your experience.
3059
- 🎤 Please enable mic and camera if asked, to give us a complete picture.`);
3060
- const buttonElement = createElement('div', 'button', buttonStyle, 'Read guidelines to begin');
3061
- this.removeGreeting = () => {
3062
- // this.container.innerHTML = ''
3063
- if (micRequired || cameraRequired) {
3064
- void this.userRecorder.startRecording(30, Quality.Standard, micRequired, cameraRequired);
3065
- }
3066
- this.container.removeChild(buttonElement);
3067
- this.container.removeChild(descriptionElement);
3068
- this.container.removeChild(titleElement);
3069
- return false;
3070
- };
3071
- buttonElement.onclick = () => {
3072
- this.removeGreeting();
3073
- const durations = this.signalManager?.getDurations();
3074
- if (durations && this.signalManager) {
3075
- durations.testStart = this.app.timestamp();
3076
- this.signalManager.setDurations(durations);
3077
- }
3078
- void this.signalManager?.signalTest('begin');
3079
- this.container.style.fontFamily = `-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"`;
3080
- Object.assign(this.container.style, containerWidgetStyle);
3081
- this.showWidget(this.test?.guidelines || '', this.test?.tasks || []);
3082
- };
3083
- this.container.append(titleElement, descriptionElement, buttonElement);
3084
- this.bg.appendChild(this.container);
3085
- document.body.appendChild(this.bg);
3086
- }
3087
- showWidget(guidelines, tasks, inProgress) {
3088
- this.container.innerHTML = '';
3089
- Object.assign(this.bg.style, {
3090
- position: 'fixed',
3091
- zIndex: 99999999999999,
3092
- right: '8px',
3093
- left: 'unset',
3094
- width: 'fit-content',
3095
- top: '8px',
3096
- height: 'fit-content',
3097
- background: 'unset',
3098
- display: 'unset',
3099
- alignItems: 'unset',
3100
- justifyContent: 'unset',
3101
- });
3102
- // Create title section
3103
- const titleSection = this.createTitleSection();
3104
- this.container.style.fontFamily = `-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"`;
3105
- Object.assign(this.container.style, containerWidgetStyle);
3106
- const descriptionSection = this.createDescriptionSection(guidelines);
3107
- const tasksSection = this.createTasksSection(tasks);
3108
- const stopButton = createElement('div', 'stop_bn_or', stopWidgetStyle, 'Abort Session');
3109
- const stopContainer = createElement('div', 'stop_ct_or', { fontSize: '13px!important' });
3110
- stopContainer.style.fontSize = '13px';
3111
- stopContainer.append(stopButton);
3112
- this.container.append(titleSection, descriptionSection, tasksSection, stopContainer);
3113
- this.taskSection = tasksSection;
3114
- this.descriptionSection = descriptionSection;
3115
- this.stopButton = stopButton;
3116
- this.stopButtonContainer = stopContainer;
3117
- stopButton.onclick = () => {
3118
- this.userRecorder.discard();
3119
- void this.signalManager?.signalTest('skipped');
3120
- document.body.removeChild(this.bg);
3121
- window.close();
3122
- };
3123
- if (!inProgress) {
3124
- this.hideTaskSection();
3125
- }
3126
- else {
3127
- this.toggleDescriptionVisibility();
3128
- }
3129
- }
3130
- createTitleSection() {
3131
- const title = createElement('div', 'title', titleWidgetStyle);
3132
- const leftIcon = generateGrid();
3133
- const titleText = createElement('div', 'title_text', {
3134
- maxWidth: '19rem',
3135
- overflow: 'hidden',
3136
- textOverflow: 'ellipsis',
3137
- width: '100%',
3138
- fontSize: 16,
3139
- lineHeight: 'auto',
3140
- cursor: 'pointer',
3141
- }, this.test?.title);
3142
- const rightIcon = generateChevron();
3143
- title.append(leftIcon, titleText, rightIcon);
3144
- const toggleWidget = (isVisible) => {
3145
- this.widgetVisible = isVisible;
3146
- this.container.style.fontFamily = `-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"`;
3147
- Object.assign(this.container.style, this.widgetVisible
3148
- ? containerWidgetStyle
3149
- : { border: 'none', background: 'none', padding: 0 });
3150
- if (this.taskSection) {
3151
- Object.assign(this.taskSection.style, this.widgetVisible ? descriptionWidgetStyle : { display: 'none' });
3152
- }
3153
- if (this.descriptionSection) {
3154
- Object.assign(this.descriptionSection.style, this.widgetVisible ? descriptionWidgetStyle : { display: 'none' });
3155
- }
3156
- if (this.endSection) {
3157
- Object.assign(this.endSection.style, this.widgetVisible ? descriptionWidgetStyle : { display: 'none' });
3158
- }
3159
- if (this.stopButton) {
3160
- Object.assign(this.stopButton.style, this.widgetVisible ? stopWidgetStyle : { display: 'none' });
3161
- }
3162
- return isVisible;
3163
- };
3164
- const collapseWidget = () => {
3165
- Object.assign(rightIcon.style, {
3166
- transform: this.widgetVisible ? 'rotate(0deg)' : 'rotate(180deg)',
3167
- });
3168
- toggleWidget(!this.widgetVisible);
3169
- };
3170
- titleText.onclick = collapseWidget;
3171
- rightIcon.onclick = collapseWidget;
3172
- attachDND(this.bg, leftIcon);
3173
- this.collapseWidget = () => toggleWidget(false);
3174
- return title;
3175
- }
3176
- createDescriptionSection(guidelines) {
3177
- const section = createElement('div', 'description_section_or', descriptionWidgetStyle);
3178
- const titleContainer = createElement('div', 'description_s_title_or', sectionTitleStyle);
3179
- const title = createElement('div', 'title', {
3180
- fontSize: 13,
3181
- fontWeight: 500,
3182
- lineHeight: 'auto',
3183
- }, 'Introduction & Guidelines');
3184
- const icon = createElement('div', 'icon', symbolIcon, '-');
3185
- const content = createElement('div', 'content', contentStyle);
3186
- const descriptionC = createElement('div', 'text_description', {
3187
- maxHeight: '250px',
3188
- overflowY: 'auto',
3189
- whiteSpace: 'pre-wrap',
3190
- fontSize: 13,
3191
- color: '#454545',
3192
- lineHeight: 'auto',
3193
- });
3194
- descriptionC.innerHTML = guidelines;
3195
- const button = createElement('div', 'button_begin_or', buttonWidgetStyle, 'Begin Test');
3196
- titleContainer.append(title, icon);
3197
- content.append(descriptionC, button);
3198
- section.append(titleContainer, content);
3199
- const toggleDescriptionVisibility = () => {
3200
- this.widgetGuidelinesVisible = !this.widgetGuidelinesVisible;
3201
- icon.textContent = this.widgetGuidelinesVisible ? '-' : '+';
3202
- Object.assign(content.style, this.widgetGuidelinesVisible ? contentStyle : { display: 'none' });
3203
- };
3204
- titleContainer.onclick = toggleDescriptionVisibility;
3205
- this.toggleDescriptionVisibility = () => {
3206
- this.widgetGuidelinesVisible = false;
3207
- icon.textContent = this.widgetGuidelinesVisible ? '-' : '+';
3208
- Object.assign(content.style, this.widgetGuidelinesVisible ? contentStyle : { display: 'none' });
3209
- content.removeChild(button);
3210
- };
3211
- button.onclick = () => {
3212
- toggleDescriptionVisibility();
3213
- if (this.test) {
3214
- const durations = this.signalManager?.getDurations();
3215
- const taskDurationInd = durations
3216
- ? durations.tasks.findIndex((t) => this.test && t.taskId === this.test.tasks[0].task_id)
3217
- : null;
3218
- if (durations && taskDurationInd === -1) {
3219
- durations.tasks.push({
3220
- taskId: this.test.tasks[0].task_id,
3221
- started: this.app.timestamp(),
3222
- });
3223
- this.signalManager?.setDurations(durations);
3224
- }
3225
- void this.signalManager?.signalTask(this.test.tasks[0].task_id, 'begin');
3226
- }
3227
- this.showTaskSection();
3228
- content.removeChild(button);
3229
- };
3230
- return section;
3231
- }
3232
- createTasksSection(tasks) {
3233
- this.container.style.fontFamily = `-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"`;
3234
- Object.assign(this.container.style, containerWidgetStyle);
3235
- const section = createElement('div', 'task_section_or', descriptionWidgetStyle);
3236
- const titleContainer = createElement('div', 'description_t_title_or', sectionTitleStyle);
3237
- const title = createElement('div', 'title', {
3238
- fontSize: '13px',
3239
- fontWeight: '500',
3240
- lineHeight: 'auto',
3241
- }, 'Tasks');
3242
- const icon = createElement('div', 'icon', symbolIcon, '-');
3243
- const content = createElement('div', 'content', contentStyle);
3244
- const pagination = createElement('div', 'pagination', paginationStyle);
3245
- // const leftArrow = createElement('span', 'leftArrow', {}, '<')
3246
- // const rightArrow = createElement('span', 'rightArrow', {}, '>')
3247
- const taskCard = createElement('div', 'taskCard', taskDescriptionCard);
3248
- const taskText = createElement('div', 'taskText', taskTextStyle);
3249
- const taskDescription = createElement('div', 'taskDescription', taskDescriptionStyle);
3250
- const taskButtons = createElement('div', 'taskButtons', taskButtonsRow);
3251
- const inputTitle = createElement('div', 'taskText', taskTextStyle);
3252
- inputTitle.textContent = 'Your answer';
3253
- const inputArea = createElement('textarea', 'taskDescription', {
3254
- resize: 'vertical',
3255
- });
3256
- const inputContainer = createElement('div', 'inputArea', taskDescriptionCard);
3257
- inputContainer.append(inputTitle, inputArea);
3258
- const closePanelButton = createElement('div', 'closePanelButton', taskButtonStyle, 'Collapse Panel');
3259
- const nextButton = createElement('div', 'nextButton', taskButtonBorderedStyle, 'Done, Next');
3260
- titleContainer.append(title, icon);
3261
- taskCard.append(taskText, taskDescription);
3262
- taskButtons.append(closePanelButton, nextButton);
3263
- content.append(pagination, taskCard, inputContainer, taskButtons);
3264
- section.append(titleContainer, content);
3265
- const updateTaskContent = () => {
3266
- const task = tasks[this.currentTaskIndex];
3267
- taskText.textContent = task.title;
3268
- taskDescription.textContent = task.description;
3269
- if (task.allow_typing) {
3270
- inputContainer.style.display = 'flex';
3271
- }
3272
- else {
3273
- inputContainer.style.display = 'none';
3274
- }
3275
- };
3276
- // pagination.appendChild(leftArrow)
3277
- tasks.forEach((_, index) => {
3278
- const pageNumber = createElement('span', `or_task_${index}`, {
3279
- outline: '1px solid #efefef',
3280
- fontSize: '13px',
3281
- height: '24px',
3282
- width: '24px',
3283
- display: 'flex',
3284
- flexDirection: 'column',
3285
- alignItems: 'center',
3286
- justifyContent: 'center',
3287
- borderRadius: '6.25em',
3288
- }, (index + 1).toString());
3289
- pageNumber.id = `or_task_${index}`;
3290
- pagination.append(pageNumber);
3291
- });
3292
- // pagination.appendChild(rightArrow)
3293
- const toggleTasksVisibility = () => {
3294
- this.widgetTasksVisible = !this.widgetTasksVisible;
3295
- icon.textContent = this.widgetTasksVisible ? '-' : '+';
3296
- Object.assign(content.style, this.widgetTasksVisible ? contentStyle : { display: 'none' });
3297
- };
3298
- this.hideTaskSection = () => {
3299
- icon.textContent = '+';
3300
- Object.assign(content.style, {
3301
- display: 'none',
3302
- });
3303
- this.widgetTasksVisible = false;
3304
- return false;
3305
- };
3306
- this.showTaskSection = () => {
3307
- icon.textContent = '-';
3308
- Object.assign(content.style, contentStyle);
3309
- this.widgetTasksVisible = true;
3310
- return true;
3311
- };
3312
- const highlightActive = () => {
3313
- const activeTaskEl = document.getElementById(`or_task_${this.currentTaskIndex}`);
3314
- if (activeTaskEl) {
3315
- Object.assign(activeTaskEl.style, taskNumberActive);
3316
- }
3317
- for (let i = 0; i < this.currentTaskIndex; i++) {
3318
- const taskEl = document.getElementById(`or_task_${i}`);
3319
- if (taskEl) {
3320
- Object.assign(taskEl.style, taskNumberDone);
3321
- }
3322
- }
3323
- };
3324
- titleContainer.onclick = toggleTasksVisibility;
3325
- closePanelButton.onclick = this.collapseWidget;
3326
- nextButton.onclick = () => {
3327
- const textAnswer = tasks[this.currentTaskIndex].allow_typing ? inputArea.value : undefined;
3328
- inputArea.value = '';
3329
- void this.signalManager?.signalTask(tasks[this.currentTaskIndex].task_id, 'done', textAnswer);
3330
- if (this.currentTaskIndex < tasks.length - 1) {
3331
- this.currentTaskIndex++;
3332
- updateTaskContent();
3333
- const durations = this.signalManager?.getDurations();
3334
- if (durations &&
3335
- durations.tasks.findIndex((t) => t.taskId === tasks[this.currentTaskIndex].task_id) === -1) {
3336
- durations.tasks.push({
3337
- taskId: tasks[this.currentTaskIndex].task_id,
3338
- started: this.app.timestamp(),
3339
- });
3340
- this.signalManager?.setDurations(durations);
3341
- }
3342
- void this.signalManager?.signalTask(tasks[this.currentTaskIndex].task_id, 'begin');
3343
- highlightActive();
3344
- }
3345
- else {
3346
- this.showEndSection();
3347
- }
3348
- this.app.localStorage.setItem('or_uxt_task_index', this.currentTaskIndex.toString());
3349
- };
3350
- setTimeout(() => {
3351
- const firstTaskEl = document.getElementById('or_task_0');
3352
- if (firstTaskEl) {
3353
- Object.assign(firstTaskEl.style, taskNumberActive);
3354
- }
3355
- updateTaskContent();
3356
- highlightActive();
3357
- }, 1);
3358
- return section;
3359
- }
3360
- showEndSection() {
3361
- let isLoading = true;
3362
- void this.signalManager?.signalTest('done');
3363
- const section = createElement('div', 'end_section_or', endSectionStyle);
3364
- const title = createElement('div', 'end_title_or', {
3365
- fontSize: '1.25rem',
3366
- fontWeight: '500',
3367
- }, 'Thank you! 👍');
3368
- const description = createElement('div', 'end_description_or', {}, this.test?.conclusion ??
3369
- 'Thank you for participating in our usability test. Your feedback has been captured and will be used to enhance our website. \n' +
3370
- '\n' +
3371
- 'We appreciate your time and valuable input.');
3372
- const button = createElement('div', 'end_button_or', buttonWidgetStyle, 'Submitting Feedback');
3373
- const spinner = createSpinner();
3374
- button.appendChild(spinner);
3375
- if (this.test?.reqMic || this.test?.reqCamera) {
3376
- void this.userRecorder
3377
- .sendToAPI()
3378
- .then(() => {
3379
- button.removeChild(spinner);
3380
- button.textContent = 'End Session';
3381
- isLoading = false;
3382
- })
3383
- .catch((err) => {
3384
- console.error(err);
3385
- button.removeChild(spinner);
3386
- button.textContent = 'End Session';
3387
- isLoading = false;
3388
- });
3389
- }
3390
- else {
3391
- button.removeChild(spinner);
3392
- button.textContent = 'End Session';
3393
- isLoading = false;
3394
- }
3395
- if (this.taskSection) {
3396
- this.container.removeChild(this.taskSection);
3397
- }
3398
- if (this.descriptionSection) {
3399
- this.container.removeChild(this.descriptionSection);
3400
- }
3401
- if (this.stopButton && this.stopButtonContainer) {
3402
- this.container.removeChild(this.stopButtonContainer);
3403
- }
3404
- button.onclick = () => {
3405
- if (isLoading)
3406
- return;
3407
- window.close();
3408
- document.body.removeChild(this.bg);
3409
- };
3410
- section.append(title, description, button);
3411
- this.endSection = section;
3412
- this.container.append(section);
3413
- }
3414
- }
3415
-
3416
- //@ts-ignore
3417
- function isNode(sth) {
3418
- return !!sth && sth.nodeType != null;
3419
- }
3420
- function isSVGElement(node) {
3421
- return (node.namespaceURI === 'http://www.w3.org/2000/svg' || node.localName === 'svg');
3422
- }
3423
- function isUseElement(node) {
3424
- return node.localName === 'use';
3425
- }
3426
- function isElementNode(node) {
3427
- return node.nodeType === Node.ELEMENT_NODE;
3428
- }
3429
- function isCommentNode(node) {
3430
- return node.nodeType === Node.COMMENT_NODE;
3431
- }
3432
- function isTextNode(node) {
3433
- return node.nodeType === Node.TEXT_NODE;
3434
- }
3435
- function isDocument(node) {
3436
- return node.nodeType === Node.DOCUMENT_NODE;
3437
- }
3438
- function isRootNode(node) {
3439
- return node.nodeType === Node.DOCUMENT_NODE || node.nodeType === Node.DOCUMENT_FRAGMENT_NODE;
3440
- }
3441
- function hasTag(el, tagName) {
3442
- // @ts-ignore
3443
- return el.localName === tagName;
3444
- }
3445
-
3446
- class CanvasRecorder {
3447
- constructor(app, options) {
3448
- this.app = app;
3449
- this.options = options;
3450
- this.snapshots = {};
3451
- this.intervals = [];
3452
- this.restartTracking = () => {
3453
- this.clear();
3454
- this.app.nodes.scanTree(this.captureCanvas);
3455
- };
3456
- this.captureCanvas = (node) => {
3457
- const id = this.app.nodes.getID(node);
3458
- if (!id || !hasTag(node, 'canvas')) {
3459
- return;
3460
- }
3461
- const isIgnored = this.app.sanitizer.isObscured(id) || this.app.sanitizer.isHidden(id);
3462
- if (isIgnored || !hasTag(node, 'canvas') || this.snapshots[id]) {
3463
- return;
3464
- }
3465
- const observer = new IntersectionObserver((entries) => {
3466
- entries.forEach((entry) => {
3467
- if (entry.isIntersecting) {
3468
- if (entry.target) {
3469
- if (this.snapshots[id] && this.snapshots[id].createdAt) {
3470
- this.snapshots[id].paused = false;
3471
- }
3472
- else {
3473
- this.recordCanvas(entry.target, id);
3474
- }
3475
- /**
3476
- * We can switch this to start observing when element is in the view
3477
- * but otherwise right now we're just pausing when it's not
3478
- * just to save some bandwidth and space on backend
3479
- * */
3480
- // observer.unobserve(entry.target)
3481
- }
3482
- else {
3483
- if (this.snapshots[id]) {
3484
- this.snapshots[id].paused = true;
3485
- }
3486
- }
3487
- }
3488
- });
1655
+ const observer = new IntersectionObserver((entries) => {
1656
+ entries.forEach((entry) => {
1657
+ if (entry.isIntersecting) {
1658
+ if (this.snapshots[id] && this.snapshots[id].createdAt) {
1659
+ this.snapshots[id].paused = false;
1660
+ }
1661
+ else {
1662
+ this.recordCanvas(entry.target, id);
1663
+ }
1664
+ /**
1665
+ * We can switch this to start observing when element is in the view
1666
+ * but otherwise right now we're just pausing when it's not
1667
+ * just to save some bandwidth and space on backend
1668
+ * */
1669
+ // observer.unobserve(entry.target)
1670
+ }
1671
+ else {
1672
+ if (this.snapshots[id]) {
1673
+ this.snapshots[id].paused = true;
1674
+ }
1675
+ }
1676
+ });
3489
1677
  });
1678
+ this.observers.set(id, observer);
3490
1679
  observer.observe(node);
3491
1680
  };
3492
1681
  this.recordCanvas = (node, id) => {
@@ -3496,15 +1685,23 @@ class CanvasRecorder {
3496
1685
  createdAt: ts,
3497
1686
  paused: false,
3498
1687
  dummy: document.createElement('canvas'),
1688
+ isCapturing: false,
1689
+ isStopped: false,
3499
1690
  };
3500
1691
  const canvasMsg = CanvasNode(id.toString(), ts);
3501
1692
  this.app.send(canvasMsg);
1693
+ const cachedCanvas = node;
3502
1694
  const captureFn = (canvas) => {
1695
+ if (!this.snapshots[id] || this.snapshots[id].isCapturing || this.snapshots[id].isStopped) {
1696
+ return;
1697
+ }
1698
+ this.snapshots[id].isCapturing = true;
3503
1699
  captureSnapshot(canvas, this.options.quality, this.snapshots[id].dummy, this.options.fixedScaling, this.fileExt, (blob) => {
3504
- if (!blob)
1700
+ if (this.snapshots[id]) {
1701
+ this.snapshots[id].isCapturing = false;
1702
+ }
1703
+ if (!blob || !this.snapshots[id] || this.snapshots[id].isStopped) {
3505
1704
  return;
3506
- if (!this.snapshots[id]) {
3507
- return this.app.debug.warn('Canvas not present in snapshots after capture:', this.snapshots, id);
3508
1705
  }
3509
1706
  this.snapshots[id].images.push({ id: this.app.timestamp(), data: blob });
3510
1707
  if (this.snapshots[id].images.length > 9) {
@@ -3514,34 +1711,31 @@ class CanvasRecorder {
3514
1711
  });
3515
1712
  };
3516
1713
  const int = setInterval(() => {
3517
- const cid = this.app.nodes.getID(node);
3518
- const canvas = cid ? this.app.nodes.getNode(cid) : undefined;
3519
- if (!this.snapshots[id]) {
1714
+ const snapshot = this.snapshots[id];
1715
+ if (!snapshot || snapshot.isStopped) {
3520
1716
  this.app.debug.log('Canvas is not present in {snapshots}');
3521
- clearInterval(int);
1717
+ this.cleanupCanvas(id);
3522
1718
  return;
3523
1719
  }
3524
- if (!canvas || !hasTag(canvas, 'canvas') || canvas !== node) {
3525
- this.app.debug.log('Canvas element not in sync', canvas, node);
3526
- clearInterval(int);
1720
+ if (!document.contains(cachedCanvas)) {
1721
+ this.app.debug.log('Canvas element not in sync', cachedCanvas, node);
1722
+ this.cleanupCanvas(id);
3527
1723
  return;
3528
1724
  }
3529
- else {
3530
- if (!this.snapshots[id].paused) {
3531
- if (this.options.useAnimationFrame) {
3532
- requestAnimationFrame(() => {
3533
- captureFn(canvas);
3534
- });
3535
- }
3536
- else {
3537
- captureFn(canvas);
3538
- }
1725
+ if (!snapshot.paused) {
1726
+ if (this.options.useAnimationFrame) {
1727
+ requestAnimationFrame(() => {
1728
+ captureFn(cachedCanvas);
1729
+ });
1730
+ }
1731
+ else {
1732
+ captureFn(cachedCanvas);
3539
1733
  }
3540
1734
  }
3541
1735
  }, this.interval);
3542
- this.intervals.push(int);
1736
+ this.intervals.set(id, int);
3543
1737
  };
3544
- this.fileExt = options.fileExt ?? 'webp';
1738
+ this.fileExt = 'webp';
3545
1739
  this.interval = 1000 / options.fps;
3546
1740
  }
3547
1741
  startTracking() {
@@ -3554,16 +1748,90 @@ class CanvasRecorder {
3554
1748
  if (Object.keys(this.snapshots).length === 0) {
3555
1749
  return;
3556
1750
  }
3557
- const formData = new FormData();
3558
- images.forEach((snapshot) => {
3559
- const blob = snapshot.data;
3560
- if (!blob)
3561
- return;
3562
- formData.append('snapshot', blob, `${createdAt}_${canvasId}_${snapshot.id}.${this.fileExt}`);
3563
- if (this.options.isDebug) {
3564
- saveImageData(blob, `${createdAt}_${canvasId}_${snapshot.id}.${this.fileExt}`);
1751
+ if (this.pendingBatches.length >= this.MAX_QUEUE_SIZE) {
1752
+ this.app.debug.warn('Upload queue full, dropping canvas batch');
1753
+ return;
1754
+ }
1755
+ this.pendingBatches.push({ images, canvasId, createdAt });
1756
+ if (!this.isProcessingQueue) {
1757
+ this.processUploadQueue();
1758
+ }
1759
+ }
1760
+ async processUploadQueue() {
1761
+ this.isProcessingQueue = true;
1762
+ while (this.pendingBatches.length > 0) {
1763
+ if (this.uploadQueue >= this.MAX_CONCURRENT_UPLOADS) {
1764
+ await new Promise((resolve) => setTimeout(resolve, 100));
1765
+ continue;
3565
1766
  }
3566
- });
1767
+ const batch = this.pendingBatches.shift();
1768
+ if (!batch)
1769
+ break;
1770
+ this.uploadBatch(batch.images, batch.canvasId, batch.createdAt);
1771
+ }
1772
+ this.isProcessingQueue = false;
1773
+ }
1774
+ async uploadBatch(images, canvasId, createdAt) {
1775
+ if (this.options.isDebug) {
1776
+ const fileEntries = [];
1777
+ images.forEach((snapshot) => {
1778
+ if (!snapshot.data)
1779
+ return;
1780
+ fileEntries.push({ data: snapshot.data, name: `${createdAt}_${canvasId}_${snapshot.id}.${this.fileExt}` });
1781
+ });
1782
+ void saveArchive(fileEntries, `canvas_${canvasId}_${createdAt}`);
1783
+ return;
1784
+ }
1785
+ let formData;
1786
+ if (this.options.framesSupport) {
1787
+ // Pack frames into binary format: [uint64 LE timestamp][uint32 LE size][data] per frame
1788
+ const buffers = [];
1789
+ let totalSize = 0;
1790
+ for (const snapshot of images) {
1791
+ if (!snapshot.data)
1792
+ continue;
1793
+ const ab = await snapshot.data.arrayBuffer();
1794
+ buffers.push(ab);
1795
+ totalSize += 8 + 4 + ab.byteLength; // uint64 ts + uint32 size + data
1796
+ }
1797
+ if (totalSize === 0)
1798
+ return;
1799
+ const packed = new ArrayBuffer(totalSize);
1800
+ const view = new DataView(packed);
1801
+ const bytes = new Uint8Array(packed);
1802
+ let offset = 0;
1803
+ for (let i = 0; i < images.length; i++) {
1804
+ if (!images[i].data)
1805
+ continue;
1806
+ const ab = buffers.shift();
1807
+ const ts = images[i].id;
1808
+ // uint64 LE as two uint32 LE writes -- timestamp
1809
+ view.setUint32(offset, ts % 0x100000000, true);
1810
+ view.setUint32(offset + 4, Math.floor(ts / 0x100000000), true);
1811
+ offset += 8;
1812
+ // uint32 LE -- size
1813
+ view.setUint32(offset, ab.byteLength, true);
1814
+ offset += 4;
1815
+ // image data
1816
+ bytes.set(new Uint8Array(ab), offset);
1817
+ offset += ab.byteLength;
1818
+ }
1819
+ formData = new FormData();
1820
+ formData.append('type', 'frames');
1821
+ const fileName = `${createdAt}_${canvasId}.${this.fileExt}.frames`;
1822
+ formData.append('frames', new Blob([packed]), fileName);
1823
+ }
1824
+ else {
1825
+ // Legacy: send individual image files
1826
+ formData = new FormData();
1827
+ images.forEach((snapshot) => {
1828
+ const blob = snapshot.data;
1829
+ if (!blob)
1830
+ return;
1831
+ const name = `${createdAt}_${canvasId}_${snapshot.id}.${this.fileExt}`;
1832
+ formData.append('snapshot', blob, name);
1833
+ });
1834
+ }
3567
1835
  const initRestart = () => {
3568
1836
  this.app.debug.log('Restarting tracker; token expired');
3569
1837
  this.app.stop(false);
@@ -3571,6 +1839,7 @@ class CanvasRecorder {
3571
1839
  void this.app.start({}, true);
3572
1840
  }, 250);
3573
1841
  };
1842
+ this.uploadQueue++;
3574
1843
  fetch(this.app.options.ingestPoint + '/v1/web/images', {
3575
1844
  method: 'POST',
3576
1845
  headers: {
@@ -3586,10 +1855,50 @@ class CanvasRecorder {
3586
1855
  })
3587
1856
  .catch((e) => {
3588
1857
  this.app.debug.error('error saving canvas', e);
1858
+ })
1859
+ .finally(() => {
1860
+ this.uploadQueue--;
3589
1861
  });
3590
1862
  }
1863
+ cleanupCanvas(id) {
1864
+ if (this.snapshots[id]) {
1865
+ this.snapshots[id].isStopped = true;
1866
+ }
1867
+ const interval = this.intervals.get(id);
1868
+ if (interval) {
1869
+ clearInterval(interval);
1870
+ this.intervals.delete(id);
1871
+ }
1872
+ const observer = this.observers.get(id);
1873
+ if (observer) {
1874
+ observer.disconnect();
1875
+ this.observers.delete(id);
1876
+ }
1877
+ if (this.snapshots[id]?.dummy) {
1878
+ const dummy = this.snapshots[id].dummy;
1879
+ dummy.width = 0;
1880
+ dummy.height = 0;
1881
+ }
1882
+ delete this.snapshots[id];
1883
+ }
3591
1884
  clear() {
3592
- this.intervals.forEach((int) => clearInterval(int));
1885
+ // Flush remaining images before cleanup
1886
+ Object.keys(this.snapshots).forEach((idStr) => {
1887
+ const id = parseInt(idStr, 10);
1888
+ const snapshot = this.snapshots[id];
1889
+ if (snapshot && snapshot.images.length > 0) {
1890
+ this.sendSnaps(snapshot.images, id, snapshot.createdAt);
1891
+ snapshot.images = [];
1892
+ }
1893
+ });
1894
+ Object.keys(this.snapshots).forEach((idStr) => {
1895
+ const id = parseInt(idStr, 10);
1896
+ this.cleanupCanvas(id);
1897
+ });
1898
+ // don't clear pendingBatches or stop queue processing
1899
+ // to allow flushed images to finish uploading in the background
1900
+ this.intervals.clear();
1901
+ this.observers.clear();
3593
1902
  this.snapshots = {};
3594
1903
  }
3595
1904
  }
@@ -3616,15 +1925,80 @@ function captureSnapshot(canvas, quality = 'medium', dummy, fixedScaling = false
3616
1925
  canvas.toBlob(onBlob, imageFormat, qualityInt[quality]);
3617
1926
  }
3618
1927
  }
3619
- function saveImageData(imageDataBlob, name) {
3620
- const imageDataUrl = URL.createObjectURL(imageDataBlob);
1928
+ async function saveArchive(files, archiveName) {
1929
+ const zipBlob = await createZipBlob(files);
1930
+ const url = URL.createObjectURL(zipBlob);
3621
1931
  const link = document.createElement('a');
3622
- link.href = imageDataUrl;
3623
- link.download = name;
1932
+ link.href = url;
1933
+ link.download = `${archiveName}.zip`;
3624
1934
  link.style.display = 'none';
3625
1935
  document.body.appendChild(link);
3626
1936
  link.click();
3627
1937
  document.body.removeChild(link);
1938
+ URL.revokeObjectURL(url);
1939
+ }
1940
+ async function createZipBlob(files) {
1941
+ const parts = [];
1942
+ const centralDir = [];
1943
+ let offset = 0;
1944
+ for (const file of files) {
1945
+ const nameBytes = new TextEncoder().encode(file.name);
1946
+ const dataBytes = new Uint8Array(await file.data.arrayBuffer());
1947
+ const crc = crc32(dataBytes);
1948
+ // Local file header (30 bytes + filename)
1949
+ const local = new Uint8Array(30 + nameBytes.length);
1950
+ const lv = new DataView(local.buffer);
1951
+ lv.setUint32(0, 0x04034b50, true);
1952
+ lv.setUint16(4, 20, true);
1953
+ lv.setUint16(8, 0, true);
1954
+ lv.setUint32(14, crc, true);
1955
+ lv.setUint32(18, dataBytes.length, true);
1956
+ lv.setUint32(22, dataBytes.length, true);
1957
+ lv.setUint16(26, nameBytes.length, true);
1958
+ local.set(nameBytes, 30);
1959
+ // Central directory entry (46 bytes + filename)
1960
+ const cd = new Uint8Array(46 + nameBytes.length);
1961
+ const cv = new DataView(cd.buffer);
1962
+ cv.setUint32(0, 0x02014b50, true);
1963
+ cv.setUint16(4, 20, true);
1964
+ cv.setUint16(6, 20, true);
1965
+ cv.setUint32(16, crc, true);
1966
+ cv.setUint32(20, dataBytes.length, true);
1967
+ cv.setUint32(24, dataBytes.length, true);
1968
+ cv.setUint16(28, nameBytes.length, true);
1969
+ cv.setUint32(42, offset, true);
1970
+ cd.set(nameBytes, 46);
1971
+ parts.push(local);
1972
+ parts.push(dataBytes);
1973
+ centralDir.push(cd);
1974
+ offset += local.length + dataBytes.length;
1975
+ }
1976
+ const cdOffset = offset;
1977
+ let cdSize = 0;
1978
+ for (const entry of centralDir) {
1979
+ parts.push(entry);
1980
+ cdSize += entry.length;
1981
+ }
1982
+ // End of central directory (22 bytes)
1983
+ const eocd = new Uint8Array(22);
1984
+ const ev = new DataView(eocd.buffer);
1985
+ ev.setUint32(0, 0x06054b50, true);
1986
+ ev.setUint16(8, files.length, true);
1987
+ ev.setUint16(10, files.length, true);
1988
+ ev.setUint32(12, cdSize, true);
1989
+ ev.setUint32(16, cdOffset, true);
1990
+ parts.push(eocd);
1991
+ return new Blob(parts, { type: 'application/zip' });
1992
+ }
1993
+ function crc32(data) {
1994
+ let crc = 0xffffffff;
1995
+ for (let i = 0; i < data.length; i++) {
1996
+ crc ^= data[i];
1997
+ for (let j = 0; j < 8; j++) {
1998
+ crc = (crc >>> 1) ^ (crc & 1 ? 0xedb88320 : 0);
1999
+ }
2000
+ }
2001
+ return (crc ^ 0xffffffff) >>> 0;
3628
2002
  }
3629
2003
 
3630
2004
  const LogLevel = {
@@ -3745,6 +2119,27 @@ class Maintainer {
3745
2119
  }
3746
2120
  }
3747
2121
 
2122
+ // 4 levels, 128 frames between each level, 8_388_608 nodes per page
2123
+ // lets hope no one will need more :D
2124
+ const BITS_LEVEL = 2; // 4
2125
+ const BITS_ORDER = 7; // 128
2126
+ const BITS_NODE = 22; // 8_388_608
2127
+ const SHIFT_ORDER = BITS_NODE;
2128
+ const SHIFT_LEVEL = BITS_NODE + BITS_ORDER;
2129
+ const MASK_NODE = (1 << BITS_NODE) - 1;
2130
+ const MASK_ORDER = (1 << BITS_ORDER) - 1;
2131
+ const MASK_LEVEL = (1 << BITS_LEVEL) - 1;
2132
+ function pack(level, order, nodeId) {
2133
+ if (level < 0 || level > MASK_LEVEL)
2134
+ throw new RangeError('OR: nesting level overflow, max 4');
2135
+ if (order < 0 || order > MASK_ORDER)
2136
+ throw new RangeError('OR: frame order overflow, max 128');
2137
+ const v = ((level & MASK_LEVEL) << SHIFT_LEVEL) |
2138
+ ((order & MASK_ORDER) << SHIFT_ORDER) |
2139
+ (nodeId & MASK_NODE);
2140
+ return v >>> 0;
2141
+ }
2142
+
3748
2143
  class Nodes {
3749
2144
  constructor(params) {
3750
2145
  this.nodes = new Map();
@@ -3772,6 +2167,9 @@ class Nodes {
3772
2167
  }
3773
2168
  listeners.push([type, listener, useCapture]);
3774
2169
  };
2170
+ this.createFrameId = (level, frameOrder) => {
2171
+ return pack(level, frameOrder, 0);
2172
+ };
3775
2173
  this.unregisterNode = (node) => {
3776
2174
  const id = node[this.node_id];
3777
2175
  if (id !== undefined) {
@@ -3792,16 +2190,8 @@ class Nodes {
3792
2190
  this.maintainer = new Maintainer(this.nodes, this.unregisterNode, params.maintainer);
3793
2191
  this.maintainer.start();
3794
2192
  }
3795
- syntheticMode(frameOrder) {
3796
- const maxSafeNumber = Number.MAX_SAFE_INTEGER;
3797
- const placeholderSize = 99999999;
3798
- const nextFrameId = placeholderSize * frameOrder;
3799
- // I highly doubt that this will ever happen,
3800
- // but it will be easier to debug if it does
3801
- if (nextFrameId > maxSafeNumber) {
3802
- throw new Error('Placeholder id overflow');
3803
- }
3804
- this.nextNodeId = nextFrameId;
2193
+ crossdomainMode(level, frameOrder) {
2194
+ this.nextNodeId = this.createFrameId(level, frameOrder);
3805
2195
  }
3806
2196
  registerNode(node) {
3807
2197
  let id = node[this.node_id];
@@ -4566,8 +2956,7 @@ class Observer {
4566
2956
  sl.assignedNodes({ flatten: true }).forEach((n) => {
4567
2957
  const nid = this.app.nodes.getID(n);
4568
2958
  if (nid !== undefined) {
4569
- this.recents.set(nid, RecentsType.Removed);
4570
- this.commitNode(nid);
2959
+ this.recents.set(nid, RecentsType.Changed);
4571
2960
  }
4572
2961
  });
4573
2962
  });
@@ -4634,13 +3023,6 @@ class Observer {
4634
3023
  return true;
4635
3024
  }
4636
3025
  let slot = node.assignedSlot;
4637
- let isLightDom = false;
4638
- if (slot) {
4639
- // Check if the node is in light DOM (not in shadow DOM)
4640
- // This is a workaround for the issue with shadow DOM and slots
4641
- // where the slot is not assigned to the node in shadow DOM.
4642
- isLightDom = node.getRootNode() instanceof ShadowRoot;
4643
- }
4644
3026
  const parent = node.parentNode;
4645
3027
  let parentID;
4646
3028
  // Disable parent check for the upper context HTMLHtmlElement, because it is root there... (before)
@@ -4653,15 +3035,7 @@ class Observer {
4653
3035
  this.unbindTree(node);
4654
3036
  return false;
4655
3037
  }
4656
- if (isLightDom && slot) {
4657
- parentID = this.app.nodes.getID(slot);
4658
- // in light dom, we don't "slot" the node,
4659
- // but rather use the slot as a parent
4660
- slot = null;
4661
- }
4662
- else {
4663
- parentID = this.app.nodes.getID(parent);
4664
- }
3038
+ parentID = this.app.nodes.getID(parent);
4665
3039
  if (parentID === undefined) {
4666
3040
  this.unbindTree(node);
4667
3041
  return false;
@@ -4917,6 +3291,7 @@ class IFrameOffsets {
4917
3291
 
4918
3292
  var InlineCssMode;
4919
3293
  (function (InlineCssMode) {
3294
+ InlineCssMode[InlineCssMode["Unset"] = -1] = "Unset";
4920
3295
  /** default behavior -- will parse and cache the css file on backend */
4921
3296
  InlineCssMode[InlineCssMode["Disabled"] = 0] = "Disabled";
4922
3297
  /** will attempt to record the linked css file as AdoptedStyleSheet object */
@@ -4926,7 +3301,8 @@ var InlineCssMode;
4926
3301
  /** will fetch the file, then save it as plain css inside <style> node */
4927
3302
  InlineCssMode[InlineCssMode["PlainFetched"] = 3] = "PlainFetched";
4928
3303
  })(InlineCssMode || (InlineCssMode = {}));
4929
- function getInlineOptions(mode) {
3304
+ const localhostStylesDoc = 'https://docs.openreplay.com/en/troubleshooting/localhost/';
3305
+ function getInlineOptions(mode, logger) {
4930
3306
  switch (mode) {
4931
3307
  case InlineCssMode.Inline:
4932
3308
  return {
@@ -4952,6 +3328,27 @@ function getInlineOptions(mode) {
4952
3328
  forcePlain: true,
4953
3329
  },
4954
3330
  };
3331
+ case InlineCssMode.Unset:
3332
+ const isLocalhost = /^https?:\/\/(localhost|127\.0\.0\.1)(:\d+)?\/?/.test(window.location.href);
3333
+ if (isLocalhost) {
3334
+ logger(`Enabling InlineCssMode.PlainFetched by default on localhost to preserve css styles, refer to ${localhostStylesDoc} for details, set InlineCssMode to 0 to skip this behavior`);
3335
+ return {
3336
+ inlineRemoteCss: true,
3337
+ inlinerOptions: {
3338
+ forceFetch: true,
3339
+ forcePlain: true,
3340
+ },
3341
+ };
3342
+ }
3343
+ else {
3344
+ return {
3345
+ inlineRemoteCss: false,
3346
+ inlinerOptions: {
3347
+ forceFetch: false,
3348
+ forcePlain: false,
3349
+ },
3350
+ };
3351
+ }
4955
3352
  case InlineCssMode.Disabled:
4956
3353
  default:
4957
3354
  return {
@@ -4974,7 +3371,7 @@ class TopObserver extends Observer {
4974
3371
  const observerOptions = {
4975
3372
  disableSprites: opts.disableSprites,
4976
3373
  disableThrottling: opts.disableThrottling,
4977
- ...getInlineOptions(opts.inlineCss)
3374
+ ...getInlineOptions(opts.inlineCss, console.warn),
4978
3375
  };
4979
3376
  super(params.app, true, observerOptions);
4980
3377
  this.iframeOffsets = new IFrameOffsets();
@@ -5025,7 +3422,10 @@ class TopObserver extends Observer {
5025
3422
  this.app.debug.info('doc already observed for', id);
5026
3423
  return;
5027
3424
  }
5028
- const observer = new IFrameObserver(this.app, false, {});
3425
+ const observer = new IFrameObserver(this.app, false, {
3426
+ disableSprites: this.options.disableSprites,
3427
+ ...getInlineOptions(this.options.inlineCss, console.warn),
3428
+ });
5029
3429
  this.iframeObservers.set(iframe, observer);
5030
3430
  this.docObservers.set(currentDoc, observer);
5031
3431
  this.iframeObserversArr.push(observer);
@@ -5047,7 +3447,10 @@ class TopObserver extends Observer {
5047
3447
  handle();
5048
3448
  }
5049
3449
  handleShadowRoot(shRoot) {
5050
- const observer = new ShadowRootObserver(this.app);
3450
+ const observer = new ShadowRootObserver(this.app, false, {
3451
+ disableSprites: this.options.disableSprites,
3452
+ ...getInlineOptions(this.options.inlineCss, console.warn),
3453
+ });
5051
3454
  this.shadowRootObservers.set(shRoot, observer);
5052
3455
  observer.observe(shRoot.host);
5053
3456
  }
@@ -5073,7 +3476,7 @@ class TopObserver extends Observer {
5073
3476
  this.app.nodes.callNodeCallbacks(document, true);
5074
3477
  }, window.document.documentElement);
5075
3478
  }
5076
- crossdomainObserve(rootNodeId, frameOder) {
3479
+ crossdomainObserve(rootNodeId, frameOder, frameLevel) {
5077
3480
  const observer = this;
5078
3481
  Element.prototype.attachShadow = function () {
5079
3482
  // eslint-disable-next-line
@@ -5082,8 +3485,11 @@ class TopObserver extends Observer {
5082
3485
  return shadow;
5083
3486
  };
5084
3487
  this.app.nodes.clear();
5085
- this.app.nodes.syntheticMode(frameOder);
5086
- const iframeObserver = new IFrameObserver(this.app);
3488
+ this.app.nodes.crossdomainMode(frameLevel, frameOder);
3489
+ const iframeObserver = new IFrameObserver(this.app, false, {
3490
+ disableSprites: this.options.disableSprites,
3491
+ ...getInlineOptions(this.options.inlineCss, console.warn),
3492
+ });
5087
3493
  this.iframeObservers.set(window.document, iframeObserver);
5088
3494
  iframeObserver.syntheticObserve(rootNodeId, window.document);
5089
3495
  }
@@ -5187,6 +3593,7 @@ class Sanitizer {
5187
3593
  }
5188
3594
  }
5189
3595
 
3596
+ const tokenSeparator = '_$_';
5190
3597
  class Session {
5191
3598
  constructor(params) {
5192
3599
  this.metadata = {};
@@ -5214,19 +3621,22 @@ class Session {
5214
3621
  this.getSessionToken = (projectKey) => {
5215
3622
  const tokenWithProject = this.token || this.app.sessionStorage.getItem(this.options.session_token_key);
5216
3623
  if (projectKey && tokenWithProject) {
5217
- const savedProject = tokenWithProject.split('_&_')[1];
3624
+ const savedProject = tokenWithProject.split(tokenSeparator)[1];
5218
3625
  if (!savedProject || savedProject !== projectKey) {
5219
3626
  this.app.sessionStorage.removeItem(this.options.session_token_key);
5220
3627
  this.token = undefined;
5221
3628
  return undefined;
5222
3629
  }
5223
3630
  }
5224
- const token = tokenWithProject ? tokenWithProject.split('_&_')[0] : null;
3631
+ const token = tokenWithProject ? tokenWithProject.split(tokenSeparator)[0] : null;
5225
3632
  return token || undefined;
5226
3633
  };
3634
+ this.getRawTokenWithProject = () => {
3635
+ return this.token || this.app.sessionStorage.getItem(this.options.session_token_key);
3636
+ };
5227
3637
  this.setSessionToken = (token, projectKey) => {
5228
- this.token = `${token}_&_${projectKey}`;
5229
- this.app.sessionStorage.setItem(this.options.session_token_key, `${token}_&_${projectKey}`);
3638
+ this.token = `${token}${tokenSeparator}${projectKey}`;
3639
+ this.app.sessionStorage.setItem(this.options.session_token_key, `${token}${tokenSeparator}${projectKey}`);
5230
3640
  };
5231
3641
  this.app = params.app;
5232
3642
  this.options = params.options;
@@ -5289,7 +3699,7 @@ class Session {
5289
3699
  }
5290
3700
  getSessionHash() {
5291
3701
  const pageNo = this.getPageNumber();
5292
- const token = this.getSessionToken();
3702
+ const token = this.getRawTokenWithProject();
5293
3703
  if (pageNo === undefined || token === undefined) {
5294
3704
  return;
5295
3705
  }
@@ -5382,9 +3792,8 @@ class Ticker {
5382
3792
  * this value is injected during build time via rollup
5383
3793
  * */
5384
3794
  // @ts-ignore
5385
- 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/web/i\",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}`};s&&(h[\"Content-Encoding\"]=\"gzip\"),null!==this.token?fetch(`${this.ingestURL}?batch=${null!==(e=this.pageNo)&&void 0!==e?e:\"noPageNum\"}_${null!=n?n:\"noBatchNum\"}`,{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(\"OpenReplay:\",e),this.retry(t,s,`${null!=i?i:\"noBatchNum\"}_reject:${e.message}`)})):setTimeout((()=>{this.sendBatch(t,s,`${null!=i?i:\"noBatchNum\"}_newToken`)}),500)}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 38: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 37:return this.uint(t[1])&&this.string(t[2])&&this.uint(t[3]);case 39:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4])&&this.uint(t[5])&&this.uint(t[6])&&this.uint(t[7]);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 59:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.uint(t[4])&&this.string(t[5])&&this.string(t[6])&&this.string(t[7]);case 67:case 73:return this.uint(t[1])&&this.string(t[2])&&this.uint(t[3])&&this.string(t[4]);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 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(\"OpenReplay: 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(\"OpenReplay: 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";
3795
+ 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/web/i\",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}`};s&&(h[\"Content-Encoding\"]=\"gzip\"),null!==this.token?fetch(`${this.ingestURL}?batch=${null!==(e=this.pageNo)&&void 0!==e?e:\"noPageNum\"}_${null!=n?n:\"noBatchNum\"}`,{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(\"OpenReplay:\",e),this.retry(t,s,`${null!=i?i:\"noBatchNum\"}_reject:${e.message}`)})):setTimeout((()=>{this.sendBatch(t,s,`${null!=i?i:\"noBatchNum\"}_newToken`)}),500)}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(\"OpenReplay: 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(\"OpenReplay: 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";
5386
3796
  const CANCELED = 'canceled';
5387
- const uxtStorageKey = 'or_uxt_active';
5388
3797
  const bufferStorageKey = 'or_buffer_1';
5389
3798
  const UnsuccessfulStart = (reason) => ({ reason, success: false });
5390
3799
  const SuccessfulStart = (body) => ({ ...body, success: true });
@@ -5420,6 +3829,9 @@ const proto = {
5420
3829
  startIframe: 'start tracker inside frame',
5421
3830
  // checking updates
5422
3831
  polling: 'hello-how-are-you-im-under-the-water-please-help-me',
3832
+ // happens if tab is old and has outdated token but
3833
+ // not communicating with backend to update it (for whatever reason)
3834
+ reset: 'reset-your-session-please',
5423
3835
  };
5424
3836
  class App {
5425
3837
  constructor(projectKey, sessionToken, options, signalError, insideIframe) {
@@ -5436,7 +3848,7 @@ class App {
5436
3848
  this.stopCallbacks = [];
5437
3849
  this.commitCallbacks = [];
5438
3850
  this.activityState = ActivityState.NotActive;
5439
- this.version = '16.4.11-beta.0'; // TODO: version compatability check inside each plugin.
3851
+ this.version = '17.2.3'; // TODO: version compatability check inside each plugin.
5440
3852
  this.socketMode = false;
5441
3853
  this.compressionThreshold = 24 * 1000;
5442
3854
  this.bc = null;
@@ -5446,10 +3858,7 @@ class App {
5446
3858
  this.rootId = null;
5447
3859
  this.pageFrames = [];
5448
3860
  this.frameOderNumber = 0;
5449
- this.features = {
5450
- 'feature-flags': true,
5451
- 'usability-test': true,
5452
- };
3861
+ this.frameLevel = 0;
5453
3862
  this.emptyBatchCounter = 0;
5454
3863
  /** used by child iframes for crossdomain only */
5455
3864
  this.parentActive = false;
@@ -5479,6 +3888,7 @@ class App {
5479
3888
  this.rootId = data.id;
5480
3889
  this.session.setSessionToken(data.token, this.projectKey);
5481
3890
  this.frameOderNumber = data.frameOrderNumber;
3891
+ this.frameLevel = data.frameLevel;
5482
3892
  this.debug.log('starting iframe tracking', data);
5483
3893
  this.allowAppStart();
5484
3894
  }
@@ -5528,8 +3938,8 @@ class App {
5528
3938
  line: proto.iframeId,
5529
3939
  id,
5530
3940
  token,
5531
- // since indexes go from 0 we +1
5532
3941
  frameOrderNumber: order,
3942
+ frameLevel: this.frameLevel + 1,
5533
3943
  };
5534
3944
  this.debug.log('Got child frame signal; nodeId', id, event.source, iframeData);
5535
3945
  // @ts-ignore
@@ -5547,7 +3957,8 @@ class App {
5547
3957
  * */
5548
3958
  if (data.line === proto.iframeBatch) {
5549
3959
  const msgBatch = data.messages;
5550
- const mappedMessages = msgBatch.map((msg) => {
3960
+ const mappedMessages = [];
3961
+ msgBatch.forEach((msg) => {
5551
3962
  if (msg[0] === 20 /* MType.MouseMove */) {
5552
3963
  let fixedMessage = msg;
5553
3964
  this.pageFrames.forEach((frame) => {
@@ -5557,7 +3968,7 @@ class App {
5557
3968
  fixedMessage = [type, x + left, y + top];
5558
3969
  }
5559
3970
  });
5560
- return fixedMessage;
3971
+ mappedMessages.push(fixedMessage);
5561
3972
  }
5562
3973
  if (msg[0] === 68 /* MType.MouseClick */) {
5563
3974
  let fixedMessage = msg;
@@ -5583,9 +3994,11 @@ class App {
5583
3994
  ];
5584
3995
  }
5585
3996
  });
5586
- return fixedMessage;
3997
+ mappedMessages.push(fixedMessage);
3998
+ }
3999
+ if (![28 /* MType.UserID */, 29 /* MType.UserAnonymousID */, 30 /* MType.Metadata */].includes(msg[0])) {
4000
+ mappedMessages.push(msg);
5587
4001
  }
5588
- return msg;
5589
4002
  });
5590
4003
  this.messages.push(...mappedMessages);
5591
4004
  }
@@ -5669,6 +4082,19 @@ class App {
5669
4082
  }
5670
4083
  };
5671
4084
  this.startTimeout = null;
4085
+ this.restart = () => {
4086
+ this.stop(false);
4087
+ this.waitStatus(ActivityState.NotActive).then(() => {
4088
+ this.allowAppStart();
4089
+ this.start(this.prevOpts, true)
4090
+ .then((r) => {
4091
+ this.debug.info('Session restart', r);
4092
+ })
4093
+ .catch((e) => {
4094
+ this.debug.error('Session restart failed', e);
4095
+ });
4096
+ });
4097
+ };
5672
4098
  this.send = (message, urgent = false) => {
5673
4099
  if (this.activityState === ActivityState.NotActive) {
5674
4100
  return;
@@ -5756,7 +4182,6 @@ class App {
5756
4182
  });
5757
4183
  });
5758
4184
  };
5759
- this.onUxtCb = [];
5760
4185
  this.contextId = Math.random().toString(36).slice(2);
5761
4186
  this.projectKey = projectKey;
5762
4187
  this.networkOptions = options.network;
@@ -5791,7 +4216,7 @@ class App {
5791
4216
  useAnimationFrame: false,
5792
4217
  },
5793
4218
  forceNgOff: false,
5794
- inlineCss: 0,
4219
+ inlineCss: InlineCssMode.Unset,
5795
4220
  disableSprites: false,
5796
4221
  disableThrottling: false,
5797
4222
  };
@@ -5824,7 +4249,6 @@ class App {
5824
4249
  app: this,
5825
4250
  isDictDisabled: Boolean(this.options.disableStringDict || this.options.crossdomain?.enabled),
5826
4251
  });
5827
- this.featureFlags = new FeatureFlags(this);
5828
4252
  this.tagWatcher = new TagWatcher({
5829
4253
  sessionStorage: this.sessionStorage,
5830
4254
  errLog: this.debug.error,
@@ -5832,7 +4256,10 @@ class App {
5832
4256
  });
5833
4257
  this.session.attachUpdateCallback(({ userID, metadata }) => {
5834
4258
  if (userID != null) {
5835
- // TODO: nullable userID
4259
+ if (!userID || typeof userID !== 'string' || userID.trim().length === 0) {
4260
+ this.debug.warn('Invalid userID (must be type string), ignoring.');
4261
+ return;
4262
+ }
5836
4263
  this.send(UserID(userID));
5837
4264
  }
5838
4265
  if (metadata != null) {
@@ -5849,6 +4276,7 @@ class App {
5849
4276
  * listen for messages from parent window, so we can signal that we're alive
5850
4277
  * */
5851
4278
  window.addEventListener('message', this.parentCrossDomainFrameListener);
4279
+ window.addEventListener('message', this.crossDomainIframeListener);
5852
4280
  setInterval(() => {
5853
4281
  window.parent.postMessage({
5854
4282
  line: proto.polling,
@@ -5903,6 +4331,12 @@ class App {
5903
4331
  });
5904
4332
  }
5905
4333
  }
4334
+ if (ev.data.line === proto.reset) {
4335
+ const newToken = ev.data.token;
4336
+ this.debug.log('Received reset signal from another tab');
4337
+ this.session.setSessionToken(newToken, this.projectKey);
4338
+ this.restart();
4339
+ }
5906
4340
  };
5907
4341
  }
5908
4342
  }
@@ -6006,15 +4440,21 @@ class App {
6006
4440
  else if (data.type === 'compress') {
6007
4441
  const batch = data.batch;
6008
4442
  const batchSize = batch.byteLength;
6009
- if (batchSize > this.compressionThreshold) {
6010
- gzip(data.batch, { mtime: 0 }, (err, result) => {
6011
- if (err) {
6012
- this.debug.error('Openreplay compression error:', err);
6013
- this.worker?.postMessage({ type: 'uncompressed', batch: batch });
6014
- }
6015
- else {
6016
- this.worker?.postMessage({ type: 'compressed', batch: result });
6017
- }
4443
+ const hasCompressionAPI = 'CompressionStream' in globalThis;
4444
+ if (batchSize > this.compressionThreshold && hasCompressionAPI) {
4445
+ const blob = new Blob([batch]);
4446
+ const stream = blob.stream().pipeThrough(new CompressionStream('gzip'));
4447
+ new Response(stream)
4448
+ .arrayBuffer()
4449
+ .then((compressedBuffer) => {
4450
+ this.worker?.postMessage({
4451
+ type: 'compressed',
4452
+ batch: new Uint8Array(compressedBuffer),
4453
+ });
4454
+ })
4455
+ .catch((err) => {
4456
+ this.debug.error('Openreplay compression error:', err);
4457
+ this.worker?.postMessage({ type: 'uncompressed', batch: batch });
6018
4458
  });
6019
4459
  }
6020
4460
  else {
@@ -6307,8 +4747,7 @@ class App {
6307
4747
  const {
6308
4748
  // this token is needed to fetch conditions and flags,
6309
4749
  // but it can't be used to record a session
6310
- token, userBrowser, userCity, userCountry, userDevice, userOS, userState, projectID, features, } = await r.json();
6311
- this.features = features ? features : this.features;
4750
+ token, userBrowser, userCity, userCountry, userDevice, userOS, userState, projectID, } = await r.json();
6312
4751
  this.session.assign({ projectID });
6313
4752
  this.session.setUserInfo({
6314
4753
  userBrowser,
@@ -6321,10 +4760,6 @@ class App {
6321
4760
  const onStartInfo = { sessionToken: token, userUUID: '', sessionID: '' };
6322
4761
  this.startCallbacks.forEach((cb) => cb(onStartInfo));
6323
4762
  await this.conditionsManager?.fetchConditions(projectID, token);
6324
- if (this.features['feature-flags']) {
6325
- await this.featureFlags.reloadFlags(token);
6326
- this.conditionsManager?.processFlags(this.featureFlags.flags);
6327
- }
6328
4763
  await this.tagWatcher.fetchTags(this.options.ingestPoint, token);
6329
4764
  }
6330
4765
  /**
@@ -6480,9 +4915,10 @@ class App {
6480
4915
  // Reset session metadata only if requested directly
6481
4916
  this.session.reset();
6482
4917
  }
4918
+ const userId = startOpts.userID ? startOpts.userID.trim() : undefined;
6483
4919
  this.session.assign({
6484
4920
  // MBTODO: maybe it would make sense to `forceNew` if the `userID` was changed
6485
- userID: startOpts.userID,
4921
+ userID: userId || undefined,
6486
4922
  metadata: startOpts.metadata,
6487
4923
  });
6488
4924
  const timestamp = now();
@@ -6520,6 +4956,7 @@ class App {
6520
4956
  assistOnly: startOpts.assistOnly ?? this.socketMode,
6521
4957
  width: window.screen.width,
6522
4958
  height: window.screen.height,
4959
+ referrer: document.referrer,
6523
4960
  }),
6524
4961
  });
6525
4962
  if (r.status !== 200) {
@@ -6536,8 +4973,7 @@ class App {
6536
4973
  delay, // derived from token
6537
4974
  sessionID, // derived from token
6538
4975
  startTimestamp, // real startTS (server time), derived from sessionID
6539
- userBrowser, userCity, userCountry, userDevice, userOS, userState, canvasEnabled, canvasQuality, canvasFPS, assistOnly: socketOnly, features, } = await r.json();
6540
- this.features = features ? features : this.features;
4976
+ userBrowser, userCity, userCountry, userDevice, userOS, userState, canvasEnabled, canvasQuality, canvasFPS, framesSupport, assistOnly: socketOnly, } = await r.json();
6541
4977
  if (typeof token !== 'string' ||
6542
4978
  typeof userUUID !== 'string' ||
6543
4979
  (typeof startTimestamp !== 'number' && typeof startTimestamp !== 'undefined') ||
@@ -6550,6 +4986,12 @@ class App {
6550
4986
  }
6551
4987
  this.delay = delay;
6552
4988
  this.session.setSessionToken(token, this.projectKey);
4989
+ if (sessionToken && sessionToken !== token) {
4990
+ this.bc?.postMessage({
4991
+ type: proto.reset,
4992
+ token: token,
4993
+ });
4994
+ }
6553
4995
  this.session.setUserInfo({
6554
4996
  userBrowser,
6555
4997
  userCity,
@@ -6590,17 +5032,9 @@ class App {
6590
5032
  if (startOpts.startCallback) {
6591
5033
  startOpts.startCallback(SuccessfulStart(onStartInfo));
6592
5034
  }
6593
- if (this.features['feature-flags']) {
6594
- try {
6595
- void this.featureFlags.reloadFlags();
6596
- }
6597
- catch (e) {
6598
- this.debug.log("Error getting feature flags", e);
6599
- }
6600
- }
6601
5035
  await this.tagWatcher.fetchTags(this.options.ingestPoint, token);
6602
5036
  this.activityState = ActivityState.Active;
6603
- if (this.options.crossdomain?.enabled && !this.insideIframe) {
5037
+ if (this.options.crossdomain?.enabled) {
6604
5038
  void this.bootChildrenFrames();
6605
5039
  }
6606
5040
  if (canvasEnabled && !this.options.canvas.disableCanvas) {
@@ -6612,6 +5046,7 @@ class App {
6612
5046
  isDebug: this.options.canvas.__save_canvas_locally,
6613
5047
  fixedScaling: this.options.canvas.fixedCanvasScaling,
6614
5048
  useAnimationFrame: this.options.canvas.useAnimationFrame,
5049
+ framesSupport: !!framesSupport,
6615
5050
  });
6616
5051
  }
6617
5052
  /** --------------- COLD START BUFFER ------------------*/
@@ -6627,44 +5062,13 @@ class App {
6627
5062
  /** --------------- COLD START BUFFER ------------------*/
6628
5063
  }
6629
5064
  if (this.insideIframe && this.rootId) {
6630
- this.observer.crossdomainObserve(this.rootId, this.frameOderNumber);
5065
+ this.observer.crossdomainObserve(this.rootId, this.frameOderNumber, this.frameLevel);
6631
5066
  }
6632
5067
  else {
6633
5068
  this.observer.observe();
6634
5069
  }
6635
5070
  this.ticker.start();
6636
5071
  this.canvasRecorder?.startTracking();
6637
- if (this.features['usability-test'] && !this.insideIframe) {
6638
- this.uxtManager = this.uxtManager
6639
- ? this.uxtManager
6640
- : new UserTestManager(this, uxtStorageKey);
6641
- let uxtId;
6642
- const savedUxtTag = this.localStorage.getItem(uxtStorageKey);
6643
- if (savedUxtTag) {
6644
- uxtId = parseInt(savedUxtTag, 10);
6645
- }
6646
- if (location?.search) {
6647
- const query = new URLSearchParams(location.search);
6648
- if (query.has('oruxt')) {
6649
- const qId = query.get('oruxt');
6650
- uxtId = qId ? parseInt(qId, 10) : undefined;
6651
- }
6652
- }
6653
- if (uxtId) {
6654
- if (!this.uxtManager.isActive) {
6655
- // eslint-disable-next-line
6656
- this.uxtManager.getTest(uxtId, token, Boolean(savedUxtTag)).then((id) => {
6657
- if (id) {
6658
- this.onUxtCb.forEach((cb) => cb(id));
6659
- }
6660
- });
6661
- }
6662
- else {
6663
- // @ts-ignore
6664
- this.onUxtCb.forEach((cb) => cb(uxtId));
6665
- }
6666
- }
6667
- }
6668
5072
  return SuccessfulStart(onStartInfo);
6669
5073
  }
6670
5074
  catch (reason) {
@@ -6685,13 +5089,6 @@ class App {
6685
5089
  return UnsuccessfulStart(errorMessage);
6686
5090
  }
6687
5091
  }
6688
- addOnUxtCb(cb) {
6689
- // @ts-ignore
6690
- this.onUxtCb.push(cb);
6691
- }
6692
- getUxtId() {
6693
- return this.uxtManager?.getTestId();
6694
- }
6695
5092
  async waitStart() {
6696
5093
  return new Promise((resolve) => {
6697
5094
  const int = setInterval(() => {
@@ -6780,7 +5177,7 @@ class App {
6780
5177
  stop(stopWorker = true) {
6781
5178
  if (this.activityState !== ActivityState.NotActive) {
6782
5179
  try {
6783
- if (!this.insideIframe && this.options.crossdomain?.enabled) {
5180
+ if (this.options.crossdomain?.enabled) {
6784
5181
  this.killChildrenFrames();
6785
5182
  }
6786
5183
  this.attributeSender.clear();
@@ -6812,9 +5209,16 @@ function Connection (app) {
6812
5209
  if (connection === undefined) {
6813
5210
  return;
6814
5211
  }
6815
- const sendConnectionInformation = () => app.send(ConnectionInformation(Math.round(connection.downlink * 1000), connection.type || 'unknown'));
6816
- sendConnectionInformation();
6817
- connection.addEventListener('change', sendConnectionInformation);
5212
+ const sendConnectionInformation = () => {
5213
+ app.send(ConnectionInformation(Math.round(connection.downlink * 1000), connection.effectiveType || 'unknown'));
5214
+ };
5215
+ app.attachStartCallback(() => {
5216
+ sendConnectionInformation();
5217
+ connection.addEventListener('change', sendConnectionInformation);
5218
+ });
5219
+ app.attachStopCallback(() => {
5220
+ connection.removeEventListener('change', sendConnectionInformation);
5221
+ });
6818
5222
  }
6819
5223
 
6820
5224
  const printError = IN_BROWSER && 'InstallTrigger' in window // detect Firefox
@@ -7343,19 +5747,21 @@ const labelElementFor = IN_BROWSER && 'labels' in HTMLInputElement.prototype
7343
5747
  }
7344
5748
  }
7345
5749
  };
7346
- function getInputLabel(node) {
7347
- let label = getLabelAttribute(node);
7348
- if (label === null) {
7349
- const labelElement = labelElementFor(node);
7350
- label =
7351
- (labelElement && labelElement.innerText) ||
7352
- node.placeholder ||
7353
- node.name ||
7354
- node.id ||
7355
- node.className ||
7356
- node.type;
7357
- }
7358
- return normSpaces(label).slice(0, 100);
5750
+ function getInputLabel(node, customAttributes) {
5751
+ const openreplayLabel = getLabelAttribute(node);
5752
+ if (openreplayLabel !== null)
5753
+ return normSpaces(openreplayLabel).slice(0, 100);
5754
+ const customAttributeLabel = getCustomAttributeLabel(node, customAttributes);
5755
+ if (customAttributeLabel)
5756
+ return customAttributeLabel;
5757
+ if (node.id)
5758
+ return `#${node.id}`;
5759
+ const classLabel = getClassSelector(node);
5760
+ if (classLabel)
5761
+ return classLabel;
5762
+ const labelElement = labelElementFor(node);
5763
+ const label = node.name || node.placeholder || (labelElement && labelElement.innerText) || node.type;
5764
+ return normSpaces(label || '').slice(0, 100);
7359
5765
  }
7360
5766
  const InputMode = {
7361
5767
  Plain: 0,
@@ -7436,7 +5842,7 @@ function Input (app, opts) {
7436
5842
  }, 3);
7437
5843
  function sendInputChange(id, node, hesitationTime, inputTime) {
7438
5844
  const { value, mask } = getInputValue(id, node);
7439
- let label = getInputLabel(node);
5845
+ let label = getInputLabel(node, options.customAttributes);
7440
5846
  if (app.sanitizer.privateMode) {
7441
5847
  label = label.replaceAll(/./g, '*');
7442
5848
  }
@@ -7545,21 +5951,28 @@ function _getTarget(target, document) {
7545
5951
  return target === document.documentElement ? null : target;
7546
5952
  }
7547
5953
  function Mouse (app, options) {
7548
- const { disableClickmaps = false } = options || {};
5954
+ const { disableClickmaps = false, customAttributes } = options || {};
7549
5955
  function getTargetLabel(target) {
7550
5956
  const dl = getLabelAttribute(target);
7551
5957
  if (dl !== null) {
7552
5958
  return dl;
7553
5959
  }
7554
5960
  if (hasTag(target, 'input')) {
7555
- return getInputLabel(target);
7556
- }
5961
+ return getInputLabel(target, customAttributes);
5962
+ }
5963
+ const customAttributeLabel = getCustomAttributeLabel(target, customAttributes);
5964
+ if (customAttributeLabel)
5965
+ return customAttributeLabel;
5966
+ if (target.id)
5967
+ return `#${target.id}`;
5968
+ const classLabel = getClassSelector(target);
5969
+ if (classLabel)
5970
+ return classLabel;
7557
5971
  if (isClickable(target)) {
7558
5972
  let label = '';
7559
5973
  if (target instanceof HTMLElement) {
7560
5974
  label = app.sanitizer.getInnerTextSecure(target);
7561
5975
  }
7562
- label = label || target.id || target.className;
7563
5976
  return normSpaces(label).slice(0, 100);
7564
5977
  }
7565
5978
  return '';
@@ -7703,6 +6116,11 @@ function getCSSPath(el) {
7703
6116
  return false;
7704
6117
  if (el.id)
7705
6118
  return `#${cssEscape(el.id)}`;
6119
+ // if has data attributes - use them as they are more likely to be stable and unique
6120
+ const dataAttr = Array.from(el.attributes).find(attr => attr.name.startsWith('data-'));
6121
+ if (dataAttr) {
6122
+ return `[${dataAttr.name}="${cssEscape(dataAttr.value)}"]`;
6123
+ }
7706
6124
  const parts = [];
7707
6125
  while (el && el.nodeType === 1 && el !== el.ownerDocument) {
7708
6126
  if (el.id) {
@@ -8050,11 +6468,21 @@ function Viewport (app, options) {
8050
6468
  const urlSanitizer = options?.urlSanitizer || ((u) => u);
8051
6469
  const titleSanitizer = options?.titleSanitizer || ((t) => t);
8052
6470
  const sendSetPageLocation = app.safe(() => {
8053
- const { URL } = document;
8054
- if (URL !== url) {
8055
- url = URL;
6471
+ const currURL = document.URL;
6472
+ if (currURL !== url) {
6473
+ url = currURL;
6474
+ if (options?.replaceHashSymbol) {
6475
+ // replace hash router symbol if needed without affecting pathname of the url
6476
+ const u = new URL(currURL);
6477
+ const hashRoute = u.hash.startsWith('#/') ? u.hash.slice(2) : "";
6478
+ const routePath = hashRoute ? "/" + hashRoute.replace(/^\/+/, "") : "";
6479
+ const cleaned = u.origin + u.pathname.replace(/\/$/, "") + routePath + u.search;
6480
+ url = cleaned;
6481
+ }
8056
6482
  const sanitized = urlSanitizer(url);
8057
- const safeTitle = app.sanitizer.privateMode ? stringWiper(document.title) : titleSanitizer(document.title);
6483
+ const safeTitle = app.sanitizer.privateMode
6484
+ ? stringWiper(document.title)
6485
+ : titleSanitizer(document.title);
8058
6486
  const safeUrl = app.sanitizer.privateMode ? stringWiper(sanitized) : sanitized;
8059
6487
  const safeReferrer = app.sanitizer.privateMode ? stringWiper(referrer) : referrer;
8060
6488
  app.send(SetPageLocation(safeUrl, safeReferrer, navigationStart, safeTitle));
@@ -8479,9 +6907,9 @@ function axiosSpy (app, instance, opts, sanitize, stringify) {
8479
6907
  });
8480
6908
  }
8481
6909
  function isAxiosError(payload) {
8482
- return isObject(payload) && payload.isAxiosError === true;
6910
+ return isObject$1(payload) && payload.isAxiosError === true;
8483
6911
  }
8484
- function isObject(thing) {
6912
+ function isObject$1(thing) {
8485
6913
  return thing !== null && typeof thing === 'object';
8486
6914
  }
8487
6915
 
@@ -9814,6 +8242,11 @@ function Tabs (app) {
9814
8242
  app.send(TabChange(app.session.getTabId()));
9815
8243
  }
9816
8244
  }
8245
+ app.attachStartCallback(() => {
8246
+ // just for the good measure in case of restarts caused by assist plugin,
8247
+ // to keep latest active tab in state for live player
8248
+ changeTab();
8249
+ });
9817
8250
  app.attachEventListener(window, 'focus', changeTab, false, false);
9818
8251
  }
9819
8252
 
@@ -9855,6 +8288,8 @@ function webAnimations(app, options = {}) {
9855
8288
  handled.add(anim);
9856
8289
  anim.addEventListener('finish', () => {
9857
8290
  const lastKF = anim.effect.getKeyframes().at(-1);
8291
+ if (!lastKF)
8292
+ return;
9858
8293
  const computedStyle = getComputedStyle(el);
9859
8294
  const keys = Object.keys(lastKF).filter((p) => !toIgnore.includes(p));
9860
8295
  // @ts-ignore
@@ -9895,6 +8330,977 @@ function webAnimations(app, options = {}) {
9895
8330
  });
9896
8331
  }
9897
8332
 
8333
+ /**
8334
+ * Detects client browser, OS, and device information
8335
+ */
8336
+ function uaParse(sWindow) {
8337
+ const unknown = '-';
8338
+ // Screen detection
8339
+ let width = 0;
8340
+ let height = 0;
8341
+ let screenSize = '';
8342
+ if (sWindow.screen.width) {
8343
+ width = sWindow.screen.width;
8344
+ height = sWindow.screen.height;
8345
+ screenSize = `${width} x ${height}`;
8346
+ }
8347
+ // Browser detection
8348
+ const nVer = sWindow.navigator.appVersion ?? '0';
8349
+ const nAgt = sWindow.navigator.userAgent ?? 'unknown';
8350
+ let browser = sWindow.navigator.appName ?? "unknown";
8351
+ let version = String(parseFloat(nVer));
8352
+ let nameOffset;
8353
+ let verOffset;
8354
+ let ix;
8355
+ // Browser detection logic
8356
+ if ((verOffset = nAgt.indexOf('YaBrowser')) !== -1) {
8357
+ browser = 'Yandex';
8358
+ version = nAgt.substring(verOffset + 10);
8359
+ }
8360
+ else if ((verOffset = nAgt.indexOf('SamsungBrowser')) !== -1) {
8361
+ browser = 'Samsung';
8362
+ version = nAgt.substring(verOffset + 15);
8363
+ }
8364
+ else if ((verOffset = nAgt.indexOf('UCBrowser')) !== -1) {
8365
+ browser = 'UC Browser';
8366
+ version = nAgt.substring(verOffset + 10);
8367
+ }
8368
+ else if ((verOffset = nAgt.indexOf('OPR')) !== -1) {
8369
+ browser = 'Opera';
8370
+ version = nAgt.substring(verOffset + 4);
8371
+ }
8372
+ else if ((verOffset = nAgt.indexOf('Opera')) !== -1) {
8373
+ browser = 'Opera';
8374
+ version = nAgt.substring(verOffset + 6);
8375
+ if ((verOffset = nAgt.indexOf('Version')) !== -1) {
8376
+ version = nAgt.substring(verOffset + 8);
8377
+ }
8378
+ }
8379
+ else if ((verOffset = nAgt.indexOf('Edge')) !== -1) {
8380
+ browser = 'Microsoft Legacy Edge';
8381
+ version = nAgt.substring(verOffset + 5);
8382
+ }
8383
+ else if ((verOffset = nAgt.indexOf('Edg')) !== -1) {
8384
+ browser = 'Microsoft Edge';
8385
+ version = nAgt.substring(verOffset + 4);
8386
+ }
8387
+ else if ((verOffset = nAgt.indexOf('MSIE')) !== -1) {
8388
+ browser = 'Microsoft Internet Explorer';
8389
+ version = nAgt.substring(verOffset + 5);
8390
+ }
8391
+ else if ((verOffset = nAgt.indexOf('Chrome')) !== -1) {
8392
+ browser = 'Chrome';
8393
+ version = nAgt.substring(verOffset + 7);
8394
+ }
8395
+ else if ((verOffset = nAgt.indexOf('Safari')) !== -1) {
8396
+ browser = 'Safari';
8397
+ version = nAgt.substring(verOffset + 7);
8398
+ if ((verOffset = nAgt.indexOf('Version')) !== -1) {
8399
+ version = nAgt.substring(verOffset + 8);
8400
+ }
8401
+ }
8402
+ else if ((verOffset = nAgt.indexOf('Firefox')) !== -1) {
8403
+ browser = 'Firefox';
8404
+ version = nAgt.substring(verOffset + 8);
8405
+ }
8406
+ else if (nAgt.indexOf('Trident/') !== -1) {
8407
+ browser = 'Microsoft Internet Explorer';
8408
+ version = nAgt.substring(nAgt.indexOf('rv:') + 3);
8409
+ }
8410
+ else if ((nameOffset = nAgt.lastIndexOf(' ') + 1) < (verOffset = nAgt.lastIndexOf('/'))) {
8411
+ browser = nAgt.substring(nameOffset, verOffset);
8412
+ version = nAgt.substring(verOffset + 1);
8413
+ if (browser.toLowerCase() === browser.toUpperCase()) {
8414
+ browser = sWindow.navigator.appName;
8415
+ }
8416
+ }
8417
+ // Trim the version string
8418
+ if ((ix = version.indexOf(';')) !== -1) {
8419
+ version = version.substring(0, ix);
8420
+ }
8421
+ if ((ix = version.indexOf(' ')) !== -1) {
8422
+ version = version.substring(0, ix);
8423
+ }
8424
+ if ((ix = version.indexOf(')')) !== -1) {
8425
+ version = version.substring(0, ix);
8426
+ }
8427
+ let majorVersion = parseInt(version, 10);
8428
+ if (isNaN(majorVersion)) {
8429
+ version = String(parseFloat(nVer));
8430
+ majorVersion = parseInt(nVer, 10);
8431
+ }
8432
+ // Mobile detection
8433
+ const mobile = /Mobile|mini|Fennec|Android|iP(ad|od|hone)/.test(nVer);
8434
+ // Cookie detection
8435
+ let cookieEnabled = sWindow.navigator.cookieEnabled || false;
8436
+ if (typeof navigator.cookieEnabled === 'undefined' && !cookieEnabled) {
8437
+ sWindow.document.cookie = 'testcookie';
8438
+ cookieEnabled = sWindow.document.cookie.indexOf('testcookie') !== -1;
8439
+ }
8440
+ // OS detection
8441
+ let os = unknown;
8442
+ const clientStrings = [
8443
+ { s: 'Windows 10', r: /(Windows 10.0|Windows NT 10.0)/ },
8444
+ { s: 'Windows 8.1', r: /(Windows 8.1|Windows NT 6.3)/ },
8445
+ { s: 'Windows 8', r: /(Windows 8|Windows NT 6.2)/ },
8446
+ { s: 'Windows 7', r: /(Windows 7|Windows NT 6.1)/ },
8447
+ { s: 'Windows Vista', r: /Windows NT 6.0/ },
8448
+ { s: 'Windows Server 2003', r: /Windows NT 5.2/ },
8449
+ { s: 'Windows XP', r: /(Windows NT 5.1|Windows XP)/ },
8450
+ { s: 'Windows 2000', r: /(Windows NT 5.0|Windows 2000)/ },
8451
+ { s: 'Windows ME', r: /(Win 9x 4.90|Windows ME)/ },
8452
+ { s: 'Windows 98', r: /(Windows 98|Win98)/ },
8453
+ { s: 'Windows 95', r: /(Windows 95|Win95|Windows_95)/ },
8454
+ { s: 'Windows NT 4.0', r: /(Windows NT 4.0|WinNT4.0|WinNT|Windows NT)/ },
8455
+ { s: 'Windows CE', r: /Windows CE/ },
8456
+ { s: 'Windows 3.11', r: /Win16/ },
8457
+ { s: 'Android', r: /Android/ },
8458
+ { s: 'Open BSD', r: /OpenBSD/ },
8459
+ { s: 'Sun OS', r: /SunOS/ },
8460
+ { s: 'Chrome OS', r: /CrOS/ },
8461
+ { s: 'Linux', r: /(Linux|X11(?!.*CrOS))/ },
8462
+ { s: 'iOS', r: /(iPhone|iPad|iPod)/ },
8463
+ { s: 'Mac OS X', r: /Mac OS X/ },
8464
+ { s: 'Mac OS', r: /(Mac OS|MacPPC|MacIntel|Mac_PowerPC|Macintosh)/ },
8465
+ { s: 'QNX', r: /QNX/ },
8466
+ { s: 'UNIX', r: /UNIX/ },
8467
+ { s: 'BeOS', r: /BeOS/ },
8468
+ { s: 'OS/2', r: /OS\/2/ },
8469
+ {
8470
+ s: 'Search Bot',
8471
+ r: /(nuhk|Googlebot|Yammybot|Openbot|Slurp|MSNBot|Ask Jeeves\/Teoma|ia_archiver)/,
8472
+ },
8473
+ ];
8474
+ // Find matching OS
8475
+ for (const client of clientStrings) {
8476
+ if (client.r.test(nAgt)) {
8477
+ os = client.s;
8478
+ break;
8479
+ }
8480
+ }
8481
+ // OS Version detection
8482
+ let osVersion = unknown;
8483
+ if (/Windows/.test(os)) {
8484
+ const matches = /Windows (.*)/.exec(os);
8485
+ if (matches && matches[1]) {
8486
+ osVersion = matches[1];
8487
+ // Handle Windows 10/11 detection with newer API if available
8488
+ if (osVersion === '10' && 'userAgentData' in sWindow.navigator) {
8489
+ const nav = navigator;
8490
+ if (nav.userAgentData) {
8491
+ nav.userAgentData
8492
+ .getHighEntropyValues(['platformVersion'])
8493
+ .then((ua) => {
8494
+ const version = parseInt(ua.platformVersion.split('.')[0], 10);
8495
+ osVersion = version < 13 ? '10' : '11';
8496
+ })
8497
+ .catch(() => {
8498
+ // ignore errors and keep osVersion as is
8499
+ });
8500
+ }
8501
+ }
8502
+ }
8503
+ os = 'Windows';
8504
+ }
8505
+ // OS version detection for Mac/Android/iOS
8506
+ switch (os) {
8507
+ case 'Mac OS':
8508
+ case 'Mac OS X':
8509
+ case 'Android': {
8510
+ const matches = /(?:Android|Mac OS|Mac OS X|MacPPC|MacIntel|Mac_PowerPC|Macintosh) ([\.\_\d]+)/.exec(nAgt);
8511
+ osVersion = matches && matches[1] ? matches[1] : unknown;
8512
+ break;
8513
+ }
8514
+ case 'iOS': {
8515
+ const matches = /OS (\d+)_(\d+)_?(\d+)?/.exec(nVer);
8516
+ if (matches && matches[1]) {
8517
+ osVersion = `${matches[1]}.${matches[2]}.${parseInt(matches[3] || '0', 10)}`;
8518
+ }
8519
+ break;
8520
+ }
8521
+ }
8522
+ // Return client data
8523
+ return {
8524
+ screen: screenSize,
8525
+ width,
8526
+ height,
8527
+ browser,
8528
+ browserVersion: version,
8529
+ browserMajorVersion: majorVersion,
8530
+ mobile,
8531
+ os,
8532
+ osVersion,
8533
+ cookies: cookieEnabled,
8534
+ };
8535
+ }
8536
+ function isObject(item) {
8537
+ const isNull = item === null;
8538
+ return Boolean(item && typeof item === 'object' && !Array.isArray(item) && !isNull);
8539
+ }
8540
+ function getUTCOffsetString() {
8541
+ const date = new Date();
8542
+ const offsetMinutes = date.getTimezoneOffset();
8543
+ const hours = Math.abs(Math.floor(offsetMinutes / 60));
8544
+ const minutes = Math.abs(offsetMinutes % 60);
8545
+ const sign = offsetMinutes <= 0 ? '+' : '-';
8546
+ const hoursStr = hours.toString().padStart(2, '0');
8547
+ const minutesStr = minutes.toString().padStart(2, '0');
8548
+ return `UTC${sign}${hoursStr}:${minutesStr}`;
8549
+ }
8550
+
8551
+ const refKey = '$__or__initial_ref__$';
8552
+ const distinctIdKey = '$__or__distinct_device_id__$';
8553
+ const utmParamsKey = '$__or__utm_params__$';
8554
+ const superPropKey = '$__or__super_properties__$';
8555
+ const userIdKey = '$__or__user_id__$';
8556
+ const win = 'window' in globalThis
8557
+ ? window
8558
+ : {
8559
+ navigator: { userAgent: '' },
8560
+ screen: {},
8561
+ document: {
8562
+ cookie: '',
8563
+ },
8564
+ location: { search: '' },
8565
+ };
8566
+ const doc = 'document' in globalThis ? document : { referrer: '' };
8567
+ const searchEngineList = [
8568
+ 'google',
8569
+ 'bing',
8570
+ 'yahoo',
8571
+ 'baidu',
8572
+ 'yandex',
8573
+ 'duckduckgo',
8574
+ 'ecosia',
8575
+ 'ask',
8576
+ 'aol',
8577
+ 'wolframalpha',
8578
+ 'startpage',
8579
+ 'swisscows',
8580
+ 'qwant',
8581
+ 'lycos',
8582
+ 'dogpile',
8583
+ 'info',
8584
+ 'teoma',
8585
+ 'webcrawler',
8586
+ 'naver',
8587
+ 'seznam',
8588
+ 'perplexity',
8589
+ ];
8590
+ class ConstantProperties {
8591
+ constructor(localStorage, sessionStorage) {
8592
+ this.localStorage = localStorage;
8593
+ this.sessionStorage = sessionStorage;
8594
+ this.user_id = null;
8595
+ this.setUserId = (user_id) => {
8596
+ this.user_id = user_id;
8597
+ this.sessionStorage.setItem(userIdKey, user_id ?? '');
8598
+ };
8599
+ this.resetUserId = (hard) => {
8600
+ this.user_id = null;
8601
+ if (hard) {
8602
+ this.deviceId = this.getDistinctDeviceId(true);
8603
+ }
8604
+ };
8605
+ this.getDistinctDeviceId = (force) => {
8606
+ const potentialStored = this.localStorage.getItem(distinctIdKey);
8607
+ if (potentialStored && !force) {
8608
+ return potentialStored;
8609
+ }
8610
+ else {
8611
+ const distinctId = `${Math.random().toString(36).slice(2)}-${Math.random().toString(36).slice(2)}-${Math.random().toString(36).slice(2)}`;
8612
+ this.localStorage.setItem(distinctIdKey, distinctId);
8613
+ return distinctId;
8614
+ }
8615
+ };
8616
+ this.getReferrer = () => {
8617
+ const potentialStored = this.sessionStorage.getItem(refKey);
8618
+ if (potentialStored) {
8619
+ return potentialStored;
8620
+ }
8621
+ else {
8622
+ const ref = doc.referrer;
8623
+ this.sessionStorage.setItem(refKey, ref);
8624
+ return ref;
8625
+ }
8626
+ };
8627
+ this.parseUTM = () => {
8628
+ const potentialStored = this.sessionStorage.getItem(utmParamsKey);
8629
+ if (potentialStored) {
8630
+ const obj = JSON.parse(potentialStored);
8631
+ this.utmSource = obj.utm_source;
8632
+ this.utmMedium = obj.utm_medium;
8633
+ this.utmCampaign = obj.utm_campaign;
8634
+ }
8635
+ else {
8636
+ const searchParams = new URLSearchParams(win.location.search);
8637
+ this.utmSource = searchParams.get('utm_source') || null;
8638
+ this.utmMedium = searchParams.get('utm_medium') || null;
8639
+ this.utmCampaign = searchParams.get('utm_campaign') || null;
8640
+ const obj = {
8641
+ utm_source: this.utmSource,
8642
+ utm_medium: this.utmMedium,
8643
+ utm_campaign: this.utmCampaign,
8644
+ };
8645
+ this.sessionStorage.setItem(utmParamsKey, JSON.stringify(obj));
8646
+ }
8647
+ };
8648
+ this.getSearchEngine = (ref) => {
8649
+ for (const searchEngine of searchEngineList) {
8650
+ if (ref.includes(searchEngine)) {
8651
+ return searchEngine;
8652
+ }
8653
+ }
8654
+ return null;
8655
+ };
8656
+ this.getSuperProperties = () => {
8657
+ const potentialStored = this.localStorage.getItem(superPropKey);
8658
+ if (potentialStored) {
8659
+ return JSON.parse(potentialStored);
8660
+ }
8661
+ else {
8662
+ return {};
8663
+ }
8664
+ };
8665
+ this.saveSuperProperties = (props) => {
8666
+ this.localStorage.setItem(superPropKey, JSON.stringify(props));
8667
+ };
8668
+ this.clearSuperProperties = () => {
8669
+ this.localStorage.setItem(superPropKey, JSON.stringify({}));
8670
+ };
8671
+ const { width, height, browser, browserVersion, browserMajorVersion, os, osVersion, mobile } = uaParse(win);
8672
+ const storedUserId = this.sessionStorage.getItem(userIdKey);
8673
+ if (storedUserId) {
8674
+ this.user_id = storedUserId;
8675
+ }
8676
+ this.os = os;
8677
+ this.osVersion = osVersion;
8678
+ this.browser = `${browser}`;
8679
+ this.browserVersion = `${browserVersion} (${browserMajorVersion})`;
8680
+ this.platform = mobile ? 'mobile' : 'desktop';
8681
+ this.screenHeight = height;
8682
+ this.screenWidth = width;
8683
+ this.initialReferrer = this.getReferrer();
8684
+ this.deviceId = this.getDistinctDeviceId();
8685
+ this.searchEngine = this.getSearchEngine(this.initialReferrer);
8686
+ this.parseUTM();
8687
+ }
8688
+ get all() {
8689
+ return {
8690
+ os: this.os,
8691
+ os_version: this.osVersion,
8692
+ browser: this.browser,
8693
+ browser_version: this.browserVersion,
8694
+ platform: this.platform,
8695
+ screen_height: this.screenHeight,
8696
+ screen_width: this.screenWidth,
8697
+ initial_referrer: this.initialReferrer,
8698
+ utm_source: this.utmSource,
8699
+ utm_medium: this.utmMedium,
8700
+ utm_campaign: this.utmCampaign,
8701
+ user_id: this.user_id,
8702
+ distinct_id: this.deviceId,
8703
+ sdk_edition: 'web',
8704
+ sdk_version: '17.2.3',
8705
+ timezone: getUTCOffsetString(),
8706
+ search_engine: this.searchEngine,
8707
+ };
8708
+ }
8709
+ get defaultPropertyKeys() {
8710
+ return Object.keys(this.all);
8711
+ }
8712
+ get distinctId() {
8713
+ return this.deviceId;
8714
+ }
8715
+ }
8716
+
8717
+ const mutationTypes = {
8718
+ identity: 'identity',
8719
+ deleteUser: 'delete_user',
8720
+ setProperty: 'set_property',
8721
+ setPropertyOnce: 'set_property_once',
8722
+ appendProperty: 'append_property',
8723
+ appendUniqueProperty: 'append_unique_property',
8724
+ incrementProperty: 'increment_property',
8725
+ };
8726
+ const categories = {
8727
+ people: 'user_actions',
8728
+ events: 'events',
8729
+ };
8730
+ const createEvent = (category, type, timestamp, payload) => {
8731
+ if (category === categories.people) {
8732
+ return {
8733
+ category,
8734
+ data: {
8735
+ type,
8736
+ user_id: payload.user_id,
8737
+ payload: payload.properties,
8738
+ timestamp,
8739
+ },
8740
+ };
8741
+ }
8742
+ else {
8743
+ if (!payload) {
8744
+ throw new Error('Payload is required for event creation');
8745
+ }
8746
+ return {
8747
+ category,
8748
+ data: {
8749
+ name: payload.name,
8750
+ payload: payload.properties,
8751
+ timestamp,
8752
+ },
8753
+ };
8754
+ }
8755
+ };
8756
+
8757
+ const reservedProps = ['properties', 'token', 'timestamp'];
8758
+ class Events {
8759
+ constructor(constantProperties, getTimestamp, batcher) {
8760
+ this.constantProperties = constantProperties;
8761
+ this.getTimestamp = getTimestamp;
8762
+ this.batcher = batcher;
8763
+ this.ownProperties = {};
8764
+ /**
8765
+ * Add event to batch with option to send it immediately,
8766
+ * properties are optional and will not be saved as super prop
8767
+ * */
8768
+ this.sendEvent = (eventName, properties, options) => {
8769
+ // add properties
8770
+ const eventProps = {};
8771
+ if (properties) {
8772
+ if (!isObject(properties)) {
8773
+ throw new Error('Properties must be an object');
8774
+ }
8775
+ Object.entries(properties).forEach(([key, value]) => {
8776
+ if (!this.constantProperties.defaultPropertyKeys.includes(key)) {
8777
+ eventProps[key] = value;
8778
+ }
8779
+ });
8780
+ }
8781
+ const eventPayload = {
8782
+ name: eventName,
8783
+ properties: { ...this.ownProperties, ...eventProps },
8784
+ };
8785
+ const event = createEvent(categories.events, undefined, this.getTimestamp(), eventPayload);
8786
+ if (options?.send_immediately) {
8787
+ void this.batcher.sendImmediately(event);
8788
+ }
8789
+ else {
8790
+ this.batcher.addEvent(event);
8791
+ }
8792
+ };
8793
+ /**
8794
+ * creates super property for all events
8795
+ * */
8796
+ this.setProperty = (nameOrProperties, value) => {
8797
+ let changed = false;
8798
+ if (isObject(nameOrProperties)) {
8799
+ Object.entries(nameOrProperties).forEach(([key, val]) => {
8800
+ if (!this.constantProperties.defaultPropertyKeys.includes(key)) {
8801
+ this.ownProperties[key] = val;
8802
+ changed = true;
8803
+ }
8804
+ });
8805
+ }
8806
+ if (typeof nameOrProperties === 'string' && value !== undefined) {
8807
+ if (!this.constantProperties.defaultPropertyKeys.includes(nameOrProperties)) {
8808
+ this.ownProperties[nameOrProperties] = value;
8809
+ changed = true;
8810
+ }
8811
+ }
8812
+ if (changed) {
8813
+ this.constantProperties.saveSuperProperties(this.ownProperties);
8814
+ }
8815
+ };
8816
+ /**
8817
+ * set super property only if it doesn't exist yet
8818
+ * */
8819
+ this.setPropertiesOnce = (nameOrProperties, value) => {
8820
+ let changed = false;
8821
+ if (isObject(nameOrProperties)) {
8822
+ Object.entries(nameOrProperties).forEach(([key, val]) => {
8823
+ if (!this.ownProperties[key] && !reservedProps.includes(key)) {
8824
+ this.ownProperties[key] = val;
8825
+ changed = true;
8826
+ }
8827
+ });
8828
+ }
8829
+ if (typeof nameOrProperties === 'string' && value !== undefined) {
8830
+ if (!this.ownProperties[nameOrProperties] && !reservedProps.includes(nameOrProperties)) {
8831
+ this.ownProperties[nameOrProperties] = value;
8832
+ changed = true;
8833
+ }
8834
+ }
8835
+ if (changed) {
8836
+ this.constantProperties.saveSuperProperties(this.ownProperties);
8837
+ }
8838
+ };
8839
+ /**
8840
+ * removes properties from list of super properties
8841
+ * */
8842
+ this.unsetProperties = (properties) => {
8843
+ let changed = false;
8844
+ if (Array.isArray(properties)) {
8845
+ properties.forEach((key) => {
8846
+ if (this.ownProperties[key] && !reservedProps.includes(key)) {
8847
+ delete this.ownProperties[key];
8848
+ changed = true;
8849
+ }
8850
+ });
8851
+ }
8852
+ else if (this.ownProperties[properties] && !reservedProps.includes(properties)) {
8853
+ delete this.ownProperties[properties];
8854
+ changed = true;
8855
+ }
8856
+ if (changed) {
8857
+ this.constantProperties.saveSuperProperties(this.ownProperties);
8858
+ }
8859
+ };
8860
+ /** clears all super properties */
8861
+ this.reset = () => {
8862
+ this.ownProperties = {};
8863
+ this.constantProperties.clearSuperProperties();
8864
+ };
8865
+ /** mixpanel compatibility */
8866
+ this.register = this.setProperty;
8867
+ this.register_once = this.setPropertiesOnce;
8868
+ this.unregister = this.unsetProperties;
8869
+ this.track = this.sendEvent;
8870
+ this.ownProperties = this.constantProperties.getSuperProperties();
8871
+ }
8872
+ }
8873
+
8874
+ class People {
8875
+ constructor(constantProperties, getTimestamp, onId, batcher) {
8876
+ this.constantProperties = constantProperties;
8877
+ this.getTimestamp = getTimestamp;
8878
+ this.onId = onId;
8879
+ this.batcher = batcher;
8880
+ this.ownProperties = {};
8881
+ this.identify = (user_id, options) => {
8882
+ if (!user_id || typeof user_id !== 'string') {
8883
+ throw new Error('OR SDK: user_id (string) is required for .identify()');
8884
+ }
8885
+ // if user exists already, reset properties
8886
+ if (this.constantProperties.user_id && this.constantProperties.user_id !== user_id) {
8887
+ this.reset();
8888
+ }
8889
+ this.constantProperties.setUserId(user_id);
8890
+ if (!options?.fromTracker) {
8891
+ this.onId(user_id);
8892
+ }
8893
+ const identityEvent = createEvent(categories.people, mutationTypes.identity, this.getTimestamp(), { user_id });
8894
+ this.batcher.addEvent(identityEvent);
8895
+ };
8896
+ /** Resets user id and own properties
8897
+ *
8898
+ * !hard reset will destroy persistent device id!
8899
+ * */
8900
+ this.reset = (hard) => {
8901
+ this.constantProperties.resetUserId(hard);
8902
+ this.ownProperties = {};
8903
+ };
8904
+ /**
8905
+ * Will delete user and its data from backend, then reset all local properties
8906
+ */
8907
+ this.deleteUser = () => {
8908
+ const removedUser = this.constantProperties.user_id;
8909
+ if (!removedUser)
8910
+ return;
8911
+ this.constantProperties.setUserId(null);
8912
+ this.ownProperties = {};
8913
+ const deleteEvent = createEvent(categories.people, mutationTypes.deleteUser, undefined, {
8914
+ user_id: removedUser,
8915
+ });
8916
+ this.batcher.addEvent(deleteEvent);
8917
+ this.reset();
8918
+ };
8919
+ /**
8920
+ * set user properties, overwriting existing ones
8921
+ * */
8922
+ this.setProperties = (propertyOrObj, value) => {
8923
+ if (!propertyOrObj) {
8924
+ throw new Error('OR SDK: no user properties provided to set');
8925
+ }
8926
+ const properties = {};
8927
+ if (typeof propertyOrObj === 'string' && propertyOrObj && value) {
8928
+ properties[propertyOrObj] = value;
8929
+ }
8930
+ else if (isObject(propertyOrObj)) {
8931
+ Object.assign(properties, propertyOrObj);
8932
+ }
8933
+ else {
8934
+ throw new Error('OR SDK: invalid user properties provided to set');
8935
+ }
8936
+ Object.entries(properties).forEach(([key, value]) => {
8937
+ if (!this.constantProperties.defaultPropertyKeys.includes(key)) {
8938
+ this.ownProperties[key] = value;
8939
+ }
8940
+ });
8941
+ const setEvent = createEvent(categories.people, mutationTypes.setProperty, undefined, {
8942
+ user_id: this.user_id,
8943
+ properties,
8944
+ });
8945
+ this.batcher.addEvent(setEvent);
8946
+ };
8947
+ /**
8948
+ * Set property if it doesn't exist yet
8949
+ * */
8950
+ this.setPropertiesOnce = (properties) => {
8951
+ if (!isObject(properties)) {
8952
+ throw new Error('Properties must be an object');
8953
+ }
8954
+ Object.entries(properties).forEach(([key, value]) => {
8955
+ if (!this.constantProperties.defaultPropertyKeys.includes(key) && !this.ownProperties[key]) {
8956
+ this.ownProperties[key] = value;
8957
+ }
8958
+ });
8959
+ const setEvent = createEvent(categories.people, mutationTypes.setPropertyOnce, undefined, {
8960
+ user_id: this.user_id,
8961
+ properties,
8962
+ });
8963
+ this.batcher.addEvent(setEvent);
8964
+ };
8965
+ /**
8966
+ * Add value to property (will turn string prop into array)
8967
+ * */
8968
+ this.appendValues = (key, value) => {
8969
+ if (!this.constantProperties.defaultPropertyKeys.includes(key) && this.ownProperties[key]) {
8970
+ if (Array.isArray(this.ownProperties[key])) {
8971
+ this.ownProperties[key].push(value);
8972
+ }
8973
+ else {
8974
+ this.ownProperties[key] = [this.ownProperties[key], value];
8975
+ }
8976
+ }
8977
+ const appendEvent = createEvent(categories.people, mutationTypes.appendProperty, undefined, {
8978
+ properties: { [key]: value },
8979
+ user_id: this.user_id,
8980
+ });
8981
+ this.batcher.addEvent(appendEvent);
8982
+ };
8983
+ /**
8984
+ * Add unique values to property (will turn string prop into array)
8985
+ * */
8986
+ this.appendUniqueValues = (key, value) => {
8987
+ if (!this.ownProperties[key])
8988
+ return;
8989
+ if (Array.isArray(this.ownProperties[key])) {
8990
+ if (!this.ownProperties[key].includes(value)) {
8991
+ this.appendValues(key, value);
8992
+ }
8993
+ }
8994
+ else if (this.ownProperties[key] !== value) {
8995
+ this.appendValues(key, value);
8996
+ }
8997
+ const unionEvent = createEvent(categories.people, mutationTypes.appendUniqueProperty, undefined, {
8998
+ properties: { [key]: value },
8999
+ user_id: this.user_id,
9000
+ });
9001
+ this.batcher.addEvent(unionEvent);
9002
+ };
9003
+ /**
9004
+ * Adds value (incl. negative) to existing numerical property
9005
+ * */
9006
+ this.increment = (key, value) => {
9007
+ if (!this.ownProperties[key]) {
9008
+ this.ownProperties[key] = 0;
9009
+ }
9010
+ if (this.ownProperties[key] && typeof this.ownProperties[key] !== 'number') {
9011
+ throw new Error('OR SDK: Property must be a number to increment');
9012
+ }
9013
+ // @ts-ignore
9014
+ this.ownProperties[key] += value;
9015
+ const incrementEvent = createEvent(categories.people, mutationTypes.incrementProperty, undefined, {
9016
+ user_id: this.user_id,
9017
+ properties: { [key]: value },
9018
+ });
9019
+ this.batcher.addEvent(incrementEvent);
9020
+ };
9021
+ /** mixpanel compatibility */
9022
+ this.union = this.appendUniqueValues;
9023
+ this.set = this.setProperties;
9024
+ this.set_once = this.setPropertiesOnce;
9025
+ this.append = this.appendValues;
9026
+ this.incrementBy = this.increment;
9027
+ }
9028
+ get user_id() {
9029
+ return this.constantProperties.user_id;
9030
+ }
9031
+ }
9032
+
9033
+ /**
9034
+ * Creates batches of events, then sends them at intervals.
9035
+ */
9036
+ class Batcher {
9037
+ constructor(backendUrl, getToken, init) {
9038
+ this.backendUrl = backendUrl;
9039
+ this.getToken = getToken;
9040
+ this.init = init;
9041
+ this.autosendInterval = 5 * 1000;
9042
+ this.retryTimeout = 3 * 1000;
9043
+ this.retryLimit = 3;
9044
+ this.apiEdp = '/v1/sdk/i';
9045
+ this.batch = {
9046
+ [categories.people]: [],
9047
+ [categories.events]: [],
9048
+ };
9049
+ this.intervalId = null;
9050
+ }
9051
+ getBatches() {
9052
+ this.batch[categories.people] = this.dedupePeopleEvents();
9053
+ const finalData = { data: this.batch };
9054
+ return finalData;
9055
+ }
9056
+ addEvent(event) {
9057
+ this.batch[event.category].push(event.data);
9058
+ }
9059
+ sendImmediately(event) {
9060
+ this.sendBatch({ [event.category]: [event.data] });
9061
+ }
9062
+ /**
9063
+ *
9064
+ * Essentially we're dividing the batch by identify events and squash all same category events into one in each part,
9065
+ * taking priority to the last one
9066
+ */
9067
+ dedupePeopleEvents() {
9068
+ const peopleEvents = this.batch[categories.people];
9069
+ const finalEvents = [];
9070
+ const currentPart = [];
9071
+ for (let event of peopleEvents) {
9072
+ if (event.type === 'identity') {
9073
+ if (currentPart.length > 0) {
9074
+ finalEvents.push(...this.squashPeopleEvents(currentPart), event);
9075
+ currentPart.length = 0;
9076
+ }
9077
+ else {
9078
+ finalEvents.push(event);
9079
+ }
9080
+ }
9081
+ else {
9082
+ currentPart.push(event);
9083
+ }
9084
+ }
9085
+ if (currentPart.length > 0) {
9086
+ finalEvents.push(...this.squashPeopleEvents(currentPart));
9087
+ }
9088
+ return finalEvents;
9089
+ }
9090
+ squashPeopleEvents(events) {
9091
+ if (!events || events.length === 0) {
9092
+ return [];
9093
+ }
9094
+ const uniqueEventsByType = new Map();
9095
+ for (let event of events) {
9096
+ const prev = uniqueEventsByType.get(event.type);
9097
+ if (prev) {
9098
+ if (event.type === 'increment_property') {
9099
+ const previousValues = Object.entries(prev.payload);
9100
+ const currentValues = Object.entries(event.payload);
9101
+ const uniqueKeys = new Set([...previousValues.map(([key]) => key), ...currentValues.map(([key]) => key)]);
9102
+ const mergedPayload = {};
9103
+ uniqueKeys.forEach((key) => {
9104
+ const prevValue = typeof prev.payload[key] === 'number' ? prev.payload[key] : 0;
9105
+ const currValue = typeof event.payload[key] === 'number' ? event.payload[key] : 0;
9106
+ mergedPayload[key] = prevValue + currValue;
9107
+ });
9108
+ uniqueEventsByType.set(event.type, {
9109
+ type: event.type,
9110
+ timestamp: event.timestamp,
9111
+ payload: mergedPayload,
9112
+ });
9113
+ continue;
9114
+ }
9115
+ // merge payloads, taking priority to the latest one
9116
+ uniqueEventsByType.set(event.type, {
9117
+ type: event.type,
9118
+ timestamp: event.timestamp,
9119
+ payload: { ...(prev.payload ?? {}), ...(event.payload ?? {}) },
9120
+ });
9121
+ }
9122
+ else {
9123
+ uniqueEventsByType.set(event.type, event);
9124
+ }
9125
+ }
9126
+ return Array.from(uniqueEventsByType.values());
9127
+ }
9128
+ sendBatch(batch) {
9129
+ const sentBatch = batch;
9130
+ let attempts = 0;
9131
+ const send = () => {
9132
+ const token = this.getToken();
9133
+ if (!token) {
9134
+ return;
9135
+ }
9136
+ attempts++;
9137
+ return fetch(`${this.backendUrl}${this.apiEdp}`, {
9138
+ method: 'POST',
9139
+ headers: {
9140
+ 'Content-Type': 'application/json',
9141
+ Authorization: `Bearer ${token}`,
9142
+ },
9143
+ body: JSON.stringify(sentBatch),
9144
+ })
9145
+ .then((response) => {
9146
+ if ([403, 401].includes(response.status)) {
9147
+ if (attempts < this.retryLimit) {
9148
+ return this.init().then(() => {
9149
+ send();
9150
+ });
9151
+ }
9152
+ return;
9153
+ }
9154
+ if (!response.ok) {
9155
+ throw new Error(`HTTP error! status: ${response.status}`);
9156
+ }
9157
+ })
9158
+ .catch(() => {
9159
+ if (attempts < this.retryLimit) {
9160
+ setTimeout(() => void send(), this.retryTimeout);
9161
+ }
9162
+ });
9163
+ };
9164
+ void send();
9165
+ }
9166
+ startAutosend() {
9167
+ if (this.intervalId) {
9168
+ clearInterval(this.intervalId);
9169
+ }
9170
+ this.intervalId = setInterval(() => {
9171
+ this.flush();
9172
+ }, this.autosendInterval);
9173
+ }
9174
+ flush() {
9175
+ const categories = Object.keys(this.batch);
9176
+ const isEmpty = categories.every((category) => this.batch[category].length === 0);
9177
+ if (isEmpty) {
9178
+ return;
9179
+ }
9180
+ this.sendBatch(this.getBatches());
9181
+ categories.forEach((key) => {
9182
+ this.batch[key] = [];
9183
+ });
9184
+ }
9185
+ stop() {
9186
+ this.flush();
9187
+ if (this.intervalId) {
9188
+ clearInterval(this.intervalId);
9189
+ this.intervalId = null;
9190
+ }
9191
+ }
9192
+ }
9193
+
9194
+ const STORAGEKEY = '__or_sdk_analytics_token';
9195
+ class Analytics {
9196
+ /**
9197
+ * @param localStorage Class or Object that implements Storage-like interface that stores
9198
+ * values persistently like window.localStorage or any other file-based storage
9199
+ *
9200
+ * @param sessionStorage Class or Object that implements Storage-like interface that stores values
9201
+ * on per-session basis like window.sessionStorage or any other in-memory storage
9202
+ *
9203
+ * @param getToken Function that returns token to bind events to a session
9204
+ *
9205
+ * @param getTimestamp returns current timestamp
9206
+ *
9207
+ * @param setUserId callback for people.identify
9208
+ *
9209
+ * @param standalone if true, analytics will manage its own token (instead of using with openreplay tracker session)
9210
+ * */
9211
+ constructor(options) {
9212
+ this.token = null;
9213
+ this.standalone = false;
9214
+ this._getToken = () => {
9215
+ if (this.standalone) {
9216
+ return this.token;
9217
+ }
9218
+ else {
9219
+ return this.getToken();
9220
+ }
9221
+ };
9222
+ this._getTimestamp = () => {
9223
+ if (this.standalone) {
9224
+ return Date.now();
9225
+ }
9226
+ return this.getTimestamp();
9227
+ };
9228
+ this.init = async () => {
9229
+ if (!this.standalone) {
9230
+ this.batcher.startAutosend();
9231
+ return;
9232
+ }
9233
+ else {
9234
+ const defaultFields = this.constantProperties.all;
9235
+ const apiEdp = '/v1/sdk/start';
9236
+ const data = {
9237
+ projectKey: this.projectKey,
9238
+ defaultFields,
9239
+ };
9240
+ const resp = await fetch(apiEdp, {
9241
+ method: 'POST',
9242
+ body: JSON.stringify(data),
9243
+ });
9244
+ if (!resp.ok) {
9245
+ throw new Error(`HTTP error! status: ${resp.status}`);
9246
+ }
9247
+ const result = await resp.json();
9248
+ if (result.token) {
9249
+ this.token = result.token;
9250
+ this.sessionStorage.setItem(STORAGEKEY, result.token);
9251
+ }
9252
+ else {
9253
+ throw new Error('No token received from server');
9254
+ }
9255
+ }
9256
+ };
9257
+ this.reset = () => {
9258
+ this.people.reset(true);
9259
+ this.events.reset();
9260
+ this.batcher.stop();
9261
+ if (this.standalone) {
9262
+ this.token = null;
9263
+ this.sessionStorage.setItem(STORAGEKEY, '');
9264
+ }
9265
+ };
9266
+ /**
9267
+ * COMPATIBILITY LAYER
9268
+ * */
9269
+ /**
9270
+ * Identify a user with an id (e.g. email, username, etc.)
9271
+ * will bind all events and properties (including device_id) to this user
9272
+ *
9273
+ * you will need to manually call people.reset() to clear the id on logout event
9274
+ * */
9275
+ this.identify = (user_id) => {
9276
+ return this.people.identify(user_id);
9277
+ };
9278
+ /**
9279
+ * Add event to batch with option to send it immediately,
9280
+ * properties are optional and will not be saved as super prop
9281
+ * */
9282
+ this.track = (eventName, properties, options) => {
9283
+ return this.events.track(eventName, properties, options);
9284
+ };
9285
+ this.sessionStorage = options.sessionStorage || sessionStorage;
9286
+ this.localStorage = options.localStorage || localStorage;
9287
+ this.backendUrl = options.ingestPoint;
9288
+ this.projectKey = options.projectKey;
9289
+ this.getToken = options.getToken || (() => '');
9290
+ this.getTimestamp = options.getTimestamp || (() => Date.now());
9291
+ this.setUserId = options.setUserId || (() => { });
9292
+ this.standalone = !options.notStandalone;
9293
+ this.token = this.sessionStorage.getItem(STORAGEKEY);
9294
+ this.constantProperties = new ConstantProperties(this.localStorage, this.sessionStorage);
9295
+ this.batcher = new Batcher(this.backendUrl, this._getToken, this.init);
9296
+ this.events = new Events(this.constantProperties, this._getTimestamp, this.batcher);
9297
+ this.people = new People(this.constantProperties, this._getTimestamp, this.setUserId, this.batcher);
9298
+ if (options.notStandalone) {
9299
+ this.init();
9300
+ }
9301
+ }
9302
+ }
9303
+
9898
9304
  const Messages = _Messages;
9899
9305
  const DOCS_SETUP = '/en/sdk';
9900
9306
  function processOptions(obj) {
@@ -9936,6 +9342,7 @@ class API {
9936
9342
  constructor(options) {
9937
9343
  this.options = options;
9938
9344
  this.app = null;
9345
+ this.analytics = null;
9939
9346
  this.crossdomainMode = false;
9940
9347
  this.checkDoNotTrack = () => {
9941
9348
  return (this.options.respectDoNotTrack &&
@@ -9946,7 +9353,7 @@ class API {
9946
9353
  this.signalStartIssue = (reason, missingApi) => {
9947
9354
  const doNotTrack = this.checkDoNotTrack();
9948
9355
  console.log("Tracker couldn't start due to:", JSON.stringify({
9949
- trackerVersion: '16.4.11-beta.0',
9356
+ trackerVersion: '17.2.3',
9950
9357
  projectKey: this.options.projectKey,
9951
9358
  doNotTrack,
9952
9359
  reason: missingApi.length ? `missing api: ${missingApi.join(',')}` : reason,
@@ -9958,6 +9365,24 @@ class API {
9958
9365
  }
9959
9366
  this.app.restartCanvasTracking();
9960
9367
  };
9368
+ this.getSessionURL = (options) => {
9369
+ if (this.app === null) {
9370
+ return undefined;
9371
+ }
9372
+ return this.app.getSessionURL(options);
9373
+ };
9374
+ this.setUserID = (id) => {
9375
+ if (typeof id === 'string' && this.app !== null) {
9376
+ this.app.session.setUserID(id);
9377
+ this.analytics?.people.identify(id, { fromTracker: true });
9378
+ }
9379
+ };
9380
+ this.identify = this.setUserID;
9381
+ this.track = this.analytics?.track;
9382
+ this.userID = (id) => {
9383
+ deprecationWarn("'userID' method", "'setUserID' method", '/');
9384
+ this.setUserID(id);
9385
+ };
9961
9386
  this.handleError = (e, metadata = {}) => {
9962
9387
  if (this.app === null) {
9963
9388
  return;
@@ -9980,6 +9405,21 @@ class API {
9980
9405
  }
9981
9406
  this.app.send(Incident(options.label ?? '', options.startTime, options.endTime ?? options.startTime));
9982
9407
  };
9408
+ this.analyticsToken = null;
9409
+ /**
9410
+ * Use custom token for analytics events without session recording
9411
+ * */
9412
+ this.setAnalyticsToken = (token) => {
9413
+ this.analyticsToken = token;
9414
+ };
9415
+ this.getAnalyticsToken = () => {
9416
+ if (this.analyticsToken) {
9417
+ return this.analyticsToken;
9418
+ }
9419
+ else {
9420
+ return this.app?.session.getSessionToken() ?? '';
9421
+ }
9422
+ };
9983
9423
  this.crossdomainMode = Boolean(inIframe() && options.crossdomain?.enabled);
9984
9424
  if (!IN_BROWSER || !processOptions(options)) {
9985
9425
  return;
@@ -10038,6 +9478,34 @@ class API {
10038
9478
  }
10039
9479
  const app = new App(options.projectKey, options.sessionToken, options, this.signalStartIssue, this.crossdomainMode);
10040
9480
  this.app = app;
9481
+ if (options.projectKey && options.analytics?.active) {
9482
+ let isSaas = false;
9483
+ if (!options.ingestPoint) {
9484
+ isSaas = true;
9485
+ }
9486
+ if (options.ingestPoint) {
9487
+ const saasHost = 'api.openreplay.com';
9488
+ const urlObj = new URL(options.ingestPoint);
9489
+ if (urlObj.hostname === saasHost) {
9490
+ isSaas = true;
9491
+ }
9492
+ }
9493
+ const defaultEdp = 'https://api.openreplay.com/ingest';
9494
+ this.analytics = new Analytics({
9495
+ localStorage: options.localStorage ?? localStorage,
9496
+ sessionStorage: options.sessionStorage ?? sessionStorage,
9497
+ getToken: () => this.getAnalyticsToken(),
9498
+ getTimestamp: () => this.app?.timestamp() ?? Date.now(),
9499
+ setUserId: (id) => {
9500
+ this.app?.session.setUserID(id);
9501
+ },
9502
+ notStandalone: true,
9503
+ ingestPoint: isSaas
9504
+ ? defaultEdp
9505
+ : (options.analytics?.ingestPoint ?? options.ingestPoint ?? defaultEdp),
9506
+ projectKey: options.projectKey,
9507
+ });
9508
+ }
10041
9509
  if (!this.crossdomainMode) {
10042
9510
  // no need to send iframe viewport data since its a node for us
10043
9511
  Viewport(app, options.urls);
@@ -10048,7 +9516,7 @@ class API {
10048
9516
  // no tabs in iframes yet
10049
9517
  Tabs(app);
10050
9518
  }
10051
- Mouse(app, options.mouse);
9519
+ Mouse(app, { ...options.mouse, customAttributes: options.customAttributes });
10052
9520
  // inside iframe, we ignore viewport scroll
10053
9521
  Scroll(app, this.crossdomainMode);
10054
9522
  CSSRules(app, options.css);
@@ -10068,9 +9536,6 @@ class API {
10068
9536
  selection(app);
10069
9537
  webAnimations(app, options.webAnimations);
10070
9538
  window.__OPENREPLAY__ = this;
10071
- if (options.flags && options.flags.onFlagsLoad) {
10072
- this.onFlagsLoad(options.flags.onFlagsLoad);
10073
- }
10074
9539
  const wOpen = window.open;
10075
9540
  if (options.autoResetOnWindowOpen || options.resetTabOnWindowOpen) {
10076
9541
  app.attachStartCallback(() => {
@@ -10093,24 +9558,6 @@ class API {
10093
9558
  });
10094
9559
  }
10095
9560
  }
10096
- isFlagEnabled(flagName) {
10097
- return this.featureFlags.isFlagEnabled(flagName);
10098
- }
10099
- onFlagsLoad(callback) {
10100
- this.app?.featureFlags.onFlagsLoad(callback);
10101
- }
10102
- clearPersistFlags() {
10103
- this.app?.featureFlags.clearPersistFlags();
10104
- }
10105
- reloadFlags() {
10106
- return this.app?.featureFlags.reloadFlags();
10107
- }
10108
- getFeatureFlag(flagName) {
10109
- return this.app?.featureFlags.getFeatureFlag(flagName);
10110
- }
10111
- getAllFeatureFlags() {
10112
- return this.app?.featureFlags.flags;
10113
- }
10114
9561
  use(fn) {
10115
9562
  return fn(this.app, this.options);
10116
9563
  }
@@ -10138,6 +9585,10 @@ class API {
10138
9585
  if (this.app === null) {
10139
9586
  return Promise.reject("Browser doesn't support required api, or doNotTrack is active.");
10140
9587
  }
9588
+ if (startOpts?.userID) {
9589
+ this.app.session.setUserID(startOpts.userID);
9590
+ this.analytics?.people.identify(startOpts.userID, { fromTracker: true });
9591
+ }
10141
9592
  return this.app.start(startOpts);
10142
9593
  }
10143
9594
  else {
@@ -10241,31 +9692,10 @@ class API {
10241
9692
  }
10242
9693
  return this.app.getTabId();
10243
9694
  }
10244
- getUxId() {
10245
- if (this.app === null) {
10246
- return null;
10247
- }
10248
- return this.app.getUxtId();
10249
- }
10250
9695
  sessionID() {
10251
9696
  deprecationWarn("'sessionID' method", "'getSessionID' method", '/');
10252
9697
  return this.getSessionID();
10253
9698
  }
10254
- getSessionURL(options) {
10255
- if (this.app === null) {
10256
- return undefined;
10257
- }
10258
- return this.app.getSessionURL(options);
10259
- }
10260
- setUserID(id) {
10261
- if (typeof id === 'string' && this.app !== null) {
10262
- this.app.session.setUserID(id);
10263
- }
10264
- }
10265
- userID(id) {
10266
- deprecationWarn("'userID' method", "'setUserID' method", '/');
10267
- this.setUserID(id);
10268
- }
10269
9699
  setUserAnonymousID(id) {
10270
9700
  if (typeof id === 'string' && this.app !== null) {
10271
9701
  this.app.send(UserAnonymousID(id));
@@ -10290,6 +9720,9 @@ class API {
10290
9720
  return this.issue(key, payload);
10291
9721
  }
10292
9722
  else {
9723
+ if (!payload || typeof payload === 'string') {
9724
+ return this.app.send(CustomEvent(key, payload));
9725
+ }
10293
9726
  try {
10294
9727
  if ('or_timestamp' in payload) {
10295
9728
  const startTs = this.getSessionInfo()?.timestamp ?? 0;
@@ -10300,9 +9733,7 @@ class API {
10300
9733
  }
10301
9734
  payload = JSON.stringify(payload);
10302
9735
  }
10303
- catch (e) {
10304
- return;
10305
- }
9736
+ catch (_) { }
10306
9737
  this.app.send(CustomEvent(key, payload));
10307
9738
  }
10308
9739
  }
@@ -10324,6 +9755,13 @@ class TrackerSingleton {
10324
9755
  constructor() {
10325
9756
  this.instance = null;
10326
9757
  this.isConfigured = false;
9758
+ this.identify = this.setUserID;
9759
+ this.track = (eventName, properties, options) => {
9760
+ if (!IN_BROWSER || !this.ensureConfigured() || !this.instance) {
9761
+ return;
9762
+ }
9763
+ this.instance.track?.(eventName, properties);
9764
+ };
10327
9765
  }
10328
9766
  /**
10329
9767
  * Call this method once to create tracker configuration
@@ -10374,6 +9812,15 @@ class TrackerSingleton {
10374
9812
  }
10375
9813
  this.instance.setUserID(id);
10376
9814
  }
9815
+ get analytics() {
9816
+ if (this.instance?.analytics) {
9817
+ return this.instance.analytics;
9818
+ }
9819
+ else {
9820
+ return null;
9821
+ }
9822
+ }
9823
+ ;
10377
9824
  /**
10378
9825
  * Set metadata for the current session
10379
9826
  *
@@ -10426,42 +9873,6 @@ class TrackerSingleton {
10426
9873
  }
10427
9874
  this.instance.handleError(e, metadata);
10428
9875
  }
10429
- isFlagEnabled(flagName) {
10430
- if (!IN_BROWSER || !this.ensureConfigured() || !this.instance) {
10431
- return false;
10432
- }
10433
- return this.instance.isFlagEnabled(flagName);
10434
- }
10435
- onFlagsLoad(...args) {
10436
- if (!IN_BROWSER || !this.ensureConfigured() || !this.instance) {
10437
- return;
10438
- }
10439
- this.instance.onFlagsLoad(...args);
10440
- }
10441
- clearPersistFlags() {
10442
- if (!IN_BROWSER || !this.ensureConfigured() || !this.instance) {
10443
- return;
10444
- }
10445
- this.instance.clearPersistFlags();
10446
- }
10447
- reloadFlags() {
10448
- if (!IN_BROWSER || !this.ensureConfigured() || !this.instance) {
10449
- return;
10450
- }
10451
- return this.instance.reloadFlags();
10452
- }
10453
- getFeatureFlag(flagName) {
10454
- if (!IN_BROWSER || !this.ensureConfigured() || !this.instance) {
10455
- return;
10456
- }
10457
- return this.instance.getFeatureFlag(flagName);
10458
- }
10459
- getAllFeatureFlags() {
10460
- if (!IN_BROWSER || !this.ensureConfigured() || !this.instance) {
10461
- return;
10462
- }
10463
- return this.instance.getAllFeatureFlags();
10464
- }
10465
9876
  restartCanvasTracking() {
10466
9877
  if (!IN_BROWSER || !this.ensureConfigured() || !this.instance) {
10467
9878
  return;
@@ -10584,14 +9995,8 @@ class TrackerSingleton {
10584
9995
  }
10585
9996
  return this.instance.getTabId();
10586
9997
  }
10587
- getUxId() {
10588
- if (!IN_BROWSER || !this.ensureConfigured() || !this.instance) {
10589
- return null;
10590
- }
10591
- return this.instance.getUxId();
10592
- }
10593
9998
  }
10594
9999
  const tracker = new TrackerSingleton();
10595
10000
 
10596
- export { App, Messages, SanitizeLevel, API as default, tracker };
10001
+ export { Analytics, App, Messages, SanitizeLevel, API as default, tracker as openReplay, tracker };
10597
10002
  //# sourceMappingURL=entry.js.map