@openreplay/tracker 14.0.7 → 14.0.9-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.
Files changed (43) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/bun.lockb +0 -0
  3. package/cjs/app/canvas.js +2 -4
  4. package/cjs/app/index.d.ts +7 -0
  5. package/cjs/app/index.js +173 -126
  6. package/cjs/index.js +1 -1
  7. package/cjs/modules/network.js +4 -2
  8. package/lib/app/canvas.js +2 -4
  9. package/lib/app/index.d.ts +7 -0
  10. package/lib/app/index.js +173 -126
  11. package/lib/common/tsconfig.tsbuildinfo +1 -1
  12. package/lib/index.js +1 -1
  13. package/lib/modules/network.js +4 -2
  14. package/package.json +2 -1
  15. package/tsconfig-base.json +2 -2
  16. package/cjs/modules/Network/beaconProxy.d.ts +0 -16
  17. package/cjs/modules/Network/beaconProxy.js +0 -87
  18. package/cjs/modules/Network/fetchProxy.d.ts +0 -34
  19. package/cjs/modules/Network/fetchProxy.js +0 -309
  20. package/cjs/modules/Network/index.d.ts +0 -3
  21. package/cjs/modules/Network/index.js +0 -28
  22. package/cjs/modules/Network/networkMessage.d.ts +0 -49
  23. package/cjs/modules/Network/networkMessage.js +0 -87
  24. package/cjs/modules/Network/types.d.ts +0 -13
  25. package/cjs/modules/Network/types.js +0 -3
  26. package/cjs/modules/Network/utils.d.ts +0 -11
  27. package/cjs/modules/Network/utils.js +0 -216
  28. package/cjs/modules/Network/xhrProxy.d.ts +0 -39
  29. package/cjs/modules/Network/xhrProxy.js +0 -249
  30. package/lib/modules/Network/beaconProxy.d.ts +0 -16
  31. package/lib/modules/Network/beaconProxy.js +0 -79
  32. package/lib/modules/Network/fetchProxy.d.ts +0 -34
  33. package/lib/modules/Network/fetchProxy.js +0 -280
  34. package/lib/modules/Network/index.d.ts +0 -3
  35. package/lib/modules/Network/index.js +0 -22
  36. package/lib/modules/Network/networkMessage.d.ts +0 -49
  37. package/lib/modules/Network/networkMessage.js +0 -83
  38. package/lib/modules/Network/types.d.ts +0 -13
  39. package/lib/modules/Network/types.js +0 -2
  40. package/lib/modules/Network/utils.d.ts +0 -11
  41. package/lib/modules/Network/utils.js +0 -204
  42. package/lib/modules/Network/xhrProxy.d.ts +0 -39
  43. package/lib/modules/Network/xhrProxy.js +0 -221
