@openreplay/tracker 4.0.1 → 4.1.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 (60) hide show
  1. package/cjs/app/guards.d.ts +1 -0
  2. package/cjs/app/guards.js +6 -1
  3. package/cjs/app/index.d.ts +1 -1
  4. package/cjs/app/index.js +14 -8
  5. package/cjs/app/messages.gen.js +1 -0
  6. package/cjs/app/nodes.d.ts +1 -1
  7. package/cjs/app/nodes.js +3 -5
  8. package/cjs/app/observer/iframe_observer.js +1 -0
  9. package/cjs/app/observer/iframe_offsets.d.ts +8 -0
  10. package/cjs/app/observer/iframe_offsets.js +59 -0
  11. package/cjs/app/observer/observer.js +4 -4
  12. package/cjs/app/observer/top_observer.d.ts +2 -4
  13. package/cjs/app/observer/top_observer.js +11 -21
  14. package/cjs/app/sanitizer.d.ts +10 -4
  15. package/cjs/app/sanitizer.js +33 -15
  16. package/cjs/app/session.js +1 -1
  17. package/cjs/common/messages.gen.js +1 -0
  18. package/cjs/index.d.ts +1 -0
  19. package/cjs/index.js +6 -6
  20. package/cjs/modules/constructedStyleSheets.d.ts +4 -0
  21. package/cjs/modules/{adoptedStyleSheets.js → constructedStyleSheets.js} +21 -20
  22. package/cjs/modules/cssrules.js +65 -18
  23. package/cjs/modules/img.js +27 -19
  24. package/cjs/modules/input.js +2 -2
  25. package/cjs/modules/mouse.js +11 -7
  26. package/cjs/modules/scroll.js +32 -12
  27. package/cjs/utils.d.ts +5 -3
  28. package/cjs/utils.js +18 -13
  29. package/lib/app/guards.d.ts +1 -0
  30. package/lib/app/guards.js +4 -0
  31. package/lib/app/index.d.ts +1 -1
  32. package/lib/app/index.js +14 -8
  33. package/lib/app/messages.gen.js +1 -0
  34. package/lib/app/nodes.d.ts +1 -1
  35. package/lib/app/nodes.js +3 -5
  36. package/lib/app/observer/iframe_observer.js +1 -0
  37. package/lib/app/observer/iframe_offsets.d.ts +8 -0
  38. package/lib/app/observer/iframe_offsets.js +56 -0
  39. package/lib/app/observer/observer.js +4 -4
  40. package/lib/app/observer/top_observer.d.ts +2 -4
  41. package/lib/app/observer/top_observer.js +11 -21
  42. package/lib/app/sanitizer.d.ts +10 -4
  43. package/lib/app/sanitizer.js +32 -15
  44. package/lib/app/session.js +1 -1
  45. package/lib/common/messages.gen.js +1 -0
  46. package/lib/common/tsconfig.tsbuildinfo +1 -1
  47. package/lib/index.d.ts +1 -0
  48. package/lib/index.js +4 -5
  49. package/lib/modules/constructedStyleSheets.d.ts +4 -0
  50. package/lib/modules/{adoptedStyleSheets.js → constructedStyleSheets.js} +20 -21
  51. package/lib/modules/cssrules.js +67 -19
  52. package/lib/modules/img.js +28 -20
  53. package/lib/modules/input.js +3 -3
  54. package/lib/modules/mouse.js +11 -7
  55. package/lib/modules/scroll.js +33 -13
  56. package/lib/utils.d.ts +5 -3
  57. package/lib/utils.js +17 -11
  58. package/package.json +1 -1
  59. package/cjs/modules/adoptedStyleSheets.d.ts +0 -2
  60. package/lib/modules/adoptedStyleSheets.d.ts +0 -2
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.styleSheetIDMap = exports.nextID = void 0;
3
4
  const messages_gen_js_1 = require("../app/messages.gen.js");
4
5
  const guards_js_1 = require("../app/guards.js");
5
6
  function hasAdoptedSS(node) {
@@ -7,6 +8,13 @@ function hasAdoptedSS(node) {
7
8
  // @ts-ignore
8
9
  !!node.adoptedStyleSheets);
9
10
  }
11
+ // TODO: incapsulate to be init-ed on-start and join with cssrules.ts under one folder
12
+ let _id = 0xf;
13
+ function nextID() {
14
+ return _id++;
15
+ }
16
+ exports.nextID = nextID;
17
+ exports.styleSheetIDMap = new Map();
10
18
  function default_1(app) {
11
19
  if (app === null) {
12
20
  return;
@@ -18,10 +26,9 @@ function default_1(app) {
18
26
  });
19
27
  return;
20
28
  }
21
- let nextID = 0xf;
22
29
  const styleSheetIDMap = new Map();
23
30
  const adoptedStyleSheetsOwnings = new Map();
