You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
407 lines
16 KiB
407 lines
16 KiB
/** |
|
* |
|
* client |
|
* |
|
*/ |
|
import { ExecutionResult } from 'graphql'; |
|
import { Sink, ID, Disposable, Message, ConnectionInitMessage, ConnectionAckMessage, PingMessage, PongMessage, SubscribePayload, JSONMessageReviver, JSONMessageReplacer } from './common.mjs'; |
|
/** This file is the entry point for browsers, re-export common elements. */ |
|
export * from './common.mjs'; |
|
/** |
|
* WebSocket started connecting. |
|
* |
|
* @category Client |
|
*/ |
|
export type EventConnecting = 'connecting'; |
|
/** |
|
* WebSocket has opened. |
|
* |
|
* @category Client |
|
*/ |
|
export type EventOpened = 'opened'; |
|
/** |
|
* Open WebSocket connection has been acknowledged. |
|
* |
|
* @category Client |
|
*/ |
|
export type EventConnected = 'connected'; |
|
/** |
|
* `PingMessage` has been received or sent. |
|
* |
|
* @category Client |
|
*/ |
|
export type EventPing = 'ping'; |
|
/** |
|
* `PongMessage` has been received or sent. |
|
* |
|
* @category Client |
|
*/ |
|
export type EventPong = 'pong'; |
|
/** |
|
* A message has been received. |
|
* |
|
* @category Client |
|
*/ |
|
export type EventMessage = 'message'; |
|
/** |
|
* WebSocket connection has closed. |
|
* |
|
* @category Client |
|
*/ |
|
export type EventClosed = 'closed'; |
|
/** |
|
* WebSocket connection had an error or client had an internal error. |
|
* |
|
* @category Client |
|
*/ |
|
export type EventError = 'error'; |
|
/** |
|
* All events that could occur. |
|
* |
|
* @category Client |
|
*/ |
|
export type Event = EventConnecting | EventOpened | EventConnected | EventPing | EventPong | EventMessage | EventClosed | EventError; |
|
/** @category Client */ |
|
export type EventConnectingListener = (isRetry: boolean) => void; |
|
/** |
|
* The first argument is actually the `WebSocket`, but to avoid |
|
* bundling DOM typings because the client can run in Node env too, |
|
* you should assert the websocket type during implementation. |
|
* |
|
* @category Client |
|
*/ |
|
export type EventOpenedListener = (socket: unknown) => void; |
|
/** |
|
* The first argument is actually the `WebSocket`, but to avoid |
|
* bundling DOM typings because the client can run in Node env too, |
|
* you should assert the websocket type during implementation. |
|
* |
|
* Also, the second argument is the optional payload that the server may |
|
* send through the `ConnectionAck` message. |
|
* |
|
* @category Client |
|
*/ |
|
export type EventConnectedListener = (socket: unknown, payload: ConnectionAckMessage['payload'], wasRetry: boolean) => void; |
|
/** |
|
* The first argument communicates whether the ping was received from the server. |
|
* If `false`, the ping was sent by the client. |
|
* |
|
* @category Client |
|
*/ |
|
export type EventPingListener = (received: boolean, payload: PingMessage['payload']) => void; |
|
/** |
|
* The first argument communicates whether the pong was received from the server. |
|
* If `false`, the pong was sent by the client. |
|
* |
|
* @category Client |
|
*/ |
|
export type EventPongListener = (received: boolean, payload: PongMessage['payload']) => void; |
|
/** |
|
* Called for all **valid** messages received by the client. Mainly useful for |
|
* debugging and logging received messages. |
|
* |
|
* @category Client |
|
*/ |
|
export type EventMessageListener = (message: Message) => void; |
|
/** |
|
* The argument is actually the websocket `CloseEvent`, but to avoid |
|
* bundling DOM typings because the client can run in Node env too, |
|
* you should assert the websocket type during implementation. |
|
* |
|
* @category Client |
|
*/ |
|
export type EventClosedListener = (event: unknown) => void; |
|
/** |
|
* Events dispatched from the WebSocket `onerror` are handled in this listener, |
|
* as well as all internal client errors that could throw. |
|
* |
|
* @category Client |
|
*/ |
|
export type EventErrorListener = (error: unknown) => void; |
|
/** @category Client */ |
|
export type EventListener<E extends Event> = E extends EventConnecting ? EventConnectingListener : E extends EventOpened ? EventOpenedListener : E extends EventConnected ? EventConnectedListener : E extends EventPing ? EventPingListener : E extends EventPong ? EventPongListener : E extends EventMessage ? EventMessageListener : E extends EventClosed ? EventClosedListener : E extends EventError ? EventErrorListener : never; |
|
/** |
|
* Configuration used for the GraphQL over WebSocket client. |
|
* |
|
* @category Client |
|
*/ |
|
export interface ClientOptions<P extends ConnectionInitMessage['payload'] = ConnectionInitMessage['payload']> { |
|
/** |
|
* URL of the GraphQL over WebSocket Protocol compliant server to connect. |
|
* |
|
* If the option is a function, it will be called on every WebSocket connection attempt. |
|
* Returning a promise is supported too and the connecting phase will stall until it |
|
* resolves with the URL. |
|
* |
|
* A good use-case for having a function is when using the URL for authentication, |
|
* where subsequent reconnects (due to auth) may have a refreshed identity token in |
|
* the URL. |
|
*/ |
|
url: string | (() => Promise<string> | string); |
|
/** |
|
* Optional parameters, passed through the `payload` field with the `ConnectionInit` message, |
|
* that the client specifies when establishing a connection with the server. You can use this |
|
* for securely passing arguments for authentication. |
|
* |
|
* If you decide to return a promise, keep in mind that the server might kick you off if it |
|
* takes too long to resolve! Check the `connectionInitWaitTimeout` on the server for more info. |
|
* |
|
* Throwing an error from within this function will close the socket with the `Error` message |
|
* in the close event reason. |
|
*/ |
|
connectionParams?: P | (() => Promise<P> | P); |
|
/** |
|
* Controls when should the connection be established. |
|
* |
|
* - `false`: Establish a connection immediately. Use `onNonLazyError` to handle errors. |
|
* - `true`: Establish a connection on first subscribe and close on last unsubscribe. Use |
|
* the subscription sink's `error` to handle errors. |
|
* |
|
* @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; however, |
|
* to avoid swallowing errors, 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. |
|
* |
|
* @default console.error |
|
*/ |
|
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 |
|
* a calmdown time before actually closing the connection. Kinda' like a lazy close "debounce". |
|
* |
|
* @default 0 |
|
*/ |
|
lazyCloseTimeout?: number; |
|
/** |
|
* The timout between dispatched keep-alive messages, naimly server pings. Internally |
|
* dispatches the `PingMessage` type to the server and expects a `PongMessage` in response. |
|
* This helps with making sure that the connection with the server is alive and working. |
|
* |
|
* Timeout countdown starts from the moment the socket was opened and subsequently |
|
* after every received `PongMessage`. |
|
* |
|
* Note that NOTHING will happen automatically with the client if the server never |
|
* responds to a `PingMessage` with a `PongMessage`. If you want the connection to close, |
|
* you should implement your own logic on top of the client. A simple example looks like this: |
|
* |
|
* ```js |
|
* import { createClient } from 'graphql-ws'; |
|
* |
|
* let activeSocket, timedOut; |
|
* createClient({ |
|
* url: 'ws://i.time.out:4000/after-5/seconds', |
|
* keepAlive: 10_000, // ping server every 10 seconds |
|
* on: { |
|
* connected: (socket) => (activeSocket = socket), |
|
* ping: (received) => { |
|
* if (!received) // sent |
|
* timedOut = setTimeout(() => { |
|
* if (activeSocket.readyState === WebSocket.OPEN) |
|
* activeSocket.close(4408, 'Request Timeout'); |
|
* }, 5_000); // wait 5 seconds for the pong and then close the connection |
|
* }, |
|
* pong: (received) => { |
|
* if (received) clearTimeout(timedOut); // pong is received, clear connection close timeout |
|
* }, |
|
* }, |
|
* }); |
|
* ``` |
|
* |
|
* @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. |
|
* |
|
* Useful for when integrating your own custom client pinger that performs |
|
* custom actions before responding to a ping, or to pass along the optional pong |
|
* message payload. Please check the readme recipes for a concrete example. |
|
*/ |
|
disablePong?: boolean; |
|
/** |
|
* How many times should the client try to reconnect on abnormal socket closure before it errors out? |
|
* |
|
* The library classifies the following close events as fatal: |
|
* - _All internal WebSocket fatal close codes (check `isFatalInternalCloseCode` in `src/client.ts` for exact list)_ |
|
* - `4500: Internal server error` |
|
* - `4005: Internal client error` |
|
* - `4400: Bad request` |
|
* - `4004: Bad response` |
|
* - `4401: Unauthorized` _tried subscribing before connect ack_ |
|
* - `4406: Subprotocol not acceptable` |
|
* - `4409: Subscriber for <id> already exists` _distinction is very important_ |
|
* - `4429: Too many initialisation requests` |
|
* |
|
* In addition to the aforementioned close events, any _non-CloseEvent_ connection problem |
|
* is considered fatal by default. However, this specific behaviour can be altered by using |
|
* the `shouldRetry` option. |
|
* |
|
* These events are reported immediately and the client will not reconnect. |
|
* |
|
* @default 5 |
|
*/ |
|
retryAttempts?: number; |
|
/** |
|
* Control the wait time between retries. You may implement your own strategy |
|
* by timing the resolution of the returned promise with the retries count. |
|
* `retries` argument counts actual connection attempts, so it will begin with |
|
* 0 after the first retryable disconnect. |
|
* |
|
* @default 'Randomised exponential backoff' |
|
*/ |
|
retryWait?: (retries: number) => Promise<void>; |
|
/** |
|
* Check if the close event or connection error is fatal. If you return `false`, |
|
* the client will fail immediately without additional retries; however, if you |
|
* return `true`, the client will keep retrying until the `retryAttempts` have |
|
* been exceeded. |
|
* |
|
* The argument is whatever has been thrown during the connection phase. |
|
* |
|
* Beware, the library classifies a few close events as fatal regardless of |
|
* what is returned here. They are listed in the documentation of the `retryAttempts` |
|
* option. |
|
* |
|
* @default 'Only `CloseEvent`s' |
|
*/ |
|
shouldRetry?: (errOrCloseEvent: unknown) => boolean; |
|
/** |
|
* @deprecated Use `shouldRetry` instead. |
|
* |
|
* Check if the close event or connection error is fatal. If you return `true`, |
|
* the client will fail immediately without additional retries; however, if you |
|
* return `false`, the client will keep retrying until the `retryAttempts` have |
|
* been exceeded. |
|
* |
|
* The argument is either a WebSocket `CloseEvent` or an error thrown during |
|
* the connection phase. |
|
* |
|
* Beware, the library classifies a few close events as fatal regardless of |
|
* what is returned. They are listed in the documentation of the `retryAttempts` |
|
* option. |
|
* |
|
* @default 'Any non-`CloseEvent`' |
|
*/ |
|
isFatalConnectionProblem?: (errOrCloseEvent: unknown) => boolean; |
|
/** |
|
* Register listeners before initialising the client. This way |
|
* you can ensure to catch all client relevant emitted events. |
|
* |
|
* The listeners passed in will **always** be the first ones |
|
* to get the emitted event before other registered listeners. |
|
*/ |
|
on?: Partial<{ |
|
[event in Event]: EventListener<event>; |
|
}>; |
|
/** |
|
* A custom WebSocket implementation to use instead of the |
|
* one provided by the global scope. Mostly useful for when |
|
* using the client outside of the browser environment. |
|
*/ |
|
webSocketImpl?: unknown; |
|
/** |
|
* A custom ID generator for identifying subscriptions. |
|
* |
|
* The default generates a v4 UUID to be used as the ID using `Math` |
|
* as the random number generator. Supply your own generator |
|
* in case you need more uniqueness. |
|
* |
|
* Reference: https://gist.github.com/jed/982883 |
|
*/ |
|
generateID?: (payload: SubscribePayload) => ID; |
|
/** |
|
* An optional override for the JSON.parse function used to hydrate |
|
* incoming messages to this client. Useful for parsing custom datatypes |
|
* out of the incoming JSON. |
|
*/ |
|
jsonMessageReviver?: JSONMessageReviver; |
|
/** |
|
* An optional override for the JSON.stringify function used to serialize |
|
* outgoing messages from this client. Useful for serializing custom |
|
* datatypes out to the client. |
|
*/ |
|
jsonMessageReplacer?: JSONMessageReplacer; |
|
} |
|
/** @category Client */ |
|
export interface Client extends Disposable { |
|
/** |
|
* Listens on the client which dispatches events about the socket state. |
|
*/ |
|
on<E extends Event>(event: E, listener: EventListener<E>): () => void; |
|
/** |
|
* Subscribes through the WebSocket following the config parameters. It |
|
* uses the `sink` to emit received data or errors. Returns a _cleanup_ |
|
* function used for dropping the subscription and cleaning stuff up. |
|
*/ |
|
subscribe<Data = Record<string, unknown>, Extensions = unknown>(payload: SubscribePayload, sink: Sink<ExecutionResult<Data, Extensions>>): () => void; |
|
/** |
|
* Subscribes and iterates over emitted results from the WebSocket |
|
* through the returned async iterator. |
|
*/ |
|
iterate<Data = Record<string, unknown>, Extensions = unknown>(payload: SubscribePayload): AsyncIterableIterator<ExecutionResult<Data, Extensions>>; |
|
/** |
|
* Terminates the WebSocket abruptly and immediately. |
|
* |
|
* A close event `4499: Terminated` is issued to the current WebSocket and a |
|
* syntetic {@link TerminatedCloseEvent} is immediately emitted without waiting for |
|
* the one coming from `WebSocket.onclose`. |
|
* |
|
* Terminating is not considered fatal and a connection retry will occur as expected. |
|
* |
|
* Useful in cases where the WebSocket is stuck and not emitting any events; |
|
* can happen on iOS Safari, see: https://github.com/enisdenjo/graphql-ws/discussions/290. |
|
*/ |
|
terminate(): void; |
|
} |
|
/** |
|
* Creates a disposable GraphQL over WebSocket client. |
|
* |
|
* @category Client |
|
*/ |
|
export declare function createClient<P extends ConnectionInitMessage['payload'] = ConnectionInitMessage['payload']>(options: ClientOptions<P>): Client; |
|
/** |
|
* A syntetic close event `4499: Terminated` is issued to the current to immediately |
|
* close the connection without waiting for the one coming from `WebSocket.onclose`. |
|
* |
|
* Terminating is not considered fatal and a connection retry will occur as expected. |
|
* |
|
* Useful in cases where the WebSocket is stuck and not emitting any events; |
|
* can happen on iOS Safari, see: https://github.com/enisdenjo/graphql-ws/discussions/290. |
|
*/ |
|
export declare class TerminatedCloseEvent extends Error { |
|
name: string; |
|
message: string; |
|
code: number; |
|
reason: string; |
|
wasClean: boolean; |
|
}
|
|
|