1
1
import type { Envelope , InternalBaseTransportOptions , Transport , TransportMakeRequestResponse } from '@sentry/types' ;
2
- import { forEachEnvelopeItem , logger } from '@sentry/utils' ;
2
+ import { forEachEnvelopeItem , logger , parseRetryAfterHeader } from '@sentry/utils' ;
3
3
4
- export const BETWEEN_DELAY = 100 ; // 100 ms
4
+ export const MIN_DELAY = 100 ; // 100 ms
5
5
export const START_DELAY = 5_000 ; // 5 seconds
6
6
const MAX_DELAY = 3.6e6 ; // 1 hour
7
7
const DEFAULT_QUEUE_SIZE = 30 ;
8
8
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
+ }
10
20
11
21
interface OfflineTransportOptions extends InternalBaseTransportOptions {
12
22
/**
@@ -30,9 +40,9 @@ interface OfflineTransportOptions extends InternalBaseTransportOptions {
30
40
*
31
41
* @param envelope The envelope that failed to send.
32
42
* @param error The error that occurred.
33
- * @param retryDelay The current retry delay.
43
+ * @param retryDelay The current retry delay in milliseconds .
34
44
*/
35
- shouldStore ?: ( envelope : Envelope , error : Error , retryDelay : number ) => MaybeAsync < boolean > ;
45
+ shouldStore ?: ( envelope : Envelope , error : Error , retryDelay : number ) => boolean | Promise < boolean > ;
36
46
}
37
47
38
48
interface OfflineStore {
@@ -44,18 +54,6 @@ export type CreateOfflineStore = (maxQueueCount: number) => OfflineStore;
44
54
45
55
type Timer = number | { unref ?: ( ) => void } ;
46
56
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
-
59
57
/**
60
58
* Wraps a transport and stores and retries events when they fail to send.
61
59
*
@@ -74,6 +72,10 @@ export function makeOfflineTransport<TO>(
74
72
let retryDelay = START_DELAY ;
75
73
let flushTimer : Timer | undefined ;
76
74
75
+ function log ( msg : string , error ?: Error ) : void {
76
+ __DEBUG_BUILD__ && logger . info ( `[Offline]: ${ msg } ` , error ) ;
77
+ }
78
+
77
79
function shouldQueue ( env : Envelope , error : Error , retryDelay : number ) : MaybeAsync < boolean > {
78
80
if ( isReplayEnvelope ( env ) ) {
79
81
return false ;
@@ -86,25 +88,19 @@ export function makeOfflineTransport<TO>(
86
88
return true ;
87
89
}
88
90
89
- function flushLater ( overrideDelay ? : number ) : void {
91
+ function flushIn ( delay : number ) : void {
90
92
if ( flushTimer ) {
91
- if ( overrideDelay ) {
92
- clearTimeout ( flushTimer as ReturnType < typeof setTimeout > ) ;
93
- } else {
94
- return ;
95
- }
93
+ clearTimeout ( flushTimer as ReturnType < typeof setTimeout > ) ;
96
94
}
97
95
98
- const delay = overrideDelay || retryDelay ;
99
-
100
96
flushTimer = setTimeout ( async ( ) => {
101
97
flushTimer = undefined ;
102
98
103
99
const found = await store . pop ( ) ;
104
100
if ( found ) {
105
- __DEBUG_BUILD__ && logger . info ( '[Offline]: Attempting to send previously queued event') ;
101
+ log ( ' Attempting to send previously queued event') ;
106
102
void send ( found ) . catch ( e => {
107
- __DEBUG_BUILD__ && logger . info ( '[Offline]: Failed to send when retrying ', e ) ;
103
+ log ( ' Failed to retry sending ', e ) ;
108
104
} ) ;
109
105
}
110
106
} , delay ) as Timer ;
@@ -113,6 +109,14 @@ export function makeOfflineTransport<TO>(
113
109
if ( typeof flushTimer !== 'number' && typeof flushTimer . unref === 'function' ) {
114
110
flushTimer . unref ( ) ;
115
111
}
112
+ }
113
+
114
+ function flushWithBackOff ( ) : void {
115
+ if ( flushTimer ) {
116
+ return ;
117
+ }
118
+
119
+ flushIn ( retryDelay ) ;
116
120
117
121
retryDelay *= 2 ;
118
122
@@ -124,18 +128,27 @@ export function makeOfflineTransport<TO>(
124
128
async function send ( envelope : Envelope ) : Promise < void | TransportMakeRequestResponse > {
125
129
try {
126
130
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
+ }
131
142
}
132
143
144
+ flushIn ( delay ) ;
145
+ retryDelay = START_DELAY ;
133
146
return result ;
134
147
} catch ( e ) {
135
148
if ( await shouldQueue ( envelope , e , retryDelay ) ) {
136
149
await store . insert ( envelope ) ;
137
- flushLater ( ) ;
138
- __DEBUG_BUILD__ && logger . info ( '[Offline]: Event queued', e ) ;
150
+ flushWithBackOff ( ) ;
151
+ log ( 'Error sending. Event queued', e ) ;
139
152
return { } ;
140
153
} else {
141
154
throw e ;
@@ -144,7 +157,7 @@ export function makeOfflineTransport<TO>(
144
157
}
145
158
146
159
if ( options . flushAtStartup ) {
147
- flushLater ( ) ;
160
+ flushWithBackOff ( ) ;
148
161
}
149
162
150
163
return {
0 commit comments