@openreplay/tracker 3.4.16 → 3.5.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 (48) hide show
  1. package/README.md +8 -1
  2. package/cjs/app/context.d.ts +18 -0
  3. package/cjs/app/context.js +48 -0
  4. package/cjs/app/index.d.ts +39 -9
  5. package/cjs/app/index.js +155 -121
  6. package/cjs/app/logger.d.ts +27 -0
  7. package/cjs/app/logger.js +42 -0
  8. package/cjs/app/observer/observer.d.ts +2 -27
  9. package/cjs/app/observer/observer.js +18 -92
  10. package/cjs/app/observer/top_observer.d.ts +3 -3
  11. package/cjs/app/observer/top_observer.js +11 -8
  12. package/cjs/app/sanitizer.d.ts +16 -0
  13. package/cjs/app/sanitizer.js +46 -0
  14. package/cjs/index.d.ts +10 -9
  15. package/cjs/index.js +32 -21
  16. package/cjs/modules/console.js +1 -1
  17. package/cjs/modules/img.js +15 -1
  18. package/cjs/modules/input.d.ts +3 -1
  19. package/cjs/modules/input.js +6 -3
  20. package/cjs/modules/mouse.js +3 -1
  21. package/cjs/utils.d.ts +0 -8
  22. package/cjs/utils.js +3 -4
  23. package/lib/app/context.d.ts +18 -0
  24. package/lib/app/context.js +43 -0
  25. package/lib/app/index.d.ts +39 -9
  26. package/lib/app/index.js +156 -122
  27. package/lib/app/logger.d.ts +27 -0
  28. package/lib/app/logger.js +39 -1
  29. package/lib/app/observer/observer.d.ts +2 -27
  30. package/lib/app/observer/observer.js +7 -79
  31. package/lib/app/observer/top_observer.d.ts +3 -3
  32. package/lib/app/observer/top_observer.js +10 -7
  33. package/lib/app/sanitizer.d.ts +16 -0
  34. package/lib/app/sanitizer.js +44 -1
  35. package/lib/index.d.ts +10 -9
  36. package/lib/index.js +32 -21
  37. package/lib/modules/console.js +1 -1
  38. package/lib/modules/img.js +16 -2
  39. package/lib/modules/input.d.ts +3 -1
  40. package/lib/modules/input.js +6 -3
  41. package/lib/modules/mouse.js +3 -1
  42. package/lib/utils.d.ts +0 -8
  43. package/lib/utils.js +2 -3
  44. package/package.json +1 -1
  45. package/cjs/app/observer.d.ts +0 -47
  46. package/cjs/app/observer.js +0 -395
  47. package/lib/app/observer.d.ts +0 -47
  48. package/lib/app/observer.js +0 -392
package/cjs/app/logger.js CHANGED
@@ -1 +1,43 @@
1
1
  "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LogLevel = void 0;
4
+ exports.LogLevel = {
5
+ Verbose: 4,
6
+ Errors: 4,
7
+ Warnings: 3,
8
+ Log: 2,
9
+ Silent: 0,
10
+ };
11
+ function IsCustomLevel(l) {
12
+ return typeof l === 'object';
13
+ }
14
+ class Logger {
15
+ constructor(options = exports.LogLevel.Silent) {
16
+ this.options = options;
17
+ this.opts = options === true
18
+ ? { level: exports.LogLevel.Verbose }
19
+ : typeof options === "number" ? { level: options } : options;
20
+ }
21
+ log(...args) {
22
+ if (IsCustomLevel(this.opts.level)
23
+ ? this.opts.level.log
24
+ : this.opts.level >= exports.LogLevel.Log) {
25
+ console.log(...args);
26
+ }
27
+ }
28
+ warn(...args) {
29
+ if (IsCustomLevel(this.opts.level)
30
+ ? this.opts.level.warn
31
+ : this.opts.level >= exports.LogLevel.Warnings) {
32
+ console.warn(...args);
33
+ }
34
+ }
35
+ error(...args) {
36
+ if (IsCustomLevel(this.opts.level)
37
+ ? this.opts.level.error
38
+ : this.opts.level >= exports.LogLevel.Errors) {
39
+ console.error(...args);
40
+ }
41
+ }
42
+ }
43
+ exports.default = Logger;
@@ -1,25 +1,5 @@
1
1
  import App from "../index.js";
