@openreplay/tracker 17.2.8 → 17.2.9

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.
@@ -171,11 +171,10 @@ export default class App {
171
171
  parentActive: boolean;
172
172
  checkStatus: () => boolean;
173
173
  parentCrossDomainFrameListener: (event: MessageEvent) => void;
174
- /**
175
- * context ids for iframes,
176
- * order is not so important as long as its consistent
177
- * */
178
174
  trackedFrames: string[];
175
+ private frameLastSeen;
176
+ private readonly FRAME_STALE_MS;
177
+ private pruneStaleFrames;
179
178
  crossDomainIframeListener: (event: MessageEvent) => void;
180
179
  /**
181
180
  * { command : [remaining iframes] }
@@ -290,6 +289,7 @@ export default class App {
290
289
  * */
291
290
  uploadOfflineRecording(): Promise<void>;
292
291
  prevOpts: StartOptions;
292
+ private userStartCallback?;
293
293
  private _start;
294
294
  restartCanvasTracking: () => void;
295
295
  flushBuffer: (buffer: Message[]) => Promise<unknown>;
@@ -28,6 +28,12 @@ export default abstract class Observer {
28
28
  private readonly inlineRemoteCss;
29
29
  private readonly inlinerOptions;
30
30
  private readonly domParser;
31
+ /**
32
+ * Bumped on every disconnect(). Stale async CSS-inlining callbacks from a
33
+ * previous session (e.g. agent reload → tracker restart) compare against
34
+ * this and bail instead of sending messages that reference vanished node ids.
35
+ */
36
+ private generation;
31
37
  constructor(app: App, isTopContext?: boolean, options?: Options);
32
38
  private clear;
33
39
  /**
package/dist/lib/entry.js CHANGED
@@ -2909,6 +2909,12 @@ class Observer {
2909
2909
  this.inlineRemoteCss = false;
2910
2910
  this.inlinerOptions = undefined;
2911
2911
  this.domParser = new DOMParser();
2912
+ /**
2913
+ * Bumped on every disconnect(). Stale async CSS-inlining callbacks from a
2914
+ * previous session (e.g. agent reload → tracker restart) compare against
2915
+ * this and bail instead of sending messages that reference vanished node ids.
2916
+ */
2917
+ this.generation = 0;
2912
2918
  this.throttling = true;
2913
2919
  this.throttledSetNodeData = throttleWithTrailing((id, parentElement, data) => this.sendNodeData(id, parentElement, data), 30);
2914
2920
  this.throttling = !Boolean(options.disableThrottling);
@@ -3055,14 +3061,21 @@ class Observer {
3055
3061
  }
3056
3062
  if (name === 'style' || (name === 'href' && hasTag(node, 'link'))) {
3057
3063
  if ('rel' in node && node.rel === 'stylesheet' && this.inlineRemoteCss) {
3064
+ const gen = this.generation;
3058
3065
  setTimeout(() => {
3059
3066
  inlineRemoteCss(
3060
3067
  // @ts-ignore
3061
3068
  node, id, this.app.getBaseHref(), nextID, (id, cssText, index, baseHref) => {
3069
+ if (this.generation !== gen)
3070
+ return;
3062
3071
  this.app.send(AdoptedSSInsertRuleURLBased(id, cssText, index, baseHref));
3063
3072
  }, (sheetId, ownerId) => {
3073
+ if (this.generation !== gen)
3074
+ return;
3064
3075
  this.app.send(AdoptedSSAddOwner(sheetId, ownerId));
3065
3076
  }, this.inlinerOptions?.forceFetch, this.inlinerOptions?.forcePlain, (cssText, fakeTextId) => {
3077
+ if (this.generation !== gen)
3078
+ return;
3066
3079
  this.app.send(CreateTextNode(fakeTextId, id, 0));
3067
3080
  this.app.send(SetCSSDataURLBased(fakeTextId, cssText, this.app.getBaseHref()));
3068
3081
  });
@@ -3328,6 +3341,7 @@ class Observer {
3328
3341
  this.observer.disconnect();
3329
3342
  this.clear();
3330
3343
  this.throttledSetNodeData.clear();
3344
+ this.generation++;
3331
3345
  }
3332
3346
  }
3333
3347
 
@@ -3991,7 +4005,7 @@ class App {
3991
4005
  this.stopCallbacks = [];
3992
4006
  this.commitCallbacks = [];
3993
4007
  this.activityState = ActivityState.NotActive;
3994
- this.version = '17.2.8'; // TODO: version compatability check inside each plugin.
4008
+ this.version = '17.2.9'; // TODO: version compatability check inside each plugin.
3995
4009
  this.socketMode = false;
3996
4010
  this.compressionThreshold = 24 * 1000;
3997
4011
  this.bc = null;
@@ -4013,9 +4027,13 @@ class App {
4013
4027
  if (!data || event.source === window)
4014
4028
  return;
4015
4029
  if (data.line === proto.startIframe) {
4016
- if (this.active())
4030
+ // Avoid corrupting an in-flight start; let it complete.
4031
+ if (this.activityState === ActivityState.Starting)
4017
4032
  return;
4018
4033
  try {
4034
+ if (this.active()) {
4035
+ this.stop();
4036
+ }
4019
4037
  if (data.token) {
4020
4038
  this.session.setSessionToken(data.token, this.projectKey);
4021
4039
  }
@@ -4047,17 +4065,22 @@ class App {
4047
4065
  }
4048
4066
  }
4049
4067
  };
4050
- /**
4051
- * context ids for iframes,
4052
- * order is not so important as long as its consistent
4053
- * */
4054
4068
  this.trackedFrames = [];
4069
+ this.frameLastSeen = new Map();
4070
+ this.FRAME_STALE_MS = 1500;
4055
4071
  this.crossDomainIframeListener = (event) => {
4056
- if (!this.active() || event.source === window)
4072
+ if (event.source === window)
4057
4073
  return;
4058
4074
  const { data } = event;
4059
4075
  if (!data)
4060
4076
  return;
4077
+ // Record liveness regardless of our own active state so the queue can prune
4078
+ // stale contexts reliably once we resume dispatching commands after a cycle.
4079
+ if ((data.line === proto.polling || data.line === proto.iframeSignal) && data.context) {
4080
+ this.frameLastSeen.set(data.context, Date.now());
4081
+ }
4082
+ if (!this.active())
4083
+ return;
4061
4084
  if (data.line === proto.iframeSignal) {
4062
4085
  // @ts-ignore
4063
4086
  event.source?.postMessage({ ping: true, line: proto.parentAlive }, '*');
@@ -4155,11 +4178,23 @@ class App {
4155
4178
  if (!this.pollingQueue.order.length) {
4156
4179
  return;
4157
4180
  }
4158
- const nextCommand = this.pollingQueue.order[0];
4159
- if (nextCommand && this.pollingQueue[nextCommand].length === 0) {
4160
- this.pollingQueue.order = this.pollingQueue.order.filter((c) => c !== nextCommand);
4181
+ this.pruneStaleFrames();
4182
+ const liveSet = new Set(this.trackedFrames);
4183
+ while (this.pollingQueue.order.length > 0) {
4184
+ const head = this.pollingQueue.order[0];
4185
+ this.pollingQueue[head] = this.pollingQueue[head].filter((ctx) => liveSet.has(ctx));
4186
+ if (this.pollingQueue[head].length === 0) {
4187
+ delete this.pollingQueue[head];
4188
+ this.pollingQueue.order.shift();
4189
+ }
4190
+ else {
4191
+ break;
4192
+ }
4193
+ }
4194
+ if (!this.pollingQueue.order.length) {
4161
4195
  return;
4162
4196
  }
4197
+ const nextCommand = this.pollingQueue.order[0];
4163
4198
  if (this.pollingQueue[nextCommand].includes(data.context)) {
4164
4199
  this.pollingQueue[nextCommand] = this.pollingQueue[nextCommand].filter((c) => c !== data.context);
4165
4200
  const message = { line: nextCommand };
@@ -4177,6 +4212,7 @@ class App {
4177
4212
  // @ts-ignore
4178
4213
  event.source?.postMessage(message, '*');
4179
4214
  if (this.pollingQueue[nextCommand].length === 0) {
4215
+ delete this.pollingQueue[nextCommand];
4180
4216
  this.pollingQueue.order.shift();
4181
4217
  }
4182
4218
  }
@@ -4190,6 +4226,11 @@ class App {
4190
4226
  order: [],
4191
4227
  };
4192
4228
  this.addCommand = (cmd) => {
4229
+ this.pruneStaleFrames();
4230
+ if (this.pollingQueue[cmd]) {
4231
+ this.pollingQueue[cmd] = Array.from(new Set([...this.pollingQueue[cmd], ...this.trackedFrames]));
4232
+ return;
4233
+ }
4193
4234
  this.pollingQueue.order.push(cmd);
4194
4235
  this.pollingQueue[cmd] = [...this.trackedFrames];
4195
4236
  };
@@ -4501,6 +4542,16 @@ class App {
4501
4542
  };
4502
4543
  }
4503
4544
  }
4545
+ pruneStaleFrames() {
4546
+ const staleAfter = Date.now() - this.FRAME_STALE_MS;
4547
+ this.trackedFrames = this.trackedFrames.filter((ctx) => {
4548
+ const last = this.frameLastSeen.get(ctx);
4549
+ if (last !== undefined && last >= staleAfter)
4550
+ return true;
4551
+ this.frameLastSeen.delete(ctx);
4552
+ return false;
4553
+ });
4554
+ }
4504
4555
  allowAppStart() {
4505
4556
  this.canStart = true;
4506
4557
  if (this.startTimeout) {
@@ -5051,6 +5102,9 @@ class App {
5051
5102
  if (Object.keys(startOpts).length !== 0) {
5052
5103
  this.prevOpts = startOpts;
5053
5104
  }
5105
+ if (startOpts.startCallback) {
5106
+ this.userStartCallback = startOpts.startCallback;
5107
+ }
5054
5108
  const isColdStart = this.activityState === ActivityState.ColdStart;
5055
5109
  if (isColdStart && this.coldInterval) {
5056
5110
  clearInterval(this.coldInterval);
@@ -5190,8 +5244,8 @@ class App {
5190
5244
  // TODO: start as early as possible (before receiving the token)
5191
5245
  /** after start */
5192
5246
  this.startCallbacks.forEach((cb) => cb(onStartInfo)); // MBTODO: callbacks after DOM "mounted" (observed)
5193
- if (startOpts.startCallback) {
5194
- startOpts.startCallback(SuccessfulStart(onStartInfo));
5247
+ if (this.userStartCallback) {
5248
+ this.userStartCallback(SuccessfulStart(onStartInfo));
5195
5249
  }
5196
5250
  this.activityState = ActivityState.Active;
5197
5251
  if (this.options.crossdomain?.enabled) {
@@ -5354,6 +5408,7 @@ class App {
5354
5408
  this.canvasRecorder?.clear();
5355
5409
  this.messages.length = 0;
5356
5410
  this.parentActive = false;
5411
+ this.pageFrames = [];
5357
5412
  }
5358
5413
  finally {
5359
5414
  this.activityState = ActivityState.NotActive;
@@ -8883,7 +8938,7 @@ class ConstantProperties {
8883
8938
  user_id: this.user_id,
8884
8939
  distinct_id: this.deviceId,
8885
8940
  sdk_edition: 'web',
8886
- sdk_version: '17.2.8',
8941
+ sdk_version: '17.2.9',
8887
8942
  timezone: getUTCOffsetString(),
8888
8943
  search_engine: this.searchEngine,
8889
8944
  };
@@ -9585,7 +9640,7 @@ class API {
9585
9640
  this.signalStartIssue = (reason, missingApi) => {
9586
9641
  const doNotTrack = this.checkDoNotTrack();
9587
9642
  console.log("Tracker couldn't start due to:", JSON.stringify({
9588
- trackerVersion: '17.2.8',
9643
+ trackerVersion: '17.2.9',
9589
9644
  projectKey: this.options.projectKey,
9590
9645
  doNotTrack,
9591
9646
  reason: missingApi.length ? `missing api: ${missingApi.join(',')}` : reason,