@openreplay/tracker 14.0.9 → 14.0.10-beta.1
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/CHANGELOG.md +6 -0
- package/cjs/app/index.d.ts +18 -8
- package/cjs/app/index.js +198 -149
- package/cjs/index.js +1 -1
- package/cjs/modules/exception.js +7 -2
- package/cjs/utils.js +4 -4
- package/lib/app/index.d.ts +18 -8
- package/lib/app/index.js +198 -149
- package/lib/index.js +1 -1
- package/lib/modules/exception.js +7 -2
- package/lib/utils.js +4 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
# 14.0.10
|
|
2
|
+
|
|
3
|
+
- adjust timestamps for messages from tracker instances inside child iframes (if they were loaded later)
|
|
4
|
+
- restart child trackers if parent tracker is restarted
|
|
5
|
+
- fixes for general stability of crossdomain iframe tracking
|
|
6
|
+
|
|
1
7
|
# 14.0.9
|
|
2
8
|
|
|
3
9
|
- more stable crossdomain iframe tracking (refactored child/parent process discovery)
|
package/cjs/app/index.d.ts
CHANGED
|
@@ -37,6 +37,12 @@ declare const SuccessfulStart: (body: OnStartInfo) => SuccessfulStart;
|
|
|
37
37
|
export type StartPromiseReturn = SuccessfulStart | UnsuccessfulStart;
|
|
38
38
|
type StartCallback = (i: OnStartInfo) => void;
|
|
39
39
|
type CommitCallback = (messages: Array<Message>) => void;
|
|
40
|
+
declare enum ActivityState {
|
|
41
|
+
NotActive = 0,
|
|
42
|
+
Starting = 1,
|
|
43
|
+
Active = 2,
|
|
44
|
+
ColdStart = 3
|
|
45
|
+
}
|
|
40
46
|
type AppOptions = {
|
|
41
47
|
revID: string;
|
|
42
48
|
node_id: string;
|
|
@@ -99,7 +105,7 @@ export type Options = AppOptions & ObserverOptions & SanitizerOptions;
|
|
|
99
105
|
export declare const DEFAULT_INGEST_POINT = "https://api.openreplay.com/ingest";
|
|
100
106
|
export default class App {
|
|
101
107
|
private readonly signalError;
|
|
102
|
-
|
|
108
|
+
readonly insideIframe: boolean;
|
|
103
109
|
readonly nodes: Nodes;
|
|
104
110
|
readonly ticker: Ticker;
|
|
105
111
|
readonly projectKey: string;
|
|
@@ -146,12 +152,15 @@ export default class App {
|
|
|
146
152
|
/** used by child iframes for crossdomain only */
|
|
147
153
|
parentActive: boolean;
|
|
148
154
|
checkStatus: () => boolean;
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
155
|
+
parentCrossDomainFrameListener: (event: MessageEvent) => void;
|
|
156
|
+
trackedFrames: number[];
|
|
157
|
+
crossDomainIframeListener: (event: MessageEvent) => void;
|
|
158
|
+
pollingQueue: string[];
|
|
159
|
+
bootChildrenFrames: () => Promise<void>;
|
|
160
|
+
killChildrenFrames: () => void;
|
|
152
161
|
signalIframeTracker: () => void;
|
|
153
162
|
startTimeout: ReturnType<typeof setTimeout> | null;
|
|
154
|
-
|
|
163
|
+
allowAppStart(): void;
|
|
155
164
|
private checkNodeId;
|
|
156
165
|
private initWorker;
|
|
157
166
|
private handleWorkerMsg;
|
|
@@ -176,9 +185,9 @@ export default class App {
|
|
|
176
185
|
timestamp(): number;
|
|
177
186
|
safe<T extends (this: any, ...args: any[]) => void>(fn: T): T;
|
|
178
187
|
attachCommitCallback(cb: CommitCallback): void;
|
|
179
|
-
attachStartCallback(cb: StartCallback, useSafe?: boolean)
|
|
180
|
-
attachStopCallback(cb: () => any, useSafe?: boolean)
|
|
181
|
-
attachEventListener(target: EventTarget, type: string, listener: EventListener, useSafe?: boolean, useCapture?: boolean)
|
|
188
|
+
attachStartCallback: (cb: StartCallback, useSafe?: boolean) => void;
|
|
189
|
+
attachStopCallback: (cb: () => any, useSafe?: boolean) => void;
|
|
190
|
+
attachEventListener: (target: EventTarget, type: string, listener: EventListener, useSafe?: boolean, useCapture?: boolean) => void;
|
|
182
191
|
checkRequiredVersion(version: string): boolean;
|
|
183
192
|
private getTrackerInfo;
|
|
184
193
|
getSessionInfo(): {
|
|
@@ -260,6 +269,7 @@ export default class App {
|
|
|
260
269
|
getUxtId(): number | null;
|
|
261
270
|
waitStart(): Promise<unknown>;
|
|
262
271
|
waitStarted(): Promise<unknown>;
|
|
272
|
+
waitStatus(status: ActivityState): Promise<unknown>;
|
|
263
273
|
/**
|
|
264
274
|
* basically we ask other tabs during constructor
|
|
265
275
|
* and here we just apply 10ms delay just in case
|
package/cjs/app/index.js
CHANGED
|
@@ -72,14 +72,14 @@ const proto = {
|
|
|
72
72
|
resp: 'never-gonna-let-you-down',
|
|
73
73
|
// regenerating id (copied other tab)
|
|
74
74
|
reg: 'never-gonna-run-around-and-desert-you',
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
//
|
|
82
|
-
|
|
75
|
+
iframeSignal: 'tracker inside a child iframe',
|
|
76
|
+
iframeId: 'getting node id for child iframe',
|
|
77
|
+
iframeBatch: 'batch of messages from an iframe window',
|
|
78
|
+
parentAlive: 'signal that parent is live',
|
|
79
|
+
killIframe: 'stop tracker inside frame',
|
|
80
|
+
startIframe: 'start tracker inside frame',
|
|
81
|
+
// checking updates
|
|
82
|
+
polling: 'hello-how-are-you-im-under-the-water-please-help-me',
|
|
83
83
|
};
|
|
84
84
|
class App {
|
|
85
85
|
constructor(projectKey, sessionToken, options, signalError, insideIframe) {
|
|
@@ -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.
|
|
99
|
+
this.version = '14.0.10-beta.1'; // TODO: version compatability check inside each plugin.
|
|
100
100
|
this.socketMode = false;
|
|
101
101
|
this.compressionThreshold = 24 * 1000;
|
|
102
102
|
this.bc = null;
|
|
@@ -116,104 +116,144 @@ class App {
|
|
|
116
116
|
this.checkStatus = () => {
|
|
117
117
|
return this.parentActive;
|
|
118
118
|
};
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
119
|
+
this.parentCrossDomainFrameListener = (event) => {
|
|
120
|
+
const { data } = event;
|
|
121
|
+
if (!data || event.source === window)
|
|
122
|
+
return;
|
|
123
|
+
if (data.line === proto.startIframe) {
|
|
124
|
+
if (this.active())
|
|
125
|
+
return;
|
|
126
|
+
try {
|
|
127
|
+
this.allowAppStart();
|
|
128
|
+
void this.start();
|
|
129
|
+
}
|
|
130
|
+
catch (e) {
|
|
131
|
+
console.error('children frame restart failed:', e);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (data.line === proto.parentAlive) {
|
|
135
|
+
this.parentActive = true;
|
|
136
|
+
}
|
|
137
|
+
if (data.line === proto.iframeId) {
|
|
138
|
+
this.parentActive = true;
|
|
139
|
+
this.rootId = data.id;
|
|
140
|
+
this.session.setSessionToken(data.token);
|
|
141
|
+
this.frameOderNumber = data.frameOrderNumber;
|
|
142
|
+
this.debug.log('starting iframe tracking', data);
|
|
143
|
+
this.allowAppStart();
|
|
144
|
+
this.delay = data.frameTimeOffset;
|
|
145
|
+
}
|
|
146
|
+
if (data.line === proto.killIframe) {
|
|
147
|
+
if (this.active()) {
|
|
148
|
+
this.stop();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
this.trackedFrames = [];
|
|
153
|
+
this.crossDomainIframeListener = (event) => {
|
|
154
|
+
if (!this.active() || event.source === window)
|
|
155
|
+
return;
|
|
156
|
+
const { data } = event;
|
|
157
|
+
if (!data)
|
|
158
|
+
return;
|
|
159
|
+
if (data.line === proto.iframeSignal) {
|
|
160
|
+
// @ts-ignore
|
|
161
|
+
event.source?.postMessage({ ping: true, line: proto.parentAlive }, '*');
|
|
162
|
+
const childIframeDomain = data.domain;
|
|
163
|
+
const pageIframes = Array.from(document.querySelectorAll('iframe'));
|
|
164
|
+
this.pageFrames = pageIframes;
|
|
165
|
+
const signalId = async () => {
|
|
166
|
+
const id = await this.checkNodeId(pageIframes, childIframeDomain);
|
|
167
|
+
if (id && !this.trackedFrames.includes(id)) {
|
|
168
|
+
try {
|
|
169
|
+
this.trackedFrames.push(id);
|
|
170
|
+
await this.waitStarted();
|
|
171
|
+
const token = this.session.getSessionToken();
|
|
172
|
+
const iframeData = {
|
|
173
|
+
line: proto.iframeId,
|
|
174
|
+
context: this.contextId,
|
|
175
|
+
domain: childIframeDomain,
|
|
176
|
+
id,
|
|
177
|
+
token,
|
|
178
|
+
frameOrderNumber: this.trackedFrames.length,
|
|
179
|
+
frameTimeOffset: this.timestamp(),
|
|
180
|
+
};
|
|
181
|
+
// @ts-ignore
|
|
182
|
+
event.source?.postMessage(iframeData, '*');
|
|
183
|
+
}
|
|
184
|
+
catch (e) {
|
|
185
|
+
console.error(e);
|
|
186
|
+
}
|
|
165
187
|
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
});
|
|
182
|
-
|
|
188
|
+
};
|
|
189
|
+
void signalId();
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* proxying messages from iframe to main body, so they can be in one batch (same indexes, etc)
|
|
193
|
+
* plus we rewrite some of the messages to be relative to the main context/window
|
|
194
|
+
* */
|
|
195
|
+
if (data.line === proto.iframeBatch) {
|
|
196
|
+
const msgBatch = data.messages;
|
|
197
|
+
const mappedMessages = msgBatch.map((msg) => {
|
|
198
|
+
if (msg[0] === 20 /* MType.MouseMove */) {
|
|
199
|
+
let fixedMessage = msg;
|
|
200
|
+
this.pageFrames.forEach((frame) => {
|
|
201
|
+
if (frame.dataset.domain === event.data.domain) {
|
|
202
|
+
const [type, x, y] = msg;
|
|
203
|
+
const { left, top } = frame.getBoundingClientRect();
|
|
204
|
+
fixedMessage = [type, x + left, y + top];
|
|
183
205
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
206
|
+
});
|
|
207
|
+
return fixedMessage;
|
|
208
|
+
}
|
|
209
|
+
if (msg[0] === 68 /* MType.MouseClick */) {
|
|
210
|
+
let fixedMessage = msg;
|
|
211
|
+
this.pageFrames.forEach((frame) => {
|
|
212
|
+
if (frame.dataset.domain === event.data.domain) {
|
|
213
|
+
const [type, id, hesitationTime, label, selector, normX, normY] = msg;
|
|
214
|
+
const { left, top, width, height } = frame.getBoundingClientRect();
|
|
215
|
+
const contentWidth = document.documentElement.scrollWidth;
|
|
216
|
+
const contentHeight = document.documentElement.scrollHeight;
|
|
217
|
+
// (normalizedX * frameWidth + frameLeftOffset)/docSize
|
|
218
|
+
const fullX = (normX / 100) * width + left;
|
|
219
|
+
const fullY = (normY / 100) * height + top;
|
|
220
|
+
const fixedX = fullX / contentWidth;
|
|
221
|
+
const fixedY = fullY / contentHeight;
|
|
222
|
+
fixedMessage = [
|
|
223
|
+
type,
|
|
224
|
+
id,
|
|
225
|
+
hesitationTime,
|
|
226
|
+
label,
|
|
227
|
+
selector,
|
|
228
|
+
Math.round(fixedX * 1e3) / 1e1,
|
|
229
|
+
Math.round(fixedY * 1e3) / 1e1,
|
|
230
|
+
];
|
|
209
231
|
}
|
|
210
|
-
return msg;
|
|
211
232
|
});
|
|
212
|
-
|
|
233
|
+
return fixedMessage;
|
|
213
234
|
}
|
|
214
|
-
|
|
215
|
-
|
|
235
|
+
return msg;
|
|
236
|
+
});
|
|
237
|
+
this.messages.push(...mappedMessages);
|
|
216
238
|
}
|
|
239
|
+
if (data.line === proto.polling) {
|
|
240
|
+
if (!this.pollingQueue.length) {
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
while (this.pollingQueue.length) {
|
|
244
|
+
const msg = this.pollingQueue.shift();
|
|
245
|
+
// @ts-ignore
|
|
246
|
+
event.source?.postMessage({ line: msg }, '*');
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
this.pollingQueue = [];
|
|
251
|
+
this.bootChildrenFrames = async () => {
|
|
252
|
+
await this.waitStarted();
|
|
253
|
+
this.pollingQueue.push(proto.startIframe);
|
|
254
|
+
};
|
|
255
|
+
this.killChildrenFrames = () => {
|
|
256
|
+
this.pollingQueue.push(proto.killIframe);
|
|
217
257
|
};
|
|
218
258
|
this.signalIframeTracker = () => {
|
|
219
259
|
const domain = this.initialHostName;
|
|
@@ -236,6 +276,27 @@ class App {
|
|
|
236
276
|
this.startTimeout = null;
|
|
237
277
|
this.coldStartCommitN = 0;
|
|
238
278
|
this.delay = 0;
|
|
279
|
+
this.attachStartCallback = (cb, useSafe = false) => {
|
|
280
|
+
if (useSafe) {
|
|
281
|
+
cb = this.safe(cb);
|
|
282
|
+
}
|
|
283
|
+
this.startCallbacks.push(cb);
|
|
284
|
+
};
|
|
285
|
+
this.attachStopCallback = (cb, useSafe = false) => {
|
|
286
|
+
if (useSafe) {
|
|
287
|
+
cb = this.safe(cb);
|
|
288
|
+
}
|
|
289
|
+
this.stopCallbacks.push(cb);
|
|
290
|
+
};
|
|
291
|
+
this.attachEventListener = (target, type, listener, useSafe = true, useCapture = true) => {
|
|
292
|
+
if (useSafe) {
|
|
293
|
+
listener = this.safe(listener);
|
|
294
|
+
}
|
|
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;
|
|
297
|
+
this.attachStartCallback(createListener, useSafe);
|
|
298
|
+
this.attachStopCallback(deleteListener, useSafe);
|
|
299
|
+
};
|
|
239
300
|
this.coldInterval = null;
|
|
240
301
|
this.orderNumber = 0;
|
|
241
302
|
this.coldStartTs = 0;
|
|
@@ -351,25 +412,24 @@ class App {
|
|
|
351
412
|
}
|
|
352
413
|
this.initWorker();
|
|
353
414
|
const thisTab = this.session.getTabId();
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
}
|
|
372
|
-
window.addEventListener('message', catchParentMessage);
|
|
415
|
+
/**
|
|
416
|
+
* listen for messages from parent window, so we can signal that we're alive
|
|
417
|
+
* */
|
|
418
|
+
if (this.insideIframe) {
|
|
419
|
+
window.addEventListener('message', this.parentCrossDomainFrameListener);
|
|
420
|
+
setInterval(() => {
|
|
421
|
+
window.parent.postMessage({
|
|
422
|
+
line: proto.polling,
|
|
423
|
+
}, '*');
|
|
424
|
+
}, 250);
|
|
425
|
+
}
|
|
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) {
|
|
431
|
+
window.addEventListener('message', this.crossDomainIframeListener);
|
|
432
|
+
}
|
|
373
433
|
if (this.bc !== null) {
|
|
374
434
|
this.bc.postMessage({
|
|
375
435
|
line: proto.ask,
|
|
@@ -378,7 +438,7 @@ class App {
|
|
|
378
438
|
});
|
|
379
439
|
this.startTimeout = setTimeout(() => {
|
|
380
440
|
this.allowAppStart();
|
|
381
|
-
},
|
|
441
|
+
}, 250);
|
|
382
442
|
this.bc.onmessage = (ev) => {
|
|
383
443
|
if (ev.data.context === this.contextId) {
|
|
384
444
|
return;
|
|
@@ -634,27 +694,6 @@ class App {
|
|
|
634
694
|
attachCommitCallback(cb) {
|
|
635
695
|
this.commitCallbacks.push(cb);
|
|
636
696
|
}
|
|
637
|
-
attachStartCallback(cb, useSafe = false) {
|
|
638
|
-
if (useSafe) {
|
|
639
|
-
cb = this.safe(cb);
|
|
640
|
-
}
|
|
641
|
-
this.startCallbacks.push(cb);
|
|
642
|
-
}
|
|
643
|
-
attachStopCallback(cb, useSafe = false) {
|
|
644
|
-
if (useSafe) {
|
|
645
|
-
cb = this.safe(cb);
|
|
646
|
-
}
|
|
647
|
-
this.stopCallbacks.push(cb);
|
|
648
|
-
}
|
|
649
|
-
attachEventListener(target, type, listener, useSafe = true, useCapture = true) {
|
|
650
|
-
if (useSafe) {
|
|
651
|
-
listener = this.safe(listener);
|
|
652
|
-
}
|
|
653
|
-
const createListener = () => target ? (0, utils_js_1.createEventListener)(target, type, listener, useCapture) : null;
|
|
654
|
-
const deleteListener = () => target ? (0, utils_js_1.deleteEventListener)(target, type, listener, useCapture) : null;
|
|
655
|
-
this.attachStartCallback(createListener, useSafe);
|
|
656
|
-
this.attachStopCallback(deleteListener, useSafe);
|
|
657
|
-
}
|
|
658
697
|
// TODO: full correct semantic
|
|
659
698
|
checkRequiredVersion(version) {
|
|
660
699
|
const reqVer = version.split(/[.-]/);
|
|
@@ -1112,8 +1151,8 @@ class App {
|
|
|
1112
1151
|
}
|
|
1113
1152
|
await this.tagWatcher.fetchTags(this.options.ingestPoint, token);
|
|
1114
1153
|
this.activityState = ActivityState.Active;
|
|
1115
|
-
if (this.options.crossdomain?.enabled
|
|
1116
|
-
this.
|
|
1154
|
+
if (this.options.crossdomain?.enabled && !this.insideIframe) {
|
|
1155
|
+
void this.bootChildrenFrames();
|
|
1117
1156
|
}
|
|
1118
1157
|
if (canvasEnabled && !this.options.canvas.disableCanvas) {
|
|
1119
1158
|
this.canvasRecorder =
|
|
@@ -1220,9 +1259,12 @@ class App {
|
|
|
1220
1259
|
});
|
|
1221
1260
|
}
|
|
1222
1261
|
async waitStarted() {
|
|
1262
|
+
return this.waitStatus(ActivityState.Active);
|
|
1263
|
+
}
|
|
1264
|
+
async waitStatus(status) {
|
|
1223
1265
|
return new Promise((resolve) => {
|
|
1224
1266
|
const check = () => {
|
|
1225
|
-
if (this.activityState ===
|
|
1267
|
+
if (this.activityState === status) {
|
|
1226
1268
|
resolve(true);
|
|
1227
1269
|
}
|
|
1228
1270
|
else {
|
|
@@ -1237,14 +1279,14 @@ class App {
|
|
|
1237
1279
|
* and here we just apply 10ms delay just in case
|
|
1238
1280
|
* */
|
|
1239
1281
|
async start(...args) {
|
|
1240
|
-
if (this.insideIframe) {
|
|
1241
|
-
this.signalIframeTracker();
|
|
1242
|
-
}
|
|
1243
1282
|
if (this.activityState === ActivityState.Active ||
|
|
1244
1283
|
this.activityState === ActivityState.Starting) {
|
|
1245
1284
|
const reason = 'OpenReplay: trying to call `start()` on the instance that has been started already.';
|
|
1246
1285
|
return Promise.resolve(UnsuccessfulStart(reason));
|
|
1247
1286
|
}
|
|
1287
|
+
if (this.insideIframe) {
|
|
1288
|
+
this.signalIframeTracker();
|
|
1289
|
+
}
|
|
1248
1290
|
if (!document.hidden) {
|
|
1249
1291
|
await this.waitStart();
|
|
1250
1292
|
return this._start(...args);
|
|
@@ -1293,23 +1335,30 @@ class App {
|
|
|
1293
1335
|
}
|
|
1294
1336
|
stop(stopWorker = true) {
|
|
1295
1337
|
if (this.activityState !== ActivityState.NotActive) {
|
|
1338
|
+
console.trace('stopped');
|
|
1296
1339
|
try {
|
|
1340
|
+
if (!this.insideIframe && this.options.crossdomain?.enabled) {
|
|
1341
|
+
this.killChildrenFrames();
|
|
1342
|
+
}
|
|
1297
1343
|
this.attributeSender.clear();
|
|
1298
1344
|
this.sanitizer.clear();
|
|
1299
1345
|
this.observer.disconnect();
|
|
1300
1346
|
this.nodes.clear();
|
|
1301
1347
|
this.ticker.stop();
|
|
1302
1348
|
this.stopCallbacks.forEach((cb) => cb());
|
|
1303
|
-
this.debug.log('OpenReplay tracking stopped.');
|
|
1304
1349
|
this.tagWatcher.clear();
|
|
1305
1350
|
if (this.worker && stopWorker) {
|
|
1306
1351
|
this.worker.postMessage('stop');
|
|
1307
1352
|
}
|
|
1308
1353
|
this.canvasRecorder?.clear();
|
|
1309
1354
|
this.messages.length = 0;
|
|
1355
|
+
this.trackedFrames = [];
|
|
1356
|
+
this.parentActive = false;
|
|
1357
|
+
this.canStart = false;
|
|
1310
1358
|
}
|
|
1311
1359
|
finally {
|
|
1312
1360
|
this.activityState = ActivityState.NotActive;
|
|
1361
|
+
this.debug.log('OpenReplay tracking stopped.');
|
|
1313
1362
|
}
|
|
1314
1363
|
}
|
|
1315
1364
|
}
|
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.
|
|
101
|
+
trackerVersion: '14.0.10-beta.1',
|
|
102
102
|
projectKey: this.options.projectKey,
|
|
103
103
|
doNotTrack,
|
|
104
104
|
reason: missingApi.length ? `missing api: ${missingApi.join(',')}` : reason,
|
package/cjs/modules/exception.js
CHANGED
|
@@ -69,8 +69,13 @@ function default_1(app, opts) {
|
|
|
69
69
|
app.send(msg);
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
|
-
|
|
73
|
-
|
|
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 (?)
|
package/cjs/utils.js
CHANGED
|
@@ -131,9 +131,9 @@ function createEventListener(target, event, cb, capture) {
|
|
|
131
131
|
}
|
|
132
132
|
catch (e) {
|
|
133
133
|
const msg = e.message;
|
|
134
|
-
console.
|
|
134
|
+
console.error(
|
|
135
135
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
136
|
-
`Openreplay: ${msg}; if this error is caused by an IframeObserver, ignore it
|
|
136
|
+
`Openreplay: ${msg}; if this error is caused by an IframeObserver, ignore it`, event);
|
|
137
137
|
}
|
|
138
138
|
}
|
|
139
139
|
exports.createEventListener = createEventListener;
|
|
@@ -144,9 +144,9 @@ function deleteEventListener(target, event, cb, capture) {
|
|
|
144
144
|
}
|
|
145
145
|
catch (e) {
|
|
146
146
|
const msg = e.message;
|
|
147
|
-
console.
|
|
147
|
+
console.error(
|
|
148
148
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
149
|
-
`Openreplay: ${msg}; if this error is caused by an IframeObserver, ignore it
|
|
149
|
+
`Openreplay: ${msg}; if this error is caused by an IframeObserver, ignore it`, event);
|
|
150
150
|
}
|
|
151
151
|
}
|
|
152
152
|
exports.deleteEventListener = deleteEventListener;
|
package/lib/app/index.d.ts
CHANGED
|
@@ -37,6 +37,12 @@ declare const SuccessfulStart: (body: OnStartInfo) => SuccessfulStart;
|
|
|
37
37
|
export type StartPromiseReturn = SuccessfulStart | UnsuccessfulStart;
|
|
38
38
|
type StartCallback = (i: OnStartInfo) => void;
|
|
39
39
|
type CommitCallback = (messages: Array<Message>) => void;
|
|
40
|
+
declare enum ActivityState {
|
|
41
|
+
NotActive = 0,
|
|
42
|
+
Starting = 1,
|
|
43
|
+
Active = 2,
|
|
44
|
+
ColdStart = 3
|
|
45
|
+
}
|
|
40
46
|
type AppOptions = {
|
|
41
47
|
revID: string;
|
|
42
48
|
node_id: string;
|
|
@@ -99,7 +105,7 @@ export type Options = AppOptions & ObserverOptions & SanitizerOptions;
|
|
|
99
105
|
export declare const DEFAULT_INGEST_POINT = "https://api.openreplay.com/ingest";
|
|
100
106
|
export default class App {
|
|
101
107
|
private readonly signalError;
|
|
102
|
-
|
|
108
|
+
readonly insideIframe: boolean;
|
|
103
109
|
readonly nodes: Nodes;
|
|
104
110
|
readonly ticker: Ticker;
|
|
105
111
|
readonly projectKey: string;
|
|
@@ -146,12 +152,15 @@ export default class App {
|
|
|
146
152
|
/** used by child iframes for crossdomain only */
|
|
147
153
|
parentActive: boolean;
|
|
148
154
|
checkStatus: () => boolean;
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
155
|
+
parentCrossDomainFrameListener: (event: MessageEvent) => void;
|
|
156
|
+
trackedFrames: number[];
|
|
157
|
+
crossDomainIframeListener: (event: MessageEvent) => void;
|
|
158
|
+
pollingQueue: string[];
|
|
159
|
+
bootChildrenFrames: () => Promise<void>;
|
|
160
|
+
killChildrenFrames: () => void;
|
|
152
161
|
signalIframeTracker: () => void;
|
|
153
162
|
startTimeout: ReturnType<typeof setTimeout> | null;
|
|
154
|
-
|
|
163
|
+
allowAppStart(): void;
|
|
155
164
|
private checkNodeId;
|
|
156
165
|
private initWorker;
|
|
157
166
|
private handleWorkerMsg;
|
|
@@ -176,9 +185,9 @@ export default class App {
|
|
|
176
185
|
timestamp(): number;
|
|
177
186
|
safe<T extends (this: any, ...args: any[]) => void>(fn: T): T;
|
|
178
187
|
attachCommitCallback(cb: CommitCallback): void;
|
|
179
|
-
attachStartCallback(cb: StartCallback, useSafe?: boolean)
|
|
180
|
-
attachStopCallback(cb: () => any, useSafe?: boolean)
|
|
181
|
-
attachEventListener(target: EventTarget, type: string, listener: EventListener, useSafe?: boolean, useCapture?: boolean)
|
|
188
|
+
attachStartCallback: (cb: StartCallback, useSafe?: boolean) => void;
|
|
189
|
+
attachStopCallback: (cb: () => any, useSafe?: boolean) => void;
|
|
190
|
+
attachEventListener: (target: EventTarget, type: string, listener: EventListener, useSafe?: boolean, useCapture?: boolean) => void;
|
|
182
191
|
checkRequiredVersion(version: string): boolean;
|
|
183
192
|
private getTrackerInfo;
|
|
184
193
|
getSessionInfo(): {
|
|
@@ -260,6 +269,7 @@ export default class App {
|
|
|
260
269
|
getUxtId(): number | null;
|
|
261
270
|
waitStart(): Promise<unknown>;
|
|
262
271
|
waitStarted(): Promise<unknown>;
|
|
272
|
+
waitStatus(status: ActivityState): Promise<unknown>;
|
|
263
273
|
/**
|
|
264
274
|
* basically we ask other tabs during constructor
|
|
265
275
|
* and here we just apply 10ms delay just in case
|
package/lib/app/index.js
CHANGED
|
@@ -43,14 +43,14 @@ const proto = {
|
|
|
43
43
|
resp: 'never-gonna-let-you-down',
|
|
44
44
|
// regenerating id (copied other tab)
|
|
45
45
|
reg: 'never-gonna-run-around-and-desert-you',
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
//
|
|
53
|
-
|
|
46
|
+
iframeSignal: 'tracker inside a child iframe',
|
|
47
|
+
iframeId: 'getting node id for child iframe',
|
|
48
|
+
iframeBatch: 'batch of messages from an iframe window',
|
|
49
|
+
parentAlive: 'signal that parent is live',
|
|
50
|
+
killIframe: 'stop tracker inside frame',
|
|
51
|
+
startIframe: 'start tracker inside frame',
|
|
52
|
+
// checking updates
|
|
53
|
+
polling: 'hello-how-are-you-im-under-the-water-please-help-me',
|
|
54
54
|
};
|
|
55
55
|
export default class App {
|
|
56
56
|
constructor(projectKey, sessionToken, options, signalError, insideIframe) {
|
|
@@ -67,7 +67,7 @@ export default class App {
|
|
|
67
67
|
this.stopCallbacks = [];
|
|
68
68
|
this.commitCallbacks = [];
|
|
69
69
|
this.activityState = ActivityState.NotActive;
|
|
70
|
-
this.version = '14.0.
|
|
70
|
+
this.version = '14.0.10-beta.1'; // TODO: version compatability check inside each plugin.
|
|
71
71
|
this.socketMode = false;
|
|
72
72
|
this.compressionThreshold = 24 * 1000;
|
|
73
73
|
this.bc = null;
|
|
@@ -87,104 +87,144 @@ export default class App {
|
|
|
87
87
|
this.checkStatus = () => {
|
|
88
88
|
return this.parentActive;
|
|
89
89
|
};
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
90
|
+
this.parentCrossDomainFrameListener = (event) => {
|
|
91
|
+
const { data } = event;
|
|
92
|
+
if (!data || event.source === window)
|
|
93
|
+
return;
|
|
94
|
+
if (data.line === proto.startIframe) {
|
|
95
|
+
if (this.active())
|
|
96
|
+
return;
|
|
97
|
+
try {
|
|
98
|
+
this.allowAppStart();
|
|
99
|
+
void this.start();
|
|
100
|
+
}
|
|
101
|
+
catch (e) {
|
|
102
|
+
console.error('children frame restart failed:', e);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (data.line === proto.parentAlive) {
|
|
106
|
+
this.parentActive = true;
|
|
107
|
+
}
|
|
108
|
+
if (data.line === proto.iframeId) {
|
|
109
|
+
this.parentActive = true;
|
|
110
|
+
this.rootId = data.id;
|
|
111
|
+
this.session.setSessionToken(data.token);
|
|
112
|
+
this.frameOderNumber = data.frameOrderNumber;
|
|
113
|
+
this.debug.log('starting iframe tracking', data);
|
|
114
|
+
this.allowAppStart();
|
|
115
|
+
this.delay = data.frameTimeOffset;
|
|
116
|
+
}
|
|
117
|
+
if (data.line === proto.killIframe) {
|
|
118
|
+
if (this.active()) {
|
|
119
|
+
this.stop();
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
this.trackedFrames = [];
|
|
124
|
+
this.crossDomainIframeListener = (event) => {
|
|
125
|
+
if (!this.active() || event.source === window)
|
|
126
|
+
return;
|
|
127
|
+
const { data } = event;
|
|
128
|
+
if (!data)
|
|
129
|
+
return;
|
|
130
|
+
if (data.line === proto.iframeSignal) {
|
|
131
|
+
// @ts-ignore
|
|
132
|
+
event.source?.postMessage({ ping: true, line: proto.parentAlive }, '*');
|
|
133
|
+
const childIframeDomain = data.domain;
|
|
134
|
+
const pageIframes = Array.from(document.querySelectorAll('iframe'));
|
|
135
|
+
this.pageFrames = pageIframes;
|
|
136
|
+
const signalId = async () => {
|
|
137
|
+
const id = await this.checkNodeId(pageIframes, childIframeDomain);
|
|
138
|
+
if (id && !this.trackedFrames.includes(id)) {
|
|
139
|
+
try {
|
|
140
|
+
this.trackedFrames.push(id);
|
|
141
|
+
await this.waitStarted();
|
|
142
|
+
const token = this.session.getSessionToken();
|
|
143
|
+
const iframeData = {
|
|
144
|
+
line: proto.iframeId,
|
|
145
|
+
context: this.contextId,
|
|
146
|
+
domain: childIframeDomain,
|
|
147
|
+
id,
|
|
148
|
+
token,
|
|
149
|
+
frameOrderNumber: this.trackedFrames.length,
|
|
150
|
+
frameTimeOffset: this.timestamp(),
|
|
151
|
+
};
|
|
152
|
+
// @ts-ignore
|
|
153
|
+
event.source?.postMessage(iframeData, '*');
|
|
154
|
+
}
|
|
155
|
+
catch (e) {
|
|
156
|
+
console.error(e);
|
|
157
|
+
}
|
|
136
158
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
});
|
|
153
|
-
|
|
159
|
+
};
|
|
160
|
+
void signalId();
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* proxying messages from iframe to main body, so they can be in one batch (same indexes, etc)
|
|
164
|
+
* plus we rewrite some of the messages to be relative to the main context/window
|
|
165
|
+
* */
|
|
166
|
+
if (data.line === proto.iframeBatch) {
|
|
167
|
+
const msgBatch = data.messages;
|
|
168
|
+
const mappedMessages = msgBatch.map((msg) => {
|
|
169
|
+
if (msg[0] === 20 /* MType.MouseMove */) {
|
|
170
|
+
let fixedMessage = msg;
|
|
171
|
+
this.pageFrames.forEach((frame) => {
|
|
172
|
+
if (frame.dataset.domain === event.data.domain) {
|
|
173
|
+
const [type, x, y] = msg;
|
|
174
|
+
const { left, top } = frame.getBoundingClientRect();
|
|
175
|
+
fixedMessage = [type, x + left, y + top];
|
|
154
176
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
177
|
+
});
|
|
178
|
+
return fixedMessage;
|
|
179
|
+
}
|
|
180
|
+
if (msg[0] === 68 /* MType.MouseClick */) {
|
|
181
|
+
let fixedMessage = msg;
|
|
182
|
+
this.pageFrames.forEach((frame) => {
|
|
183
|
+
if (frame.dataset.domain === event.data.domain) {
|
|
184
|
+
const [type, id, hesitationTime, label, selector, normX, normY] = msg;
|
|
185
|
+
const { left, top, width, height } = frame.getBoundingClientRect();
|
|
186
|
+
const contentWidth = document.documentElement.scrollWidth;
|
|
187
|
+
const contentHeight = document.documentElement.scrollHeight;
|
|
188
|
+
// (normalizedX * frameWidth + frameLeftOffset)/docSize
|
|
189
|
+
const fullX = (normX / 100) * width + left;
|
|
190
|
+
const fullY = (normY / 100) * height + top;
|
|
191
|
+
const fixedX = fullX / contentWidth;
|
|
192
|
+
const fixedY = fullY / contentHeight;
|
|
193
|
+
fixedMessage = [
|
|
194
|
+
type,
|
|
195
|
+
id,
|
|
196
|
+
hesitationTime,
|
|
197
|
+
label,
|
|
198
|
+
selector,
|
|
199
|
+
Math.round(fixedX * 1e3) / 1e1,
|
|
200
|
+
Math.round(fixedY * 1e3) / 1e1,
|
|
201
|
+
];
|
|
180
202
|
}
|
|
181
|
-
return msg;
|
|
182
203
|
});
|
|
183
|
-
|
|
204
|
+
return fixedMessage;
|
|
184
205
|
}
|
|
185
|
-
|
|
186
|
-
|
|
206
|
+
return msg;
|
|
207
|
+
});
|
|
208
|
+
this.messages.push(...mappedMessages);
|
|
187
209
|
}
|
|
210
|
+
if (data.line === proto.polling) {
|
|
211
|
+
if (!this.pollingQueue.length) {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
while (this.pollingQueue.length) {
|
|
215
|
+
const msg = this.pollingQueue.shift();
|
|
216
|
+
// @ts-ignore
|
|
217
|
+
event.source?.postMessage({ line: msg }, '*');
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
this.pollingQueue = [];
|
|
222
|
+
this.bootChildrenFrames = async () => {
|
|
223
|
+
await this.waitStarted();
|
|
224
|
+
this.pollingQueue.push(proto.startIframe);
|
|
225
|
+
};
|
|
226
|
+
this.killChildrenFrames = () => {
|
|
227
|
+
this.pollingQueue.push(proto.killIframe);
|
|
188
228
|
};
|
|
189
229
|
this.signalIframeTracker = () => {
|
|
190
230
|
const domain = this.initialHostName;
|
|
@@ -207,6 +247,27 @@ export default class App {
|
|
|
207
247
|
this.startTimeout = null;
|
|
208
248
|
this.coldStartCommitN = 0;
|
|
209
249
|
this.delay = 0;
|
|
250
|
+
this.attachStartCallback = (cb, useSafe = false) => {
|
|
251
|
+
if (useSafe) {
|
|
252
|
+
cb = this.safe(cb);
|
|
253
|
+
}
|
|
254
|
+
this.startCallbacks.push(cb);
|
|
255
|
+
};
|
|
256
|
+
this.attachStopCallback = (cb, useSafe = false) => {
|
|
257
|
+
if (useSafe) {
|
|
258
|
+
cb = this.safe(cb);
|
|
259
|
+
}
|
|
260
|
+
this.stopCallbacks.push(cb);
|
|
261
|
+
};
|
|
262
|
+
this.attachEventListener = (target, type, listener, useSafe = true, useCapture = true) => {
|
|
263
|
+
if (useSafe) {
|
|
264
|
+
listener = this.safe(listener);
|
|
265
|
+
}
|
|
266
|
+
const createListener = () => target ? createEventListener(target, type, listener, useCapture) : null;
|
|
267
|
+
const deleteListener = () => target ? deleteEventListener(target, type, listener, useCapture) : null;
|
|
268
|
+
this.attachStartCallback(createListener, useSafe);
|
|
269
|
+
this.attachStopCallback(deleteListener, useSafe);
|
|
270
|
+
};
|
|
210
271
|
this.coldInterval = null;
|
|
211
272
|
this.orderNumber = 0;
|
|
212
273
|
this.coldStartTs = 0;
|
|
@@ -322,25 +383,24 @@ export default class App {
|
|
|
322
383
|
}
|
|
323
384
|
this.initWorker();
|
|
324
385
|
const thisTab = this.session.getTabId();
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
}
|
|
343
|
-
window.addEventListener('message', catchParentMessage);
|
|
386
|
+
/**
|
|
387
|
+
* listen for messages from parent window, so we can signal that we're alive
|
|
388
|
+
* */
|
|
389
|
+
if (this.insideIframe) {
|
|
390
|
+
window.addEventListener('message', this.parentCrossDomainFrameListener);
|
|
391
|
+
setInterval(() => {
|
|
392
|
+
window.parent.postMessage({
|
|
393
|
+
line: proto.polling,
|
|
394
|
+
}, '*');
|
|
395
|
+
}, 250);
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* if we get a signal from child iframes, we check for their node_id and send it back,
|
|
399
|
+
* so they can act as if it was just a same-domain iframe
|
|
400
|
+
* */
|
|
401
|
+
if (!this.insideIframe) {
|
|
402
|
+
window.addEventListener('message', this.crossDomainIframeListener);
|
|
403
|
+
}
|
|
344
404
|
if (this.bc !== null) {
|
|
345
405
|
this.bc.postMessage({
|
|
346
406
|
line: proto.ask,
|
|
@@ -349,7 +409,7 @@ export default class App {
|
|
|
349
409
|
});
|
|
350
410
|
this.startTimeout = setTimeout(() => {
|
|
351
411
|
this.allowAppStart();
|
|
352
|
-
},
|
|
412
|
+
}, 250);
|
|
353
413
|
this.bc.onmessage = (ev) => {
|
|
354
414
|
if (ev.data.context === this.contextId) {
|
|
355
415
|
return;
|
|
@@ -605,27 +665,6 @@ export default class App {
|
|
|
605
665
|
attachCommitCallback(cb) {
|
|
606
666
|
this.commitCallbacks.push(cb);
|
|
607
667
|
}
|
|
608
|
-
attachStartCallback(cb, useSafe = false) {
|
|
609
|
-
if (useSafe) {
|
|
610
|
-
cb = this.safe(cb);
|
|
611
|
-
}
|
|
612
|
-
this.startCallbacks.push(cb);
|
|
613
|
-
}
|
|
614
|
-
attachStopCallback(cb, useSafe = false) {
|
|
615
|
-
if (useSafe) {
|
|
616
|
-
cb = this.safe(cb);
|
|
617
|
-
}
|
|
618
|
-
this.stopCallbacks.push(cb);
|
|
619
|
-
}
|
|
620
|
-
attachEventListener(target, type, listener, useSafe = true, useCapture = true) {
|
|
621
|
-
if (useSafe) {
|
|
622
|
-
listener = this.safe(listener);
|
|
623
|
-
}
|
|
624
|
-
const createListener = () => target ? createEventListener(target, type, listener, useCapture) : null;
|
|
625
|
-
const deleteListener = () => target ? deleteEventListener(target, type, listener, useCapture) : null;
|
|
626
|
-
this.attachStartCallback(createListener, useSafe);
|
|
627
|
-
this.attachStopCallback(deleteListener, useSafe);
|
|
628
|
-
}
|
|
629
668
|
// TODO: full correct semantic
|
|
630
669
|
checkRequiredVersion(version) {
|
|
631
670
|
const reqVer = version.split(/[.-]/);
|
|
@@ -1083,8 +1122,8 @@ export default class App {
|
|
|
1083
1122
|
}
|
|
1084
1123
|
await this.tagWatcher.fetchTags(this.options.ingestPoint, token);
|
|
1085
1124
|
this.activityState = ActivityState.Active;
|
|
1086
|
-
if (this.options.crossdomain?.enabled
|
|
1087
|
-
this.
|
|
1125
|
+
if (this.options.crossdomain?.enabled && !this.insideIframe) {
|
|
1126
|
+
void this.bootChildrenFrames();
|
|
1088
1127
|
}
|
|
1089
1128
|
if (canvasEnabled && !this.options.canvas.disableCanvas) {
|
|
1090
1129
|
this.canvasRecorder =
|
|
@@ -1191,9 +1230,12 @@ export default class App {
|
|
|
1191
1230
|
});
|
|
1192
1231
|
}
|
|
1193
1232
|
async waitStarted() {
|
|
1233
|
+
return this.waitStatus(ActivityState.Active);
|
|
1234
|
+
}
|
|
1235
|
+
async waitStatus(status) {
|
|
1194
1236
|
return new Promise((resolve) => {
|
|
1195
1237
|
const check = () => {
|
|
1196
|
-
if (this.activityState ===
|
|
1238
|
+
if (this.activityState === status) {
|
|
1197
1239
|
resolve(true);
|
|
1198
1240
|
}
|
|
1199
1241
|
else {
|
|
@@ -1208,14 +1250,14 @@ export default class App {
|
|
|
1208
1250
|
* and here we just apply 10ms delay just in case
|
|
1209
1251
|
* */
|
|
1210
1252
|
async start(...args) {
|
|
1211
|
-
if (this.insideIframe) {
|
|
1212
|
-
this.signalIframeTracker();
|
|
1213
|
-
}
|
|
1214
1253
|
if (this.activityState === ActivityState.Active ||
|
|
1215
1254
|
this.activityState === ActivityState.Starting) {
|
|
1216
1255
|
const reason = 'OpenReplay: trying to call `start()` on the instance that has been started already.';
|
|
1217
1256
|
return Promise.resolve(UnsuccessfulStart(reason));
|
|
1218
1257
|
}
|
|
1258
|
+
if (this.insideIframe) {
|
|
1259
|
+
this.signalIframeTracker();
|
|
1260
|
+
}
|
|
1219
1261
|
if (!document.hidden) {
|
|
1220
1262
|
await this.waitStart();
|
|
1221
1263
|
return this._start(...args);
|
|
@@ -1264,23 +1306,30 @@ export default class App {
|
|
|
1264
1306
|
}
|
|
1265
1307
|
stop(stopWorker = true) {
|
|
1266
1308
|
if (this.activityState !== ActivityState.NotActive) {
|
|
1309
|
+
console.trace('stopped');
|
|
1267
1310
|
try {
|
|
1311
|
+
if (!this.insideIframe && this.options.crossdomain?.enabled) {
|
|
1312
|
+
this.killChildrenFrames();
|
|
1313
|
+
}
|
|
1268
1314
|
this.attributeSender.clear();
|
|
1269
1315
|
this.sanitizer.clear();
|
|
1270
1316
|
this.observer.disconnect();
|
|
1271
1317
|
this.nodes.clear();
|
|
1272
1318
|
this.ticker.stop();
|
|
1273
1319
|
this.stopCallbacks.forEach((cb) => cb());
|
|
1274
|
-
this.debug.log('OpenReplay tracking stopped.');
|
|
1275
1320
|
this.tagWatcher.clear();
|
|
1276
1321
|
if (this.worker && stopWorker) {
|
|
1277
1322
|
this.worker.postMessage('stop');
|
|
1278
1323
|
}
|
|
1279
1324
|
this.canvasRecorder?.clear();
|
|
1280
1325
|
this.messages.length = 0;
|
|
1326
|
+
this.trackedFrames = [];
|
|
1327
|
+
this.parentActive = false;
|
|
1328
|
+
this.canStart = false;
|
|
1281
1329
|
}
|
|
1282
1330
|
finally {
|
|
1283
1331
|
this.activityState = ActivityState.NotActive;
|
|
1332
|
+
this.debug.log('OpenReplay tracking stopped.');
|
|
1284
1333
|
}
|
|
1285
1334
|
}
|
|
1286
1335
|
}
|
package/lib/index.js
CHANGED
|
@@ -67,7 +67,7 @@ export default class API {
|
|
|
67
67
|
const orig = this.options.ingestPoint || DEFAULT_INGEST_POINT;
|
|
68
68
|
req.open('POST', orig + '/v1/web/not-started');
|
|
69
69
|
req.send(JSON.stringify({
|
|
70
|
-
trackerVersion: '14.0.
|
|
70
|
+
trackerVersion: '14.0.10-beta.1',
|
|
71
71
|
projectKey: this.options.projectKey,
|
|
72
72
|
doNotTrack,
|
|
73
73
|
reason: missingApi.length ? `missing api: ${missingApi.join(',')}` : reason,
|
package/lib/modules/exception.js
CHANGED
|
@@ -61,8 +61,13 @@ export default function (app, opts) {
|
|
|
61
61
|
app.send(msg);
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
try {
|
|
65
|
+
app.attachEventListener(context, 'unhandledrejection', handler);
|
|
66
|
+
app.attachEventListener(context, 'error', handler);
|
|
67
|
+
}
|
|
68
|
+
catch (e) {
|
|
69
|
+
console.error('Error while attaching to error proto contexts', e);
|
|
70
|
+
}
|
|
66
71
|
}
|
|
67
72
|
if (options.captureExceptions) {
|
|
68
73
|
app.observer.attachContextCallback(patchContext); // TODO: attach once-per-iframe (?)
|
package/lib/utils.js
CHANGED
|
@@ -116,9 +116,9 @@ export function createEventListener(target, event, cb, capture) {
|
|
|
116
116
|
}
|
|
117
117
|
catch (e) {
|
|
118
118
|
const msg = e.message;
|
|
119
|
-
console.
|
|
119
|
+
console.error(
|
|
120
120
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
121
|
-
`Openreplay: ${msg}; if this error is caused by an IframeObserver, ignore it
|
|
121
|
+
`Openreplay: ${msg}; if this error is caused by an IframeObserver, ignore it`, event);
|
|
122
122
|
}
|
|
123
123
|
}
|
|
124
124
|
export function deleteEventListener(target, event, cb, capture) {
|
|
@@ -128,9 +128,9 @@ export function deleteEventListener(target, event, cb, capture) {
|
|
|
128
128
|
}
|
|
129
129
|
catch (e) {
|
|
130
130
|
const msg = e.message;
|
|
131
|
-
console.
|
|
131
|
+
console.error(
|
|
132
132
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
133
|
-
`Openreplay: ${msg}; if this error is caused by an IframeObserver, ignore it
|
|
133
|
+
`Openreplay: ${msg}; if this error is caused by an IframeObserver, ignore it`, event);
|
|
134
134
|
}
|
|
135
135
|
}
|
|
136
136
|
class FIFOTaskScheduler {
|