diff --git a/packages/dev-middleware/src/inspector-proxy/InspectorProxy.js b/packages/dev-middleware/src/inspector-proxy/InspectorProxy.js index 4cde8e9172f254..aa37d7de3d275b 100644 --- a/packages/dev-middleware/src/inspector-proxy/InspectorProxy.js +++ b/packages/dev-middleware/src/inspector-proxy/InspectorProxy.js @@ -295,6 +295,7 @@ export default class InspectorProxy implements InspectorProxyQueries { // // https://datatracker.ietf.org/doc/html/rfc6455#section-5.5.2 #startHeartbeat(socket: WS, intervalMs: number) { + let pingStatus: 'idle' | 'sending' = 'idle'; let terminateTimeout = null; const pingTimeout: Timeout = setTimeout(() => { @@ -303,27 +304,47 @@ export default class InspectorProxy implements InspectorProxyQueries { pingTimeout.refresh(); return; } - socket.ping(); - terminateTimeout = setTimeout(() => { - if (socket.readyState !== WS.OPEN) { + + pingStatus = 'sending'; + socket.ping(() => { + if (pingStatus !== 'sending') { + /** + * Sometimes, this `sent` callback fires later than + * the actual pong reply. + * + * If any message came in between ping `sending` and `sent`, + * then the connection exists; and we don't need to do anything. + */ return; } - // We don't use close() here because that initiates a closing handshake, - // which will not complete if the other end has gone away - 'close' - // would not be emitted. - // - // terminate() emits 'close' immediately, allowing us to handle it and - // inform any clients. - socket.terminate(); - }, MAX_PONG_LATENCY_MS).unref(); + + pingStatus = 'idle'; + terminateTimeout = setTimeout(() => { + if (socket.readyState !== WS.OPEN) { + return; + } + // We don't use close() here because that initiates a closing handshake, + // which will not complete if the other end has gone away - 'close' + // would not be emitted. + // + // terminate() emits 'close' immediately, allowing us to handle it and + // inform any clients. + socket.terminate(); + }, MAX_PONG_LATENCY_MS).unref(); + }); }, intervalMs).unref(); - socket.on('pong', () => { + const onAnyMessageFromDebugger = () => { + pingStatus = 'idle'; terminateTimeout && clearTimeout(terminateTimeout); pingTimeout.refresh(); - }); + }; + + socket.on('pong', onAnyMessageFromDebugger); + socket.on('message', onAnyMessageFromDebugger); socket.on('close', () => { + pingStatus = 'idle'; terminateTimeout && clearTimeout(terminateTimeout); clearTimeout(pingTimeout); });