@@ -29,17 +29,21 @@ import type {
2929 ReplayPluginOptions ,
3030 Session ,
3131} from './types' ;
32+ import { FlushState } from './types' ;
3233import { addEvent } from './util/addEvent' ;
3334import { addMemoryEntry } from './util/addMemoryEntry' ;
35+ import { clearPendingReplay } from './util/clearPendingReplay' ;
3436import { createBreadcrumb } from './util/createBreadcrumb' ;
3537import { createPerformanceEntries } from './util/createPerformanceEntries' ;
3638import { createPerformanceSpans } from './util/createPerformanceSpans' ;
3739import { debounce } from './util/debounce' ;
40+ import { getPendingReplay } from './util/getPendingReplay' ;
3841import { isExpired } from './util/isExpired' ;
3942import { isSessionExpired } from './util/isSessionExpired' ;
4043import { overwriteRecordDroppedEvent , restoreRecordDroppedEvent } from './util/monkeyPatchRecordDroppedEvent' ;
4144import { sendReplay } from './util/sendReplay' ;
42- import { RateLimitError } from './util/sendReplayRequest' ;
45+ import { RateLimitError , sendReplayRequest } from './util/sendReplayRequest' ;
46+ import { setFlushState } from './util/setFlushState' ;
4347
4448/**
4549 * The main replay container class, which holds all the state and methods for recording and sending replays.
@@ -151,7 +155,7 @@ export class ReplayContainer implements ReplayContainerInterface {
151155 * Creates or loads a session, attaches listeners to varying events (DOM,
152156 * _performanceObserver, Recording, Sentry SDK, etc)
153157 */
154- public start ( ) : void {
158+ public async start ( ) : Promise < void > {
155159 this . _setInitialState ( ) ;
156160
157161 this . _loadSession ( { expiry : SESSION_IDLE_DURATION } ) ;
@@ -162,6 +166,23 @@ export class ReplayContainer implements ReplayContainerInterface {
162166 return ;
163167 }
164168
169+ const useCompression = Boolean ( this . _options . useCompression ) ;
170+
171+ // Flush any pending events that were previously unable to be sent
172+ try {
173+ const pendingEvent = await getPendingReplay ( { useCompression } ) ;
174+ if ( pendingEvent ) {
175+ await sendReplayRequest ( {
176+ ...pendingEvent ,
177+ session : this . session ,
178+ options : this . _options ,
179+ } ) ;
180+ clearPendingReplay ( ) ;
181+ }
182+ } catch {
183+ // ignore
184+ }
185+
165186 if ( ! this . session . sampled ) {
166187 // If session was not sampled, then we do not initialize the integration at all.
167188 return ;
@@ -178,7 +199,7 @@ export class ReplayContainer implements ReplayContainerInterface {
178199 this . _updateSessionActivity ( ) ;
179200
180201 this . eventBuffer = createEventBuffer ( {
181- useCompression : Boolean ( this . _options . useCompression ) ,
202+ useCompression,
182203 } ) ;
183204
184205 this . _addListeners ( ) ;
@@ -366,6 +387,7 @@ export class ReplayContainer implements ReplayContainerInterface {
366387 // enable flag to create the root replay
367388 if ( type === 'new' ) {
368389 this . _setInitialState ( ) ;
390+ clearPendingReplay ( ) ;
369391 }
370392
371393 const currentSessionId = this . getSessionId ( ) ;
@@ -801,34 +823,45 @@ export class ReplayContainer implements ReplayContainerInterface {
801823 return ;
802824 }
803825
804- await this . _addPerformanceEntries ( ) ;
826+ try {
827+ const promises : Promise < any > [ ] = [ ] ;
805828
806- // Check eventBuffer again, as it could have been stopped in the meanwhile
807- if ( ! this . eventBuffer || ! this . eventBuffer . pendingLength ) {
808- return ;
809- }
829+ promises . push ( this . _addPerformanceEntries ( ) ) ;
810830
811- // Only attach memory event if eventBuffer is not empty
812- await addMemoryEntry ( this ) ;
831+ // Do not continue if there are no pending events in buffer
832+ if ( ! this . eventBuffer ?. pendingLength ) {
833+ return ;
834+ }
813835
814- // Check eventBuffer again, as it could have been stopped in the meanwhile
815- if ( ! this . eventBuffer ) {
816- return ;
817- }
836+ // Only attach memory entry if eventBuffer is not empty
837+ promises . push ( addMemoryEntry ( this ) ) ;
818838
819- try {
820- // Note this empties the event buffer regardless of outcome of sending replay
821- const recordingData = await this . eventBuffer . finish ( ) ;
839+ // This empties the event buffer regardless of outcome of sending replay
840+ promises . push ( this . eventBuffer . finish ( ) ) ;
822841
823842 // NOTE: Copy values from instance members, as it's possible they could
824843 // change before the flush finishes.
825844 const replayId = this . session . id ;
826845 const eventContext = this . _popEventContext ( ) ;
827846 // Always increment segmentId regardless of outcome of sending replay
828847 const segmentId = this . session . segmentId ++ ;
848+
849+ // Write to local storage before flushing, in case flush request never starts
850+ setFlushState ( FlushState . START , {
851+ recordingData : this . eventBuffer . pendingEvents ,
852+ replayId,
853+ eventContext,
854+ segmentId,
855+ includeReplayStartTimestamp : segmentId === 0 ,
856+ timestamp : new Date ( ) . getTime ( ) ,
857+ } ) ;
858+
859+ // Save session (new segment id) after we save flush data assuming
829860 this . _maybeSaveSession ( ) ;
830861
831- await sendReplay ( {
862+ const [ , , recordingData ] = await Promise . all ( promises ) ;
863+
864+ const sendReplayPromise = sendReplay ( {
832865 replayId,
833866 recordingData,
834867 segmentId,
@@ -838,8 +871,16 @@ export class ReplayContainer implements ReplayContainerInterface {
838871 options : this . getOptions ( ) ,
839872 timestamp : new Date ( ) . getTime ( ) ,
840873 } ) ;
874+
875+ // If replay request starts, optimistically update some states
876+ setFlushState ( FlushState . SENT_REQUEST ) ;
877+
878+ await sendReplayPromise ;
879+
880+ setFlushState ( FlushState . SENT_REQUEST ) ;
841881 } catch ( err ) {
842882 this . _handleException ( err ) ;
883+ setFlushState ( FlushState . ERROR ) ;
843884
844885 if ( err instanceof RateLimitError ) {
845886 this . _handleRateLimit ( err . rateLimits ) ;
0 commit comments