@sentry/replay 7.53.0 → 7.54.0

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/esm/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { getCurrentHub, addBreadcrumb, addGlobalEventProcessor, prepareEvent, setContext, captureException } from '@sentry/core';
2
- import { GLOBAL_OBJ, logger, normalize, htmlTreeAsString, uuid4, SENTRY_XHR_DATA_KEY, dropUndefinedKeys, stringMatchesSomePattern, addInstrumentationHandler, browserPerformanceTimeOrigin, createEnvelope, createEventEnvelopeHeaders, getSdkMetadataForEnvelopeHeader, isNodeEnv } from '@sentry/utils';
1
+ import { getCurrentHub, addGlobalEventProcessor, prepareEvent, setContext, captureException } from '@sentry/core';
2
+ import { GLOBAL_OBJ, normalize, htmlTreeAsString, logger, uuid4, SENTRY_XHR_DATA_KEY, dropUndefinedKeys, stringMatchesSomePattern, addInstrumentationHandler, browserPerformanceTimeOrigin, createEnvelope, createEventEnvelopeHeaders, getSdkMetadataForEnvelopeHeader, isNodeEnv } from '@sentry/utils';
3
3
 
4
4
  // exporting a separate copy of `WINDOW` rather than exporting the one from `@sentry/browser`
5
5
  // prevents the browser package from being bundled in the CDN bundle, and avoids a
@@ -1133,7 +1133,7 @@ if (typeof window !== 'undefined' && window.Proxy && window.Reflect) {
1133
1133
  },
1134
1134
  });
1135
1135
  }
