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