@@ -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,18 @@ 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
- `[Replay] Stopping Replay${ reason ? ` triggered by ${ reason } ` : '' } ` ,
380
+ `[Replay] Stopping Replay${ reason ? ` triggered by ${ reason } ` : '' } ${ new Date ( ) . toISOString ( ) } ${
381
+ this . _isEnabled
382
+ } `,
379
383
this . _options . _experiments . traceInternals ,
380
384
) ;
381
385
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
386
this . _removeListeners ( ) ;
386
387
this . stopRecording ( ) ;
387
388
@@ -475,16 +476,6 @@ export class ReplayContainer implements ReplayContainerInterface {
475
476
476
477
// Once this session ends, we do not want to refresh it
477
478
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
479
this . _updateUserActivity ( activityTime ) ;
489
480
this . _updateSessionActivity ( activityTime ) ;
490
481
this . _maybeSaveSession ( ) ;
@@ -740,6 +731,7 @@ export class ReplayContainer implements ReplayContainerInterface {
740
731
741
732
// Need to set as enabled before we start recording, as `record()` can trigger a flush with a new checkout
742
733
this . _isEnabled = true ;
734
+ this . _isPaused = false ;
743
735
744
736
this . startRecording ( ) ;
745
737
}
@@ -756,17 +748,17 @@ export class ReplayContainer implements ReplayContainerInterface {
756
748
/**
757
749
* Loads (or refreshes) the current session.
758
750
*/
759
- private _initializeSessionForSampling ( ) : void {
751
+ private _initializeSessionForSampling ( previousSessionId ?: string ) : void {
760
752
// Whenever there is _any_ error sample rate, we always allow buffering
761
753
// Because we decide on sampling when an error occurs, we need to buffer at all times if sampling for errors
762
754
const allowBuffering = this . _options . errorSampleRate > 0 ;
763
755
764
756
const session = loadOrCreateSession (
765
- this . session ,
766
757
{
767
758
sessionIdleExpire : this . timeouts . sessionIdleExpire ,
768
759
maxReplayDuration : this . _options . maxReplayDuration ,
769
760
traceInternals : this . _options . _experiments . traceInternals ,
761
+ previousSessionId,
770
762
} ,
771
763
{
772
764
stickySession : this . _options . stickySession ,
@@ -791,37 +783,32 @@ export class ReplayContainer implements ReplayContainerInterface {
791
783
792
784
const currentSession = this . session ;
793
785
794
- const newSession = maybeRefreshSession (
795
- currentSession ,
796
- {
786
+ if (
787
+ shouldRefreshSession ( currentSession , {
797
788
sessionIdleExpire : this . timeouts . sessionIdleExpire ,
798
- traceInternals : this . _options . _experiments . traceInternals ,
799
789
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' } ) ;
790
+ } )
791
+ ) {
792
+ void this . _refreshSession ( currentSession ) ;
819
793
return false ;
820
794
}
821
795
822
796
return true ;
823
797
}
824
798
799
+ /**
800
+ * Refresh a session with a new one.
801
+ * This stops the current session (without forcing a flush, as that would never work since we are expired),
802
+ * and then does a new sampling based on the refreshed session.
803
+ */
804
+ private async _refreshSession ( session : Session ) : Promise < void > {
805
+ if ( ! this . _isEnabled ) {
806
+ return ;
807
+ }
808
+ await this . stop ( { reason : 'refresh session' } ) ;
809
+ this . initializeSampling ( session . id ) ;
810
+ }
811
+
825
812
/**
826
813
* Adds listeners to record events for the replay
827
814
*/
@@ -933,10 +920,14 @@ export class ReplayContainer implements ReplayContainerInterface {
933
920
934
921
const expired = isSessionExpired ( this . session , {
935
922
maxReplayDuration : this . _options . maxReplayDuration ,
936
- ... this . timeouts ,
923
+ sessionIdleExpire : this . timeouts . sessionIdleExpire ,
937
924
} ) ;
938
925
939
- if ( breadcrumb && ! expired ) {
926
+ if ( expired ) {
927
+ return ;
928
+ }
929
+
930
+ if ( breadcrumb ) {
940
931
this . _createCustomBreadcrumb ( breadcrumb ) ;
941
932
}
942
933
@@ -1081,7 +1072,9 @@ export class ReplayContainer implements ReplayContainerInterface {
1081
1072
* Should never be called directly, only by `flush`
1082
1073
*/
1083
1074
private async _runFlush ( ) : Promise < void > {
1084
- if ( ! this . session || ! this . eventBuffer ) {
1075
+ const replayId = this . getSessionId ( ) ;
1076
+
1077
+ if ( ! this . session || ! this . eventBuffer || ! replayId ) {
1085
1078
__DEBUG_BUILD__ && logger . error ( '[Replay] No session or eventBuffer found to flush.' ) ;
1086
1079
return ;
1087
1080
}
@@ -1101,13 +1094,15 @@ export class ReplayContainer implements ReplayContainerInterface {
1101
1094
return ;
1102
1095
}
1103
1096
1097
+ // if this changed in the meanwhile, e.g. because the session was refreshed or similar, we abort here
1098
+ if ( replayId !== this . getSessionId ( ) ) {
1099
+ return ;
1100
+ }
1101
+
1104
1102
try {
1105
1103
// This uses the data from the eventBuffer, so we need to call this before `finish()
1106
1104
this . _updateInitialTimestampFromEventBuffer ( ) ;
1107
1105
1108
- // Note this empties the event buffer regardless of outcome of sending replay
1109
- const recordingData = await this . eventBuffer . finish ( ) ;
1110
-
1111
1106
const timestamp = Date . now ( ) ;
1112
1107
1113
1108
// Check total duration again, to avoid sending outdated stuff
@@ -1117,14 +1112,14 @@ export class ReplayContainer implements ReplayContainerInterface {
1117
1112
throw new Error ( 'Session is too long, not sending replay' ) ;
1118
1113
}
1119
1114
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
1115
const eventContext = this . _popEventContext ( ) ;
1124
1116
// Always increment segmentId regardless of outcome of sending replay
1125
1117
const segmentId = this . session . segmentId ++ ;
1126
1118
this . _maybeSaveSession ( ) ;
1127
1119
1120
+ // Note this empties the event buffer regardless of outcome of sending replay
1121
+ const recordingData = await this . eventBuffer . finish ( ) ;
1122
+
1128
1123
await sendReplay ( {
1129
1124
replayId,
1130
1125
recordingData,
0 commit comments