@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,7 @@
1
- import { CSSInsertRuleURLBased, CSSDeleteRule, TechnicalInfo } from '../app/messages.gen.js';
1
+ import { AdoptedSSInsertRuleURLBased, // TODO: rename to common StyleSheet names
2
+ AdoptedSSDeleteRule, AdoptedSSAddOwner, TechnicalInfo, } from '../app/messages.gen.js';
2
3
  import { hasTag } from '../app/guards.js';
4
+ import { nextID, styleSheetIDMap } from './constructedStyleSheets.js';
3
5
  export default function (app) {
4
6
  if (app === null) {
5
7
  return;
@@ -8,42 +10,88 @@ export default function (app) {
8
10
  app.send(TechnicalInfo('no_stylesheet_prototype_in_window', ''));
9
11
  return;
10
12
  }
11
- const processOperation = app.safe((stylesheet, index, rule) => {
12
- const sendMessage = typeof rule === 'string'
13
- ? (nodeID) => app.send(CSSInsertRuleURLBased(nodeID, rule, index, app.getBaseHref()))
14
- : (nodeID) => app.send(CSSDeleteRule(nodeID, index));
15
- // TODO: Extend messages to maintain nested rules (CSSGroupingRule prototype, as well as CSSKeyframesRule)
16
- if (stylesheet.ownerNode == null) {
17
- throw new Error('Owner Node not found');
18
- }
19
- const nodeID = app.nodes.getID(stylesheet.ownerNode);
20
- if (nodeID !== undefined) {
21
- sendMessage(nodeID);
22
- } // else error?
13
+ const sendInserDeleteRule = app.safe((sheet, index, rule) => {
14
+ const sheetID = styleSheetIDMap.get(sheet);
15
+ if (!sheetID) {
16
+ // OK-case. Sheet haven't been registered yet. Rules will be sent on registration.
17
+ return;
18
+ }
19
+ if (typeof rule === 'string') {
20
+ app.send(AdoptedSSInsertRuleURLBased(sheetID, rule, index, app.getBaseHref()));
21
+ }
22
+ else {
23
+ app.send(AdoptedSSDeleteRule(sheetID, index));
24
+ }
25
+ });
26
+ // TODO: proper rule insertion/removal (how?)
27
+ const sendReplaceGroupingRule = app.safe((rule) => {
28
+ let topmostRule = rule;
29
+ while (topmostRule.parentRule) {
30
+ topmostRule = topmostRule.parentRule;
31
+ }
32
+ const sheet = topmostRule.parentStyleSheet;
33
+ if (!sheet) {
34
+ app.debug.warn('No parent StyleSheet found for', topmostRule, rule);
35
+ return;
36
+ }
37
+ const sheetID = styleSheetIDMap.get(sheet);
38
+ if (!sheetID) {
39
+ app.debug.warn('No sheedID found for', sheet, styleSheetIDMap);
40
+ return;
41
+ }
42
+ const cssText = topmostRule.cssText;
43
+ const ruleList = sheet.cssRules;
44
+ const idx = Array.from(ruleList).indexOf(topmostRule);
45
+ if (idx >= 0) {
46
+ app.send(AdoptedSSInsertRuleURLBased(sheetID, cssText, idx, app.getBaseHref()));
47
+ app.send(AdoptedSSDeleteRule(sheetID, idx + 1)); // Remove previous clone
48
+ }
49
+ else {
50
+ app.debug.warn('Rule index not found in', sheet, topmostRule);
51
+ }
23
52
  });
24
53
  const patchContext = (context) => {
25
54
  const { insertRule, deleteRule } = context.CSSStyleSheet.prototype;
55
+ const { insertRule: groupInsertRule, deleteRule: groupDeleteRule } = context.CSSGroupingRule.prototype;
26
56
  context.CSSStyleSheet.prototype.insertRule = function (rule, index = 0) {
27
- processOperation(this, index, rule);
57
+ sendInserDeleteRule(this, index, rule);
28
58
  return insertRule.call(this, rule, index);
29
59
  };
30
60
  context.CSSStyleSheet.prototype.deleteRule = function (index) {
31
- processOperation(this, index);
61
+ sendInserDeleteRule(this, index);
32
62
  return deleteRule.call(this, index);
33
63
  };
64
+ context.CSSGroupingRule.prototype.insertRule = function (rule, index = 0) {
65
+ const result = groupInsertRule.call(this, rule, index);
66
+ sendReplaceGroupingRule(this);
67
+ return result;
68
+ };
69
+ context.CSSGroupingRule.prototype.deleteRule = function (index = 0) {
70
+ const result = groupDeleteRule.call(this, index);
71
+ sendReplaceGroupingRule(this);
72
+ return result;
73
+ };
34
74
  };
35
75
  patchContext(window);
36
76
  app.observer.attachContextCallback(patchContext);
37
77
  app.nodes.attachNodeCallback((node) => {
38
- if (!hasTag(node, 'STYLE') || !node.sheet) {
78
+ if (!(hasTag(node, 'STYLE') || hasTag(node, 'style')) || !node.sheet) {
39
79
  return;
40
80
  }
41
81
  if (node.textContent !== null && node.textContent.trim().length > 0) {
42
- return; // Only fully virtual sheets maintained so far
82
+ return; // Non-virtual styles captured by the observer as a text
83
+ }
84
+ const nodeID = app.nodes.getID(node);
85
+ if (!nodeID) {
86
+ return;
43
87
  }
44
- const rules = node.sheet.cssRules;
88
+ const sheet = node.sheet;
89
+ const sheetID = nextID();
90
+ styleSheetIDMap.set(sheet, sheetID);
91
+ app.send(AdoptedSSAddOwner(sheetID, nodeID));
92
+ const rules = sheet.cssRules;
45
93
  for (let i = 0; i < rules.length; i++) {
46
- processOperation(node.sheet, i, rules[i].cssText);
94
+ sendInserDeleteRule(sheet, i, rules[i].cssText);
47
95
  }
48
96
  });
49
97
  }
@@ -1,4 +1,4 @@
1
- import { timestamp, isURL } from '../utils.js';
1
+ import { timestamp, isURL, IS_FIREFOX, MAX_STR_LEN } from '../utils.js';
2
2
  import { ResourceTiming, SetNodeAttributeURLBased, SetNodeAttribute } from '../app/messages.gen.js';
3
3
  import { hasTag } from '../app/guards.js';
4
4
  function resolveURL(url, location = document.location) {
@@ -8,7 +8,7 @@ function resolveURL(url, location = document.location) {
8
8
  }
9
9
  else if (url.startsWith('http://') ||
10
10
  url.startsWith('https://') ||
11
- url.startsWith('data:') // any other possible value here?
11
+ url.startsWith('data:') // any other possible value here? https://bugzilla.mozilla.org/show_bug.cgi?id=1758035
12
12
  ) {
13
13
  return url;
14
14
  }
@@ -16,6 +16,10 @@ function resolveURL(url, location = document.location) {
16
16
  return location.origin + location.pathname + url;
17
17
  }
18
18
  }
19
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1607081
20
+ function isSVGInFireFox(url) {
21
+ return IS_FIREFOX && (url.startsWith('data:image/svg+xml') || url.match(/.svg$|/i));
22
+ }
19
23
  const PLACEHOLDER_SRC = 'https://static.openreplay.com/tracker/placeholder.jpeg';
20
24
  export default function (app) {
21
25
  function sendPlaceholder(id, node) {
@@ -40,30 +44,34 @@ export default function (app) {
40
44
  app.send(SetNodeAttribute(id, 'srcset', resolvedSrcset));
41
45
  };
42
46
  const sendSrc = function (id, img) {
43
- const src = img.src;
44
- app.send(SetNodeAttributeURLBased(id, 'src', src, app.getBaseHref()));
47
+ if (img.src.length > MAX_STR_LEN) {
48
+ sendPlaceholder(id, img);
49
+ }
50
+ app.send(SetNodeAttributeURLBased(id, 'src', img.src, app.getBaseHref()));
45
51
  };
46
- const sendImgAttrs = app.safe(function () {
47
- const id = app.nodes.getID(this);
52
+ const sendImgError = app.safe(function (img) {
53
+ const resolvedSrc = resolveURL(img.src || ''); // Src type is null sometimes. - is it true?
54
+ if (isURL(resolvedSrc)) {
55
+ app.send(ResourceTiming(timestamp(), 0, 0, 0, 0, 0, resolvedSrc, 'img'));
56
+ }
57
+ });
58
+ const sendImgAttrs = app.safe(function (img) {
59
+ const id = app.nodes.getID(img);
48
60
  if (id === undefined) {
49
61
  return;
50
62
  }
51
- const { src, complete, naturalWidth, naturalHeight, srcset } = this;
52
- if (!complete) {
63
+ if (!img.complete) {
53
64
  return;
54
65
  }
55
- const resolvedSrc = resolveURL(src || ''); // Src type is null sometimes. - is it true?
56
- if (naturalWidth === 0 && naturalHeight === 0) {
57
- if (isURL(resolvedSrc)) {
58
- app.send(ResourceTiming(timestamp(), 0, 0, 0, 0, 0, resolvedSrc, 'img'));
59
- }
66
+ if (img.naturalHeight === 0 && img.naturalWidth === 0 && !isSVGInFireFox(img.src)) {
67
+ sendImgError(img);
60
68
  }
61
- else if (resolvedSrc.length >= 1e5 || app.sanitizer.isMasked(id)) {
62
- sendPlaceholder(id, this);
69
+ else if (app.sanitizer.isHidden(id) || app.sanitizer.isObscured(id)) {
70
+ sendPlaceholder(id, img);
63
71
  }
64
72
  else {
65
- sendSrc(id, this);
66
- sendSrcset(id, this);
73
+ sendSrc(id, img);
74
+ sendSrcset(id, img);
67
75
  }
68
76
  });
69
77
  const observer = new MutationObserver((mutations) => {
@@ -90,9 +98,9 @@ export default function (app) {
90
98
  if (!hasTag(node, 'IMG')) {
91
99
  return;
92
100
  }
93
- app.nodes.attachElementListener('error', node, sendImgAttrs.bind(node));
94
- app.nodes.attachElementListener('load', node, sendImgAttrs.bind(node));
95
- sendImgAttrs.call(node);
101
+ app.nodes.attachNodeListener(node, 'error', () => sendImgError(node));
102
+ app.nodes.attachNodeListener(node, 'load', () => sendImgAttrs(node));
103
+ sendImgAttrs(node);
96
104
  observer.observe(node, { attributes: true, attributeFilter: ['src', 'srcset'] });
97
105
  });
98
106
  }
@@ -1,4 +1,4 @@
1
- import { normSpaces, IN_BROWSER, getLabelAttribute, hasOpenreplayAttribute } from '../utils.js';
1
+ import { normSpaces, IN_BROWSER, getLabelAttribute } from '../utils.js';
2
2
  import { hasTag } from '../app/guards.js';
3
3
  import { SetInputTarget, SetInputValue, SetInputChecked } from '../app/messages.gen.js';
4
4
  const INPUT_TYPES = ['text', 'password', 'email', 'search', 'number', 'range', 'date'];
@@ -76,10 +76,10 @@ export default function (app, opts) {
76
76
  function sendInputValue(id, node) {
77
77
  let value = node.value;
78
78
  let inputMode = options.defaultInputMode;
79
- if (node.type === 'password' || hasOpenreplayAttribute(node, 'hidden')) {
79
+ if (node.type === 'password' || app.sanitizer.isHidden(id)) {
80
80
  inputMode = 2 /* Hidden */;
81
81
  }
82
- else if (hasOpenreplayAttribute(node, 'obscured') ||
82
+ else if (app.sanitizer.isObscured(id) ||
83
83
  (inputMode === 0 /* Plain */ &&
84
84
  ((options.obscureInputNumbers && node.type !== 'date' && /\d\d\d\d/.test(value)) ||
85
85
  (options.obscureInputDates && node.type === 'date') ||
@@ -96,11 +96,13 @@ export default function (app) {
96
96
  let mousePositionChanged = false;
97
97
  let mouseTarget = null;
98
98
  let mouseTargetTime = 0;
99
+ let selectorMap = {};
99
100
  app.attachStopCallback(() => {
100
101
  mousePositionX = -1;
101
102
  mousePositionY = -1;
102
103
  mousePositionChanged = false;
103
104
  mouseTarget = null;
105
+ selectorMap = {};
104
106
  });
105
107
  const sendMouseMove = () => {
106
108
  if (mousePositionChanged) {
@@ -108,25 +110,27 @@ export default function (app) {
108
110
  mousePositionChanged = false;
109
111
  }
110
112
  };
111
- const patchDocument = (document) => {
112
- const selectorMap = {};
113
+ const patchDocument = (document, topframe = false) => {
113
114
  function getSelector(id, target) {
114
115
  return (selectorMap[id] = selectorMap[id] || _getSelector(target, document));
115
116
  }
116
- app.attachEventListener(document.documentElement, 'mouseover', (e) => {
117
+ const attachListener = topframe
118
+ ? app.attachEventListener.bind(app) // attached/removed on start/stop
119
+ : app.nodes.attachNodeListener.bind(app.nodes); // attached/removed on node register/unregister
120
+ attachListener(document.documentElement, 'mouseover', (e) => {
117
121
  const target = getTarget(e.target, document);
118
122
  if (target !== mouseTarget) {
119
123
  mouseTarget = target;
120
124
  mouseTargetTime = performance.now();
121
125
  }
122
126
  });
123
- app.attachEventListener(document, 'mousemove', (e) => {
124
- const { top, left } = app.observer.getDocumentOffset(document);
127
+ attachListener(document, 'mousemove', (e) => {
128
+ const [left, top] = app.observer.getDocumentOffset(document); // MBTODO?: document-id related message
125
129
  mousePositionX = e.clientX + left;
126
130
  mousePositionY = e.clientY + top;
127
131
  mousePositionChanged = true;
128
132
  }, false);
129
- app.attachEventListener(document, 'click', (e) => {
133
+ attachListener(document, 'click', (e) => {
130
134
  const target = getTarget(e.target, document);
131
135
  if ((!e.clientX && !e.clientY) || target === null) {
132
136
  return;
@@ -144,6 +148,6 @@ export default function (app) {
144
148
  patchDocument(node);
145
149
  }
146
150
  });
147
- patchDocument(document);
151
+ patchDocument(document, true);
148
152
  app.ticker.attach(sendMouseMove, 10);
149
153
  }
@@ -1,20 +1,33 @@
1
1
  import { SetViewportScroll, SetNodeScroll } from '../app/messages.gen.js';
2
- import { isElementNode, isRootNode } from '../app/guards.js';
2
+ import { isNode, isElementNode, isRootNode, isDocument } from '../app/guards.js';
3
+ function getDocumentScroll(doc) {
4
+ const win = doc.defaultView;
5
+ return [
6
+ (win && win.pageXOffset) ||
7
+ (doc.documentElement && doc.documentElement.scrollLeft) ||
8
+ (doc.body && doc.body.scrollLeft) ||
9
+ 0,
10
+ (win && win.pageYOffset) ||
11
+ (doc.documentElement && doc.documentElement.scrollTop) ||
12
+ (doc.body && doc.body.scrollTop) ||
13
+ 0,
14
+ ];
15
+ }
3
16
  export default function (app) {
4
17
  let documentScroll = false;
5
18
  const nodeScroll = new Map();
6
19
  function setNodeScroll(target) {
7
- if (target instanceof Element) {
20
+ if (!isNode(target)) {
21
+ return;
22
+ }
23
+ if (isElementNode(target)) {
8
24
  nodeScroll.set(target, [target.scrollLeft, target.scrollTop]);
9
25
  }
26
+ if (isDocument(target)) {
27
+ nodeScroll.set(target, getDocumentScroll(target));
28
+ }
10
29
  }
11
- const sendSetViewportScroll = app.safe(() => app.send(SetViewportScroll(window.pageXOffset ||
12
- (document.documentElement && document.documentElement.scrollLeft) ||
13
- (document.body && document.body.scrollLeft) ||
14
- 0, window.pageYOffset ||
15
- (document.documentElement && document.documentElement.scrollTop) ||
16
- (document.body && document.body.scrollTop) ||
17
- 0)));
30
+ const sendSetViewportScroll = app.safe(() => app.send(SetViewportScroll(...getDocumentScroll(document))));
18
31
  const sendSetNodeScroll = app.safe((s, node) => {
19
32
  const id = app.nodes.getID(node);
20
33
  if (id !== undefined) {
@@ -27,12 +40,19 @@ export default function (app) {
27
40
  nodeScroll.clear();
28
41
  });
29
42
  app.nodes.attachNodeCallback((node, isStart) => {
30
- if (isStart && isElementNode(node) && node.scrollLeft + node.scrollTop > 0) {
31
- nodeScroll.set(node, [node.scrollLeft, node.scrollTop]);
43
+ // MBTODO: iterate over all the nodes on start instead of using isStart hack
44
+ if (isStart) {
45
+ if (isElementNode(node) && node.scrollLeft + node.scrollTop > 0) {
46
+ nodeScroll.set(node, [node.scrollLeft, node.scrollTop]);
47
+ }
48
+ else if (isDocument(node)) {
49
+ // DRY somehow?
50
+ nodeScroll.set(node, getDocumentScroll(node));
51
+ }
32
52
  }
33
- else if (isRootNode(node)) {
53
+ if (isRootNode(node)) {
34
54
  // scroll is not-composed event (https://javascript.info/shadow-dom-events)
35
- app.attachEventListener(node, 'scroll', (e) => {
55
+ app.nodes.attachNodeListener(node, 'scroll', (e) => {
36
56
  setNodeScroll(e.target);
37
57
  });
38
58
  }
package/lib/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/lib/utils.js CHANGED
@@ -1,6 +1,12 @@
1
- export function timestamp() {
2
- return Math.round(performance.now()) + performance.timing.navigationStart;
3
- }
1
+ const DEPRECATED_ATTRS = { htmlmasked: 'hidden', masked: 'obscured' };
2
+ export const IN_BROWSER = !(typeof window === 'undefined');
3
+ export const IS_FIREFOX = IN_BROWSER && navigator.userAgent.match(/firefox|fxios/i);
4
+ export const MAX_STR_LEN = 1e5;
5
+ const navigationStart = (IN_BROWSER && performance.timing.navigationStart) || performance.timeOrigin;
6
+ // performance.now() is buggy in some browsers
7
+ export const timestamp = IN_BROWSER && performance.now() && navigationStart
8
+ ? () => Math.round(performance.now() + navigationStart)
9
+ : () => Date.now();
4
10
  export const stars = 'repeat' in String.prototype
5
11
  ? (str) => '*'.repeat(str.length)
6
12
  : (str) => str.replace(/./g, '*');
@@ -11,7 +17,6 @@ export function normSpaces(str) {
11
17
  export function isURL(s) {
12
18
  return s.startsWith('https://') || s.startsWith('http://');
13
19
  }
14
- export const IN_BROWSER = !(typeof window === 'undefined');
15
20
  // TODO: JOIN IT WITH LOGGER somehow (use logging decorators?); Don't forget about index.js loggin when there is no logger instance.
16
21
  export const DOCS_HOST = 'https://docs.openreplay.com';
17
22
  const warnedFeatures = {};
@@ -33,14 +38,15 @@ export function getLabelAttribute(e) {
33
38
  }
34
39
  return value;
35
40
  }
36
- export function hasOpenreplayAttribute(e, name) {
37
- const newName = `data-openreplay-${name}`;
41
+ export function hasOpenreplayAttribute(e, attr) {
42
+ const newName = `data-openreplay-${attr}`;
38
43
  if (e.hasAttribute(newName)) {
39
- return true;
40
- }
41
- const oldName = `data-asayer-${name}`;
42
- if (e.hasAttribute(oldName)) {
43
- deprecationWarn(`"${oldName}" attribute`, `"${newName}" attribute`, '/installation/sanitize-data');
44
+ // @ts-ignore
45
+ if (DEPRECATED_ATTRS[attr]) {
46
+ deprecationWarn(`"${newName}" attribute`,
47
+ // @ts-ignore
48
+ `"${DEPRECATED_ATTRS[attr]}" attribute`, '/installation/sanitize-data');
49
+ }
44
50
  return true;
45
51
  }
46
52
  return false;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@openreplay/tracker",
3
3
  "description": "The OpenReplay tracker main package",
4
- "version": "4.0.1",
4
+ "version": "4.1.0",
5
5
  "keywords": [
6
6
  "logging",
7
7
  "replay"
@@ -1,2 +0,0 @@
1
- import type App from '../app/index.js';
2
- export default function (app: App | null): void;
@@ -1,2 +0,0 @@
1
- import type App from '../app/index.js';
2
- export default function (app: App | null): void;