@@ -625,7 +625,7 @@ export class ReplayContainer implements ReplayContainerInterface {
625625 // Send replay when the page/tab becomes hidden. There is no reason to send
626626 // replay if it becomes visible, since no actions we care about were done
627627 // while it was hidden
628- this . _conditionalFlush ( { finishImmediate : true } ) ;
628+ this . _conditionalFlush ( { sync : true } ) ;
629629 }
630630
631631 /**
@@ -756,8 +756,8 @@ export class ReplayContainer implements ReplayContainerInterface {
756756 * Page is likely to unload so need to bypass debounce completely and
757757 * synchronously retrieve pending events from buffer and send request asap.
758758 */
759- if ( options . finishImmediate ) {
760- void this . _runFlush ( options ) ;
759+ if ( options . sync ) {
760+ this . _flushSync ( ) ;
761761 return ;
762762 }
763763
@@ -804,81 +804,143 @@ export class ReplayContainer implements ReplayContainerInterface {
804804 *
805805 * Should never be called directly, only by `flush`
806806 */
807- private async _runFlush ( options : FlushOptions = { } ) : Promise < void > {
808- if ( ! this . session || ! this . eventBuffer ) {
809- __DEBUG_BUILD__ && logger . error ( '[Replay] No session or eventBuffer found to flush.' ) ;
810- return ;
811- }
812-
807+ private async _runFlush ( ) : Promise < void > {
813808 try {
814- this . _debouncedFlush . cancel ( ) ;
809+ const flushData = this . _prepareFlush ( ) ;
810+
811+ if ( ! flushData ) {
812+ return ;
813+ }
815814
816- const promises : Promise < any > [ ] = [ ] ;
815+ const { promises, replayId , segmentId , eventContext , eventBuffer , session } = flushData ;
817816
818- promises . push ( this . _addPerformanceEntries ( ) ) ;
817+ // NOTE: Be mindful that nothing after this point (the first `await`)
818+ // will run after when the page is unloaded.
819+ await Promise . all ( promises ) ;
819820
820- // Do not continue if there are no pending events in buffer
821- if ( ! this . eventBuffer || ! this . eventBuffer . pendingLength ) {
821+ // This can be empty due to blur events calling `runFlush` directly. In
822+ // the case where we have a snapshot checkout and a blur event
823+ // happening near the same time, the blur event can end up emptying the
824+ // buffer even if snapshot happens first.
825+ if ( ! eventBuffer . pendingLength ) {
822826 return ;
823827 }
824828
825- // Only attach memory entry if eventBuffer is not empty
826- promises . push ( addMemoryEntry ( this ) ) ;
829+ // This empties the event buffer regardless of outcome of sending replay
830+ const recordingData = await eventBuffer . finish ( ) ;
827831
828- // NOTE: Copy values from instance members, as it's possible they could
829- // change before the flush finishes.
830- const replayId = this . session . id ;
831- const eventContext = this . _popEventContext ( ) ;
832- // Always increment segmentId regardless of outcome of sending replay
833- const segmentId = this . session . segmentId ++ ;
834-
835- // Save session (new segment id) after we save flush data assuming either
836- // 1) request succeeds or 2) it fails or never happens, in which case we
837- // need to retry this segment.
838- this . _maybeSaveSession ( ) ;
832+ await sendReplay ( {
833+ replayId,
834+ recordingData,
835+ segmentId,
836+ includeReplayStartTimestamp : segmentId === 0 ,
837+ eventContext,
838+ session,
839+ options : this . getOptions ( ) ,
840+ timestamp : new Date ( ) . getTime ( ) ,
841+ } ) ;
842+ } catch ( err ) {
843+ this . _handleSendError ( err ) ;
844+ }
845+ }
839846
840- let recordingData : ReplayRecordingData ;
847+ /**
848+ * Flush event buffer synchonously.
849+ * This is necessary e.g. when running flush on page unload or similar.
850+ */
851+ private _flushSync ( ) : void {
852+ try {
853+ const flushData = this . _prepareFlush ( ) ;
841854
842- if ( options . finishImmediate && this . eventBuffer . pendingLength ) {
843- recordingData = this . eventBuffer . finishImmediate ( ) ;
844- } else {
845- // NOTE: Be mindful that nothing after this point (the first `await`)
846- // will run after when the page is unloaded.
847- await Promise . all ( promises ) ;
848-
849- // This can be empty due to blur events calling `runFlush` directly. In
850- // the case where we have a snapshot checkout and a blur event
851- // happening near the same time, the blur event can end up emptying the
852- // buffer even if snapshot happens first.
853- if ( ! this . eventBuffer . pendingLength ) {
854- return ;
855- }
856- // This empties the event buffer regardless of outcome of sending replay
857- recordingData = await this . eventBuffer . finish ( ) ;
855+ if ( ! flushData ) {
856+ return ;
858857 }
859858
860- await sendReplay ( {
859+ const { replayId, segmentId, eventContext, eventBuffer, session } = flushData ;
860+
861+ const recordingData = eventBuffer . finishSync ( ) ;
862+
863+ sendReplay ( {
861864 replayId,
862865 recordingData,
863866 segmentId,
864867 includeReplayStartTimestamp : segmentId === 0 ,
865868 eventContext,
866- session : this . session ,
869+ session,
867870 options : this . getOptions ( ) ,
868871 timestamp : new Date ( ) . getTime ( ) ,
872+ } ) . catch ( err => {
873+ this . _handleSendError ( err ) ;
869874 } ) ;
870875 } catch ( err ) {
871- this . _handleException ( err ) ;
876+ this . _handleSendError ( err ) ;
877+ }
878+ }
872879
873- if ( err instanceof RateLimitError ) {
874- this . _handleRateLimit ( err . rateLimits ) ;
875- return ;
880+ /** Prepare flush data */
881+ private _prepareFlush ( ) :
882+ | {
883+ replayId : string ;
884+ eventContext : PopEventContext ;
885+ segmentId : number ;
886+ promises : Promise < unknown > [ ] ;
887+ eventBuffer : EventBuffer ;
888+ session : Session ;
876889 }
890+ | undefined {
891+ if ( ! this . session || ! this . eventBuffer ) {
892+ __DEBUG_BUILD__ && logger . error ( '[Replay] No session or eventBuffer found to flush.' ) ;
893+ return ;
894+ }
877895
878- // This means we retried 3 times, and all of them failed
879- // In this case, we want to completely stop the replay - otherwise, we may get inconsistent segments
880- this . stop ( ) ;
896+ this . _debouncedFlush . cancel ( ) ;
897+
898+ const promises : Promise < unknown > [ ] = [ ] ;
899+
900+ promises . push ( this . _addPerformanceEntries ( ) ) ;
901+
902+ // Do not continue if there are no pending events in buffer
903+ if ( ! this . eventBuffer || ! this . eventBuffer . pendingLength ) {
904+ return ;
881905 }
906+
907+ // Only attach memory entry if eventBuffer is not empty
908+ promises . push ( addMemoryEntry ( this ) ) ;
909+
910+ // NOTE: Copy values from instance members, as it's possible they could
911+ // change before the flush finishes.
912+ const replayId = this . session . id ;
913+ const eventContext = this . _popEventContext ( ) ;
914+ // Always increment segmentId regardless of outcome of sending replay
915+ const segmentId = this . session . segmentId ++ ;
916+
917+ // Save session (new segment id) after we save flush data assuming either
918+ // 1) request succeeds or 2) it fails or never happens, in which case we
919+ // need to retry this segment.
920+ this . _maybeSaveSession ( ) ;
921+
922+ return {
923+ replayId,
924+ eventContext,
925+ segmentId,
926+ promises,
927+ eventBuffer : this . eventBuffer ,
928+ session : this . session ,
929+ } ;
930+ }
931+
932+ /** Handle an error when sending a replay. */
933+ private _handleSendError ( error : unknown ) : void {
934+ this . _handleException ( error ) ;
935+
936+ if ( error instanceof RateLimitError ) {
937+ this . _handleRateLimit ( error . rateLimits ) ;
938+ return ;
939+ }
940+
941+ // This means we retried 3 times, and all of them failed
942+ // In this case, we want to completely stop the replay - otherwise, we may get inconsistent segments
943+ this . stop ( ) ;
882944 }
883945
884946 /**
0 commit comments