@@ -19,8 +19,8 @@ import { setupPerformanceObserver } from './coreHandlers/performanceObserver';
19
19
import { createEventBuffer } from './eventBuffer' ;
20
20
import { clearSession } from './session/clearSession' ;
21
21
import { loadOrCreateSession } from './session/loadOrCreateSession' ;
22
- import { maybeRefreshSession } from './session/maybeRefreshSession' ;
23
22
import { saveSession } from './session/saveSession' ;
23
+ import { shouldRefreshSession } from './session/shouldRefreshSession' ;
24
24
import type {
25
25
AddEventResult ,
26
26
AddUpdateCallback ,
@@ -218,7 +218,7 @@ export class ReplayContainer implements ReplayContainerInterface {
218
218
* Initializes the plugin based on sampling configuration. Should not be
219
219
* called outside of constructor.
220
220
*/
221
- public initializeSampling ( ) : void {
221
+ public initializeSampling ( previousSessionId ?: string ) : void {
222
222
const { errorSampleRate, sessionSampleRate } = this . _options ;
223
223
224
224
// If neither sample rate is > 0, then do nothing - user will need to call one of
@@ -229,7 +229,7 @@ export class ReplayContainer implements ReplayContainerInterface {
229
229
230
230
// Otherwise if there is _any_ sample rate set, try to load an existing
231
231
// session, or create a new one.
232
- this . _initializeSessionForSampling ( ) ;
232
+ this . _initializeSessionForSampling ( previousSessionId ) ;
233
233
234
234
if ( ! this . session ) {
235
235
// This should not happen, something wrong has occurred
@@ -274,7 +274,6 @@ export class ReplayContainer implements ReplayContainerInterface {
274
274
logInfoNextTick ( '[Replay] Starting replay in session mode' , this . _options . _experiments . traceInternals ) ;
275
275
276
276
const session = loadOrCreateSession (
277
- this . session ,
278
277
{
279
278
timeouts : this . timeouts ,
280
279
traceInternals : this . _options . _experiments . traceInternals ,
@@ -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
timeouts : this . timeouts ,
310
308
traceInternals : this . _options . _experiments . traceInternals ,
@@ -372,15 +370,18 @@ export class ReplayContainer implements ReplayContainerInterface {
372
370
return ;
373
371
}
374
372
373
+ // We can't move `_isEnabled` after awaiting a flush, otherwise we can
374
+ // enter into an infinite loop when `stop()` is called while flushing.
375
+ this . _isEnabled = false ;
376
+
375
377
try {
376
378
logInfo (
377
- `[Replay] Stopping Replay${ reason ? ` triggered by ${ reason } ` : '' } ` ,
379
+ `[Replay] Stopping Replay${ reason ? ` triggered by ${ reason } ` : '' } ${ new Date ( ) . toISOString ( ) } ${
380
+ this . _isEnabled
381
+ } `,
378
382
this . _options . _experiments . traceInternals ,
379
383
) ;
380
384
381
- // We can't move `_isEnabled` after awaiting a flush, otherwise we can
382
- // enter into an infinite loop when `stop()` is called while flushing.
383
- this . _isEnabled = false ;
384
385
this . _removeListeners ( ) ;
385
386
this . stopRecording ( ) ;
386
387
@@ -474,16 +475,6 @@ export class ReplayContainer implements ReplayContainerInterface {
474
475
475
476
// Once this session ends, we do not want to refresh it
476
477
if ( this . session ) {
477
- this . session . shouldRefresh = false ;
478
-
479
- // It's possible that the session lifespan is > max session lifespan
480
- // because we have been buffering beyond max session lifespan (we ignore
481
- // expiration given that `shouldRefresh` is true). Since we flip
482
- // `shouldRefresh`, the session could be considered expired due to
483
- // lifespan, which is not what we want. Update session start date to be
484
- // the current timestamp, so that session is not considered to be
485
- // expired. This means that max replay duration can be MAX_SESSION_LIFE +
486
- // (length of buffer), which we are ok with.
487
478
this . _updateUserActivity ( activityTime ) ;
488
479
this . _updateSessionActivity ( activityTime ) ;
489
480
this . _maybeSaveSession ( ) ;
@@ -735,6 +726,7 @@ export class ReplayContainer implements ReplayContainerInterface {
735
726
736
727
// Need to set as enabled before we start recording, as `record()` can trigger a flush with a new checkout
737
728
this . _isEnabled = true ;
729
+ this . _isPaused = false ;
738
730
739
731
this . startRecording ( ) ;
740
732
}
@@ -751,16 +743,16 @@ export class ReplayContainer implements ReplayContainerInterface {
751
743
/**
752
744
* Loads (or refreshes) the current session.
753
745
*/
754
- private _initializeSessionForSampling ( ) : void {
746
+ private _initializeSessionForSampling ( previousSessionId ?: string ) : void {
755
747
// Whenever there is _any_ error sample rate, we always allow buffering
756
748
// Because we decide on sampling when an error occurs, we need to buffer at all times if sampling for errors
757
749
const allowBuffering = this . _options . errorSampleRate > 0 ;
758
750
759
751
const session = loadOrCreateSession (
760
- this . session ,
761
752
{
762
753
timeouts : this . timeouts ,
763
754
traceInternals : this . _options . _experiments . traceInternals ,
755
+ previousSessionId,
764
756
} ,
765
757
{
766
758
stickySession : this . _options . stickySession ,
@@ -785,36 +777,27 @@ export class ReplayContainer implements ReplayContainerInterface {
785
777
786
778
const currentSession = this . session ;
787
779
788
- const newSession = maybeRefreshSession (
789
- currentSession ,
790
- {
791
- timeouts : this . timeouts ,
792
- traceInternals : this . _options . _experiments . traceInternals ,
793
- } ,
794
- {
795
- stickySession : Boolean ( this . _options . stickySession ) ,
796
- sessionSampleRate : this . _options . sessionSampleRate ,
797
- allowBuffering : this . _options . errorSampleRate > 0 ,
798
- } ,
799
- ) ;
800
-
801
- const isNew = newSession . id !== currentSession . id ;
802
-
803
- // If session was newly created (i.e. was not loaded from storage), then
804
- // enable flag to create the root replay
805
- if ( isNew ) {
806
- this . setInitialState ( ) ;
807
- this . session = newSession ;
808
- }
809
-
810
- if ( ! this . session . sampled ) {
811
- void this . stop ( { reason : 'session not refreshed' } ) ;
780
+ if ( shouldRefreshSession ( currentSession , this . timeouts ) ) {
781
+ void this . _refreshSession ( currentSession ) ;
812
782
return false ;
813
783
}
814
784
815
785
return true ;
816
786
}
817
787
788
+ /**
789
+ * Refresh a session with a new one.
790
+ * This stops the current session (without forcing a flush, as that would never work since we are expired),
791
+ * and then does a new sampling based on the refreshed session.
792
+ */
793
+ private async _refreshSession ( session : Session ) : Promise < void > {
794
+ if ( ! this . _isEnabled ) {
795
+ return ;
796
+ }
797
+ await this . stop ( { reason : 'refresh session' } ) ;
798
+ this . initializeSampling ( session . id ) ;
799
+ }
800
+
818
801
/**
819
802
* Adds listeners to record events for the replay
820
803
*/
@@ -1071,7 +1054,9 @@ export class ReplayContainer implements ReplayContainerInterface {
1071
1054
* Should never be called directly, only by `flush`
1072
1055
*/
1073
1056
private async _runFlush ( ) : Promise < void > {
1074
- if ( ! this . session || ! this . eventBuffer ) {
1057
+ const replayId = this . getSessionId ( ) ;
1058
+
1059
+ if ( ! this . session || ! this . eventBuffer || ! replayId ) {
1075
1060
__DEBUG_BUILD__ && logger . error ( '[Replay] No session or eventBuffer found to flush.' ) ;
1076
1061
return ;
1077
1062
}
@@ -1091,13 +1076,15 @@ export class ReplayContainer implements ReplayContainerInterface {
1091
1076
return ;
1092
1077
}
1093
1078
1079
+ // if this changed in the meanwhile, e.g. because the session was refreshed or similar, we abort here
1080
+ if ( replayId !== this . getSessionId ( ) ) {
1081
+ return ;
1082
+ }
1083
+
1094
1084
try {
1095
1085
// This uses the data from the eventBuffer, so we need to call this before `finish()
1096
1086
this . _updateInitialTimestampFromEventBuffer ( ) ;
1097
1087
1098
- // Note this empties the event buffer regardless of outcome of sending replay
1099
- const recordingData = await this . eventBuffer . finish ( ) ;
1100
-
1101
1088
const timestamp = Date . now ( ) ;
1102
1089
1103
1090
// Check total duration again, to avoid sending outdated stuff
@@ -1107,14 +1094,14 @@ export class ReplayContainer implements ReplayContainerInterface {
1107
1094
throw new Error ( 'Session is too long, not sending replay' ) ;
1108
1095
}
1109
1096
1110
- // NOTE: Copy values from instance members, as it's possible they could
1111
- // change before the flush finishes.
1112
- const replayId = this . session . id ;
1113
1097
const eventContext = this . _popEventContext ( ) ;
1114
1098
// Always increment segmentId regardless of outcome of sending replay
1115
1099
const segmentId = this . session . segmentId ++ ;
1116
1100
this . _maybeSaveSession ( ) ;
1117
1101
1102
+ // Note this empties the event buffer regardless of outcome of sending replay
1103
+ const recordingData = await this . eventBuffer . finish ( ) ;
1104
+
1118
1105
await sendReplay ( {
1119
1106
replayId,
1120
1107
recordingData,
0 commit comments