@@ -18,8 +18,8 @@ import { setupPerformanceObserver } from './coreHandlers/performanceObserver';
18
18
import { createEventBuffer } from './eventBuffer' ;
19
19
import { clearSession } from './session/clearSession' ;
20
20
import { loadOrCreateSession } from './session/loadOrCreateSession' ;
21
- import { maybeRefreshSession } from './session/maybeRefreshSession' ;
22
21
import { saveSession } from './session/saveSession' ;
22
+ import { shouldRefreshSession } from './session/shouldRefreshSession' ;
23
23
import type {
24
24
AddEventResult ,
25
25
AddUpdateCallback ,
@@ -217,7 +217,7 @@ export class ReplayContainer implements ReplayContainerInterface {
217
217
* Initializes the plugin based on sampling configuration. Should not be
218
218
* called outside of constructor.
219
219
*/
220
- public initializeSampling ( ) : void {
220
+ public initializeSampling ( previousSessionId ?: string ) : void {
221
221
const { errorSampleRate, sessionSampleRate } = this . _options ;
222
222
223
223
// If neither sample rate is > 0, then do nothing - user will need to call one of
@@ -228,7 +228,7 @@ export class ReplayContainer implements ReplayContainerInterface {
228
228
229
229
// Otherwise if there is _any_ sample rate set, try to load an existing
230
230
// session, or create a new one.
231
- this . _initializeSessionForSampling ( ) ;
231
+ this . _initializeSessionForSampling ( previousSessionId ) ;
232
232
233
233
if ( ! this . session ) {
234
234
// This should not happen, something wrong has occurred
@@ -273,7 +273,6 @@ export class ReplayContainer implements ReplayContainerInterface {
273
273
logInfoNextTick ( '[Replay] Starting replay in session mode' , this . _options . _experiments . traceInternals ) ;
274
274
275
275
const session = loadOrCreateSession (
276
- this . session ,
277
276
{
278
277
maxReplayDuration : this . _options . maxReplayDuration ,
279
278
sessionIdleExpire : this . timeouts . sessionIdleExpire ,
@@ -304,7 +303,6 @@ export class ReplayContainer implements ReplayContainerInterface {
304
303
logInfoNextTick ( '[Replay] Starting replay in buffer mode' , this . _options . _experiments . traceInternals ) ;
305
304
306
305
const session = loadOrCreateSession (
307
- this . session ,
308
306
{
309
307
sessionIdleExpire : this . timeouts . sessionIdleExpire ,
310
308
maxReplayDuration : this . _options . maxReplayDuration ,
@@ -373,15 +371,16 @@ export class ReplayContainer implements ReplayContainerInterface {
373
371
return ;
374
372
}
375
373
374
+ // We can't move `_isEnabled` after awaiting a flush, otherwise we can
375
+ // enter into an infinite loop when `stop()` is called while flushing.
376
+ this . _isEnabled = false ;
377
+
376
378
try {
377
379
logInfo (
378
380
`[Replay] Stopping Replay${ reason ? ` triggered by ${ reason } ` : '' } ` ,
379
381
this . _options . _experiments . traceInternals ,
380
382
) ;
381
383
382
- // We can't move `_isEnabled` after awaiting a flush, otherwise we can
383
- // enter into an infinite loop when `stop()` is called while flushing.
384
- this . _isEnabled = false ;
385
384
this . _removeListeners ( ) ;
386
385
this . stopRecording ( ) ;
387
386
@@ -475,16 +474,6 @@ export class ReplayContainer implements ReplayContainerInterface {
475
474
476
475
// Once this session ends, we do not want to refresh it
477
476
if ( this . session ) {
478
- this . session . shouldRefresh = false ;
479
-
480
- // It's possible that the session lifespan is > max session lifespan
481
- // because we have been buffering beyond max session lifespan (we ignore
482
- // expiration given that `shouldRefresh` is true). Since we flip
483
- // `shouldRefresh`, the session could be considered expired due to
484
- // lifespan, which is not what we want. Update session start date to be
485
- // the current timestamp, so that session is not considered to be
486
- // expired. This means that max replay duration can be MAX_REPLAY_DURATION +
487
- // (length of buffer), which we are ok with.
488
477
this . _updateUserActivity ( activityTime ) ;
489
478
this . _updateSessionActivity ( activityTime ) ;
490
479
this . _maybeSaveSession ( ) ;
@@ -612,8 +601,6 @@ export class ReplayContainer implements ReplayContainerInterface {
612
601
* @hidden
613
602
*/
614
603
public checkAndHandleExpiredSession ( ) : boolean | void {
615
- const oldSessionId = this . getSessionId ( ) ;
616
-
617
604
// Prevent starting a new session if the last user activity is older than
618
605
// SESSION_IDLE_PAUSE_DURATION. Otherwise non-user activity can trigger a new
619
606
// session+recording. This creates noisy replays that do not have much
@@ -635,24 +622,11 @@ export class ReplayContainer implements ReplayContainerInterface {
635
622
// --- There is recent user activity --- //
636
623
// This will create a new session if expired, based on expiry length
637
624
if ( ! this . _checkSession ( ) ) {
638
- return ;
639
- }
640
-
641
- // Session was expired if session ids do not match
642
- const expired = oldSessionId !== this . getSessionId ( ) ;
643
-
644
- if ( ! expired ) {
645
- return true ;
646
- }
647
-
648
- // Session is expired, trigger a full snapshot (which will create a new session)
649
- if ( this . isPaused ( ) ) {
650
- this . resume ( ) ;
651
- } else {
652
- this . _triggerFullSnapshot ( ) ;
625
+ // Check session handles the refreshing itself
626
+ return false ;
653
627
}
654
628
655
- return false ;
629
+ return true ;
656
630
}
657
631
658
632
/**
@@ -740,6 +714,7 @@ export class ReplayContainer implements ReplayContainerInterface {
740
714
741
715
// Need to set as enabled before we start recording, as `record()` can trigger a flush with a new checkout
742
716
this . _isEnabled = true ;
717
+ this . _isPaused = false ;
743
718
744
719
this . startRecording ( ) ;
745
720
}
@@ -756,17 +731,17 @@ export class ReplayContainer implements ReplayContainerInterface {
756
731
/**
757
732
* Loads (or refreshes) the current session.
758
733
*/
759
- private _initializeSessionForSampling ( ) : void {
734
+ private _initializeSessionForSampling ( previousSessionId ?: string ) : void {
760
735
// Whenever there is _any_ error sample rate, we always allow buffering
761
736
// Because we decide on sampling when an error occurs, we need to buffer at all times if sampling for errors
762
737
const allowBuffering = this . _options . errorSampleRate > 0 ;
763
738
764
739
const session = loadOrCreateSession (
765
- this . session ,
766
740
{
767
741
sessionIdleExpire : this . timeouts . sessionIdleExpire ,
768
742
maxReplayDuration : this . _options . maxReplayDuration ,
769
743
traceInternals : this . _options . _experiments . traceInternals ,
744
+ previousSessionId,
770
745
} ,
771
746
{
772
747
stickySession : this . _options . stickySession ,
@@ -791,37 +766,32 @@ export class ReplayContainer implements ReplayContainerInterface {
791
766
792
767
const currentSession = this . session ;
793
768
794
- const newSession = maybeRefreshSession (
795
- currentSession ,
796
- {
769
+ if (
770
+ shouldRefreshSession ( currentSession , {
797
771
sessionIdleExpire : this . timeouts . sessionIdleExpire ,
798
- traceInternals : this . _options . _experiments . traceInternals ,
799
772
maxReplayDuration : this . _options . maxReplayDuration ,
800
- } ,
801
- {
802
- stickySession : Boolean ( this . _options . stickySession ) ,
803
- sessionSampleRate : this . _options . sessionSampleRate ,
804
- allowBuffering : this . _options . errorSampleRate > 0 ,
805
- } ,
806
- ) ;
807
-
808
- const isNew = newSession . id !== currentSession . id ;
809
-
810
- // If session was newly created (i.e. was not loaded from storage), then
811
- // enable flag to create the root replay
812
- if ( isNew ) {
813
- this . setInitialState ( ) ;
814
- this . session = newSession ;
815
- }
816
-
817
- if ( ! this . session . sampled ) {
818
- void this . stop ( { reason : 'session not refreshed' } ) ;
773
+ } )
774
+ ) {
775
+ void this . _refreshSession ( currentSession ) ;
819
776
return false ;
820
777
}
821
778
822
779
return true ;
823
780
}
824
781
782
+ /**
783
+ * Refresh a session with a new one.
784
+ * This stops the current session (without forcing a flush, as that would never work since we are expired),
785
+ * and then does a new sampling based on the refreshed session.
786
+ */
787
+ private async _refreshSession ( session : Session ) : Promise < void > {
788
+ if ( ! this . _isEnabled ) {
789
+ return ;
790
+ }
791
+ await this . stop ( { reason : 'refresh session' } ) ;
792
+ this . initializeSampling ( session . id ) ;
793
+ }
794
+
825
795
/**
826
796
* Adds listeners to record events for the replay
827
797
*/
@@ -933,10 +903,14 @@ export class ReplayContainer implements ReplayContainerInterface {
933
903
934
904
const expired = isSessionExpired ( this . session , {
935
905
maxReplayDuration : this . _options . maxReplayDuration ,
936
- ... this . timeouts ,
906
+ sessionIdleExpire : this . timeouts . sessionIdleExpire ,
937
907
} ) ;
938
908
939
- if ( breadcrumb && ! expired ) {
909
+ if ( expired ) {
910
+ return ;
911
+ }
912
+
913
+ if ( breadcrumb ) {
940
914
this . _createCustomBreadcrumb ( breadcrumb ) ;
941
915
}
942
916
@@ -1081,7 +1055,9 @@ export class ReplayContainer implements ReplayContainerInterface {
1081
1055
* Should never be called directly, only by `flush`
1082
1056
*/
1083
1057
private async _runFlush ( ) : Promise < void > {
1084
- if ( ! this . session || ! this . eventBuffer ) {
1058
+ const replayId = this . getSessionId ( ) ;
1059
+
1060
+ if ( ! this . session || ! this . eventBuffer || ! replayId ) {
1085
1061
__DEBUG_BUILD__ && logger . error ( '[Replay] No session or eventBuffer found to flush.' ) ;
1086
1062
return ;
1087
1063
}
@@ -1101,13 +1077,15 @@ export class ReplayContainer implements ReplayContainerInterface {
1101
1077
return ;
1102
1078
}
1103
1079
1080
+ // if this changed in the meanwhile, e.g. because the session was refreshed or similar, we abort here
1081
+ if ( replayId !== this . getSessionId ( ) ) {
1082
+ return ;
1083
+ }
1084
+
1104
1085
try {
1105
1086
// This uses the data from the eventBuffer, so we need to call this before `finish()
1106
1087
this . _updateInitialTimestampFromEventBuffer ( ) ;
1107
1088
1108
- // Note this empties the event buffer regardless of outcome of sending replay
1109
- const recordingData = await this . eventBuffer . finish ( ) ;
1110
-
1111
1089
const timestamp = Date . now ( ) ;
1112
1090
1113
1091
// Check total duration again, to avoid sending outdated stuff
@@ -1117,14 +1095,14 @@ export class ReplayContainer implements ReplayContainerInterface {
1117
1095
throw new Error ( 'Session is too long, not sending replay' ) ;
1118
1096
}
1119
1097
1120
- // NOTE: Copy values from instance members, as it's possible they could
1121
- // change before the flush finishes.
1122
- const replayId = this . session . id ;
1123
1098
const eventContext = this . _popEventContext ( ) ;
1124
1099
// Always increment segmentId regardless of outcome of sending replay
1125
1100
const segmentId = this . session . segmentId ++ ;
1126
1101
this . _maybeSaveSession ( ) ;
1127
1102
1103
+ // Note this empties the event buffer regardless of outcome of sending replay
1104
+ const recordingData = await this . eventBuffer . finish ( ) ;
1105
+
1128
1106
await sendReplay ( {
1129
1107
replayId,
1130
1108
recordingData,
0 commit comments