diff --git a/docs/interfaces/_client_.clientoptions.md b/docs/interfaces/_client_.clientoptions.md index 72a71e8d..eb756cd5 100644 --- a/docs/interfaces/_client_.clientoptions.md +++ b/docs/interfaces/_client_.clientoptions.md @@ -19,6 +19,7 @@ Configuration used for the GraphQL over WebSocket client. * [keepAlive](_client_.clientoptions.md#keepalive) * [lazy](_client_.clientoptions.md#lazy) * [on](_client_.clientoptions.md#on) +* [onNonLazyError](_client_.clientoptions.md#onnonlazyerror) * [retryAttempts](_client_.clientoptions.md#retryattempts) * [retryWait](_client_.clientoptions.md#retrywait) * [url](_client_.clientoptions.md#url) @@ -91,6 +92,28 @@ to get the emitted event before other registered listeners. ___ +### onNonLazyError + +• `Optional` **onNonLazyError**: undefined \| (errorOrCloseEvent: unknown) => void + +Used ONLY when the client is in non-lazy mode (`lazy = false`). When +using this mode, the errors might have no sinks to report to. To avoid +swallowing errors, or having uncaught promises; consider using `onNonLazyError`, +which will be called when either: +- An unrecoverable error/close event occurs +- Silent retry attempts have been exceeded + +After a client has errored out, it will NOT perform any automatic actions. + +The argument can be a websocket `CloseEvent` or an `Error`. To avoid bundling +DOM types, you should derive and assert the correct type. When receiving: +- A `CloseEvent`: retry attempts have been exceeded or the specific +close event is labeled as fatal (read more in `retryAttempts`). +- An `Error`: some internal issue has occured, all internal errors are +fatal by nature. + +___ + ### retryAttempts • `Optional` **retryAttempts**: undefined \| number diff --git a/src/client.ts b/src/client.ts index 9ea82372..06129e4f 100644 --- a/src/client.ts +++ b/src/client.ts @@ -77,6 +77,24 @@ export interface ClientOptions { * @default true */ lazy?: boolean; + /** + * Used ONLY when the client is in non-lazy mode (`lazy = false`). When + * using this mode, the errors might have no sinks to report to. To avoid + * swallowing errors, or having uncaught promises; consider using `onNonLazyError`, + * which will be called when either: + * - An unrecoverable error/close event occurs + * - Silent retry attempts have been exceeded + * + * After a client has errored out, it will NOT perform any automatic actions. + * + * The argument can be a websocket `CloseEvent` or an `Error`. To avoid bundling + * DOM types, you should derive and assert the correct type. When receiving: + * - A `CloseEvent`: retry attempts have been exceeded or the specific + * close event is labeled as fatal (read more in `retryAttempts`). + * - An `Error`: some internal issue has occured, all internal errors are + * fatal by nature. + */ + onNonLazyError?: (errorOrCloseEvent: unknown) => void; /** * How long should the client wait before closing the socket after the last oparation has * completed. This is meant to be used in combination with `lazy`. You might want to have @@ -155,6 +173,7 @@ export function createClient(options: ClientOptions): Client { url, connectionParams, lazy = true, + onNonLazyError, keepAlive = 0, retryAttempts = 5, retryWait = async function randomisedExponentialBackoff(retries) { @@ -523,8 +542,14 @@ export function createClient(options: ClientOptions): Client { // cancelled, shouldnt try again return; } catch (errOrCloseEvent) { - // return if shouldnt try again - if (!shouldRetryConnectOrThrow(errOrCloseEvent)) return; + try { + // return and report if shouldnt try again + if (!shouldRetryConnectOrThrow(errOrCloseEvent)) + return onNonLazyError?.(errOrCloseEvent); + } catch { + // report thrown error, no further retries + return onNonLazyError?.(errOrCloseEvent); + } } } })(); diff --git a/src/tests/client.ts b/src/tests/client.ts index 2a3c7459..41b3a5ff 100644 --- a/src/tests/client.ts +++ b/src/tests/client.ts @@ -645,6 +645,24 @@ describe('lazy', () => { // but will close eventually await server.waitForClientClose(); }); + + it('should report errors to the `onNonLazyError` callback', async (done) => { + const { url, ...server } = await startTServer(); + + createClient({ + url, + lazy: false, + retryAttempts: 0, + onNonLazyError: (err) => { + expect((err as CloseEvent).code).toBe(1005); + done(); + }, + }); + + await server.waitForClient((client) => { + client.close(); + }); + }); }); describe('reconnecting', () => {