diff --git a/src/__tests__/server.ts b/src/__tests__/server.ts index d58fc65c..0ea52b27 100644 --- a/src/__tests__/server.ts +++ b/src/__tests__/server.ts @@ -1795,6 +1795,65 @@ describe('Subscribe', () => { fail("Shouldn't have received a message"); }, 20); }); + + it('should not send error messages if socket closes before onSubscribe hooks resolves', async () => { + let resolveOnSubscribe: () => void = () => { + throw new Error('On subscribe resolved early'); + }; + const waitForOnSubscribe = new Promise( + (resolve) => (resolveOnSubscribe = resolve), + ); + + let resolveSubscribe: () => void = () => { + throw new Error('Subscribe resolved early'); + }; + + const sendFn = jest.fn(); + + const closed = makeServer({ + schema, + async onSubscribe() { + resolveOnSubscribe(); + await new Promise((resolve) => (resolveSubscribe = resolve)); + return [new GraphQLError('Oopsie!')]; + }, + }).opened( + { + protocol: GRAPHQL_TRANSPORT_WS_PROTOCOL, + send: sendFn, + close: () => { + // noop + }, + onMessage: async (cb) => { + await cb(stringifyMessage({ type: MessageType.ConnectionInit })); + await cb( + stringifyMessage({ + id: '1', + type: MessageType.Subscribe, + payload: { query: '{ getValue }' }, + }), + ); + }, + onPing: () => { + /**/ + }, + onPong: () => { + /**/ + }, + }, + {}, + ); + + await waitForOnSubscribe; + + closed(4321, 'Bye bye!'); + + resolveSubscribe(); + + await new Promise((resolve) => setTimeout(resolve, 20)); + + expect(sendFn).toBeCalledTimes(1); // only the ack message + }); }); describe('Disconnect/close', () => { diff --git a/src/server.ts b/src/server.ts index 8212a602..0a9fbe60 100644 --- a/src/server.ts +++ b/src/server.ts @@ -730,7 +730,9 @@ export function makeServer< const maybeExecArgsOrErrors = await onSubscribe?.(ctx, message); if (maybeExecArgsOrErrors) { if (areGraphQLErrors(maybeExecArgsOrErrors)) - return await emit.error(maybeExecArgsOrErrors); + return id in ctx.subscriptions + ? await emit.error(maybeExecArgsOrErrors) + : void 0; else if (Array.isArray(maybeExecArgsOrErrors)) throw new Error( 'Invalid return value from onSubscribe hook, expected an array of GraphQLError objects', @@ -760,7 +762,9 @@ export function makeServer< execArgs.document, ); if (validationErrors.length > 0) - return await emit.error(validationErrors); + return id in ctx.subscriptions + ? await emit.error(validationErrors) + : void 0; } const operationAST = getOperationAST( @@ -768,9 +772,11 @@ export function makeServer< execArgs.operationName, ); if (!operationAST) - return await emit.error([ - new GraphQLError('Unable to identify operation'), - ]); + return id in ctx.subscriptions + ? await emit.error([ + new GraphQLError('Unable to identify operation'), + ]) + : void 0; // if `onSubscribe` didn't specify a rootValue, inject one if (!('rootValue' in execArgs))