2
- export interface Window extends globalThis.Window {
3
- HTMLInputElement: typeof HTMLInputElement;
4
- HTMLLinkElement: typeof HTMLLinkElement;
5
- HTMLStyleElement: typeof HTMLStyleElement;
6
- SVGStyleElement: typeof SVGStyleElement;
7
- HTMLIFrameElement: typeof HTMLIFrameElement;
8
- Text: typeof Text;
9
- Element: typeof Element;
10
- ShadowRoot: typeof ShadowRoot;
11
- }
12
- declare type WindowConstructor = Document | Element | Text | ShadowRoot | HTMLInputElement | HTMLLinkElement | HTMLStyleElement | HTMLIFrameElement;
13
- declare type Constructor<T> = {
14
- new (...args: any[]): T;
15
- name: string;
16
- };
17
- export declare function isInstance<T extends WindowConstructor>(node: Node, constr: Constructor<T>): node is T;
18
- export interface Options {
19
- obscureTextEmails: boolean;
20
- obscureTextNumbers: boolean;
21
- }
22
- export default abstract class Observer<AdditionalOptions = {}> {
2
+ export default abstract class Observer {
23
3
  protected readonly app: App;
24
4
  protected readonly context: Window;
25
5
  private readonly observer;
@@ -29,14 +9,10 @@ export default abstract class Observer<AdditionalOptions = {}> {
29
9
  private readonly indexes;
30
10
  private readonly attributesList;
31
11
  private readonly textSet;
32
- private readonly textMasked;
33
- protected readonly options: Options & AdditionalOptions;
34
12
  private readonly inUpperContext;
35
- constructor(app: App, options: Partial<Options> & AdditionalOptions, context?: Window);
13
+ constructor(app: App, context?: Window);
36
14
  private clear;
37
15
  private sendNodeAttribute;
38
- getInnerTextSecure(el: HTMLElement): string;
39
- private checkObscure;
40
16
  private sendNodeData;
41
17
  private bindNode;
42
18
  private bindTree;
@@ -47,4 +23,3 @@ export default abstract class Observer<AdditionalOptions = {}> {
47
23
  protected observeRoot(node: Node, beforeCommit: (id?: number) => unknown, nodeToBind?: Node): void;
48
24
  disconnect(): void;
49
25
  }
50
- export {};
@@ -1,39 +1,15 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.isInstance = void 0;
4
- const utils_js_1 = require("../../utils.js");
5
3
  const index_js_1 = require("../../messages/index.js");
4
+ const context_js_1 = require("../context.js");
6
5
  function isSVGElement(node) {
7
6
  return node.namespaceURI === 'http://www.w3.org/2000/svg';
8
7
  }
9
- // TODO: we need a type expert here so we won't have to ignore the lines
10
- // TODO: use it everywhere (static function; export from which file? <-- global Window typing required)
11
- function isInstance(node, constr) {
12
- const doc = node.ownerDocument;
13
- if (!doc) { // null if Document
14
- return constr.name === 'Document';
15
- }
16
- let context =
17
- // @ts-ignore (for EI, Safary)
18
- doc.parentWindow ||
19
- doc.defaultView; // TODO: smart global typing for Window object
20
- while (context.parent && context.parent !== context) {
21
- // @ts-ignore
22
- if (node instanceof context[constr.name]) {
23
- return true;
24
- }
25
- // @ts-ignore
26
- context = context.parent;
27
- }
28
- // @ts-ignore
29
- return node instanceof context[constr.name];
30
- }
31
- exports.isInstance = isInstance;
32
8
  function isIgnored(node) {
33
- if (isInstance(node, Text)) {
9
+ if ((0, context_js_1.isInstance)(node, Text)) {
34
10
  return false;
35
11
  }
36
- if (!isInstance(node, Element)) {
12
+ if (!(0, context_js_1.isInstance)(node, Element)) {
37
13
  return true;
38
14
  }
39
15
  const tag = node.tagName.toUpperCase();
@@ -49,7 +25,7 @@ function isIgnored(node) {
49
25
  tag === 'BASE');
50
26
  }
51
27
  function isRootNode(node) {
52
- return isInstance(node, Document) || isInstance(node, ShadowRoot);
28
+ return (0, context_js_1.isInstance)(node, Document) || (0, context_js_1.isInstance)(node, ShadowRoot);
53
29
  }
54
30
  function isObservable(node) {
55
31
  if (isRootNode(node)) {
@@ -58,7 +34,7 @@ function isObservable(node) {
58
34
  return !isIgnored(node);
59
35
  }
60
36
  class Observer {
61
- constructor(app, options, context = window) {
37
+ constructor(app, context = window) {
62
38
  this.app = app;
63
39
  this.context = context;
64
40
  this.commited = [];
@@ -67,34 +43,12 @@ class Observer {
67
43
  this.indexes = [];
68
44
  this.attributesList = [];
69
45
  this.textSet = new Set();
70
- this.textMasked = new Set();
71
- this.options = Object.assign({
72
- obscureTextEmails: true,
73
- obscureTextNumbers: false,
74
- }, options);
75
- this.inUpperContext = context.parent === context;
46
+ this.inUpperContext = context.parent === context; //TODO: get rid of context here
76
47
  this.observer = new MutationObserver(this.app.safe((mutations) => {
77
48
  for (const mutation of mutations) {
78
49
  const target = mutation.target;
79
50
  const type = mutation.type;
80
- // TODO TODO TODO: move to iframe_observer/remove??? (check if )
81
- // Special case
82
- // 'childList' on Document might happen in case of iframe.
83
- // TODO: generalize as much as possible
84
- // if (isInstance(target, Document) // Also ShadowRoot can be here
85
- // && type === 'childList'
86
- // //&& new Array(mutation.addedNodes).some(node => isInstance(node, HTMLHtmlElement))
87
- // ) {
88
- // const parentFrame = target.defaultView?.frameElement
89
- // if (!parentFrame) { continue }
90
- // this.bindTree(target.documentElement)
91
- // const frameID = this.app.nodes.getID(parentFrame)
92
- // const docID = this.app.nodes.getID(target.documentElement)
93
- // if (frameID === undefined || docID === undefined) { continue }
94
- // this.app.send(CreateIFrameDocument(frameID, docID));
95
- // continue;
96
- // }
97
- if (!isObservable(target) || !context.document.contains(target)) {
51
+ if (!isObservable(target) || !(0, context_js_1.inDocument)(target)) {
98
52
  continue;
99
53
  }
100
54
  if (type === 'childList') {
@@ -139,7 +93,6 @@ class Observer {
139
93
  this.indexes.length = 1;
140
94
  this.attributesList.length = 0;
141
95
  this.textSet.clear();
142
- //this.textMasked.clear();
143
96
  }
144
97
  sendNodeAttribute(id, node, name, value) {
145
98
  if (isSVGElement(node)) {
@@ -169,7 +122,7 @@ class Observer {
169
122
  return;
170
123
  }
171
124
  if (name === 'value' &&
172
- isInstance(node, HTMLInputElement) &&
125
+ (0, context_js_1.isInstance)(node, HTMLInputElement) &&
173
126
  node.type !== 'button' &&
174
127
  node.type !== 'reset' &&
175
128
  node.type !== 'submit') {
@@ -179,7 +132,7 @@ class Observer {
179
132
  this.app.send(new index_js_1.RemoveNodeAttribute(id, name));
180
133
  return;
181
134
  }
182
- if (name === 'style' || name === 'href' && isInstance(node, HTMLLinkElement)) {
135
+ if (name === 'style' || name === 'href' && (0, context_js_1.isInstance)(node, HTMLLinkElement)) {
183
136
  this.app.send(new index_js_1.SetNodeAttributeURLBased(id, name, value, this.app.getBaseHref()));
184
137
  return;
185
138
  }
@@ -188,35 +141,14 @@ class Observer {
188
141
  }
189
142
  this.app.send(new index_js_1.SetNodeAttribute(id, name, value));
190
143
  }
191
- /* TODO: abstract sanitation */
192
- getInnerTextSecure(el) {
193
- const id = this.app.nodes.getID(el);
194
- if (!id) {
195
- return '';
196
- }
197
- return this.checkObscure(id, el.innerText);
198
- }
199
- checkObscure(id, data) {
200
- if (this.textMasked.has(id)) {
201
- return data.replace(/[^\f\n\r\t\v\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]/g, '█');
202
- }
203
- if (this.options.obscureTextNumbers) {
204
- data = data.replace(/\d/g, '0');
205
- }
206
- if (this.options.obscureTextEmails) {
207
- data = data.replace(/([^\s]+)@([^\s]+)\.([^\s]+)/g, (...f) => (0, utils_js_1.stars)(f[1]) + '@' + (0, utils_js_1.stars)(f[2]) + '.' + (0, utils_js_1.stars)(f[3]));
208
- }
209
- return data;
210
- }
211
144
  sendNodeData(id, parentElement, data) {
212
- if (isInstance(parentElement, HTMLStyleElement) || isInstance(parentElement, SVGStyleElement)) {
145
+ if ((0, context_js_1.isInstance)(parentElement, HTMLStyleElement) || (0, context_js_1.isInstance)(parentElement, SVGStyleElement)) {
213
146
  this.app.send(new index_js_1.SetCSSDataURLBased(id, data, this.app.getBaseHref()));
214
147
  return;
215
148
  }
216
- data = this.checkObscure(id, data);
149
+ data = this.app.sanitizer.sanitize(id, data);
217
150
  this.app.send(new index_js_1.SetNodeData(id, data));
218
151
  }
219
- /* end TODO: abstract sanitation */
220
152
  bindNode(node) {
221
153
  const r = this.app.nodes.registerNode(node);
222
154
  const id = r[0];
@@ -254,7 +186,7 @@ class Observer {
254
186
  // Disable parent check for the upper context HTMLHtmlElement, because it is root there... (before)
255
187
  // TODO: get rid of "special" cases (there is an issue with CreateDocument altered behaviour though)
256
188
  // TODO: Clean the logic (though now it workd fine)
257
- if (!isInstance(node, HTMLHtmlElement) || !this.inUpperContext) {
189
+ if (!(0, context_js_1.isInstance)(node, HTMLHtmlElement) || !this.inUpperContext) {
258
190
  if (parent === null) {
259
191
  this.unbindNode(node);
260
192
  return false;
@@ -268,10 +200,7 @@ class Observer {
268
200
  this.unbindNode(node);
269
201
  return false;
270
202
  }
271
- if (this.textMasked.has(parentID) ||
272
- (isInstance(node, Element) && (0, utils_js_1.hasOpenreplayAttribute)(node, 'masked'))) {
273
- this.textMasked.add(id);
274
- }
203
+ this.app.sanitizer.handleNode(id, parentID, node);
275
204
  }
276
205
  let sibling = node.previousSibling;
277
206
  while (sibling !== null) {
@@ -292,7 +221,7 @@ class Observer {
292
221
  throw 'commitNode: missing node index';
293
222
  }
294
223
  if (isNew === true) {
295
- if (isInstance(node, Element)) {
224
+ if ((0, context_js_1.isInstance)(node, Element)) {
296
225
  if (parentID !== undefined) {
297
226
  this.app.send(new index_js_1.CreateElementNode(id, parentID, index, node.tagName, isSVGElement(node)));
298
227
  }
@@ -301,7 +230,7 @@ class Observer {
301
230
  this.sendNodeAttribute(id, node, attr.nodeName, attr.value);
302
231
  }
303
232
  }
304
- else if (isInstance(node, Text)) {
233
+ else if ((0, context_js_1.isInstance)(node, Text)) {
305
234
  // for text node id != 0, hence parentID !== undefined and parent is Element
306
235
  this.app.send(new index_js_1.CreateTextNode(id, parentID, index));
307
236
  this.sendNodeData(id, parent, node.data);
@@ -313,7 +242,7 @@ class Observer {
313
242
  }
314
243
  const attr = this.attributesList[id];
315
244
  if (attr !== undefined) {
316
- if (!isInstance(node, Element)) {
245
+ if (!(0, context_js_1.isInstance)(node, Element)) {
317
246
  throw 'commitNode: node is not an element';
318
247
  }
319
248
  for (const name of attr) {
@@ -321,7 +250,7 @@ class Observer {
321
250
  }
322
251
  }
323
252
  if (this.textSet.has(id)) {
324
- if (!isInstance(node, Text)) {
253
+ if (!(0, context_js_1.isInstance)(node, Text)) {
325
254
  throw 'commitNode: node is not a text';
326
255
  }
327
256
  // for text node id != 0, hence parent is Element
@@ -344,8 +273,7 @@ class Observer {
344
273
  let node;
345
274
  for (let id = 0; id < this.recents.length; id++) {
346
275
  // TODO: make things/logic nice here.
347
- // commit required in any case if recents[id] true or false (in case of unbinding).
348
- // ???!?!?R@TW:$HKJ$WLKn
276
+ // commit required in any case if recents[id] true or false (in case of unbinding) or undefined (in case of attr change).
349
277
  if (!this.myNodes[id]) {
350
278
  continue;
351
279
  }
@@ -373,8 +301,6 @@ class Observer {
373
301
  disconnect() {
374
302
  this.observer.disconnect();
375
303
  this.clear();
376
- // to sanitizer
377
- this.textMasked.clear();
378
304
  this.myNodes.length = 0;
379
305
  }
380
306
  }
@@ -1,10 +1,10 @@
1
1
  import Observer from "./observer.js";
2
- import type { Options as BaseOptions } from "./observer.js";
3
2
  import App from "../index.js";
4
- export interface Options extends Partial<BaseOptions> {
3
+ export interface Options {
5
4
  captureIFrames: boolean;
6
5
  }
7
- export default class TopObserver extends Observer<Options> {
6
+ export default class TopObserver extends Observer {
7
+ private readonly options;
8
8
  constructor(app: App, options: Partial<Options>);
9
9
  private iframeObservers;
10
10
  private handleIframe;
@@ -1,27 +1,30 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const observer_js_1 = require("./observer.js");
4
+ const context_js_1 = require("../context.js");
4
5
  const iframe_observer_js_1 = require("./iframe_observer.js");
5
6
  const shadow_root_observer_js_1 = require("./shadow_root_observer.js");
6
7
  const index_js_1 = require("../../messages/index.js");
7
- const attachShadowNativeFn = Element.prototype.attachShadow;
8
+ const utils_js_1 = require("../../utils.js");
9
+ const attachShadowNativeFn = utils_js_1.IN_BROWSER ? Element.prototype.attachShadow : () => new ShadowRoot();
8
10
  class TopObserver extends observer_js_1.default {
9
11
  constructor(app, options) {
10
- super(app, Object.assign({
11
- captureIFrames: false
12
- }, options));
12
+ super(app);
13
13
  this.iframeObservers = [];
14
14
  this.shadowRootObservers = [];
15
+ this.options = Object.assign({
16
+ captureIFrames: false
17
+ }, options);
15
18
  // IFrames
16
19
  this.app.nodes.attachNodeCallback(node => {
17
- if ((0, observer_js_1.isInstance)(node, HTMLIFrameElement) &&
20
+ if ((0, context_js_1.isInstance)(node, HTMLIFrameElement) &&
18
21
  (this.options.captureIFrames || node.getAttribute("data-openreplay-capture"))) {
19
22
  this.handleIframe(node);
20
23
  }
21
24
  });
22
25
  // ShadowDOM
23
26
  this.app.nodes.attachNodeCallback(node => {
24
- if ((0, observer_js_1.isInstance)(node, Element) && node.shadowRoot !== null) {
27
+ if ((0, context_js_1.isInstance)(node, Element) && node.shadowRoot !== null) {
25
28
  this.handleShadowRoot(node.shadowRoot);
26
29
  }
27
30
  });
@@ -40,7 +43,7 @@ class TopObserver extends observer_js_1.default {
40
43
  if (!context) {
41
44
  return;
42
45
  }
43
- const observer = new iframe_observer_js_1.default(this.app, this.options, context);
46
+ const observer = new iframe_observer_js_1.default(this.app, context);
44
47
  this.iframeObservers.push(observer);
45
48
  observer.observe(iframe);
46
49
  });
@@ -48,7 +51,7 @@ class TopObserver extends observer_js_1.default {
48
51
  handle();
49
52
  }
50
53
  handleShadowRoot(shRoot) {
51
- const observer = new shadow_root_observer_js_1.default(this.app, this.options, this.context);
54
+ const observer = new shadow_root_observer_js_1.default(this.app, this.context);
52
55
  this.shadowRootObservers.push(observer);
53
56
  observer.observe(shRoot.host);
54
57
  }
@@ -0,0 +1,16 @@
1
+ import App from "./index.js";
2
+ export interface Options {
3
+ obscureTextEmails: boolean;
4
+ obscureTextNumbers: boolean;
5
+ }
6
+ export default class Sanitizer {
7
+ private readonly app;
8
+ private readonly masked;
9
+ private readonly options;
10
+ constructor(app: App, options: Partial<Options>);
11
+ handleNode(id: number, parentID: number, node: Node): void;
12
+ sanitize(id: number, data: string): string;
13
+ isMasked(id: number): boolean;
14
+ getInnerTextSecure(el: HTMLElement): string;
15
+ clear(): void;
16
+ }
@@ -1 +1,47 @@
1
1
  "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const utils_js_1 = require("../utils.js");
4
+ const context_js_1 = require("./context.js");
5
+ class Sanitizer {
6
+ constructor(app, options) {
7
+ this.app = app;
8
+ this.masked = new Set();
9
+ this.options = Object.assign({
10
+ obscureTextEmails: true,
11
+ obscureTextNumbers: false,
12
+ }, options);
13
+ }
14
+ handleNode(id, parentID, node) {
15
+ if (this.masked.has(parentID) ||
16
+ ((0, context_js_1.isInstance)(node, Element) && (0, utils_js_1.hasOpenreplayAttribute)(node, 'masked'))) {
17
+ this.masked.add(id);
18
+ }
19
+ }
20
+ sanitize(id, data) {
21
+ if (this.masked.has(id)) {
22
+ // TODO: is it the best place to put trim() ? Might trimmed spaces be considered in layout in certain cases?
23
+ return data.trim().replace(/[^\f\n\r\t\v\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]/g, '█');
24
+ }
25
+ if (this.options.obscureTextNumbers) {
26
+ data = data.replace(/\d/g, '0');
27
+ }
28
+ if (this.options.obscureTextEmails) {
29
+ data = data.replace(/([^\s]+)@([^\s]+)\.([^\s]+)/g, (...f) => (0, utils_js_1.stars)(f[1]) + '@' + (0, utils_js_1.stars)(f[2]) + '.' + (0, utils_js_1.stars)(f[3]));
30
+ }
31
+ return data;
32
+ }
33
+ isMasked(id) {
34
+ return this.masked.has(id);
35
+ }
36
+ getInnerTextSecure(el) {
37
+ const id = this.app.nodes.getID(el);
38
+ if (!id) {
39
+ return '';
40
+ }
41
+ return this.sanitize(id, el.innerText);
42
+ }
43
+ clear() {
44
+ this.masked.clear();
45
+ }
46
+ }
47
+ exports.default = Sanitizer;
package/cjs/index.d.ts CHANGED
@@ -2,18 +2,20 @@ import App from "./app/index.js";
2
2
  export { default as App } from './app/index.js';
3
3
  import * as _Messages from "./messages/index.js";
4
4
  export declare const Messages: typeof _Messages;
5
- import { Options as AppOptions } from "./app/index.js";
6
- import { Options as ConsoleOptions } from "./modules/console.js";
7
- import { Options as ExceptionOptions } from "./modules/exception.js";
8
- import { Options as InputOptions } from "./modules/input.js";
9
- import { Options as PerformanceOptions } from "./modules/performance.js";
10
- import { Options as TimingOptions } from "./modules/timing.js";
11
- export type { OnStartInfo } from './app/index.js';
5
+ import type { Options as AppOptions } from "./app/index.js";
6
+ import type { Options as ConsoleOptions } from "./modules/console.js";
7
+ import type { Options as ExceptionOptions } from "./modules/exception.js";
8
+ import type { Options as InputOptions } from "./modules/input.js";
9
+ import type { Options as PerformanceOptions } from "./modules/performance.js";
10
+ import type { Options as TimingOptions } from "./modules/timing.js";
11
+ import type { StartOptions } from './app/index.js';
12
+ import type { OnStartInfo } from './app/index.js';
12
13
  export declare type Options = Partial<AppOptions & ConsoleOptions & ExceptionOptions & InputOptions & PerformanceOptions & TimingOptions> & {
13
14
  projectID?: number;
14
15
  projectKey: string;
15
16
  sessionToken?: string;
16
17
  respectDoNotTrack?: boolean;
18
+ autoResetOnWindowOpen?: boolean;
17
19
  __DISABLE_SECURE_MODE?: boolean;
18
20
  };
19
21
  export default class API {
@@ -22,8 +24,7 @@ export default class API {
22
24
  constructor(options: Options);
23
25
  use<T>(fn: (app: App | null, options?: Options) => T): T;
24
26
  isActive(): boolean;
25
- active(): boolean;
26
- start(): Promise<import("./app/index.js").OnStartInfo>;
27
+ start(startOpts?: StartOptions): Promise<OnStartInfo>;
27
28
  stop(): void;
28
29
  getSessionToken(): string | null | undefined;
29
30
  getSessionID(): string | null | undefined;
package/cjs/index.js CHANGED
@@ -81,7 +81,7 @@ class API {
81
81
  (navigator.doNotTrack == '1'
82
82
  // @ts-ignore
83
83
  || window.doNotTrack == '1');
84
- this.app = doNotTrack ||
84
+ const app = this.app = doNotTrack ||
85
85
  !('Map' in window) ||
86
86
  !('Set' in window) ||
87
87
  !('MutationObserver' in window) ||
@@ -92,20 +92,34 @@ class API {
92
92
  !('Worker' in window)
93
93
  ? null
94
94
  : new index_js_1.default(options.projectKey, options.sessionToken, options);
95
- if (this.app !== null) {
96
- (0, viewport_js_1.default)(this.app);
97
- (0, cssrules_js_1.default)(this.app);
98
- (0, connection_js_1.default)(this.app);
99
- (0, console_js_1.default)(this.app, options);
100
- (0, exception_js_1.default)(this.app, options);
101
- (0, img_js_1.default)(this.app);
102
- (0, input_js_1.default)(this.app, options);
103
- (0, mouse_js_1.default)(this.app);
104
- (0, timing_js_1.default)(this.app, options);
105
- (0, performance_js_1.default)(this.app, options);
106
- (0, scroll_js_1.default)(this.app);
107
- (0, longtasks_js_1.default)(this.app);
95
+ if (app !== null) {
96
+ (0, viewport_js_1.default)(app);
97
+ (0, cssrules_js_1.default)(app);
98
+ (0, connection_js_1.default)(app);
99
+ (0, console_js_1.default)(app, options);
100
+ (0, exception_js_1.default)(app, options);
101
+ (0, img_js_1.default)(app);
102
+ (0, input_js_1.default)(app, options);
103
+ (0, mouse_js_1.default)(app);
104
+ (0, timing_js_1.default)(app, options);
105
+ (0, performance_js_1.default)(app, options);
106
+ (0, scroll_js_1.default)(app);
107
+ (0, longtasks_js_1.default)(app);
108
108
  window.__OPENREPLAY__ = this;
109
+ if (options.autoResetOnWindowOpen) {
110
+ const wOpen = window.open;
111
+ app.attachStartCallback(() => {
112
+ // @ts-ignore ?
113
+ window.open = function (...args) {
114
+ app.resetNextPageSession(true);
115
+ wOpen.call(window, ...args);
116
+ app.resetNextPageSession(false);
117
+ };
118
+ });
119
+ app.attachStopCallback(() => {
120
+ window.open = wOpen;
121
+ });
122
+ }
109
123
  }
110
124
  else {
111
125
  console.log("OpenReplay: browser doesn't support API required for tracking or doNotTrack is set to 1.");
@@ -115,7 +129,7 @@ class API {
115
129
  // no-cors issue only with text/plain or not-set Content-Type
116
130
  // req.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
117
131
  req.send(JSON.stringify({
118
- trackerVersion: '3.4.16',
132
+ trackerVersion: '3.5.0',
119
133
  projectKey: options.projectKey,
120
134
  doNotTrack,
121
135
  // TODO: add precise reason (an exact API missing)
@@ -131,11 +145,7 @@ class API {
131
145
  }
132
146
  return this.app.active();
133
147
  }
134
- active() {
135
- (0, utils_js_1.deprecationWarn)("'active' method", "'isActive' method", "/");
136
- return this.isActive();
137
- }
138
- start() {
148
+ start(startOpts) {
139
149
  if (!utils_js_1.IN_BROWSER) {
140
150
  console.error(`OpenReplay: you are trying to start Tracker on a node.js environment. If you want to use OpenReplay with SSR, please, use componentDidMount or useEffect API for placing the \`tracker.start()\` line. Check documentation on ${utils_js_1.DOCS_HOST}${DOCS_SETUP}`);
141
151
  return Promise.reject("Trying to start not in browser.");
@@ -143,7 +153,8 @@ class API {
143
153
  if (this.app === null) {
144
154
  return Promise.reject("Browser doesn't support required api, or doNotTrack is active.");
145
155
  }
146
- return this.app.start();
156
+ // TODO: check argument typing
157
+ return this.app.start(startOpts);
147
158
  }
148
159
  stop() {
149
160
  if (this.app === null) {
@@ -18,7 +18,7 @@ function printString(arg) {
18
18
  if (Array.isArray(arg)) {
19
19
  return `Array(${arg.length})`;
20
20
  }
21
- return arg.toString();
21
+ return String(arg);
22
22
  }
23
23
  function printFloat(arg) {
24
24
  if (typeof arg !== 'number')
@@ -2,7 +2,18 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const utils_js_1 = require("../utils.js");
4
4
  const index_js_1 = require("../messages/index.js");
5
+ const PLACEHOLDER_SRC = "https://static.openreplay.com/tracker/placeholder.jpeg";
5
6
  function default_1(app) {
7
+ function sendPlaceholder(id, node) {
8
+ app.send(new index_js_1.SetNodeAttribute(id, "src", PLACEHOLDER_SRC));
9
+ const { width, height } = node.getBoundingClientRect();
10
+ if (!node.hasAttribute("width")) {
11
+ app.send(new index_js_1.SetNodeAttribute(id, "width", String(width)));
12
+ }
13
+ if (!node.hasAttribute("height")) {
14
+ app.send(new index_js_1.SetNodeAttribute(id, "height", String(height)));
15
+ }
16
+ }
6
17
  const sendImgSrc = app.safe(function () {
7
18
  const id = app.nodes.getID(this);
8
19
  if (id === undefined) {
@@ -17,7 +28,10 @@ function default_1(app) {
17
28
  app.send(new index_js_1.ResourceTiming((0, utils_js_1.timestamp)(), 0, 0, 0, 0, 0, src, 'img'));
18
29
  }
19
30
  }
20
- else if (src.length < 1e5) {
31
+ else if (src.length >= 1e5 || app.sanitizer.isMasked(id)) {
32
+ sendPlaceholder(id, this);
33
+ }
34
+ else {
21
35
  app.send(new index_js_1.SetNodeAttributeURLBased(id, 'src', src, app.getBaseHref()));
22
36
  }
23
37
  });
@@ -1,5 +1,6 @@
1
1
  import App from "../app/index.js";
2
- export declare function getInputLabel(node: HTMLInputElement): string;
2
+ declare type TextEditableElement = HTMLInputElement | HTMLTextAreaElement;
3
+ export declare function getInputLabel(node: TextEditableElement): string;
3
4
  export declare const enum InputMode {
4
5
  Plain = 0,
5
6
  Obscured = 1,
@@ -11,3 +12,4 @@ export interface Options {
11
12
  defaultInputMode: InputMode;
12
13
  }
13
14
  export default function (app: App, opts: Partial<Options>): void;
15
+ export {};
@@ -3,7 +3,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getInputLabel = void 0;
4
4
  const utils_js_1 = require("../utils.js");
5
5
  const index_js_1 = require("../messages/index.js");
6
- function isInput(node) {
6
+ function isTextEditable(node) {
7
+ if (node instanceof HTMLTextAreaElement) {
8
+ return true;
9
+ }
7
10
  if (!(node instanceof HTMLInputElement)) {
8
11
  return false;
9
12
  }
@@ -111,7 +114,7 @@ function default_1(app, opts) {
111
114
  app.ticker.attach(() => {
112
115
  inputValues.forEach((value, id) => {
113
116
  const node = app.nodes.getNode(id);
114
- if (!isInput(node)) {
117
+ if (!isTextEditable(node)) {
115
118
  inputValues.delete(id);
116
119
  return;
117
120
  }
@@ -142,7 +145,7 @@ function default_1(app, opts) {
142
145
  if (id === undefined) {
143
146
  return;
144
147
  }
145
- if (isInput(node)) {
148
+ if (isTextEditable(node)) {
146
149
  inputValues.set(id, node.value);
147
150
  sendInputValue(id, node);
148
151
  return;