@openreplay/tracker 16.2.2-beta.19 → 16.3.0-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/entry.js CHANGED
@@ -1469,6 +1469,43 @@ function simpleMerge(defaultObj, givenObj) {
1469
1469
  }
1470
1470
  return result;
1471
1471
  }
1472
+ function throttleWithTrailing(fn, interval) {
1473
+ const lastCalls = new Map();
1474
+ const timeouts = new Map();
1475
+ const lastArgs = new Map();
1476
+ const throttled = function (key, ...args) {
1477
+ const now = Date.now();
1478
+ const lastCall = lastCalls.get(key) ?? 0;
1479
+ const remaining = interval - (now - lastCall);
1480
+ lastArgs.set(key, args);
1481
+ if (remaining <= 0) {
1482
+ if (timeouts.has(key)) {
1483
+ clearTimeout(timeouts.get(key));
1484
+ timeouts.delete(key);
1485
+ }
1486
+ lastCalls.set(key, now);
1487
+ fn(key, ...args);
1488
+ }
1489
+ else if (!timeouts.has(key)) {
1490
+ const timeoutId = setTimeout(() => {
1491
+ lastCalls.set(key, Date.now());
1492
+ timeouts.delete(key);
1493
+ const finalArgs = lastArgs.get(key);
1494
+ fn(key, ...finalArgs);
1495
+ }, remaining);
1496
+ timeouts.set(key, timeoutId);
1497
+ }
1498
+ };
1499
+ throttled.clear = () => {
1500
+ for (const timeout of timeouts.values()) {
1501
+ clearTimeout(timeout);
1502
+ }
1503
+ timeouts.clear();
1504
+ lastArgs.clear();
1505
+ lastCalls.clear();
1506
+ };
1507
+ return throttled;
1508
+ }
1472
1509
 
1473
1510
  // Auto-generated, do not edit
1474
1511
  /* eslint-disable */
@@ -3564,7 +3601,7 @@ function isNodeStillActive(node) {
3564
3601
  return [false, e];
3565
3602
  }
3566
3603
  }
3567
- const defaults = {
3604
+ const defaults$1 = {
3568
3605
  interval: SECOND * 30,
3569
3606
  batchSize: 2500,
3570
3607
  enabled: true,
@@ -3592,7 +3629,7 @@ class Maintainer {
3592
3629
  clearInterval(this.interval);
3593
3630
  }
3594
3631
  };
3595
- this.options = { ...defaults, ...options };
3632
+ this.options = { ...defaults$1, ...options };
3596
3633
  }
3597
3634
  }
3598
3635
 
