@sentry/replay 7.64.0 → 7.65.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
@@ -4521,41 +4521,6 @@ function deleteSession() {
4521
4521
  }
4522
4522
  }
4523
4523
 
4524
- /**
4525
- * Given an initial timestamp and an expiry duration, checks to see if current
4526
- * time should be considered as expired.
4527
- */
4528
- function isExpired(
4529
- initialTime,
4530
- expiry,
4531
- targetTime = +new Date(),
4532
- ) {
4533
- // Always expired if < 0
4534
- if (initialTime === null || expiry === undefined || expiry < 0) {
4535
- return true;
4536
- }
4537
-
4538
- // Never expires if == 0
4539
- if (expiry === 0) {
4540
- return false;
4541
- }
4542
-
4543
- return initialTime + expiry <= targetTime;
4544
- }
4545
-
4546
- /**
4547
- * Checks to see if session is expired
4548
- */
4549
- function isSessionExpired(session, timeouts, targetTime = +new Date()) {
4550
- return (
4551
- // First, check that maximum session length has not been exceeded
4552
- isExpired(session.started, timeouts.maxSessionLife, targetTime) ||
4553
- // check that the idle timeout has not been exceeded (i.e. user has
4554
- // performed an action within the last `sessionIdleExpire` ms)
4555
- isExpired(session.lastActivity, timeouts.sessionIdleExpire, targetTime)
4556
- );
4557
- }
4558
-
4559
4524
  /**
4560
4525
  * Given a sample rate, returns true if replay should be sampled.
4561
4526
  *
@@ -4598,6 +4563,7 @@ function makeSession(session) {
4598
4563
  const segmentId = session.segmentId || 0;
4599
4564
  const sampled = session.sampled;
4600
4565
  const shouldRefresh = typeof session.shouldRefresh === 'boolean' ? session.shouldRefresh : true;
4566
+ const previousSessionId = session.previousSessionId;
4601
4567
 
4602
4568
  return {
4603
4569
  id,
@@ -4606,6 +4572,7 @@ function makeSession(session) {
4606
4572
  segmentId,
4607
4573
  sampled,
4608
4574
  shouldRefresh,
4575
+ previousSessionId,
4609
4576
  };
4610
4577
  }
4611
4578
 
@@ -4621,10 +4588,14 @@ function getSessionSampleType(sessionSampleRate, allowBuffering) {
4621
4588
  * that all replays will be saved to as attachments. Currently, we only expect
4622
4589
  * one of these Sentry events per "replay session".
4623
4590
  */
