@@ -29,17 +29,21 @@ import type {
29
29
ReplayPluginOptions ,
30
30
Session ,
31
31
} from './types' ;
32
+ import { FlushState } from './types' ;
32
33
import { addEvent } from './util/addEvent' ;
33
34
import { addMemoryEntry } from './util/addMemoryEntry' ;
35
+ import { clearPendingReplay } from './util/clearPendingReplay' ;
34
36
import { createBreadcrumb } from './util/createBreadcrumb' ;
35
37
import { createPerformanceEntries } from './util/createPerformanceEntries' ;
36
38
import { createPerformanceSpans } from './util/createPerformanceSpans' ;
37
39
import { debounce } from './util/debounce' ;
40
+ import { getPendingReplay } from './util/getPendingReplay' ;
38
41
import { isExpired } from './util/isExpired' ;
39
42
import { isSessionExpired } from './util/isSessionExpired' ;
40
43
import { overwriteRecordDroppedEvent , restoreRecordDroppedEvent } from './util/monkeyPatchRecordDroppedEvent' ;
41
44
import { sendReplay } from './util/sendReplay' ;
42
- import { RateLimitError } from './util/sendReplayRequest' ;
45
+ import { RateLimitError , sendReplayRequest } from './util/sendReplayRequest' ;
46
+ import { setFlushState } from './util/setFlushState' ;
43
47
44
48
/**
45
49
* 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 {
151
155
* Creates or loads a session, attaches listeners to varying events (DOM,
152
156
* _performanceObserver, Recording, Sentry SDK, etc)
153
157
*/
154
- public start ( ) : void {
158
+ public async start ( ) : Promise < void > {
155
159
this . _setInitialState ( ) ;
156
160
157
161
this . _loadSession ( { expiry : SESSION_IDLE_DURATION } ) ;
@@ -162,6 +166,23 @@ export class ReplayContainer implements ReplayContainerInterface {
162
166
return ;
163
167
}
164
168
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
+
165
186
if ( ! this . session . sampled ) {
166
187
// If session was not sampled, then we do not initialize the integration at all.
167
188
return ;
@@ -178,7 +199,7 @@ export class ReplayContainer implements ReplayContainerInterface {
178
199
this . _updateSessionActivity ( ) ;
179
200
180
201
this . eventBuffer = createEventBuffer ( {
181
- useCompression : Boolean ( this . _options . useCompression ) ,
202
+ useCompression,
182
203
} ) ;
183
204
184
205
this . _addListeners ( ) ;
@@ -366,6 +387,7 @@ export class ReplayContainer implements ReplayContainerInterface {
366
387
// enable flag to create the root replay
367
388
if ( type === 'new' ) {
368
389
this . _setInitialState ( ) ;
390
+ clearPendingReplay ( ) ;
369
391
}
370
392
371
393
const currentSessionId = this . getSessionId ( ) ;
@@ -801,34 +823,45 @@ export class ReplayContainer implements ReplayContainerInterface {
801
823
return ;
802
824
}
803
825
804
- await this . _addPerformanceEntries ( ) ;
826
+ try {
827
+ const promises : Promise < any > [ ] = [ ] ;
805
828
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 ( ) ) ;
810
830
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
+ }
813
835
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 ) ) ;
818
838
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 ( ) ) ;
822
841
823
842
// NOTE: Copy values from instance members, as it's possible they could
824
843
// change before the flush finishes.
825
844
const replayId = this . session . id ;
826
845
const eventContext = this . _popEventContext ( ) ;
827
846
// Always increment segmentId regardless of outcome of sending replay
828
847
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
829
860
this . _maybeSaveSession ( ) ;
830
861
831
- await sendReplay ( {
862
+ const [ , , recordingData ] = await Promise . all ( promises ) ;
863
+
864
+ const sendReplayPromise = sendReplay ( {
832
865
replayId,
833
866
recordingData,
834
867
segmentId,
@@ -838,8 +871,16 @@ export class ReplayContainer implements ReplayContainerInterface {
838
871
options : this . getOptions ( ) ,
839
872
timestamp : new Date ( ) . getTime ( ) ,
840
873
} ) ;
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 ) ;
841
881
} catch ( err ) {
842
882
this . _handleException ( err ) ;
883
+ setFlushState ( FlushState . ERROR ) ;
843
884
844
885
if ( err instanceof RateLimitError ) {
845
886
this . _handleRateLimit ( err . rateLimits ) ;
0 commit comments