@@ -4046,7 +4083,6 @@ function ConstructedStyleSheets (app) {
4046
4083
  return replace.call(this, text).then((sheet) => {
4047
4084
  const sheetID = styleSheetIDMap.get(this);
4048
4085
  if (sheetID) {
4049
- console.log('replace');
4050
4086
  app.send(AdoptedSSReplaceURLBased(sheetID, text, app.getBaseHref()));
4051
4087
  }
4052
4088
  return sheet;
@@ -4056,7 +4092,6 @@ function ConstructedStyleSheets (app) {
4056
4092
  context.CSSStyleSheet.prototype.replaceSync = function (text) {
4057
4093
  const sheetID = styleSheetIDMap.get(this);
4058
4094
  if (sheetID) {
4059
- console.log('replaceSync');
4060
4095
  app.send(AdoptedSSReplaceURLBased(sheetID, text, app.getBaseHref()));
4061
4096
  }
4062
4097
  return replaceSync.call(this, text);
@@ -4221,6 +4256,7 @@ class Observer {
4221
4256
  this.inlineRemoteCss = false;
4222
4257
  this.inlinerOptions = undefined;
4223
4258
  this.domParser = new DOMParser();
4259
+ this.throttledSetNodeData = throttleWithTrailing((id, parentElement, data) => this.sendNodeData(id, parentElement, data), 30);
4224
4260
  this.disableSprites = Boolean(options.disableSprites);
4225
4261
  this.inlineRemoteCss = Boolean(options.inlineRemoteCss);
4226
4262
  this.inlinerOptions = options.inlinerOptions;
@@ -4277,6 +4313,7 @@ class Observer {
4277
4313
  this.indexes.length = 1;
4278
4314
  this.attributesMap.clear();
4279
4315
  this.textSet.clear();
4316
+ this.throttledSetNodeData.clear();
4280
4317
  }
4281
4318
  /**
4282
4319
  * EXPERIMENTAL: Unbinds the removed nodes in case of iframe src change.
@@ -4532,7 +4569,7 @@ class Observer {
4532
4569
  else if (isTextNode(node)) {
4533
4570
  // for text node id != 0, hence parentID !== undefined and parent is Element
4534
4571
  this.app.send(CreateTextNode(id, parentID, index));
4535
- this.sendNodeData(id, parent, node.data);
4572
+ this.throttledSetNodeData(id, parent, node.data);
4536
4573
  }
4537
4574
  return true;
4538
4575
  }
@@ -4553,7 +4590,7 @@ class Observer {
4553
4590
  throw 'commitNode: node is not a text';
4554
4591
  }
4555
4592
  // for text node id != 0, hence parent is Element
4556
- this.sendNodeData(id, parent, node.data);
4593
+ this.throttledSetNodeData(id, parent, node.data);
4557
4594
  }
4558
4595
  return true;
4559
4596
  }
@@ -4596,6 +4633,7 @@ class Observer {
4596
4633
  disconnect() {
4597
4634
  this.observer.disconnect();
4598
4635
  this.clear();
4636
+ this.throttledSetNodeData.clear();
4599
4637
  }
4600
4638
  }
4601
4639
 
@@ -5208,7 +5246,7 @@ class App {
5208
5246
  this.stopCallbacks = [];
5209
5247
  this.commitCallbacks = [];
5210
5248
  this.activityState = ActivityState.NotActive;
5211
- this.version = '16.2.2-beta.19'; // TODO: version compatability check inside each plugin.
5249
+ this.version = '16.3.0-beta.1'; // TODO: version compatability check inside each plugin.
5212
5250
  this.socketMode = false;
5213
5251
  this.compressionThreshold = 24 * 1000;
5214
5252
  this.bc = null;
@@ -8043,6 +8081,11 @@ function Viewport (app) {
8043
8081
  app.ticker.attach(sendSetViewportSize, 5, false);
8044
8082
  }
8045
8083
 
8084
+ const defaults = {
8085
+ checkCssInterval: 200,
8086
+ scanInMemoryCSS: false,
8087
+ checkLimit: undefined,
8088
+ };
8046
8089
  function CSSRules (app, opts) {
8047
8090
  if (app === null)
8048
8091
  return;
@@ -8050,34 +8093,48 @@ function CSSRules (app, opts) {
8050
8093
  app.send(TechnicalInfo('no_stylesheet_prototype_in_window', ''));
8051
8094
  return;
8052
8095
  }
8053
- // Track CSS rule snapshots by sheetID:index
8096
+ const options = { ...defaults, ...opts };
8097
+ // sheetID:index -> ruleText
8054
8098
  const ruleSnapshots = new Map();
8055
8099
  let checkInterval = null;
8056
- const checkIntervalMs = opts.checkCssInterval || 200; // Check every 200ms
8057
- // Check all rules for changes
8100
+ const trackedSheets = new Set();
8101
+ const checkIntervalMs = options.checkCssInterval || 200;
8102
+ let checkIterations = {};
8058
8103
  function checkRuleChanges() {
8059
- for (let i = 0; i < document.styleSheets.length; i++) {
8104
+ if (!options.scanInMemoryCSS)
8105
+ return;
8106
+ const allSheets = trackedSheets.values();
8107
+ for (const sheet of allSheets) {
8060
8108
  try {
8061
- const sheet = document.styleSheets[i];
8062
8109
  const sheetID = styleSheetIDMap.get(sheet);
8063
8110
  if (!sheetID)
8064
8111
  continue;
8065
- // Check each rule in the sheet
8112
+ if (options.checkLimit) {
8113
+ if (!checkIterations[sheetID]) {
8114
+ checkIterations[sheetID] = 0;
8115
+ }
8116
+ else {
8117
+ checkIterations[sheetID]++;
8118
+ }
8119
+ if (checkIterations[sheetID] > options.checkLimit) {
8120
+ trackedSheets.delete(sheet);
8121
+ return;
8122
+ }
8123
+ }
8066
8124
  for (let j = 0; j < sheet.cssRules.length; j++) {
8067
8125
  try {
8068
8126
  const rule = sheet.cssRules[j];
8069
8127
  const key = `${sheetID}:${j}`;
8070
- const newText = rule.cssText;
8071
8128
  const oldText = ruleSnapshots.get(key);
8129
+ const newText = rule.cssText;
8072
8130
  if (oldText !== newText) {
8073
- // Rule is new or changed
8074
8131
  if (oldText !== undefined) {
8075
- // Rule changed - send update
8132
+ // Rule is changed
8076
8133
  app.send(AdoptedSSDeleteRule(sheetID, j));
8077
8134
  app.send(AdoptedSSInsertRuleURLBased(sheetID, newText, j, app.getBaseHref()));
8078
8135
  }
8079
8136
  else {
8080
- // Rule added - send insert
8137
+ // Rule added
8081
8138
  app.send(AdoptedSSInsertRuleURLBased(sheetID, newText, j, app.getBaseHref()));
8082
8139
  }
8083
8140
  ruleSnapshots.set(key, newText);
@@ -8087,7 +8144,6 @@ function CSSRules (app, opts) {
8087
8144
  /* Skip inaccessible rules */
8088
8145
  }
8089
8146
  }
8090
- // Check for deleted rules
8091
8147
  const keysToCheck = Array.from(ruleSnapshots.keys()).filter((key) => key.startsWith(`${sheetID}:`));
8092
8148
  for (const key of keysToCheck) {
8093
8149
  const index = parseInt(key.split(':')[1], 10);
@@ -8098,21 +8154,30 @@ function CSSRules (app, opts) {
8098
8154
  }
8099
8155
  catch (e) {
8100
8156
  /* Skip inaccessible sheets */
8157
+ trackedSheets.delete(sheet);
8101
8158
  }
8102
8159
  }
8103
8160
  }
8104
- // Standard API hooks
8161
+ const emptyRuleReg = /{\s*}/;
8162
+ function isRuleEmpty(rule) {
8163
+ return emptyRuleReg.test(rule);
8164
+ }
8105
8165
  const sendInsertDeleteRule = app.safe((sheet, index, rule) => {
8106
8166
  const sheetID = styleSheetIDMap.get(sheet);
8107
8167
  if (!sheetID)
8108
8168
  return;
8109
8169
  if (typeof rule === 'string') {
8110
8170
  app.send(AdoptedSSInsertRuleURLBased(sheetID, rule, index, app.getBaseHref()));
8111
- ruleSnapshots.set(`${sheetID}:${index}`, rule);
8171
+ if (isRuleEmpty(rule)) {
8172
+ ruleSnapshots.set(`${sheetID}:${index}`, rule);
8173
+ trackedSheets.add(sheet);
8174
+ }
8112
8175
  }
8113
8176
  else {
8114
8177
  app.send(AdoptedSSDeleteRule(sheetID, index));
8115
- ruleSnapshots.delete(`${sheetID}:${index}`);
8178
+ if (ruleSnapshots.has(`${sheetID}:${index}`)) {
8179
+ ruleSnapshots.delete(`${sheetID}:${index}`);
8180
+ }
8116
8181
  }
8117
8182
  });
8118
8183
  const sendReplaceGroupingRule = app.safe((rule) => {
@@ -8130,7 +8195,10 @@ function CSSRules (app, opts) {
8130
8195
  if (idx >= 0) {
8131
8196
  app.send(AdoptedSSInsertRuleURLBased(sheetID, cssText, idx, app.getBaseHref()));
8132
8197
  app.send(AdoptedSSDeleteRule(sheetID, idx + 1));
8133
- ruleSnapshots.set(`${sheetID}:${idx}`, cssText);
8198
+ if (isRuleEmpty(cssText)) {
8199
+ ruleSnapshots.set(`${sheetID}:${idx}`, cssText);
8200
+ trackedSheets.add(sheet);
8201
+ }
8134
8202
  }
8135
8203
  });
8136
8204
  // Patch prototype methods
@@ -8160,10 +8228,8 @@ function CSSRules (app, opts) {
8160
8228
  return result;
8161
8229
  };
8162
8230
  });
8163
- // Apply patches
8164
8231
  patchContext(window);
8165
8232
  app.observer.attachContextCallback(patchContext);
8166
- // Track style nodes
8167
8233
  app.nodes.attachNodeCallback((node) => {
8168
8234
  if (!hasTag(node, 'style') || !node.sheet)
8169
8235
  return;
@@ -8185,9 +8251,8 @@ function CSSRules (app, opts) {
8185
8251
  }
8186
8252
  }
8187
8253
  });
8188
- // Start checking and setup cleanup
8189
8254
  function startChecking() {
8190
- if (checkInterval)
8255
+ if (checkInterval || !options.scanInMemoryCSS)
8191
8256
  return;
8192
8257
  checkInterval = window.setInterval(checkRuleChanges, checkIntervalMs);
8193
8258
  }
@@ -8413,152 +8478,6 @@ function isObject(thing) {
8413
8478
  return thing !== null && typeof thing === 'object';
8414
8479
  }
8415
8480
 
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
-
8562
8481
  /**
8563
8482
  * I know we're not using most of the information from this class
8564
8483
  * but it can be useful in the future if we will decide to display more stuff in our ui
@@ -8590,18 +8509,13 @@ class NetworkMessage {
8590
8509
  }
8591
8510
  getMessage() {
8592
8511
  const { reqHs, resHs } = this.writeHeaders();
8593
- const reqBody = this.method === 'GET'
8594
- ? JSON.stringify(sanitizeObject(this.getData)) : filterBody(this.requestData);
8595
8512
  const request = {
8596
- headers: filterHeaders(reqHs),
8597
- body: reqBody,
8598
- };
8599
- const response = {
8600
- headers: filterHeaders(resHs),
8601
- body: filterBody(this.response)
8513
+ headers: reqHs,
8514
+ body: this.method === 'GET' ? JSON.stringify(this.getData) : this.requestData,
8602
8515
  };
8516
+ const response = { headers: resHs, body: this.response };
8603
8517
  const messageInfo = this.sanitize({
8604
- url: tryFilterUrl(this.url),
8518
+ url: this.url,
8605
8519
  method: this.method,
8606
8520
  status: this.status,
8607
8521
  request,
@@ -8917,10 +8831,9 @@ class ResponseProxyHandler {
8917
8831
  if (typeof this.resp.body.getReader !== 'function') {
8918
8832
  return;
8919
8833
  }
8920
- const clonedResp = this.resp.clone();
8921
- const _getReader = clonedResp.body.getReader;
8834
+ const _getReader = this.resp.body.getReader;
8922
8835
  // @ts-ignore
8923
- clonedResp.body.getReader = () => {
8836
+ this.resp.body.getReader = () => {
8924
8837
  const reader = _getReader.apply(this.resp.body);
8925
8838
  // when readyState is already 4,
8926
8839
  // it's not a chunked stream, or it had already been read.
@@ -9762,7 +9675,7 @@ class API {
9762
9675
  this.signalStartIssue = (reason, missingApi) => {
9763
9676
  const doNotTrack = this.checkDoNotTrack();
9764
9677
  console.log("Tracker couldn't start due to:", JSON.stringify({
9765
- trackerVersion: '16.2.2-beta.19',
9678
+ trackerVersion: '16.3.0-beta.1',
9766
9679
  projectKey: this.options.projectKey,
9767
9680
  doNotTrack,
9768
9681
  reason: missingApi.length ? `missing api: ${missingApi.join(',')}` : reason,
@@ -9861,7 +9774,7 @@ class API {
9861
9774
  Mouse(app, options.mouse);
9862
9775
  // inside iframe, we ignore viewport scroll
9863
9776
  Scroll(app, this.crossdomainMode);
9864
- CSSRules(app, options);
9777
+ CSSRules(app, options.css);
9865
9778
  ConstructedStyleSheets(app);
9866
9779
  Console(app, options);
9867
9780
  Exception(app, options);