@openreplay/tracker 18.0.11 → 18.0.12-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/dist/lib/index.js CHANGED
@@ -2711,9 +2711,15 @@ function ConstructedStyleSheets (app) {
2711
2711
  app.send(AdoptedSSAddOwner(sheetID, nodeID));
2712
2712
  }
2713
2713
  if (init) {
2714
- const rules = s.cssRules;
2715
- for (let i = 0; i < rules.length; i++) {
2716
- app.send(AdoptedSSInsertRuleURLBased(sheetID, rules[i].cssText, i, app.getBaseHref()));
2714
+ try {
2715
+ const rules = s.cssRules;
2716
+ for (let i = 0; i < rules.length; i++) {
2717
+ app.send(AdoptedSSInsertRuleURLBased(sheetID, rules[i].cssText, i, app.getBaseHref()));
2718
+ }
2719
+ }
2720
+ catch (e) {
2721
+ app.debug.log('Couldnt access adopted stylesheet', e);
2722
+ // Skip inaccessible (cross-origin) stylesheet
2717
2723
  }
2718
2724
  }
2719
2725
  nowOwning.push(sheetID);
@@ -4024,12 +4030,16 @@ const proto = {
4024
4030
  parentAlive: 'signal that parent is live',
4025
4031
  killIframe: 'stop tracker inside frame',
4026
4032
  startIframe: 'start tracker inside frame',
4033
+ // child -> parent: once-per-minute encoded debug snapshot from inside an iframe
4034
+ iframeDebug: 'iframe debug snapshot',
4027
4035
  // checking updates
4028
4036
  polling: 'hello-how-are-you-im-under-the-water-please-help-me',
4029
4037
  // happens if tab is old and has outdated token but
4030
4038
  // not communicating with backend to update it (for whatever reason)
4031
4039
  reset: 'reset-your-session-please',
4032
4040
  };
4041
+ /** reverse map proto value -> short readable key, for the crossdomain debug log */
4042
+ const protoLabel = Object.fromEntries(Object.entries(proto).map(([k, v]) => [v, k]));
4033
4043
  class App {
4034
4044
  get tagMatcher() {
4035
4045
  return this.tagWatcher.matcher;
@@ -4048,7 +4058,7 @@ class App {
4048
4058
  this.stopCallbacks = [];
4049
4059
  this.commitCallbacks = [];
4050
4060
  this.activityState = ActivityState.NotActive;
4051
- this.version = '18.0.11'; // TODO: version compatability check inside each plugin.
4061
+ this.version = '18.0.12-beta.1'; // TODO: version compatability check inside each plugin.
4052
4062
  this.socketMode = false;
4053
4063
  this.compressionThreshold = 24 * 1000;
4054
4064
  this.bc = null;
@@ -4065,10 +4075,64 @@ class App {
4065
4075
  this.checkStatus = () => {
4066
4076
  return this.parentActive;
4067
4077
  };
4078
+ /** child-side crossdomain debug state (only meaningful when insideIframe) */
4079
+ this.lastTokenReceived = null;
4080
+ this.lastParentMsgAt = 0;
4081
+ this.lastSentToParentAt = 0;
4082
+ this.iframeDebugInterval = null;
4083
+ /**
4084
+ * Child-side counterpart of emitCrossdomainDebug: once per minute an iframe posts an
4085
+ * encoded snapshot of its own tracking state up to the parent, which records it as a
4086
+ * console log. Posted directly (not via this.send) so it is reported even when the
4087
+ * child is NOT active — an inactive/orphaned child is exactly what we want to catch.
4088
+ */
4089
+ this.emitIframeDebug = () => {
4090
+ if (!this.insideIframe || !this.options.crossdomain?.enabled)
4091
+ return;
4092
+ const now = Date.now();
4093
+ const rel = (t) => (t ? now - t : null);
4094
+ const payload = {
4095
+ ctx: this.contextId,
4096
+ active: this.active(),
4097
+ state: ActivityState[this.activityState],
4098
+ parentActive: this.parentActive,
4099
+ rootId: this.rootId,
4100
+ frameOrder: this.frameOderNumber,
4101
+ // when and what token we last received from the parent (token truncated)
4102
+ token: this.lastTokenReceived
4103
+ ? { tok: this.lastTokenReceived.tok, agoMs: now - this.lastTokenReceived.at }
4104
+ : null,
4105
+ // last two-way communication with the parent
4106
+ lastParentMsgAgoMs: rel(this.lastParentMsgAt),
4107
+ lastSentToParentAgoMs: rel(this.lastSentToParentAt),
4108
+ };
4109
+ const json = JSON.stringify(payload);
4110
+ let encoded;
4111
+ try {
4112
+ encoded = btoa(json);
4113
+ }
4114
+ catch {
4115
+ encoded = json;
4116
+ }
4117
+ try {
4118
+ window.parent.postMessage({ line: proto.iframeDebug, context: this.contextId, debug: encoded }, this.options.crossdomain?.parentDomain ?? '*');
4119
+ this.markSentToParent();
4120
+ }
4121
+ catch (e) {
4122
+ this.debug.error('iframe debug post failed', e);
4123
+ }
4124
+ };
4068
4125
  this.parentCrossDomainFrameListener = (event) => {
4069
4126
  const { data } = event;
4070
4127
  if (!data || event.source === window)
4071
4128
  return;
4129
+ // Debug: remember the last time the parent talked to us.
4130
+ if (data.line === proto.startIframe ||
4131
+ data.line === proto.parentAlive ||
4132
+ data.line === proto.iframeId ||
4133
+ data.line === proto.killIframe) {
4134
+ this.lastParentMsgAt = Date.now();
4135
+ }
4072
4136
  if (data.line === proto.startIframe) {
4073
4137
  // Avoid corrupting an in-flight start; let it complete.
4074
4138
  if (this.activityState === ActivityState.Starting)
@@ -4079,6 +4143,7 @@ class App {
4079
4143
  }
4080
4144
  if (data.token) {
4081
4145
  this.session.setSessionToken(data.token, this.projectKey);
4146
+ this.lastTokenReceived = { tok: String(data.token).slice(-8), at: Date.now() };
4082
4147
  }
4083
4148
  if (data.id !== undefined) {
4084
4149
  this.rootId = data.id;
@@ -4097,6 +4162,7 @@ class App {
4097
4162
  this.parentActive = true;
4098
4163
  this.rootId = data.id;
4099
4164
  this.session.setSessionToken(data.token, this.projectKey);
4165
+ this.lastTokenReceived = { tok: String(data.token).slice(-8), at: Date.now() };
4100
4166
  this.frameOderNumber = data.frameOrderNumber;
4101
4167
  this.frameLevel = data.frameLevel;
4102
4168
  this.debug.log('starting iframe tracking', data);
@@ -4109,14 +4175,110 @@ class App {
4109
4175
  }
4110
4176
  };
4111
4177
  this.trackedFrames = [];
4178
+ /** every context that has been enrolled at least once, to tell an orphan (re-adopt) apart
4179
+ * from a brand-new child still mid-enrollment (leave alone). */
4180
+ this.everTrackedFrames = new Set();
4112
4181
  this.frameLastSeen = new Map();
4182
+ /** crossdomain debug diagnostics, reported once per minute as an encoded console log */
4183
+ this.frameOrigin = new Map();
4184
+ this.frameAnyLastSeen = new Map();
4185
+ this.frameBatchLastSeen = new Map();
4186
+ this.frameLastSent = new Map();
4187
+ this.xdomainDebugInterval = null;
4188
+ /** last time we re-adopted a given orphaned context, to avoid restart spam */
4189
+ this.reAdoptCooldown = new Map();
4190
+ this.RE_ADOPT_COOLDOWN_MS = 2000;
4191
+ /**
4192
+ * Stable, collision-free frame-order allocation. Node ids are partitioned by
4193
+ * (frameLevel, frameOrder) via pack() — every (level, order) owns its own id block, so
4194
+ * two simultaneously-live frames sharing an order at the same level corrupt each other's
4195
+ * node trees and one stops rendering. The previous `trackedFrames.findIndex+1` derived
4196
+ * order from a mutable array index, and pruneStaleFrames()'s .filter() shifts those
4197
+ * indices, so a newly enrolled frame could be handed an order still in use by a live
4198
+ * (but pruned) frame. We instead assign each context a persistent order, unique among all
4199
+ * non-recycled contexts at its level, freed only when the context is GC'd (truly gone).
4200
+ */
4201
+ this.frameAlloc = new Map();
4202
+ this.usedOrdersByLevel = new Map();
4113
4203
  this.FRAME_STALE_MS = 1500;
4204
+ /**
4205
+ * Once per minute: emit an encoded console log from the parent tracker describing every
4206
+ * tracked child iframe and the freshness of our two-way communication with it. Lets us
4207
+ * see in replay which crossdomain iframe went silent and on which leg of the handshake.
4208
+ */
4209
+ /** drop debug entries for contexts we have neither heard from nor messaged in this long */
4210
+ this.XDOMAIN_DEBUG_RETENTION_MS = 10 * 60000;
4211
+ this.emitCrossdomainDebug = () => {
4212
+ if (this.insideIframe || !this.options.crossdomain?.enabled || !this.active())
4213
+ return;
4214
+ const now = Date.now();
4215
+ const rel = (t) => (t === undefined ? null : now - t);
4216
+ // Report the union of currently-tracked frames and every context we have any debug
4217
+ // record for: a frame that broke and stopped polling gets pruned from trackedFrames,
4218
+ // but it is exactly the one we want to surface (with a large lastAnyMsgAgoMs).
4219
+ const tracked = new Set(this.trackedFrames);
4220
+ const contexts = new Set([
4221
+ ...this.trackedFrames,
4222
+ ...this.frameAnyLastSeen.keys(),
4223
+ ...this.frameLastSent.keys(),
4224
+ ]);
4225
+ const frames = Array.from(contexts).map((ctx, i) => {
4226
+ const sent = this.frameLastSent.get(ctx);
4227
+ const alloc = this.frameAlloc.get(ctx);
4228
+ return {
4229
+ // the actual allocated (level, order) node-id partition, else an enumeration index
4230
+ n: alloc ? alloc.order : i + 1,
4231
+ level: alloc ? alloc.level : null,
4232
+ // identify by domain if we have it, otherwise the context id, otherwise the number
4233
+ id: this.frameOrigin.get(ctx) || ctx || `#${i + 1}`,
4234
+ tracked: tracked.has(ctx),
4235
+ lastAnyMsgAgoMs: rel(this.frameAnyLastSeen.get(ctx)),
4236
+ lastBatchAgoMs: rel(this.frameBatchLastSeen.get(ctx)),
4237
+ lastSent: sent ? { line: sent.line, agoMs: now - sent.t } : null,
4238
+ };
4239
+ });
4240
+ // GC: forget contexts that have been silent and un-messaged past the retention window.
4241
+ const cutoff = now - this.XDOMAIN_DEBUG_RETENTION_MS;
4242
+ for (const ctx of contexts) {
4243
+ if (tracked.has(ctx))
4244
+ continue;
4245
+ const seen = this.frameAnyLastSeen.get(ctx) ?? 0;
4246
+ const sentT = this.frameLastSent.get(ctx)?.t ?? 0;
4247
+ if (Math.max(seen, sentT) < cutoff) {
4248
+ this.frameOrigin.delete(ctx);
4249
+ this.frameAnyLastSeen.delete(ctx);
4250
+ this.frameBatchLastSeen.delete(ctx);
4251
+ this.frameLastSent.delete(ctx);
4252
+ this.reAdoptCooldown.delete(ctx);
4253
+ this.everTrackedFrames.delete(ctx);
4254
+ this.freeFrameOrder(ctx);
4255
+ }
4256
+ }
4257
+ const payload = { t: now, count: frames.length, frames };
4258
+ const json = JSON.stringify(payload);
4259
+ let encoded;
4260
+ try {
4261
+ // payload is ASCII (base36 contexts, URL origins, numbers), so plain base64 is safe
4262
+ encoded = btoa(json);
4263
+ }
4264
+ catch {
4265
+ encoded = json;
4266
+ }
4267
+ this.send(ConsoleLog('info', `[OR_XDOMAIN_DEBUG] ${encoded}`));
4268
+ };
4114
4269
  this.crossDomainIframeListener = (event) => {
4115
4270
  if (event.source === window)
4116
4271
  return;
4117
4272
  const { data } = event;
4118
4273
  if (!data)
4119
4274
  return;
4275
+ // Debug: remember when we last heard *anything* from this context, and its domain.
4276
+ if (data.context) {
4277
+ this.frameAnyLastSeen.set(data.context, Date.now());
4278
+ if (event.origin && !this.frameOrigin.has(data.context)) {
4279
+ this.frameOrigin.set(data.context, event.origin);
4280
+ }
4281
+ }
4120
4282
  // Record liveness regardless of our own active state so the queue can prune
4121
4283
  // stale contexts reliably once we resume dispatching commands after a cycle.
4122
4284
  if ((data.line === proto.polling || data.line === proto.iframeSignal) && data.context) {
@@ -4124,9 +4286,15 @@ class App {
4124
4286
  }
4125
4287
  if (!this.active())
4126
4288
  return;
4289
+ if (data.line === proto.iframeDebug) {
4290
+ // A child posted its once-per-minute snapshot; surface it in our recorded console.
4291
+ this.send(ConsoleLog('info', `[OR_XDOMAIN_IFRAME_DEBUG] ${data.debug}`));
4292
+ return;
4293
+ }
4127
4294
  if (data.line === proto.iframeSignal) {
4128
4295
  // @ts-ignore
4129
4296
  event.source?.postMessage({ ping: true, line: proto.parentAlive }, '*');
4297
+ this.recordSentToFrame(data.context, proto.parentAlive);
4130
4298
  const signalId = async () => {
4131
4299
  if (event.source === null) {
4132
4300
  return console.error('Couldnt connect to event.source for child iframe tracking');
@@ -4143,22 +4311,25 @@ class App {
4143
4311
  else {
4144
4312
  this.trackedFrames.push(data.context);
4145
4313
  }
4314
+ this.everTrackedFrames.add(data.context);
4146
4315
  await this.waitStarted();
4147
4316
  const token = this.session.getSessionToken(this.projectKey);
4148
- const order = this.trackedFrames.findIndex((f) => f === data.context) + 1;
4149
- if (order === 0) {
4150
- this.debug.error('Couldnt get order number for iframe', data.context, this.trackedFrames);
4151
- }
4317
+ // Persistent, collision-free order (NOT the shifting array index). A restart of the
4318
+ // same context keeps its order/id-block for continuity; distinct live frames at the
4319
+ // same level never share one.
4320
+ const frameLevel = this.frameLevel + 1;
4321
+ const order = this.allocateFrameOrder(data.context, frameLevel);
4152
4322
  const iframeData = {
4153
4323
  line: proto.iframeId,
4154
4324
  id,
4155
4325
  token,
4156
4326
  frameOrderNumber: order,
4157
- frameLevel: this.frameLevel + 1,
4327
+ frameLevel,
4158
4328
  };
4159
4329
  this.debug.log('Got child frame signal; nodeId', id, event.source, iframeData);
4160
4330
  // @ts-ignore
4161
4331
  event.source?.postMessage(iframeData, '*');
4332
+ this.recordSentToFrame(data.context, proto.iframeId);
4162
4333
  }
4163
4334
  catch (e) {
4164
4335
  console.error(e);
@@ -4171,6 +4342,9 @@ class App {
4171
4342
  * plus we rewrite some of the messages to be relative to the main context/window
4172
4343
  * */
4173
4344
  if (data.line === proto.iframeBatch) {
4345
+ if (data.context) {
4346
+ this.frameBatchLastSeen.set(data.context, Date.now());
4347
+ }
4174
4348
  const msgBatch = data.messages;
4175
4349
  const mappedMessages = [];
4176
4350
  msgBatch.forEach((msg) => {
@@ -4218,6 +4392,16 @@ class App {
4218
4392
  this.messages.push(...mappedMessages);
4219
4393
  }
4220
4394
  if (data.line === proto.polling) {
4395
+ // Self-heal: a live child that was enrolled before but fell out of trackedFrames
4396
+ // (pruned during a stop/start gap) keeps polling yet never re-signals. Re-adopt it
4397
+ // so it restarts and re-enrolls. We require everTrackedFrames so a brand-new child
4398
+ // still mid-enrollment (iframeSignal/checkNodeId in flight) is left alone.
4399
+ if (data.context &&
4400
+ this.everTrackedFrames.has(data.context) &&
4401
+ !this.trackedFrames.includes(data.context)) {
4402
+ this.reAdoptOrphanFrame(event, data.context);
4403
+ return;
4404
+ }
4221
4405
  if (!this.pollingQueue.order.length) {
4222
4406
  return;
4223
4407
  }
@@ -4254,6 +4438,7 @@ class App {
4254
4438
  }
4255
4439
  // @ts-ignore
4256
4440
  event.source?.postMessage(message, '*');
4441
+ this.recordSentToFrame(data.context, nextCommand);
4257
4442
  if (this.pollingQueue[nextCommand].length === 0) {
4258
4443
  delete this.pollingQueue[nextCommand];
4259
4444
  this.pollingQueue.order.shift();
@@ -4291,6 +4476,7 @@ class App {
4291
4476
  source: thisTab,
4292
4477
  context: this.contextId,
4293
4478
  }, this.options.crossdomain?.parentDomain ?? '*');
4479
+ this.markSentToParent();
4294
4480
  /**
4295
4481
  * since we need to wait uncertain amount of time
4296
4482
  * and I don't want to have recursion going on,
@@ -4311,6 +4497,7 @@ class App {
4311
4497
  source: thisTab,
4312
4498
  context: this.contextId,
4313
4499
  }, this.options.crossdomain?.parentDomain ?? '*');
4500
+ this.markSentToParent();
4314
4501
  this.debug.info('Trying to signal to parent, attempt:', retries + 1);
4315
4502
  retries++;
4316
4503
  };
@@ -4528,7 +4715,13 @@ class App {
4528
4715
  line: proto.polling,
4529
4716
  context: this.contextId,
4530
4717
  }, options.crossdomain?.parentDomain ?? '*');
4718
+ this.markSentToParent();
4531
4719
  }, 250);
4720
+ // Child-only: once per minute, post an encoded snapshot of our own tracking state
4721
+ // (active?, token received, last comms) up to the parent so it lands in the replay.
4722
+ if (this.iframeDebugInterval)
4723
+ clearInterval(this.iframeDebugInterval);
4724
+ this.iframeDebugInterval = setInterval(this.emitIframeDebug, 60000);
4532
4725
  }
4533
4726
  else {
4534
4727
  this.initWorker();
@@ -4537,6 +4730,13 @@ class App {
4537
4730
  * so they can act as if it was just a same-domain iframe
4538
4731
  * */
4539
4732
  window.addEventListener('message', this.crossDomainIframeListener);
4733
+ // Parent-only: once per minute, log an encoded snapshot of every tracked child
4734
+ // iframe and the freshness of our two-way comms, to debug iframes that go silent.
4735
+ if (this.options.crossdomain?.enabled) {
4736
+ if (this.xdomainDebugInterval)
4737
+ clearInterval(this.xdomainDebugInterval);
4738
+ this.xdomainDebugInterval = setInterval(this.emitCrossdomainDebug, 60000);
4739
+ }
4540
4740
  }
4541
4741
  if (this.bc !== null) {
4542
4742
  this.bc.postMessage({
@@ -4586,6 +4786,62 @@ class App {
4586
4786
  };
4587
4787
  }
4588
4788
  }
4789
+ /** stamp every outbound post to the parent window, for the child debug snapshot */
4790
+ markSentToParent() {
4791
+ this.lastSentToParentAt = Date.now();
4792
+ }
4793
+ allocateFrameOrder(ctx, level) {
4794
+ const existing = this.frameAlloc.get(ctx);
4795
+ if (existing !== undefined)
4796
+ return existing.order;
4797
+ let used = this.usedOrdersByLevel.get(level);
4798
+ if (!used) {
4799
+ used = new Set();
4800
+ this.usedOrdersByLevel.set(level, used);
4801
+ }
4802
+ let order = -1;
4803
+ for (let n = 1; n <= MASK_ORDER; n++) {
4804
+ if (!used.has(n)) {
4805
+ order = n;
4806
+ break;
4807
+ }
4808
+ }
4809
+ if (order === -1) {
4810
+ // Overflow (>127 live frames at one level): evict the least-recently-seen context at
4811
+ // this level that is not currently tracked, and reuse its slot rather than failing.
4812
+ let lru = null;
4813
+ let lruSeen = Infinity;
4814
+ const trackedSet = new Set(this.trackedFrames);
4815
+ this.frameAlloc.forEach((alloc, c) => {
4816
+ if (alloc.level !== level || trackedSet.has(c))
4817
+ return;
4818
+ const seen = this.frameAnyLastSeen.get(c) ?? 0;
4819
+ if (seen < lruSeen) {
4820
+ lruSeen = seen;
4821
+ lru = c;
4822
+ }
4823
+ });
4824
+ if (lru !== null) {
4825
+ order = this.frameAlloc.get(lru).order;
4826
+ this.frameAlloc.delete(lru);
4827
+ this.debug.error('OR: frame order space exhausted, evicting', lru, 'for', ctx);
4828
+ }
4829
+ else {
4830
+ order = MASK_ORDER;
4831
+ this.debug.error('OR: frame order overflow, reusing max order for', ctx);
4832
+ }
4833
+ }
4834
+ used.add(order);
4835
+ this.frameAlloc.set(ctx, { order, level });
4836
+ return order;
4837
+ }
4838
+ freeFrameOrder(ctx) {
4839
+ const alloc = this.frameAlloc.get(ctx);
4840
+ if (!alloc)
4841
+ return;
4842
+ this.frameAlloc.delete(ctx);
4843
+ this.usedOrdersByLevel.get(alloc.level)?.delete(alloc.order);
4844
+ }
4589
4845
  pruneStaleFrames() {
4590
4846
  const staleAfter = Date.now() - this.FRAME_STALE_MS;
4591
4847
  this.trackedFrames = this.trackedFrames.filter((ctx) => {
@@ -4596,6 +4852,45 @@ class App {
4596
4852
  return false;
4597
4853
  });
4598
4854
  }
4855
+ /** records the last command/signal we posted to a given child iframe context (debug) */
4856
+ recordSentToFrame(ctx, line) {
4857
+ if (!ctx)
4858
+ return;
4859
+ this.frameLastSent.set(ctx, { line: protoLabel[line] ?? line, t: Date.now() });
4860
+ }
4861
+ /**
4862
+ * Self-heal for the "kill-then-prune orphan" race: a live child can fall out of
4863
+ * `trackedFrames` (its 250ms poll was delayed past FRAME_STALE_MS during the parent's
4864
+ * stop/start NotActive gap, so pruneStaleFrames evicted it). It keeps polling but the
4865
+ * only re-enrollment path is an `iframeSignal`, which a stopped/active-but-orphaned
4866
+ * child never re-emits — so it would record nothing forever. When we (the parent) are
4867
+ * active and see a poll from an un-tracked context, push a `startIframe` so the child
4868
+ * restarts, re-runs the full handshake and re-observes with a fresh rootId. Cooldowned
4869
+ * so we don't spam restarts during the child's start window.
4870
+ */
4871
+ reAdoptOrphanFrame(event, ctx) {
4872
+ const now = Date.now();
4873
+ const last = this.reAdoptCooldown.get(ctx) ?? 0;
4874
+ if (now - last < this.RE_ADOPT_COOLDOWN_MS)
4875
+ return;
4876
+ this.reAdoptCooldown.set(ctx, now);
4877
+ const message = {
4878
+ line: proto.startIframe,
4879
+ token: this.session.getSessionToken(this.projectKey),
4880
+ };
4881
+ const targetFrame = this.pageFrames.find((f) => f.contentWindow === event.source) ||
4882
+ Array.from(document.querySelectorAll('iframe')).find((f) => f.contentWindow === event.source);
4883
+ if (targetFrame) {
4884
+ const nodeId = targetFrame[this.options.node_id];
4885
+ if (nodeId !== undefined) {
4886
+ message.id = nodeId;
4887
+ }
4888
+ }
4889
+ // @ts-ignore
4890
+ event.source?.postMessage(message, '*');
4891
+ this.recordSentToFrame(ctx, proto.startIframe);
4892
+ this.debug.log('Re-adopting orphaned crossdomain iframe', ctx);
4893
+ }
4599
4894
  allowAppStart() {
4600
4895
  this.canStart = true;
4601
4896
  if (this.startTimeout) {
@@ -4761,7 +5056,9 @@ class App {
4761
5056
  window.parent.postMessage({
4762
5057
  line: proto.iframeBatch,
4763
5058
  messages: this.messages,
5059
+ context: this.contextId,
4764
5060
  }, this.options.crossdomain?.parentDomain ?? '*');
5061
+ this.markSentToParent();
4765
5062
  this.commitCallbacks.forEach((cb) => cb(this.messages));
4766
5063
  this.messages.length = 0;
4767
5064
  return;
@@ -7017,15 +7314,28 @@ function CSSRules (app, opts) {
7017
7314
  if (!nodeID)
7018
7315
  return;
7019
7316
  const sheet = node.sheet;
7317
+ // Accessing cssRules on a cross-origin stylesheet (e.g. injected by a
7318
+ // browser extension) throws a SecurityError. Probe it before registering
7319
+ // the sheet so an inaccessible sheet is skipped instead of aborting start.
7320
+ let rules;
7321
+ try {
7322
+ rules = sheet.cssRules;
7323
+ }
7324
+ catch (e) {
7325
+ // Skip inaccessible (cross-origin) stylesheet
7326
+ app.debug.log('Couldnt access stylesheet during initial scan', e);
7327
+ return;
7328
+ }
7020
7329
  const sheetID = nextID();
7021
7330
  styleSheetIDMap.set(sheet, sheetID);
7022
7331
  app.send(AdoptedSSAddOwner(sheetID, nodeID));
7023
- for (let i = 0; i < sheet.cssRules.length; i++) {
7332
+ for (let i = 0; i < rules.length; i++) {
7024
7333
  try {
7025
- sendInsertDeleteRule(sheet, i, sheet.cssRules[i].cssText);
7334
+ sendInsertDeleteRule(sheet, i, rules[i].cssText);
7026
7335
  }
7027
7336
  catch (e) {
7028
7337
  // Skip inaccessible rules
7338
+ app.debug.log('Couldnt access stylesheet rule during initial scan', e);
7029
7339
  }
7030
7340
  }
7031
7341
  });
@@ -7454,7 +7764,7 @@ class NetworkMessage {
7454
7764
  return null;
7455
7765
  const gqlHeader = "application/graphql-response";
7456
7766
  const isGraphql = messageInfo.url.includes("/graphql")
7457
- || Object.values(messageInfo.request.headers).some(v => v && typeof v === 'string' && v.includes(gqlHeader));
7767
+ || Object.values(messageInfo.request.headers).some(v => v.includes(gqlHeader));
7458
7768
  if (isGraphql && messageInfo.response.body && typeof messageInfo.response.body === 'string') {
7459
7769
  const isError = messageInfo.response.body.includes("errors");
7460
7770
  messageInfo.status = isError ? 400 : 200;
@@ -7558,7 +7868,6 @@ const genStringBody = (body) => {
7558
7868
  }
7559
7869
  else if (body instanceof Blob ||
7560
7870
  body instanceof ReadableStream ||
7561
- ArrayBuffer.isView(body) ||
7562
7871
  body instanceof ArrayBuffer) {
7563
7872
  result = 'byte data';
7564
7873
  }
@@ -9047,7 +9356,7 @@ class ConstantProperties {
9047
9356
  user_id: this.user_id,
9048
9357
  distinct_id: this.deviceId,
9049
9358
  sdk_edition: 'web',
9050
- sdk_version: '18.0.11',
9359
+ sdk_version: '18.0.12-beta.1',
9051
9360
  timezone: getUTCOffsetString(),
9052
9361
  search_engine: this.searchEngine,
9053
9362
  };
@@ -9749,7 +10058,7 @@ class API {
9749
10058
  this.signalStartIssue = (reason, missingApi) => {
9750
10059
  const doNotTrack = this.checkDoNotTrack();
9751
10060
  console.log("Tracker couldn't start due to:", JSON.stringify({
9752
- trackerVersion: '18.0.11',
10061
+ trackerVersion: '18.0.12-beta.1',
9753
10062
  projectKey: this.options.projectKey,
9754
10063
  doNotTrack,
9755
10064
  reason: missingApi.length ? `missing api: ${missingApi.join(',')}` : reason,