@@ -12,7 +12,7 @@ import { print } from 'graphql';
12
12
import { DEFAULT_ADMIN_SERVER_PORT } from "../types" ;
13
13
14
14
import { MaybePromise , RequireProps } from '../util/type-utils' ;
15
- import { isNode } from '../util/util' ;
15
+ import { delay , isNode } from '../util/util' ;
16
16
import { isErrorLike } from '../util/error' ;
17
17
import { getDeferred } from '../util/promise' ;
18
18
@@ -219,7 +219,7 @@ export class AdminClient<Plugins extends { [key: string]: AdminPlugin<any, any>
219
219
private attachStreamWebsocket ( adminSessionBaseUrl : string , targetStream : Duplex ) : Duplex {
220
220
const adminSessionBaseWSUrl = adminSessionBaseUrl . replace ( / ^ h t t p / , 'ws' ) ;
221
221
const wsStream = connectWebSocketStream ( `${ adminSessionBaseWSUrl } /stream` , {
222
- headers : this . adminClientOptions ? .requestOptions ?. headers // Only used in Node.js (via WS)
222
+ headers : this . adminClientOptions . requestOptions ?. headers // Only used in Node.js (via WS)
223
223
} ) ;
224
224
225
225
let streamConnected = false ;
@@ -246,26 +246,7 @@ export class AdminClient<Plugins extends { [key: string]: AdminPlugin<any, any>
246
246
targetStream . emit ( 'server-shutdown' ) ;
247
247
} else if ( streamConnected && ( await this . running ) === true ) {
248
248
console . warn ( 'Admin client stream unexpectedly disconnected' , closeEvent ) ;
249
-
250
- this . emit ( 'stream-reconnecting' ) ;
251
-
252
- // Unclean shutdown means something has gone wrong somewhere. Try to reconnect.
253
- const newStream = this . attachStreamWebsocket ( adminSessionBaseUrl , targetStream ) ;
254
-
255
- new Promise ( ( resolve , reject ) => {
256
- newStream . once ( 'connect' , resolve ) ;
257
- newStream . once ( 'error' , reject ) ;
258
- } ) . then ( ( ) => {
259
- // On a successful connect, business resumes as normal.
260
- console . warn ( 'Admin client stream reconnected' ) ;
261
- this . emit ( 'stream-reconnected' ) ;
262
- } ) . catch ( ( err ) => {
263
- // On a failed reconnect, we just shut down completely.
264
- console . warn ( 'Admin client stream reconnection failed, shutting down:' , err . message ) ;
265
- if ( this . debug ) console . warn ( err ) ;
266
- this . emit ( 'stream-reconnect-failed' , err ) ;
267
- targetStream . emit ( 'server-shutdown' ) ;
268
- } ) ;
249
+ this . tryToReconnectStream ( adminSessionBaseUrl , targetStream ) ;
269
250
}
270
251
// If never connected successfully, we do nothing.
271
252
} ) ;
@@ -280,6 +261,40 @@ export class AdminClient<Plugins extends { [key: string]: AdminPlugin<any, any>
280
261
return wsStream ;
281
262
}
282
263
264
+ /**
265
+ * Attempt to recreate a stream after disconnection, up to a limited number of retries. This is
266
+ * different to normal connection setup, as it assumes the target stream is otherwise already
267
+ * set up and active.
268
+ */
269
+ private async tryToReconnectStream ( adminSessionBaseUrl : string , targetStream : Duplex , retries = 3 ) {
270
+ this . emit ( 'stream-reconnecting' ) ;
271
+
272
+ // Unclean shutdown means something has gone wrong somewhere. Try to reconnect.
273
+ const newStream = this . attachStreamWebsocket ( adminSessionBaseUrl , targetStream ) ;
274
+
275
+ new Promise ( ( resolve , reject ) => {
276
+ newStream . once ( 'connect' , resolve ) ;
277
+ newStream . once ( 'error' , reject ) ;
278
+ } ) . then ( ( ) => {
279
+ // On a successful connect, business resumes as normal.
280
+ console . warn ( 'Admin client stream reconnected' ) ;
281
+ this . emit ( 'stream-reconnected' ) ;
282
+ } ) . catch ( async ( err ) => {
283
+ if ( retries > 0 ) {
284
+ // We delay re-retrying briefly - this helps to handle cases like the computer going
285
+ // to sleep (where the server & client pause in parallel, but race to do so).
286
+ await delay ( 50 ) ;
287
+ return this . tryToReconnectStream ( adminSessionBaseUrl , targetStream , retries - 1 ) ;
288
+ }
289
+
290
+ // Otherwise, once retries have failed, we give up entirely:
291
+ console . warn ( 'Admin client stream reconnection failed, shutting down:' , err . message ) ;
292
+ if ( this . debug ) console . warn ( err ) ;
293
+ this . emit ( 'stream-reconnect-failed' , err ) ;
294
+ targetStream . emit ( 'server-shutdown' ) ;
295
+ } ) ;
296
+ }
297
+
283
298
private openStreamToMockServer ( adminSessionBaseUrl : string ) : Promise < Duplex > {
284
299
// To allow reconnects, we need to not end the client stream when an individual web socket ends.
285
300
// To make that work, we return a separate stream, which isn't directly connected to the websocket
0 commit comments