@@ -19,8 +19,8 @@ import { setupPerformanceObserver } from './coreHandlers/performanceObserver';
1919import { createEventBuffer } from './eventBuffer' ;
2020import { clearSession } from './session/clearSession' ;
2121import { loadOrCreateSession } from './session/loadOrCreateSession' ;
22- import { maybeRefreshSession } from './session/maybeRefreshSession' ;
2322import { saveSession } from './session/saveSession' ;
23+ import { shouldRefreshSession } from './session/shouldRefreshSession' ;
2424import type {
2525 AddEventResult ,
2626 AddUpdateCallback ,
@@ -218,7 +218,7 @@ export class ReplayContainer implements ReplayContainerInterface {
218218 * Initializes the plugin based on sampling configuration. Should not be
219219 * called outside of constructor.
220220 */
221- public initializeSampling ( ) : void {
221+ public initializeSampling ( previousSessionId ?: string ) : void {
222222 const { errorSampleRate, sessionSampleRate } = this . _options ;
223223
224224 // 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 {
229229
230230 // Otherwise if there is _any_ sample rate set, try to load an existing
231231 // session, or create a new one.
232- this . _initializeSessionForSampling ( ) ;
232+ this . _initializeSessionForSampling ( previousSessionId ) ;
233233
234234 if ( ! this . session ) {
235235 // This should not happen, something wrong has occurred
@@ -274,7 +274,6 @@ export class ReplayContainer implements ReplayContainerInterface {
274274 logInfoNextTick ( '[Replay] Starting replay in session mode' , this . _options . _experiments . traceInternals ) ;
275275
276276 const session = loadOrCreateSession (
277- this . session ,
278277 {
279278 timeouts : this . timeouts ,
280279 traceInternals : this . _options . _experiments . traceInternals ,
@@ -304,7 +303,6 @@ export class ReplayContainer implements ReplayContainerInterface {
304303 logInfoNextTick ( '[Replay] Starting replay in buffer mode' , this . _options . _experiments . traceInternals ) ;
305304
306305 const session = loadOrCreateSession (
307- this . session ,
308306 {
309307 timeouts : this . timeouts ,
310308 traceInternals : this . _options . _experiments . traceInternals ,
@@ -372,15 +370,18 @@ export class ReplayContainer implements ReplayContainerInterface {
372370 return ;
373371 }
374372
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+
375377 try {
376378 logInfo (
377- `[Replay] Stopping Replay${ reason ? ` triggered by ${ reason } ` : '' } ` ,
379+ `[Replay] Stopping Replay${ reason ? ` triggered by ${ reason } ` : '' } ${ new Date ( ) . toISOString ( ) } ${
380+ this . _isEnabled
381+ } `,
378382 this . _options . _experiments . traceInternals ,
379383 ) ;
380384
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 ;
384385 this . _removeListeners ( ) ;
385386 this . stopRecording ( ) ;
386387
@@ -474,16 +475,6 @@ export class ReplayContainer implements ReplayContainerInterface {
474475
475476 // Once this session ends, we do not want to refresh it
476477 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.
487478 this . _updateUserActivity ( activityTime ) ;
488479 this . _updateSessionActivity ( activityTime ) ;
489480 this . _maybeSaveSession ( ) ;
@@ -735,6 +726,7 @@ export class ReplayContainer implements ReplayContainerInterface {
735726
736727 // Need to set as enabled before we start recording, as `record()` can trigger a flush with a new checkout
737728 this . _isEnabled = true ;
729+ this . _isPaused = false ;
738730
739731 this . startRecording ( ) ;
740732 }
@@ -751,16 +743,16 @@ export class ReplayContainer implements ReplayContainerInterface {
751743 /**
752744 * Loads (or refreshes) the current session.
753745 */
754- private _initializeSessionForSampling ( ) : void {
746+ private _initializeSessionForSampling ( previousSessionId ?: string ) : void {
755747 // Whenever there is _any_ error sample rate, we always allow buffering
756748 // Because we decide on sampling when an error occurs, we need to buffer at all times if sampling for errors
757749 const allowBuffering = this . _options . errorSampleRate > 0 ;
758750
759751 const session = loadOrCreateSession (
760- this . session ,
761752 {
762753 timeouts : this . timeouts ,
763754 traceInternals : this . _options . _experiments . traceInternals ,
755+ previousSessionId,
764756 } ,
765757 {
766758 stickySession : this . _options . stickySession ,
@@ -785,36 +777,27 @@ export class ReplayContainer implements ReplayContainerInterface {
785777
786778 const currentSession = this . session ;
787779
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 ) ;
812782 return false ;
813783 }
814784
815785 return true ;
816786 }
817787
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+
818801 /**
819802 * Adds listeners to record events for the replay
820803 */
@@ -1071,7 +1054,9 @@ export class ReplayContainer implements ReplayContainerInterface {
10711054 * Should never be called directly, only by `flush`
10721055 */
10731056 private async _runFlush ( ) : Promise < void > {
1074- if ( ! this . session || ! this . eventBuffer ) {
1057+ const replayId = this . getSessionId ( ) ;
1058+
1059+ if ( ! this . session || ! this . eventBuffer || ! replayId ) {
10751060 __DEBUG_BUILD__ && logger . error ( '[Replay] No session or eventBuffer found to flush.' ) ;
10761061 return ;
10771062 }
@@ -1091,13 +1076,15 @@ export class ReplayContainer implements ReplayContainerInterface {
10911076 return ;
10921077 }
10931078
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+
10941084 try {
10951085 // This uses the data from the eventBuffer, so we need to call this before `finish()
10961086 this . _updateInitialTimestampFromEventBuffer ( ) ;
10971087
1098- // Note this empties the event buffer regardless of outcome of sending replay
1099- const recordingData = await this . eventBuffer . finish ( ) ;
1100-
11011088 const timestamp = Date . now ( ) ;
11021089
11031090 // Check total duration again, to avoid sending outdated stuff
@@ -1107,14 +1094,14 @@ export class ReplayContainer implements ReplayContainerInterface {
11071094 throw new Error ( 'Session is too long, not sending replay' ) ;
11081095 }
11091096
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 ;
11131097 const eventContext = this . _popEventContext ( ) ;
11141098 // Always increment segmentId regardless of outcome of sending replay
11151099 const segmentId = this . session . segmentId ++ ;
11161100 this . _maybeSaveSession ( ) ;
11171101
1102+ // Note this empties the event buffer regardless of outcome of sending replay
1103+ const recordingData = await this . eventBuffer . finish ( ) ;
1104+
11181105 await sendReplay ( {
11191106 replayId,
11201107 recordingData,
0 commit comments