@openreplay/tracker 3.5.15-beta.0 → 3.5.16-beta.2

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.
@@ -1,5 +1,5 @@
1
1
  import type Message from "../common/messages.js";
2
- import Nodes from "./nodes.js";
2
+ import Nodes, { CheckOptions } from "./nodes.js";
3
3
  import Sanitizer from "./sanitizer.js";
4
4
  import Ticker from "./ticker.js";
5
5
  import Logger from "./logger.js";
@@ -46,8 +46,10 @@ declare type AppOptions = {
46
46
  __debug__?: LoggerOptions;
47
47
  localStorage: Storage;
48
48
  sessionStorage: Storage;
49
+ maxMemorySize: number;
50
+ memoryCheckInterval: number;
49
51
  onStart?: StartCallback;
50
- } & WebworkerOptions;
52
+ } & WebworkerOptions & CheckOptions;
51
53
  export declare type Options = AppOptions & ObserverOptions & SanitizerOptions;
52
54
  export declare const DEFAULT_INGEST_POINT = "https://api.openreplay.com/ingest";
53
55
  export default class App {
@@ -103,6 +105,7 @@ export default class App {
103
105
  resetNextPageSession(flag: boolean): void;
104
106
  private _start;
105
107
  start(options?: StartOptions): Promise<StartPromiseReturn>;
106
- stop(calledFromAPI?: boolean): void;
108
+ stop(calledFromAPI?: boolean, restarting?: boolean): void;
109
+ restart(): void;
107
110
  }
108
111
  export {};
package/cjs/app/index.js CHANGED
@@ -27,12 +27,13 @@ class App {
27
27
  // if (options.onStart !== undefined) {
28
28
  // deprecationWarn("'onStart' option", "tracker.start().then(/* handle session info */)")
29
29
  // } ?? maybe onStart is good
30
+ // private gc?: NodeJS.Timer = undefined;
30
31
  this.messages = [];
31
32
  this.startCallbacks = [];
32
33
  this.stopCallbacks = [];
33
34
  this.commitCallbacks = [];
34
35
  this.activityState = ActivityState.NotActive;
35
- this.version = '3.5.15-beta.0'; // TODO: version compatability check inside each plugin.
36
+ this.version = '3.5.16-beta.2'; // TODO: version compatability check inside each plugin.
36
37
  this.projectKey = projectKey;
37
38
  this.options = Object.assign({
38
39
  revID: '',
@@ -48,10 +49,12 @@ class App {
48
49
  __debug_report_edp: null,
49
50
  localStorage: window.localStorage,
50
51
  sessionStorage: window.sessionStorage,
52
+ maxMemorySize: 550 * 1e6,
53
+ memoryCheckInterval: 2 * 60 * 1000,
51
54
  }, options);
52
55
  this.revID = this.options.revID;
53
56
  this.sanitizer = new sanitizer_js_1.default(this, options);
54
- this.nodes = new nodes_js_1.default(this.options.node_id);
57
+ this.nodes = new nodes_js_1.default(this.options.node_id, this.options.maxMemorySize, this.options.memoryCheckInterval);
55
58
  this.observer = new top_observer_js_1.default(this, options);
56
59
  this.ticker = new ticker_js_1.default(this);
57
60
  this.ticker.attach(() => this.commit());
@@ -143,6 +146,7 @@ class App {
143
146
  fn.apply(this, args);
144
147
  }
145
148
  catch (e) {
149
+ console.error(e);
146
150
  app._debug("safe_fn_call", e);
147
151
  // time: timestamp(),
148
152
  // name: e.name,
@@ -316,6 +320,16 @@ class App {
316
320
  this.observer.observe();
317
321
  this.ticker.start();
318
322
  this.notify.log("OpenReplay tracking started.");
323
+ // // GC
324
+ // if (!this.gc) {
325
+ // this.gc = setInterval(() => {
326
+ // console.log('checking')
327
+ // // @ts-ignore
328
+ // if (window.performance.memory.usedJSHeapSize > this.options.maxMemorySize) {
329
+ // this.restart();
330
+ // }
331
+ // }, this.options.memoryCheckInterval)
332
+ // }
319
333
  // get rid of onStart ?
320
334
  if (typeof this.options.onStart === 'function') {
321
335
  this.options.onStart(onStartInfo);
@@ -349,9 +363,10 @@ class App {
349
363
  });
350
364
  }
351
365
  }
352
- stop(calledFromAPI = false) {
366
+ stop(calledFromAPI = false, restarting = false) {
353
367
  if (this.activityState !== ActivityState.NotActive) {
354
368
  try {
369
+ // this.gc && clearInterval(this.gc)
355
370
  this.sanitizer.clear();
356
371
  this.observer.disconnect();
357
372
  this.nodes.clear();
@@ -361,7 +376,7 @@ class App {
361
376
  this.session.reset();
362
377
  }
363
378
  this.notify.log("OpenReplay tracking stopped.");
364
- if (this.worker) {
379
+ if (this.worker && !restarting) {
365
380
  this.worker.postMessage("stop");
366
381
  }
367
382
  }
@@ -370,5 +385,9 @@ class App {
370
385
  }
371
386
  }
372
387
  }
388
+ restart() {
389
+ this.stop(false, true);
390
+ this.start({ forceNew: false });
391
+ }
373
392
  }
374
393
  exports.default = App;
@@ -1,15 +1,21 @@
1
+ /// <reference types="node" resolution-mode="require"/>
1
2
  declare type NodeCallback = (node: Node, isStart: boolean) => void;
3
+ export interface CheckOptions {
4
+ maxMemorySize: number;
5
+ memoryCheckInterval: number;
6
+ }
2
7
  export default class Nodes {
3
8
  private readonly node_id;
4
9
  private nodes;
5
10
  private readonly nodeCallbacks;
6
11
  private readonly elementListeners;
7
- constructor(node_id: string);
12
+ gc: NodeJS.Timer | undefined;
13
+ constructor(node_id: string, maxMemorySize: CheckOptions["maxMemorySize"], memoryCheckInterval: CheckOptions["memoryCheckInterval"]);
8
14
  attachNodeCallback(nodeCallback: NodeCallback): void;
9
15
  attachElementListener(type: string, node: Element, elementListener: EventListener): void;
10
16
  registerNode(node: Node): [id: number, isNew: boolean];
11
17
  unregisterNode(node: Node): number | undefined;
12
- cleanTree(): (Node | undefined)[];
18
+ cleanTree(): void;
13
19
  callNodeCallbacks(node: Node, isStart: boolean): void;
14
20
  getID(node: Node): number | undefined;
15
21
  getNode(id: number): Node | undefined;
package/cjs/app/nodes.js CHANGED
@@ -1,11 +1,17 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  class Nodes {
4
- constructor(node_id) {
4
+ constructor(node_id, maxMemorySize, memoryCheckInterval) {
5
5
  this.node_id = node_id;
6
6
  this.nodes = [];
7
7
  this.nodeCallbacks = [];
8
8
  this.elementListeners = new Map();
9
+ this.gc = setInterval(() => {
10
+ // @ts-ignore should use settings object here
11
+ if (window.performance.memory.usedJSHeapSize > maxMemorySize) {
12
+ this.cleanTree();
13
+ }
14
+ }, memoryCheckInterval);
9
15
  }
10
16
  attachNodeCallback(nodeCallback) {
11
17
  this.nodeCallbacks.push(nodeCallback);
@@ -48,7 +54,21 @@ class Nodes {
48
54
  return id;
49
55
  }
50
56
  cleanTree() {
51
- return this.nodes = this.nodes.filter((n) => n !== undefined);
57
+ // sadly we keep empty items in array here resulting in some memory still being used
58
+ // but its still better than keeping dead nodes or undef elements
59
+ // plus we keep our index positions for new/alive nodes
60
+ // performance test: 3ms for 30k nodes with 17k dead ones
61
+ for (let i = 0; i < this.nodes.length; i++) {
62
+ const node = this.nodes[i];
63
+ if (node === undefined) {
64
+ delete this.nodes[i];
65
+ continue;
66
+ }
67
+ if (!document.contains(node)) {
68
+ this.unregisterNode(node);
69
+ delete this.nodes[i];
70
+ }
71
+ }
52
72
  }
53
73
  callNodeCallbacks(node, isStart) {
54
74
  this.nodeCallbacks.forEach((cb) => cb(node, isStart));
@@ -68,6 +88,10 @@ class Nodes {
68
88
  this.unregisterNode(node);
69
89
  }
70
90
  this.nodes.length = 0;
91
+ if (this.gc) {
92
+ clearInterval(this.gc);
93
+ this.gc = undefined;
94
+ }
71
95
  }
72
96
  }
73
97
  exports.default = Nodes;
@@ -51,16 +51,12 @@ class Observer {
51
51
  for (const mutation of mutations) { // mutations order is sequential
52
52
  const target = mutation.target;
53
53
  const type = mutation.type;
54
- // const deleted: number[] = [];
55
54
  if (!isObservable(target)) {
56
55
  continue;
57
56
  }
58
57
  if (type === 'childList') {
59
58
  for (let i = 0; i < mutation.removedNodes.length; i++) {
60
- // this.bindTree(mutation.removedNodes[i]);
61
- const id = this.unbindNode(mutation.removedNodes[i]);
62
- // id && this.recents.delete(id)
63
- // id && deleted.push(id)
59
+ this.bindTree(mutation.removedNodes[i]);
64
60
  }
65
61
  for (let i = 0; i < mutation.addedNodes.length; i++) {
66
62
  this.bindTree(mutation.addedNodes[i]);
@@ -92,7 +88,6 @@ class Observer {
92
88
  }
93
89
  }
94
90
  this.commitNodes();
95
- this.app.nodes.cleanTree();
96
91
  }));
97
92
  }
98
93
  clear() {
package/cjs/index.js CHANGED
@@ -127,7 +127,7 @@ class API {
127
127
  // no-cors issue only with text/plain or not-set Content-Type
128
128
  // req.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
129
129
  req.send(JSON.stringify({
130
- trackerVersion: '3.5.15-beta.0',
130
+ trackerVersion: '3.5.16-beta.2',
131
131
  projectKey: options.projectKey,
132
132
  doNotTrack,
133
133
  // TODO: add precise reason (an exact API missing)
@@ -1,5 +1,5 @@
1
1
  import type Message from "../common/messages.js";
2
- import Nodes from "./nodes.js";
2
+ import Nodes, { CheckOptions } from "./nodes.js";
3
3
  import Sanitizer from "./sanitizer.js";
4
4
  import Ticker from "./ticker.js";
5
5
  import Logger from "./logger.js";
@@ -46,8 +46,10 @@ declare type AppOptions = {
46
46
  __debug__?: LoggerOptions;
47
47
  localStorage: Storage;
48
48
  sessionStorage: Storage;
49
+ maxMemorySize: number;
50
+ memoryCheckInterval: number;
49
51
  onStart?: StartCallback;
50
- } & WebworkerOptions;
52
+ } & WebworkerOptions & CheckOptions;
51
53
  export declare type Options = AppOptions & ObserverOptions & SanitizerOptions;
52
54
  export declare const DEFAULT_INGEST_POINT = "https://api.openreplay.com/ingest";
53
55
  export default class App {
@@ -103,6 +105,7 @@ export default class App {
103
105
  resetNextPageSession(flag: boolean): void;
104
106
  private _start;
105
107
  start(options?: StartOptions): Promise<StartPromiseReturn>;
106
- stop(calledFromAPI?: boolean): void;
108
+ stop(calledFromAPI?: boolean, restarting?: boolean): void;
109
+ restart(): void;
107
110
  }
108
111
  export {};
package/lib/app/index.js CHANGED
@@ -24,12 +24,13 @@ export default class App {
24
24
  // if (options.onStart !== undefined) {
25
25
  // deprecationWarn("'onStart' option", "tracker.start().then(/* handle session info */)")
26
26
  // } ?? maybe onStart is good
27
+ // private gc?: NodeJS.Timer = undefined;
27
28
  this.messages = [];
28
29
  this.startCallbacks = [];
29
30
  this.stopCallbacks = [];
30
31
  this.commitCallbacks = [];
31
32
  this.activityState = ActivityState.NotActive;
32
- this.version = '3.5.15-beta.0'; // TODO: version compatability check inside each plugin.
33
+ this.version = '3.5.16-beta.2'; // TODO: version compatability check inside each plugin.
33
34
  this.projectKey = projectKey;
34
35
  this.options = Object.assign({
35
36
  revID: '',
@@ -45,10 +46,12 @@ export default class App {
45
46
  __debug_report_edp: null,
46
47
  localStorage: window.localStorage,
47
48
  sessionStorage: window.sessionStorage,
49
+ maxMemorySize: 550 * 1e6,
50
+ memoryCheckInterval: 2 * 60 * 1000,
48
51
  }, options);
49
52
  this.revID = this.options.revID;
50
53
  this.sanitizer = new Sanitizer(this, options);
51
- this.nodes = new Nodes(this.options.node_id);
54
+ this.nodes = new Nodes(this.options.node_id, this.options.maxMemorySize, this.options.memoryCheckInterval);
52
55
  this.observer = new Observer(this, options);
53
56
  this.ticker = new Ticker(this);
54
57
  this.ticker.attach(() => this.commit());
@@ -140,6 +143,7 @@ export default class App {
140
143
  fn.apply(this, args);
141
144
  }
142
145
  catch (e) {
146
+ console.error(e);
143
147
  app._debug("safe_fn_call", e);
144
148
  // time: timestamp(),
145
149
  // name: e.name,
@@ -313,6 +317,16 @@ export default class App {
313
317
  this.observer.observe();
314
318
  this.ticker.start();
315
319
  this.notify.log("OpenReplay tracking started.");
320
+ // // GC
321
+ // if (!this.gc) {
322
+ // this.gc = setInterval(() => {
323
+ // console.log('checking')
324
+ // // @ts-ignore
325
+ // if (window.performance.memory.usedJSHeapSize > this.options.maxMemorySize) {
326
+ // this.restart();
327
+ // }
328
+ // }, this.options.memoryCheckInterval)
329
+ // }
316
330
  // get rid of onStart ?
317
331
  if (typeof this.options.onStart === 'function') {
318
332
  this.options.onStart(onStartInfo);
@@ -346,9 +360,10 @@ export default class App {
346
360
  });
347
361
  }
348
362
  }
349
- stop(calledFromAPI = false) {
363
+ stop(calledFromAPI = false, restarting = false) {
350
364
  if (this.activityState !== ActivityState.NotActive) {
351
365
  try {
366
+ // this.gc && clearInterval(this.gc)
352
367
  this.sanitizer.clear();
353
368
  this.observer.disconnect();
354
369
  this.nodes.clear();
@@ -358,7 +373,7 @@ export default class App {
358
373
  this.session.reset();
359
374
  }
360
375
  this.notify.log("OpenReplay tracking stopped.");
361
- if (this.worker) {
376
+ if (this.worker && !restarting) {
362
377
  this.worker.postMessage("stop");
363
378
  }
364
379
  }
@@ -367,4 +382,8 @@ export default class App {
367
382
  }
368
383
  }
369
384
  }
385
+ restart() {
386
+ this.stop(false, true);
387
+ this.start({ forceNew: false });
388
+ }
370
389
  }
@@ -1,15 +1,21 @@
1
+ /// <reference types="node" resolution-mode="require"/>
1
2
  declare type NodeCallback = (node: Node, isStart: boolean) => void;
3
+ export interface CheckOptions {
4
+ maxMemorySize: number;
5
+ memoryCheckInterval: number;
6
+ }
2
7
  export default class Nodes {
3
8
  private readonly node_id;
4
9
  private nodes;
5
10
  private readonly nodeCallbacks;
6
11
  private readonly elementListeners;
7
- constructor(node_id: string);
12
+ gc: NodeJS.Timer | undefined;
13
+ constructor(node_id: string, maxMemorySize: CheckOptions["maxMemorySize"], memoryCheckInterval: CheckOptions["memoryCheckInterval"]);
8
14
  attachNodeCallback(nodeCallback: NodeCallback): void;
9
15
  attachElementListener(type: string, node: Element, elementListener: EventListener): void;
10
16
  registerNode(node: Node): [id: number, isNew: boolean];
11
17
  unregisterNode(node: Node): number | undefined;
12
- cleanTree(): (Node | undefined)[];
18
+ cleanTree(): void;
13
19
  callNodeCallbacks(node: Node, isStart: boolean): void;
14
20
  getID(node: Node): number | undefined;
15
21
  getNode(id: number): Node | undefined;
package/lib/app/nodes.js CHANGED
@@ -1,9 +1,15 @@
1
1
  export default class Nodes {
2
- constructor(node_id) {
2
+ constructor(node_id, maxMemorySize, memoryCheckInterval) {
3
3
  this.node_id = node_id;
4
4
  this.nodes = [];
5
5
  this.nodeCallbacks = [];
6
6
  this.elementListeners = new Map();
7
+ this.gc = setInterval(() => {
8
+ // @ts-ignore should use settings object here
9
+ if (window.performance.memory.usedJSHeapSize > maxMemorySize) {
10
+ this.cleanTree();
11
+ }
12
+ }, memoryCheckInterval);
7
13
  }
8
14
  attachNodeCallback(nodeCallback) {
9
15
  this.nodeCallbacks.push(nodeCallback);
@@ -46,7 +52,21 @@ export default class Nodes {
46
52
  return id;
47
53
  }
48
54
  cleanTree() {
49
- return this.nodes = this.nodes.filter((n) => n !== undefined);
55
+ // sadly we keep empty items in array here resulting in some memory still being used
56
+ // but its still better than keeping dead nodes or undef elements
57
+ // plus we keep our index positions for new/alive nodes
58
+ // performance test: 3ms for 30k nodes with 17k dead ones
59
+ for (let i = 0; i < this.nodes.length; i++) {
60
+ const node = this.nodes[i];
61
+ if (node === undefined) {
62
+ delete this.nodes[i];
63
+ continue;
64
+ }
65
+ if (!document.contains(node)) {
66
+ this.unregisterNode(node);
67
+ delete this.nodes[i];
68
+ }
69
+ }
50
70
  }
51
71
  callNodeCallbacks(node, isStart) {
52
72
  this.nodeCallbacks.forEach((cb) => cb(node, isStart));
@@ -66,5 +86,9 @@ export default class Nodes {
66
86
  this.unregisterNode(node);
67
87
  }
68
88
  this.nodes.length = 0;
89
+ if (this.gc) {
90
+ clearInterval(this.gc);
91
+ this.gc = undefined;
92
+ }
69
93
  }
70
94
  }
@@ -49,16 +49,12 @@ export default class Observer {
49
49
  for (const mutation of mutations) { // mutations order is sequential
50
50
  const target = mutation.target;
51
51
  const type = mutation.type;
52
- // const deleted: number[] = [];
53
52
  if (!isObservable(target)) {
54
53
  continue;
55
54
  }
56
55
  if (type === 'childList') {
57
56
  for (let i = 0; i < mutation.removedNodes.length; i++) {
58
- // this.bindTree(mutation.removedNodes[i]);
59
- const id = this.unbindNode(mutation.removedNodes[i]);
60
- // id && this.recents.delete(id)
61
- // id && deleted.push(id)
57
+ this.bindTree(mutation.removedNodes[i]);
62
58
  }
63
59
  for (let i = 0; i < mutation.addedNodes.length; i++) {
64
60
  this.bindTree(mutation.addedNodes[i]);
@@ -90,7 +86,6 @@ export default class Observer {
90
86
  }
91
87
  }
92
88
  this.commitNodes();
93
- this.app.nodes.cleanTree();
94
89
  }));
95
90
  }
96
91
  clear() {
package/lib/index.js CHANGED
@@ -123,7 +123,7 @@ export default class API {
123
123
  // no-cors issue only with text/plain or not-set Content-Type
124
124
  // req.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
125
125
  req.send(JSON.stringify({
126
- trackerVersion: '3.5.15-beta.0',
126
+ trackerVersion: '3.5.16-beta.2',
127
127
  projectKey: options.projectKey,
128
128
  doNotTrack,
129
129
  // TODO: add precise reason (an exact API missing)
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": "3.5.15-beta.0",
4
+ "version": "3.5.16-beta.2",
5
5
  "keywords": [
6
6
  "logging",
7
7
  "replay"