@openreplay/tracker 16.2.0 → 16.2.2-beta.19

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
@@ -4046,6 +4046,7 @@ function ConstructedStyleSheets (app) {
4046
4046
  return replace.call(this, text).then((sheet) => {
4047
4047
  const sheetID = styleSheetIDMap.get(this);
4048
4048
  if (sheetID) {
4049
+ console.log('replace');
4049
4050
  app.send(AdoptedSSReplaceURLBased(sheetID, text, app.getBaseHref()));
4050
4051
  }
4051
4052
  return sheet;
@@ -4055,6 +4056,7 @@ function ConstructedStyleSheets (app) {
4055
4056
  context.CSSStyleSheet.prototype.replaceSync = function (text) {
4056
4057
  const sheetID = styleSheetIDMap.get(this);
4057
4058
  if (sheetID) {
4059
+ console.log('replaceSync');
4058
4060
  app.send(AdoptedSSReplaceURLBased(sheetID, text, app.getBaseHref()));
4059
4061
  }
4060
4062
  return replaceSync.call(this, text);
@@ -4697,11 +4699,52 @@ class IFrameOffsets {
4697
4699
 
4698
4700
  var InlineCssMode;
4699
4701
  (function (InlineCssMode) {
4700
- InlineCssMode[InlineCssMode["None"] = 0] = "None";
4701
- InlineCssMode[InlineCssMode["RemoteOnly"] = 1] = "RemoteOnly";
4702
- InlineCssMode[InlineCssMode["RemoteWithForceFetch"] = 2] = "RemoteWithForceFetch";
4703
- InlineCssMode[InlineCssMode["All"] = 3] = "All";
4702
+ /** default behavior -- will parse and cache the css file on backend */
4703
+ InlineCssMode[InlineCssMode["Disabled"] = 0] = "Disabled";
4704
+ /** will attempt to record the linked css file as AdoptedStyleSheet object */
4705
+ InlineCssMode[InlineCssMode["Inline"] = 1] = "Inline";
4706
+ /** will fetch the file, then simulated AdoptedStyleSheets behavior programmaticaly for the replay */
4707
+ InlineCssMode[InlineCssMode["InlineFetched"] = 2] = "InlineFetched";
4708
+ /** will fetch the file, then save it as plain css inside <style> node */
4709
+ InlineCssMode[InlineCssMode["PlainFetched"] = 3] = "PlainFetched";
4704
4710
  })(InlineCssMode || (InlineCssMode = {}));
4711
+ function getInlineOptions(mode) {
4712
+ switch (mode) {
4713
+ case InlineCssMode.Inline:
4714
+ return {
4715
+ inlineRemoteCss: true,
4716
+ inlinerOptions: {
4717
+ forceFetch: false,
4718
+ forcePlain: false,
4719
+ },
4720
+ };
4721
+ case InlineCssMode.InlineFetched:
4722
+ return {
4723
+ inlineRemoteCss: true,
4724
+ inlinerOptions: {
4725
+ forceFetch: true,
4726
+ forcePlain: false,
4727
+ },
4728
+ };
4729
+ case InlineCssMode.PlainFetched:
4730
+ return {
4731
+ inlineRemoteCss: true,
4732
+ inlinerOptions: {
4733
+ forceFetch: true,
4734
+ forcePlain: true,
4735
+ },
4736
+ };
4737
+ case InlineCssMode.Disabled:
4738
+ default:
4739
+ return {
4740
+ inlineRemoteCss: false,
4741
+ inlinerOptions: {
4742
+ forceFetch: false,
4743
+ forcePlain: false,
4744
+ },
4745
+ };
4746
+ }
4747
+ }
4705
4748
  const attachShadowNativeFn = IN_BROWSER ? Element.prototype.attachShadow : () => new ShadowRoot();
4706
4749
  class TopObserver extends Observer {
4707
4750
  constructor(params) {
@@ -4710,7 +4753,11 @@ class TopObserver extends Observer {
4710
4753
  disableSprites: false,
4711
4754
  inlineCss: 0,
4712
4755
  }, params.options);
4713
- super(params.app, true, opts);
4756
+ const observerOptions = {
4757
+ disableSprites: opts.disableSprites,
4758
+ ...getInlineOptions(opts.inlineCss)
4759
+ };
4760
+ super(params.app, true, observerOptions);
4714
4761
  this.iframeOffsets = new IFrameOffsets();
4715
4762
  this.contextCallbacks = [];
4716
4763
  // Attached once per Tracker instance
@@ -5130,43 +5177,6 @@ function getTimezone() {
5130
5177
  return `UTC${sign}${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}`;
5131
5178
  }
5132
5179
  const delay = (ms) => new Promise((res) => setTimeout(res, ms));
5133
- function getInlineOptions(mode) {
5134
- switch (mode) {
5135
- case InlineCssMode.RemoteOnly:
5136
- return {
5137
- inlineRemoteCss: true,
5138
- inlinerOptions: {
5139
- forceFetch: false,
5140
- forcePlain: false,
5141
- },
5142
- };
5143
- case InlineCssMode.RemoteWithForceFetch:
5144
- return {
5145
- inlineRemoteCss: true,
5146
- inlinerOptions: {
5147
- forceFetch: true,
5148
- forcePlain: false,
5149
- },
5150
- };
5151
- case InlineCssMode.All:
5152
- return {
5153
- inlineRemoteCss: true,
5154
- inlinerOptions: {
5155
- forceFetch: true,
5156
- forcePlain: true,
5157
- },
5158
- };
5159
- case InlineCssMode.None:
5160
- default:
5161
- return {
5162
- inlineRemoteCss: false,
5163
- inlinerOptions: {
5164
- forceFetch: false,
5165
- forcePlain: false,
5166
- },
5167
- };
5168
- }
5169
- }
5170
5180
  const proto = {
5171
5181
  // ask if there are any tabs alive
5172
5182
  ask: 'never-gonna-give-you-up',
@@ -5198,7 +5208,7 @@ class App {
5198
5208
  this.stopCallbacks = [];
5199
5209
  this.commitCallbacks = [];
5200
5210
  this.activityState = ActivityState.NotActive;
5201
- this.version = '16.2.0'; // TODO: version compatability check inside each plugin.
5211
+ this.version = '16.2.2-beta.19'; // TODO: version compatability check inside each plugin.
5202
5212
  this.socketMode = false;
5203
5213
  this.compressionThreshold = 24 * 1000;
5204
5214
  this.bc = null;
@@ -5521,19 +5531,6 @@ class App {
5521
5531
  this.onUxtCb = [];
5522
5532
  this.contextId = Math.random().toString(36).slice(2);
5523
5533
  this.projectKey = projectKey;
5524
- this.inlineCss = getInlineOptions(options.inlineCss ?? 0);
5525
- if (Object.keys(options).findIndex((k) => ['fixedCanvasScaling', 'disableCanvas'].includes(k)) !==
5526
- -1) {
5527
- console.warn('Openreplay: canvas options are moving to separate key "canvas" in next update. Please update your configuration.');
5528
- options = {
5529
- ...options,
5530
- canvas: {
5531
- __save_canvas_locally: options.__save_canvas_locally,
5532
- fixedCanvasScaling: options.fixedCanvasScaling,
5533
- disableCanvas: options.disableCanvas,
5534
- },
5535
- };
5536
- }
5537
5534
  this.networkOptions = options.network;
5538
5535
  const defaultOptions = {
5539
5536
  revID: '',
@@ -5548,13 +5545,10 @@ class App {
5548
5545
  __is_snippet: false,
5549
5546
  __debug_report_edp: null,
5550
5547
  __debug__: LogLevel.Silent,
5551
- __save_canvas_locally: false,
5552
5548
  localStorage: null,
5553
5549
  sessionStorage: null,
5554
5550
  forceSingleTab: false,
5555
5551
  assistSocketHost: '',
5556
- fixedCanvasScaling: false,
5557
- disableCanvas: false,
5558
5552
  captureIFrames: true,
5559
5553
  obscureTextEmails: false,
5560
5554
  obscureTextNumbers: false,
@@ -5592,7 +5586,7 @@ class App {
5592
5586
  forceNgOff: Boolean(options.forceNgOff),
5593
5587
  maintainer: this.options.nodes?.maintainer,
5594
5588
  });
5595
- this.observer = new TopObserver({ app: this, options: { ...options, ...this.inlineCss } });
5589
+ this.observer = new TopObserver({ app: this, options });
5596
5590
  this.ticker = new Ticker(this);
5597
5591
  this.ticker.attach(() => this.commit());
5598
5592
  this.debug = new Logger(this.options.__debug__);
@@ -8049,60 +8043,107 @@ function Viewport (app) {
8049
8043
  app.ticker.attach(sendSetViewportSize, 5, false);
8050
8044
  }
8051
8045
 
8052
- function CSSRules (app) {
8053
- if (app === null) {
8046
+ function CSSRules (app, opts) {
8047
+ if (app === null)
8054
8048
  return;
8055
- }
8056
8049
  if (!window.CSSStyleSheet) {
8057
8050
  app.send(TechnicalInfo('no_stylesheet_prototype_in_window', ''));
8058
8051
  return;
8059
8052
  }
8053
+ // Track CSS rule snapshots by sheetID:index
8054
+ const ruleSnapshots = new Map();
8055
+ let checkInterval = null;
8056
+ const checkIntervalMs = opts.checkCssInterval || 200; // Check every 200ms
8057
+ // Check all rules for changes
8058
+ function checkRuleChanges() {
8059
+ for (let i = 0; i < document.styleSheets.length; i++) {
8060
+ try {
8061
+ const sheet = document.styleSheets[i];
8062
+ const sheetID = styleSheetIDMap.get(sheet);
8063
+ if (!sheetID)
8064
+ continue;
8065
+ // Check each rule in the sheet
8066
+ for (let j = 0; j < sheet.cssRules.length; j++) {
8067
+ try {
8068
+ const rule = sheet.cssRules[j];
8069
+ const key = `${sheetID}:${j}`;
8070
+ const newText = rule.cssText;
8071
+ const oldText = ruleSnapshots.get(key);
8072
+ if (oldText !== newText) {
8073
+ // Rule is new or changed
8074
+ if (oldText !== undefined) {
8075
+ // Rule changed - send update
8076
+ app.send(AdoptedSSDeleteRule(sheetID, j));
8077
+ app.send(AdoptedSSInsertRuleURLBased(sheetID, newText, j, app.getBaseHref()));
8078
+ }
8079
+ else {
8080
+ // Rule added - send insert
8081
+ app.send(AdoptedSSInsertRuleURLBased(sheetID, newText, j, app.getBaseHref()));
8082
+ }
8083
+ ruleSnapshots.set(key, newText);
8084
+ }
8085
+ }
8086
+ catch (e) {
8087
+ /* Skip inaccessible rules */
8088
+ }
8089
+ }
8090
+ // Check for deleted rules
8091
+ const keysToCheck = Array.from(ruleSnapshots.keys()).filter((key) => key.startsWith(`${sheetID}:`));
8092
+ for (const key of keysToCheck) {
8093
+ const index = parseInt(key.split(':')[1], 10);
8094
+ if (index >= sheet.cssRules.length) {
8095
+ ruleSnapshots.delete(key);
8096
+ }
8097
+ }
8098
+ }
8099
+ catch (e) {
8100
+ /* Skip inaccessible sheets */
8101
+ }
8102
+ }
8103
+ }
8104
+ // Standard API hooks
8060
8105
  const sendInsertDeleteRule = app.safe((sheet, index, rule) => {
8061
8106
  const sheetID = styleSheetIDMap.get(sheet);
8062
- if (!sheetID) {
8063
- // OK-case. Sheet haven't been registered yet. Rules will be sent on registration.
8107
+ if (!sheetID)
8064
8108
  return;
8065
- }
8066
8109
  if (typeof rule === 'string') {
8067
8110
  app.send(AdoptedSSInsertRuleURLBased(sheetID, rule, index, app.getBaseHref()));
8111
+ ruleSnapshots.set(`${sheetID}:${index}`, rule);
8068
8112
  }
8069
8113
  else {
8070
8114
  app.send(AdoptedSSDeleteRule(sheetID, index));
8115
+ ruleSnapshots.delete(`${sheetID}:${index}`);
8071
8116
  }
8072
8117
  });
8073
- // TODO: proper rule insertion/removal (how?)
8074
8118
  const sendReplaceGroupingRule = app.safe((rule) => {
8075
8119
  let topmostRule = rule;
8076
- while (topmostRule.parentRule) {
8120
+ while (topmostRule.parentRule)
8077
8121
  topmostRule = topmostRule.parentRule;
8078
- }
8079
8122
  const sheet = topmostRule.parentStyleSheet;
8080
- if (!sheet) {
8081
- app.debug.warn('No parent StyleSheet found for', topmostRule, rule);
8123
+ if (!sheet)
8082
8124
  return;
8083
- }
8084
8125
  const sheetID = styleSheetIDMap.get(sheet);
8085
- if (!sheetID) {
8086
- app.debug.warn('No sheedID found for', sheet, styleSheetIDMap);
8126
+ if (!sheetID)
8087
8127
  return;
8088
- }
8089
8128
  const cssText = topmostRule.cssText;
8090
- const ruleList = sheet.cssRules;
8091
- const idx = Array.from(ruleList).indexOf(topmostRule);
8129
+ const idx = Array.from(sheet.cssRules).indexOf(topmostRule);
8092
8130
  if (idx >= 0) {
8093
8131
  app.send(AdoptedSSInsertRuleURLBased(sheetID, cssText, idx, app.getBaseHref()));
8094
- app.send(AdoptedSSDeleteRule(sheetID, idx + 1)); // Remove previous clone
8095
- }
8096
- else {
8097
- app.debug.warn('Rule index not found in', sheet, topmostRule);
8132
+ app.send(AdoptedSSDeleteRule(sheetID, idx + 1));
8133
+ ruleSnapshots.set(`${sheetID}:${idx}`, cssText);
8098
8134
  }
8099
8135
  });
8136
+ // Patch prototype methods
8100
8137
  const patchContext = app.safe((context) => {
8138
+ if (context.__css_tracking_patched__)
8139
+ return;
8140
+ context.__css_tracking_patched__ = true;
8101
8141
  const { insertRule, deleteRule } = context.CSSStyleSheet.prototype;
8102
8142
  const { insertRule: groupInsertRule, deleteRule: groupDeleteRule } = context.CSSGroupingRule.prototype;
8103
8143
  context.CSSStyleSheet.prototype.insertRule = function (rule, index = 0) {
8104
- sendInsertDeleteRule(this, index, rule);
8105
- return insertRule.call(this, rule, index);
8144
+ const result = insertRule.call(this, rule, index);
8145
+ sendInsertDeleteRule(this, result, rule);
8146
+ return result;
8106
8147
  };
8107
8148
  context.CSSStyleSheet.prototype.deleteRule = function (index) {
8108
8149
  sendInsertDeleteRule(this, index);
@@ -8113,33 +8154,50 @@ function CSSRules (app) {
8113
8154
  sendReplaceGroupingRule(this);
8114
8155
  return result;
8115
8156
  };
8116
- context.CSSGroupingRule.prototype.deleteRule = function (index = 0) {
8157
+ context.CSSGroupingRule.prototype.deleteRule = function (index) {
8117
8158
  const result = groupDeleteRule.call(this, index);
8118
8159
  sendReplaceGroupingRule(this);
8119
8160
  return result;
8120
8161
  };
8121
8162
  });
8163
+ // Apply patches
8122
8164
  patchContext(window);
8123
8165
  app.observer.attachContextCallback(patchContext);
8166
+ // Track style nodes
8124
8167
  app.nodes.attachNodeCallback((node) => {
8125
- if (!hasTag(node, 'style') || !node.sheet) {
8168
+ if (!hasTag(node, 'style') || !node.sheet)
8169
+ return;
8170
+ if (node.textContent !== null && node.textContent.trim().length > 0)
8126
8171
  return;
8127
- }
8128
- if (node.textContent !== null && node.textContent.trim().length > 0) {
8129
- return; // Non-virtual styles captured by the observer as a text
8130
- }
8131
8172
  const nodeID = app.nodes.getID(node);
8132
- if (!nodeID) {
8173
+ if (!nodeID)
8133
8174
  return;
8134
- }
8135
8175
  const sheet = node.sheet;
8136
8176
  const sheetID = nextID();
8137
8177
  styleSheetIDMap.set(sheet, sheetID);
8138
8178
  app.send(AdoptedSSAddOwner(sheetID, nodeID));
8139
- const rules = sheet.cssRules;
8140
- for (let i = 0; i < rules.length; i++) {
8141
- sendInsertDeleteRule(sheet, i, rules[i].cssText);
8179
+ for (let i = 0; i < sheet.cssRules.length; i++) {
8180
+ try {
8181
+ sendInsertDeleteRule(sheet, i, sheet.cssRules[i].cssText);
8182
+ }
8183
+ catch (e) {
8184
+ // Skip inaccessible rules
8185
+ }
8186
+ }
8187
+ });
8188
+ // Start checking and setup cleanup
8189
+ function startChecking() {
8190
+ if (checkInterval)
8191
+ return;
8192
+ checkInterval = window.setInterval(checkRuleChanges, checkIntervalMs);
8193
+ }
8194
+ setTimeout(startChecking, 50);
8195
+ app.attachStopCallback(() => {
8196
+ if (checkInterval) {
8197
+ clearInterval(checkInterval);
8198
+ checkInterval = null;
8142
8199
  }
8200
+ ruleSnapshots.clear();
8143
8201
  });
8144
8202
  }
8145
8203
 
@@ -8355,6 +8413,152 @@ function isObject(thing) {
8355
8413
  return thing !== null && typeof thing === 'object';
8356
8414
  }
8357
8415
 
8416
+ const sensitiveParams = new Set([
8417
+ "password",
8418
+ "pass",
8419
+ "pwd",
8420
+ "mdp",
8421
+ "token",
8422
+ "bearer",
8423
+ "jwt",
8424
+ "api_key",
8425
+ "api-key",
8426
+ "apiKey",
8427
+ "secret",
8428
+ "ssn",
8429
+ "zip",
8430
+ "zipcode",
8431
+ "x-api-key",
8432
+ "www-authenticate",
8433
+ "x-csrf-token",
8434
+ "x-requested-with",
8435
+ "x-forwarded-for",
8436
+ "x-real-ip",
8437
+ "cookie",
8438
+ "authorization",
8439
+ "auth",
8440
+ "proxy-authorization",
8441
+ "set-cookie",
8442
+ "account_key",
8443
+ ]);
8444
+ function numDigits(x) {
8445
+ return (Math.log10((x ^ (x >> 31)) - (x >> 31)) | 0) + 1;
8446
+ }
8447
+ function obscure(value) {
8448
+ if (typeof value === "number") {
8449
+ const digits = numDigits(value);
8450
+ return "9".repeat(digits);
8451
+ }
8452
+ if (typeof value === "string") {
8453
+ return value.replace(/[^\f\n\r\t\v\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff\s]/g, '*');
8454
+ }
8455
+ return value;
8456
+ }
8457
+ function filterHeaders(headers) {
8458
+ const filteredHeaders = {};
8459
+ if (Array.isArray(headers)) {
8460
+ headers.forEach(({ name, value }) => {
8461
+ if (sensitiveParams.has(name.toLowerCase())) {
8462
+ filteredHeaders[name] = obscure(value);
8463
+ }
8464
+ else {
8465
+ filteredHeaders[name] = value;
8466
+ }
8467
+ });
8468
+ }
8469
+ else {
8470
+ for (const [key, value] of Object.entries(headers)) {
8471
+ if (sensitiveParams.has(key.toLowerCase())) {
8472
+ filteredHeaders[key] = obscure(value);
8473
+ }
8474
+ else {
8475
+ filteredHeaders[key] = value;
8476
+ }
8477
+ }
8478
+ }
8479
+ return filteredHeaders;
8480
+ }
8481
+ function filterBody(body) {
8482
+ if (!body) {
8483
+ return body;
8484
+ }
8485
+ let parsedBody;
8486
+ let isJSON = false;
8487
+ try {
8488
+ parsedBody = JSON.parse(body);
8489
+ isJSON = true;
8490
+ }
8491
+ catch (e) {
8492
+ // not json
8493
+ }
8494
+ if (isJSON) {
8495
+ obscureSensitiveData(parsedBody);
8496
+ return JSON.stringify(parsedBody);
8497
+ }
8498
+ else {
8499
+ const isUrlSearch = typeof body === "string" && body.includes("?") && body.includes("=");
8500
+ if (isUrlSearch) {
8501
+ try {
8502
+ const params = new URLSearchParams(body);
8503
+ for (const key of params.keys()) {
8504
+ if (sensitiveParams.has(key.toLowerCase())) {
8505
+ const value = obscure(params.get(key));
8506
+ params.set(key, value);
8507
+ }
8508
+ }
8509
+ return params.toString();
8510
+ }
8511
+ catch (e) {
8512
+ // not url query ?
8513
+ return body;
8514
+ }
8515
+ }
8516
+ else {
8517
+ // not json or url query
8518
+ return body;
8519
+ }
8520
+ }
8521
+ }
8522
+ function sanitizeObject(obj) {
8523
+ obscureSensitiveData(obj);
8524
+ return obj;
8525
+ }
8526
+ function obscureSensitiveData(obj) {
8527
+ if (Array.isArray(obj)) {
8528
+ obj.forEach(obscureSensitiveData);
8529
+ }
8530
+ else if (obj && typeof obj === "object") {
8531
+ for (const key in obj) {
8532
+ if (Object.hasOwn(obj, key)) {
8533
+ if (sensitiveParams.has(key.toLowerCase())) {
8534
+ obj[key] = obscure(obj[key]);
8535
+ }
8536
+ else if (obj[key] !== null && typeof obj[key] === "object") {
8537
+ obscureSensitiveData(obj[key]);
8538
+ }
8539
+ }
8540
+ }
8541
+ }
8542
+ }
8543
+ function tryFilterUrl(url) {
8544
+ if (!url)
8545
+ return "";
8546
+ try {
8547
+ const urlObj = new URL(url);
8548
+ if (urlObj.searchParams) {
8549
+ for (const key of urlObj.searchParams.keys()) {
8550
+ if (sensitiveParams.has(key.toLowerCase())) {
8551
+ urlObj.searchParams.set(key, "******");
8552
+ }
8553
+ }
8554
+ }
8555
+ return urlObj.toString();
8556
+ }
8557
+ catch (e) {
8558
+ return url;
8559
+ }
8560
+ }
8561
+
8358
8562
  /**
8359
8563
  * I know we're not using most of the information from this class
8360
8564
  * but it can be useful in the future if we will decide to display more stuff in our ui
@@ -8386,13 +8590,18 @@ class NetworkMessage {
8386
8590
  }
8387
8591
  getMessage() {
8388
8592
  const { reqHs, resHs } = this.writeHeaders();
8593
+ const reqBody = this.method === 'GET'
8594
+ ? JSON.stringify(sanitizeObject(this.getData)) : filterBody(this.requestData);
8389
8595
  const request = {
8390
- headers: reqHs,
8391
- body: this.method === 'GET' ? JSON.stringify(this.getData) : this.requestData,
8596
+ headers: filterHeaders(reqHs),
8597
+ body: reqBody,
8598
+ };
8599
+ const response = {
8600
+ headers: filterHeaders(resHs),
8601
+ body: filterBody(this.response)
8392
8602
  };
8393
- const response = { headers: resHs, body: this.response };
8394
8603
  const messageInfo = this.sanitize({
8395
- url: this.url,
8604
+ url: tryFilterUrl(this.url),
8396
8605
  method: this.method,
8397
8606
  status: this.status,
8398
8607
  request,
@@ -8708,9 +8917,10 @@ class ResponseProxyHandler {
8708
8917
  if (typeof this.resp.body.getReader !== 'function') {
8709
8918
  return;
8710
8919
  }
8711
- const _getReader = this.resp.body.getReader;
8920
+ const clonedResp = this.resp.clone();
8921
+ const _getReader = clonedResp.body.getReader;
8712
8922
  // @ts-ignore
8713
- this.resp.body.getReader = () => {
8923
+ clonedResp.body.getReader = () => {
8714
8924
  const reader = _getReader.apply(this.resp.body);
8715
8925
  // when readyState is already 4,
8716
8926
  // it's not a chunked stream, or it had already been read.
@@ -9552,7 +9762,7 @@ class API {
9552
9762
  this.signalStartIssue = (reason, missingApi) => {
9553
9763
  const doNotTrack = this.checkDoNotTrack();
9554
9764
  console.log("Tracker couldn't start due to:", JSON.stringify({
9555
- trackerVersion: '16.2.0',
9765
+ trackerVersion: '16.2.2-beta.19',
9556
9766
  projectKey: this.options.projectKey,
9557
9767
  doNotTrack,
9558
9768
  reason: missingApi.length ? `missing api: ${missingApi.join(',')}` : reason,
@@ -9651,7 +9861,7 @@ class API {
9651
9861
  Mouse(app, options.mouse);
9652
9862
  // inside iframe, we ignore viewport scroll
9653
9863
  Scroll(app, this.crossdomainMode);
9654
- CSSRules(app);
9864
+ CSSRules(app, options);
9655
9865
  ConstructedStyleSheets(app);
9656
9866
  Console(app, options);
9657
9867
  Exception(app, options);