@openreplay/tracker 14.0.10-beta.1 → 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.
@@ -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;
@@ -100,6 +106,12 @@ type AppOptions = {
100
106
  parentDomain?: string;
101
107
  };
102
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;
103
115
  } & WebworkerOptions & SessOptions;
104
116
  export type Options = AppOptions & ObserverOptions & SanitizerOptions;
105
117
  export declare const DEFAULT_INGEST_POINT = "https://api.openreplay.com/ingest";
@@ -146,16 +158,24 @@ export default class App {
146
158
  private rootId;
147
159
  private pageFrames;
148
160
  private frameOderNumber;
149
- private readonly initialHostName;
150
161
  private features;
151
162
  constructor(projectKey: string, sessionToken: string | undefined, options: Partial<Options>, signalError: (error: string, apis: string[]) => void, insideIframe: boolean);
152
163
  /** used by child iframes for crossdomain only */
153
164
  parentActive: boolean;
154
165
  checkStatus: () => boolean;
155
166
  parentCrossDomainFrameListener: (event: MessageEvent) => void;
156
- trackedFrames: number[];
167
+ /**
168
+ * context ids for iframes,
169
+ * order is not so important as long as its consistent
170
+ * */
171
+ trackedFrames: string[];
157
172
  crossDomainIframeListener: (event: MessageEvent) => void;
158
- pollingQueue: string[];
173
+ /**
174
+ * { command : [remaining iframes] }
175
+ * + order of commands
176
+ **/
177
+ pollingQueue: Record<string, any>;
178
+ private readonly addCommand;
159
179
  bootChildrenFrames: () => Promise<void>;
160
180
  killChildrenFrames: () => void;
161
181
  signalIframeTracker: () => void;
package/cjs/app/index.js CHANGED
@@ -96,7 +96,7 @@ class App {
96
96
  this.stopCallbacks = [];
97
97
  this.commitCallbacks = [];
98
98
  this.activityState = ActivityState.NotActive;
99
- this.version = '14.0.10-beta.1'; // TODO: version compatability check inside each plugin.
99
+ this.version = '14.0.10-beta.2'; // TODO: version compatability check inside each plugin.
100
100
  this.socketMode = false;
101
101
  this.compressionThreshold = 24 * 1000;
102
102
  this.bc = null;
@@ -106,7 +106,6 @@ class App {
106
106
  this.rootId = null;
107
107
  this.pageFrames = [];
108
108
  this.frameOderNumber = 0;
109
- this.initialHostName = location.hostname;
110
109
  this.features = {
111
110
  'feature-flags': true,
112
111
  'usability-test': true,
@@ -141,7 +140,6 @@ class App {
141
140
  this.frameOderNumber = data.frameOrderNumber;
142
141
  this.debug.log('starting iframe tracking', data);
143
142
  this.allowAppStart();
144
- this.delay = data.frameTimeOffset;
145
143
  }
146
144
  if (data.line === proto.killIframe) {
147
145
  if (this.active()) {
@@ -149,6 +147,10 @@ class App {
149
147
  }
150
148
  }
151
149
  };
150
+ /**
151
+ * context ids for iframes,
152
+ * order is not so important as long as its consistent
153
+ * */
152
154
  this.trackedFrames = [];
153
155
  this.crossDomainIframeListener = (event) => {
154
156
  if (!this.active() || event.source === window)
@@ -159,25 +161,30 @@ class App {
159
161
  if (data.line === proto.iframeSignal) {
160
162
  // @ts-ignore
161
163
  event.source?.postMessage({ ping: true, line: proto.parentAlive }, '*');
162
- const childIframeDomain = data.domain;
163
164
  const pageIframes = Array.from(document.querySelectorAll('iframe'));
164
165
  this.pageFrames = pageIframes;
165
166
  const signalId = async () => {
166
- const id = await this.checkNodeId(pageIframes, childIframeDomain);
167
- if (id && !this.trackedFrames.includes(id)) {
167
+ if (event.source === null) {
168
+ return console.error('Couldnt connect to event.source for child iframe tracking');
169
+ }
170
+ const id = await this.checkNodeId(pageIframes, event.source);
171
+ if (id && !this.trackedFrames.includes(data.context)) {
168
172
  try {
169
- this.trackedFrames.push(id);
173
+ this.trackedFrames.push(data.context);
170
174
  await this.waitStarted();
171
175
  const token = this.session.getSessionToken();
176
+ const order = this.trackedFrames.findIndex((f) => f === data.context) + 1;
177
+ if (order === 0) {
178
+ this.debug.error('Couldnt get order number for iframe', data.context, this.trackedFrames);
179
+ }
172
180
  const iframeData = {
173
181
  line: proto.iframeId,
174
- context: this.contextId,
175
- domain: childIframeDomain,
176
182
  id,
177
183
  token,
178
- frameOrderNumber: this.trackedFrames.length,
179
- frameTimeOffset: this.timestamp(),
184
+ // since indexes go from 0 we +1
185
+ frameOrderNumber: order,
180
186
  };
187
+ this.debug.log('Got child frame signal; nodeId', id, event.source, iframeData);
181
188
  // @ts-ignore
182
189
  event.source?.postMessage(iframeData, '*');
183
190
  }
@@ -185,6 +192,9 @@ class App {
185
192
  console.error(e);
186
193
  }
187
194
  }
195
+ else {
196
+ this.debug.log('Couldnt get node id for iframe', event.source, pageIframes);
197
+ }
188
198
  };
189
199
  void signalId();
190
200
  }
@@ -198,7 +208,7 @@ class App {
198
208
  if (msg[0] === 20 /* MType.MouseMove */) {
199
209
  let fixedMessage = msg;
200
210
  this.pageFrames.forEach((frame) => {
201
- if (frame.dataset.domain === event.data.domain) {
211
+ if (frame.contentWindow === event.source) {
202
212
  const [type, x, y] = msg;
203
213
  const { left, top } = frame.getBoundingClientRect();
204
214
  fixedMessage = [type, x + left, y + top];
@@ -209,7 +219,7 @@ class App {
209
219
  if (msg[0] === 68 /* MType.MouseClick */) {
210
220
  let fixedMessage = msg;
211
221
  this.pageFrames.forEach((frame) => {
212
- if (frame.dataset.domain === event.data.domain) {
222
+ if (frame.contentWindow === event.source) {
213
223
  const [type, id, hesitationTime, label, selector, normX, normY] = msg;
214
224
  const { left, top, width, height } = frame.getBoundingClientRect();
215
225
  const contentWidth = document.documentElement.scrollWidth;
@@ -237,34 +247,47 @@ class App {
237
247
  this.messages.push(...mappedMessages);
238
248
  }
239
249
  if (data.line === proto.polling) {
240
- if (!this.pollingQueue.length) {
250
+ if (!this.pollingQueue.order.length) {
241
251
  return;
242
252
  }
243
- while (this.pollingQueue.length) {
244
- const msg = this.pollingQueue.shift();
253
+ const nextCommand = this.pollingQueue.order[0];
254
+ if (this.pollingQueue[nextCommand].includes(data.context)) {
255
+ this.pollingQueue[nextCommand] = this.pollingQueue[nextCommand].filter((c) => c !== data.context);
245
256
  // @ts-ignore
246
- event.source?.postMessage({ line: msg }, '*');
257
+ event.source?.postMessage({ line: nextCommand }, '*');
258
+ if (this.pollingQueue[nextCommand].length === 0) {
259
+ this.pollingQueue.order.shift();
260
+ }
247
261
  }
248
262
  }
249
263
  };
250
- this.pollingQueue = [];
264
+ /**
265
+ * { command : [remaining iframes] }
266
+ * + order of commands
267
+ **/
268
+ this.pollingQueue = {
269
+ order: [],
270
+ };
271
+ this.addCommand = (cmd) => {
272
+ this.pollingQueue.order.push(cmd);
273
+ this.pollingQueue[cmd] = [...this.trackedFrames];
274
+ };
251
275
  this.bootChildrenFrames = async () => {
252
276
  await this.waitStarted();
253
- this.pollingQueue.push(proto.startIframe);
277
+ this.addCommand(proto.startIframe);
254
278
  };
255
279
  this.killChildrenFrames = () => {
256
- this.pollingQueue.push(proto.killIframe);
280
+ this.addCommand(proto.killIframe);
257
281
  };
258
282
  this.signalIframeTracker = () => {
259
- const domain = this.initialHostName;
260
283
  const thisTab = this.session.getTabId();
261
284
  const signalToParent = (n) => {
262
285
  window.parent.postMessage({
263
286
  line: proto.iframeSignal,
264
287
  source: thisTab,
265
288
  context: this.contextId,
266
- domain,
267
289
  }, this.options.crossdomain?.parentDomain ?? '*');
290
+ console.log('trying to signal to parent', n);
268
291
  setTimeout(() => {
269
292
  if (!this.checkStatus() && n < 100) {
270
293
  void signalToParent(n + 1);
@@ -292,8 +315,12 @@ class App {
292
315
  if (useSafe) {
293
316
  listener = this.safe(listener);
294
317
  }
295
- const createListener = () => target ? (0, utils_js_1.createEventListener)(target, type, listener, useCapture) : null;
296
- const deleteListener = () => target ? (0, utils_js_1.deleteEventListener)(target, type, listener, useCapture) : null;
318
+ const createListener = () => target
319
+ ? (0, utils_js_1.createEventListener)(target, type, listener, useCapture, this.options.angularMode)
320
+ : null;
321
+ const deleteListener = () => target
322
+ ? (0, utils_js_1.deleteEventListener)(target, type, listener, useCapture, this.options.angularMode)
323
+ : null;
297
324
  this.attachStartCallback(createListener, useSafe);
298
325
  this.attachStopCallback(deleteListener, useSafe);
299
326
  };
@@ -373,6 +400,7 @@ class App {
373
400
  __save_canvas_locally: false,
374
401
  useAnimationFrame: false,
375
402
  },
403
+ angularMode: false,
376
404
  };
377
405
  this.options = (0, utils_js_1.simpleMerge)(defaultOptions, options);
378
406
  if (!this.insideIframe &&
@@ -386,7 +414,7 @@ class App {
386
414
  this.localStorage = this.options.localStorage ?? window.localStorage;
387
415
  this.sessionStorage = this.options.sessionStorage ?? window.sessionStorage;
388
416
  this.sanitizer = new sanitizer_js_1.default(this, options);
389
- this.nodes = new nodes_js_1.default(this.options.node_id);
417
+ this.nodes = new nodes_js_1.default(this.options.node_id, Boolean(options.angularMode));
390
418
  this.observer = new top_observer_js_1.default(this, options);
391
419
  this.ticker = new ticker_js_1.default(this);
392
420
  this.ticker.attach(() => this.commit());
@@ -410,24 +438,25 @@ class App {
410
438
  if (sessionToken != null) {
411
439
  this.session.applySessionHash(sessionToken);
412
440
  }
413
- this.initWorker();
414
441
  const thisTab = this.session.getTabId();
415
- /**
416
- * listen for messages from parent window, so we can signal that we're alive
417
- * */
418
442
  if (this.insideIframe) {
443
+ /**
444
+ * listen for messages from parent window, so we can signal that we're alive
445
+ * */
419
446
  window.addEventListener('message', this.parentCrossDomainFrameListener);
420
447
  setInterval(() => {
421
448
  window.parent.postMessage({
422
449
  line: proto.polling,
450
+ context: this.contextId,
423
451
  }, '*');
424
452
  }, 250);
425
453
  }
426
- /**
427
- * if we get a signal from child iframes, we check for their node_id and send it back,
428
- * so they can act as if it was just a same-domain iframe
429
- * */
430
- if (!this.insideIframe) {
454
+ else {
455
+ this.initWorker();
456
+ /**
457
+ * if we get a signal from child iframes, we check for their node_id and send it back,
458
+ * so they can act as if it was just a same-domain iframe
459
+ * */
431
460
  window.addEventListener('message', this.crossDomainIframeListener);
432
461
  }
433
462
  if (this.bc !== null) {
@@ -475,9 +504,9 @@ class App {
475
504
  this.startTimeout = null;
476
505
  }
477
506
  }
478
- async checkNodeId(iframes, domain) {
507
+ async checkNodeId(iframes, source) {
479
508
  for (const iframe of iframes) {
480
- if (iframe.dataset.domain === domain) {
509
+ if (iframe.contentWindow && iframe.contentWindow === source) {
481
510
  /**
482
511
  * Here we're trying to get node id from the iframe (which is kept in observer)
483
512
  * because of async nature of dom initialization, we give 100 retries with 100ms delay each
@@ -614,19 +643,18 @@ class App {
614
643
  this.messages.length = 0;
615
644
  return;
616
645
  }
617
- if (this.worker === undefined || !this.messages.length) {
618
- return;
619
- }
620
646
  if (this.insideIframe) {
621
647
  window.parent.postMessage({
622
648
  line: proto.iframeBatch,
623
649
  messages: this.messages,
624
- domain: this.initialHostName,
625
650
  }, this.options.crossdomain?.parentDomain ?? '*');
626
651
  this.commitCallbacks.forEach((cb) => cb(this.messages));
627
652
  this.messages.length = 0;
628
653
  return;
629
654
  }
655
+ if (this.worker === undefined || !this.messages.length) {
656
+ return;
657
+ }
630
658
  try {
631
659
  (0, utils_js_1.requestIdleCb)(() => {
632
660
  this.messages.unshift((0, messages_gen_js_1.TabData)(this.session.getTabId()));
@@ -1018,7 +1046,7 @@ class App {
1018
1046
  if (isColdStart && this.coldInterval) {
1019
1047
  clearInterval(this.coldInterval);
1020
1048
  }
1021
- if (!this.worker) {
1049
+ if (!this.worker && !this.insideIframe) {
1022
1050
  const reason = 'No worker found: perhaps, CSP is not set.';
1023
1051
  this.signalError(reason, []);
1024
1052
  return Promise.resolve(UnsuccessfulStart(reason));
@@ -1045,7 +1073,7 @@ class App {
1045
1073
  metadata: startOpts.metadata,
1046
1074
  });
1047
1075
  const timestamp = (0, utils_js_1.now)();
1048
- this.worker.postMessage({
1076
+ this.worker?.postMessage({
1049
1077
  type: 'start',
1050
1078
  pageNo: this.session.incPageNo(),
1051
1079
  ingestPoint: this.options.ingestPoint,
@@ -1086,7 +1114,7 @@ class App {
1086
1114
  const reason = error === CANCELED ? CANCELED : `Server error: ${r.status}. ${error}`;
1087
1115
  return UnsuccessfulStart(reason);
1088
1116
  }
1089
- if (!this.worker) {
1117
+ if (!this.worker && !this.insideIframe) {
1090
1118
  const reason = 'no worker found after start request (this should not happen in real world)';
1091
1119
  this.signalError(reason, []);
1092
1120
  return UnsuccessfulStart(reason);
@@ -1124,10 +1152,10 @@ class App {
1124
1152
  });
1125
1153
  if (socketOnly) {
1126
1154
  this.socketMode = true;
1127
- this.worker.postMessage('stop');
1155
+ this.worker?.postMessage('stop');
1128
1156
  }
1129
1157
  else {
1130
- this.worker.postMessage({
1158
+ this.worker?.postMessage({
1131
1159
  type: 'auth',
1132
1160
  token,
1133
1161
  beaconSizeLimit,
@@ -1146,6 +1174,9 @@ class App {
1146
1174
  // TODO: start as early as possible (before receiving the token)
1147
1175
  /** after start */
1148
1176
  this.startCallbacks.forEach((cb) => cb(onStartInfo)); // MBTODO: callbacks after DOM "mounted" (observed)
1177
+ if (startOpts.startCallback) {
1178
+ startOpts.startCallback(SuccessfulStart(onStartInfo));
1179
+ }
1149
1180
  if (this.features['feature-flags']) {
1150
1181
  void this.featureFlags.reloadFlags();
1151
1182
  }
@@ -1,16 +1,17 @@
1
1
  type NodeCallback = (node: Node, isStart: boolean) => void;
2
2
  export default class Nodes {
3
3
  private readonly node_id;
4
+ private readonly angularMode;
4
5
  private nodes;
5
6
  private totalNodeAmount;
6
7
  private readonly nodeCallbacks;
7
8
  private readonly elementListeners;
8
9
  private nextNodeId;
9
- constructor(node_id: string);
10
+ constructor(node_id: string, angularMode: boolean);
10
11
  syntheticMode(frameOrder: number): void;
11
12
  attachNodeCallback(nodeCallback: NodeCallback): void;
12
13
  scanTree: (cb: (node: Node | void) => void) => void;
13
- attachNodeListener(node: Node, type: string, listener: EventListener, useCapture?: boolean): void;
14
+ attachNodeListener: (node: Node, type: string, listener: EventListener, useCapture?: boolean) => void;
14
15
  registerNode(node: Node): [/*id:*/ number, /*isNew:*/ boolean];
15
16
  unregisterNode(node: Node): number | undefined;
16
17
  cleanTree(): void;
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.10-beta.1',
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,
@@ -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;