11import type { Envelope , InternalBaseTransportOptions , Transport , TransportMakeRequestResponse } from '@sentry/types' ;
2- import { forEachEnvelopeItem , logger } from '@sentry/utils' ;
2+ import { forEachEnvelopeItem , logger , parseRetryAfterHeader } from '@sentry/utils' ;
33
4- export const BETWEEN_DELAY = 100 ; // 100 ms
4+ export const MIN_DELAY = 100 ; // 100 ms
55export const START_DELAY = 5_000 ; // 5 seconds
66const MAX_DELAY = 3.6e6 ; // 1 hour
77const DEFAULT_QUEUE_SIZE = 30 ;
88
9- type MaybeAsync < T > = T | Promise < T > ;
9+ function isReplayEnvelope ( envelope : Envelope ) : boolean {
10+ let isReplay = false ;
11+
12+ forEachEnvelopeItem ( envelope , ( _ , type ) => {
13+ if ( type === 'replay_event' ) {
14+ isReplay = true ;
15+ }
16+ } ) ;
17+
18+ return isReplay ;
19+ }
1020
1121interface OfflineTransportOptions extends InternalBaseTransportOptions {
1222 /**
@@ -30,9 +40,9 @@ interface OfflineTransportOptions extends InternalBaseTransportOptions {
3040 *
3141 * @param envelope The envelope that failed to send.
3242 * @param error The error that occurred.
33- * @param retryDelay The current retry delay.
43+ * @param retryDelay The current retry delay in milliseconds .
3444 */
35- shouldStore ?: ( envelope : Envelope , error : Error , retryDelay : number ) => MaybeAsync < boolean > ;
45+ shouldStore ?: ( envelope : Envelope , error : Error , retryDelay : number ) => boolean | Promise < boolean > ;
3646}
3747
3848interface OfflineStore {
@@ -44,18 +54,6 @@ export type CreateOfflineStore = (maxQueueCount: number) => OfflineStore;
4454
4555type Timer = number | { unref ?: ( ) => void } ;
4656
47- function isReplayEnvelope ( envelope : Envelope ) : boolean {
48- let isReplay = false ;
49-
50- forEachEnvelopeItem ( envelope , ( _ , type ) => {
51- if ( type === 'replay_event' ) {
52- isReplay = true ;
53- }
54- } ) ;
55-
56- return isReplay ;
57- }
58-
5957/**
6058 * Wraps a transport and stores and retries events when they fail to send.
6159 *
@@ -74,6 +72,10 @@ export function makeOfflineTransport<TO>(
7472 let retryDelay = START_DELAY ;
7573 let flushTimer : Timer | undefined ;
7674
75+ function log ( msg : string , error ?: Error ) : void {
76+ __DEBUG_BUILD__ && logger . info ( `[Offline]: ${ msg } ` , error ) ;
77+ }
78+
7779 function shouldQueue ( env : Envelope , error : Error , retryDelay : number ) : MaybeAsync < boolean > {
7880 if ( isReplayEnvelope ( env ) ) {
7981 return false ;
@@ -86,25 +88,19 @@ export function makeOfflineTransport<TO>(
8688 return true ;
8789 }
8890
89- function flushLater ( overrideDelay ? : number ) : void {
91+ function flushIn ( delay : number ) : void {
9092 if ( flushTimer ) {
91- if ( overrideDelay ) {
92- clearTimeout ( flushTimer as ReturnType < typeof setTimeout > ) ;
93- } else {
94- return ;
95- }
93+ clearTimeout ( flushTimer as ReturnType < typeof setTimeout > ) ;
9694 }
9795
98- const delay = overrideDelay || retryDelay ;
99-
10096 flushTimer = setTimeout ( async ( ) => {
10197 flushTimer = undefined ;
10298
10399 const found = await store . pop ( ) ;
104100 if ( found ) {
105- __DEBUG_BUILD__ && logger . info ( '[Offline]: Attempting to send previously queued event') ;
101+ log ( ' Attempting to send previously queued event') ;
106102 void send ( found ) . catch ( e => {
107- __DEBUG_BUILD__ && logger . info ( '[Offline]: Failed to send when retrying ', e ) ;
103+ log ( ' Failed to retry sending ', e ) ;
108104 } ) ;
109105 }
110106 } , delay ) as Timer ;
@@ -113,6 +109,14 @@ export function makeOfflineTransport<TO>(
113109 if ( typeof flushTimer !== 'number' && typeof flushTimer . unref === 'function' ) {
114110 flushTimer . unref ( ) ;
115111 }
112+ }
113+
114+ function flushWithBackOff ( ) : void {
115+ if ( flushTimer ) {
116+ return ;
117+ }
118+
119+ flushIn ( retryDelay ) ;
116120
117121 retryDelay *= 2 ;
118122
@@ -124,18 +128,27 @@ export function makeOfflineTransport<TO>(
124128 async function send ( envelope : Envelope ) : Promise < void | TransportMakeRequestResponse > {
125129 try {
126130 const result = await transport . send ( envelope ) ;
127- // If the status code wasn't a server error, reset retryDelay and flush
128- if ( result && ( result . statusCode || 500 ) < 400 ) {
129- retryDelay = START_DELAY ;
130- flushLater ( BETWEEN_DELAY ) ;
131+
132+ let delay = MIN_DELAY ;
133+
134+ if ( result ) {
135+ // If there's a retry-after header, use that as the next delay.
136+ if ( result . headers && result . headers [ 'retry-after' ] ) {
137+ delay = parseRetryAfterHeader ( result . headers [ 'retry-after' ] ) ;
138+ } // If we have a server error, return now so we don't flush the queue.
139+ else if ( ( result . statusCode || 0 ) >= 400 ) {
140+ return result ;
141+ }
131142 }
132143
144+ flushIn ( delay ) ;
145+ retryDelay = START_DELAY ;
133146 return result ;
134147 } catch ( e ) {
135148 if ( await shouldQueue ( envelope , e , retryDelay ) ) {
136149 await store . insert ( envelope ) ;
137- flushLater ( ) ;
138- __DEBUG_BUILD__ && logger . info ( '[Offline]: Event queued', e ) ;
150+ flushWithBackOff ( ) ;
151+ log ( 'Error sending. Event queued', e ) ;
139152 return { } ;
140153 } else {
141154 throw e ;
@@ -144,7 +157,7 @@ export function makeOfflineTransport<TO>(
144157 }
145158
146159 if ( options . flushAtStartup ) {
147- flushLater ( ) ;
160+ flushWithBackOff ( ) ;
148161 }
149162
150163 return {
0 commit comments