package/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
+ # 14.0.8
2
+
3
+ - use separate library to handle network requests ([@openreplay/network-proxy](https://www.npmjs.com/package/@openreplay/network-proxy))
4
+ - fixes for window.message listeners
5
+
1
6
  # 14.0.7
2
7
 
3
8
  - check for stopping status during restarts
package/bun.lockb CHANGED
Binary file
package/cjs/app/canvas.js CHANGED
@@ -106,10 +106,8 @@ class CanvasRecorder {
106
106
  startTracking() {
107
107
  setTimeout(() => {
108
108
  this.app.nodes.scanTree(this.captureCanvas);
109
- this.app.nodes.attachNodeCallback((node) => {
110
- this.captureCanvas(node);
111
- });
112
- }, 500);
109
+ this.app.nodes.attachNodeCallback(this.captureCanvas);
110
+ }, 250);
113
111
  }
114
112
  sendSnaps(images, canvasId, createdAt) {
115
113
  if (Object.keys(this.snapshots).length === 0) {
@@ -143,6 +143,13 @@ export default class App {
143
143
  private readonly initialHostName;
144
144
  private features;
145
145
  constructor(projectKey: string, sessionToken: string | undefined, options: Partial<Options>, signalError: (error: string, apis: string[]) => void, insideIframe: boolean);
146
+ /** used by child iframes for crossdomain only */
147
+ parentActive: boolean;
148
+ checkStatus: () => boolean;
149
+ /** used by child iframes for crossdomain only */
150
+ /** track app instances in crossdomain child iframes */
151
+ crossdomainIframesModule: () => void;
152
+ signalIframeTracker: () => void;
146
153
  startTimeout: ReturnType<typeof setTimeout> | null;
147
154
  private allowAppStart;
148
155
  private checkNodeId;
package/cjs/app/index.js CHANGED
@@ -78,6 +78,8 @@ const proto = {
78
78
  iframeId: 'never-gonna-say-goodbye',
79
79
  // batch of messages from an iframe window
80
80
  iframeBatch: 'never-gonna-tell-a-lie-and-hurt-you',
81
+ // signal that parent is live
82
+ parentAlive: 'i-dont-know-more-lines',
81
83
  };
82
84
  class App {
83
85
  constructor(projectKey, sessionToken, options, signalError, insideIframe) {
@@ -94,7 +96,7 @@ class App {
94
96
  this.stopCallbacks = [];
95
97
  this.commitCallbacks = [];
96
98
  this.activityState = ActivityState.NotActive;
97
- this.version = '14.0.7'; // TODO: version compatability check inside each plugin.
99
+ this.version = '14.0.9-beta.1'; // TODO: version compatability check inside each plugin.
98
100
  this.socketMode = false;
99
101
  this.compressionThreshold = 24 * 1000;
100
102
  this.bc = null;
@@ -109,6 +111,129 @@ class App {
109
111
  'feature-flags': true,
110
112
  'usability-test': true,
111
113
  };
114
+ /** used by child iframes for crossdomain only */
115
+ this.parentActive = false;
116
+ this.checkStatus = () => {
117
+ return this.parentActive;
118
+ };
119
+ /** used by child iframes for crossdomain only */
120
+ /** track app instances in crossdomain child iframes */
121
+ this.crossdomainIframesModule = () => {
122
+ if (!this.insideIframe) {
123
+ /**
124
+ * if we get a signal from child iframes, we check for their node_id and send it back,
125
+ * so they can act as if it was just a same-domain iframe
126
+ * */
127
+ let crossdomainFrameCount = 0;
128
+ const catchIframeMessage = (event) => {
129
+ const { data } = event;
130
+ if (!data)
131
+ return;
132
+ if (data.line === proto.iframeSignal) {
133
+ // @ts-ignore
134
+ event.source?.postMessage({ ping: true, line: proto.parentAlive }, '*');
135
+ const childIframeDomain = data.domain;
136
+ const pageIframes = Array.from(document.querySelectorAll('iframe'));
137
+ this.pageFrames = pageIframes;
138
+ const signalId = async () => {
139
+ const id = await this.checkNodeId(pageIframes, childIframeDomain);
140
+ if (id) {
141
+ try {
142
+ await this.waitStarted();
143
+ crossdomainFrameCount++;
144
+ const token = this.session.getSessionToken();
145
+ const iframeData = {
146
+ line: proto.iframeId,
147
+ context: this.contextId,
148
+ domain: childIframeDomain,
149
+ id,
150
+ token,
151
+ frameOrderNumber: crossdomainFrameCount,
152
+ };
153
+ this.debug.log('iframe_data', iframeData);
154
+ // @ts-ignore
155
+ event.source?.postMessage(iframeData, '*');
156
+ }
157
+ catch (e) {
158
+ console.error(e);
159
+ }
160
+ }
161
+ };
162
+ void signalId();
163
+ }
164
+ /**
165
+ * proxying messages from iframe to main body, so they can be in one batch (same indexes, etc)
166
+ * plus we rewrite some of the messages to be relative to the main context/window
167
+ * */
168
+ if (data.line === proto.iframeBatch) {
169
+ const msgBatch = data.messages;
170
+ const mappedMessages = msgBatch.map((msg) => {
171
+ if (msg[0] === 20 /* MType.MouseMove */) {
172
+ let fixedMessage = msg;
173
+ this.pageFrames.forEach((frame) => {
174
+ if (frame.dataset.domain === event.data.domain) {
175
+ const [type, x, y] = msg;
176
+ const { left, top } = frame.getBoundingClientRect();
177
+ fixedMessage = [type, x + left, y + top];
178
+ }
179
+ });
180
+ return fixedMessage;
181
+ }
182
+ if (msg[0] === 68 /* MType.MouseClick */) {
183
+ let fixedMessage = msg;
184
+ this.pageFrames.forEach((frame) => {
185
+ if (frame.dataset.domain === event.data.domain) {
186
+ const [type, id, hesitationTime, label, selector, normX, normY] = msg;
187
+ const { left, top, width, height } = frame.getBoundingClientRect();
188
+ const contentWidth = document.documentElement.scrollWidth;
189
+ const contentHeight = document.documentElement.scrollHeight;
190
+ // (normalizedX * frameWidth + frameLeftOffset)/docSize
191
+ const fullX = (normX / 100) * width + left;
192
+ const fullY = (normY / 100) * height + top;
193
+ const fixedX = fullX / contentWidth;
194
+ const fixedY = fullY / contentHeight;
195
+ fixedMessage = [
196
+ type,
197
+ id,
198
+ hesitationTime,
199
+ label,
200
+ selector,
201
+ Math.round(fixedX * 1e3) / 1e1,
202
+ Math.round(fixedY * 1e3) / 1e1,
203
+ ];
204
+ }
205
+ });
206
+ return fixedMessage;
207
+ }
208
+ return msg;
209
+ });
210
+ this.messages.push(...mappedMessages);
211
+ }
212
+ };
213
+ window.addEventListener('message', catchIframeMessage);
214
+ this.attachStopCallback(() => {
215
+ window.removeEventListener('message', catchIframeMessage);
216
+ });
217
+ }
218
+ };
219
+ this.signalIframeTracker = () => {
220
+ const domain = this.initialHostName;
221
+ const thisTab = this.session.getTabId();
222
+ const signalToParent = (n) => {
223
+ window.parent.postMessage({
224
+ line: proto.iframeSignal,
225
+ source: thisTab,
226
+ context: this.contextId,
227
+ domain,
228
+ }, this.options.crossdomain?.parentDomain ?? '*');
229
+ setTimeout(() => {
230
+ if (!this.checkStatus() && n < 100) {
231
+ void signalToParent(n + 1);
232
+ }
233
+ }, 250);
234
+ };
235
+ void signalToParent(1);
236
+ };
112
237
  this.startTimeout = null;
113
238
  this.coldStartCommitN = 0;
114
239
  this.delay = 0;
@@ -227,129 +352,26 @@ class App {
227
352
  }
228
353
  this.initWorker();
229
354
  const thisTab = this.session.getTabId();
230
- if (!this.insideIframe) {
231
- /**
232
- * if we get a signal from child iframes, we check for their node_id and send it back,
233
- * so they can act as if it was just a same-domain iframe
234
- * */
235
- let crossdomainFrameCount = 0;
236
- const catchIframeMessage = (event) => {
237
- const { data } = event;
238
- if (data.line === proto.iframeSignal) {
239
- const childIframeDomain = data.domain;
240
- const pageIframes = Array.from(document.querySelectorAll('iframe'));
241
- this.pageFrames = pageIframes;
242
- const signalId = async () => {
243
- let tries = 0;
244
- while (tries < 10) {
245
- const id = this.checkNodeId(pageIframes, childIframeDomain);
246
- if (id) {
247
- this.waitStarted()
248
- .then(() => {
249
- crossdomainFrameCount++;
250
- const token = this.session.getSessionToken();
251
- const iframeData = {
252
- line: proto.iframeId,
253
- context: this.contextId,
254
- domain: childIframeDomain,
255
- id,
256
- token,
257
- frameOrderNumber: crossdomainFrameCount,
258
- };
259
- this.debug.log('iframe_data', iframeData);
260
- // @ts-ignore
261
- event.source?.postMessage(iframeData, '*');
262
- })
263
- .catch(console.error);
264
- tries = 10;
265
- break;
266
- }
267
- tries++;
268
- await delay(100);
269
- }
270
- };
271
- void signalId();
272
- }
273
- /**
274
- * proxying messages from iframe to main body, so they can be in one batch (same indexes, etc)
275
- * plus we rewrite some of the messages to be relative to the main context/window
276
- * */
277
- if (data.line === proto.iframeBatch) {
278
- const msgBatch = data.messages;
279
- const mappedMessages = msgBatch.map((msg) => {
280
- if (msg[0] === 20 /* MType.MouseMove */) {
281
- let fixedMessage = msg;
282
- this.pageFrames.forEach((frame) => {
283
- if (frame.dataset.domain === event.data.domain) {
284
- const [type, x, y] = msg;
285
- const { left, top } = frame.getBoundingClientRect();
286
- fixedMessage = [type, x + left, y + top];
287
- }
288
- });
289
- return fixedMessage;
290
- }
291
- if (msg[0] === 68 /* MType.MouseClick */) {
292
- let fixedMessage = msg;
293
- this.pageFrames.forEach((frame) => {
294
- if (frame.dataset.domain === event.data.domain) {
295
- const [type, id, hesitationTime, label, selector, normX, normY] = msg;
296
- const { left, top, width, height } = frame.getBoundingClientRect();
297
- const contentWidth = document.documentElement.scrollWidth;
298
- const contentHeight = document.documentElement.scrollHeight;
299
- // (normalizedX * frameWidth + frameLeftOffset)/docSize
300
- const fullX = (normX / 100) * width + left;
301
- const fullY = (normY / 100) * height + top;
302
- const fixedX = fullX / contentWidth;
303
- const fixedY = fullY / contentHeight;
304
- fixedMessage = [
305
- type,
306
- id,
307
- hesitationTime,
308
- label,
309
- selector,
310
- Math.round(fixedX * 1e3) / 1e1,
311
- Math.round(fixedY * 1e3) / 1e1,
312
- ];
313
- }
314
- });
315
- return fixedMessage;
316
- }
317
- return msg;
318
- });
319
- this.messages.push(...mappedMessages);
320
- }
321
- };
322
- window.addEventListener('message', catchIframeMessage);
323
- this.attachStopCallback(() => {
324
- window.removeEventListener('message', catchIframeMessage);
325
- });
326
- }
327
- else {
328
- const catchParentMessage = (event) => {
329
- const { data } = event;
330
- if (data.line !== proto.iframeId) {
331
- return;
332
- }
355
+ const catchParentMessage = (event) => {
356
+ const { data } = event;
357
+ if (!data)
358
+ return;
359
+ if (data.line === proto.parentAlive) {
360
+ this.parentActive = true;
361
+ }
362
+ if (data.line === proto.iframeId) {
363
+ this.parentActive = true;
333
364
  this.rootId = data.id;
334
365
  this.session.setSessionToken(data.token);
335
366
  this.frameOderNumber = data.frameOrderNumber;
336
367
  this.debug.log('starting iframe tracking', data);
337
368
  this.allowAppStart();
338
- };
339
- window.addEventListener('message', catchParentMessage);
340
- this.attachStopCallback(() => {
341
- window.removeEventListener('message', catchParentMessage);
342
- });
343
- // communicating with parent window,
344
- // even if its crossdomain is possible via postMessage api
345
- const domain = this.initialHostName;
346
- window.parent.postMessage({
347
- line: proto.iframeSignal,
348
- source: thisTab,
349
- context: this.contextId,
350
- domain,
351
- }, '*');
352
- }
369
+ }
370
+ };
371
+ window.addEventListener('message', catchParentMessage);
372
+ this.attachStopCallback(() => {
373
+ window.removeEventListener('message', catchParentMessage);
374
+ });
353
375
  if (this.bc !== null) {
354
376
  this.bc.postMessage({
355
377
  line: proto.ask,
@@ -395,11 +417,29 @@ class App {
395
417
  this.startTimeout = null;
396
418
  }
397
419
  }
398
- checkNodeId(iframes, domain) {
420
+ async checkNodeId(iframes, domain) {
399
421
  for (const iframe of iframes) {
400
422
  if (iframe.dataset.domain === domain) {
401
- // @ts-ignore
402
- return iframe[this.options.node_id];
423
+ /**
424
+ * Here we're trying to get node id from the iframe (which is kept in observer)
425
+ * because of async nature of dom initialization, we give 100 retries with 100ms delay each
426
+ * which equals to 10 seconds. This way we have a period where we give app some time to load
427
+ * and tracker some time to parse the initial DOM tree even on slower devices
428
+ * */
429
+ let tries = 0;
430
+ while (tries < 100) {
431
+ // @ts-ignore
432
+ const potentialId = iframe[this.options.node_id];
433
+ if (potentialId !== undefined) {
434
+ tries = 100;
435
+ return potentialId;
436
+ }
437
+ else {
438
+ tries++;
439
+ await delay(100);
440
+ }
441
+ }
442
+ return null;
403
443
  }
404
444
  }
405
445
  return null;
@@ -524,7 +564,7 @@ class App {
524
564
  line: proto.iframeBatch,
525
565
  messages: this.messages,
526
566
  domain: this.initialHostName,
527
- }, '*');
567
+ }, this.options.crossdomain?.parentDomain ?? '*');
528
568
  this.commitCallbacks.forEach((cb) => cb(this.messages));
529
569
  this.messages.length = 0;
530
570
  return;
@@ -609,7 +649,6 @@ class App {
609
649
  }
610
650
  this.stopCallbacks.push(cb);
611
651
  }
612
- // Use app.nodes.attachNodeListener for registered nodes instead
613
652
  attachEventListener(target, type, listener, useSafe = true, useCapture = true) {
614
653
  if (useSafe) {
615
654
  listener = this.safe(listener);
@@ -1076,6 +1115,9 @@ class App {
1076
1115
  }
1077
1116
  await this.tagWatcher.fetchTags(this.options.ingestPoint, token);
1078
1117
  this.activityState = ActivityState.Active;
1118
+ if (this.options.crossdomain?.enabled || this.insideIframe) {
1119
+ this.crossdomainIframesModule();
1120
+ }
1079
1121
  if (canvasEnabled && !this.options.canvas.disableCanvas) {
1080
1122
  this.canvasRecorder =
1081
1123
  this.canvasRecorder ??
@@ -1086,7 +1128,6 @@ class App {
1086
1128
  fixedScaling: this.options.canvas.fixedCanvasScaling,
1087
1129
  useAnimationFrame: this.options.canvas.useAnimationFrame,
1088
1130
  });
1089
- this.canvasRecorder.startTracking();
1090
1131
  }
1091
1132
  /** --------------- COLD START BUFFER ------------------*/
1092
1133
  if (isColdStart) {
@@ -1109,8 +1150,11 @@ class App {
1109
1150
  }
1110
1151
  this.ticker.start();
1111
1152
  }
1153
+ this.canvasRecorder?.startTracking();
1112
1154
  if (this.features['usability-test']) {
1113
- this.uxtManager = this.uxtManager ? this.uxtManager : new index_js_1.default(this, uxtStorageKey);
1155
+ this.uxtManager = this.uxtManager
1156
+ ? this.uxtManager
1157
+ : new index_js_1.default(this, uxtStorageKey);
1114
1158
  let uxtId;
1115
1159
  const savedUxtTag = this.localStorage.getItem(uxtStorageKey);
1116
1160
  if (savedUxtTag) {
@@ -1191,6 +1235,9 @@ class App {
1191
1235
  * and here we just apply 10ms delay just in case
1192
1236
  * */
1193
1237
  async start(...args) {
1238
+ if (this.insideIframe) {
1239
+ this.signalIframeTracker();
1240
+ }
1194
1241
  if (this.activityState === ActivityState.Active ||
1195
1242
  this.activityState === ActivityState.Starting) {
1196
1243
  const reason = 'OpenReplay: trying to call `start()` on the instance that has been started already.';
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.7',
101
+ trackerVersion: '14.0.9-beta.1',
102
102
  projectKey: this.options.projectKey,
103
103
  doNotTrack,
104
104
  reason: missingApi.length ? `missing api: ${missingApi.join(',')}` : reason,
@@ -6,7 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const messages_gen_js_1 = require("../app/messages.gen.js");
7
7
  const utils_js_1 = require("../utils.js");
8
8
  const axiosSpy_js_1 = __importDefault(require("./axiosSpy.js"));
9
- const index_js_1 = __importDefault(require("./Network/index.js"));
9
+ const network_proxy_1 = __importDefault(require("@openreplay/network-proxy"));
10
10
  function getXHRRequestDataObject(xhr) {
11
11
  // @ts-ignore this is 3x faster than using Map<XHR, XHRRequestData>
12
12
  if (!xhr.__or_req_data__) {
@@ -79,7 +79,9 @@ function default_1(app, opts = {}) {
79
79
  const patchWindow = (context) => {
80
80
  /* ====== modern way ====== */
81
81
  if (options.useProxy) {
82
- return (0, index_js_1.default)(context, options.ignoreHeaders, setSessionTokenHeader, sanitize, (message) => app.send(message), (url) => app.isServiceURL(url), options.tokenUrlMatcher);
82
+ return (0, network_proxy_1.default)(context, options.ignoreHeaders, setSessionTokenHeader, sanitize, (message) => {
83
+ app.send((0, messages_gen_js_1.NetworkRequest)(message.requestType, message.method, message.url, message.request, message.response, message.status, message.startTime + (0, utils_js_1.getTimeOrigin)(), message.duration, message.responseSize));
84
+ }, (url) => app.isServiceURL(url), { xhr: true, fetch: true, beacon: true }, options.tokenUrlMatcher);
83
85
  }
84
86
  /* ====== Fetch ====== */
85
87
  const origFetch = context.fetch.bind(context);
package/lib/app/canvas.js CHANGED
@@ -104,10 +104,8 @@ class CanvasRecorder {
104
104
  startTracking() {
105
105
  setTimeout(() => {
106
106
  this.app.nodes.scanTree(this.captureCanvas);
107
- this.app.nodes.attachNodeCallback((node) => {
108
- this.captureCanvas(node);
109
- });
110
- }, 500);
107
+ this.app.nodes.attachNodeCallback(this.captureCanvas);
108
+ }, 250);
111
109
  }
112
110
  sendSnaps(images, canvasId, createdAt) {
113
111
  if (Object.keys(this.snapshots).length === 0) {
@@ -143,6 +143,13 @@ export default class App {
143
143
  private readonly initialHostName;
144
144
  private features;
145
145
  constructor(projectKey: string, sessionToken: string | undefined, options: Partial<Options>, signalError: (error: string, apis: string[]) => void, insideIframe: boolean);
146
+ /** used by child iframes for crossdomain only */
147
+ parentActive: boolean;
148
+ checkStatus: () => boolean;
149
+ /** used by child iframes for crossdomain only */
150
+ /** track app instances in crossdomain child iframes */
151
+ crossdomainIframesModule: () => void;
152
+ signalIframeTracker: () => void;
146
153
  startTimeout: ReturnType<typeof setTimeout> | null;
147
154
  private allowAppStart;
148
155
  private checkNodeId;