@@ -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 ,
@@ -219,7 +219,7 @@ export class ReplayContainer implements ReplayContainerInterface {
219
219
* Initializes the plugin based on sampling configuration. Should not be
220
220
* called outside of constructor.
221
221
*/
222
- public initializeSampling ( ) : void {
222
+ public initializeSampling ( previousSessionId ?: string ) : void {
223
223
const { errorSampleRate, sessionSampleRate } = this . _options ;
224
224
225
225
// If neither sample rate is > 0, then do nothing - user will need to call one of
@@ -230,7 +230,7 @@ export class ReplayContainer implements ReplayContainerInterface {
230
230
231
231
// Otherwise if there is _any_ sample rate set, try to load an existing
232
232
// session, or create a new one.
233
- this . _initializeSessionForSampling ( ) ;
233
+ this . _initializeSessionForSampling ( previousSessionId ) ;
234
234
235
235
if ( ! this . session ) {
236
236
// This should not happen, something wrong has occurred
@@ -275,7 +275,6 @@ export class ReplayContainer implements ReplayContainerInterface {
275
275
logInfoNextTick ( '[Replay] Starting replay in session mode' , this . _options . _experiments . traceInternals ) ;
276
276
277
277
const session = loadOrCreateSession (
278
- this . session ,
279
278
{
280
279
timeouts : this . timeouts ,
281
280
traceInternals : this . _options . _experiments . traceInternals ,
@@ -305,7 +304,6 @@ export class ReplayContainer implements ReplayContainerInterface {
305
304
logInfoNextTick ( '[Replay] Starting replay in buffer mode' , this . _options . _experiments . traceInternals ) ;
306
305
307
306
const session = loadOrCreateSession (
308
- this . session ,
309
307
{
310
308
timeouts : this . timeouts ,
311
309
traceInternals : this . _options . _experiments . traceInternals ,
@@ -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_SESSION_LIFE +
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,16 +748,16 @@ 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
timeouts : this . timeouts ,
768
759
traceInternals : this . _options . _experiments . traceInternals ,
760
+ previousSessionId,
769
761
} ,
770
762
{
771
763
stickySession : this . _options . stickySession ,
@@ -790,36 +782,27 @@ export class ReplayContainer implements ReplayContainerInterface {
790
782
791
783
const currentSession = this . session ;
792
784
793
- const newSession = maybeRefreshSession (
794
- currentSession ,
795
- {
796
- timeouts : this . timeouts ,
797
- traceInternals : this . _options . _experiments . traceInternals ,
798
- } ,
799
- {
800
- stickySession : Boolean ( this . _options . stickySession ) ,
801
- sessionSampleRate : this . _options . sessionSampleRate ,
802
- allowBuffering : this . _options . errorSampleRate > 0 ,
803
- } ,
804
- ) ;
805
-
806
- const isNew = newSession . id !== currentSession . id ;
807
-
808
- // If session was newly created (i.e. was not loaded from storage), then
809
- // enable flag to create the root replay
810
- if ( isNew ) {
811
- this . setInitialState ( ) ;
812
- this . session = newSession ;
813
- }
814
-
815
- if ( ! this . session . sampled ) {
816
- void this . stop ( { reason : 'session not refreshed' } ) ;
785
+ if ( shouldRefreshSession ( currentSession , this . timeouts ) ) {
786
+ void this . _refreshSession ( currentSession ) ;
817
787
return false ;
818
788
}
819
789
820
790
return true ;
821
791
}
822
792
793
+ /**
794
+ * Refresh a session with a new one.
795
+ * This stops the current session (without forcing a flush, as that would never work since we are expired),
796
+ * and then does a new sampling based on the refreshed session.
797
+ */
798
+ private async _refreshSession ( session : Session ) : Promise < void > {
799
+ if ( ! this . _isEnabled ) {
800
+ return ;
801
+ }
802
+ await this . stop ( { reason : 'refresh session' } ) ;
803
+ this . initializeSampling ( session . id ) ;
804
+ }
805
+
823
806
/**
824
807
* Adds listeners to record events for the replay
825
808
*/
@@ -1076,7 +1059,9 @@ export class ReplayContainer implements ReplayContainerInterface {
1076
1059
* Should never be called directly, only by `flush`
1077
1060
*/
1078
1061
private async _runFlush ( ) : Promise < void > {
1079
- if ( ! this . session || ! this . eventBuffer ) {
1062
+ const replayId = this . getSessionId ( ) ;
1063
+
1064
+ if ( ! this . session || ! this . eventBuffer || ! replayId ) {
1080
1065
__DEBUG_BUILD__ && logger . error ( '[Replay] No session or eventBuffer found to flush.' ) ;
1081
1066
return ;
1082
1067
}
@@ -1096,13 +1081,15 @@ export class ReplayContainer implements ReplayContainerInterface {
1096
1081
return ;
1097
1082
}
1098
1083
1084
+ // if this changed in the meanwhile, e.g. because the session was refreshed or similar, we abort here
1085
+ if ( replayId !== this . getSessionId ( ) ) {
1086
+ return ;
1087
+ }
1088
+
1099
1089
try {
1100
1090
// This uses the data from the eventBuffer, so we need to call this before `finish()
1101
1091
this . _updateInitialTimestampFromEventBuffer ( ) ;
1102
1092
1103
- // Note this empties the event buffer regardless of outcome of sending replay
1104
- const recordingData = await this . eventBuffer . finish ( ) ;
1105
-
1106
1093
const timestamp = Date . now ( ) ;
1107
1094
1108
1095
// Check total duration again, to avoid sending outdated stuff
@@ -1112,14 +1099,14 @@ export class ReplayContainer implements ReplayContainerInterface {
1112
1099
throw new Error ( 'Session is too long, not sending replay' ) ;
1113
1100
}
1114
1101
1115
- // NOTE: Copy values from instance members, as it's possible they could
1116
- // change before the flush finishes.
1117
- const replayId = this . session . id ;
1118
1102
const eventContext = this . _popEventContext ( ) ;
1119
1103
// Always increment segmentId regardless of outcome of sending replay
1120
1104
const segmentId = this . session . segmentId ++ ;
1121
1105
this . _maybeSaveSession ( ) ;
1122
1106
1107
+ // Note this empties the event buffer regardless of outcome of sending replay
1108
+ const recordingData = await this . eventBuffer . finish ( ) ;
1109
+
1123
1110
await sendReplay ( {
1124
1111
replayId,
1125
1112
recordingData,
0 commit comments