11/* eslint-disable max-lines */ // TODO: We might want to split this file up
22import { addGlobalEventProcessor , captureException , getCurrentHub } from '@sentry/core' ;
3- import type { Breadcrumb , ReplayRecordingMode } from '@sentry/types' ;
3+ import type { Breadcrumb , ReplayRecordingMode , ReplayRecordingData } from '@sentry/types' ;
44import type { RateLimits } from '@sentry/utils' ;
55import { addInstrumentationHandler , disabledUntil , logger } from '@sentry/utils' ;
66import { EventType , record } from 'rrweb' ;
@@ -28,22 +28,19 @@ import type {
2828 ReplayContainer as ReplayContainerInterface ,
2929 ReplayPluginOptions ,
3030 Session ,
31+ FlushOptions ,
3132} from './types' ;
32- import { FlushState } from './types' ;
3333import { addEvent } from './util/addEvent' ;
3434import { addMemoryEntry } from './util/addMemoryEntry' ;
35- import { clearPendingReplay } from './util/clearPendingReplay' ;
3635import { createBreadcrumb } from './util/createBreadcrumb' ;
3736import { createPerformanceEntries } from './util/createPerformanceEntries' ;
3837import { createPerformanceSpans } from './util/createPerformanceSpans' ;
3938import { debounce } from './util/debounce' ;
40- import { getPendingReplay } from './util/getPendingReplay' ;
4139import { isExpired } from './util/isExpired' ;
4240import { isSessionExpired } from './util/isSessionExpired' ;
4341import { overwriteRecordDroppedEvent , restoreRecordDroppedEvent } from './util/monkeyPatchRecordDroppedEvent' ;
4442import { sendReplay } from './util/sendReplay' ;
45- import { RateLimitError , sendReplayRequest } from './util/sendReplayRequest' ;
46- import { setFlushState } from './util/setFlushState' ;
43+ import { RateLimitError } from './util/sendReplayRequest' ;
4744
4845/**
4946 * The main replay container class, which holds all the state and methods for recording and sending replays.
@@ -166,23 +163,6 @@ export class ReplayContainer implements ReplayContainerInterface {
166163 return ;
167164 }
168165
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-
186166 if ( ! this . session . sampled ) {
187167 // If session was not sampled, then we do not initialize the integration at all.
188168 return ;
@@ -199,7 +179,7 @@ export class ReplayContainer implements ReplayContainerInterface {
199179 this . _updateSessionActivity ( ) ;
200180
201181 this . eventBuffer = createEventBuffer ( {
202- useCompression,
182+ useCompression : Boolean ( this . _options . useCompression ) ,
203183 } ) ;
204184
205185 this . _addListeners ( ) ;
@@ -345,7 +325,6 @@ export class ReplayContainer implements ReplayContainerInterface {
345325 }
346326
347327 /**
348- *
349328 * Always flush via `_debouncedFlush` so that we do not have flushes triggered
350329 * from calling both `flush` and `_debouncedFlush`. Otherwise, there could be
351330 * cases of mulitple flushes happening closely together.
@@ -356,7 +335,7 @@ export class ReplayContainer implements ReplayContainerInterface {
356335 return this . _debouncedFlush . flush ( ) as Promise < void > ;
357336 }
358337
359- /** Get the current sesion (=replay) ID */
338+ /** Get the current session (=replay) ID */
360339 public getSessionId ( ) : string | undefined {
361340 return this . session && this . session . id ;
362341 }
@@ -387,7 +366,6 @@ export class ReplayContainer implements ReplayContainerInterface {
387366 // enable flag to create the root replay
388367 if ( type === 'new' ) {
389368 this . _setInitialState ( ) ;
390- clearPendingReplay ( ) ;
391369 }
392370
393371 const currentSessionId = this . getSessionId ( ) ;
@@ -647,7 +625,7 @@ export class ReplayContainer implements ReplayContainerInterface {
647625 // Send replay when the page/tab becomes hidden. There is no reason to send
648626 // replay if it becomes visible, since no actions we care about were done
649627 // while it was hidden
650- this . _conditionalFlush ( ) ;
628+ this . _conditionalFlush ( { finishImmediate : true } ) ;
651629 }
652630
653631 /**
@@ -769,11 +747,20 @@ export class ReplayContainer implements ReplayContainerInterface {
769747 /**
770748 * Only flush if `this.recordingMode === 'session'`
771749 */
772- private _conditionalFlush ( ) : void {
750+ private _conditionalFlush ( options : FlushOptions = { } ) : void {
773751 if ( this . recordingMode === 'error' ) {
774752 return ;
775753 }
776754
755+ /**
756+ * Page is likely to unload so need to bypass debounce completely and
757+ * synchronously retrieve pending events from buffer and send request asap.
758+ */
759+ if ( options . finishImmediate ) {
760+ void this . _runFlush ( options ) ;
761+ return ;
762+ }
763+
777764 void this . flushImmediate ( ) ;
778765 }
779766
@@ -817,13 +804,15 @@ export class ReplayContainer implements ReplayContainerInterface {
817804 *
818805 * Should never be called directly, only by `flush`
819806 */
820- private async _runFlush ( ) : Promise < void > {
807+ private async _runFlush ( options : FlushOptions = { } ) : Promise < void > {
821808 if ( ! this . session || ! this . eventBuffer ) {
822809 __DEBUG_BUILD__ && logger . error ( '[Replay] No session or eventBuffer found to flush.' ) ;
823810 return ;
824811 }
825812
826813 try {
814+ this . _debouncedFlush . cancel ( ) ;
815+
827816 const promises : Promise < any > [ ] = [ ] ;
828817
829818 promises . push ( this . _addPerformanceEntries ( ) ) ;
@@ -843,29 +832,30 @@ export class ReplayContainer implements ReplayContainerInterface {
843832 // Always increment segmentId regardless of outcome of sending replay
844833 const segmentId = this . session . segmentId ++ ;
845834
846- // Write to local storage before flushing, in case flush request never starts.
847- // Ensure that this happens before *any* `await` happens, otherwise we
848- // will lose data.
849- setFlushState ( FlushState . START , {
850- recordingData : this . eventBuffer . pendingEvents ,
851- replayId,
852- eventContext,
853- segmentId,
854- includeReplayStartTimestamp : segmentId === 0 ,
855- timestamp : new Date ( ) . getTime ( ) ,
856- } ) ;
857-
858835 // Save session (new segment id) after we save flush data assuming either
859836 // 1) request succeeds or 2) it fails or never happens, in which case we
860837 // need to retry this segment.
861838 this . _maybeSaveSession ( ) ;
862839
863- // NOTE: Be mindful that nothing after this point (the first `await`)
864- // will run after when the page is unloaded.
865- await Promise . all ( promises ) ;
840+ let recordingData : ReplayRecordingData ;
866841
867- // This empties the event buffer regardless of outcome of sending replay
868- const recordingData = await this . eventBuffer . finish ( ) ;
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 ( ) ;
858+ }
869859
870860 const sendReplayPromise = sendReplay ( {
871861 replayId,
@@ -878,15 +868,11 @@ export class ReplayContainer implements ReplayContainerInterface {
878868 timestamp : new Date ( ) . getTime ( ) ,
879869 } ) ;
880870
881- // If replay request starts, optimistically update some states
882- setFlushState ( FlushState . SENT_REQUEST ) ;
883-
884871 await sendReplayPromise ;
885872
886- setFlushState ( FlushState . SENT_REQUEST ) ;
873+ return ;
887874 } catch ( err ) {
888875 this . _handleException ( err ) ;
889- setFlushState ( FlushState . ERROR ) ;
890876
891877 if ( err instanceof RateLimitError ) {
892878 this . _handleRateLimit ( err . rateLimits ) ;
0 commit comments