From 47b9830b23eaadf1f0dc919f8fb34394392ffebc Mon Sep 17 00:00:00 2001 From: d34thwings Date: Tue, 7 Sep 2021 11:40:09 +0200 Subject: [PATCH] feat(client): Add connectionAckWaitTimeout option to client --- docs/enums/common.CloseCode.md | 7 +++++ docs/interfaces/client.ClientOptions.md | 19 ++++++++++++++ src/client.ts | 34 +++++++++++++++++++++++++ src/common.ts | 1 + 4 files changed, 61 insertions(+) diff --git a/docs/enums/common.CloseCode.md b/docs/enums/common.CloseCode.md index 56f3e3e6..b5515985 100644 --- a/docs/enums/common.CloseCode.md +++ b/docs/enums/common.CloseCode.md @@ -11,6 +11,7 @@ ### Enumeration members - [BadRequest](common.CloseCode.md#badrequest) +- [ConnectionAcknowledgementTimeout](common.CloseCode.md#connectionacknowledgementtimeout) - [ConnectionInitialisationTimeout](common.CloseCode.md#connectioninitialisationtimeout) - [Forbidden](common.CloseCode.md#forbidden) - [InternalServerError](common.CloseCode.md#internalservererror) @@ -27,6 +28,12 @@ ___ +### ConnectionAcknowledgementTimeout + +• **ConnectionAcknowledgementTimeout** = `4418` + +___ + ### ConnectionInitialisationTimeout • **ConnectionInitialisationTimeout** = `4408` diff --git a/docs/interfaces/client.ClientOptions.md b/docs/interfaces/client.ClientOptions.md index 41655403..66ff6063 100644 --- a/docs/interfaces/client.ClientOptions.md +++ b/docs/interfaces/client.ClientOptions.md @@ -10,6 +10,7 @@ Configuration used for the GraphQL over WebSocket client. ### Properties +- [connectionAckWaitTimeout](client.ClientOptions.md#connectionackwaittimeout) - [connectionParams](client.ClientOptions.md#connectionparams) - [disablePong](client.ClientOptions.md#disablepong) - [jsonMessageReplacer](client.ClientOptions.md#jsonmessagereplacer) @@ -31,6 +32,24 @@ Configuration used for the GraphQL over WebSocket client. ## Properties +### connectionAckWaitTimeout + +• `Optional` **connectionAckWaitTimeout**: `number` + +The amount of time for which the client will wait +for `ConnectionAck` message. + +Set the value to `Infinity`, `''`, `0`, `null` or `undefined` to skip waiting. + +If the wait timeout has passed and the server +has not responded with `ConnectionAck` message, +the client will terminate the socket by +dispatching a close event `4418: Connection acknowledgement timeout` + +**`default`** 0 + +___ + ### connectionParams • `Optional` **connectionParams**: `Record`<`string`, `unknown`\> \| () => `undefined` \| `Record`<`string`, `unknown`\> \| `Promise`<`undefined` \| `Record`<`string`, `unknown`\>\> diff --git a/src/client.ts b/src/client.ts index 26755806..b45cff6b 100644 --- a/src/client.ts +++ b/src/client.ts @@ -304,6 +304,20 @@ export interface ClientOptions { * @default 0 */ keepAlive?: number; + /** + * The amount of time for which the client will wait + * for `ConnectionAck` message. + * + * Set the value to `Infinity`, `''`, `0`, `null` or `undefined` to skip waiting. + * + * If the wait timeout has passed and the server + * has not responded with `ConnectionAck` message, + * the client will terminate the socket by + * dispatching a close event `4418: Connection acknowledgement timeout` + * + * @default 0 + */ + connectionAckWaitTimeout?: number; /** * Disable sending the `PongMessage` automatically. * @@ -423,6 +437,7 @@ export function createClient(options: ClientOptions): Client { lazyCloseTimeout = 0, keepAlive = 0, disablePong, + connectionAckWaitTimeout = 0, retryAttempts = 5, retryWait = async function randomisedExponentialBackoff(retries) { let retryDelay = 1000; // start with 1s delay @@ -575,6 +590,21 @@ export function createClient(options: ClientOptions): Client { } } + let connectionAckTimeout: ReturnType; + function startConnectionAckTimeout() { + if ( + isFinite(connectionAckWaitTimeout) && + connectionAckWaitTimeout > 0 + ) { + connectionAckTimeout = setTimeout(() => { + socket.close( + CloseCode.ConnectionAcknowledgementTimeout, + 'Connection acknowledgement timeout', + ); + }, connectionAckWaitTimeout); + } + } + socket.onerror = (err) => { // we let the onclose reject the promise for correct retry handling emitter.emit('error', err); @@ -583,6 +613,7 @@ export function createClient(options: ClientOptions): Client { socket.onclose = (event) => { connecting = undefined; clearTimeout(queuedPing); + clearTimeout(connectionAckTimeout); emitter.emit('closed', event); denied(event); }; @@ -608,6 +639,7 @@ export function createClient(options: ClientOptions): Client { replacer, ), ); + startConnectionAckTimeout(); enqueuePing(); // enqueue ping (noop if disabled) } catch (err) { socket.close( @@ -651,6 +683,7 @@ export function createClient(options: ClientOptions): Client { throw new Error( `First message cannot be of type ${message.type}`, ); + clearTimeout(connectionAckTimeout); acknowledged = true; emitter.emit('connected', socket, message.payload); // connected = socket opened + acknowledged retrying = false; // future lazy connects are not retries @@ -723,6 +756,7 @@ export function createClient(options: ClientOptions): Client { // CloseCode.Forbidden, might grant access out after retry CloseCode.SubprotocolNotAcceptable, // CloseCode.ConnectionInitialisationTimeout, might not time out after retry + // CloseCode.ConnectionAcknowledgementTimeout, might not time out after retry CloseCode.SubscriberAlreadyExists, CloseCode.TooManyInitialisationRequests, ].includes(errOrCloseEvent.code)) diff --git a/src/common.ts b/src/common.ts index ae08b48d..22da2887 100644 --- a/src/common.ts +++ b/src/common.ts @@ -33,6 +33,7 @@ export enum CloseCode { Forbidden = 4403, SubprotocolNotAcceptable = 4406, ConnectionInitialisationTimeout = 4408, + ConnectionAcknowledgementTimeout = 4418, /** Subscriber distinction is very important */ SubscriberAlreadyExists = 4409, TooManyInitialisationRequests = 4429,