Skip to content

Commit

Permalink
feat(server): Add onClose callback for closures at _any_ point in time
Browse files Browse the repository at this point in the history
Note that `onDisconnect` is called only if the connection is acknowledged.
  • Loading branch information
enisdenjo committed Jan 13, 2021
1 parent 33ed5f2 commit dd0d4fa
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 8 deletions.
20 changes: 20 additions & 0 deletions docs/interfaces/server.serveroptions.md
Expand Up @@ -21,6 +21,7 @@ Name | Default |
- [connectionInitWaitTimeout](server.serveroptions.md#connectioninitwaittimeout)
- [context](server.serveroptions.md#context)
- [execute](server.serveroptions.md#execute)
- [onClose](server.serveroptions.md#onclose)
- [onComplete](server.serveroptions.md#oncomplete)
- [onConnect](server.serveroptions.md#onconnect)
- [onDisconnect](server.serveroptions.md#ondisconnect)
Expand Down Expand Up @@ -84,6 +85,25 @@ in the close event reason.

___

### onClose

`Optional` **onClose**: *undefined* \| (`ctx`: [*Context*](server.context.md)<E\>, `code`: *number*, `reason`: *string*) => *void* \| *Promise*<*void*\>

Called when the socket closes for whatever reason, at any
point in time. Provides the close event too. Beware
that this callback happens AFTER all subscriptions have
been gracefully completed and AFTER the `onDisconnect` callback.

If you are interested in tracking the subscriptions completions,
consider using the `onComplete` callback.

In comparison to `onDisconnect`, this callback will ALWAYS
be called, regardless if the user succesfully went through
the connection initialisation or not. `onConnect` might not
called before the `onClose`.

___

### onComplete

`Optional` **onComplete**: *undefined* \| (`ctx`: [*Context*](server.context.md)<E\>, `message`: [*CompleteMessage*](message.completemessage.md)) => *void* \| *Promise*<*void*\>
Expand Down
26 changes: 24 additions & 2 deletions src/server.ts
Expand Up @@ -184,6 +184,25 @@ export interface ServerOptions<E = unknown> {
code: number,
reason: string,
) => Promise<void> | void;
/**
* Called when the socket closes for whatever reason, at any
* point in time. Provides the close event too. Beware
* that this callback happens AFTER all subscriptions have
* been gracefully completed and AFTER the `onDisconnect` callback.
*
* If you are interested in tracking the subscriptions completions,
* consider using the `onComplete` callback.
*
* In comparison to `onDisconnect`, this callback will ALWAYS
* be called, regardless if the user succesfully went through
* the connection initialisation or not. `onConnect` might not
* called before the `onClose`.
*/
onClose?: (
ctx: Context<E>,
code: number,
reason: string,
) => Promise<void> | void;
/**
* The subscribe callback executed right after
* acknowledging the request before any payload
Expand Down Expand Up @@ -419,6 +438,7 @@ export function makeServer<E = unknown>(options: ServerOptions<E>): Server<E> {
connectionInitWaitTimeout = 3 * 1000, // 3 seconds
onConnect,
onDisconnect,
onClose,
onSubscribe,
onOperation,
onNext,
Expand All @@ -430,8 +450,9 @@ export function makeServer<E = unknown>(options: ServerOptions<E>): Server<E> {
opened(socket, extra) {
if (socket.protocol !== GRAPHQL_TRANSPORT_WS_PROTOCOL) {
socket.close(1002, 'Protocol Error');
return async () => {
/* nothing was set up */
return async (code, reason) => {
/* nothing was set up, just notify the closure */
await onClose?.(ctx, code, reason);
};
}

Expand Down Expand Up @@ -687,6 +708,7 @@ export function makeServer<E = unknown>(options: ServerOptions<E>): Server<E> {
await sub?.return?.();
}
if (ctx.acknowledged) await onDisconnect?.(ctx, code, reason);
await onClose?.(ctx, code, reason);
};
},
};
Expand Down
21 changes: 15 additions & 6 deletions src/tests/server.ts
Expand Up @@ -1391,12 +1391,18 @@ describe('Subscribe', () => {
});
});

describe('Disconnect', () => {
it('should report close code and reason to disconnect callback after connection acknowledgement', async (done) => {
describe('Disconnect/close', () => {
it('should report close code and reason to disconnect and close callback after connection acknowledgement', async (done) => {
const { url, waitForConnect } = await startTServer({
// 1st
onDisconnect: (_ctx, code, reason) => {
expect(code).toBe(4321);
expect(reason).toBe('Byebye');
},
// 2nd
onClose: (_ctx, code, reason) => {
expect(code).toBe(4321);
expect(reason).toBe('Byebye');
done();
},
});
Expand All @@ -1413,17 +1419,20 @@ describe('Disconnect', () => {
client.ws.close(4321, 'Byebye');
});

it('should not trigger the disconnect callback if connection is not acknowledged', async () => {
const { url, waitForClientClose } = await startTServer({
it('should trigger the close callback instead of disconnect if connection is not acknowledged', async (done) => {
const { url } = await startTServer({
onDisconnect: () => {
fail("Disconnect callback shouldn't be triggered");
},
onClose: (_ctx, code, reason) => {
expect(code).toBe(4321);
expect(reason).toBe('Byebye');
done();
},
});

const client = await createTClient(url);

client.ws.close(4321, 'Byebye');

await waitForClientClose();
});
});

0 comments on commit dd0d4fa

Please sign in to comment.