@sentry/replay 7.68.0 → 7.69.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
@@ -4560,7 +4560,6 @@ function makeSession(session) {
4560
4560
  const lastActivity = session.lastActivity || now;
4561
4561
  const segmentId = session.segmentId || 0;
4562
4562
  const sampled = session.sampled;
4563
- const shouldRefresh = typeof session.shouldRefresh === 'boolean' ? session.shouldRefresh : true;
4564
4563
  const previousSessionId = session.previousSessionId;
4565
4564
 
4566
4565
  return {
@@ -4569,7 +4568,6 @@ function makeSession(session) {
4569
4568
  lastActivity,
4570
4569
  segmentId,
4571
4570
  sampled,
4572
- shouldRefresh,
4573
4571
  previousSessionId,
4574
4572
  };
4575
4573
  }
@@ -4671,47 +4669,22 @@ function isSessionExpired(
4671
4669
  );
4672
4670
  }
4673
4671
 
4674
- /**
4675
- * Check a session, and either return it or a refreshed version of it.
4676
- * The refreshed version may be unsampled.
4677
- * You can check if the session has changed by comparing the session IDs.
4678
- */
4679
- function maybeRefreshSession(
4672
+ /** If the session should be refreshed or not. */
4673
+ function shouldRefreshSession(
4680
4674
  session,
4681
- {
4682
- traceInternals,
4683
- maxReplayDuration,
4684
- sessionIdleExpire,
4685
- }
4686
-
4687
- ,
4688
- sessionOptions,
4675
+ { sessionIdleExpire, maxReplayDuration },
4689
4676
  ) {
4690
4677
  // If not expired, all good, just keep the session
4691
4678
  if (!isSessionExpired(session, { sessionIdleExpire, maxReplayDuration })) {
4692
- return session;
4693
- }
4694
-
4695
- const isBuffering = session.sampled === 'buffer';
4696
-
4697
- // If we are buffering & the session may be refreshed, just return it
4698
- if (isBuffering && session.shouldRefresh) {
4699
- return session;
4679
+ return false;
4700
4680
  }
4701
4681
 
4702
- // If we are buffering & the session may not be refreshed (=it was converted to session previously already)
4703
- // We return an unsampled new session
4704
- if (isBuffering) {
4705
- logInfoNextTick('[Replay] Session should not be refreshed', traceInternals);
4706
- return makeSession({ sampled: false });
4682
+ // If we are buffering & haven't ever flushed yet, always continue
4683
+ if (session.sampled === 'buffer' && session.segmentId === 0) {
4684
+ return false;
4707
4685
  }
4708
4686
 
4709
- // Else, we are not buffering, and the session is expired, so we need to create a new one
4710
- logInfoNextTick('[Replay] Session has expired, creating new one...', traceInternals);
4711
-
4712
- const newSession = createSession(sessionOptions, { previousSessionId: session.id });
4713
-
4714
- return newSession;
4687
+ return true;
4715
4688
  }
4716
4689
 
4717
4690
  /**
@@ -4719,26 +4692,30 @@ function maybeRefreshSession(
4719
4692
  * Returns a session that may be unsampled.
4720
4693
  */
4721
4694
  function loadOrCreateSession(
4722
- currentSession,
4723
4695
  {
4724
4696
  traceInternals,
4725
4697
  sessionIdleExpire,
4726
4698
  maxReplayDuration,
4699
+ previousSessionId,
4727
4700
  }
4728
4701
 
4729
4702
  ,
4730
4703
  sessionOptions,
4731
4704
  ) {
4732
- // If session exists and is passed, use it instead of always hitting session storage
4733
- const existingSession = currentSession || (sessionOptions.stickySession && fetchSession(traceInternals));
4705
+ const existingSession = sessionOptions.stickySession && fetchSession(traceInternals);
4734
4706
 
4735
4707
  // No session exists yet, just create a new one
4736
4708
  if (!existingSession) {
4737
- logInfoNextTick('[Replay] Created new session', traceInternals);
4738
- return createSession(sessionOptions);
4709
+ logInfoNextTick('[Replay] Creating new session', traceInternals);
4710
+ return createSession(sessionOptions, { previousSessionId });
4739
4711
  }
4740
4712
 
4741
- return maybeRefreshSession(existingSession, { sessionIdleExpire, traceInternals, maxReplayDuration }, sessionOptions);
4713
+ if (!shouldRefreshSession(existingSession, { sessionIdleExpire, maxReplayDuration })) {
4714
+ return existingSession;
4715
+ }
4716
+
4717
+ logInfoNextTick('[Replay] Session in sessionStorage is expired, creating new one...');
4718
+ return createSession(sessionOptions, { previousSessionId: existingSession.id });
4742
4719
  }
4743
4720
 
4744
4721
  const ReplayEventTypeCustom = 5;
@@ -4749,39 +4726,46 @@ function isCustomEvent(event) {
4749
4726
 
4750
4727
  /**
4751
4728
  * Add an event to the event buffer.
4729
+ * In contrast to `addEvent`, this does not return a promise & does not wait for the adding of the event to succeed/fail.
4730
+ * Instead this returns `true` if we tried to add the event, else false.
4731
+ * It returns `false` e.g. if we are paused, disabled, or out of the max replay duration.
4732
+ *
4752
4733
  * `isCheckout` is true if this is either the very first event, or an event triggered by `checkoutEveryNms`.
4753
4734
  */
4754
- async function addEvent(
4735
+ function addEventSync(replay, event, isCheckout) {
4736
+ if (!shouldAddEvent(replay, event)) {
4737
+ return false;
4738
+ }
4739
+
4740
+ void _addEvent(replay, event, isCheckout);
4741
+
4742
+ return true;
4743
+ }
4744
+
4745
+ /**
4746
+ * Add an event to the event buffer.
4747
+ * Resolves to `null` if no event was added, else to `void`.
4748
+ *
4749
+ * `isCheckout` is true if this is either the very first event, or an event triggered by `checkoutEveryNms`.
4750
+ */
4751
+ function addEvent(
4755
4752
  replay,
4756
4753
  event,
4757
4754
  isCheckout,
4758
4755
  ) {
4759
- if (!replay.eventBuffer) {
4760
- // This implies that `_isEnabled` is false
4761
- return null;
4762
- }
4763
-
4764
- if (replay.isPaused()) {
4765
- // Do not add to event buffer when recording is paused
4766
- return null;
4756
+ if (!shouldAddEvent(replay, event)) {
4757
+ return Promise.resolve(null);
4767
4758
  }
4768
4759
 
4769
- const timestampInMs = timestampToMs(event.timestamp);
4770
-
4771
- // Throw out events that happen more than 5 minutes ago. This can happen if
4772
- // page has been left open and idle for a long period of time and user
4773
- // comes back to trigger a new session. The performance entries rely on
4774
- // `performance.timeOrigin`, which is when the page first opened.
4775
- if (timestampInMs + replay.timeouts.sessionIdlePause < Date.now()) {
4776
- return null;
4777
- }
4760
+ return _addEvent(replay, event, isCheckout);
4761
+ }
4778
4762
 
4779
- // Throw out events that are +60min from the initial timestamp
4780
- if (timestampInMs > replay.getContext().initialTimestamp + replay.getOptions().maxReplayDuration) {
4781
- logInfo(
4782
- `[Replay] Skipping event with timestamp ${timestampInMs} because it is after maxReplayDuration`,
4783
- replay.getOptions()._experiments.traceInternals,
4784
- );
4763
+ async function _addEvent(
4764
+ replay,
4765
+ event,
4766
+ isCheckout,
4767
+ ) {
4768
+ if (!replay.eventBuffer) {
4785
4769
  return null;
4786
4770
  }
4787
4771
 
@@ -4817,6 +4801,34 @@ async function addEvent(
4817
4801
  }
4818
4802
  }
4819
4803
 
4804
+ /** Exported only for tests. */
4805
+ function shouldAddEvent(replay, event) {
4806
+ if (!replay.eventBuffer || replay.isPaused() || !replay.isEnabled()) {
4807
+ return false;
4808
+ }
4809
+
4810
+ const timestampInMs = timestampToMs(event.timestamp);
4811
+
4812
+ // Throw out events that happen more than 5 minutes ago. This can happen if
4813
+ // page has been left open and idle for a long period of time and user
4814
+ // comes back to trigger a new session. The performance entries rely on
4815
+ // `performance.timeOrigin`, which is when the page first opened.
4816
+ if (timestampInMs + replay.timeouts.sessionIdlePause < Date.now()) {
4817
+ return false;
4818
+ }
4819
+
4820
+ // Throw out events that are +60min from the initial timestamp
4821
+ if (timestampInMs > replay.getContext().initialTimestamp + replay.getOptions().maxReplayDuration) {
4822
+ logInfo(
4823
+ `[Replay] Skipping event with timestamp ${timestampInMs} because it is after maxReplayDuration`,
4824
+ replay.getOptions()._experiments.traceInternals,
4825
+ );
4826
+ return false;
4827
+ }
4828
+
4829
+ return true;
4830
+ }
4831
+
4820
4832
  function maybeApplyCallback(
4821
4833
  event,
4822
4834
  callback,
@@ -4938,7 +4950,7 @@ function isRrwebError(event, hint) {
4938
4950
  return false;
4939
4951
  }
4940
4952
 
4941
- // @ts-ignore this may be set by rrweb when it finds errors
4953
+ // @ts-expect-error this may be set by rrweb when it finds errors
4942
4954
  if (hint.originalException && hint.originalException.__rrweb__) {
4943
4955
  return true;
4944
4956
  }
@@ -5781,7 +5793,7 @@ function getAllowedHeaders(headers, allowedHeaders) {
5781
5793
  function _serializeFormData(formData) {
5782
5794
  // This is a bit simplified, but gives us a decent estimate
5783
5795
  // This converts e.g. { name: 'Anne Smith', age: 13 } to 'name=Anne+Smith&age=13'
5784
- // @ts-ignore passing FormData to URLSearchParams actually works
5796
+ // @ts-expect-error passing FormData to URLSearchParams actually works
5785
5797
  return new URLSearchParams(formData).toString();
5786
5798
  }
5787
5799
 
@@ -6401,9 +6413,7 @@ function addGlobalListeners(replay) {
6401
6413
  const scope = getCurrentHub().getScope();
6402
6414
  const client = getCurrentHub().getClient();
6403
6415
 
6404
- if (scope) {
6405
- scope.addScopeListener(handleScopeListener(replay));
6406
- }
6416
+ scope.addScopeListener(handleScopeListener(replay));
6407
6417
  addInstrumentationHandler('dom', handleDomListener(replay));
6408
6418
  addInstrumentationHandler('history', handleHistorySpanListener(replay));
6409
6419
  handleNetworkBreadcrumbs(replay);
@@ -6449,7 +6459,7 @@ async function addMemoryEntry(replay) {
6449
6459
  try {
6450
6460
  return Promise.all(
6451
6461
  createPerformanceSpans(replay, [
6452
- // @ts-ignore memory doesn't exist on type Performance as the API is non-standard (we check that it exists above)
6462
+ // @ts-expect-error memory doesn't exist on type Performance as the API is non-standard (we check that it exists above)
6453
6463
  createMemoryEntry(WINDOW.performance.memory),
6454
6464
  ]),
6455
6465
  );
@@ -6480,16 +6490,15 @@ function createMemoryEntry(memoryEntry) {
6480
6490
  }
6481
6491
 
6482
6492
  // Map entryType -> function to normalize data for event
6483
- // @ts-ignore TODO: entry type does not fit the create* functions entry type
6484
6493
  const ENTRY_TYPES
6485
6494
 
6486
6495
  = {
6487
- // @ts-ignore TODO: entry type does not fit the create* functions entry type
6496
+ // @ts-expect-error TODO: entry type does not fit the create* functions entry type
6488
6497
  resource: createResourceEntry,
6489
6498
  paint: createPaintEntry,
6490
- // @ts-ignore TODO: entry type does not fit the create* functions entry type
6499
+ // @ts-expect-error TODO: entry type does not fit the create* functions entry type
6491
6500
  navigation: createNavigationEntry,
6492
- // @ts-ignore TODO: entry type does not fit the create* functions entry type
6501
+ // @ts-expect-error TODO: entry type does not fit the create* functions entry type
6493
6502
  ['largest-contentful-paint']: createLargestContentfulPaint,
6494
6503
  };
6495
6504
 
@@ -6740,9 +6749,12 @@ function getHandleRecordingEmit(replay) {
6740
6749
  replay.setInitialState();
6741
6750
  }
6742
6751
 
6743
- // We need to clear existing events on a checkout, otherwise they are
6744
- // incremental event updates and should be appended
6745
- void addEvent(replay, event, isCheckout);
6752
+ // If the event is not added (e.g. due to being paused, disabled, or out of the max replay duration),
6753
+ // Skip all further steps
6754
+ if (!addEventSync(replay, event, isCheckout)) {
6755
+ // Return true to skip scheduling a debounced flush
6756
+ return true;
6757
+ }
6746
6758
 
6747
6759
  // Different behavior for full snapshots (type=2), ignore other event types
6748
6760
  // See https://github.com/rrweb-io/rrweb/blob/d8f9290ca496712aa1e7d472549480c4e7876594/packages/rrweb/src/types.ts#L16
@@ -6756,7 +6768,7 @@ function getHandleRecordingEmit(replay) {
6756
6768
  //
6757
6769
  // `isCheckout` is always true, but want to be explicit that it should
6758
6770
  // only be added for checkouts
6759
- void addSettingsEvent(replay, isCheckout);
6771
+ addSettingsEvent(replay, isCheckout);
6760
6772
 
6761
6773
  // If there is a previousSessionId after a full snapshot occurs, then
6762
6774
  // the replay session was started due to session expiration. The new session
@@ -6833,10 +6845,10 @@ function createOptionsEvent(replay) {
6833
6845
  function addSettingsEvent(replay, isCheckout) {
6834
6846
  // Only need to add this event when sending the first segment
6835
6847
  if (!isCheckout || !replay.session || replay.session.segmentId !== 0) {
6836
- return Promise.resolve(null);
6848
+ return;
6837
6849
  }
6838
6850
 
6839
- return addEvent(replay, createOptionsEvent(replay), false);
6851
+ addEventSync(replay, createOptionsEvent(replay), false);
6840
6852
  }
6841
6853
 
6842
6854
  /**
@@ -7045,7 +7057,7 @@ async function sendReplayRequest({
7045
7057
 
7046
7058
  try {
7047
7059
  // In case browsers don't allow this property to be writable
7048
- // @ts-ignore This needs lib es2022 and newer
7060
+ // @ts-expect-error This needs lib es2022 and newer
7049
7061
  error.cause = err;
7050
7062
  } catch (e) {
7051
7063
  // nothing to do
@@ -7116,7 +7128,7 @@ async function sendReplay(
7116
7128
 
7117
7129
  try {
7118
7130
  // In case browsers don't allow this property to be writable
7119
- // @ts-ignore This needs lib es2022 and newer
7131
+ // @ts-expect-error This needs lib es2022 and newer
7120
7132
  error.cause = err;
7121
7133
  } catch (e) {
7122
7134
  // nothing to do
@@ -7333,7 +7345,7 @@ class ReplayContainer {
7333
7345
  * Initializes the plugin based on sampling configuration. Should not be
7334
7346
  * called outside of constructor.
7335
7347
  */
7336
- initializeSampling() {
7348
+ initializeSampling(previousSessionId) {
7337
7349
  const { errorSampleRate, sessionSampleRate } = this._options;
7338
7350
 
7339
7351
  // If neither sample rate is > 0, then do nothing - user will need to call one of
@@ -7344,7 +7356,7 @@ class ReplayContainer {
7344
7356
 
7345
7357
  // Otherwise if there is _any_ sample rate set, try to load an existing
7346
7358
  // session, or create a new one.
7347
- this._initializeSessionForSampling();
7359
+ this._initializeSessionForSampling(previousSessionId);
7348
7360
 
7349
7361
  if (!this.session) {
7350
7362
  // This should not happen, something wrong has occurred
@@ -7389,7 +7401,6 @@ class ReplayContainer {
7389
7401
  logInfoNextTick('[Replay] Starting replay in session mode', this._options._experiments.traceInternals);
7390
7402
 
7391
7403
  const session = loadOrCreateSession(
7392
- this.session,
7393
7404
  {
7394
7405
  maxReplayDuration: this._options.maxReplayDuration,
7395
7406
  sessionIdleExpire: this.timeouts.sessionIdleExpire,
@@ -7420,7 +7431,6 @@ class ReplayContainer {
7420
7431
  logInfoNextTick('[Replay] Starting replay in buffer mode', this._options._experiments.traceInternals);
7421
7432
 
7422
7433
  const session = loadOrCreateSession(
7423
- this.session,
7424
7434
  {
7425
7435
  sessionIdleExpire: this.timeouts.sessionIdleExpire,
7426
7436
  maxReplayDuration: this._options.maxReplayDuration,
@@ -7489,15 +7499,16 @@ class ReplayContainer {
7489
7499
  return;
7490
7500
  }
7491
7501
 
7502
+ // We can't move `_isEnabled` after awaiting a flush, otherwise we can
7503
+ // enter into an infinite loop when `stop()` is called while flushing.
7504
+ this._isEnabled = false;
7505
+
7492
7506
  try {
7493
7507
  logInfo(
7494
7508
  `[Replay] Stopping Replay${reason ? ` triggered by ${reason}` : ''}`,
7495
7509
  this._options._experiments.traceInternals,
7496
7510
  );
7497
7511
 
7498
- // We can't move `_isEnabled` after awaiting a flush, otherwise we can
7499
- // enter into an infinite loop when `stop()` is called while flushing.
7500
- this._isEnabled = false;
7501
7512
  this._removeListeners();
7502
7513
  this.stopRecording();
7503
7514
 
@@ -7591,16 +7602,6 @@ class ReplayContainer {
7591
7602
 
7592
7603
  // Once this session ends, we do not want to refresh it
7593
7604
  if (this.session) {
7594
- this.session.shouldRefresh = false;
7595
-
7596
- // It's possible that the session lifespan is > max session lifespan
7597
- // because we have been buffering beyond max session lifespan (we ignore
7598
- // expiration given that `shouldRefresh` is true). Since we flip
7599
- // `shouldRefresh`, the session could be considered expired due to
7600
- // lifespan, which is not what we want. Update session start date to be
7601
- // the current timestamp, so that session is not considered to be
7602
- // expired. This means that max replay duration can be MAX_REPLAY_DURATION +
7603
- // (length of buffer), which we are ok with.
7604
7605
  this._updateUserActivity(activityTime);
7605
7606
  this._updateSessionActivity(activityTime);
7606
7607
  this._maybeSaveSession();
@@ -7728,8 +7729,6 @@ class ReplayContainer {
7728
7729
  * @hidden
7729
7730
  */
7730
7731
  checkAndHandleExpiredSession() {
7731
- const oldSessionId = this.getSessionId();
7732
-
7733
7732
  // Prevent starting a new session if the last user activity is older than
7734
7733
  // SESSION_IDLE_PAUSE_DURATION. Otherwise non-user activity can trigger a new
7735
7734
  // session+recording. This creates noisy replays that do not have much
@@ -7751,24 +7750,11 @@ class ReplayContainer {
7751
7750
  // --- There is recent user activity --- //
7752
7751
  // This will create a new session if expired, based on expiry length
7753
7752
  if (!this._checkSession()) {
7754
- return;
7755
- }
7756
-
7757
- // Session was expired if session ids do not match
7758
- const expired = oldSessionId !== this.getSessionId();
7759
-
7760
- if (!expired) {
7761
- return true;
7762
- }
7763
-
7764
- // Session is expired, trigger a full snapshot (which will create a new session)
7765
- if (this.isPaused()) {
7766
- this.resume();
7767
- } else {
7768
- this._triggerFullSnapshot();
7753
+ // Check session handles the refreshing itself
7754
+ return false;
7769
7755
  }
7770
7756
 
7771
- return false;
7757
+ return true;
7772
7758
  }
7773
7759
 
7774
7760
  /**
@@ -7808,7 +7794,8 @@ class ReplayContainer {
7808
7794
  });
7809
7795
 
7810
7796
  this.addUpdate(() => {
7811
- void addEvent(this, {
7797
+ // Return `false` if the event _was_ added, as that means we schedule a flush
7798
+ return !addEventSync(this, {
7812
7799
  type: ReplayEventTypeCustom,
7813
7800
  timestamp: breadcrumb.timestamp || 0,
7814
7801
  data: {
@@ -7856,6 +7843,7 @@ class ReplayContainer {
7856
7843
 
7857
7844
  // Need to set as enabled before we start recording, as `record()` can trigger a flush with a new checkout
7858
7845
  this._isEnabled = true;
7846
+ this._isPaused = false;
7859
7847
 
7860
7848
  this.startRecording();
7861
7849
  }
@@ -7872,17 +7860,17 @@ class ReplayContainer {
7872
7860
  /**
7873
7861
  * Loads (or refreshes) the current session.
7874
7862
  */
7875
- _initializeSessionForSampling() {
7863
+ _initializeSessionForSampling(previousSessionId) {
7876
7864
  // Whenever there is _any_ error sample rate, we always allow buffering
7877
7865
  // Because we decide on sampling when an error occurs, we need to buffer at all times if sampling for errors
7878
7866
  const allowBuffering = this._options.errorSampleRate > 0;
7879
7867
 
7880
7868
  const session = loadOrCreateSession(
7881
- this.session,
7882
7869
  {
7883
7870
  sessionIdleExpire: this.timeouts.sessionIdleExpire,
7884
7871
  maxReplayDuration: this._options.maxReplayDuration,
7885
7872
  traceInternals: this._options._experiments.traceInternals,
7873
+ previousSessionId,
7886
7874
  },
7887
7875
  {
7888
7876
  stickySession: this._options.stickySession,
@@ -7907,37 +7895,32 @@ class ReplayContainer {
7907
7895
 
7908
7896
  const currentSession = this.session;
7909
7897
 
7910
- const newSession = maybeRefreshSession(
7911
- currentSession,
7912
- {
7898
+ if (
7899
+ shouldRefreshSession(currentSession, {
7913
7900
  sessionIdleExpire: this.timeouts.sessionIdleExpire,
7914
- traceInternals: this._options._experiments.traceInternals,
7915
7901
  maxReplayDuration: this._options.maxReplayDuration,
7916
- },
7917
- {
7918
- stickySession: Boolean(this._options.stickySession),
7919
- sessionSampleRate: this._options.sessionSampleRate,
7920
- allowBuffering: this._options.errorSampleRate > 0,
7921
- },
7922
- );
7923
-
7924
- const isNew = newSession.id !== currentSession.id;
7925
-
7926
- // If session was newly created (i.e. was not loaded from storage), then
7927
- // enable flag to create the root replay
7928
- if (isNew) {
7929
- this.setInitialState();
7930
- this.session = newSession;
7931
- }
7932
-
7933
- if (!this.session.sampled) {
7934
- void this.stop({ reason: 'session not refreshed' });
7902
+ })
7903
+ ) {
7904
+ void this._refreshSession(currentSession);
7935
7905
  return false;
7936
7906
  }
7937
7907
 
7938
7908
  return true;
7939
7909
  }
7940
7910
 
7911
+ /**
7912
+ * Refresh a session with a new one.
7913
+ * This stops the current session (without forcing a flush, as that would never work since we are expired),
7914
+ * and then does a new sampling based on the refreshed session.
7915
+ */
7916
+ async _refreshSession(session) {
7917
+ if (!this._isEnabled) {
7918
+ return;
7919
+ }
7920
+ await this.stop({ reason: 'refresh session' });
7921
+ this.initializeSampling(session.id);
7922
+ }
7923
+
7941
7924
  /**
7942
7925
  * Adds listeners to record events for the replay
7943
7926
  */
@@ -8049,10 +8032,14 @@ class ReplayContainer {
8049
8032
 
8050
8033
  const expired = isSessionExpired(this.session, {
8051
8034
  maxReplayDuration: this._options.maxReplayDuration,
8052
- ...this.timeouts,
8035
+ sessionIdleExpire: this.timeouts.sessionIdleExpire,
8053
8036
  });
8054
8037
 
8055
- if (breadcrumb && !expired) {
8038
+ if (expired) {
8039
+ return;
8040
+ }
8041
+
8042
+ if (breadcrumb) {
8056
8043
  this._createCustomBreadcrumb(breadcrumb);
8057
8044
  }
8058
8045
 
@@ -8197,7 +8184,9 @@ class ReplayContainer {
8197
8184
  * Should never be called directly, only by `flush`
8198
8185
  */
8199
8186
  async _runFlush() {
8200
- if (!this.session || !this.eventBuffer) {
8187
+ const replayId = this.getSessionId();
8188
+
8189
+ if (!this.session || !this.eventBuffer || !replayId) {
8201
8190
  (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.error('[Replay] No session or eventBuffer found to flush.');
8202
8191
  return;
8203
8192
  }
@@ -8217,13 +8206,15 @@ class ReplayContainer {
8217
8206
  return;
8218
8207
  }
8219
8208
 
8209
+ // if this changed in the meanwhile, e.g. because the session was refreshed or similar, we abort here
8210
+ if (replayId !== this.getSessionId()) {
8211
+ return;
8212
+ }
8213
+
8220
8214
  try {
8221
8215
  // This uses the data from the eventBuffer, so we need to call this before `finish()
8222
8216
  this._updateInitialTimestampFromEventBuffer();
8223
8217
 
8224
- // Note this empties the event buffer regardless of outcome of sending replay
8225
- const recordingData = await this.eventBuffer.finish();
8226
-
8227
8218
  const timestamp = Date.now();
8228
8219
 
8229
8220
  // Check total duration again, to avoid sending outdated stuff
@@ -8233,14 +8224,14 @@ class ReplayContainer {
8233
8224
  throw new Error('Session is too long, not sending replay');
8234
8225
  }
8235
8226
 
8236
- // NOTE: Copy values from instance members, as it's possible they could
8237
- // change before the flush finishes.
8238
- const replayId = this.session.id;
8239
8227
  const eventContext = this._popEventContext();
8240
8228
  // Always increment segmentId regardless of outcome of sending replay
8241
8229
  const segmentId = this.session.segmentId++;
8242
8230
  this._maybeSaveSession();
8243
8231
 
8232
+ // Note this empties the event buffer regardless of outcome of sending replay
8233
+ const recordingData = await this.eventBuffer.finish();
8234
+
8244
8235
  await sendReplay({
8245
8236
  replayId,
8246
8237
  recordingData,