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