@openreplay/tracker 16.4.10 → 17.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/dist/cjs/common/messages.gen.d.ts +57 -7
  2. package/dist/cjs/entry.js +564 -1453
  3. package/dist/cjs/entry.js.map +1 -1
  4. package/dist/cjs/index.js +526 -1373
  5. package/dist/cjs/index.js.map +1 -1
  6. package/dist/cjs/main/app/guards.d.ts +1 -0
  7. package/dist/cjs/main/app/index.d.ts +5 -12
  8. package/dist/cjs/main/app/messages.gen.d.ts +7 -2
  9. package/dist/cjs/main/app/observer/observer.d.ts +4 -0
  10. package/dist/cjs/main/app/observer/top_observer.d.ts +3 -1
  11. package/dist/cjs/main/index.d.ts +9 -13
  12. package/dist/cjs/main/modules/conditionsManager.d.ts +6 -1
  13. package/dist/cjs/main/modules/longAnimationTask.d.ts +25 -0
  14. package/dist/cjs/main/modules/tagWatcher.d.ts +1 -1
  15. package/dist/cjs/main/modules/webAnimations.d.ts +9 -0
  16. package/dist/cjs/main/singleton.d.ts +0 -7
  17. package/dist/cjs/main/utils.d.ts +3 -0
  18. package/dist/lib/common/messages.gen.d.ts +57 -7
  19. package/dist/lib/entry.js +564 -1453
  20. package/dist/lib/entry.js.map +1 -1
  21. package/dist/lib/index.js +526 -1373
  22. package/dist/lib/index.js.map +1 -1
  23. package/dist/lib/main/app/guards.d.ts +1 -0
  24. package/dist/lib/main/app/index.d.ts +5 -12
  25. package/dist/lib/main/app/messages.gen.d.ts +7 -2
  26. package/dist/lib/main/app/observer/observer.d.ts +4 -0
  27. package/dist/lib/main/app/observer/top_observer.d.ts +3 -1
  28. package/dist/lib/main/index.d.ts +9 -13
  29. package/dist/lib/main/modules/conditionsManager.d.ts +6 -1
  30. package/dist/lib/main/modules/longAnimationTask.d.ts +25 -0
  31. package/dist/lib/main/modules/tagWatcher.d.ts +1 -1
  32. package/dist/lib/main/modules/webAnimations.d.ts +9 -0
  33. package/dist/lib/main/singleton.d.ts +0 -7
  34. package/dist/lib/main/utils.d.ts +3 -0
  35. package/dist/types/common/messages.gen.d.ts +57 -7
  36. package/dist/types/main/app/guards.d.ts +1 -0
  37. package/dist/types/main/app/index.d.ts +5 -12
  38. package/dist/types/main/app/messages.gen.d.ts +7 -2
  39. package/dist/types/main/app/observer/observer.d.ts +4 -0
  40. package/dist/types/main/app/observer/top_observer.d.ts +3 -1
  41. package/dist/types/main/index.d.ts +9 -13
  42. package/dist/types/main/modules/conditionsManager.d.ts +6 -1
  43. package/dist/types/main/modules/longAnimationTask.d.ts +25 -0
  44. package/dist/types/main/modules/tagWatcher.d.ts +1 -1
  45. package/dist/types/main/modules/webAnimations.d.ts +9 -0
  46. package/dist/types/main/singleton.d.ts +0 -7
  47. package/dist/types/main/utils.d.ts +3 -0
  48. package/package.json +13 -13
  49. package/dist/cjs/main/modules/featureFlags.d.ts +0 -25
  50. package/dist/cjs/main/modules/userTesting/SignalManager.d.ts +0 -29
  51. package/dist/cjs/main/modules/userTesting/dnd.d.ts +0 -1
  52. package/dist/cjs/main/modules/userTesting/index.d.ts +0 -45
  53. package/dist/cjs/main/modules/userTesting/recorder.d.ts +0 -24
  54. package/dist/cjs/main/modules/userTesting/styles.d.ts +0 -277
  55. package/dist/cjs/main/modules/userTesting/utils.d.ts +0 -9
  56. package/dist/lib/main/modules/featureFlags.d.ts +0 -25
  57. package/dist/lib/main/modules/userTesting/SignalManager.d.ts +0 -29
  58. package/dist/lib/main/modules/userTesting/dnd.d.ts +0 -1
  59. package/dist/lib/main/modules/userTesting/index.d.ts +0 -45
  60. package/dist/lib/main/modules/userTesting/recorder.d.ts +0 -24
  61. package/dist/lib/main/modules/userTesting/styles.d.ts +0 -277
  62. package/dist/lib/main/modules/userTesting/utils.d.ts +0 -9
  63. package/dist/types/main/modules/featureFlags.d.ts +0 -25
  64. package/dist/types/main/modules/userTesting/SignalManager.d.ts +0 -29
  65. package/dist/types/main/modules/userTesting/dnd.d.ts +0 -1
  66. package/dist/types/main/modules/userTesting/index.d.ts +0 -45
  67. package/dist/types/main/modules/userTesting/recorder.d.ts +0 -24
  68. package/dist/types/main/modules/userTesting/styles.d.ts +0 -277
  69. package/dist/types/main/modules/userTesting/utils.d.ts +0 -9
package/dist/cjs/entry.js CHANGED
@@ -1114,93 +1114,6 @@ const mapCondition = (condition) => {
1114
1114
  return con;
1115
1115
  };
1116
1116
 
1117
- class FeatureFlags {
1118
- constructor(app) {
1119
- this.app = app;
1120
- this.flags = [];
1121
- this.storageKey = '__openreplay_flags';
1122
- const persistFlags = this.app.sessionStorage.getItem(this.storageKey);
1123
- if (persistFlags) {
1124
- const persistFlagsStrArr = persistFlags.split(';').filter(Boolean);
1125
- this.flags = persistFlagsStrArr.map((flag) => JSON.parse(flag));
1126
- }
1127
- }
1128
- getFeatureFlag(flagName) {
1129
- return this.flags.find((flag) => flag.key === flagName);
1130
- }
1131
- isFlagEnabled(flagName) {
1132
- return this.flags.findIndex((flag) => flag.key === flagName) !== -1;
1133
- }
1134
- onFlagsLoad(cb) {
1135
- this.onFlagsCb = cb;
1136
- }
1137
- async reloadFlags(token) {
1138
- const persistFlagsStr = this.app.sessionStorage.getItem(this.storageKey);
1139
- const persistFlags = {};
1140
- if (persistFlagsStr) {
1141
- const persistArray = persistFlagsStr.split(';').filter(Boolean);
1142
- persistArray.forEach((flag) => {
1143
- const flagObj = JSON.parse(flag);
1144
- persistFlags[flagObj.key] = { key: flagObj.key, value: flagObj.value };
1145
- });
1146
- }
1147
- const sessionInfo = this.app.session.getInfo();
1148
- const userInfo = this.app.session.userInfo;
1149
- const requestObject = {
1150
- projectID: sessionInfo.projectID,
1151
- userID: sessionInfo.userID,
1152
- metadata: sessionInfo.metadata,
1153
- referrer: document.referrer,
1154
- os: userInfo.userOS,
1155
- device: userInfo.userDevice,
1156
- country: userInfo.userCountry,
1157
- state: userInfo.userState,
1158
- city: userInfo.userCity,
1159
- browser: userInfo.userBrowser,
1160
- persistFlags: persistFlags,
1161
- };
1162
- const authToken = token ?? this.app.session.getSessionToken();
1163
- const resp = await fetch(this.app.options.ingestPoint + '/v1/web/feature-flags', {
1164
- method: 'POST',
1165
- headers: {
1166
- 'Content-Type': 'application/json',
1167
- Authorization: `Bearer ${authToken}`,
1168
- },
1169
- body: JSON.stringify(requestObject),
1170
- });
1171
- if (resp.status === 200) {
1172
- const data = await resp.json();
1173
- return this.handleFlags(data.flags);
1174
- }
1175
- }
1176
- handleFlags(flags) {
1177
- const persistFlags = [];
1178
- flags.forEach((flag) => {
1179
- if (flag.is_persist)
1180
- persistFlags.push(flag);
1181
- });
1182
- let str = '';
1183
- const uniquePersistFlags = this.diffPersist(persistFlags);
1184
- uniquePersistFlags.forEach((flag) => {
1185
- str += `${JSON.stringify(flag)};`;
1186
- });
1187
- this.app.sessionStorage.setItem(this.storageKey, str);
1188
- this.flags = flags;
1189
- return this.onFlagsCb?.(flags);
1190
- }
1191
- clearPersistFlags() {
1192
- this.app.sessionStorage.removeItem(this.storageKey);
1193
- }
1194
- diffPersist(flags) {
1195
- const persistFlags = this.app.sessionStorage.getItem(this.storageKey);
1196
- if (!persistFlags)
1197
- return flags;
1198
- const persistFlagsStrArr = persistFlags.split(';').filter(Boolean);
1199
- const persistFlagsArr = persistFlagsStrArr.map((flag) => JSON.parse(flag));
1200
- return flags.filter((flag) => persistFlagsArr.findIndex((pf) => pf.key === flag.key) === -1);
1201
- }
1202
- }
1203
-
1204
1117
  const DEPRECATED_ATTRS = { htmlmasked: 'hidden', masked: 'obscured' };
1205
1118
  const IN_BROWSER = !(typeof window === 'undefined');
1206
1119
  const IS_FIREFOX = IN_BROWSER && navigator.userAgent.match(/firefox|fxios/i);
@@ -1469,6 +1382,43 @@ function simpleMerge(defaultObj, givenObj) {
1469
1382
  }
1470
1383
  return result;
1471
1384
  }
1385
+ function throttleWithTrailing(fn, interval) {
1386
+ const lastCalls = new Map();
1387
+ const timeouts = new Map();
1388
+ const lastArgs = new Map();
1389
+ const throttled = function (key, ...args) {
1390
+ const now = Date.now();
1391
+ const lastCall = lastCalls.get(key) ?? 0;
1392
+ const remaining = interval - (now - lastCall);
1393
+ lastArgs.set(key, args);
1394
+ if (remaining <= 0) {
1395
+ if (timeouts.has(key)) {
1396
+ clearTimeout(timeouts.get(key));
1397
+ timeouts.delete(key);
1398
+ }
1399
+ lastCalls.set(key, now);
1400
+ fn(key, ...args);
1401
+ }
1402
+ else if (!timeouts.has(key)) {
1403
+ const timeoutId = setTimeout(() => {
1404
+ lastCalls.set(key, Date.now());
1405
+ timeouts.delete(key);
1406
+ const finalArgs = lastArgs.get(key);
1407
+ fn(key, ...finalArgs);
1408
+ }, remaining);
1409
+ timeouts.set(key, timeoutId);
1410
+ }
1411
+ };
1412
+ throttled.clear = () => {
1413
+ for (const timeout of timeouts.values()) {
1414
+ clearTimeout(timeout);
1415
+ }
1416
+ timeouts.clear();
1417
+ lastArgs.clear();
1418
+ lastCalls.clear();
1419
+ };
1420
+ return throttled;
1421
+ }
1472
1422
 
1473
1423
  // Auto-generated, do not edit
1474
1424
  /* eslint-disable */
@@ -1679,6 +1629,13 @@ function SetNodeAttributeDictGlobal(id, name, value) {
1679
1629
  value,
1680
1630
  ];
1681
1631
  }
