@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.
- package/cjs/app/index.d.ts +23 -3
- package/cjs/app/index.js +76 -45
- package/cjs/app/nodes.d.ts +3 -2
- package/cjs/app/nodes.js +17 -16
- package/cjs/app/observer/iframe_observer.d.ts +1 -1
- package/cjs/app/observer/iframe_observer.js +2 -2
- package/cjs/app/observer/observer.d.ts +4 -0
- package/cjs/app/observer/observer.js +37 -6
- package/cjs/app/observer/top_observer.d.ts +1 -1
- package/cjs/app/observer/top_observer.js +2 -2
- package/cjs/index.js +1 -1
- package/cjs/modules/img.js +1 -1
- package/cjs/utils.d.ts +3 -3
- package/cjs/utils.js +28 -9
- package/lib/app/index.d.ts +23 -3
- package/lib/app/index.js +76 -45
- package/lib/app/nodes.d.ts +3 -2
- package/lib/app/nodes.js +17 -16
- package/lib/app/observer/iframe_observer.d.ts +1 -1
- package/lib/app/observer/iframe_observer.js +2 -2
- package/lib/app/observer/observer.d.ts +4 -0
- package/lib/app/observer/observer.js +37 -6
- package/lib/app/observer/top_observer.d.ts +1 -1
- package/lib/app/observer/top_observer.js +2 -2
- package/lib/index.js +1 -1
- package/lib/modules/img.js +1 -1
- package/lib/utils.d.ts +3 -3
- package/lib/utils.js +28 -9
- package/package.json +1 -1
package/cjs/app/index.d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
167
|
-
|
|
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(
|
|
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
|
-
|
|
179
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
244
|
-
|
|
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:
|
|
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
|
-
|
|
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.
|
|
277
|
+
this.addCommand(proto.startIframe);
|
|
254
278
|
};
|
|
255
279
|
this.killChildrenFrames = () => {
|
|
256
|
-
this.
|
|
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
|
|
296
|
-
|
|
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
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
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,
|
|
507
|
+
async checkNodeId(iframes, source) {
|
|
479
508
|
for (const iframe of iframes) {
|
|
480
|
-
if (iframe.
|
|
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
|
|
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
|
|
1155
|
+
this.worker?.postMessage('stop');
|
|
1128
1156
|
}
|
|
1129
1157
|
else {
|
|
1130
|
-
this.worker
|
|
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
|
}
|
package/cjs/app/nodes.d.ts
CHANGED
|
@@ -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)
|
|
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 =
|
|
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
|
}
|
|
@@ -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(
|
|
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)(
|
|
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.
|
|
107
|
-
name = name.
|
|
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.
|
|
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(
|
|
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(
|
|
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(
|
|
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.
|
|
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,
|
package/cjs/modules/img.js
CHANGED
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;
|