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