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