24
- const updateAdoptedStyleSheets = (root) => {
31
+ const sendAdoptedStyleSheetsUpdate = (root) => {
25
32
  let nodeID = app.nodes.getID(root);
26
33
  if (root === document) {
27
34
  nodeID = 0; // main document doesn't have nodeID. ID count starts from the documentElement
@@ -39,7 +46,7 @@ function default_1(app) {
39
46
  let sheetID = styleSheetIDMap.get(s);
40
47
  const init = !sheetID;
41
48
  if (!sheetID) {
42
- sheetID = ++nextID;
49
+ sheetID = nextID();
43
50
  }
44
51
  nowOwning.push(sheetID);
45
52
  if (!pastOwning.includes(sheetID)) {
@@ -65,12 +72,20 @@ function default_1(app) {
65
72
  Object.defineProperty(prototype, 'adoptedStyleSheets', Object.assign(Object.assign({}, nativeAdoptedStyleSheetsDescriptor), { set: function (value) {
66
73
  // @ts-ignore
67
74
  const retVal = nativeAdoptedStyleSheetsDescriptor.set.call(this, value);
68
- updateAdoptedStyleSheets(this);
75
+ sendAdoptedStyleSheetsUpdate(this);
69
76
  return retVal;
70
77
  } }));
71
78
  }
72
79
  }
73
80
  const patchContext = (context) => {
81
+ // @ts-ignore
82
+ if (context.__openreplay_adpss_patched__) {
83
+ return;
84
+ }
85
+ else {
86
+ // @ts-ignore
87
+ context.__openreplay_adpss_patched__ = true;
88
+ }
74
89
  patchAdoptedStyleSheets(context.Document.prototype);
75
90
  patchAdoptedStyleSheets(context.ShadowRoot.prototype);
76
91
  //@ts-ignore TODO: configure ts (use necessary lib)
@@ -93,20 +108,6 @@ function default_1(app) {
93
108
  }
94
109
  return replaceSync.call(this, text);
95
110
  };
96
- context.CSSStyleSheet.prototype.insertRule = function (rule, index = 0) {
97
- const sheetID = styleSheetIDMap.get(this);
98
- if (sheetID) {
99
- app.send((0, messages_gen_js_1.AdoptedSSInsertRuleURLBased)(sheetID, rule, index, app.getBaseHref()));
100
- }
101
- return insertRule.call(this, rule, index);
102
- };
103
- context.CSSStyleSheet.prototype.deleteRule = function (index) {
104
- const sheetID = styleSheetIDMap.get(this);
105
- if (sheetID) {
106
- app.send((0, messages_gen_js_1.AdoptedSSDeleteRule)(sheetID, index));
107
- }
108
- return deleteRule.call(this, index);
109
- };
110
111
  };
111
112
  patchContext(window);
112
113
  app.observer.attachContextCallback(patchContext);
@@ -116,11 +117,11 @@ function default_1(app) {
116
117
  });
117
118
  // So far main Document is not triggered with nodeCallbacks
118
119
  app.attachStartCallback(() => {
119
- updateAdoptedStyleSheets(document);
120
+ sendAdoptedStyleSheetsUpdate(document);
120
121
  });
121
122
  app.nodes.attachNodeCallback((node) => {
122
123
  if (hasAdoptedSS(node)) {
123
- updateAdoptedStyleSheets(node);
124
+ sendAdoptedStyleSheetsUpdate(node);
124
125
  }
125
126
  });
126
127
  }
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const messages_gen_js_1 = require("../app/messages.gen.js");
4
4
  const guards_js_1 = require("../app/guards.js");
5
+ const constructedStyleSheets_js_1 = require("./constructedStyleSheets.js");
5
6
  function default_1(app) {
6
7
  if (app === null) {
7
8
  return;
@@ -10,42 +11,88 @@ function default_1(app) {
10
11
  app.send((0, messages_gen_js_1.TechnicalInfo)('no_stylesheet_prototype_in_window', ''));
11
12
  return;
12
13
  }
13
- const processOperation = app.safe((stylesheet, index, rule) => {
14
- const sendMessage = typeof rule === 'string'
15
- ? (nodeID) => app.send((0, messages_gen_js_1.CSSInsertRuleURLBased)(nodeID, rule, index, app.getBaseHref()))
16
- : (nodeID) => app.send((0, messages_gen_js_1.CSSDeleteRule)(nodeID, index));
17
- // TODO: Extend messages to maintain nested rules (CSSGroupingRule prototype, as well as CSSKeyframesRule)
18
- if (stylesheet.ownerNode == null) {
19
- throw new Error('Owner Node not found');
20
- }
21
- const nodeID = app.nodes.getID(stylesheet.ownerNode);
22
- if (nodeID !== undefined) {
23
- sendMessage(nodeID);
24
- } // else error?
14
+ const sendInserDeleteRule = app.safe((sheet, index, rule) => {
15
+ const sheetID = constructedStyleSheets_js_1.styleSheetIDMap.get(sheet);
16
+ if (!sheetID) {
17
+ // OK-case. Sheet haven't been registered yet. Rules will be sent on registration.
18
+ return;
19
+ }
20
+ if (typeof rule === 'string') {
21
+ app.send((0, messages_gen_js_1.AdoptedSSInsertRuleURLBased)(sheetID, rule, index, app.getBaseHref()));
22
+ }
23
+ else {
24
+ app.send((0, messages_gen_js_1.AdoptedSSDeleteRule)(sheetID, index));
25
+ }
26
+ });
27
+ // TODO: proper rule insertion/removal (how?)
28
+ const sendReplaceGroupingRule = app.safe((rule) => {
29
+ let topmostRule = rule;
30
+ while (topmostRule.parentRule) {
31
+ topmostRule = topmostRule.parentRule;
32
+ }
33
+ const sheet = topmostRule.parentStyleSheet;
34
+ if (!sheet) {
35
+ app.debug.warn('No parent StyleSheet found for', topmostRule, rule);
36
+ return;
37
+ }
38
+ const sheetID = constructedStyleSheets_js_1.styleSheetIDMap.get(sheet);
39
+ if (!sheetID) {
40
+ app.debug.warn('No sheedID found for', sheet, constructedStyleSheets_js_1.styleSheetIDMap);
41
+ return;
42
+ }
43
+ const cssText = topmostRule.cssText;
44
+ const ruleList = sheet.cssRules;
45
+ const idx = Array.from(ruleList).indexOf(topmostRule);
46
+ if (idx >= 0) {
47
+ app.send((0, messages_gen_js_1.AdoptedSSInsertRuleURLBased)(sheetID, cssText, idx, app.getBaseHref()));
48
+ app.send((0, messages_gen_js_1.AdoptedSSDeleteRule)(sheetID, idx + 1)); // Remove previous clone
49
+ }
50
+ else {
51
+ app.debug.warn('Rule index not found in', sheet, topmostRule);
52
+ }
25
53
  });
26
54
  const patchContext = (context) => {
27
55
  const { insertRule, deleteRule } = context.CSSStyleSheet.prototype;
56
+ const { insertRule: groupInsertRule, deleteRule: groupDeleteRule } = context.CSSGroupingRule.prototype;
28
57
  context.CSSStyleSheet.prototype.insertRule = function (rule, index = 0) {
29
- processOperation(this, index, rule);
58
+ sendInserDeleteRule(this, index, rule);
30
59
  return insertRule.call(this, rule, index);
31
60
  };
32
61
  context.CSSStyleSheet.prototype.deleteRule = function (index) {
33
- processOperation(this, index);
62
+ sendInserDeleteRule(this, index);
34
63
  return deleteRule.call(this, index);
35
64
  };
65
+ context.CSSGroupingRule.prototype.insertRule = function (rule, index = 0) {
66
+ const result = groupInsertRule.call(this, rule, index);
67
+ sendReplaceGroupingRule(this);
68
+ return result;
69
+ };
70
+ context.CSSGroupingRule.prototype.deleteRule = function (index = 0) {
71
+ const result = groupDeleteRule.call(this, index);
72
+ sendReplaceGroupingRule(this);
73
+ return result;
74
+ };
36
75
  };
37
76
  patchContext(window);
38
77
  app.observer.attachContextCallback(patchContext);
39
78
  app.nodes.attachNodeCallback((node) => {
40
- if (!(0, guards_js_1.hasTag)(node, 'STYLE') || !node.sheet) {
79
+ if (!((0, guards_js_1.hasTag)(node, 'STYLE') || (0, guards_js_1.hasTag)(node, 'style')) || !node.sheet) {
41
80
  return;
42
81
  }
43
82
  if (node.textContent !== null && node.textContent.trim().length > 0) {
44
- return; // Only fully virtual sheets maintained so far
83
+ return; // Non-virtual styles captured by the observer as a text
84
+ }
85
+ const nodeID = app.nodes.getID(node);
86
+ if (!nodeID) {
87
+ return;
45
88
  }
46
- const rules = node.sheet.cssRules;
89
+ const sheet = node.sheet;
90
+ const sheetID = (0, constructedStyleSheets_js_1.nextID)();
91
+ constructedStyleSheets_js_1.styleSheetIDMap.set(sheet, sheetID);
92
+ app.send((0, messages_gen_js_1.AdoptedSSAddOwner)(sheetID, nodeID));
93
+ const rules = sheet.cssRules;
47
94
  for (let i = 0; i < rules.length; i++) {
48
- processOperation(node.sheet, i, rules[i].cssText);
95
+ sendInserDeleteRule(sheet, i, rules[i].cssText);
49
96
  }
50
97
  });
51
98
  }
@@ -10,7 +10,7 @@ function resolveURL(url, location = document.location) {
10
10
  }
11
11
  else if (url.startsWith('http://') ||
12
12
  url.startsWith('https://') ||
13
- url.startsWith('data:') // any other possible value here?
13
+ url.startsWith('data:') // any other possible value here? https://bugzilla.mozilla.org/show_bug.cgi?id=1758035
14
14
  ) {
15
15
  return url;
16
16
  }
@@ -18,6 +18,10 @@ function resolveURL(url, location = document.location) {
18
18
  return location.origin + location.pathname + url;
19
19
  }
20
20
  }
21
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1607081
22
+ function isSVGInFireFox(url) {
23
+ return utils_js_1.IS_FIREFOX && (url.startsWith('data:image/svg+xml') || url.match(/.svg$|/i));
24
+ }
21
25
  const PLACEHOLDER_SRC = 'https://static.openreplay.com/tracker/placeholder.jpeg';
22
26
  function default_1(app) {
23
27
  function sendPlaceholder(id, node) {
@@ -42,30 +46,34 @@ function default_1(app) {
42
46
  app.send((0, messages_gen_js_1.SetNodeAttribute)(id, 'srcset', resolvedSrcset));
43
47
  };
44
48
  const sendSrc = function (id, img) {
45
- const src = img.src;
46
- app.send((0, messages_gen_js_1.SetNodeAttributeURLBased)(id, 'src', src, app.getBaseHref()));
49
+ if (img.src.length > utils_js_1.MAX_STR_LEN) {
50
+ sendPlaceholder(id, img);
51
+ }
52
+ app.send((0, messages_gen_js_1.SetNodeAttributeURLBased)(id, 'src', img.src, app.getBaseHref()));
47
53
  };
48
- const sendImgAttrs = app.safe(function () {
49
- const id = app.nodes.getID(this);
54
+ const sendImgError = app.safe(function (img) {
55
+ const resolvedSrc = resolveURL(img.src || ''); // Src type is null sometimes. - is it true?
56
+ if ((0, utils_js_1.isURL)(resolvedSrc)) {
57
+ app.send((0, messages_gen_js_1.ResourceTiming)((0, utils_js_1.timestamp)(), 0, 0, 0, 0, 0, resolvedSrc, 'img'));
58
+ }
59
+ });
60
+ const sendImgAttrs = app.safe(function (img) {
61
+ const id = app.nodes.getID(img);
50
62
  if (id === undefined) {
51
63
  return;
52
64
  }
53
- const { src, complete, naturalWidth, naturalHeight, srcset } = this;
54
- if (!complete) {
65
+ if (!img.complete) {
55
66
  return;
56
67
  }
57
- const resolvedSrc = resolveURL(src || ''); // Src type is null sometimes. - is it true?
58
- if (naturalWidth === 0 && naturalHeight === 0) {
59
- if ((0, utils_js_1.isURL)(resolvedSrc)) {
60
- app.send((0, messages_gen_js_1.ResourceTiming)((0, utils_js_1.timestamp)(), 0, 0, 0, 0, 0, resolvedSrc, 'img'));
61
- }
68
+ if (img.naturalHeight === 0 && img.naturalWidth === 0 && !isSVGInFireFox(img.src)) {
69
+ sendImgError(img);
62
70
  }
63
- else if (resolvedSrc.length >= 1e5 || app.sanitizer.isMasked(id)) {
64
- sendPlaceholder(id, this);
71
+ else if (app.sanitizer.isHidden(id) || app.sanitizer.isObscured(id)) {
72
+ sendPlaceholder(id, img);
65
73
  }
66
74
  else {
67
- sendSrc(id, this);
68
- sendSrcset(id, this);
75
+ sendSrc(id, img);
76
+ sendSrcset(id, img);
69
77
  }
70
78
  });
71
79
  const observer = new MutationObserver((mutations) => {
@@ -92,9 +100,9 @@ function default_1(app) {
92
100
  if (!(0, guards_js_1.hasTag)(node, 'IMG')) {
93
101
  return;
94
102
  }
95
- app.nodes.attachElementListener('error', node, sendImgAttrs.bind(node));
96
- app.nodes.attachElementListener('load', node, sendImgAttrs.bind(node));
97
- sendImgAttrs.call(node);
103
+ app.nodes.attachNodeListener(node, 'error', () => sendImgError(node));
104
+ app.nodes.attachNodeListener(node, 'load', () => sendImgAttrs(node));
105
+ sendImgAttrs(node);
98
106
  observer.observe(node, { attributes: true, attributeFilter: ['src', 'srcset'] });
99
107
  });
100
108
  }
@@ -80,10 +80,10 @@ function default_1(app, opts) {
80
80
  function sendInputValue(id, node) {
81
81
  let value = node.value;
82
82
  let inputMode = options.defaultInputMode;
83
- if (node.type === 'password' || (0, utils_js_1.hasOpenreplayAttribute)(node, 'hidden')) {
83
+ if (node.type === 'password' || app.sanitizer.isHidden(id)) {
84
84
  inputMode = 2 /* Hidden */;
85
85
  }
86
- else if ((0, utils_js_1.hasOpenreplayAttribute)(node, 'obscured') ||
86
+ else if (app.sanitizer.isObscured(id) ||
87
87
  (inputMode === 0 /* Plain */ &&
88
88
  ((options.obscureInputNumbers && node.type !== 'date' && /\d\d\d\d/.test(value)) ||
89
89
  (options.obscureInputDates && node.type === 'date') ||
@@ -98,11 +98,13 @@ function default_1(app) {
98
98
  let mousePositionChanged = false;
99
99
  let mouseTarget = null;
100
100
  let mouseTargetTime = 0;
101
+ let selectorMap = {};
101
102
  app.attachStopCallback(() => {
102
103
  mousePositionX = -1;
103
104
  mousePositionY = -1;
104
105
  mousePositionChanged = false;
105
106
  mouseTarget = null;
107
+ selectorMap = {};
106
108
  });
107
109
  const sendMouseMove = () => {
108
110
  if (mousePositionChanged) {
@@ -110,25 +112,27 @@ function default_1(app) {
110
112
  mousePositionChanged = false;
111
113
  }
112
114
  };
113
- const patchDocument = (document) => {
114
- const selectorMap = {};
115
+ const patchDocument = (document, topframe = false) => {
115
116
  function getSelector(id, target) {
116
117
  return (selectorMap[id] = selectorMap[id] || _getSelector(target, document));
117
118
  }
118
- app.attachEventListener(document.documentElement, 'mouseover', (e) => {
119
+ const attachListener = topframe
120
+ ? app.attachEventListener.bind(app) // attached/removed on start/stop
121
+ : app.nodes.attachNodeListener.bind(app.nodes); // attached/removed on node register/unregister
122
+ attachListener(document.documentElement, 'mouseover', (e) => {
119
123
  const target = getTarget(e.target, document);
120
124
  if (target !== mouseTarget) {
121
125
  mouseTarget = target;
122
126
  mouseTargetTime = performance.now();
123
127
  }
124
128
  });
125
- app.attachEventListener(document, 'mousemove', (e) => {
126
- const { top, left } = app.observer.getDocumentOffset(document);
129
+ attachListener(document, 'mousemove', (e) => {
130
+ const [left, top] = app.observer.getDocumentOffset(document); // MBTODO?: document-id related message
127
131
  mousePositionX = e.clientX + left;
128
132
  mousePositionY = e.clientY + top;
129
133
  mousePositionChanged = true;
130
134
  }, false);
131
- app.attachEventListener(document, 'click', (e) => {
135
+ attachListener(document, 'click', (e) => {
132
136
  const target = getTarget(e.target, document);
133
137
  if ((!e.clientX && !e.clientY) || target === null) {
134
138
  return;
@@ -146,7 +150,7 @@ function default_1(app) {
146
150
  patchDocument(node);
147
151
  }
148
152
  });
149
- patchDocument(document);
153
+ patchDocument(document, true);
150
154
  app.ticker.attach(sendMouseMove, 10);
151
155
  }
152
156
  exports.default = default_1;
@@ -2,21 +2,34 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const messages_gen_js_1 = require("../app/messages.gen.js");
4
4
  const guards_js_1 = require("../app/guards.js");
5
+ function getDocumentScroll(doc) {
6
+ const win = doc.defaultView;
7
+ return [
8
+ (win && win.pageXOffset) ||
9
+ (doc.documentElement && doc.documentElement.scrollLeft) ||
10
+ (doc.body && doc.body.scrollLeft) ||
11
+ 0,
12
+ (win && win.pageYOffset) ||
13
+ (doc.documentElement && doc.documentElement.scrollTop) ||
14
+ (doc.body && doc.body.scrollTop) ||
15
+ 0,
16
+ ];
17
+ }
5
18
  function default_1(app) {
6
19
  let documentScroll = false;
7
20
  const nodeScroll = new Map();
8
21
  function setNodeScroll(target) {
9
- if (target instanceof Element) {
22
+ if (!(0, guards_js_1.isNode)(target)) {
23
+ return;
24
+ }
25
+ if ((0, guards_js_1.isElementNode)(target)) {
10
26
  nodeScroll.set(target, [target.scrollLeft, target.scrollTop]);
11
27
  }
28
+ if ((0, guards_js_1.isDocument)(target)) {
29
+ nodeScroll.set(target, getDocumentScroll(target));
30
+ }
12
31
  }
13
- const sendSetViewportScroll = app.safe(() => app.send((0, messages_gen_js_1.SetViewportScroll)(window.pageXOffset ||
14
- (document.documentElement && document.documentElement.scrollLeft) ||
15
- (document.body && document.body.scrollLeft) ||
16
- 0, window.pageYOffset ||
17
- (document.documentElement && document.documentElement.scrollTop) ||
18
- (document.body && document.body.scrollTop) ||
19
- 0)));
32
+ const sendSetViewportScroll = app.safe(() => app.send((0, messages_gen_js_1.SetViewportScroll)(...getDocumentScroll(document))));
20
33
  const sendSetNodeScroll = app.safe((s, node) => {
21
34
  const id = app.nodes.getID(node);
22
35
  if (id !== undefined) {
@@ -29,12 +42,19 @@ function default_1(app) {
29
42
  nodeScroll.clear();
30
43
  });
31
44
  app.nodes.attachNodeCallback((node, isStart) => {
32
- if (isStart && (0, guards_js_1.isElementNode)(node) && node.scrollLeft + node.scrollTop > 0) {
33
- nodeScroll.set(node, [node.scrollLeft, node.scrollTop]);
45
+ // MBTODO: iterate over all the nodes on start instead of using isStart hack
46
+ if (isStart) {
47
+ if ((0, guards_js_1.isElementNode)(node) && node.scrollLeft + node.scrollTop > 0) {
48
+ nodeScroll.set(node, [node.scrollLeft, node.scrollTop]);
49
+ }
50
+ else if ((0, guards_js_1.isDocument)(node)) {
51
+ // DRY somehow?
52
+ nodeScroll.set(node, getDocumentScroll(node));
53
+ }
34
54
  }
35
- else if ((0, guards_js_1.isRootNode)(node)) {
55
+ if ((0, guards_js_1.isRootNode)(node)) {
36
56
  // scroll is not-composed event (https://javascript.info/shadow-dom-events)
37
- app.attachEventListener(node, 'scroll', (e) => {
57
+ app.nodes.attachNodeListener(node, 'scroll', (e) => {
38
58
  setNodeScroll(e.target);
39
59
  });
40
60
  }
package/cjs/utils.d.ts CHANGED
@@ -1,9 +1,11 @@
1
- export declare function timestamp(): number;
1
+ export declare const IN_BROWSER: boolean;
2
+ export declare const IS_FIREFOX: false | RegExpMatchArray | null;
3
+ export declare const MAX_STR_LEN = 100000;
4
+ export declare const timestamp: () => number;
2
5
  export declare const stars: (str: string) => string;
3
6
  export declare function normSpaces(str: string): string;
4
7
  export declare function isURL(s: string): boolean;
5
- export declare const IN_BROWSER: boolean;
6
8
  export declare const DOCS_HOST = "https://docs.openreplay.com";
7
9
  export declare function deprecationWarn(nameOfFeature: string, useInstead: string, docsPath?: string): void;
8
10
  export declare function getLabelAttribute(e: Element): string | null;
9
- export declare function hasOpenreplayAttribute(e: Element, name: string): boolean;
11
+ export declare function hasOpenreplayAttribute(e: Element, attr: string): boolean;
package/cjs/utils.js CHANGED
@@ -1,10 +1,15 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.hasOpenreplayAttribute = exports.getLabelAttribute = exports.deprecationWarn = exports.DOCS_HOST = exports.IN_BROWSER = exports.isURL = exports.normSpaces = exports.stars = exports.timestamp = void 0;
4
- function timestamp() {
5
- return Math.round(performance.now()) + performance.timing.navigationStart;
6
- }
7
- exports.timestamp = timestamp;
3
+ exports.hasOpenreplayAttribute = exports.getLabelAttribute = exports.deprecationWarn = exports.DOCS_HOST = exports.isURL = exports.normSpaces = exports.stars = exports.timestamp = exports.MAX_STR_LEN = exports.IS_FIREFOX = exports.IN_BROWSER = void 0;
4
+ const DEPRECATED_ATTRS = { htmlmasked: 'hidden', masked: 'obscured' };
5
+ exports.IN_BROWSER = !(typeof window === 'undefined');
6
+ exports.IS_FIREFOX = exports.IN_BROWSER && navigator.userAgent.match(/firefox|fxios/i);
7
+ exports.MAX_STR_LEN = 1e5;
8
+ const navigationStart = (exports.IN_BROWSER && performance.timing.navigationStart) || performance.timeOrigin;
9
+ // performance.now() is buggy in some browsers
10
+ exports.timestamp = exports.IN_BROWSER && performance.now() && navigationStart
11
+ ? () => Math.round(performance.now() + navigationStart)
12
+ : () => Date.now();
8
13
  exports.stars = 'repeat' in String.prototype
9
14
  ? (str) => '*'.repeat(str.length)
10
15
  : (str) => str.replace(/./g, '*');
@@ -17,7 +22,6 @@ function isURL(s) {
17
22
  return s.startsWith('https://') || s.startsWith('http://');
18
23
  }
19
24
  exports.isURL = isURL;
20
- exports.IN_BROWSER = !(typeof window === 'undefined');
21
25
  // TODO: JOIN IT WITH LOGGER somehow (use logging decorators?); Don't forget about index.js loggin when there is no logger instance.
22
26
  exports.DOCS_HOST = 'https://docs.openreplay.com';
23
27
  const warnedFeatures = {};
@@ -41,14 +45,15 @@ function getLabelAttribute(e) {
41
45
  return value;
42
46
  }
43
47
  exports.getLabelAttribute = getLabelAttribute;
44
- function hasOpenreplayAttribute(e, name) {
45
- const newName = `data-openreplay-${name}`;
48
+ function hasOpenreplayAttribute(e, attr) {
49
+ const newName = `data-openreplay-${attr}`;
46
50
  if (e.hasAttribute(newName)) {
47
- return true;
48
- }
49
- const oldName = `data-asayer-${name}`;
50
- if (e.hasAttribute(oldName)) {
51
- deprecationWarn(`"${oldName}" attribute`, `"${newName}" attribute`, '/installation/sanitize-data');
51
+ // @ts-ignore
52
+ if (DEPRECATED_ATTRS[attr]) {
53
+ deprecationWarn(`"${newName}" attribute`,
54
+ // @ts-ignore
55
+ `"${DEPRECATED_ATTRS[attr]}" attribute`, '/installation/sanitize-data');
56
+ }
52
57
  return true;
53
58
  }
54
59
  return false;
@@ -1,3 +1,4 @@
1
+ export declare function isNode(sth: any): sth is Node;
1
2
  export declare function isSVGElement(node: Element): node is SVGElement;
2
3
  export declare function isElementNode(node: Node): node is Element;
3
4
  export declare function isTextNode(node: Node): node is Text;
package/lib/app/guards.js CHANGED
@@ -1,3 +1,7 @@
1
+ //@ts-ignore
2
+ export function isNode(sth) {
3
+ return !!sth && sth.nodeType != null;
4
+ }
1
5
  export function isSVGElement(node) {
2
6
  return node.namespaceURI === 'http://www.w3.org/2000/svg';
3
7
  }
@@ -77,7 +77,7 @@ export default class App {
77
77
  private _debug;
78
78
  send(message: Message, urgent?: boolean): void;
79
79
  private commit;
80
- safe<T extends (...args: any[]) => void>(fn: T): T;
80
+ safe<T extends (this: any, ...args: any[]) => void>(fn: T): T;
81
81
  attachCommitCallback(cb: CommitCallback): void;
82
82
  attachStartCallback(cb: StartCallback, useSafe?: boolean): void;
83
83
  attachStopCallback(cb: () => any, useSafe?: boolean): void;
package/lib/app/index.js CHANGED
@@ -71,17 +71,17 @@ export default class App {
71
71
  this.session.applySessionHash(sessionToken);
72
72
  }
73
73
  try {
74
- this.worker = new Worker(URL.createObjectURL(new Blob(['"use strict";class t{constructor(t,i,s,e=10,n=1e3){this.onUnauthorised=i,this.onFailure=s,this.MAX_ATTEMPTS_COUNT=e,this.ATTEMPT_TIMEOUT=n,this.attemptsCount=0,this.busy=!1,this.queue=[],this.token=null,this.ingestURL=t+"/v1/web/i"}authorise(t){this.token=t}push(t){this.busy||!this.token?this.queue.push(t):this.sendBatch(t)}retry(t){this.attemptsCount>=this.MAX_ATTEMPTS_COUNT?this.onFailure():(this.attemptsCount++,setTimeout(()=>this.sendBatch(t),this.ATTEMPT_TIMEOUT*this.attemptsCount))}sendBatch(t){this.busy=!0,fetch(this.ingestURL,{body:t,method:"POST",headers:{Authorization:"Bearer "+this.token},keepalive:t.length<65536}).then(i=>{if(401===i.status)return this.busy=!1,void this.onUnauthorised();if(i.status>=400)return void this.retry(t);this.attemptsCount=0;const s=this.queue.shift();s?this.sendBatch(s):this.busy=!1}).catch(i=>{console.warn("OpenReplay:",i),this.retry(t)})}clean(){this.queue.length=0}}const i="function"==typeof TextEncoder?new TextEncoder:{encode(t){const i=t.length,s=new Uint8Array(3*i);let e=-1;for(let n=0,r=0,h=0;h!==i;){if(n=t.charCodeAt(h),h+=1,n>=55296&&n<=56319){if(h===i){s[e+=1]=239,s[e+=1]=191,s[e+=1]=189;break}if(r=t.charCodeAt(h),!(r>=56320&&r<=57343)){s[e+=1]=239,s[e+=1]=191,s[e+=1]=189;continue}if(n=1024*(n-55296)+r-56320+65536,h+=1,n>65535){s[e+=1]=240|n>>>18,s[e+=1]=128|n>>>12&63,s[e+=1]=128|n>>>6&63,s[e+=1]=128|63&n;continue}}n<=127?s[e+=1]=0|n:n<=2047?(s[e+=1]=192|n>>>6,s[e+=1]=128|63&n):(s[e+=1]=224|n>>>12,s[e+=1]=128|n>>>6&63,s[e+=1]=128|63&n)}return s.subarray(0,e+1)}};class s extends class{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}isEmpty(){return 0===this.offset}skip(t){return this.offset+=t,this.offset<=this.size}set(t,i){this.data.set(t,i)}boolean(t){return this.data[this.offset++]=+t,this.offset<=this.size}uint(t){for((t<0||t>Number.MAX_SAFE_INTEGER)&&(t=0);t>=128;)this.data[this.offset++]=t%256|128,t=Math.floor(t/128);return this.data[this.offset++]=t,this.offset<=this.size}int(t){return t=Math.round(t),this.uint(t>=0?2*t:-2*t-1)}string(t){const s=i.encode(t),e=s.byteLength;return!(!this.uint(e)||this.offset+e>this.size)&&(this.data.set(s,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}}{encode(t){switch(t[0]){case 81:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.int(t[4])&&this.string(t[5]);case 82:return this.uint(t[1])&&this.uint(t[2]);case 0:return this.uint(t[1]);case 4:return this.string(t[1])&&this.string(t[2])&&this.uint(t[3]);case 5: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:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3]);case 11:return this.uint(t[1]);case 12:return this.uint(t[1])&&this.string(t[2])&&this.string(t[3]);case 13:case 14: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 17:return this.uint(t[1])&&this.string(t[2]);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 20:return this.uint(t[1])&&this.uint(t[2]);case 22: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 24:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3]);case 25:return this.string(t[1])&&this.string(t[2])&&this.string(t[3]);case 27:return this.string(t[1])&&this.string(t[2]);case 28:case 29:return this.string(t[1]);case 30:return this.string(t[1])&&this.string(t[2]);case 37:return this.uint(t[1])&&this.string(t[2])&&this.uint(t[3]);case 38:return this.uint(t[1])&&this.uint(t[2]);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 41:return this.string(t[1])&&this.string(t[2]);case 42:return this.string(t[1]);case 44:return this.string(t[1])&&this.string(t[2])&&this.uint(t[3]);case 45:case 46:return this.string(t[1])&&this.string(t[2]);case 47:return this.string(t[1])&&this.string(t[2])&&this.uint(t[3]);case 48:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4]);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 54:return this.uint(t[1])&&this.string(t[2]);case 55:return this.boolean(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 60:return this.uint(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4]);case 61:return this.uint(t[1])&&this.string(t[2])&&this.string(t[3]);case 63:case 64:return this.string(t[1])&&this.string(t[2]);case 67:return this.uint(t[1])&&this.string(t[2])&&this.uint(t[3])&&this.string(t[4]);case 69:return this.uint(t[1])&&this.uint(t[2])&&this.string(t[3])&&this.string(t[4]);case 70:return this.uint(t[1])&&this.uint(t[2]);case 71:return this.uint(t[1])&&this.string(t[2])&&this.string(t[3]);case 73:return this.uint(t[1])&&this.string(t[2])&&this.uint(t[3])&&this.string(t[4]);case 75:case 76:case 77:return this.uint(t[1])&&this.uint(t[2]);case 79:return this.string(t[1])&&this.string(t[2])}}}class e{constructor(t,i,e,n){this.pageNo=t,this.timestamp=i,this.url=e,this.onBatch=n,this.nextIndex=0,this.beaconSize=2e5,this.encoder=new s(this.beaconSize),this.sizeBuffer=new Uint8Array(3),this.isEmpty=!0,this.beaconSizeLimit=1e6,this.prepare()}writeType(t){return this.encoder.uint(t[0])}writeFields(t){return this.encoder.encode(t)}writeSizeAt(t,i){for(let i=0;i<3;i++)this.sizeBuffer[i]=t>>8*i;this.encoder.set(this.sizeBuffer,i)}prepare(){if(!this.encoder.isEmpty())return;const t=[81,1,this.pageNo,this.nextIndex,this.timestamp,this.url];this.writeType(t),this.writeFields(t),this.isEmpty=!0}writeWithSize(t){const i=this.encoder;if(!this.writeType(t)||!i.skip(3))return!1;const s=i.getCurrentOffset(),e=this.writeFields(t);if(e){const e=i.getCurrentOffset()-s;if(e>16777215)return console.warn("OpenReplay: max message size overflow."),!1;this.writeSizeAt(e,s-3),i.checkpoint(),this.isEmpty=this.isEmpty&&0===t[0],this.nextIndex++}return e}setBeaconSizeLimit(t){this.beaconSizeLimit=t}writeMessage(t){if(0===t[0]&&(this.timestamp=t[1]),4===t[0]&&(this.url=t[1]),!this.writeWithSize(t))for(this.finaliseBatch();!this.writeWithSize(t);){if(this.beaconSize===this.beaconSizeLimit)return console.warn("OpenReplay: beacon size overflow. Skipping large message.",t,this),this.encoder.reset(),void this.prepare();this.beaconSize=Math.min(2*this.beaconSize,this.beaconSizeLimit),this.encoder=new s(this.beaconSize),this.prepare()}}finaliseBatch(){this.isEmpty||(this.onBatch(this.encoder.flush()),this.prepare())}clean(){this.encoder.reset()}}var n;!function(t){t[t.NotActive=0]="NotActive",t[t.Starting=1]="Starting",t[t.Stopping=2]="Stopping",t[t.Active=3]="Active"}(n||(n={}));let r=null,h=null;function u(){h&&h.finaliseBatch()}function a(){n.Stopping,null!==c&&(clearInterval(c),c=null),h&&(h.clean(),h=null),n.NotActive}n.NotActive;let o,c=null;self.onmessage=({data:i})=>{if(null!=i){if("stop"===i)return u(),void a();if(Array.isArray(i)){if(!h)throw new Error("WebWorker: writer not initialised. Service Should be Started.");const t=h;i.forEach(i=>{55===i[0]&&(i[1]?o=setTimeout(()=>self.postMessage("restart"),18e5):clearTimeout(o)),t.writeMessage(i)})}else{if("start"===i.type)return n.Starting,r=new t(i.ingestPoint,()=>{self.postMessage("restart")},()=>{r&&(r.clean(),r=null),a(),self.postMessage("failed")},i.connAttemptCount,i.connAttemptGap),h=new e(i.pageNo,i.timestamp,i.url,t=>r&&r.push(t)),null===c&&(c=setInterval(u,1e4)),n.Active;if("auth"===i.type){if(!r)throw new Error("WebWorker: sender not initialised. Received auth.");if(!h)throw new Error("WebWorker: writer not initialised. Received auth.");return r.authorise(i.token),void(i.beaconSizeLimit&&h.setBeaconSizeLimit(i.beaconSizeLimit))}}}else u()};'], { type: 'text/javascript' })));
74
+ this.worker = new Worker(URL.createObjectURL(new Blob(['"use strict";class t{constructor(t,i,s,e=10,n=1e3){this.onUnauthorised=i,this.onFailure=s,this.MAX_ATTEMPTS_COUNT=e,this.ATTEMPT_TIMEOUT=n,this.attemptsCount=0,this.busy=!1,this.queue=[],this.token=null,this.ingestURL=t+"/v1/web/i"}authorise(t){this.token=t}push(t){this.busy||!this.token?this.queue.push(t):this.sendBatch(t)}retry(t){this.attemptsCount>=this.MAX_ATTEMPTS_COUNT?this.onFailure():(this.attemptsCount++,setTimeout(()=>this.sendBatch(t),this.ATTEMPT_TIMEOUT*this.attemptsCount))}sendBatch(t){this.busy=!0,fetch(this.ingestURL,{body:t,method:"POST",headers:{Authorization:"Bearer "+this.token},keepalive:t.length<65536}).then(i=>{if(401===i.status)return this.busy=!1,void this.onUnauthorised();if(i.status>=400)return void this.retry(t);this.attemptsCount=0;const s=this.queue.shift();s?this.sendBatch(s):this.busy=!1}).catch(i=>{console.warn("OpenReplay:",i),this.retry(t)})}clean(){this.queue.length=0}}const i="function"==typeof TextEncoder?new TextEncoder:{encode(t){const i=t.length,s=new Uint8Array(3*i);let e=-1;for(let n=0,r=0,h=0;h!==i;){if(n=t.charCodeAt(h),h+=1,n>=55296&&n<=56319){if(h===i){s[e+=1]=239,s[e+=1]=191,s[e+=1]=189;break}if(r=t.charCodeAt(h),!(r>=56320&&r<=57343)){s[e+=1]=239,s[e+=1]=191,s[e+=1]=189;continue}if(n=1024*(n-55296)+r-56320+65536,h+=1,n>65535){s[e+=1]=240|n>>>18,s[e+=1]=128|n>>>12&63,s[e+=1]=128|n>>>6&63,s[e+=1]=128|63&n;continue}}n<=127?s[e+=1]=0|n:n<=2047?(s[e+=1]=192|n>>>6,s[e+=1]=128|63&n):(s[e+=1]=224|n>>>12,s[e+=1]=128|n>>>6&63,s[e+=1]=128|63&n)}return s.subarray(0,e+1)}};class s extends class{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}isEmpty(){return 0===this.offset}skip(t){return this.offset+=t,this.offset<=this.size}set(t,i){this.data.set(t,i)}boolean(t){return this.data[this.offset++]=+t,this.offset<=this.size}uint(t){for((t<0||t>Number.MAX_SAFE_INTEGER)&&(t=0);t>=128;)this.data[this.offset++]=t%256|128,t=Math.floor(t/128);return this.data[this.offset++]=t,this.offset<=this.size}int(t){return t=Math.round(t),this.uint(t>=0?2*t:-2*t-1)}string(t){const s=i.encode(t),e=s.byteLength;return!(!this.uint(e)||this.offset+e>this.size)&&(this.data.set(s,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}}{encode(t){switch(t[0]){case 81:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.int(t[4])&&this.string(t[5]);case 82:return this.uint(t[1])&&this.uint(t[2]);case 0:return this.uint(t[1]);case 4:return this.string(t[1])&&this.string(t[2])&&this.uint(t[3]);case 5: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:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3]);case 11:return this.uint(t[1]);case 12:return this.uint(t[1])&&this.string(t[2])&&this.string(t[3]);case 13:case 14: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 17:return this.uint(t[1])&&this.string(t[2]);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 20:return this.uint(t[1])&&this.uint(t[2]);case 22: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 24:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3]);case 25:return this.string(t[1])&&this.string(t[2])&&this.string(t[3]);case 27:return this.string(t[1])&&this.string(t[2]);case 28:case 29:return this.string(t[1]);case 30:return this.string(t[1])&&this.string(t[2]);case 37:return this.uint(t[1])&&this.string(t[2])&&this.uint(t[3]);case 38:return this.uint(t[1])&&this.uint(t[2]);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 41:return this.string(t[1])&&this.string(t[2]);case 42:return this.string(t[1]);case 44:return this.string(t[1])&&this.string(t[2])&&this.uint(t[3]);case 45:case 46:return this.string(t[1])&&this.string(t[2]);case 47:return this.string(t[1])&&this.string(t[2])&&this.uint(t[3]);case 48:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4]);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 54:return this.uint(t[1])&&this.string(t[2]);case 55:return this.boolean(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 60:return this.uint(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4]);case 61:return this.uint(t[1])&&this.string(t[2])&&this.string(t[3]);case 63:case 64:return this.string(t[1])&&this.string(t[2]);case 67:return this.uint(t[1])&&this.string(t[2])&&this.uint(t[3])&&this.string(t[4]);case 69:return this.uint(t[1])&&this.uint(t[2])&&this.string(t[3])&&this.string(t[4]);case 70:return this.uint(t[1])&&this.uint(t[2]);case 71:return this.uint(t[1])&&this.string(t[2])&&this.string(t[3]);case 73:return this.uint(t[1])&&this.string(t[2])&&this.uint(t[3])&&this.string(t[4]);case 75:case 76:case 77:return this.uint(t[1])&&this.uint(t[2]);case 79:return this.string(t[1])&&this.string(t[2])}}}class e{constructor(t,i,e,n){this.pageNo=t,this.timestamp=i,this.url=e,this.onBatch=n,this.nextIndex=0,this.beaconSize=2e5,this.encoder=new s(this.beaconSize),this.sizeBuffer=new Uint8Array(3),this.isEmpty=!0,this.beaconSizeLimit=1e6,this.prepare()}writeType(t){return this.encoder.uint(t[0])}writeFields(t){return this.encoder.encode(t)}writeSizeAt(t,i){for(let i=0;i<3;i++)this.sizeBuffer[i]=t>>8*i;this.encoder.set(this.sizeBuffer,i)}prepare(){if(!this.encoder.isEmpty())return;const t=[81,1,this.pageNo,this.nextIndex,this.timestamp,this.url];this.writeType(t),this.writeFields(t),this.isEmpty=!0}writeWithSize(t){const i=this.encoder;if(!this.writeType(t)||!i.skip(3))return!1;const s=i.getCurrentOffset(),e=this.writeFields(t);if(e){const e=i.getCurrentOffset()-s;if(e>16777215)return console.warn("OpenReplay: max message size overflow."),!1;this.writeSizeAt(e,s-3),i.checkpoint(),this.isEmpty=this.isEmpty&&0===t[0],this.nextIndex++}return e}setBeaconSizeLimit(t){this.beaconSizeLimit=t}writeMessage(t){if(0===t[0]&&(this.timestamp=t[1]),4===t[0]&&(this.url=t[1]),!this.writeWithSize(t))for(this.finaliseBatch();!this.writeWithSize(t);){if(this.beaconSize===this.beaconSizeLimit)return console.warn("OpenReplay: beacon size overflow. Skipping large message.",t,this),this.encoder.reset(),void this.prepare();this.beaconSize=Math.min(2*this.beaconSize,this.beaconSizeLimit),this.encoder=new s(this.beaconSize),this.prepare()}}finaliseBatch(){this.isEmpty||(this.onBatch(this.encoder.flush()),this.prepare())}clean(){this.encoder.reset()}}var n;!function(t){t[t.NotActive=0]="NotActive",t[t.Starting=1]="Starting",t[t.Stopping=2]="Stopping",t[t.Active=3]="Active"}(n||(n={}));let r=null,h=null;function u(){h&&h.finaliseBatch()}function a(){n.Stopping,null!==f&&(clearInterval(f),f=null),h&&(h.clean(),h=null),r&&(r.clean(),r=null),n.NotActive}function o(){self.postMessage("restart"),a()}n.NotActive;let c,f=null;self.onmessage=({data:i})=>{if(null!=i){if("stop"===i)return u(),void a();if(Array.isArray(i)){if(!h)throw new Error("WebWorker: writer not initialised. Service Should be Started.");const t=h;i.forEach(i=>{55===i[0]&&(i[1]?c=setTimeout(()=>o(),18e5):clearTimeout(c)),t.writeMessage(i)})}else{if("start"===i.type)return n.Starting,r=new t(i.ingestPoint,()=>{o()},()=>{self.postMessage("failed"),a()},i.connAttemptCount,i.connAttemptGap),h=new e(i.pageNo,i.timestamp,i.url,t=>r&&r.push(t)),null===f&&(f=setInterval(u,1e4)),n.Active;if("auth"===i.type){if(!r)throw new Error("WebWorker: sender not initialised. Received auth.");if(!h)throw new Error("WebWorker: writer not initialised. Received auth.");return r.authorise(i.token),void(i.beaconSizeLimit&&h.setBeaconSizeLimit(i.beaconSizeLimit))}}}else u()};'], { type: 'text/javascript' })));
75
75
  this.worker.onerror = (e) => {
76
76
  this._debug('webworker_error', e);
77
77
  };
78
78
  this.worker.onmessage = ({ data }) => {
79
79
  if (data === 'failed') {
80
- this.stop();
80
+ this.stop(false);
81
81
  this._debug('worker_failed', {}); // add context (from worker)
82
82
  }
83
83
  else if (data === 'restart') {
84
- this.stop();
84
+ this.stop(false);
85
85
  this.start({ forceNew: true });
86
86
  }
87
87
  };
@@ -150,8 +150,6 @@ export default class App {
150
150
  }; // TODO: correct typing
151
151
  }
152
152
  attachCommitCallback(cb) {
153
- // TODO!: what if start callback added when activityState === Active ?
154
- // For example - attachEventListener() called during dynamic <iframe> appearance
155
153
  this.commitCallbacks.push(cb);
156
154
  }
157
155
  attachStartCallback(cb, useSafe = false) {
@@ -166,6 +164,7 @@ export default class App {
166
164
  }
167
165
  this.stopCallbacks.push(cb);
168
166
  }
167
+ // Use app.nodes.attachNodeListener for registered nodes instead
169
168
  attachEventListener(target, type, listener, useSafe = true, useCapture = true) {
170
169
  if (useSafe) {
171
170
  listener = this.safe(listener);
@@ -288,14 +287,15 @@ export default class App {
288
287
  });
289
288
  const sReset = this.sessionStorage.getItem(this.options.session_reset_key);
290
289
  this.sessionStorage.removeItem(this.options.session_reset_key);
290
+ const shouldReset = startOpts.forceNew || sReset !== null;
291
291
  return window
292
292
  .fetch(this.options.ingestPoint + '/v1/web/start', {
293
293
  method: 'POST',
294
294
  headers: {
295
295
  'Content-Type': 'application/json',
296
296
  },
297
- body: JSON.stringify(Object.assign(Object.assign({}, this.getTrackerInfo()), { timestamp, userID: this.session.getInfo().userID, token: this.session.getSessionToken(), deviceMemory,
298
- jsHeapSizeLimit, reset: startOpts.forceNew || sReset !== null })),
297
+ body: JSON.stringify(Object.assign(Object.assign({}, this.getTrackerInfo()), { timestamp, userID: this.session.getInfo().userID, token: shouldReset ? undefined : this.session.getSessionToken(), deviceMemory,
298
+ jsHeapSizeLimit })),
299
299
  })
300
300
  .then((r) => {
301
301
  if (r.status === 200) {
@@ -313,6 +313,9 @@ export default class App {
313
313
  if (!this.worker) {
314
314
  return Promise.reject('no worker found after start request (this might not happen)');
315
315
  }
316
+ if (this.activityState === ActivityState.NotActive) {
317
+ return Promise.reject('Tracker stopped during authorisation');
318
+ }
316
319
  const { token, userUUID, sessionID, projectID, beaconSizeLimit, startTimestamp, // real startTS, derived from sessionID
317
320
  } = r;
318
321
  if (typeof token !== 'string' ||
@@ -322,9 +325,12 @@ export default class App {
322
325
  (typeof beaconSizeLimit !== 'number' && typeof beaconSizeLimit !== 'undefined')) {
323
326
  return Promise.reject(`Incorrect server response: ${JSON.stringify(r)}`);
324
327
  }
328
+ if (sessionID !== this.session.getInfo().sessionID) {
329
+ this.session.reset();
330
+ }
325
331
  this.session.setSessionToken(token);
326
- this.localStorage.setItem(this.options.local_uuid_key, userUUID);
327
332
  this.session.update({ sessionID, timestamp: startTimestamp || timestamp, projectID }); // TODO: no no-explicit 'any'
333
+ this.localStorage.setItem(this.options.local_uuid_key, userUUID);
328
334
  const startWorkerMsg = {
329
335
  type: 'auth',
330
336
  token,