4624
- function createSession({ sessionSampleRate, allowBuffering, stickySession = false }) {
4591
+ function createSession(
4592
+ { sessionSampleRate, allowBuffering, stickySession = false },
4593
+ { previousSessionId } = {},
4594
+ ) {
4625
4595
  const sampled = getSessionSampleType(sessionSampleRate, allowBuffering);
4626
4596
  const session = makeSession({
4627
4597
  sampled,
4598
+ previousSessionId,
4628
4599
  });
4629
4600
 
4630
4601
  if (stickySession) {
@@ -4661,49 +4632,106 @@ function fetchSession(traceInternals) {
4661
4632
  }
4662
4633
 
4663
4634
  /**
4664
- * Get or create a session
4635
+ * Given an initial timestamp and an expiry duration, checks to see if current
4636
+ * time should be considered as expired.
4637
+ */
4638
+ function isExpired(
4639
+ initialTime,
4640
+ expiry,
4641
+ targetTime = +new Date(),
4642
+ ) {
4643
+ // Always expired if < 0
4644
+ if (initialTime === null || expiry === undefined || expiry < 0) {
4645
+ return true;
4646
+ }
4647
+
4648
+ // Never expires if == 0
4649
+ if (expiry === 0) {
4650
+ return false;
4651
+ }
4652
+
4653
+ return initialTime + expiry <= targetTime;
4654
+ }
4655
+
4656
+ /**
4657
+ * Checks to see if session is expired
4658
+ */
4659
+ function isSessionExpired(session, timeouts, targetTime = +new Date()) {
4660
+ return (
4661
+ // First, check that maximum session length has not been exceeded
4662
+ isExpired(session.started, timeouts.maxSessionLife, targetTime) ||
4663
+ // check that the idle timeout has not been exceeded (i.e. user has
4664
+ // performed an action within the last `sessionIdleExpire` ms)
4665
+ isExpired(session.lastActivity, timeouts.sessionIdleExpire, targetTime)
4666
+ );
4667
+ }
4668
+
4669
+ /**
4670
+ * Check a session, and either return it or a refreshed version of it.
4671
+ * The refreshed version may be unsampled.
4672
+ * You can check if the session has changed by comparing the session IDs.
4673
+ */
4674
+ function maybeRefreshSession(
4675
+ session,
4676
+ {
4677
+ timeouts,
4678
+ traceInternals,
4679
+ }
4680
+
4681
+ ,
4682
+ sessionOptions,
4683
+ ) {
4684
+ // If not expired, all good, just keep the session
4685
+ if (!isSessionExpired(session, timeouts)) {
4686
+ return session;
4687
+ }
4688
+
4689
+ const isBuffering = session.sampled === 'buffer';
4690
+
4691
+ // If we are buffering & the session may be refreshed, just return it
4692
+ if (isBuffering && session.shouldRefresh) {
4693
+ return session;
4694
+ }
4695
+
4696
+ // If we are buffering & the session may not be refreshed (=it was converted to session previously already)
4697
+ // We return an unsampled new session
4698
+ if (isBuffering) {
4699
+ logInfoNextTick('[Replay] Session should not be refreshed', traceInternals);
4700
+ return makeSession({ sampled: false });
4701
+ }
4702
+
4703
+ // Else, we are not buffering, and the session is expired, so we need to create a new one
4704
+ logInfoNextTick('[Replay] Session has expired, creating new one...', traceInternals);
4705
+
4706
+ const newSession = createSession(sessionOptions, { previousSessionId: session.id });
4707
+
4708
+ return newSession;
4709
+ }
4710
+
4711
+ /**
4712
+ * Get or create a session, when initializing the replay.
4713
+ * Returns a session that may be unsampled.
4665
4714
  */
4666
- function getSession({
4667
- timeouts,
4715
+ function loadOrCreateSession(
4668
4716
  currentSession,
4669
- stickySession,
4670
- sessionSampleRate,
4671
- allowBuffering,
4672
- traceInternals,
4673
- }) {
4674
- // If session exists and is passed, use it instead of always hitting session storage
4675
- const session = currentSession || (stickySession && fetchSession(traceInternals));
4676
-
4677
- if (session) {
4678
- // If there is a session, check if it is valid (e.g. "last activity" time
4679
- // should be within the "session idle time", and "session started" time is
4680
- // within "max session time").
4681
- const isExpired = isSessionExpired(session, timeouts);
4682
-
4683
- if (!isExpired || (allowBuffering && session.shouldRefresh)) {
4684
- return { type: 'saved', session };
4685
- } else if (!session.shouldRefresh) {
4686
- // This is the case if we have an error session that is completed
4687
- // (=triggered an error). Session will continue as session-based replay,
4688
- // and when this session is expired, it will not be renewed until user
4689
- // reloads.
4690
- const discardedSession = makeSession({ sampled: false });
4691
- logInfoNextTick('[Replay] Session should not be refreshed', traceInternals);
4692
- return { type: 'new', session: discardedSession };
4693
- } else {
4694
- logInfoNextTick('[Replay] Session has expired', traceInternals);
4695
- }
4696
- // Otherwise continue to create a new session
4717
+ {
4718
+ timeouts,
4719
+ traceInternals,
4697
4720
  }
4698
4721
 
4699
- const newSession = createSession({
4700
- stickySession,
4701
- sessionSampleRate,
4702
- allowBuffering,
4703
- });
4704
- logInfoNextTick('[Replay] Created new session', traceInternals);
4722
+ ,
4723
+ sessionOptions,
4724
+ ) {
4725
+ // If session exists and is passed, use it instead of always hitting session storage
4726
+ const existingSession = currentSession || (sessionOptions.stickySession && fetchSession(traceInternals));
4727
+
4728
+ // No session exists yet, just create a new one
4729
+ if (!existingSession) {
4730
+ logInfoNextTick('[Replay] Created new session', traceInternals);
4731
+ return createSession(sessionOptions);
4732
+ }
4705
4733
 
4706
- return { type: 'new', session: newSession };
4734
+ return maybeRefreshSession(existingSession, { timeouts, traceInternals }, sessionOptions);
4707
4735
  }
4708
4736
 
4709
4737
  function isCustomEvent(event) {
@@ -7308,13 +7336,7 @@ class ReplayContainer {
7308
7336
 
7309
7337
  // Otherwise if there is _any_ sample rate set, try to load an existing
7310
7338
  // session, or create a new one.
7311
- const isSessionSampled = this._loadAndCheckSession();
7312
-
7313
- if (!isSessionSampled) {
7314
- // This should only occur if `errorSampleRate` is 0 and was unsampled for
7315
- // session-based replay. In this case there is nothing to do.
7316
- return;
7317
- }
7339
+ this._initializeSessionForSampling();
7318
7340
 
7319
7341
  if (!this.session) {
7320
7342
  // This should not happen, something wrong has occurred
@@ -7322,14 +7344,16 @@ class ReplayContainer {
7322
7344
  return;
7323
7345
  }
7324
7346
 
7325
- if (this.session.sampled && this.session.sampled !== 'session') {
7326
- // If not sampled as session-based, then recording mode will be `buffer`
7327
- // Note that we don't explicitly check if `sampled === 'buffer'` because we
7328
- // could have sessions from Session storage that are still `error` from
7329
- // prior SDK version.
7330
- this.recordingMode = 'buffer';
7347
+ if (this.session.sampled === false) {
7348
+ // This should only occur if `errorSampleRate` is 0 and was unsampled for
7349
+ // session-based replay. In this case there is nothing to do.
7350
+ return;
7331
7351
  }
7332
7352
 
7353
+ // If segmentId > 0, it means we've previously already captured this session
7354
+ // In this case, we still want to continue in `session` recording mode
7355
+ this.recordingMode = this.session.sampled === 'buffer' && this.session.segmentId === 0 ? 'buffer' : 'session';
7356
+
7333
7357
  logInfoNextTick(
7334
7358
  `[Replay] Starting replay in ${this.recordingMode} mode`,
7335
7359
  this._options._experiments.traceInternals,
@@ -7356,19 +7380,20 @@ class ReplayContainer {
7356
7380
 
7357
7381
  logInfoNextTick('[Replay] Starting replay in session mode', this._options._experiments.traceInternals);
7358
7382
 
7359
- const previousSessionId = this.session && this.session.id;
7360
-
7361
- const { session } = getSession({
7362
- timeouts: this.timeouts,
7363
- stickySession: Boolean(this._options.stickySession),
7364
- currentSession: this.session,
7365
- // This is intentional: create a new session-based replay when calling `start()`
7366
- sessionSampleRate: 1,
7367
- allowBuffering: false,
7368
- traceInternals: this._options._experiments.traceInternals,
7369
- });
7383
+ const session = loadOrCreateSession(
7384
+ this.session,
7385
+ {
7386
+ timeouts: this.timeouts,
7387
+ traceInternals: this._options._experiments.traceInternals,
7388
+ },
7389
+ {
7390
+ stickySession: this._options.stickySession,
7391
+ // This is intentional: create a new session-based replay when calling `start()`
7392
+ sessionSampleRate: 1,
7393
+ allowBuffering: false,
7394
+ },
7395
+ );
7370
7396
 
7371
- session.previousSessionId = previousSessionId;
7372
7397
  this.session = session;
7373
7398
 
7374
7399
  this._initializeRecording();
@@ -7385,18 +7410,19 @@ class ReplayContainer {
7385
7410
 
7386
7411
  logInfoNextTick('[Replay] Starting replay in buffer mode', this._options._experiments.traceInternals);
7387
7412
 
7388
- const previousSessionId = this.session && this.session.id;
7389
-
7390
- const { session } = getSession({
7391
- timeouts: this.timeouts,
7392
- stickySession: Boolean(this._options.stickySession),
7393
- currentSession: this.session,
7394
- sessionSampleRate: 0,
7395
- allowBuffering: true,
7396
- traceInternals: this._options._experiments.traceInternals,
7397
- });
7413
+ const session = loadOrCreateSession(
7414
+ this.session,
7415
+ {
7416
+ timeouts: this.timeouts,
7417
+ traceInternals: this._options._experiments.traceInternals,
7418
+ },
7419
+ {
7420
+ stickySession: this._options.stickySession,
7421
+ sessionSampleRate: 0,
7422
+ allowBuffering: true,
7423
+ },
7424
+ );
7398
7425
 
7399
- session.previousSessionId = previousSessionId;
7400
7426
  this.session = session;
7401
7427
 
7402
7428
  this.recordingMode = 'buffer';
@@ -7507,7 +7533,7 @@ class ReplayContainer {
7507
7533
  * new DOM checkout.`
7508
7534
  */
7509
7535
  resume() {
7510
- if (!this._isPaused || !this._loadAndCheckSession()) {
7536
+ if (!this._isPaused || !this._checkSession()) {
7511
7537
  return;
7512
7538
  }
7513
7539
 
@@ -7615,7 +7641,7 @@ class ReplayContainer {
7615
7641
  if (!this._stopRecording) {
7616
7642
  // Create a new session, otherwise when the user action is flushed, it
7617
7643
  // will get rejected due to an expired session.
7618
- if (!this._loadAndCheckSession()) {
7644
+ if (!this._checkSession()) {
7619
7645
  return;
7620
7646
  }
7621
7647
 
@@ -7714,7 +7740,7 @@ class ReplayContainer {
7714
7740
 
7715
7741
  // --- There is recent user activity --- //
7716
7742
  // This will create a new session if expired, based on expiry length
7717
- if (!this._loadAndCheckSession()) {
7743
+ if (!this._checkSession()) {
7718
7744
  return;
7719
7745
  }
7720
7746
 
@@ -7831,31 +7857,63 @@ class ReplayContainer {
7831
7857
 
7832
7858
  /**
7833
7859
  * Loads (or refreshes) the current session.
7860
+ */
7861
+ _initializeSessionForSampling() {
7862
+ // Whenever there is _any_ error sample rate, we always allow buffering
7863
+ // Because we decide on sampling when an error occurs, we need to buffer at all times if sampling for errors
7864
+ const allowBuffering = this._options.errorSampleRate > 0;
7865
+
7866
+ const session = loadOrCreateSession(
7867
+ this.session,
7868
+ {
7869
+ timeouts: this.timeouts,
7870
+ traceInternals: this._options._experiments.traceInternals,
7871
+ },
7872
+ {
7873
+ stickySession: this._options.stickySession,
7874
+ sessionSampleRate: this._options.sessionSampleRate,
7875
+ allowBuffering,
7876
+ },
7877
+ );
7878
+
7879
+ this.session = session;
7880
+ }
7881
+
7882
+ /**
7883
+ * Checks and potentially refreshes the current session.
7834
7884
  * Returns false if session is not recorded.
7835
7885
  */
7836
- _loadAndCheckSession() {
7837
- const { type, session } = getSession({
7838
- timeouts: this.timeouts,
7839
- stickySession: Boolean(this._options.stickySession),
7840
- currentSession: this.session,
7841
- sessionSampleRate: this._options.sessionSampleRate,
7842
- allowBuffering: this._options.errorSampleRate > 0 || this.recordingMode === 'buffer',
7843
- traceInternals: this._options._experiments.traceInternals,
7844
- });
7886
+ _checkSession() {
7887
+ // If there is no session yet, we do not want to refresh anything
7888
+ // This should generally not happen, but to be safe....
7889
+ if (!this.session) {
7890
+ return false;
7891
+ }
7892
+
7893
+ const currentSession = this.session;
7894
+
7895
+ const newSession = maybeRefreshSession(
7896
+ currentSession,
7897
+ {
7898
+ timeouts: this.timeouts,
7899
+ traceInternals: this._options._experiments.traceInternals,
7900
+ },
7901
+ {
7902
+ stickySession: Boolean(this._options.stickySession),
7903
+ sessionSampleRate: this._options.sessionSampleRate,
7904
+ allowBuffering: this._options.errorSampleRate > 0,
7905
+ },
7906
+ );
7907
+
7908
+ const isNew = newSession.id !== currentSession.id;
7845
7909
 
7846
7910
  // If session was newly created (i.e. was not loaded from storage), then
7847
7911
  // enable flag to create the root replay
7848
- if (type === 'new') {
7912
+ if (isNew) {
7849
7913
  this.setInitialState();
7914
+ this.session = newSession;
7850
7915
  }
7851
7916
 
7852
- const currentSessionId = this.getSessionId();
7853
- if (session.id !== currentSessionId) {
7854
- session.previousSessionId = currentSessionId;
7855
- }
7856
-
7857
- this.session = session;
7858
-
7859
7917
  if (!this.session.sampled) {
7860
7918
  void this.stop({ reason: 'session not refreshed' });
7861
7919
  return false;