@@ -12,7 +12,8 @@ function log(msg: string, error?: Error): void {
1212}
1313
1414export interface OfflineStore {
15- insert ( env : Envelope ) : Promise < void > ;
15+ push ( env : Envelope ) : Promise < void > ;
16+ unshift ( env : Envelope ) : Promise < void > ;
1617 pop ( ) : Promise < Envelope | undefined > ;
1718}
1819
@@ -55,17 +56,19 @@ export function makeOfflineTransport<TO>(
5556) : ( options : TO & OfflineTransportOptions ) => Transport {
5657 return options => {
5758 const transport = createTransport ( options ) ;
58- const store = options . createStore ? options . createStore ( options ) : undefined ;
59+
60+ if ( ! options . createStore ) {
61+ throw new Error ( 'No `createStore` function was provided' ) ;
62+ }
63+
64+ const store = options . createStore ( options ) ;
5965
6066 let retryDelay = START_DELAY ;
6167 let flushTimer : Timer | undefined ;
6268
6369 function shouldQueue ( env : Envelope , error : Error , retryDelay : number ) : boolean | Promise < boolean > {
64- // We don't queue Session Replay envelopes because they are:
65- // - Ordered and Replay relies on the response status to know when they're successfully sent.
66- // - Likely to fill the queue quickly and block other events from being sent.
67- // We also want to drop client reports because they can be generated when we retry sending events while offline.
68- if ( envelopeContainsItemType ( env , [ 'replay_event' , 'replay_recording' , 'client_report' ] ) ) {
70+ // We want to drop client reports because they can be generated when we retry sending events while offline.
71+ if ( envelopeContainsItemType ( env , [ 'client_report' ] ) ) {
6972 return false ;
7073 }
7174
@@ -77,10 +80,6 @@ export function makeOfflineTransport<TO>(
7780 }
7881
7982 function flushIn ( delay : number ) : void {
80- if ( ! store ) {
81- return ;
82- }
83-
8483 if ( flushTimer ) {
8584 clearTimeout ( flushTimer as ReturnType < typeof setTimeout > ) ;
8685 }
@@ -91,7 +90,7 @@ export function makeOfflineTransport<TO>(
9190 const found = await store . pop ( ) ;
9291 if ( found ) {
9392 log ( 'Attempting to send previously queued event' ) ;
94- void send ( found ) . catch ( e => {
93+ void send ( found , true ) . catch ( e => {
9594 log ( 'Failed to retry sending' , e ) ;
9695 } ) ;
9796 }
@@ -113,7 +112,15 @@ export function makeOfflineTransport<TO>(
113112 retryDelay = Math . min ( retryDelay * 2 , MAX_DELAY ) ;
114113 }
115114
116- async function send ( envelope : Envelope ) : Promise < TransportMakeRequestResponse > {
115+ async function send ( envelope : Envelope , isRetry : boolean = false ) : Promise < TransportMakeRequestResponse > {
116+ // We queue all replay envelopes to avoid multiple replay envelopes being sent at the same time. If one fails, we
117+ // need to retry them in order.
118+ if ( ! isRetry && envelopeContainsItemType ( envelope , [ 'replay_event' , 'replay_recording' ] ) ) {
119+ await store . push ( envelope ) ;
120+ flushIn ( MIN_DELAY ) ;
121+ return { } ;
122+ }
123+
117124 try {
118125 const result = await transport . send ( envelope ) ;
119126
@@ -133,8 +140,13 @@ export function makeOfflineTransport<TO>(
133140 retryDelay = START_DELAY ;
134141 return result ;
135142 } catch ( e ) {
136- if ( store && ( await shouldQueue ( envelope , e as Error , retryDelay ) ) ) {
137- await store . insert ( envelope ) ;
143+ if ( await shouldQueue ( envelope , e as Error , retryDelay ) ) {
144+ // If this envelope was a retry, we want to add it to the front of the queue so it's retried again first.
145+ if ( isRetry ) {
146+ await store . unshift ( envelope ) ;
147+ } else {
148+ await store . push ( envelope ) ;
149+ }
138150 flushWithBackOff ( ) ;
139151 log ( 'Error sending. Event queued' , e as Error ) ;
140152 return { } ;
0 commit comments