Skip to content

Commit

Permalink
feat(client): Emit events for connecting, connected and closed
Browse files Browse the repository at this point in the history
  • Loading branch information
enisdenjo committed Sep 9, 2020
1 parent 2de2310 commit 627775b
Showing 1 changed file with 47 additions and 2 deletions.
49 changes: 47 additions & 2 deletions src/client.ts
Expand Up @@ -16,6 +16,19 @@ import {
} from './message';
import { isObject } from './utils';

export type EventConnecting = 'connecting';
export type EventConnected = 'connected'; // connected = socket opened + acknowledged
export type EventClosed = 'closed';
export type Event = EventConnecting | EventConnected | EventClosed;

export type EventListener<E extends Event> = E extends EventConnecting
? () => void
: E extends EventConnected
? (socket: WebSocket) => void
: E extends EventClosed
? (event: CloseEvent) => void
: never;

type CancellerRef = { current: (() => void) | null };

/** Configuration used for the `create` client function. */
Expand Down Expand Up @@ -43,6 +56,10 @@ export interface ClientOptions {
}

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_
Expand All @@ -61,6 +78,30 @@ export function createClient(options: ClientOptions): Client {
retryTimeout = 3 * 1000, // 3 seconds
} = options;

const emitter = (() => {
const listeners: { [event in Event]: EventListener<event>[] } = {
connecting: [],
connected: [],
closed: [],
};

return {
on<E extends Event>(event: E, listener: EventListener<E>) {
const l = listeners[event] as EventListener<E>[];
l.push(listener);
return () => {
l.splice(l.indexOf(listener), 1);
};
},
emit<E extends Event>(event: E, ...args: Parameters<EventListener<E>>) {
(listeners[event] as EventListener<E>[]).forEach((listener) => {
// @ts-expect-error: The args do actually fit
listener(...args);
});
},
};
})();

let state = {
socket: null as WebSocket | null,
acknowledged: false,
Expand Down Expand Up @@ -163,6 +204,7 @@ export function createClient(options: ClientOptions): Client {
// establish connection and assign to singleton
const socket = new WebSocket(url, GRAPHQL_TRANSPORT_WS_PROTOCOL);
state = { ...state, acknowledged: false, socket };
emitter.emit('connecting');

await new Promise((resolve, reject) => {
let settled = false,
Expand Down Expand Up @@ -192,6 +234,7 @@ export function createClient(options: ClientOptions): Client {

// we always want to update the state, just not reject a settled promise
state = { ...state, acknowledged: false, socket: null };
emitter.emit('closed', event);

if (!settled) {
settled = true;
Expand All @@ -213,6 +256,7 @@ export function createClient(options: ClientOptions): Client {
}

state = { ...state, acknowledged: true, socket };
emitter.emit('connected', socket); // connected = socket opened + acknowledged

if (!settled) {
settled = true;
Expand Down Expand Up @@ -297,9 +341,9 @@ export function createClient(options: ClientOptions): Client {
return;
}

// retries expired, throw
// retries expired, close for good
if (state.retries >= retryAttempts) {
throw errOrCloseEvent;
return;
}

// otherwize wait a bit and retry
Expand All @@ -311,6 +355,7 @@ export function createClient(options: ClientOptions): Client {
}

return {
on: emitter.on,
subscribe(payload, sink) {
const uuid = generateUUID();

Expand Down

0 comments on commit 627775b

Please sign in to comment.