@sentry/replay 7.62.0 → 7.63.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
@@ -3496,6 +3496,14 @@ class ClickDetector {
3496
3496
  clickCount: 0,
3497
3497
  node,
3498
3498
  };
3499
+
3500
+ // If there was a click in the last 1s on the same element, ignore it - only keep a single reference per second
3501
+ if (
3502
+ this._clicks.some(click => click.node === newClick.node && Math.abs(click.timestamp - newClick.timestamp) < 1)
3503
+ ) {
3504
+ return;
3505
+ }
3506
+
3499
3507
  this._clicks.push(newClick);
3500
3508
 
3501
3509
  // If this is the first new click, set a timeout to check for multi clicks
@@ -4434,13 +4442,15 @@ class EventBufferProxy {
4434
4442
 
4435
4443
  /** Switch the used buffer to the compression worker. */
4436
4444
  async _switchToCompressionWorker() {
4437
- const { events } = this._fallback;
4445
+ const { events, hasCheckout } = this._fallback;
4438
4446
 
4439
4447
  const addEventPromises = [];
4440
4448
  for (const event of events) {
4441
4449
  addEventPromises.push(this._compression.addEvent(event));
4442
4450
  }
4443
4451
 
4452
+ this._compression.hasCheckout = hasCheckout;
4453
+
4444
4454
  // We switch over to the new buffer immediately - any further events will be added
4445
4455
  // after the previously buffered ones
4446
4456
  this._used = this._compression;
@@ -4727,9 +4737,21 @@ async function addEvent(
4727
4737
  return null;
4728
4738
  }
4729
4739
 
4740
+ // Throw out events that are +60min from the initial timestamp
4741
+ if (timestampInMs > replay.getContext().initialTimestamp + replay.timeouts.maxSessionLife) {
4742
+ logInfo(
4743
+ `[Replay] Skipping event with timestamp ${timestampInMs} because it is after maxSessionLife`,
4744
+ replay.getOptions()._experiments.traceInternals,
4745
+ );
4746
+ return null;
4747
+ }
4748
+
4730
4749
  try {
4731
4750
  if (isCheckout && replay.recordingMode === 'buffer') {
4732
4751
  replay.eventBuffer.clear();
4752
+ }
4753
+
4754
+ if (isCheckout) {
4733
4755
  replay.eventBuffer.hasCheckout = true;
4734
4756
  }
4735
4757
 
@@ -4746,7 +4768,7 @@ async function addEvent(
4746
4768
  const reason = error && error instanceof EventBufferSizeExceededError ? 'addEventSizeExceeded' : 'addEvent';
4747
4769
 
4748
4770
  (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.error(error);
4749
- await replay.stop(reason);
4771
+ await replay.stop({ reason });
4750
4772
 
4751
4773
  const client = getCurrentHub().getClient();
4752
4774
 
@@ -7424,7 +7446,7 @@ class ReplayContainer {
7424
7446
  * Currently, this needs to be manually called (e.g. for tests). Sentry SDK
7425
7447
  * does not support a teardown
7426
7448
  */
7427
- async stop(reason) {
7449
+ async stop({ forceFlush = false, reason } = {}) {
7428
7450
  if (!this._isEnabled) {
7429
7451
  return;
7430
7452
  }
@@ -7444,7 +7466,7 @@ class ReplayContainer {
7444
7466
  this._debouncedFlush.cancel();
7445
7467
  // See comment above re: `_isEnabled`, we "force" a flush, ignoring the
7446
7468
  // `_isEnabled` state of the plugin since it was disabled above.
7447
- if (this.recordingMode === 'session') {
7469
+ if (forceFlush) {
7448
7470
  await this._flush({ force: true });
7449
7471
  }
7450
7472
 
@@ -7833,7 +7855,7 @@ class ReplayContainer {
7833
7855
  this.session = session;
7834
7856
 
7835
7857
  if (!this.session.sampled) {
7836
- void this.stop('session not refreshed');
7858
+ void this.stop({ reason: 'session not refreshed' });
7837
7859
  return false;
7838
7860
  }
7839
7861
 
@@ -8123,6 +8145,15 @@ class ReplayContainer {
8123
8145
  // Note this empties the event buffer regardless of outcome of sending replay
8124
8146
  const recordingData = await this.eventBuffer.finish();
8125
8147
 
8148
+ const timestamp = Date.now();
8149
+
8150
+ // Check total duration again, to avoid sending outdated stuff
8151
+ // We leave 30s wiggle room to accomodate late flushing etc.
8152
+ // This _could_ happen when the browser is suspended during flushing, in which case we just want to stop
8153
+ if (timestamp - this._context.initialTimestamp > this.timeouts.maxSessionLife + 30000) {
8154
+ throw new Error('Session is too long, not sending replay');
8155
+ }
8156
+
8126
8157
  // NOTE: Copy values from instance members, as it's possible they could
8127
8158
  // change before the flush finishes.
8128
8159
  const replayId = this.session.id;
@@ -8138,7 +8169,7 @@ class ReplayContainer {
8138
8169
  eventContext,
8139
8170
  session: this.session,
8140
8171
  options: this.getOptions(),
8141
- timestamp: Date.now(),
8172
+ timestamp,
8142
8173
  });
8143
8174
  } catch (err) {
8144
8175
  this._handleException(err);
@@ -8146,7 +8177,7 @@ class ReplayContainer {
8146
8177
  // This means we retried 3 times and all of them failed,
8147
8178
  // or we ran into a problem we don't want to retry, like rate limiting.
8148
8179
  // In this case, we want to completely stop the replay - otherwise, we may get inconsistent segments
8149
- void this.stop('sendReplay');
8180
+ void this.stop({ reason: 'sendReplay' });
8150
8181
 
8151
8182
  const client = getCurrentHub().getClient();
8152
8183
 
@@ -8265,7 +8296,7 @@ class ReplayContainer {
8265
8296
 
8266
8297
  // Stop replay if over the mutation limit
8267
8298
  if (overMutationLimit) {
8268
- void this.stop('mutationLimit');
8299
+ void this.stop({ reason: 'mutationLimit', forceFlush: this.recordingMode === 'session' });
8269
8300
  return false;
8270
8301
  }
8271
8302
 
@@ -8616,7 +8647,7 @@ Sentry.init({ replaysOnErrorSampleRate: ${errorSampleRate} })`,
8616
8647
  return Promise.resolve();
8617
8648
  }
8618
8649
 
8619
- return this._replay.stop();
8650
+ return this._replay.stop({ forceFlush: this._replay.recordingMode === 'session' });
8620
8651
  }
8621
8652
 
8622
8653
  /**