diff --git a/.changeset/wise-geckos-beam.md b/.changeset/wise-geckos-beam.md new file mode 100644 index 0000000000..4240e34455 --- /dev/null +++ b/.changeset/wise-geckos-beam.md @@ -0,0 +1,5 @@ +--- +'graphql-yoga': minor +--- + +Ping the client every 12 seconds to keep the connection alive diff --git a/packages/graphql-yoga/src/plugins/resultProcessor/push.ts b/packages/graphql-yoga/src/plugins/resultProcessor/push.ts index 22676fa7e0..d2517c7c95 100644 --- a/packages/graphql-yoga/src/plugins/resultProcessor/push.ts +++ b/packages/graphql-yoga/src/plugins/resultProcessor/push.ts @@ -9,6 +9,8 @@ export function processPushResult( result: ResultProcessorInput, fetchAPI: FetchAPI, ): Response { + const timeoutInSeconds = 12 + const headersInit = { 'Content-Type': 'text/event-stream', Connection: 'keep-alive', @@ -20,9 +22,19 @@ export function processPushResult( let iterator: AsyncIterator> + let pingInterval: number const textEncoder = new fetchAPI.TextEncoder() const readableStream = new fetchAPI.ReadableStream({ - start() { + start(controller) { + // ping client every 12 seconds to keep the connection alive + pingInterval = setInterval(() => { + if (!controller.desiredSize) { + clearInterval(pingInterval) + return + } + controller.enqueue(textEncoder.encode(':\n\n')) + }, timeoutInSeconds * 1000) as unknown as number + if (isAsyncIterable(result)) { iterator = result[Symbol.asyncIterator]() } else { @@ -45,10 +57,12 @@ export function processPushResult( controller.enqueue(textEncoder.encode(`data: ${chunk}\n\n`)) } if (done) { + clearInterval(pingInterval) controller.close() } }, async cancel(e) { + clearInterval(pingInterval) await iterator.return?.(e) }, })