@@ -12,7 +12,8 @@ function log(msg: string, error?: Error): void {
12
12
}
13
13
14
14
export interface OfflineStore {
15
- insert ( env : Envelope ) : Promise < void > ;
15
+ push ( env : Envelope ) : Promise < void > ;
16
+ unshift ( env : Envelope ) : Promise < void > ;
16
17
pop ( ) : Promise < Envelope | undefined > ;
17
18
}
18
19
@@ -55,17 +56,19 @@ export function makeOfflineTransport<TO>(
55
56
) : ( options : TO & OfflineTransportOptions ) => Transport {
56
57
return options => {
57
58
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 ) ;
59
65
60
66
let retryDelay = START_DELAY ;
61
67
let flushTimer : Timer | undefined ;
62
68
63
69
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' ] ) ) {
69
72
return false ;
70
73
}
71
74
@@ -77,10 +80,6 @@ export function makeOfflineTransport<TO>(
77
80
}
78
81
79
82
function flushIn ( delay : number ) : void {
80
- if ( ! store ) {
81
- return ;
82
- }
83
-
84
83
if ( flushTimer ) {
85
84
clearTimeout ( flushTimer as ReturnType < typeof setTimeout > ) ;
86
85
}
@@ -91,7 +90,7 @@ export function makeOfflineTransport<TO>(
91
90
const found = await store . pop ( ) ;
92
91
if ( found ) {
93
92
log ( 'Attempting to send previously queued event' ) ;
94
- void send ( found ) . catch ( e => {
93
+ void send ( found , true ) . catch ( e => {
95
94
log ( 'Failed to retry sending' , e ) ;
96
95
} ) ;
97
96
}
@@ -113,7 +112,15 @@ export function makeOfflineTransport<TO>(
113
112
retryDelay = Math . min ( retryDelay * 2 , MAX_DELAY ) ;
114
113
}
115
114
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
+
117
124
try {
118
125
const result = await transport . send ( envelope ) ;
119
126
@@ -133,8 +140,13 @@ export function makeOfflineTransport<TO>(
133
140
retryDelay = START_DELAY ;
134
141
return result ;
135
142
} 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
+ }
138
150
flushWithBackOff ( ) ;
139
151
log ( 'Error sending. Event queued' , e as Error ) ;
140
152
return { } ;
0 commit comments