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