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
+ 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
+
9
21
type MaybeAsync < T > = T | Promise < T > ;
10
22
11
23
interface OfflineTransportOptions extends InternalBaseTransportOptions {
@@ -44,18 +56,6 @@ export type CreateOfflineStore = (maxQueueCount: number) => OfflineStore;
44
56
45
57
type Timer = number | { unref ?: ( ) => void } ;
46
58
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
59
/**
60
60
* Wraps a transport and stores and retries events when they fail to send.
61
61
*
@@ -74,6 +74,10 @@ export function makeOfflineTransport<TO>(
74
74
let retryDelay = START_DELAY ;
75
75
let flushTimer : Timer | undefined ;
76
76
77
+ function log ( msg : string , error ?: Error ) : void {
78
+ __DEBUG_BUILD__ && logger . info ( `[Offline]: ${ msg } ` , error ) ;
79
+ }
80
+
77
81
function shouldQueue ( env : Envelope , error : Error , retryDelay : number ) : MaybeAsync < boolean > {
78
82
if ( isReplayEnvelope ( env ) ) {
79
83
return false ;
@@ -86,25 +90,19 @@ export function makeOfflineTransport<TO>(
86
90
return true ;
87
91
}
88
92
89
- function flushLater ( overrideDelay ? : number ) : void {
93
+ function flushIn ( delay : number ) : void {
90
94
if ( flushTimer ) {
91
- if ( overrideDelay ) {
92
- clearTimeout ( flushTimer as ReturnType < typeof setTimeout > ) ;
93
- } else {
94
- return ;
95
- }
95
+ clearTimeout ( flushTimer as ReturnType < typeof setTimeout > ) ;
96
96
}
97
97
98
- const delay = overrideDelay || retryDelay ;
99
-
100
98
flushTimer = setTimeout ( async ( ) => {
101
99
flushTimer = undefined ;
102
100
103
101
const found = await store . pop ( ) ;
104
102
if ( found ) {
105
- __DEBUG_BUILD__ && logger . info ( '[Offline]: Attempting to send previously queued event') ;
103
+ log ( ' Attempting to send previously queued event') ;
106
104
void send ( found ) . catch ( e => {
107
- __DEBUG_BUILD__ && logger . info ( '[Offline]: Failed to send when retrying ', e ) ;
105
+ log ( ' Failed to retry sending ', e ) ;
108
106
} ) ;
109
107
}
110
108
} , delay ) as Timer ;
@@ -113,6 +111,14 @@ export function makeOfflineTransport<TO>(
113
111
if ( typeof flushTimer !== 'number' && typeof flushTimer . unref === 'function' ) {
114
112
flushTimer . unref ( ) ;
115
113
}
114
+ }
115
+
116
+ function flushWithBackOff ( ) : void {
117
+ if ( flushTimer ) {
118
+ return ;
119
+ }
120
+
121
+ flushIn ( retryDelay ) ;
116
122
117
123
retryDelay *= 2 ;
118
124
@@ -124,18 +130,27 @@ export function makeOfflineTransport<TO>(
124
130
async function send ( envelope : Envelope ) : Promise < void | TransportMakeRequestResponse > {
125
131
try {
126
132
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
+ }
131
144
}
132
145
146
+ flushIn ( delay ) ;
147
+ retryDelay = START_DELAY ;
133
148
return result ;
134
149
} catch ( e ) {
135
150
if ( await shouldQueue ( envelope , e , retryDelay ) ) {
136
151
await store . insert ( envelope ) ;
137
- flushLater ( ) ;
138
- __DEBUG_BUILD__ && logger . info ( '[Offline]: Event queued', e ) ;
152
+ flushWithBackOff ( ) ;
153
+ log ( 'Error sending. Event queued', e ) ;
139
154
return { } ;
140
155
} else {
141
156
throw e ;
@@ -144,7 +159,7 @@ export function makeOfflineTransport<TO>(
144
159
}
145
160
146
161
if ( options . flushAtStartup ) {
147
- flushLater ( ) ;
162
+ flushWithBackOff ( ) ;
148
163
}
149
164
150
165
return {
0 commit comments