@openreplay/tracker 18.0.1 → 18.0.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/dist/cjs/entry.js CHANGED
@@ -2390,8 +2390,44 @@ function inlineRemoteCss(node, id, baseHref, getNextID, insertRule, addOwner, fo
2390
2390
  })
2391
2391
  .catch(error => {
2392
2392
  console.error(`OpenReplay: Failed to fetch CSS from ${node.href}:`, error);
2393
+ // Fetch failed (likely CORS) — retry via load event + sheet access
2394
+ retryViaLoadEvent();
2393
2395
  });
2394
2396
  }
2397
+ else {
2398
+ retryViaLoadEvent();
2399
+ }
2400
+ function retryViaLoadEvent() {
2401
+ const trySheet = () => {
2402
+ const loadedSheet = node.sheet;
2403
+ if (loadedSheet) {
2404
+ try {
2405
+ const cssText = stringifyStylesheet(loadedSheet);
2406
+ if (cssText) {
2407
+ if (sendPlain && onPlain) {
2408
+ onPlain(cssText, fakeIdHolder++);
2409
+ }
2410
+ else {
2411
+ processCssText(cssText);
2412
+ }
2413
+ return;
2414
+ }
2415
+ }
2416
+ catch (e) {
2417
+ // cross-origin sheet — nothing more we can do
2418
+ }
2419
+ }
2420
+ };
2421
+ if (node.sheet) {
2422
+ trySheet();
2423
+ }
2424
+ else {
2425
+ node.addEventListener('load', function onLoad() {
2426
+ node.removeEventListener('load', onLoad);
2427
+ trySheet();
2428
+ });
2429
+ }
2430
+ }
2395
2431
  function processCssText(cssText) {
2396
2432
  // Remove comments
2397
2433
  cssText = cssText.replace(/\/\*[\s\S]*?\*\//g, '');
@@ -2613,7 +2649,6 @@ function ConstructedStyleSheets (app) {
2613
2649
  if (!hasAdoptedSS(document)) {
2614
2650
  return;
2615
2651
  }
2616
- const styleSheetIDMap = new Map();
2617
2652
  const adoptedStyleSheetsOwnings = new Map();
2618
2653
  const sendAdoptedStyleSheetsUpdate = (root) => setTimeout(() => {
2619
2654
  let nodeID = app.nodes.getID(root);
@@ -2878,6 +2913,12 @@ class Observer {
2878
2913
  this.inlineRemoteCss = false;
2879
2914
  this.inlinerOptions = undefined;
2880
2915
  this.domParser = new DOMParser();
2916
+ /**
2917
+ * Bumped on every disconnect(). Stale async CSS-inlining callbacks from a
2918
+ * previous session (e.g. agent reload → tracker restart) compare against
2919
+ * this and bail instead of sending messages that reference vanished node ids.
2920
+ */
2921
+ this.generation = 0;
2881
2922
  this.throttling = true;
2882
2923
  this.throttledSetNodeData = throttleWithTrailing((id, parentElement, data) => this.sendNodeData(id, parentElement, data), 30);
2883
2924
  this.throttling = !Boolean(options.disableThrottling);
@@ -3024,18 +3065,23 @@ class Observer {
3024
3065
  }
3025
3066
  if (name === 'style' || (name === 'href' && hasTag(node, 'link'))) {
3026
3067
  if ('rel' in node && node.rel === 'stylesheet' && this.inlineRemoteCss) {
3068
+ const gen = this.generation;
3027
3069
  setTimeout(() => {
3028
3070
  inlineRemoteCss(
3029
3071
  // @ts-ignore
3030
3072
  node, id, this.app.getBaseHref(), nextID, (id, cssText, index, baseHref) => {
3073
+ if (this.generation !== gen)
3074
+ return;
3031
3075
  this.app.send(AdoptedSSInsertRuleURLBased(id, cssText, index, baseHref));
3032
3076
  }, (sheetId, ownerId) => {
3077
+ if (this.generation !== gen)
3078
+ return;
3033
3079
  this.app.send(AdoptedSSAddOwner(sheetId, ownerId));
3034
3080
  }, this.inlinerOptions?.forceFetch, this.inlinerOptions?.forcePlain, (cssText, fakeTextId) => {
3081
+ if (this.generation !== gen)
3082
+ return;
3035
3083
  this.app.send(CreateTextNode(fakeTextId, id, 0));
3036
- setTimeout(() => {
3037
- this.app.send(SetCSSDataURLBased(fakeTextId, cssText, this.app.getBaseHref()));
3038
- }, 10);
3084
+ this.app.send(SetCSSDataURLBased(fakeTextId, cssText, this.app.getBaseHref()));
3039
3085
  });
3040
3086
  }, 0);
3041
3087
  return;
@@ -3299,6 +3345,7 @@ class Observer {
3299
3345
  this.observer.disconnect();
3300
3346
  this.clear();
3301
3347
  this.throttledSetNodeData.clear();
3348
+ this.generation++;
3302
3349
  }
3303
3350
  }
3304
3351
 
@@ -3962,7 +4009,7 @@ class App {
3962
4009
  this.stopCallbacks = [];
3963
4010
  this.commitCallbacks = [];
3964
4011
  this.activityState = ActivityState.NotActive;
3965
- this.version = '18.0.1'; // TODO: version compatability check inside each plugin.
4012
+ this.version = '18.0.2'; // TODO: version compatability check inside each plugin.
3966
4013
  this.socketMode = false;
3967
4014
  this.compressionThreshold = 24 * 1000;
3968
4015
  this.bc = null;
@@ -3984,12 +4031,19 @@ class App {
3984
4031
  if (!data || event.source === window)
3985
4032
  return;
3986
4033
  if (data.line === proto.startIframe) {
3987
- if (this.active())
4034
+ // Avoid corrupting an in-flight start; let it complete.
4035
+ if (this.activityState === ActivityState.Starting)
3988
4036
  return;
3989
4037
  try {
4038
+ if (this.active()) {
4039
+ this.stop();
4040
+ }
3990
4041
  if (data.token) {
3991
4042
  this.session.setSessionToken(data.token, this.projectKey);
3992
4043
  }
4044
+ if (data.id !== undefined) {
4045
+ this.rootId = data.id;
4046
+ }
3993
4047
  this.allowAppStart();
3994
4048
  void this.start();
3995
4049
  }
@@ -4015,17 +4069,22 @@ class App {
4015
4069
  }
4016
4070
  }
4017
4071
  };
4018
- /**
4019
- * context ids for iframes,
4020
- * order is not so important as long as its consistent
4021
- * */
4022
4072
  this.trackedFrames = [];
4073
+ this.frameLastSeen = new Map();
4074
+ this.FRAME_STALE_MS = 1500;
4023
4075
  this.crossDomainIframeListener = (event) => {
4024
- if (!this.active() || event.source === window)
4076
+ if (event.source === window)
4025
4077
  return;
4026
4078
  const { data } = event;
4027
4079
  if (!data)
4028
4080
  return;
4081
+ // Record liveness regardless of our own active state so the queue can prune
4082
+ // stale contexts reliably once we resume dispatching commands after a cycle.
4083
+ if ((data.line === proto.polling || data.line === proto.iframeSignal) && data.context) {
4084
+ this.frameLastSeen.set(data.context, Date.now());
4085
+ }
4086
+ if (!this.active())
4087
+ return;
4029
4088
  if (data.line === proto.iframeSignal) {
4030
4089
  // @ts-ignore
4031
4090
  event.source?.postMessage({ ping: true, line: proto.parentAlive }, '*');
@@ -4123,20 +4182,41 @@ class App {
4123
4182
  if (!this.pollingQueue.order.length) {
4124
4183
  return;
4125
4184
  }
4126
- const nextCommand = this.pollingQueue.order[0];
4127
- if (nextCommand && this.pollingQueue[nextCommand].length === 0) {
4128
- this.pollingQueue.order = this.pollingQueue.order.filter((c) => c !== nextCommand);
4185
+ this.pruneStaleFrames();
4186
+ const liveSet = new Set(this.trackedFrames);
4187
+ while (this.pollingQueue.order.length > 0) {
4188
+ const head = this.pollingQueue.order[0];
4189
+ this.pollingQueue[head] = this.pollingQueue[head].filter((ctx) => liveSet.has(ctx));
4190
+ if (this.pollingQueue[head].length === 0) {
4191
+ delete this.pollingQueue[head];
4192
+ this.pollingQueue.order.shift();
4193
+ }
4194
+ else {
4195
+ break;
4196
+ }
4197
+ }
4198
+ if (!this.pollingQueue.order.length) {
4129
4199
  return;
4130
4200
  }
4201
+ const nextCommand = this.pollingQueue.order[0];
4131
4202
  if (this.pollingQueue[nextCommand].includes(data.context)) {
4132
4203
  this.pollingQueue[nextCommand] = this.pollingQueue[nextCommand].filter((c) => c !== data.context);
4133
4204
  const message = { line: nextCommand };
4134
4205
  if (nextCommand === proto.startIframe) {
4135
4206
  message.token = this.session.getSessionToken(this.projectKey);
4207
+ const targetFrame = this.pageFrames.find((f) => f.contentWindow === event.source)
4208
+ || Array.from(document.querySelectorAll('iframe')).find((f) => f.contentWindow === event.source);
4209
+ if (targetFrame) {
4210
+ const nodeId = targetFrame[this.options.node_id];
4211
+ if (nodeId !== undefined) {
4212
+ message.id = nodeId;
4213
+ }
4214
+ }
4136
4215
  }
4137
4216
  // @ts-ignore
4138
4217
  event.source?.postMessage(message, '*');
4139
4218
  if (this.pollingQueue[nextCommand].length === 0) {
4219
+ delete this.pollingQueue[nextCommand];
4140
4220
  this.pollingQueue.order.shift();
4141
4221
  }
4142
4222
  }
@@ -4150,6 +4230,11 @@ class App {
4150
4230
  order: [],
4151
4231
  };
4152
4232
  this.addCommand = (cmd) => {
4233
+ this.pruneStaleFrames();
4234
+ if (this.pollingQueue[cmd]) {
4235
+ this.pollingQueue[cmd] = Array.from(new Set([...this.pollingQueue[cmd], ...this.trackedFrames]));
4236
+ return;
4237
+ }
4153
4238
  this.pollingQueue.order.push(cmd);
4154
4239
  this.pollingQueue[cmd] = [...this.trackedFrames];
4155
4240
  };
@@ -4462,6 +4547,16 @@ class App {
4462
4547
  };
4463
4548
  }
4464
4549
  }
4550
+ pruneStaleFrames() {
4551
+ const staleAfter = Date.now() - this.FRAME_STALE_MS;
4552
+ this.trackedFrames = this.trackedFrames.filter((ctx) => {
4553
+ const last = this.frameLastSeen.get(ctx);
4554
+ if (last !== undefined && last >= staleAfter)
4555
+ return true;
4556
+ this.frameLastSeen.delete(ctx);
4557
+ return false;
4558
+ });
4559
+ }
4465
4560
  allowAppStart() {
4466
4561
  this.canStart = true;
4467
4562
  if (this.startTimeout) {
@@ -5043,6 +5138,9 @@ class App {
5043
5138
  if (Object.keys(startOpts).length !== 0) {
5044
5139
  this.prevOpts = startOpts;
5045
5140
  }
5141
+ if (startOpts.startCallback) {
5142
+ this.userStartCallback = startOpts.startCallback;
5143
+ }
5046
5144
  const isColdStart = this.activityState === ActivityState.ColdStart;
5047
5145
  if (isColdStart && this.coldInterval) {
5048
5146
  clearInterval(this.coldInterval);
@@ -5184,8 +5282,8 @@ class App {
5184
5282
  // TODO: start as early as possible (before receiving the token)
5185
5283
  /** after start */
5186
5284
  this.startCallbacks.forEach((cb) => cb(onStartInfo)); // MBTODO: callbacks after DOM "mounted" (observed)
5187
- if (startOpts.startCallback) {
5188
- startOpts.startCallback(SuccessfulStart(onStartInfo));
5285
+ if (this.userStartCallback) {
5286
+ this.userStartCallback(SuccessfulStart(onStartInfo));
5189
5287
  }
5190
5288
  this.activityState = ActivityState.Active;
5191
5289
  if (this.options.crossdomain?.enabled) {
@@ -5348,6 +5446,7 @@ class App {
5348
5446
  this.canvasRecorder?.clear();
5349
5447
  this.messages.length = 0;
5350
5448
  this.parentActive = false;
5449
+ this.pageFrames = [];
5351
5450
  }
5352
5451
  finally {
5353
5452
  this.activityState = ActivityState.NotActive;
@@ -8877,7 +8976,7 @@ class ConstantProperties {
8877
8976
  user_id: this.user_id,
8878
8977
  distinct_id: this.deviceId,
8879
8978
  sdk_edition: 'web',
8880
- sdk_version: '18.0.1',
8979
+ sdk_version: '18.0.2',
8881
8980
  timezone: getUTCOffsetString(),
8882
8981
  search_engine: this.searchEngine,
8883
8982
  };
@@ -9579,7 +9678,7 @@ class API {
9579
9678
  this.signalStartIssue = (reason, missingApi) => {
9580
9679
  const doNotTrack = this.checkDoNotTrack();
9581
9680
  console.log("Tracker couldn't start due to:", JSON.stringify({
9582
- trackerVersion: '18.0.1',
9681
+ trackerVersion: '18.0.2',
9583
9682
  projectKey: this.options.projectKey,
9584
9683
  doNotTrack,
9585
9684
  reason: missingApi.length ? `missing api: ${missingApi.join(',')}` : reason,