1632
+ function NodeAnimationResult(id, styles) {
1633
+ return [
1634
+ 36 /* Messages.Type.NodeAnimationResult */,
1635
+ id,
1636
+ styles,
1637
+ ];
1638
+ }
1682
1639
  function CSSInsertRule(id, rule, index) {
1683
1640
  return [
1684
1641
  37 /* Messages.Type.CSSInsertRule */,
@@ -1807,9 +1764,9 @@ function SetNodeAttributeDict(id, name, value) {
1807
1764
  value,
1808
1765
  ];
1809
1766
  }
1810
- function ResourceTimingDeprecated(timestamp, duration, ttfb, headerSize, encodedBodySize, decodedBodySize, url, initiator) {
1767
+ function ResourceTimingDeprecatedDeprecated(timestamp, duration, ttfb, headerSize, encodedBodySize, decodedBodySize, url, initiator) {
1811
1768
  return [
1812
- 53 /* Messages.Type.ResourceTimingDeprecated */,
1769
+ 53 /* Messages.Type.ResourceTimingDeprecatedDeprecated */,
1813
1770
  timestamp,
1814
1771
  duration,
1815
1772
  ttfb,
@@ -1891,6 +1848,13 @@ function CustomIssue(name, payload) {
1891
1848
  payload,
1892
1849
  ];
1893
1850
  }
1851
+ function SetNodeSlot(id, slotID) {
1852
+ return [
1853
+ 65 /* Messages.Type.SetNodeSlot */,
1854
+ id,
1855
+ slotID,
1856
+ ];
1857
+ }
1894
1858
  function CSSInsertRuleURLBased(id, rule, index, baseURL) {
1895
1859
  return [
1896
1860
  67 /* Messages.Type.CSSInsertRuleURLBased */,
@@ -2023,6 +1987,47 @@ function WSChannel(chType, channelName, data, timestamp, dir, messageType) {
2023
1987
  messageType,
2024
1988
  ];
2025
1989
  }
1990
+ function ResourceTiming(timestamp, duration, ttfb, headerSize, encodedBodySize, decodedBodySize, url, initiator, transferredSize, cached, queueing, dnsLookup, initialConnection, ssl, contentDownload, total, stalled) {
1991
+ return [
1992
+ 85 /* Messages.Type.ResourceTiming */,
1993
+ timestamp,
1994
+ duration,
1995
+ ttfb,
1996
+ headerSize,
1997
+ encodedBodySize,
1998
+ decodedBodySize,
1999
+ url,
2000
+ initiator,
2001
+ transferredSize,
2002
+ cached,
2003
+ queueing,
2004
+ dnsLookup,
2005
+ initialConnection,
2006
+ ssl,
2007
+ contentDownload,
2008
+ total,
2009
+ stalled,
2010
+ ];
2011
+ }
2012
+ function Incident(label, startTime, endTime) {
2013
+ return [
2014
+ 87 /* Messages.Type.Incident */,
2015
+ label,
2016
+ startTime,
2017
+ endTime,
2018
+ ];
2019
+ }
2020
+ function LongAnimationTask$1(name, duration, blockingDuration, firstUIEventTimestamp, startTime, scripts) {
2021
+ return [
2022
+ 89 /* Messages.Type.LongAnimationTask */,
2023
+ name,
2024
+ duration,
2025
+ blockingDuration,
2026
+ firstUIEventTimestamp,
2027
+ startTime,
2028
+ scripts,
2029
+ ];
2030
+ }
2026
2031
  function InputChange(id, value, valueMasked, label, hesitationTime, inputDuration) {
2027
2032
  return [
2028
2033
  112 /* Messages.Type.InputChange */,
@@ -2054,9 +2059,9 @@ function UnbindNodes(totalRemovedPercent) {
2054
2059
  totalRemovedPercent,
2055
2060
  ];
2056
2061
  }
2057
- function ResourceTiming(timestamp, duration, ttfb, headerSize, encodedBodySize, decodedBodySize, url, initiator, transferredSize, cached) {
2062
+ function ResourceTimingDeprecated(timestamp, duration, ttfb, headerSize, encodedBodySize, decodedBodySize, url, initiator, transferredSize, cached) {
2058
2063
  return [
2059
- 116 /* Messages.Type.ResourceTiming */,
2064
+ 116 /* Messages.Type.ResourceTimingDeprecated */,
2060
2065
  timestamp,
2061
2066
  duration,
2062
2067
  ttfb,
@@ -2153,9 +2158,11 @@ var _Messages = /*#__PURE__*/Object.freeze({
2153
2158
  Fetch: Fetch,
2154
2159
  GraphQL: GraphQL,
2155
2160
  GraphQLDeprecated: GraphQLDeprecated,
2161
+ Incident: Incident,
2156
2162
  InputChange: InputChange,
2157
2163
  JSException: JSException,
2158
2164
  LoadFontFace: LoadFontFace,
2165
+ LongAnimationTask: LongAnimationTask$1,
2159
2166
  LongTask: LongTask,
2160
2167
  Metadata: Metadata,
2161
2168
  MobX: MobX,
@@ -2167,6 +2174,7 @@ var _Messages = /*#__PURE__*/Object.freeze({
2167
2174
  NetworkRequest: NetworkRequest,
2168
2175
  NetworkRequestDeprecated: NetworkRequestDeprecated,
2169
2176
  NgRx: NgRx,
2177
+ NodeAnimationResult: NodeAnimationResult,
2170
2178
  OTable: OTable,
2171
2179
  PageLoadTiming: PageLoadTiming,
2172
2180
  PageRenderTiming: PageRenderTiming,
@@ -2179,6 +2187,7 @@ var _Messages = /*#__PURE__*/Object.freeze({
2179
2187
  RemoveNodeAttribute: RemoveNodeAttribute,
2180
2188
  ResourceTiming: ResourceTiming,
2181
2189
  ResourceTimingDeprecated: ResourceTimingDeprecated,
2190
+ ResourceTimingDeprecatedDeprecated: ResourceTimingDeprecatedDeprecated,
2182
2191
  SelectionChange: SelectionChange,
2183
2192
  SetCSSDataURLBased: SetCSSDataURLBased,
2184
2193
  SetInputChecked: SetInputChecked,
@@ -2192,6 +2201,7 @@ var _Messages = /*#__PURE__*/Object.freeze({
2192
2201
  SetNodeData: SetNodeData,
2193
2202
  SetNodeFocus: SetNodeFocus,
2194
2203
  SetNodeScroll: SetNodeScroll,
2204
+ SetNodeSlot: SetNodeSlot,
2195
2205
  SetPageLocation: SetPageLocation,
2196
2206
  SetPageLocationDeprecated: SetPageLocationDeprecated,
2197
2207
  SetPageVisibility: SetPageVisibility,
@@ -2266,7 +2276,7 @@ function Performance (app, opts) {
2266
2276
  const WATCHED_TAGS_KEY = '__or__watched_tags__';
2267
2277
  class TagWatcher {
2268
2278
  constructor(params) {
2269
- this.intervals = {};
2279
+ this.interval = null;
2270
2280
  this.tags = [];
2271
2281
  this.sessionStorage = params.sessionStorage;
2272
2282
  this.errLog = params.errLog;
@@ -2308,9 +2318,12 @@ class TagWatcher {
2308
2318
  }
2309
2319
  setTags(tags) {
2310
2320
  this.tags = tags;
2311
- this.intervals = {};
2312
- tags.forEach((tag) => {
2313
- this.intervals[tag.id] = setInterval(() => {
2321
+ if (this.interval) {
2322
+ clearInterval(this.interval);
2323
+ this.interval = null;
2324
+ }
2325
+ this.interval = setInterval(() => {
2326
+ this.tags.forEach((tag) => {
2314
2327
  const possibleEls = document.querySelectorAll(tag.selector);
2315
2328
  if (possibleEls.length > 0) {
2316
2329
  const el = possibleEls[0];
@@ -2318,1002 +2331,22 @@ class TagWatcher {
2318
2331
  el.__or_watcher_tagname = tag.id;
2319
2332
  this.observer.observe(el);
2320
2333
  }
2321
- }, 500);
2322
- });
2334
+ });
2335
+ }, 500);
2323
2336
  }
2324
2337
  onTagRendered(tagId) {
2325
- if (this.intervals[tagId]) {
2326
- clearInterval(this.intervals[tagId]);
2338
+ if (this.tags.findIndex(t => t.id === tagId)) {
2339
+ this.tags = this.tags.filter((tag) => tag.id !== tagId);
2327
2340
  }
2328
2341
  this.onTag(tagId);
2329
2342
  }
2330
2343
  clear() {
2331
- this.tags.forEach((tag) => {
2332
- clearInterval(this.intervals[tag.id]);
2333
- });
2334
2344
  this.tags = [];
2335
- this.intervals = {};
2336
- this.observer.disconnect();
2337
- }
2338
- }
2339
-
2340
- const bgStyle = {
2341
- position: 'fixed',
2342
- top: 0,
2343
- left: 0,
2344
- width: '100vw',
2345
- height: '100vh',
2346
- background: 'rgba(0, 0, 0, 0.40)',
2347
- display: 'flex',
2348
- alignItems: 'center',
2349
- justifyContent: 'center',
2350
- zIndex: 999999,
2351
- fontFamily: `-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"`,
2352
- };
2353
- const containerStyle = {
2354
- display: 'flex',
2355
- flexDirection: 'column',
2356
- gap: '2rem',
2357
- alignItems: 'center',
2358
- padding: '1.5rem',
2359
- borderRadius: '2px',
2360
- border: '1px solid rgb(255 255 255 / var(--tw-bg-opacity, 1))',
2361
- background: '#FFF',
2362
- width: '22rem',
2363
- };
2364
- const containerWidgetStyle = {
2365
- display: 'flex',
2366
- 'flex-direction': 'column',
2367
- gap: 'unset',
2368
- 'align-items': 'center',
2369
- padding: 'unset',
2370
- fontFamily: `-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"`,
2371
- 'border-radius': '2px',
2372
- border: '1px solid rgb(255 255 255 / var(--tw-bg-opacity, 1))',
2373
- background: 'rgba(255, 255, 255, 0.75)',
2374
- width: '22rem',
2375
- };
2376
- const titleStyle = {
2377
- fontFamily: 'Verdana, sans-serif',
2378
- fontSize: '1.25rem',
2379
- fontStyle: 'normal',
2380
- fontWeight: '500',
2381
- lineHeight: '1.75rem',
2382
- color: 'rgba(0, 0, 0, 0.85)',
2383
- };
2384
- const descriptionStyle = {
2385
- borderTop: '1px solid rgba(0, 0, 0, 0.06)',
2386
- borderBottom: '1px solid rgba(0, 0, 0, 0.06)',
2387
- padding: '1.25rem 0rem',
2388
- color: 'rgba(0, 0, 0, 0.85)',
2389
- fontFamily: 'Verdana, sans-serif',
2390
- fontSize: '13px',
2391
- fontStyle: 'normal',
2392
- fontWeight: '400',
2393
- lineHeight: 'auto',
2394
- whiteSpace: 'pre-wrap',
2395
- };
2396
- const buttonStyle = {
2397
- display: 'flex',
2398
- padding: '0.4rem 0.9375rem',
2399
- justifyContent: 'center',
2400
- alignItems: 'center',
2401
- gap: '0.625rem',
2402
- borderRadius: '0.25rem',
2403
- border: '1px solid #394EFF',
2404
- background: '#394EFF',
2405
- boxShadow: '0px 2px 0px 0px rgba(0, 0, 0, 0.04)',
2406
- color: '#FFF',
2407
- textAlign: 'center',
2408
- fontFamily: 'Verdana, sans-serif',
2409
- fontSize: '1rem',
2410
- fontStyle: 'normal',
2411
- fontWeight: '500',
2412
- lineHeight: '1.5rem',
2413
- cursor: 'pointer',
2414
- };
2415
- const sectionTitleStyle = {
2416
- fontFamily: 'Verdana, sans-serif',
2417
- fontSize: '13px',
2418
- fontWeight: '500',
2419
- lineHeight: 'auto',
2420
- display: 'flex',
2421
- justifyContent: 'space-between',
2422
- width: '100%',
2423
- cursor: 'pointer',
2424
- };
2425
- const contentStyle = {
2426
- display: 'flex',
2427
- flexDirection: 'column',
2428
- alignItems: 'flex-start',
2429
- gap: '0.625rem',
2430
- fontSize: '13px',
2431
- lineHeight: 'auto',
2432
- };
2433
- // New widget styles
2434
- const titleWidgetStyle = {
2435
- padding: '0.5rem',
2436
- gap: '0.5rem',
2437
- fontFamily: 'Verdana, sans-serif',
2438
- fontSize: '16px',
2439
- fontStyle: 'normal',
2440
- fontWeight: '500',
2441
- lineHeight: 'auto',
2442
- color: 'white',
2443
- display: 'flex',
2444
- alignItems: 'center',
2445
- width: '100%',
2446
- borderRadius: '2px',
2447
- background: 'rgba(0, 0, 0, 0.75)',
2448
- boxSizing: 'border-box',
2449
- };
2450
- const descriptionWidgetStyle = {
2451
- boxSizing: 'border-box',
2452
- display: 'block',
2453
- width: '100%',
2454
- borderBottom: '1px solid rgb(255 255 255 / var(--tw-bg-opacity, 1))',
2455
- background: '#FFF',
2456
- padding: '0.65rem',
2457
- alignSelf: 'stretch',
2458
- color: '#000',
2459
- fontFamily: 'Verdana, sans-serif',
2460
- // fontSize: '0.875rem',
2461
- fontStyle: 'normal',
2462
- fontWeight: '400',
2463
- // lineHeight: '1.375rem',
2464
- };
2465
- const endSectionStyle = {
2466
- ...descriptionWidgetStyle,
2467
- display: 'flex',
2468
- flexDirection: 'column',
2469
- alignItems: 'center',
2470
- gap: '0.625rem',
2471
- };
2472
- const symbolIcon = {
2473
- fontSize: '1.25rem',
2474
- fontWeight: '500',
2475
- cursor: 'pointer',
2476
- color: '#394EFF',
2477
- };
2478
- const buttonWidgetStyle = {
2479
- display: 'flex',
2480
- padding: '0.4rem 0.9375rem',
2481
- justifyContent: 'center',
2482
- alignItems: 'center',
2483
- gap: '0.625rem',
2484
- borderRadius: '0.25rem',
2485
- border: '1px solid #394EFF',
2486
- background: '#394EFF',
2487
- boxShadow: '0px 2px 0px 0px rgba(0, 0, 0, 0.04)',
2488
- color: '#FFF',
2489
- textAlign: 'center',
2490
- fontFamily: 'Verdana, sans-serif',
2491
- fontSize: '1rem',
2492
- fontStyle: 'normal',
2493
- fontWeight: '500',
2494
- lineHeight: '1.5rem',
2495
- width: '100%',
2496
- boxSizing: 'border-box',
2497
- cursor: 'pointer',
2498
- };
2499
- const stopWidgetStyle = {
2500
- marginTop: '1rem',
2501
- marginBottom: '1rem',
2502
- cursor: 'pointer',
2503
- display: 'block',
2504
- fontWeight: '500',
2505
- fontSize: '13px!important',
2506
- lineHeight: 'auto',
2507
- };
2508
- const paginationStyle = {
2509
- display: 'flex',
2510
- justifyContent: 'space-between',
2511
- alignItems: 'center',
2512
- gap: '1rem',
2513
- padding: '0.5rem',
2514
- width: '100%',
2515
- boxSizing: 'border-box',
2516
- };
2517
- const taskNumberActive = {
2518
- display: 'flex',
2519
- flexDirection: 'column',
2520
- alignItems: 'center',
2521
- justifyContent: 'center',
2522
- borderRadius: '6.25em',
2523
- outline: '1px solid #394EFF',
2524
- fontSize: '13px',
2525
- height: '24px',
2526
- width: '24px',
2527
- };
2528
- const taskNumberDone = {
2529
- display: 'flex',
2530
- flexDirection: 'column',
2531
- alignItems: 'center',
2532
- justifyContent: 'center',
2533
- borderRadius: '6.25em',
2534
- outline: '1px solid #D2DFFF',
2535
- boxShadow: '0px 2px 0px 0px rgba(0, 0, 0, 0.04)',
2536
- background: '#D2DFFF',
2537
- fontSize: '13px',
2538
- height: '24px',
2539
- width: '24px',
2540
- };
2541
- const taskDescriptionCard = {
2542
- borderRadius: '0.375rem',
2543
- border: '1px solid rgba(0, 0, 0, 0.06)',
2544
- background: '#F5F7FF',
2545
- boxShadow: '0px 2px 0px 0px rgba(0, 0, 0, 0.04)',
2546
- display: 'flex',
2547
- flexDirection: 'column',
2548
- padding: '0.625rem 0.9375rem',
2549
- gap: '0.5rem',
2550
- alignSelf: 'stretch',
2551
- };
2552
- const taskTextStyle = {
2553
- fontWeight: 'bold',
2554
- };
2555
- const taskDescriptionStyle = {
2556
- fontSize: '13px',
2557
- lineHeight: 'auto',
2558
- };
2559
- const taskButtonStyle = {
2560
- marginRight: '0.5rem',
2561
- cursor: 'pointer',
2562
- color: '#394EFF',
2563
- textAlign: 'center',
2564
- fontFamily: 'Verdana, sans-serif',
2565
- fontSize: '13px',
2566
- fontStyle: 'normal',
2567
- fontWeight: '500',
2568
- lineHeight: 'auto',
2569
- };
2570
- const taskButtonBorderedStyle = {
2571
- ...taskButtonStyle,
2572
- display: 'flex',
2573
- padding: '0.25rem 0.9375rem',
2574
- justifyContent: 'center',
2575
- alignItems: 'center',
2576
- gap: '0.5rem',
2577
- borderRadius: '0.25rem',
2578
- border: '1px solid #394EFF',
2579
- };
2580
- const taskButtonsRow = {
2581
- display: 'flex',
2582
- justifyContent: 'space-between',
2583
- alignItems: 'center',
2584
- width: '100%',
2585
- boxSizing: 'border-box',
2586
- };
2587
- const spinnerStyles = {
2588
- border: '4px solid rgba(255, 255, 255, 0.4)',
2589
- width: '16px',
2590
- height: '16px',
2591
- borderRadius: '50%',
2592
- borderLeftColor: '#fff',
2593
- animation: 'spin 0.5s linear infinite',
2594
- };
2595
-
2596
- const Quality = {
2597
- Standard: { width: 1280, height: 720 }};
2598
- class Recorder {
2599
- constructor(app) {
2600
- this.app = app;
2601
- this.mediaRecorder = null;
2602
- this.recordedChunks = [];
2603
- this.stream = null;
2604
- this.recStartTs = null;
2605
- }
2606
- async startRecording(fps, quality, micReq, camReq) {
2607
- this.recStartTs = this.app.timestamp();
2608
- const videoConstraints = quality;
2609
- try {
2610
- this.stream = await navigator.mediaDevices.getUserMedia({
2611
- video: camReq ? { ...videoConstraints, frameRate: { ideal: fps } } : false,
2612
- audio: micReq,
2613
- });
2614
- this.mediaRecorder = new MediaRecorder(this.stream, {
2615
- mimeType: 'video/webm;codecs=vp9',
2616
- });
2617
- this.recordedChunks = [];
2618
- this.mediaRecorder.ondataavailable = (event) => {
2619
- if (event.data.size > 0) {
2620
- this.recordedChunks.push(event.data);
2621
- }
2622
- };
2623
- this.mediaRecorder.start();
2624
- }
2625
- catch (error) {
2626
- console.error(error);
2627
- }
2628
- }
2629
- async stopRecording() {
2630
- return new Promise((resolve) => {
2631
- if (!this.mediaRecorder)
2632
- return;
2633
- this.mediaRecorder.onstop = () => {
2634
- const blob = new Blob(this.recordedChunks, {
2635
- type: 'video/webm',
2636
- });
2637
- resolve(blob);
2638
- };
2639
- this.mediaRecorder.stop();
2640
- });
2641
- }
2642
- async sendToAPI() {
2643
- const blob = await this.stopRecording();
2644
- // const formData = new FormData()
2645
- // formData.append('file', blob, 'record.webm')
2646
- // formData.append('start', this.recStartTs?.toString() ?? '')
2647
- return fetch(`${this.app.options.ingestPoint}/v1/web/uxt/upload-url`, {
2648
- headers: {
2649
- Authorization: `Bearer ${this.app.session.getSessionToken()}`,
2650
- },
2651
- })
2652
- .then((r) => {
2653
- if (r.ok) {
2654
- return r.json();
2655
- }
2656
- else {
2657
- throw new Error('Failed to get upload url');
2658
- }
2659
- })
2660
- .then(({ url }) => {
2661
- return fetch(url, {
2662
- method: 'PUT',
2663
- headers: {
2664
- 'Content-Type': 'video/webm',
2665
- },
2666
- body: blob,
2667
- });
2668
- })
2669
- .catch(console.error)
2670
- .finally(() => {
2671
- this.discard();
2672
- });
2673
- }
2674
- async saveToFile(fileName = 'recorded-video.webm') {
2675
- const blob = await this.stopRecording();
2676
- const url = URL.createObjectURL(blob);
2677
- const a = document.createElement('a');
2678
- a.style.display = 'none';
2679
- a.href = url;
2680
- a.download = fileName;
2681
- document.body.appendChild(a);
2682
- a.click();
2683
- window.URL.revokeObjectURL(url);
2684
- document.body.removeChild(a);
2685
- }
2686
- discard() {
2687
- this.mediaRecorder?.stop();
2688
- this.stream?.getTracks().forEach((track) => track.stop());
2689
- }
2690
- }
2691
-
2692
- // @ts-nocheck
2693
- function attachDND(element, dragTarget) {
2694
- dragTarget.onmousedown = function (event) {
2695
- const clientRect = element.getBoundingClientRect();
2696
- const shiftX = event.clientX - clientRect.left;
2697
- const shiftY = event.clientY - clientRect.top;
2698
- element.style.position = 'fixed';
2699
- element.style.zIndex = 99999999999999;
2700
- moveAt(event.pageX, event.pageY);
2701
- function moveAt(pageX, pageY) {
2702
- let leftC = pageX - shiftX;
2703
- let topC = pageY - shiftY;
2704
- if (leftC <= 5)
2705
- leftC = 5;
2706
- if (topC <= 5)
2707
- topC = 5;
2708
- if (leftC >= window.innerWidth - clientRect.width)
2709
- leftC = window.innerWidth - clientRect.width;
2710
- if (topC >= window.innerHeight - clientRect.height)
2711
- topC = window.innerHeight - clientRect.height;
2712
- element.style.left = `${leftC}px`;
2713
- element.style.top = `${topC}px`;
2714
- }
2715
- function onMouseMove(event) {
2716
- moveAt(event.pageX, event.pageY);
2717
- }
2718
- document.addEventListener('mousemove', onMouseMove);
2719
- const clearAll = () => {
2720
- document.removeEventListener('mousemove', onMouseMove);
2721
- document.removeEventListener('mouseup', clearAll);
2722
- };
2723
- document.addEventListener('mouseup', clearAll);
2724
- };
2725
- dragTarget.ondragstart = function () {
2726
- return false;
2727
- };
2728
- }
2729
-
2730
- function generateGrid() {
2731
- const grid = document.createElement('div');
2732
- grid.className = 'grid';
2733
- for (let i = 0; i < 16; i++) {
2734
- const cell = document.createElement('div');
2735
- Object.assign(cell.style, {
2736
- width: '2px',
2737
- height: '2px',
2738
- borderRadius: '10px',
2739
- background: 'white',
2740
- });
2741
- cell.className = 'cell';
2742
- grid.appendChild(cell);
2743
- }
2744
- Object.assign(grid.style, {
2745
- display: 'grid',
2746
- gridTemplateColumns: 'repeat(4, 1fr)',
2747
- gridTemplateRows: 'repeat(4, 1fr)',
2748
- gap: '2px',
2749
- cursor: 'grab',
2750
- });
2751
- return grid;
2752
- }
2753
- function generateChevron() {
2754
- const triangle = document.createElement('div');
2755
- Object.assign(triangle.style, {
2756
- width: '0',
2757
- height: '0',
2758
- borderLeft: '7px solid transparent',
2759
- borderRight: '7px solid transparent',
2760
- borderBottom: '7px solid white',
2761
- });
2762
- const container = document.createElement('div');
2763
- container.appendChild(triangle);
2764
- Object.assign(container.style, {
2765
- display: 'flex',
2766
- alignItems: 'center',
2767
- justifyContent: 'center',
2768
- width: '16px',
2769
- height: '16px',
2770
- cursor: 'pointer',
2771
- marginLeft: 'auto',
2772
- transform: 'rotate(180deg)',
2773
- });
2774
- return container;
2775
- }
2776
- function addKeyframes() {
2777
- const styleSheet = document.createElement('style');
2778
- styleSheet.type = 'text/css';
2779
- styleSheet.innerText = `@keyframes spin {
2780
- 0% { transform: rotate(0deg); }
2781
- 100% { transform: rotate(360deg); }
2782
- }`;
2783
- document.head.appendChild(styleSheet);
2784
- }
2785
- function createSpinner() {
2786
- addKeyframes();
2787
- const spinner = document.createElement('div');
2788
- spinner.classList.add('spinner');
2789
- Object.assign(spinner.style, spinnerStyles);
2790
- return spinner;
2791
- }
2792
- function createElement(tag, className, styles, textContent, id) {
2793
- const element = document.createElement(tag);
2794
- element.className = className;
2795
- Object.assign(element.style, styles);
2796
- if (textContent) {
2797
- element.textContent = textContent;
2798
- }
2799
- if (id) {
2800
- element.id = id;
2801
- }
2802
- return element;
2803
- }
2804
- const TEST_START = 'or_uxt_test_start';
2805
- const TASK_IND = 'or_uxt_task_index';
2806
- const SESSION_ID = 'or_uxt_session_id';
2807
- const TEST_ID = 'or_uxt_test_id';
2808
-
2809
- class SignalManager {
2810
- constructor(ingestPoint, getTimestamp, token, testId, storageKey, setStorageKey, removeStorageKey, getStorageKey, getSessionId) {
2811
- this.ingestPoint = ingestPoint;
2812
- this.getTimestamp = getTimestamp;
2813
- this.token = token;
2814
- this.testId = testId;
2815
- this.storageKey = storageKey;
2816
- this.setStorageKey = setStorageKey;
2817
- this.removeStorageKey = removeStorageKey;
2818
- this.getStorageKey = getStorageKey;
2819
- this.getSessionId = getSessionId;
2820
- this.durations = {
2821
- testStart: 0,
2822
- tasks: [],
2823
- };
2824
- this.getDurations = () => {
2825
- return this.durations;
2826
- };
2827
- this.setDurations = (durations) => {
2828
- this.durations.testStart = durations.testStart;
2829
- this.durations.tasks = durations.tasks;
2830
- };
2831
- this.signalTask = (taskId, status, taskAnswer) => {
2832
- if (!taskId)
2833
- return console.error('User Testing: No Task ID Given');
2834
- const taskStart = this.durations.tasks.find((t) => t.taskId === taskId);
2835
- const timestamp = this.getTimestamp();
2836
- const duration = taskStart ? timestamp - taskStart.started : 0;
2837
- return fetch(`${this.ingestPoint}/v1/web/uxt/signals/task`, {
2838
- method: 'POST',
2839
- headers: {
2840
- // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
2841
- Authorization: `Bearer ${this.token}`,
2842
- },
2843
- body: JSON.stringify({
2844
- testId: this.testId,
2845
- taskId,
2846
- status,
2847
- duration,
2848
- timestamp,
2849
- taskAnswer,
2850
- }),
2851
- });
2852
- };
2853
- this.signalTest = (status) => {
2854
- const timestamp = this.getTimestamp();
2855
- if (status === 'begin' && this.testId) {
2856
- const sessionId = this.getSessionId();
2857
- this.setStorageKey(SESSION_ID, sessionId);
2858
- this.setStorageKey(this.storageKey, this.testId.toString());
2859
- this.setStorageKey(TEST_START, timestamp.toString());
2860
- }
2861
- else {
2862
- this.removeStorageKey(this.storageKey);
2863
- this.removeStorageKey(TASK_IND);
2864
- this.removeStorageKey(TEST_START);
2865
- }
2866
- const start = this.durations.testStart || timestamp;
2867
- const duration = timestamp - start;
2868
- return fetch(`${this.ingestPoint}/v1/web/uxt/signals/test`, {
2869
- method: 'POST',
2870
- headers: {
2871
- // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
2872
- Authorization: `Bearer ${this.token}`,
2873
- },
2874
- body: JSON.stringify({
2875
- testId: this.testId,
2876
- status,
2877
- duration,
2878
- timestamp,
2879
- }),
2880
- });
2881
- };
2882
- const possibleStart = this.getStorageKey(TEST_START);
2883
- if (possibleStart) {
2884
- this.durations.testStart = parseInt(possibleStart, 10);
2885
- }
2886
- }
2887
- }
2888
-
2889
- class UserTestManager {
2890
- constructor(app, storageKey) {
2891
- this.app = app;
2892
- this.storageKey = storageKey;
2893
- this.bg = createElement('div', 'bg', bgStyle, undefined, '__or_ut_bg');
2894
- this.container = createElement('div', 'container', containerStyle, undefined, '__or_ut_ct');
2895
- this.widgetGuidelinesVisible = true;
2896
- this.widgetTasksVisible = false;
2897
- this.widgetVisible = true;
2898
- this.isActive = false;
2899
- this.descriptionSection = null;
2900
- this.taskSection = null;
2901
- this.endSection = null;
2902
- this.stopButton = null;
2903
- this.stopButtonContainer = null;
2904
- this.test = null;
2905
- this.testId = null;
2906
- this.signalManager = null;
2907
- this.getTest = (id, token, inProgress) => {
2908
- this.testId = id;
2909
- const ingest = this.app.options.ingestPoint;
2910
- return fetch(`${ingest}/v1/web/uxt/test/${id}`, {
2911
- headers: {
2912
- Authorization: `Bearer ${token}`,
2913
- },
2914
- })
2915
- .then((res) => res.json())
2916
- .then(({ test }) => {
2917
- this.isActive = true;
2918
- this.test = test;
2919
- 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());
2920
- this.createGreeting(test.title, test.reqMic, test.reqCamera);
2921
- if (inProgress) {
2922
- if (test.reqMic || test.reqCamera) {
2923
- void this.userRecorder.startRecording(30, Quality.Standard, test.reqMic, test.reqCamera);
2924
- }
2925
- this.showWidget(test.description, test.tasks, true);
2926
- this.showTaskSection();
2927
- }
2928
- })
2929
- .then(() => id)
2930
- .catch((err) => {
2931
- console.log('OR: Error fetching test', err);
2932
- });
2933
- };
2934
- this.hideTaskSection = () => false;
2935
- this.showTaskSection = () => true;
2936
- this.collapseWidget = () => false;
2937
- this.removeGreeting = () => false;
2938
- // eslint-disable-next-line @typescript-eslint/no-empty-function
2939
- this.toggleDescriptionVisibility = () => { };
2940
- this.currentTaskIndex = 0;
2941
- this.userRecorder = new Recorder(app);
2942
- const sessionId = this.app.getSessionID();
2943
- const savedSessionId = this.app.localStorage.getItem(SESSION_ID);
2944
- if (sessionId !== savedSessionId) {
2945
- this.app.localStorage.removeItem(this.storageKey);
2946
- this.app.localStorage.removeItem(SESSION_ID);
2947
- this.app.localStorage.removeItem(TEST_ID);
2948
- this.app.localStorage.removeItem(TASK_IND);
2949
- this.app.localStorage.removeItem(TEST_START);
2950
- }
2951
- const taskIndex = this.app.localStorage.getItem(TASK_IND);
2952
- if (taskIndex) {
2953
- this.currentTaskIndex = parseInt(taskIndex, 10);
2954
- }
2955
- }
2956
- getTestId() {
2957
- return this.testId;
2958
- }
2959
- createGreeting(title, micRequired, cameraRequired) {
2960
- const titleElement = createElement('div', 'title', titleStyle, title);
2961
- const descriptionElement = createElement('div', 'description', descriptionStyle, `Welcome, you're here to help us improve, not to be judged. Your insights matter!\n
2962
- 📹 We're recording this browser tab to learn from your experience.
2963
- 🎤 Please enable mic and camera if asked, to give us a complete picture.`);
2964
- const buttonElement = createElement('div', 'button', buttonStyle, 'Read guidelines to begin');
2965
- this.removeGreeting = () => {
2966
- // this.container.innerHTML = ''
2967
- if (micRequired || cameraRequired) {
2968
- void this.userRecorder.startRecording(30, Quality.Standard, micRequired, cameraRequired);
2969
- }
2970
- this.container.removeChild(buttonElement);
2971
- this.container.removeChild(descriptionElement);
2972
- this.container.removeChild(titleElement);
2973
- return false;
2974
- };
2975
- buttonElement.onclick = () => {
2976
- this.removeGreeting();
2977
- const durations = this.signalManager?.getDurations();
2978
- if (durations && this.signalManager) {
2979
- durations.testStart = this.app.timestamp();
2980
- this.signalManager.setDurations(durations);
2981
- }
2982
- void this.signalManager?.signalTest('begin');
2983
- this.container.style.fontFamily = `-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"`;
2984
- Object.assign(this.container.style, containerWidgetStyle);
2985
- this.showWidget(this.test?.guidelines || '', this.test?.tasks || []);
2986
- };
2987
- this.container.append(titleElement, descriptionElement, buttonElement);
2988
- this.bg.appendChild(this.container);
2989
- document.body.appendChild(this.bg);
2990
- }
2991
- showWidget(guidelines, tasks, inProgress) {
2992
- this.container.innerHTML = '';
2993
- Object.assign(this.bg.style, {
2994
- position: 'fixed',
2995
- zIndex: 99999999999999,
2996
- right: '8px',
2997
- left: 'unset',
2998
- width: 'fit-content',
2999
- top: '8px',
3000
- height: 'fit-content',
3001
- background: 'unset',
3002
- display: 'unset',
3003
- alignItems: 'unset',
3004
- justifyContent: 'unset',
3005
- });
3006
- // Create title section
3007
- const titleSection = this.createTitleSection();
3008
- this.container.style.fontFamily = `-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"`;
3009
- Object.assign(this.container.style, containerWidgetStyle);
3010
- const descriptionSection = this.createDescriptionSection(guidelines);
3011
- const tasksSection = this.createTasksSection(tasks);
3012
- const stopButton = createElement('div', 'stop_bn_or', stopWidgetStyle, 'Abort Session');
3013
- const stopContainer = createElement('div', 'stop_ct_or', { fontSize: '13px!important' });
3014
- stopContainer.style.fontSize = '13px';
3015
- stopContainer.append(stopButton);
3016
- this.container.append(titleSection, descriptionSection, tasksSection, stopContainer);
3017
- this.taskSection = tasksSection;
3018
- this.descriptionSection = descriptionSection;
3019
- this.stopButton = stopButton;
3020
- this.stopButtonContainer = stopContainer;
3021
- stopButton.onclick = () => {
3022
- this.userRecorder.discard();
3023
- void this.signalManager?.signalTest('skipped');
3024
- document.body.removeChild(this.bg);
3025
- window.close();
3026
- };
3027
- if (!inProgress) {
3028
- this.hideTaskSection();
3029
- }
3030
- else {
3031
- this.toggleDescriptionVisibility();
3032
- }
3033
- }
3034
- createTitleSection() {
3035
- const title = createElement('div', 'title', titleWidgetStyle);
3036
- const leftIcon = generateGrid();
3037
- const titleText = createElement('div', 'title_text', {
3038
- maxWidth: '19rem',
3039
- overflow: 'hidden',
3040
- textOverflow: 'ellipsis',
3041
- width: '100%',
3042
- fontSize: 16,
3043
- lineHeight: 'auto',
3044
- cursor: 'pointer',
3045
- }, this.test?.title);
3046
- const rightIcon = generateChevron();
3047
- title.append(leftIcon, titleText, rightIcon);
3048
- const toggleWidget = (isVisible) => {
3049
- this.widgetVisible = isVisible;
3050
- this.container.style.fontFamily = `-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"`;
3051
- Object.assign(this.container.style, this.widgetVisible
3052
- ? containerWidgetStyle
3053
- : { border: 'none', background: 'none', padding: 0 });
3054
- if (this.taskSection) {
3055
- Object.assign(this.taskSection.style, this.widgetVisible ? descriptionWidgetStyle : { display: 'none' });
3056
- }
3057
- if (this.descriptionSection) {
3058
- Object.assign(this.descriptionSection.style, this.widgetVisible ? descriptionWidgetStyle : { display: 'none' });
3059
- }
3060
- if (this.endSection) {
3061
- Object.assign(this.endSection.style, this.widgetVisible ? descriptionWidgetStyle : { display: 'none' });
3062
- }
3063
- if (this.stopButton) {
3064
- Object.assign(this.stopButton.style, this.widgetVisible ? stopWidgetStyle : { display: 'none' });
3065
- }
3066
- return isVisible;
3067
- };
3068
- const collapseWidget = () => {
3069
- Object.assign(rightIcon.style, {
3070
- transform: this.widgetVisible ? 'rotate(0deg)' : 'rotate(180deg)',
3071
- });
3072
- toggleWidget(!this.widgetVisible);
3073
- };
3074
- titleText.onclick = collapseWidget;
3075
- rightIcon.onclick = collapseWidget;
3076
- attachDND(this.bg, leftIcon);
3077
- this.collapseWidget = () => toggleWidget(false);
3078
- return title;
3079
- }
3080
- createDescriptionSection(guidelines) {
3081
- const section = createElement('div', 'description_section_or', descriptionWidgetStyle);
3082
- const titleContainer = createElement('div', 'description_s_title_or', sectionTitleStyle);
3083
- const title = createElement('div', 'title', {
3084
- fontSize: 13,
3085
- fontWeight: 500,
3086
- lineHeight: 'auto',
3087
- }, 'Introduction & Guidelines');
3088
- const icon = createElement('div', 'icon', symbolIcon, '-');
3089
- const content = createElement('div', 'content', contentStyle);
3090
- const descriptionC = createElement('div', 'text_description', {
3091
- maxHeight: '250px',
3092
- overflowY: 'auto',
3093
- whiteSpace: 'pre-wrap',
3094
- fontSize: 13,
3095
- color: '#454545',
3096
- lineHeight: 'auto',
3097
- });
3098
- descriptionC.innerHTML = guidelines;
3099
- const button = createElement('div', 'button_begin_or', buttonWidgetStyle, 'Begin Test');
3100
- titleContainer.append(title, icon);
3101
- content.append(descriptionC, button);
3102
- section.append(titleContainer, content);
3103
- const toggleDescriptionVisibility = () => {
3104
- this.widgetGuidelinesVisible = !this.widgetGuidelinesVisible;
3105
- icon.textContent = this.widgetGuidelinesVisible ? '-' : '+';
3106
- Object.assign(content.style, this.widgetGuidelinesVisible ? contentStyle : { display: 'none' });
3107
- };
3108
- titleContainer.onclick = toggleDescriptionVisibility;
3109
- this.toggleDescriptionVisibility = () => {
3110
- this.widgetGuidelinesVisible = false;
3111
- icon.textContent = this.widgetGuidelinesVisible ? '-' : '+';
3112
- Object.assign(content.style, this.widgetGuidelinesVisible ? contentStyle : { display: 'none' });
3113
- content.removeChild(button);
3114
- };
3115
- button.onclick = () => {
3116
- toggleDescriptionVisibility();
3117
- if (this.test) {
3118
- const durations = this.signalManager?.getDurations();
3119
- const taskDurationInd = durations
3120
- ? durations.tasks.findIndex((t) => this.test && t.taskId === this.test.tasks[0].task_id)
3121
- : null;
3122
- if (durations && taskDurationInd === -1) {
3123
- durations.tasks.push({
3124
- taskId: this.test.tasks[0].task_id,
3125
- started: this.app.timestamp(),
3126
- });
3127
- this.signalManager?.setDurations(durations);
3128
- }
3129
- void this.signalManager?.signalTask(this.test.tasks[0].task_id, 'begin');
3130
- }
3131
- this.showTaskSection();
3132
- content.removeChild(button);
3133
- };
3134
- return section;
3135
- }
3136
- createTasksSection(tasks) {
3137
- this.container.style.fontFamily = `-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"`;
3138
- Object.assign(this.container.style, containerWidgetStyle);
3139
- const section = createElement('div', 'task_section_or', descriptionWidgetStyle);
3140
- const titleContainer = createElement('div', 'description_t_title_or', sectionTitleStyle);
3141
- const title = createElement('div', 'title', {
3142
- fontSize: '13px',
3143
- fontWeight: '500',
3144
- lineHeight: 'auto',
3145
- }, 'Tasks');
3146
- const icon = createElement('div', 'icon', symbolIcon, '-');
3147
- const content = createElement('div', 'content', contentStyle);
3148
- const pagination = createElement('div', 'pagination', paginationStyle);
3149
- // const leftArrow = createElement('span', 'leftArrow', {}, '<')
3150
- // const rightArrow = createElement('span', 'rightArrow', {}, '>')
3151
- const taskCard = createElement('div', 'taskCard', taskDescriptionCard);
3152
- const taskText = createElement('div', 'taskText', taskTextStyle);
3153
- const taskDescription = createElement('div', 'taskDescription', taskDescriptionStyle);
3154
- const taskButtons = createElement('div', 'taskButtons', taskButtonsRow);
3155
- const inputTitle = createElement('div', 'taskText', taskTextStyle);
3156
- inputTitle.textContent = 'Your answer';
3157
- const inputArea = createElement('textarea', 'taskDescription', {
3158
- resize: 'vertical',
3159
- });
3160
- const inputContainer = createElement('div', 'inputArea', taskDescriptionCard);
3161
- inputContainer.append(inputTitle, inputArea);
3162
- const closePanelButton = createElement('div', 'closePanelButton', taskButtonStyle, 'Collapse Panel');
3163
- const nextButton = createElement('div', 'nextButton', taskButtonBorderedStyle, 'Done, Next');
3164
- titleContainer.append(title, icon);
3165
- taskCard.append(taskText, taskDescription);
3166
- taskButtons.append(closePanelButton, nextButton);
3167
- content.append(pagination, taskCard, inputContainer, taskButtons);
3168
- section.append(titleContainer, content);
3169
- const updateTaskContent = () => {
3170
- const task = tasks[this.currentTaskIndex];
3171
- taskText.textContent = task.title;
3172
- taskDescription.textContent = task.description;
3173
- if (task.allow_typing) {
3174
- inputContainer.style.display = 'flex';
3175
- }
3176
- else {
3177
- inputContainer.style.display = 'none';
3178
- }
3179
- };
3180
- // pagination.appendChild(leftArrow)
3181
- tasks.forEach((_, index) => {
3182
- const pageNumber = createElement('span', `or_task_${index}`, {
3183
- outline: '1px solid #efefef',
3184
- fontSize: '13px',
3185
- height: '24px',
3186
- width: '24px',
3187
- display: 'flex',
3188
- flexDirection: 'column',
3189
- alignItems: 'center',
3190
- justifyContent: 'center',
3191
- borderRadius: '6.25em',
3192
- }, (index + 1).toString());
3193
- pageNumber.id = `or_task_${index}`;
3194
- pagination.append(pageNumber);
3195
- });
3196
- // pagination.appendChild(rightArrow)
3197
- const toggleTasksVisibility = () => {
3198
- this.widgetTasksVisible = !this.widgetTasksVisible;
3199
- icon.textContent = this.widgetTasksVisible ? '-' : '+';
3200
- Object.assign(content.style, this.widgetTasksVisible ? contentStyle : { display: 'none' });
3201
- };
3202
- this.hideTaskSection = () => {
3203
- icon.textContent = '+';
3204
- Object.assign(content.style, {
3205
- display: 'none',
3206
- });
3207
- this.widgetTasksVisible = false;
3208
- return false;
3209
- };
3210
- this.showTaskSection = () => {
3211
- icon.textContent = '-';
3212
- Object.assign(content.style, contentStyle);
3213
- this.widgetTasksVisible = true;
3214
- return true;
3215
- };
3216
- const highlightActive = () => {
3217
- const activeTaskEl = document.getElementById(`or_task_${this.currentTaskIndex}`);
3218
- if (activeTaskEl) {
3219
- Object.assign(activeTaskEl.style, taskNumberActive);
3220
- }
3221
- for (let i = 0; i < this.currentTaskIndex; i++) {
3222
- const taskEl = document.getElementById(`or_task_${i}`);
3223
- if (taskEl) {
3224
- Object.assign(taskEl.style, taskNumberDone);
3225
- }
3226
- }
3227
- };
3228
- titleContainer.onclick = toggleTasksVisibility;
3229
- closePanelButton.onclick = this.collapseWidget;
3230
- nextButton.onclick = () => {
3231
- const textAnswer = tasks[this.currentTaskIndex].allow_typing ? inputArea.value : undefined;
3232
- inputArea.value = '';
3233
- void this.signalManager?.signalTask(tasks[this.currentTaskIndex].task_id, 'done', textAnswer);
3234
- if (this.currentTaskIndex < tasks.length - 1) {
3235
- this.currentTaskIndex++;
3236
- updateTaskContent();
3237
- const durations = this.signalManager?.getDurations();
3238
- if (durations &&
3239
- durations.tasks.findIndex((t) => t.taskId === tasks[this.currentTaskIndex].task_id) === -1) {
3240
- durations.tasks.push({
3241
- taskId: tasks[this.currentTaskIndex].task_id,
3242
- started: this.app.timestamp(),
3243
- });
3244
- this.signalManager?.setDurations(durations);
3245
- }
3246
- void this.signalManager?.signalTask(tasks[this.currentTaskIndex].task_id, 'begin');
3247
- highlightActive();
3248
- }
3249
- else {
3250
- this.showEndSection();
3251
- }
3252
- this.app.localStorage.setItem('or_uxt_task_index', this.currentTaskIndex.toString());
3253
- };
3254
- setTimeout(() => {
3255
- const firstTaskEl = document.getElementById('or_task_0');
3256
- if (firstTaskEl) {
3257
- Object.assign(firstTaskEl.style, taskNumberActive);
3258
- }
3259
- updateTaskContent();
3260
- highlightActive();
3261
- }, 1);
3262
- return section;
3263
- }
3264
- showEndSection() {
3265
- let isLoading = true;
3266
- void this.signalManager?.signalTest('done');
3267
- const section = createElement('div', 'end_section_or', endSectionStyle);
3268
- const title = createElement('div', 'end_title_or', {
3269
- fontSize: '1.25rem',
3270
- fontWeight: '500',
3271
- }, 'Thank you! 👍');
3272
- const description = createElement('div', 'end_description_or', {}, this.test?.conclusion ??
3273
- 'Thank you for participating in our usability test. Your feedback has been captured and will be used to enhance our website. \n' +
3274
- '\n' +
3275
- 'We appreciate your time and valuable input.');
3276
- const button = createElement('div', 'end_button_or', buttonWidgetStyle, 'Submitting Feedback');
3277
- const spinner = createSpinner();
3278
- button.appendChild(spinner);
3279
- if (this.test?.reqMic || this.test?.reqCamera) {
3280
- void this.userRecorder
3281
- .sendToAPI()
3282
- .then(() => {
3283
- button.removeChild(spinner);
3284
- button.textContent = 'End Session';
3285
- isLoading = false;
3286
- })
3287
- .catch((err) => {
3288
- console.error(err);
3289
- button.removeChild(spinner);
3290
- button.textContent = 'End Session';
3291
- isLoading = false;
3292
- });
3293
- }
3294
- else {
3295
- button.removeChild(spinner);
3296
- button.textContent = 'End Session';
3297
- isLoading = false;
3298
- }
3299
- if (this.taskSection) {
3300
- this.container.removeChild(this.taskSection);
3301
- }
3302
- if (this.descriptionSection) {
3303
- this.container.removeChild(this.descriptionSection);
2345
+ if (this.interval) {
2346
+ clearInterval(this.interval);
2347
+ this.interval = null;
3304
2348
  }
3305
- if (this.stopButton && this.stopButtonContainer) {
3306
- this.container.removeChild(this.stopButtonContainer);
3307
- }
3308
- button.onclick = () => {
3309
- if (isLoading)
3310
- return;
3311
- window.close();
3312
- document.body.removeChild(this.bg);
3313
- };
3314
- section.append(title, description, button);
3315
- this.endSection = section;
3316
- this.container.append(section);
2349
+ this.observer.disconnect();
3317
2350
  }
3318
2351
  }
3319
2352
 
@@ -4156,6 +3189,13 @@ async function parseUseEl(useElement, mode, domParser) {
4156
3189
  return;
4157
3190
  }
4158
3191
  let [url, symbolId] = href.split('#');
3192
+ if (!url && !symbolId) {
3193
+ console.warn('Openreplay: Invalid xlink:href or href found on <use>.');
3194
+ return;
3195
+ }
3196
+ if (iconCache[symbolId]) {
3197
+ return iconCache[symbolId];
3198
+ }
4159
3199
  // happens if svg spritemap is local, fastest case for us
4160
3200
  if (!url && symbolId) {
4161
3201
  const hasHashtag = href.startsWith('#');
@@ -4181,13 +3221,6 @@ async function parseUseEl(useElement, mode, domParser) {
4181
3221
  return;
4182
3222
  }
4183
3223
  }
4184
- if (!url && !symbolId) {
4185
- console.warn('Openreplay: Invalid xlink:href or href found on <use>.');
4186
- return;
4187
- }
4188
- if (iconCache[symbolId]) {
4189
- return iconCache[symbolId];
4190
- }
4191
3224
  let svgDoc;
4192
3225
  if (svgUrlCache[url]) {
4193
3226
  if (svgUrlCache[url] === 1) {
@@ -4287,6 +3320,7 @@ class Observer {
4287
3320
  this.indexes = [];
4288
3321
  this.attributesMap = new Map();
4289
3322
  this.textSet = new Set();
3323
+ this.slotMap = new Map();
4290
3324
  this.disableSprites = false;
4291
3325
  /**
4292
3326
  * this option means that, instead of using link element with href to load css,
@@ -4296,6 +3330,9 @@ class Observer {
4296
3330
  this.inlineRemoteCss = false;
4297
3331
  this.inlinerOptions = undefined;
4298
3332
  this.domParser = new DOMParser();
3333
+ this.throttling = true;
3334
+ this.throttledSetNodeData = throttleWithTrailing((id, parentElement, data) => this.sendNodeData(id, parentElement, data), 30);
3335
+ this.throttling = !Boolean(options.disableThrottling);
4299
3336
  this.disableSprites = Boolean(options.disableSprites);
4300
3337
  this.inlineRemoteCss = Boolean(options.inlineRemoteCss);
4301
3338
  this.inlinerOptions = options.inlinerOptions;
@@ -4476,6 +3513,18 @@ class Observer {
4476
3513
  }
4477
3514
  bindNode(node) {
4478
3515
  const [id, isNew] = this.app.nodes.registerNode(node);
3516
+ if (isElementNode(node) && hasTag(node, 'slot')) {
3517
+ this.app.nodes.attachNodeListener(node, 'slotchange', () => {
3518
+ const sl = node;
3519
+ sl.assignedNodes({ flatten: true }).forEach((n) => {
3520
+ const nid = this.app.nodes.getID(n);
3521
+ if (nid !== undefined) {
3522
+ this.recents.set(nid, RecentsType.Removed);
3523
+ this.commitNode(nid);
3524
+ }
3525
+ });
3526
+ });
3527
+ }
4479
3528
  if (isNew) {
4480
3529
  this.recents.set(id, RecentsType.New);
4481
3530
  }
@@ -4506,6 +3555,9 @@ class Observer {
4506
3555
  }
4507
3556
  unbindTree(node) {
4508
3557
  const id = this.app.nodes.unregisterNode(node);
3558
+ if (id !== undefined) {
3559
+ this.slotMap.delete(id);
3560
+ }
4509
3561
  if (id !== undefined && this.recents.get(id) === RecentsType.Removed) {
4510
3562
  // Sending RemoveNode only for parent to maintain
4511
3563
  this.app.send(RemoveNode(id));
@@ -4534,8 +3586,15 @@ class Observer {
4534
3586
  if (isRootNode(node)) {
4535
3587
  return true;
4536
3588
  }
4537
- // @ts-ignore SALESFORCE
4538
- const parent = node.assignedSlot ? node.assignedSlot : node.parentNode;
3589
+ let slot = node.assignedSlot;
3590
+ let isLightDom = false;
3591
+ if (slot) {
3592
+ // Check if the node is in light DOM (not in shadow DOM)
3593
+ // This is a workaround for the issue with shadow DOM and slots
3594
+ // where the slot is not assigned to the node in shadow DOM.
3595
+ isLightDom = node.getRootNode() instanceof ShadowRoot;
3596
+ }
3597
+ const parent = node.parentNode;
4539
3598
  let parentID;
4540
3599
  // Disable parent check for the upper context HTMLHtmlElement, because it is root there... (before)
4541
3600
  // TODO: get rid of "special" cases (there is an issue with CreateDocument altered behaviour though)
@@ -4547,7 +3606,15 @@ class Observer {
4547
3606
  this.unbindTree(node);
4548
3607
  return false;
4549
3608
  }
4550
- parentID = this.app.nodes.getID(parent);
3609
+ if (isLightDom && slot) {
3610
+ parentID = this.app.nodes.getID(slot);
3611
+ // in light dom, we don't "slot" the node,
3612
+ // but rather use the slot as a parent
3613
+ slot = null;
3614
+ }
3615
+ else {
3616
+ parentID = this.app.nodes.getID(parent);
3617
+ }
4551
3618
  if (parentID === undefined) {
4552
3619
  this.unbindTree(node);
4553
3620
  return false;
@@ -4607,12 +3674,35 @@ class Observer {
4607
3674
  else if (isTextNode(node)) {
4608
3675
  // for text node id != 0, hence parentID !== undefined and parent is Element
4609
3676
  this.app.send(CreateTextNode(id, parentID, index));
4610
- this.sendNodeData(id, parent, node.data);
3677
+ if (this.throttling) {
3678
+ this.throttledSetNodeData(id, parent, node.data);
3679
+ }
3680
+ else {
3681
+ this.sendNodeData(id, parent, node.data);
3682
+ }
3683
+ }
3684
+ if (slot) {
3685
+ const slotID = this.app.nodes.getID(slot);
3686
+ if (slotID !== undefined) {
3687
+ this.slotMap.set(id, slotID);
3688
+ this.app.send(SetNodeSlot(id, slotID));
3689
+ }
4611
3690
  }
4612
3691
  return true;
4613
3692
  }
4614
3693
  if (recentsType === RecentsType.Removed && parentID !== undefined) {
4615
3694
  this.app.send(MoveNode(id, parentID, index));
3695
+ if (slot) {
3696
+ const slotID = this.app.nodes.getID(slot);
3697
+ if (slotID !== undefined && this.slotMap.get(id) !== slotID) {
3698
+ this.slotMap.set(id, slotID);
3699
+ this.app.send(SetNodeSlot(id, slotID));
3700
+ }
3701
+ }
3702
+ else if (this.slotMap.has(id)) {
3703
+ this.slotMap.delete(id);
3704
+ this.app.send(SetNodeSlot(id, 0));
3705
+ }
4616
3706
  }
4617
3707
  const attr = this.attributesMap.get(id);
4618
3708
  if (attr !== undefined) {
@@ -4628,7 +3718,12 @@ class Observer {
4628
3718
  throw 'commitNode: node is not a text';
4629
3719
  }
4630
3720
  // for text node id != 0, hence parent is Element
4631
- this.sendNodeData(id, parent, node.data);
3721
+ if (this.throttling) {
3722
+ this.throttledSetNodeData(id, parent, node.data);
3723
+ }
3724
+ else {
3725
+ this.sendNodeData(id, parent, node.data);
3726
+ }
4632
3727
  }
4633
3728
  return true;
4634
3729
  }
@@ -4671,6 +3766,7 @@ class Observer {
4671
3766
  disconnect() {
4672
3767
  this.observer.disconnect();
4673
3768
  this.clear();
3769
+ this.throttledSetNodeData.clear();
4674
3770
  }
4675
3771
  }
4676
3772
 
@@ -4774,16 +3870,18 @@ class IFrameOffsets {
4774
3870
 
4775
3871
  var InlineCssMode;
4776
3872
  (function (InlineCssMode) {
3873
+ InlineCssMode[InlineCssMode["Unset"] = -1] = "Unset";
4777
3874
  /** default behavior -- will parse and cache the css file on backend */
4778
3875
  InlineCssMode[InlineCssMode["Disabled"] = 0] = "Disabled";
4779
3876
  /** will attempt to record the linked css file as AdoptedStyleSheet object */
4780
3877
  InlineCssMode[InlineCssMode["Inline"] = 1] = "Inline";
4781
- /** will fetch the file, then simulated AdoptedStyleSheets behavior programmaticaly for the replay */
3878
+ /** will fetch the file, then simulate AdoptedStyleSheets behavior programmaticaly for the replay */
4782
3879
  InlineCssMode[InlineCssMode["InlineFetched"] = 2] = "InlineFetched";
4783
3880
  /** will fetch the file, then save it as plain css inside <style> node */
4784
3881
  InlineCssMode[InlineCssMode["PlainFetched"] = 3] = "PlainFetched";
4785
3882
  })(InlineCssMode || (InlineCssMode = {}));
4786
- function getInlineOptions(mode) {
3883
+ const localhostStylesDoc = 'https://docs.openreplay.com/en/troubleshooting/localhost/';
3884
+ function getInlineOptions(mode, logger) {
4787
3885
  switch (mode) {
4788
3886
  case InlineCssMode.Inline:
4789
3887
  return {
@@ -4809,6 +3907,27 @@ function getInlineOptions(mode) {
4809
3907
  forcePlain: true,
4810
3908
  },
4811
3909
  };
3910
+ case InlineCssMode.Unset:
3911
+ const isLocalhost = /^https?:\/\/(localhost|127\.0\.0\.1)(:\d+)?\/?/.test(window.location.href);
3912
+ if (isLocalhost) {
3913
+ logger(`Enabling InlineCssMode by default on localhost to preserve css styles, refer to ${localhostStylesDoc} for details, set InlineCssMode to 0 to skip this behavior`);
3914
+ return {
3915
+ inlineRemoteCss: true,
3916
+ inlinerOptions: {
3917
+ forceFetch: false,
3918
+ forcePlain: false,
3919
+ },
3920
+ };
3921
+ }
3922
+ else {
3923
+ return {
3924
+ inlineRemoteCss: false,
3925
+ inlinerOptions: {
3926
+ forceFetch: false,
3927
+ forcePlain: false,
3928
+ },
3929
+ };
3930
+ }
4812
3931
  case InlineCssMode.Disabled:
4813
3932
  default:
4814
3933
  return {
@@ -4830,7 +3949,8 @@ class TopObserver extends Observer {
4830
3949
  }, params.options);
4831
3950
  const observerOptions = {
4832
3951
  disableSprites: opts.disableSprites,
4833
- ...getInlineOptions(opts.inlineCss)
3952
+ disableThrottling: opts.disableThrottling,
3953
+ ...getInlineOptions(opts.inlineCss, console.warn),
4834
3954
  };
4835
3955
  super(params.app, true, observerOptions);
4836
3956
  this.iframeOffsets = new IFrameOffsets();
@@ -5238,9 +4358,8 @@ class Ticker {
5238
4358
  * this value is injected during build time via rollup
5239
4359
  * */
5240
4360
  // @ts-ignore
5241
- 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 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 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 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(\"q_end\"===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,a=null,u=h.NotActive;function o(t){a&&a.finaliseBatch(t)}function c(){return new Promise((t=>{u=h.Stopping,null!==p&&(clearInterval(p),p=null),a&&(a.clean(),a=null),r&&(r.clean(),setTimeout((()=>{r=null}),20)),setTimeout((()=>{u=h.NotActive,t(null)}),100)}))}function g(){[h.Stopped,h.Stopping].includes(u)||(postMessage(\"a_stop\"),c().then((()=>{postMessage(\"a_start\")})))}let l,p=null;self.onmessage=({data:s})=>{if(\"stop\"===s)return o(),void c().then((()=>{u=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?(u=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),a=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)),u=h.Active):\"auth\"===s.type?r?a?(r.authorise(s.token),void(s.beaconSizeLimit&&a.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(a){const t=a;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";
4361
+ 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";
5242
4362
  const CANCELED = 'canceled';
5243
- const uxtStorageKey = 'or_uxt_active';
5244
4363
  const bufferStorageKey = 'or_buffer_1';
5245
4364
  const UnsuccessfulStart = (reason) => ({ reason, success: false });
5246
4365
  const SuccessfulStart = (body) => ({ ...body, success: true });
@@ -5292,7 +4411,7 @@ class App {
5292
4411
  this.stopCallbacks = [];
5293
4412
  this.commitCallbacks = [];
5294
4413
  this.activityState = ActivityState.NotActive;
5295
- this.version = '16.4.10'; // TODO: version compatability check inside each plugin.
4414
+ this.version = '17.0.0'; // TODO: version compatability check inside each plugin.
5296
4415
  this.socketMode = false;
5297
4416
  this.compressionThreshold = 24 * 1000;
5298
4417
  this.bc = null;
@@ -5303,7 +4422,6 @@ class App {
5303
4422
  this.pageFrames = [];
5304
4423
  this.frameOderNumber = 0;
5305
4424
  this.frameLevel = 0;
5306
- this.features = {};
5307
4425
  this.emptyBatchCounter = 0;
5308
4426
  /** used by child iframes for crossdomain only */
5309
4427
  this.parentActive = false;
@@ -5395,13 +4513,6 @@ class App {
5395
4513
  }
5396
4514
  };
5397
4515
  void signalId();
5398
- if (this.active()) {
5399
- // @ts-ignore
5400
- event.source?.postMessage({ line: proto.startIframe }, '*');
5401
- }
5402
- else {
5403
- this.addCommand(proto.startIframe);
5404
- }
5405
4516
  }
5406
4517
  /**
5407
4518
  * proxying messages from iframe to main body, so they can be in one batch (same indexes, etc)
@@ -5409,7 +4520,8 @@ class App {
5409
4520
  * */
5410
4521
  if (data.line === proto.iframeBatch) {
5411
4522
  const msgBatch = data.messages;
5412
- const mappedMessages = msgBatch.map((msg) => {
4523
+ const mappedMessages = [];
4524
+ msgBatch.forEach((msg) => {
5413
4525
  if (msg[0] === 20 /* MType.MouseMove */) {
5414
4526
  let fixedMessage = msg;
5415
4527
  this.pageFrames.forEach((frame) => {
@@ -5419,7 +4531,7 @@ class App {
5419
4531
  fixedMessage = [type, x + left, y + top];
5420
4532
  }
5421
4533
  });
5422
- return fixedMessage;
4534
+ mappedMessages.push(fixedMessage);
5423
4535
  }
5424
4536
  if (msg[0] === 68 /* MType.MouseClick */) {
5425
4537
  let fixedMessage = msg;
@@ -5445,9 +4557,11 @@ class App {
5445
4557
  ];
5446
4558
  }
5447
4559
  });
5448
- return fixedMessage;
4560
+ mappedMessages.push(fixedMessage);
4561
+ }
4562
+ if (![28 /* MType.UserID */, 29 /* MType.UserAnonymousID */, 30 /* MType.Metadata */].includes(msg[0])) {
4563
+ mappedMessages.push(msg);
5449
4564
  }
5450
- return msg;
5451
4565
  });
5452
4566
  this.messages.push(...mappedMessages);
5453
4567
  }
@@ -5544,211 +4658,50 @@ class App {
5544
4658
  this.conditionsManager?.processMessage(message);
5545
4659
  }
5546
4660
  else {
5547
- this.messages.push(message);
5548
- }
5549
- // TODO: commit on start if there were `urgent` sends;
5550
- // Clarify where urgent can be used for;
5551
- // Clarify workflow for each type of message in case it was sent before start
5552
- // (like Fetch before start; maybe add an option "preCapture: boolean" or sth alike)
5553
- // Careful: `this.delay` is equal to zero before start so all Timestamp-s will have to be updated on start
5554
- if (this.activityState === ActivityState.Active && urgent) {
5555
- this.commit();
5556
- }
5557
- };
5558
- this.coldStartCommitN = 0;
5559
- this.delay = 0;
5560
- this.attachStartCallback = (cb, useSafe = false) => {
5561
- if (useSafe) {
5562
- cb = this.safe(cb);
5563
- }
5564
- this.startCallbacks.push(cb);
5565
- };
5566
- this.attachStopCallback = (cb, useSafe = false) => {
5567
- if (useSafe) {
5568
- cb = this.safe(cb);
5569
- }
5570
- this.stopCallbacks.push(cb);
5571
- };
5572
- this.attachEventListener = (target, type, listener, useSafe = true, useCapture = true) => {
5573
- if (useSafe) {
5574
- listener = this.safe(listener);
5575
- }
5576
- const createListener = () => target
5577
- ? createEventListener(target, type, listener, useCapture, this.options.forceNgOff)
5578
- : null;
5579
- const deleteListener = () => target
5580
- ? deleteEventListener(target, type, listener, useCapture, this.options.forceNgOff)
5581
- : null;
5582
- this.attachStartCallback(createListener, useSafe);
5583
- this.attachStopCallback(deleteListener, useSafe);
5584
- };
5585
- this.coldInterval = null;
5586
- this.orderNumber = 0;
5587
- this.coldStartTs = 0;
5588
- this.singleBuffer = false;
5589
- /**
5590
- * start buffering messages without starting the actual session, which gives
5591
- * user 30 seconds to "activate" and record session by calling `start()` on conditional trigger,
5592
- * and we will then send buffered batch, so it won't get lost
5593
- * */
5594
- this.coldStart = async (startOpts = {}, conditional) => {
5595
- this.singleBuffer = false;
5596
- const second = 1000;
5597
- const isNewSession = this.checkSessionToken(startOpts.forceNew);
5598
- if (conditional) {
5599
- await this.setupConditionalStart(startOpts);
5600
- }
5601
- const cycle = () => {
5602
- this.orderNumber += 1;
5603
- adjustTimeOrigin();
5604
- this.coldStartTs = now();
5605
- if (this.orderNumber % 2 === 0) {
5606
- this.bufferedMessages1.length = 0;
5607
- this.bufferedMessages1.push(Timestamp(this.timestamp()));
5608
- this.bufferedMessages1.push(TabData(this.session.getTabId()));
5609
- }
5610
- else {
5611
- this.bufferedMessages2.length = 0;
5612
- this.bufferedMessages2.push(Timestamp(this.timestamp()));
5613
- this.bufferedMessages2.push(TabData(this.session.getTabId()));
5614
- }
5615
- this.stop(false);
5616
- this.activityState = ActivityState.ColdStart;
5617
- if (startOpts.sessionHash) {
5618
- this.session.applySessionHash(startOpts.sessionHash);
5619
- }
5620
- if (startOpts.forceNew) {
5621
- this.session.reset();
5622
- }
5623
- this.session.assign({
5624
- userID: startOpts.userID,
5625
- metadata: startOpts.metadata,
5626
- });
5627
- if (!isNewSession) {
5628
- this.debug.log('continuing session on new tab', this.session.getTabId());
5629
- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
5630
- this.send(TabChange(this.session.getTabId()));
5631
- }
5632
- this.observer.observe();
5633
- this.ticker.start();
5634
- };
5635
- this.coldInterval = setInterval(() => {
5636
- cycle();
5637
- }, 30 * second);
5638
- cycle();
5639
- };
5640
- this.setupConditionalStart = async (startOpts) => {
5641
- this.conditionsManager = new ConditionsManager(this, startOpts);
5642
- const r = await fetch(this.options.ingestPoint + '/v1/web/start', {
5643
- method: 'POST',
5644
- headers: {
5645
- 'Content-Type': 'application/json',
5646
- },
5647
- body: JSON.stringify({
5648
- ...this.getTrackerInfo(),
5649
- timestamp: now(),
5650
- doNotRecord: true,
5651
- bufferDiff: 0,
5652
- userID: this.session.getInfo().userID,
5653
- token: undefined,
5654
- deviceMemory,
5655
- jsHeapSizeLimit,
5656
- timezone: getTimezone(),
5657
- width: window.screen.width,
5658
- height: window.screen.height,
5659
- }),
5660
- });
5661
- const {
5662
- // this token is needed to fetch conditions and flags,
5663
- // but it can't be used to record a session
5664
- token, userBrowser, userCity, userCountry, userDevice, userOS, userState, projectID, features, } = await r.json();
5665
- this.features = features ? features : this.features;
5666
- this.session.assign({ projectID });
5667
- this.session.setUserInfo({
5668
- userBrowser,
5669
- userCity,
5670
- userCountry,
5671
- userDevice,
5672
- userOS,
5673
- userState,
5674
- });
5675
- const onStartInfo = { sessionToken: token, userUUID: '', sessionID: '' };
5676
- this.startCallbacks.forEach((cb) => cb(onStartInfo));
5677
- await this.conditionsManager?.fetchConditions(projectID, token);
5678
- if (this.features['feature-flags']) {
5679
- await this.featureFlags.reloadFlags(token);
5680
- this.conditionsManager?.processFlags(this.featureFlags.flags);
5681
- }
5682
- await this.tagWatcher.fetchTags(this.options.ingestPoint, token);
5683
- };
5684
- this.onSessionSent = () => {
5685
- return;
5686
- };
5687
- /**
5688
- * Starts offline session recording
5689
- * @param {Object} startOpts - options for session start, same as .start()
5690
- * @param {Function} onSessionSent - callback that will be called once session is fully sent
5691
- * */
5692
- this.offlineRecording = (startOpts = {}, onSessionSent) => {
5693
- this.onSessionSent = onSessionSent;
5694
- this.singleBuffer = true;
5695
- const isNewSession = this.checkSessionToken(startOpts.forceNew);
5696
- adjustTimeOrigin();
5697
- this.coldStartTs = now();
5698
- const saverBuffer = this.localStorage.getItem(bufferStorageKey);
5699
- if (saverBuffer) {
5700
- const data = JSON.parse(saverBuffer);
5701
- this.bufferedMessages1 = Array.isArray(data) ? data : this.bufferedMessages1;
5702
- this.localStorage.removeItem(bufferStorageKey);
5703
- }
5704
- this.bufferedMessages1.push(Timestamp(this.timestamp()));
5705
- this.bufferedMessages1.push(TabData(this.session.getTabId()));
5706
- this.activityState = ActivityState.ColdStart;
5707
- if (startOpts.sessionHash) {
5708
- this.session.applySessionHash(startOpts.sessionHash);
4661
+ this.messages.push(message);
5709
4662
  }
5710
- if (startOpts.forceNew) {
5711
- this.session.reset();
4663
+ // TODO: commit on start if there were `urgent` sends;
4664
+ // Clarify where urgent can be used for;
4665
+ // Clarify workflow for each type of message in case it was sent before start
4666
+ // (like Fetch before start; maybe add an option "preCapture: boolean" or sth alike)
4667
+ // Careful: `this.delay` is equal to zero before start so all Timestamp-s will have to be updated on start
4668
+ if (this.activityState === ActivityState.Active && urgent) {
4669
+ this.commit();
5712
4670
  }
5713
- this.session.assign({
5714
- userID: startOpts.userID,
5715
- metadata: startOpts.metadata,
5716
- });
5717
- const onStartInfo = { sessionToken: '', userUUID: '', sessionID: '' };
5718
- this.startCallbacks.forEach((cb) => cb(onStartInfo));
5719
- if (!isNewSession) {
5720
- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
5721
- this.send(TabChange(this.session.getTabId()));
4671
+ };
4672
+ this.coldStartCommitN = 0;
4673
+ this.delay = 0;
4674
+ this.attachStartCallback = (cb, useSafe = false) => {
4675
+ if (useSafe) {
4676
+ cb = this.safe(cb);
5722
4677
  }
5723
- this.observer.observe();
5724
- this.ticker.start();
5725
- return {
5726
- saveBuffer: this.saveBuffer,
5727
- getBuffer: this.getBuffer,
5728
- setBuffer: this.setBuffer,
5729
- };
4678
+ this.startCallbacks.push(cb);
5730
4679
  };
5731
- /**
5732
- * Saves the captured messages in localStorage (or whatever is used in its place)
5733
- *
5734
- * Then, when this.offlineRecording is called, it will preload this messages and clear the storage item
5735
- *
5736
- * Keeping the size of local storage reasonable is up to the end users of this library
5737
- * */
5738
- this.saveBuffer = () => {
5739
- this.localStorage.setItem(bufferStorageKey, JSON.stringify(this.bufferedMessages1));
4680
+ this.attachStopCallback = (cb, useSafe = false) => {
4681
+ if (useSafe) {
4682
+ cb = this.safe(cb);
4683
+ }
4684
+ this.stopCallbacks.push(cb);
5740
4685
  };
5741
- /**
5742
- * @returns buffer with stored messages for offline recording
5743
- * */
5744
- this.getBuffer = () => {
5745
- return this.bufferedMessages1;
4686
+ this.attachEventListener = (target, type, listener, useSafe = true, useCapture = true) => {
4687
+ if (useSafe) {
4688
+ listener = this.safe(listener);
4689
+ }
4690
+ const createListener = () => target
4691
+ ? createEventListener(target, type, listener, useCapture, this.options.forceNgOff)
4692
+ : null;
4693
+ const deleteListener = () => target
4694
+ ? deleteEventListener(target, type, listener, useCapture, this.options.forceNgOff)
4695
+ : null;
4696
+ this.attachStartCallback(createListener, useSafe);
4697
+ this.attachStopCallback(deleteListener, useSafe);
5746
4698
  };
5747
- /**
5748
- * Used to set a buffer with messages array
5749
- * */
5750
- this.setBuffer = (buffer) => {
5751
- this.bufferedMessages1 = buffer;
4699
+ this.coldInterval = null;
4700
+ this.orderNumber = 0;
4701
+ this.coldStartTs = 0;
4702
+ this.singleBuffer = false;
4703
+ this.onSessionSent = () => {
4704
+ return;
5752
4705
  };
5753
4706
  this.prevOpts = {};
5754
4707
  this.restartCanvasTracking = () => {
@@ -5779,7 +4732,6 @@ class App {
5779
4732
  });
5780
4733
  });
5781
4734
  };
5782
- this.onUxtCb = [];
5783
4735
  this.contextId = Math.random().toString(36).slice(2);
5784
4736
  this.projectKey = projectKey;
5785
4737
  this.networkOptions = options.network;
@@ -5814,8 +4766,9 @@ class App {
5814
4766
  useAnimationFrame: false,
5815
4767
  },
5816
4768
  forceNgOff: false,
5817
- inlineCss: 0,
4769
+ inlineCss: InlineCssMode.Unset,
5818
4770
  disableSprites: false,
4771
+ disableThrottling: false,
5819
4772
  };
5820
4773
  this.options = simpleMerge(defaultOptions, options);
5821
4774
  if (!this.insideIframe &&
@@ -5846,7 +4799,6 @@ class App {
5846
4799
  app: this,
5847
4800
  isDictDisabled: Boolean(this.options.disableStringDict || this.options.crossdomain?.enabled),
5848
4801
  });
5849
- this.featureFlags = new FeatureFlags(this);
5850
4802
  this.tagWatcher = new TagWatcher({
5851
4803
  sessionStorage: this.sessionStorage,
5852
4804
  errLog: this.debug.error,
@@ -6258,6 +5210,162 @@ class App {
6258
5210
  const sessionToken = this.session.getSessionToken(this.projectKey);
6259
5211
  return needNewSessionID || !sessionToken;
6260
5212
  }
5213
+ /**
5214
+ * start buffering messages without starting the actual session, which gives
5215
+ * user 30 seconds to "activate" and record session by calling `start()` on conditional trigger,
5216
+ * and we will then send buffered batch, so it won't get lost
5217
+ * */
5218
+ async coldStart(startOpts = {}, conditional) {
5219
+ this.singleBuffer = false;
5220
+ const second = 1000;
5221
+ const isNewSession = this.checkSessionToken(startOpts.forceNew);
5222
+ if (conditional) {
5223
+ await this.setupConditionalStart(startOpts);
5224
+ }
5225
+ const cycle = () => {
5226
+ this.orderNumber += 1;
5227
+ adjustTimeOrigin();
5228
+ this.coldStartTs = now();
5229
+ if (this.orderNumber % 2 === 0) {
5230
+ this.bufferedMessages1.length = 0;
5231
+ this.bufferedMessages1.push(Timestamp(this.timestamp()));
5232
+ this.bufferedMessages1.push(TabData(this.session.getTabId()));
5233
+ }
5234
+ else {
5235
+ this.bufferedMessages2.length = 0;
5236
+ this.bufferedMessages2.push(Timestamp(this.timestamp()));
5237
+ this.bufferedMessages2.push(TabData(this.session.getTabId()));
5238
+ }
5239
+ this.stop(false);
5240
+ this.activityState = ActivityState.ColdStart;
5241
+ if (startOpts.sessionHash) {
5242
+ this.session.applySessionHash(startOpts.sessionHash);
5243
+ }
5244
+ if (startOpts.forceNew) {
5245
+ this.session.reset();
5246
+ }
5247
+ this.session.assign({
5248
+ userID: startOpts.userID,
5249
+ metadata: startOpts.metadata,
5250
+ });
5251
+ if (!isNewSession) {
5252
+ this.debug.log('continuing session on new tab', this.session.getTabId());
5253
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
5254
+ this.send(TabChange(this.session.getTabId()));
5255
+ }
5256
+ this.observer.observe();
5257
+ this.ticker.start();
5258
+ };
5259
+ this.coldInterval = setInterval(() => {
5260
+ cycle();
5261
+ }, 30 * second);
5262
+ cycle();
5263
+ }
5264
+ async setupConditionalStart(startOpts) {
5265
+ this.conditionsManager = new ConditionsManager(this, startOpts);
5266
+ const r = await fetch(this.options.ingestPoint + '/v1/web/start', {
5267
+ method: 'POST',
5268
+ headers: {
5269
+ 'Content-Type': 'application/json',
5270
+ },
5271
+ body: JSON.stringify({
5272
+ ...this.getTrackerInfo(),
5273
+ timestamp: now(),
5274
+ doNotRecord: true,
5275
+ bufferDiff: 0,
5276
+ userID: this.session.getInfo().userID,
5277
+ token: undefined,
5278
+ deviceMemory,
5279
+ jsHeapSizeLimit,
5280
+ timezone: getTimezone(),
5281
+ width: window.screen.width,
5282
+ height: window.screen.height,
5283
+ }),
5284
+ });
5285
+ const {
5286
+ // this token is needed to fetch conditions and flags,
5287
+ // but it can't be used to record a session
5288
+ token, userBrowser, userCity, userCountry, userDevice, userOS, userState, projectID, } = await r.json();
5289
+ this.session.assign({ projectID });
5290
+ this.session.setUserInfo({
5291
+ userBrowser,
5292
+ userCity,
5293
+ userCountry,
5294
+ userDevice,
5295
+ userOS,
5296
+ userState,
5297
+ });
5298
+ const onStartInfo = { sessionToken: token, userUUID: '', sessionID: '' };
5299
+ this.startCallbacks.forEach((cb) => cb(onStartInfo));
5300
+ await this.conditionsManager?.fetchConditions(projectID, token);
5301
+ await this.tagWatcher.fetchTags(this.options.ingestPoint, token);
5302
+ }
5303
+ /**
5304
+ * Starts offline session recording
5305
+ * @param {Object} startOpts - options for session start, same as .start()
5306
+ * @param {Function} onSessionSent - callback that will be called once session is fully sent
5307
+ * */
5308
+ offlineRecording(startOpts = {}, onSessionSent) {
5309
+ this.onSessionSent = onSessionSent;
5310
+ this.singleBuffer = true;
5311
+ const isNewSession = this.checkSessionToken(startOpts.forceNew);
5312
+ adjustTimeOrigin();
5313
+ this.coldStartTs = now();
5314
+ const saverBuffer = this.localStorage.getItem(bufferStorageKey);
5315
+ if (saverBuffer) {
5316
+ const data = JSON.parse(saverBuffer);
5317
+ this.bufferedMessages1 = Array.isArray(data) ? data : this.bufferedMessages1;
5318
+ this.localStorage.removeItem(bufferStorageKey);
5319
+ }
5320
+ this.bufferedMessages1.push(Timestamp(this.timestamp()));
5321
+ this.bufferedMessages1.push(TabData(this.session.getTabId()));
5322
+ this.activityState = ActivityState.ColdStart;
5323
+ if (startOpts.sessionHash) {
5324
+ this.session.applySessionHash(startOpts.sessionHash);
5325
+ }
5326
+ if (startOpts.forceNew) {
5327
+ this.session.reset();
5328
+ }
5329
+ this.session.assign({
5330
+ userID: startOpts.userID,
5331
+ metadata: startOpts.metadata,
5332
+ });
5333
+ const onStartInfo = { sessionToken: '', userUUID: '', sessionID: '' };
5334
+ this.startCallbacks.forEach((cb) => cb(onStartInfo));
5335
+ if (!isNewSession) {
5336
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
5337
+ this.send(TabChange(this.session.getTabId()));
5338
+ }
5339
+ this.observer.observe();
5340
+ this.ticker.start();
5341
+ return {
5342
+ saveBuffer: this.saveBuffer,
5343
+ getBuffer: this.getBuffer,
5344
+ setBuffer: this.setBuffer,
5345
+ };
5346
+ }
5347
+ /**
5348
+ * Saves the captured messages in localStorage (or whatever is used in its place)
5349
+ *
5350
+ * Then, when this.offlineRecording is called, it will preload this messages and clear the storage item
5351
+ *
5352
+ * Keeping the size of local storage reasonable is up to the end users of this library
5353
+ * */
5354
+ saveBuffer() {
5355
+ this.localStorage.setItem(bufferStorageKey, JSON.stringify(this.bufferedMessages1));
5356
+ }
5357
+ /**
5358
+ * @returns buffer with stored messages for offline recording
5359
+ * */
5360
+ getBuffer() {
5361
+ return this.bufferedMessages1;
5362
+ }
5363
+ /**
5364
+ * Used to set a buffer with messages array
5365
+ * */
5366
+ setBuffer(buffer) {
5367
+ this.bufferedMessages1 = buffer;
5368
+ }
6261
5369
  /**
6262
5370
  * Uploads the stored session buffer to backend
6263
5371
  * @returns promise that resolves once messages are loaded, it has to be awaited
@@ -6313,7 +5421,7 @@ class App {
6313
5421
  while (this.bufferedMessages1.length > 0) {
6314
5422
  await this.flushBuffer(this.bufferedMessages1);
6315
5423
  }
6316
- this.postToWorker([['q_end']]);
5424
+ this.postToWorker([[-1]]);
6317
5425
  this.clearBuffers();
6318
5426
  }
6319
5427
  async _start(startOpts = {}, resetByWorker = false, conditionName) {
@@ -6401,8 +5509,7 @@ class App {
6401
5509
  delay, // derived from token
6402
5510
  sessionID, // derived from token
6403
5511
  startTimestamp, // real startTS (server time), derived from sessionID
6404
- userBrowser, userCity, userCountry, userDevice, userOS, userState, canvasEnabled, canvasQuality, canvasFPS, assistOnly: socketOnly, features, } = await r.json();
6405
- this.features = features ? features : this.features;
5512
+ userBrowser, userCity, userCountry, userDevice, userOS, userState, canvasEnabled, canvasQuality, canvasFPS, assistOnly: socketOnly, } = await r.json();
6406
5513
  if (typeof token !== 'string' ||
6407
5514
  typeof userUUID !== 'string' ||
6408
5515
  (typeof startTimestamp !== 'number' && typeof startTimestamp !== 'undefined') ||
@@ -6455,14 +5562,6 @@ class App {
6455
5562
  if (startOpts.startCallback) {
6456
5563
  startOpts.startCallback(SuccessfulStart(onStartInfo));
6457
5564
  }
6458
- if (this.features['feature-flags']) {
6459
- try {
6460
- void this.featureFlags.reloadFlags();
6461
- }
6462
- catch (e) {
6463
- this.debug.log("Error getting feature flags", e);
6464
- }
6465
- }
6466
5565
  await this.tagWatcher.fetchTags(this.options.ingestPoint, token);
6467
5566
  this.activityState = ActivityState.Active;
6468
5567
  if (this.options.crossdomain?.enabled) {
@@ -6491,47 +5590,14 @@ class App {
6491
5590
  this.commit();
6492
5591
  /** --------------- COLD START BUFFER ------------------*/
6493
5592
  }
5593
+ if (this.insideIframe && this.rootId) {
5594
+ this.observer.crossdomainObserve(this.rootId, this.frameOderNumber, this.frameLevel);
5595
+ }
6494
5596
  else {
6495
- if (this.insideIframe && this.rootId) {
6496
- this.observer.crossdomainObserve(this.rootId, this.frameOderNumber, this.frameLevel);
6497
- }
6498
- else {
6499
- this.observer.observe();
6500
- }
6501
- this.ticker.start();
5597
+ this.observer.observe();
6502
5598
  }
5599
+ this.ticker.start();
6503
5600
  this.canvasRecorder?.startTracking();
6504
- if (this.features['usability-test'] && !this.insideIframe) {
6505
- this.uxtManager = this.uxtManager
6506
- ? this.uxtManager
6507
- : new UserTestManager(this, uxtStorageKey);
6508
- let uxtId;
6509
- const savedUxtTag = this.localStorage.getItem(uxtStorageKey);
6510
- if (savedUxtTag) {
6511
- uxtId = parseInt(savedUxtTag, 10);
6512
- }
6513
- if (location?.search) {
6514
- const query = new URLSearchParams(location.search);
6515
- if (query.has('oruxt')) {
6516
- const qId = query.get('oruxt');
6517
- uxtId = qId ? parseInt(qId, 10) : undefined;
6518
- }
6519
- }
6520
- if (uxtId) {
6521
- if (!this.uxtManager.isActive) {
6522
- // eslint-disable-next-line
6523
- this.uxtManager.getTest(uxtId, token, Boolean(savedUxtTag)).then((id) => {
6524
- if (id) {
6525
- this.onUxtCb.forEach((cb) => cb(id));
6526
- }
6527
- });
6528
- }
6529
- else {
6530
- // @ts-ignore
6531
- this.onUxtCb.forEach((cb) => cb(uxtId));
6532
- }
6533
- }
6534
- }
6535
5601
  return SuccessfulStart(onStartInfo);
6536
5602
  }
6537
5603
  catch (reason) {
@@ -6552,13 +5618,6 @@ class App {
6552
5618
  return UnsuccessfulStart(errorMessage);
6553
5619
  }
6554
5620
  }
6555
- addOnUxtCb(cb) {
6556
- // @ts-ignore
6557
- this.onUxtCb.push(cb);
6558
- }
6559
- getUxtId() {
6560
- return this.uxtManager?.getTestId();
6561
- }
6562
5621
  async waitStart() {
6563
5622
  return new Promise((resolve) => {
6564
5623
  const int = setInterval(() => {
@@ -6679,9 +5738,16 @@ function Connection (app) {
6679
5738
  if (connection === undefined) {
6680
5739
  return;
6681
5740
  }
6682
- const sendConnectionInformation = () => app.send(ConnectionInformation(Math.round(connection.downlink * 1000), connection.type || 'unknown'));
6683
- sendConnectionInformation();
6684
- connection.addEventListener('change', sendConnectionInformation);
5741
+ const sendConnectionInformation = () => {
5742
+ app.send(ConnectionInformation(Math.round(connection.downlink * 1000), connection.effectiveType || 'unknown'));
5743
+ };
5744
+ app.attachStartCallback(() => {
5745
+ sendConnectionInformation();
5746
+ connection.addEventListener('change', sendConnectionInformation);
5747
+ });
5748
+ app.attachStopCallback(() => {
5749
+ connection.removeEventListener('change', sendConnectionInformation);
5750
+ });
6685
5751
  }
6686
5752
 
6687
5753
  const printError = IN_BROWSER && 'InstallTrigger' in window // detect Firefox
@@ -7102,7 +6168,7 @@ function Img (app) {
7102
6168
  const sendImgError = app.safe(function (img) {
7103
6169
  const resolvedSrc = resolveURL(img.src || ''); // Src type is null sometimes. - is it true?
7104
6170
  if (isURL(resolvedSrc)) {
7105
- app.send(ResourceTiming(app.timestamp(), 0, 0, 0, 0, 0, resolvedSrc, 'img', 0, false));
6171
+ app.send(ResourceTiming(app.timestamp(), 0, 0, 0, 0, 0, resolvedSrc, 'img', 0, false, 0, 0, 0, 0, 0, 0, 0));
7106
6172
  }
7107
6173
  });
7108
6174
  const sendImgAttrs = app.safe(function (img) {
@@ -7706,18 +6772,38 @@ function Timing (app, opts) {
7706
6772
  if (shouldSkip) {
7707
6773
  return;
7708
6774
  }
6775
+ // will probably require custom header added to responses for tracked requests:
6776
+ // Timing-Allow-Origin: *
6777
+ // https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Timing-Allow-Origin
6778
+ let stalled = 0;
6779
+ if (entry.connectEnd && entry.connectEnd > entry.domainLookupEnd) {
6780
+ // Usual case stalled is time between connection establishment and request start
6781
+ stalled = Math.max(0, entry.requestStart - entry.connectEnd);
6782
+ }
6783
+ else {
6784
+ // Connection reuse case - stalled is time between domain lookup and request start
6785
+ stalled = Math.max(0, entry.requestStart - entry.domainLookupEnd);
6786
+ }
6787
+ const timings = {
6788
+ queueing: entry.requestStart - entry.fetchStart,
6789
+ dnsLookup: entry.domainLookupEnd - entry.domainLookupStart,
6790
+ initialConnection: entry.connectEnd - entry.connectStart,
6791
+ ssl: entry.secureConnectionStart > 0 ? entry.connectEnd - entry.secureConnectionStart : 0,
6792
+ ttfb: entry.responseStart - entry.requestStart,
6793
+ contentDownload: entry.responseEnd - entry.responseStart,
6794
+ total: entry.duration ?? entry.responseEnd - entry.startTime,
6795
+ stalled,
6796
+ };
7709
6797
  const entryName = options.resourceNameSanitizer
7710
6798
  ? options.resourceNameSanitizer(entry.name)
7711
6799
  : entry.name;
7712
- const cached =
7713
- // @ts-ignore
7714
- (entry.responseStatus && entry.responseStatus === 304) ||
6800
+ const cached = (entry.responseStatus && entry.responseStatus === 304) ||
7715
6801
  // @ts-ignore
7716
6802
  (entry.deliveryType && entry.deliveryType === 'cache') ||
7717
6803
  (entry.transferSize === 0 && entry.decodedBodySize > 0);
7718
6804
  const requestFailed = entry.responseStatus && entry.responseStatus >= 400;
7719
6805
  const decodedBodySize = requestFailed ? -111 : entry.decodedBodySize || 0;
7720
- app.send(ResourceTiming(entry.startTime + getTimeOrigin(), entry.duration, entry.responseStart && entry.startTime ? entry.responseStart - entry.startTime : 0, entry.transferSize > entry.encodedBodySize ? entry.transferSize - entry.encodedBodySize : 0, entry.encodedBodySize || 0, decodedBodySize, app.sanitizer.privateMode ? entry.name.replaceAll(/./g, '*') : entryName, entry.initiatorType, entry.transferSize, cached));
6806
+ app.send(ResourceTiming(entry.startTime + getTimeOrigin(), entry.duration, timings.ttfb, entry.transferSize > entry.encodedBodySize ? entry.transferSize - entry.encodedBodySize : 0, entry.encodedBodySize || 0, decodedBodySize, app.sanitizer.privateMode ? entry.name.replaceAll(/./g, '*') : entryName, entry.initiatorType, entry.transferSize, cached, timings.queueing, timings.dnsLookup, timings.initialConnection, timings.ssl, timings.contentDownload, timings.total, timings.stalled));
7721
6807
  }
7722
6808
  const observer = new PerformanceObserver((list) => list.getEntries().forEach(resourceTiming));
7723
6809
  function onVitalsSignal(msg) {
@@ -9664,8 +8750,86 @@ function Tabs (app) {
9664
8750
  app.attachEventListener(window, 'focus', changeTab, false, false);
9665
8751
  }
9666
8752
 
8753
+ function LongAnimationTask (app, opts) {
8754
+ if (!opts.longTasks || !('PerformanceObserver' in window)) {
8755
+ return;
8756
+ }
8757
+ const onEntry = (entry) => {
8758
+ app.send(LongAnimationTask$1(entry.name, entry.duration, entry.blockingDuration, entry.firstUIEventTimestamp, entry.startTime, JSON.stringify(entry.scripts ?? [])));
8759
+ };
8760
+ const observer = new PerformanceObserver((entryList) => {
8761
+ entryList.getEntries().forEach((entry) => {
8762
+ if (entry.entryType === 'long-animation-frame') {
8763
+ onEntry(entry);
8764
+ }
8765
+ });
8766
+ });
8767
+ app.attachStartCallback(() => {
8768
+ performance.getEntriesByType('long-animation-frame').forEach((lat) => {
8769
+ onEntry(lat);
8770
+ });
8771
+ observer.observe({
8772
+ entryTypes: ['long-animation-frame'],
8773
+ });
8774
+ });
8775
+ app.attachStopCallback(() => {
8776
+ observer.disconnect();
8777
+ });
8778
+ }
8779
+
8780
+ const toIgnore = ["composite", "computedOffset", "easing", "offset"];
8781
+ function webAnimations(app, options = {}) {
8782
+ const { allElements = false } = options;
8783
+ let listening = new WeakSet();
8784
+ let handled = new WeakSet();
8785
+ function wire(anim, el, nodeId) {
8786
+ if (handled.has(anim))
8787
+ return;
8788
+ handled.add(anim);
8789
+ anim.addEventListener('finish', () => {
8790
+ const lastKF = anim.effect.getKeyframes().at(-1);
8791
+ const computedStyle = getComputedStyle(el);
8792
+ const keys = Object.keys(lastKF).filter((p) => !toIgnore.includes(p));
8793
+ // @ts-ignore
8794
+ const finalStyle = {};
8795
+ keys.forEach((key) => {
8796
+ finalStyle[key] = computedStyle[key];
8797
+ });
8798
+ app.send(NodeAnimationResult(nodeId, JSON.stringify(finalStyle)));
8799
+ }, { once: true });
8800
+ }
8801
+ function scanElement(el, nodeId) {
8802
+ el.getAnimations({ subtree: false }).forEach((anim) => wire(anim, el, nodeId));
8803
+ }
8804
+ app.nodes.attachNodeCallback((node) => {
8805
+ if ((allElements || node.nodeName.includes('-')) && 'getAnimations' in node) {
8806
+ const animations = node.getAnimations({ subtree: false });
8807
+ const id = app.nodes.getID(node);
8808
+ if (animations.length > 0 && !listening.has(node) && id) {
8809
+ listening.add(node);
8810
+ scanElement(node, id);
8811
+ node.addEventListener('animationstart', () => scanElement(node, id));
8812
+ }
8813
+ }
8814
+ });
8815
+ const origAnimate = Element.prototype.animate;
8816
+ Element.prototype.animate = function (...args) {
8817
+ const anim = origAnimate.apply(this, args);
8818
+ const id = app.nodes.getID(this);
8819
+ if (!id)
8820
+ return anim;
8821
+ wire(anim, this, id);
8822
+ return anim;
8823
+ };
8824
+ app.attachStopCallback(() => {
8825
+ Element.prototype.animate = origAnimate; // Restore original animate method
8826
+ listening = new WeakSet();
8827
+ handled = new WeakSet();
8828
+ });
8829
+ }
8830
+
9667
8831
  const Messages = _Messages;
9668
- const DOCS_SETUP = '/en/sdk/constructor';
8832
+ const DOCS_SETUP = '/en/sdk';
9669
8833
  function processOptions(obj) {
9670
8834
  if (obj == null) {
9671
8835
  console.error(`OpenReplay: invalid options argument type. Please, check documentation on ${DOCS_HOST}${DOCS_SETUP}`);
@@ -9715,7 +8879,7 @@ class API {
9715
8879
  this.signalStartIssue = (reason, missingApi) => {
9716
8880
  const doNotTrack = this.checkDoNotTrack();
9717
8881
  console.log("Tracker couldn't start due to:", JSON.stringify({
9718
- trackerVersion: '16.4.10',
8882
+ trackerVersion: '17.0.0',
9719
8883
  projectKey: this.options.projectKey,
9720
8884
  doNotTrack,
9721
8885
  reason: missingApi.length ? `missing api: ${missingApi.join(',')}` : reason,
@@ -9743,6 +8907,12 @@ class API {
9743
8907
  }
9744
8908
  }
9745
8909
  };
8910
+ this.incident = (options) => {
8911
+ if (this.app === null) {
8912
+ return;
8913
+ }
8914
+ this.app.send(Incident(options.label ?? '', options.startTime, options.endTime ?? options.startTime));
8915
+ };
9746
8916
  this.crossdomainMode = Boolean(inIframe() && options.crossdomain?.enabled);
9747
8917
  if (!IN_BROWSER || !processOptions(options)) {
9748
8918
  return;
@@ -9821,6 +8991,7 @@ class API {
9821
8991
  Img(app);
9822
8992
  Input(app, options);
9823
8993
  Timing(app, options);
8994
+ LongAnimationTask(app, options);
9824
8995
  Focus(app);
9825
8996
  Fonts(app);
9826
8997
  const skipNetwork = options.network?.disabled;
@@ -9828,10 +8999,8 @@ class API {
9828
8999
  Network(app, options.network);
9829
9000
  }
9830
9001
  selection(app);
9002
+ webAnimations(app, options.webAnimations);
9831
9003
  window.__OPENREPLAY__ = this;
9832
- if (options.flags && options.flags.onFlagsLoad) {
9833
- this.onFlagsLoad(options.flags.onFlagsLoad);
9834
- }
9835
9004
  const wOpen = window.open;
9836
9005
  if (options.autoResetOnWindowOpen || options.resetTabOnWindowOpen) {
9837
9006
  app.attachStartCallback(() => {
@@ -9854,24 +9023,6 @@ class API {
9854
9023
  });
9855
9024
  }
9856
9025
  }
9857
- isFlagEnabled(flagName) {
9858
- return this.featureFlags.isFlagEnabled(flagName);
9859
- }
9860
- onFlagsLoad(callback) {
9861
- this.app?.featureFlags.onFlagsLoad(callback);
9862
- }
9863
- clearPersistFlags() {
9864
- this.app?.featureFlags.clearPersistFlags();
9865
- }
9866
- reloadFlags() {
9867
- return this.app?.featureFlags.reloadFlags();
9868
- }
9869
- getFeatureFlag(flagName) {
9870
- return this.app?.featureFlags.getFeatureFlag(flagName);
9871
- }
9872
- getAllFeatureFlags() {
9873
- return this.app?.featureFlags.flags;
9874
- }
9875
9026
  use(fn) {
9876
9027
  return fn(this.app, this.options);
9877
9028
  }
@@ -10002,12 +9153,6 @@ class API {
10002
9153
  }
10003
9154
  return this.app.getTabId();
10004
9155
  }
10005
- getUxId() {
10006
- if (this.app === null) {
10007
- return null;
10008
- }
10009
- return this.app.getUxtId();
10010
- }
10011
9156
  sessionID() {
10012
9157
  deprecationWarn("'sessionID' method", "'getSessionID' method", '/');
10013
9158
  return this.getSessionID();
@@ -10051,12 +9196,20 @@ class API {
10051
9196
  return this.issue(key, payload);
10052
9197
  }
10053
9198
  else {
9199
+ if (!payload || typeof payload === 'string') {
9200
+ return this.app.send(CustomEvent(key, payload));
9201
+ }
10054
9202
  try {
9203
+ if ('or_timestamp' in payload) {
9204
+ const startTs = this.getSessionInfo()?.timestamp ?? 0;
9205
+ const diff = payload.or_timestamp - startTs;
9206
+ if (diff < 0) {
9207
+ console.error(`OpenReplay: event ${key} has or_timestamp (${payload.or_timestamp}) before session start (${startTs}). It will be ignored.`);
9208
+ }
9209
+ }
10055
9210
  payload = JSON.stringify(payload);
10056
9211
  }
10057
- catch (e) {
10058
- return;
10059
- }
9212
+ catch (_) { }
10060
9213
  this.app.send(CustomEvent(key, payload));
10061
9214
  }
10062
9215
  }
@@ -10180,42 +9333,6 @@ class TrackerSingleton {
10180
9333
  }
10181
9334
  this.instance.handleError(e, metadata);
10182
9335
  }
10183
- isFlagEnabled(flagName) {
10184
- if (!IN_BROWSER || !this.ensureConfigured() || !this.instance) {
10185
- return false;
10186
- }
10187
- return this.instance.isFlagEnabled(flagName);
10188
- }
10189
- onFlagsLoad(...args) {
10190
- if (!IN_BROWSER || !this.ensureConfigured() || !this.instance) {
10191
- return;
10192
- }
10193
- this.instance.onFlagsLoad(...args);
10194
- }
10195
- clearPersistFlags() {
10196
- if (!IN_BROWSER || !this.ensureConfigured() || !this.instance) {
10197
- return;
10198
- }
10199
- this.instance.clearPersistFlags();
10200
- }
10201
- reloadFlags() {
10202
- if (!IN_BROWSER || !this.ensureConfigured() || !this.instance) {
10203
- return;
10204
- }
10205
- return this.instance.reloadFlags();
10206
- }
10207
- getFeatureFlag(flagName) {
10208
- if (!IN_BROWSER || !this.ensureConfigured() || !this.instance) {
10209
- return;
10210
- }
10211
- return this.instance.getFeatureFlag(flagName);
10212
- }
10213
- getAllFeatureFlags() {
10214
- if (!IN_BROWSER || !this.ensureConfigured() || !this.instance) {
10215
- return;
10216
- }
10217
- return this.instance.getAllFeatureFlags();
10218
- }
10219
9336
  restartCanvasTracking() {
10220
9337
  if (!IN_BROWSER || !this.ensureConfigured() || !this.instance) {
10221
9338
  return;
@@ -10338,12 +9455,6 @@ class TrackerSingleton {
10338
9455
  }
10339
9456
  return this.instance.getTabId();
10340
9457
  }
10341
- getUxId() {
10342
- if (!IN_BROWSER || !this.ensureConfigured() || !this.instance) {
10343
- return null;
10344
- }
10345
- return this.instance.getUxId();
10346
- }
10347
9458
  }
10348
9459
  const tracker = new TrackerSingleton();
10349
9460