1136
- function throttle(func, wait, options = {}) {
1136
+ function throttle$1(func, wait, options = {}) {
1137
1137
  let timeout = null;
1138
1138
  let previous = 0;
1139
1139
  return function (arg) {
@@ -1868,7 +1868,7 @@ function initMoveObserver({ mousemoveCb, sampling, doc, mirror, }) {
1868
1868
  : 500;
1869
1869
  let positions = [];
1870
1870
  let timeBaseline;
1871
- const wrappedCb = throttle((source) => {
1871
+ const wrappedCb = throttle$1((source) => {
1872
1872
  const totalOffset = Date.now() - timeBaseline;
1873
1873
  callbackWrapper(mousemoveCb)(positions.map((p) => {
1874
1874
  p.timeOffset -= totalOffset;
@@ -1877,7 +1877,7 @@ function initMoveObserver({ mousemoveCb, sampling, doc, mirror, }) {
1877
1877
  positions = [];
1878
1878
  timeBaseline = null;
1879
1879
  }, callbackThreshold);
1880
- const updatePosition = throttle((evt) => {
1880
+ const updatePosition = throttle$1((evt) => {
1881
1881
  const target = getEventTarget(evt);
1882
1882
  const { clientX, clientY } = isTouchEvent(evt)
1883
1883
  ? evt.changedTouches[0]
@@ -1951,7 +1951,7 @@ function initMouseInteractionObserver({ mouseInteractionCb, doc, mirror, blockCl
1951
1951
  });
1952
1952
  }
1953
1953
  function initScrollObserver({ scrollCb, doc, mirror, blockClass, blockSelector, unblockSelector, sampling, }) {
1954
- const updatePosition = throttle((evt) => {
1954
+ const updatePosition = throttle$1((evt) => {
1955
1955
  const target = getEventTarget(evt);
1956
1956
  if (!target ||
1957
1957
  isBlocked(target, blockClass, blockSelector, unblockSelector)) {
@@ -1979,7 +1979,7 @@ function initScrollObserver({ scrollCb, doc, mirror, blockClass, blockSelector,
1979
1979
  function initViewportResizeObserver({ viewportResizeCb, }) {
1980
1980
  let lastH = -1;
1981
1981
  let lastW = -1;
1982
- const updateDimension = throttle(() => {
1982
+ const updateDimension = throttle$1(() => {
1983
1983
  const height = getWindowHeight();
1984
1984
  const width = getWindowWidth();
1985
1985
  if (lastH !== height || lastW !== width) {
@@ -2268,7 +2268,7 @@ function initStyleDeclarationObserver({ styleDeclarationCb, mirror }, { win }) {
2268
2268
  });
2269
2269
  }
2270
2270
  function initMediaInteractionObserver({ mediaInteractionCb, blockClass, blockSelector, unblockSelector, mirror, sampling, }) {
2271
- const handler = (type) => throttle(callbackWrapper((event) => {
2271
+ const handler = (type) => throttle$1(callbackWrapper((event) => {
2272
2272
  const target = getEventTarget(event);
2273
2273
  if (!target ||
2274
2274
  isBlocked(target, blockClass, blockSelector, unblockSelector)) {
@@ -3285,89 +3285,6 @@ var NodeType;
3285
3285
  NodeType[NodeType["Comment"] = 5] = "Comment";
3286
3286
  })(NodeType || (NodeType = {}));
3287
3287
 
3288
- /* eslint-disable @typescript-eslint/naming-convention */
3289
-
3290
- var EventType; (function (EventType) {
3291
- const DomContentLoaded = 0; EventType[EventType["DomContentLoaded"] = DomContentLoaded] = "DomContentLoaded";
3292
- const Load = 1; EventType[EventType["Load"] = Load] = "Load";
3293
- const FullSnapshot = 2; EventType[EventType["FullSnapshot"] = FullSnapshot] = "FullSnapshot";
3294
- const IncrementalSnapshot = 3; EventType[EventType["IncrementalSnapshot"] = IncrementalSnapshot] = "IncrementalSnapshot";
3295
- const Meta = 4; EventType[EventType["Meta"] = Meta] = "Meta";
3296
- const Custom = 5; EventType[EventType["Custom"] = Custom] = "Custom";
3297
- const Plugin = 6; EventType[EventType["Plugin"] = Plugin] = "Plugin";
3298
- })(EventType || (EventType = {}));
3299
-
3300
- /**
3301
- * This is a partial copy of rrweb's eventWithTime type which only contains the properties
3302
- * we specifcally need in the SDK.
3303
- */
3304
-
3305
- /**
3306
- * Converts a timestamp to ms, if it was in s, or keeps it as ms.
3307
- */
3308
- function timestampToMs(timestamp) {
3309
- const isMs = timestamp > 9999999999;
3310
- return isMs ? timestamp : timestamp * 1000;
3311
- }
3312
-
3313
- /**
3314
- * Add an event to the event buffer.
3315
- * `isCheckout` is true if this is either the very first event, or an event triggered by `checkoutEveryNms`.
3316
- */
3317
- async function addEvent(
3318
- replay,
3319
- event,
3320
- isCheckout,
3321
- ) {
3322
- if (!replay.eventBuffer) {
3323
- // This implies that `_isEnabled` is false
3324
- return null;
3325
- }
3326
-
3327
- if (replay.isPaused()) {
3328
- // Do not add to event buffer when recording is paused
3329
- return null;
3330
- }
3331
-
3332
- const timestampInMs = timestampToMs(event.timestamp);
3333
-
3334
- // Throw out events that happen more than 5 minutes ago. This can happen if
3335
- // page has been left open and idle for a long period of time and user
3336
- // comes back to trigger a new session. The performance entries rely on
3337
- // `performance.timeOrigin`, which is when the page first opened.
3338
- if (timestampInMs + replay.timeouts.sessionIdlePause < Date.now()) {
3339
- return null;
3340
- }
3341
-
3342
- try {
3343
- if (isCheckout) {
3344
- replay.eventBuffer.clear();
3345
- }
3346
-
3347
- const replayOptions = replay.getOptions();
3348
-
3349
- const eventAfterPossibleCallback =
3350
- typeof replayOptions.beforeAddRecordingEvent === 'function' && event.type === EventType.Custom
3351
- ? replayOptions.beforeAddRecordingEvent(event)
3352
- : event;
3353
-
3354
- if (!eventAfterPossibleCallback) {
3355
- return;
3356
- }
3357
-
3358
- return await replay.eventBuffer.addEvent(eventAfterPossibleCallback);
3359
- } catch (error) {
3360
- (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.error(error);
3361
- await replay.stop('addEvent');
3362
-
3363
- const client = getCurrentHub().getClient();
3364
-
3365
- if (client) {
3366
- client.recordDroppedEvent('internal_sdk_error', 'replay');
3367
- }
3368
- }
3369
- }
3370
-
3371
3288
  /**
3372
3289
  * Add a breadcrumb event to replay.
3373
3290
  */
@@ -3383,7 +3300,7 @@ function addBreadcrumbEvent(replay, breadcrumb) {
3383
3300
  }
3384
3301
 
3385
3302
  replay.addUpdate(() => {
3386
- void addEvent(replay, {
3303
+ void replay.throttledAddEvent({
3387
3304
  type: EventType$1.Custom,
3388
3305
  // TODO: We were converting from ms to seconds for breadcrumbs, spans,
3389
3306
  // but maybe we should just keep them as milliseconds
@@ -3724,7 +3641,7 @@ function getKeyboardBreadcrumb(event) {
3724
3641
  const { metaKey, shiftKey, ctrlKey, altKey, key, target } = event;
3725
3642
 
3726
3643
  // never capture for input fields
3727
- if (!target || isInputElement(target )) {
3644
+ if (!target || isInputElement(target ) || !key) {
3728
3645
  return null;
3729
3646
  }
3730
3647
 
@@ -3896,6 +3813,14 @@ function t(t){let e=t.length;for(;--e>=0;)t[e]=0}const e=new Uint8Array([0,0,0,0
3896
3813
 
3897
3814
  function e(){const e=new Blob([r]);return URL.createObjectURL(e)}
3898
3815
 
3816
+ /**
3817
+ * Converts a timestamp to ms, if it was in s, or keeps it as ms.
3818
+ */
3819
+ function timestampToMs(timestamp) {
3820
+ const isMs = timestamp > 9999999999;
3821
+ return isMs ? timestamp : timestamp * 1000;
3822
+ }
3823
+
3899
3824
  /**
3900
3825
  * A basic event buffer that does not do any compression.
3901
3826
  * Used as fallback if the compression worker cannot be loaded or is disabled.
@@ -4473,6 +4398,81 @@ function getSession({
4473
4398
  return { type: 'new', session: newSession };
4474
4399
  }
4475
4400
 
4401
+ /* eslint-disable @typescript-eslint/naming-convention */
4402
+
4403
+ var EventType; (function (EventType) {
4404
+ const DomContentLoaded = 0; EventType[EventType["DomContentLoaded"] = DomContentLoaded] = "DomContentLoaded";
4405
+ const Load = 1; EventType[EventType["Load"] = Load] = "Load";
4406
+ const FullSnapshot = 2; EventType[EventType["FullSnapshot"] = FullSnapshot] = "FullSnapshot";
4407
+ const IncrementalSnapshot = 3; EventType[EventType["IncrementalSnapshot"] = IncrementalSnapshot] = "IncrementalSnapshot";
4408
+ const Meta = 4; EventType[EventType["Meta"] = Meta] = "Meta";
4409
+ const Custom = 5; EventType[EventType["Custom"] = Custom] = "Custom";
4410
+ const Plugin = 6; EventType[EventType["Plugin"] = Plugin] = "Plugin";
4411
+ })(EventType || (EventType = {}));
4412
+
4413
+ /**
4414
+ * This is a partial copy of rrweb's eventWithTime type which only contains the properties
4415
+ * we specifcally need in the SDK.
4416
+ */
4417
+
4418
+ /**
4419
+ * Add an event to the event buffer.
4420
+ * `isCheckout` is true if this is either the very first event, or an event triggered by `checkoutEveryNms`.
4421
+ */
4422
+ async function addEvent(
4423
+ replay,
4424
+ event,
4425
+ isCheckout,
4426
+ ) {
4427
+ if (!replay.eventBuffer) {
4428
+ // This implies that `_isEnabled` is false
4429
+ return null;
4430
+ }
4431
+
4432
+ if (replay.isPaused()) {
4433
+ // Do not add to event buffer when recording is paused
4434
+ return null;
4435
+ }
4436
+
4437
+ const timestampInMs = timestampToMs(event.timestamp);
4438
+
4439
+ // Throw out events that happen more than 5 minutes ago. This can happen if
4440
+ // page has been left open and idle for a long period of time and user
4441
+ // comes back to trigger a new session. The performance entries rely on
4442
+ // `performance.timeOrigin`, which is when the page first opened.
4443
+ if (timestampInMs + replay.timeouts.sessionIdlePause < Date.now()) {
4444
+ return null;
4445
+ }
4446
+
4447
+ try {
4448
+ if (isCheckout) {
4449
+ replay.eventBuffer.clear();
4450
+ }
4451
+
4452
+ const replayOptions = replay.getOptions();
4453
+
4454
+ const eventAfterPossibleCallback =
4455
+ typeof replayOptions.beforeAddRecordingEvent === 'function' && event.type === EventType.Custom
4456
+ ? replayOptions.beforeAddRecordingEvent(event)
4457
+ : event;
4458
+
4459
+ if (!eventAfterPossibleCallback) {
4460
+ return;
4461
+ }
4462
+
4463
+ return await replay.eventBuffer.addEvent(eventAfterPossibleCallback);
4464
+ } catch (error) {
4465
+ (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.error(error);
4466
+ await replay.stop('addEvent');
4467
+
4468
+ const client = getCurrentHub().getClient();
4469
+
4470
+ if (client) {
4471
+ client.recordDroppedEvent('internal_sdk_error', 'replay');
4472
+ }
4473
+ }
4474
+ }
4475
+
4476
4476
  /** If the event is an error event */
4477
4477
  function isErrorEvent(event) {
4478
4478
  return !event.type;
@@ -4647,13 +4647,6 @@ function handleGlobalEventListener(
4647
4647
  event.tags = { ...event.tags, replayId: replay.getSessionId() };
4648
4648
  }
4649
4649
 
4650
- if ((typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && replay.getOptions()._experiments.traceInternals && isErrorEvent(event)) {
4651
- const exc = getEventExceptionValues(event);
4652
- addInternalBreadcrumb({
4653
- message: `Tagging event (${event.event_id}) - ${event.message} - ${exc.type}: ${exc.value}`,
4654
- });
4655
- }
4656
-
4657
4650
  // In cases where a custom client is used that does not support the new hooks (yet),
4658
4651
  // we manually call this hook method here
4659
4652
  if (afterSendHandler) {
@@ -4665,34 +4658,15 @@ function handleGlobalEventListener(
4665
4658
  };
4666
4659
  }
4667
4660
 
4668
- function addInternalBreadcrumb(arg) {
4669
- const { category, level, message, ...rest } = arg;
4670
-
4671
- addBreadcrumb({
4672
- category: category || 'console',
4673
- level: level || 'debug',
4674
- message: `[debug]: ${message}`,
4675
- ...rest,
4676
- });
4677
- }
4678
-
4679
- function getEventExceptionValues(event) {
4680
- return {
4681
- type: 'Unknown',
4682
- value: 'n/a',
4683
- ...(event.exception && event.exception.values && event.exception.values[0]),
4684
- };
4685
- }
4686
-
4687
4661
  /**
4688
- * Create a "span" for each performance entry. The parent transaction is `this.replayEvent`.
4662
+ * Create a "span" for each performance entry.
4689
4663
  */
4690
4664
  function createPerformanceSpans(
4691
4665
  replay,
4692
4666
  entries,
4693
4667
  ) {
4694
- return entries.map(({ type, start, end, name, data }) =>
4695
- addEvent(replay, {
4668
+ return entries.map(({ type, start, end, name, data }) => {
4669
+ const response = replay.throttledAddEvent({
4696
4670
  type: EventType$1.Custom,
4697
4671
  timestamp: start,
4698
4672
  data: {
@@ -4705,8 +4679,11 @@ function createPerformanceSpans(
4705
4679
  data,
4706
4680
  },
4707
4681
  },
4708
- }),
4709
- );
4682
+ });
4683
+
4684
+ // If response is a string, it means its either THROTTLED or SKIPPED
4685
+ return typeof response === 'string' ? Promise.resolve(null) : response;
4686
+ });
4710
4687
  }
4711
4688
 
4712
4689
  function handleHistory(handlerData) {
@@ -6055,6 +6032,16 @@ function addGlobalListeners(replay) {
6055
6032
  dsc.replay_id = replayId;
6056
6033
  }
6057
6034
  });
6035
+
6036
+ client.on('startTransaction', transaction => {
6037
+ replay.lastTransaction = transaction;
6038
+ });
6039
+
6040
+ // We may be missing the initial startTransaction due to timing issues,
6041
+ // so we capture it on finish again.
6042
+ client.on('finishTransaction', transaction => {
6043
+ replay.lastTransaction = transaction;
6044
+ });
6058
6045
  }
6059
6046
  }
6060
6047
 
@@ -6778,6 +6765,62 @@ async function sendReplay(
6778
6765
  }
6779
6766
  }
6780
6767
 
6768
+ const THROTTLED = '__THROTTLED';
6769
+ const SKIPPED = '__SKIPPED';
6770
+
6771
+ /**
6772
+ * Create a throttled function off a given function.
6773
+ * When calling the throttled function, it will call the original function only
6774
+ * if it hasn't been called more than `maxCount` times in the last `durationSeconds`.
6775
+ *
6776
+ * Returns `THROTTLED` if throttled for the first time, after that `SKIPPED`,
6777
+ * or else the return value of the original function.
6778
+ */
6779
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
6780
+ function throttle(
6781
+ fn,
6782
+ maxCount,
6783
+ durationSeconds,
6784
+ ) {
6785
+ const counter = new Map();
6786
+
6787
+ const _cleanup = (now) => {
6788
+ const threshold = now - durationSeconds;
6789
+ counter.forEach((_value, key) => {
6790
+ if (key < threshold) {
6791
+ counter.delete(key);
6792
+ }
6793
+ });
6794
+ };
6795
+
6796
+ const _getTotalCount = () => {
6797
+ return [...counter.values()].reduce((a, b) => a + b, 0);
6798
+ };
6799
+
6800
+ let isThrottled = false;
6801
+
6802
+ return (...rest) => {
6803
+ // Date in second-precision, which we use as basis for the throttling
6804
+ const now = Math.floor(Date.now() / 1000);
6805
+
6806
+ // First, make sure to delete any old entries
6807
+ _cleanup(now);
6808
+
6809
+ // If already over limit, do nothing
6810
+ if (_getTotalCount() >= maxCount) {
6811
+ const wasThrottled = isThrottled;
6812
+ isThrottled = true;
6813
+ return wasThrottled ? SKIPPED : THROTTLED;
6814
+ }
6815
+
6816
+ isThrottled = false;
6817
+ const count = counter.get(now) || 0;
6818
+ counter.set(now, count + 1);
6819
+
6820
+ return fn(...rest);
6821
+ };
6822
+ }
6823
+
6781
6824
  /* eslint-disable max-lines */ // TODO: We might want to split this file up
6782
6825
 
6783
6826
  /**
@@ -6800,6 +6843,11 @@ class ReplayContainer {
6800
6843
  */
6801
6844
  __init3() {this.recordingMode = 'session';}
6802
6845
 
6846
+ /**
6847
+ * The current or last active transcation.
6848
+ * This is only available when performance is enabled.
6849
+ */
6850
+
6803
6851
  /**
6804
6852
  * These are here so we can overwrite them in tests etc.
6805
6853
  * @hidden
@@ -6866,6 +6914,14 @@ class ReplayContainer {
6866
6914
  this._debouncedFlush = debounce(() => this._flush(), this._options.flushMinDelay, {
6867
6915
  maxWait: this._options.flushMaxDelay,
6868
6916
  });
6917
+
6918
+ this._throttledAddEvent = throttle(
6919
+ (event, isCheckout) => addEvent(this, event, isCheckout),
6920
+ // Max 300 events...
6921
+ 300,
6922
+ // ... per 5s
6923
+ 5,
6924
+ );
6869
6925
  }
6870
6926
 
6871
6927
  /** Get the event context. */
@@ -7057,7 +7113,9 @@ class ReplayContainer {
7057
7113
  this._debouncedFlush.cancel();
7058
7114
  // See comment above re: `_isEnabled`, we "force" a flush, ignoring the
7059
7115
  // `_isEnabled` state of the plugin since it was disabled above.
7060
- await this._flush({ force: true });
7116
+ if (this.recordingMode === 'session') {
7117
+ await this._flush({ force: true });
7118
+ }
7061
7119
 
7062
7120
  // After flush, destroy event buffer
7063
7121
  this.eventBuffer && this.eventBuffer.destroy();
@@ -7293,6 +7351,52 @@ class ReplayContainer {
7293
7351
  this._context.urls.push(url);
7294
7352
  }
7295
7353
 
7354
+ /**
7355
+ * Add a breadcrumb event, that may be throttled.
7356
+ * If it was throttled, we add a custom breadcrumb to indicate that.
7357
+ */
7358
+ throttledAddEvent(
7359
+ event,
7360
+ isCheckout,
7361
+ ) {
7362
+ const res = this._throttledAddEvent(event, isCheckout);
7363
+
7364
+ // If this is THROTTLED, it means we have throttled the event for the first time
7365
+ // In this case, we want to add a breadcrumb indicating that something was skipped
7366
+ if (res === THROTTLED) {
7367
+ const breadcrumb = createBreadcrumb({
7368
+ category: 'replay.throttled',
7369
+ });
7370
+
7371
+ this.addUpdate(() => {
7372
+ void addEvent(this, {
7373
+ type: EventType$1.Custom,
7374
+ timestamp: breadcrumb.timestamp || 0,
7375
+ data: {
7376
+ tag: 'breadcrumb',
7377
+ payload: breadcrumb,
7378
+ metric: true,
7379
+ },
7380
+ });
7381
+ });
7382
+ }
7383
+
7384
+ return res;
7385
+ }
7386
+
7387
+ /**
7388
+ * This will get the parametrized route name of the current page.
7389
+ * This is only available if performance is enabled, and if an instrumented router is used.
7390
+ */
7391
+ getCurrentRoute() {
7392
+ const lastTransaction = this.lastTransaction || getCurrentHub().getScope().getTransaction();
7393
+ if (!lastTransaction || !['route', 'custom'].includes(lastTransaction.metadata.source)) {
7394
+ return undefined;
7395
+ }
7396
+
7397
+ return lastTransaction.name;
7398
+ }
7399
+
7296
7400
  /**
7297
7401
  * Initialize and start all listeners to varying events (DOM,
7298
7402
  * Performance Observer, Recording, Sentry SDK, etc)
@@ -7531,7 +7635,7 @@ class ReplayContainer {
7531
7635
  */
7532
7636
  _createCustomBreadcrumb(breadcrumb) {
7533
7637
  this.addUpdate(() => {
7534
- void addEvent(this, {
7638
+ void this.throttledAddEvent({
7535
7639
  type: EventType$1.Custom,
7536
7640
  timestamp: breadcrumb.timestamp || 0,
7537
7641
  data: {
@@ -7730,8 +7834,8 @@ class ReplayContainer {
7730
7834
  __init18() {this._onMutationHandler = (mutations) => {
7731
7835
  const count = mutations.length;
7732
7836
 
7733
- const mutationLimit = this._options._experiments.mutationLimit || 0;
7734
- const mutationBreadcrumbLimit = this._options._experiments.mutationBreadcrumbLimit || 1000;
7837
+ const mutationLimit = this._options.mutationLimit;
7838
+ const mutationBreadcrumbLimit = this._options.mutationBreadcrumbLimit;
7735
7839
  const overMutationLimit = mutationLimit && count > mutationLimit;
7736
7840
 
7737
7841
  // Create a breadcrumb if a lot of mutations happen at the same time
@@ -7741,15 +7845,15 @@ class ReplayContainer {
7741
7845
  category: 'replay.mutations',
7742
7846
  data: {
7743
7847
  count,
7848
+ limit: overMutationLimit,
7744
7849
  },
7745
7850
  });
7746
7851
  this._createCustomBreadcrumb(breadcrumb);
7747
7852
  }
7748
7853
 
7854
+ // Stop replay if over the mutation limit
7749
7855
  if (overMutationLimit) {
7750
- // We want to skip doing an incremental snapshot if there are too many mutations
7751
- // Instead, we do a full snapshot
7752
- this._triggerFullSnapshot(false);
7856
+ void this.stop('mutationLimit');
7753
7857
  return false;
7754
7858
  }
7755
7859
 
@@ -7903,6 +8007,9 @@ class Replay {
7903
8007
  maskAllInputs = true,
7904
8008
  blockAllMedia = true,
7905
8009
 
8010
+ mutationBreadcrumbLimit = 750,
8011
+ mutationLimit = 10000,
8012
+
7906
8013
  networkDetailAllowUrls = [],
7907
8014
  networkCaptureBodies = true,
7908
8015
  networkRequestHeaders = [],
@@ -7970,6 +8077,8 @@ class Replay {
7970
8077
  blockAllMedia,
7971
8078
  maskAllInputs,
7972
8079
  maskAllText,
8080
+ mutationBreadcrumbLimit,
8081
+ mutationLimit,
7973
8082
  networkDetailAllowUrls,
7974
8083
  networkCaptureBodies,
7975
8084
  networkRequestHeaders: _getMergedNetworkHeaders(networkRequestHeaders),