@openreplay/tracker 14.0.9 → 14.0.10-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.
package/cjs/app/nodes.js CHANGED
@@ -2,8 +2,9 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const utils_js_1 = require("../utils.js");
4
4
  class Nodes {
5
- constructor(node_id) {
5
+ constructor(node_id, angularMode) {
6
6
  this.node_id = node_id;
7
+ this.angularMode = angularMode;
7
8
  this.nodes = [];
8
9
  this.totalNodeAmount = 0;
9
10
  this.nodeCallbacks = [];
@@ -12,9 +13,22 @@ class Nodes {
12
13
  this.scanTree = (cb) => {
13
14
  this.nodes.forEach((node) => cb(node));
14
15
  };
16
+ this.attachNodeListener = (node, type, listener, useCapture = true) => {
17
+ const id = this.getID(node);
18
+ if (id === undefined) {
19
+ return;
20
+ }
21
+ (0, utils_js_1.createEventListener)(node, type, listener, useCapture, this.angularMode);
22
+ let listeners = this.elementListeners.get(id);
23
+ if (listeners === undefined) {
24
+ listeners = [];
25
+ this.elementListeners.set(id, listeners);
26
+ }
27
+ listeners.push([type, listener, useCapture]);
28
+ };
15
29
  }
16
30
  syntheticMode(frameOrder) {
17
- const maxSafeNumber = 9007199254740900;
31
+ const maxSafeNumber = Number.MAX_SAFE_INTEGER;
18
32
  const placeholderSize = 99999999;
19
33
  const nextFrameId = placeholderSize * frameOrder;
20
34
  // I highly doubt that this will ever happen,
@@ -28,19 +42,6 @@ class Nodes {
28
42
  attachNodeCallback(nodeCallback) {
29
43
  this.nodeCallbacks.push(nodeCallback);
30
44
  }
31
- attachNodeListener(node, type, listener, useCapture = true) {
32
- const id = this.getID(node);
33
- if (id === undefined) {
34
- return;
35
- }
36
- (0, utils_js_1.createEventListener)(node, type, listener, useCapture);
37
- let listeners = this.elementListeners.get(id);
38
- if (listeners === undefined) {
39
- listeners = [];
40
- this.elementListeners.set(id, listeners);
41
- }
42
- listeners.push([type, listener, useCapture]);
43
- }
44
45
  registerNode(node) {
45
46
  let id = node[this.node_id];
46
47
  const isNew = id === undefined;
@@ -63,7 +64,7 @@ class Nodes {
63
64
  const listeners = this.elementListeners.get(id);
64
65
  if (listeners !== undefined) {
65
66
  this.elementListeners.delete(id);
66
- listeners.forEach((listener) => (0, utils_js_1.deleteEventListener)(node, listener[0], listener[1], listener[2]));
67
+ listeners.forEach((listener) => (0, utils_js_1.deleteEventListener)(node, listener[0], listener[1], listener[2], this.angularMode));
67
68
  }
68
69
  this.totalNodeAmount--;
69
70
  }
@@ -1,5 +1,5 @@
1
1
  import Observer from './observer.js';
2
2
  export default class IFrameObserver extends Observer {
3
3
  observe(iframe: HTMLIFrameElement): void;
4
- syntheticObserve(selfId: number, doc: Document): void;
4
+ syntheticObserve(rootNodeId: number, doc: Document): void;
5
5
  }
@@ -22,13 +22,13 @@ class IFrameObserver extends observer_js_1.default {
22
22
  this.app.send((0, messages_gen_js_1.CreateIFrameDocument)(hostID, docID));
23
23
  });
24
24
  }
25
- syntheticObserve(selfId, doc) {
25
+ syntheticObserve(rootNodeId, doc) {
26
26
  this.observeRoot(doc, (docID) => {
27
27
  if (docID === undefined) {
28
28
  this.app.debug.log('OpenReplay: Iframe document not bound');
29
29
  return;
30
30
  }
31
- this.app.send((0, messages_gen_js_1.CreateIFrameDocument)(selfId, docID));
31
+ this.app.send((0, messages_gen_js_1.CreateIFrameDocument)(rootNodeId, docID));
32
32
  });
33
33
  }
34
34
  }
@@ -10,6 +10,10 @@ export default abstract class Observer {
10
10
  private readonly textSet;
11
11
  constructor(app: App, isTopContext?: boolean);
12
12
  private clear;
13
+ /**
14
+ * Unbinds the removed nodes in case of iframe src change.
15
+ */
16
+ private handleIframeSrcChange;
13
17
  private sendNodeAttribute;
14
18
  private sendNodeData;
15
19
  private bindNode;
@@ -57,7 +57,6 @@ class Observer {
57
57
  }
58
58
  if (type === 'childList') {
59
59
  for (let i = 0; i < mutation.removedNodes.length; i++) {
60
- // Should be the same as bindTree(mutation.removedNodes[i]), but logic needs to be be untied
61
60
  if (isObservable(mutation.removedNodes[i])) {
62
61
  this.bindNode(mutation.removedNodes[i]);
63
62
  }
@@ -79,6 +78,9 @@ class Observer {
79
78
  if (name === null) {
80
79
  continue;
81
80
  }
81
+ if (target instanceof HTMLIFrameElement && name === 'src') {
82
+ this.handleIframeSrcChange(target);
83
+ }
82
84
  let attr = this.attributesMap.get(id);
83
85
  if (attr === undefined) {
84
86
  this.attributesMap.set(id, (attr = new Set()));
@@ -88,11 +90,10 @@ class Observer {
88
90
  }
89
91
  if (type === 'characterData') {
90
92
  this.textSet.add(id);
91
- continue;
92
93
  }
93
94
  }
94
95
  this.commitNodes();
95
- }));
96
+ }), this.app.options.angularMode);
96
97
  }
97
98
  clear() {
98
99
  this.commited.length = 0;
@@ -101,10 +102,40 @@ class Observer {
101
102
  this.attributesMap.clear();
102
103
  this.textSet.clear();
103
104
  }
105
+ /**
106
+ * Unbinds the removed nodes in case of iframe src change.
107
+ */
108
+ handleIframeSrcChange(iframe) {
109
+ const oldContentDocument = iframe.contentDocument;
110
+ if (oldContentDocument) {
111
+ const id = this.app.nodes.getID(oldContentDocument);
112
+ if (id !== undefined) {
113
+ const walker = document.createTreeWalker(oldContentDocument, NodeFilter.SHOW_ELEMENT + NodeFilter.SHOW_TEXT, {
114
+ acceptNode: (node) => isIgnored(node) || this.app.nodes.getID(node) === undefined
115
+ ? NodeFilter.FILTER_REJECT
116
+ : NodeFilter.FILTER_ACCEPT,
117
+ },
118
+ // @ts-ignore
119
+ false);
120
+ let removed = 0;
121
+ const totalBeforeRemove = this.app.nodes.getNodeCount();
122
+ while (walker.nextNode()) {
123
+ if (!iframe.contentDocument.contains(walker.currentNode)) {
124
+ removed += 1;
125
+ this.app.nodes.unregisterNode(walker.currentNode);
126
+ }
127
+ }
128
+ const removedPercent = Math.floor((removed / totalBeforeRemove) * 100);
129
+ if (removedPercent > 30) {
130
+ this.app.send((0, messages_gen_js_1.UnbindNodes)(removedPercent));
131
+ }
132
+ }
133
+ }
134
+ }
104
135
  sendNodeAttribute(id, node, name, value) {
105
136
  if ((0, guards_js_1.isSVGElement)(node)) {
106
- if (name.substr(0, 6) === 'xlink:') {
107
- name = name.substr(6);
137
+ if (name.substring(0, 6) === 'xlink:') {
138
+ name = name.substring(6);
108
139
  }
109
140
  if (value === null) {
110
141
  this.app.send((0, messages_gen_js_1.RemoveNodeAttribute)(id, name));
@@ -125,7 +156,7 @@ class Observer {
125
156
  name === 'integrity' ||
126
157
  name === 'crossorigin' ||
127
158
  name === 'autocomplete' ||
128
- name.substr(0, 2) === 'on') {
159
+ name.substring(0, 2) === 'on') {
129
160
  return;
130
161
  }
131
162
  if (name === 'value' &&
@@ -19,7 +19,7 @@ export default class TopObserver extends Observer {
19
19
  private shadowRootObservers;
20
20
  private handleShadowRoot;
21
21
  observe(): void;
22
- crossdomainObserve(selfId: number, frameOder: number): void;
22
+ crossdomainObserve(rootNodeId: number, frameOder: number): void;
23
23
  disconnect(): void;
24
24
  }
25
25
  export {};
@@ -107,7 +107,7 @@ class TopObserver extends observer_js_1.default {
107
107
  this.app.nodes.callNodeCallbacks(document, true);
108
108
  }, window.document.documentElement);
109
109
  }
110
- crossdomainObserve(selfId, frameOder) {
110
+ crossdomainObserve(rootNodeId, frameOder) {
111
111
  const observer = this;
112
112
  Element.prototype.attachShadow = function () {
113
113
  // eslint-disable-next-line
@@ -119,7 +119,7 @@ class TopObserver extends observer_js_1.default {
119
119
  this.app.nodes.syntheticMode(frameOder);
120
120
  const iframeObserver = new iframe_observer_js_1.default(this.app);
121
121
  this.iframeObservers.push(iframeObserver);
122
- iframeObserver.syntheticObserve(selfId, window.document);
122
+ iframeObserver.syntheticObserve(rootNodeId, window.document);
123
123
  }
124
124
  disconnect() {
125
125
  this.iframeOffsets.clear();
package/cjs/index.js CHANGED
@@ -98,7 +98,7 @@ class API {
98
98
  const orig = this.options.ingestPoint || index_js_1.DEFAULT_INGEST_POINT;
99
99
  req.open('POST', orig + '/v1/web/not-started');
100
100
  req.send(JSON.stringify({
101
- trackerVersion: '14.0.9',
101
+ trackerVersion: '14.0.10-beta.2',
102
102
  projectKey: this.options.projectKey,
103
103
  doNotTrack,
104
104
  reason: missingApi.length ? `missing api: ${missingApi.join(',')}` : reason,
@@ -69,8 +69,13 @@ function default_1(app, opts) {
69
69
  app.send(msg);
70
70
  }
71
71
  }
72
- app.attachEventListener(context, 'unhandledrejection', handler);
73
- app.attachEventListener(context, 'error', handler);
72
+ try {
73
+ app.attachEventListener(context, 'unhandledrejection', handler);
74
+ app.attachEventListener(context, 'error', handler);
75
+ }
76
+ catch (e) {
77
+ console.error('Error while attaching to error proto contexts', e);
78
+ }
74
79
  }
75
80
  if (options.captureExceptions) {
76
81
  app.observer.attachContextCallback(patchContext); // TODO: attach once-per-iframe (?)
@@ -93,7 +93,7 @@ function default_1(app) {
93
93
  }
94
94
  }
95
95
  }
96
- }));
96
+ }), app.options.angularMode);
97
97
  app.attachStopCallback(() => {
98
98
  observer.disconnect();
99
99
  });
package/cjs/utils.d.ts CHANGED
@@ -22,8 +22,8 @@ export declare function inIframe(): boolean | null;
22
22
  * we need to use this to achieve safe behavior
23
23
  * */
24
24
  export declare function ngSafeBrowserMethod(method: string): string;
25
- export declare function createMutationObserver(cb: MutationCallback): MutationObserver;
26
- export declare function createEventListener(target: EventTarget, event: string, cb: EventListenerOrEventListenerObject, capture?: boolean): void;
27
- export declare function deleteEventListener(target: EventTarget, event: string, cb: EventListenerOrEventListenerObject, capture?: boolean): void;
25
+ export declare function createMutationObserver(cb: MutationCallback, angularMode?: boolean): MutationObserver;
26
+ export declare function createEventListener(target: EventTarget, event: string, cb: EventListenerOrEventListenerObject, capture?: boolean, angularMode?: boolean): void;
27
+ export declare function deleteEventListener(target: EventTarget, event: string, cb: EventListenerOrEventListenerObject, capture?: boolean, angularMode?: boolean): void;
28
28
  export declare function requestIdleCb(callback: () => void): void;
29
29
  export declare function simpleMerge<T>(defaultObj: T, givenObj: Partial<T>): T;
package/cjs/utils.js CHANGED
@@ -119,34 +119,53 @@ function ngSafeBrowserMethod(method) {
119
119
  : method;
120
120
  }
121
121
  exports.ngSafeBrowserMethod = ngSafeBrowserMethod;
122
- function createMutationObserver(cb) {
123
- const mObserver = ngSafeBrowserMethod('MutationObserver');
124
- return new window[mObserver](cb);
122
+ function createMutationObserver(cb, angularMode) {
123
+ if (angularMode) {
124
+ const mObserver = ngSafeBrowserMethod('MutationObserver');
125
+ return new window[mObserver](cb);
126
+ }
127
+ else {
128
+ return new MutationObserver(cb);
129
+ }
125
130
  }
126
131
  exports.createMutationObserver = createMutationObserver;
127
- function createEventListener(target, event, cb, capture) {
128
- const safeAddEventListener = ngSafeBrowserMethod('addEventListener');
132
+ function createEventListener(target, event, cb, capture, angularMode) {
133
+ let safeAddEventListener;
134
+ if (angularMode) {
135
+ safeAddEventListener = ngSafeBrowserMethod('addEventListener');
136
+ }
137
+ else {
138
+ safeAddEventListener = 'addEventListener';
139
+ }
129
140
  try {
130
141
  target[safeAddEventListener](event, cb, capture);
142
+ target.addEventListener(event, cb, capture);
131
143
  }
132
144
  catch (e) {
133
145
  const msg = e.message;
134
- console.debug(
146
+ console.error(
135
147
  // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
136
- `Openreplay: ${msg}; if this error is caused by an IframeObserver, ignore it`);
148
+ `Openreplay: ${msg}; if this error is caused by an IframeObserver, ignore it`, event, target);
137
149
  }
138
150
  }
139
151
  exports.createEventListener = createEventListener;
140
- function deleteEventListener(target, event, cb, capture) {
141
- const safeRemoveEventListener = ngSafeBrowserMethod('removeEventListener');
152
+ function deleteEventListener(target, event, cb, capture, angularMode) {
153
+ let safeRemoveEventListener;
154
+ if (angularMode) {
155
+ safeRemoveEventListener = ngSafeBrowserMethod('removeEventListener');
156
+ }
157
+ else {
158
+ safeRemoveEventListener = 'removeEventListener';
159
+ }
142
160
  try {
143
161
  target[safeRemoveEventListener](event, cb, capture);
162
+ target.removeEventListener(event, cb, capture);
144
163
  }
145
164
  catch (e) {
146
165
  const msg = e.message;
147
- console.debug(
166
+ console.error(
148
167
  // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
149
- `Openreplay: ${msg}; if this error is caused by an IframeObserver, ignore it`);
168
+ `Openreplay: ${msg}; if this error is caused by an IframeObserver, ignore it`, event, target);
150
169
  }
151
170
  }
152
171
  exports.deleteEventListener = deleteEventListener;
@@ -18,6 +18,12 @@ export interface StartOptions {
18
18
  forceNew?: boolean;
19
19
  sessionHash?: string;
20
20
  assistOnly?: boolean;
21
+ /**
22
+ * @deprecated We strongly advise to use .start().then instead.
23
+ *
24
+ * This method is kept for snippet compatibility only
25
+ * */
26
+ startCallback?: (result: StartPromiseReturn) => void;
21
27
  }
22
28
  interface OnStartInfo {
23
29
  sessionID: string;
@@ -37,6 +43,12 @@ declare const SuccessfulStart: (body: OnStartInfo) => SuccessfulStart;
37
43
  export type StartPromiseReturn = SuccessfulStart | UnsuccessfulStart;
38
44
  type StartCallback = (i: OnStartInfo) => void;
39
45
  type CommitCallback = (messages: Array<Message>) => void;
46
+ declare enum ActivityState {
47
+ NotActive = 0,
48
+ Starting = 1,
49
+ Active = 2,
50
+ ColdStart = 3
51
+ }
40
52
  type AppOptions = {
41
53
  revID: string;
42
54
  node_id: string;
@@ -94,12 +106,18 @@ type AppOptions = {
94
106
  parentDomain?: string;
95
107
  };
96
108
  network?: NetworkOptions;
109
+ /**
110
+ * use this flag if you're using Angular
111
+ * basically goes around window.Zone api changes to mutation observer
112
+ * and event listeners
113
+ * */
114
+ angularMode?: boolean;
97
115
  } & WebworkerOptions & SessOptions;
98
116
  export type Options = AppOptions & ObserverOptions & SanitizerOptions;
99
117
  export declare const DEFAULT_INGEST_POINT = "https://api.openreplay.com/ingest";
100
118
  export default class App {
101
119
  private readonly signalError;
102
- private readonly insideIframe;
120
+ readonly insideIframe: boolean;
103
121
  readonly nodes: Nodes;
104
122
  readonly ticker: Ticker;
105
123
  readonly projectKey: string;
@@ -140,18 +158,29 @@ export default class App {
140
158
  private rootId;
141
159
  private pageFrames;
142
160
  private frameOderNumber;
143
- private readonly initialHostName;
144
161
  private features;
145
162
  constructor(projectKey: string, sessionToken: string | undefined, options: Partial<Options>, signalError: (error: string, apis: string[]) => void, insideIframe: boolean);
146
163
  /** used by child iframes for crossdomain only */
147
164
  parentActive: boolean;
148
165
  checkStatus: () => boolean;
149
- /** used by child iframes for crossdomain only */
150
- /** track app instances in crossdomain child iframes */
151
- crossdomainIframesModule: () => void;
166
+ parentCrossDomainFrameListener: (event: MessageEvent) => void;
167
+ /**
168
+ * context ids for iframes,
169
+ * order is not so important as long as its consistent
170
+ * */
171
+ trackedFrames: string[];
172
+ crossDomainIframeListener: (event: MessageEvent) => void;
173
+ /**
174
+ * { command : [remaining iframes] }
175
+ * + order of commands
176
+ **/
177
+ pollingQueue: Record<string, any>;
178
+ private readonly addCommand;
179
+ bootChildrenFrames: () => Promise<void>;
180
+ killChildrenFrames: () => void;
152
181
  signalIframeTracker: () => void;
153
182
  startTimeout: ReturnType<typeof setTimeout> | null;
154
- private allowAppStart;
183
+ allowAppStart(): void;
155
184
  private checkNodeId;
156
185
  private initWorker;
157
186
  private handleWorkerMsg;
@@ -176,9 +205,9 @@ export default class App {
176
205
  timestamp(): number;
177
206
  safe<T extends (this: any, ...args: any[]) => void>(fn: T): T;
178
207
  attachCommitCallback(cb: CommitCallback): void;
179
- attachStartCallback(cb: StartCallback, useSafe?: boolean): void;
180
- attachStopCallback(cb: () => any, useSafe?: boolean): void;
181
- attachEventListener(target: EventTarget, type: string, listener: EventListener, useSafe?: boolean, useCapture?: boolean): void;
208
+ attachStartCallback: (cb: StartCallback, useSafe?: boolean) => void;
209
+ attachStopCallback: (cb: () => any, useSafe?: boolean) => void;
210
+ attachEventListener: (target: EventTarget, type: string, listener: EventListener, useSafe?: boolean, useCapture?: boolean) => void;
182
211
  checkRequiredVersion(version: string): boolean;
183
212
  private getTrackerInfo;
184
213
  getSessionInfo(): {
@@ -260,6 +289,7 @@ export default class App {
260
289
  getUxtId(): number | null;
261
290
  waitStart(): Promise<unknown>;
262
291
  waitStarted(): Promise<unknown>;
292
+ waitStatus(status: ActivityState): Promise<unknown>;
263
293
  /**
264
294
  * basically we ask other tabs during constructor
265
295
  * and here we just apply 10ms delay just in case