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+ 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+ }
20+
921type MaybeAsync < T > = T | Promise < T > ;
1022
1123interface OfflineTransportOptions extends InternalBaseTransportOptions {
@@ -44,18 +56,6 @@ export type CreateOfflineStore = (maxQueueCount: number) => OfflineStore;
4456
4557type Timer = number | { unref ?: ( ) => void } ;
4658
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-
5959/**
6060 * Wraps a transport and stores and retries events when they fail to send.
6161 *
@@ -74,6 +74,10 @@ export function makeOfflineTransport<TO>(
7474 let retryDelay = START_DELAY ;
7575 let flushTimer : Timer | undefined ;
7676
77+ function log ( msg : string , error ?: Error ) : void {
78+ __DEBUG_BUILD__ && logger . info ( `[Offline]: ${ msg } ` , error ) ;
79+ }
80+
7781 function shouldQueue ( env : Envelope , error : Error , retryDelay : number ) : MaybeAsync < boolean > {
7882 if ( isReplayEnvelope ( env ) ) {
7983 return false ;
@@ -86,25 +90,19 @@ export function makeOfflineTransport<TO>(
8690 return true ;
8791 }
8892
89- function flushLater ( overrideDelay ? : number ) : void {
93+ function flushIn ( delay : number ) : void {
9094 if ( flushTimer ) {
91- if ( overrideDelay ) {
92- clearTimeout ( flushTimer as ReturnType < typeof setTimeout > ) ;
93- } else {
94- return ;
95- }
95+ clearTimeout ( flushTimer as ReturnType < typeof setTimeout > ) ;
9696 }
9797
98- const delay = overrideDelay || retryDelay ;
99-
10098 flushTimer = setTimeout ( async ( ) => {
10199 flushTimer = undefined ;
102100
103101 const found = await store . pop ( ) ;
104102 if ( found ) {
105- __DEBUG_BUILD__ && logger . info ( '[Offline]: Attempting to send previously queued event') ;
103+ log ( ' Attempting to send previously queued event') ;
106104 void send ( found ) . catch ( e => {
107- __DEBUG_BUILD__ && logger . info ( '[Offline]: Failed to send when retrying ', e ) ;
105+ log ( ' Failed to retry sending ', e ) ;
108106 } ) ;
109107 }
110108 } , delay ) as Timer ;
@@ -113,6 +111,14 @@ export function makeOfflineTransport<TO>(
113111 if ( typeof flushTimer !== 'number' && typeof flushTimer . unref === 'function' ) {
114112 flushTimer . unref ( ) ;
115113 }
114+ }
115+
116+ function flushWithBackOff ( ) : void {
117+ if ( flushTimer ) {
118+ return ;
119+ }
120+
121+ flushIn ( retryDelay ) ;
116122
117123 retryDelay *= 2 ;
118124
@@ -124,18 +130,27 @@ export function makeOfflineTransport<TO>(
124130 async function send ( envelope : Envelope ) : Promise < void | TransportMakeRequestResponse > {
125131 try {
126132 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 ) ;
133+
134+ let delay = MIN_DELAY ;
135+
136+ if ( result ) {
137+ // If there's a retry-after header, use that as the next delay.
138+ if ( result . headers && result . headers [ 'retry-after' ] ) {
139+ delay = parseRetryAfterHeader ( result . headers [ 'retry-after' ] ) ;
140+ } // If we have a server error, return now so we don't flush the queue.
141+ else if ( ( result . statusCode || 0 ) >= 400 ) {
142+ return result ;
143+ }
131144 }
132145
146+ flushIn ( delay ) ;
147+ retryDelay = START_DELAY ;
133148 return result ;
134149 } catch ( e ) {
135150 if ( await shouldQueue ( envelope , e , retryDelay ) ) {
136151 await store . insert ( envelope ) ;
137- flushLater ( ) ;
138- __DEBUG_BUILD__ && logger . info ( '[Offline]: Event queued', e ) ;
152+ flushWithBackOff ( ) ;
153+ log ( 'Error sending. Event queued', e ) ;
139154 return { } ;
140155 } else {
141156 throw e ;
@@ -144,7 +159,7 @@ export function makeOfflineTransport<TO>(
144159 }
145160
146161 if ( options . flushAtStartup ) {
147- flushLater ( ) ;
162+ flushWithBackOff ( ) ;
148163 }
149164
150165 return {
0 commit comments