fix: recover the websocket when a close goes unreported

This commit is contained in:
Carlos Valente
2026-09-15 18:20:35 +02:00
parent daeca0050d
commit 1d402385ac
2 changed files with 29 additions and 7 deletions
@@ -40,6 +40,10 @@ class MockWebSocket {
this.onclose?.();
}
/** Simulates a lost connection without an onclose event. */
closeSilently() {
this.readyState = MockWebSocket.CLOSED;
}
open() {
this.readyState = MockWebSocket.OPEN;
this.onopen?.();
@@ -146,6 +150,16 @@ describe('socket connection watchdog', () => {
expect(MockWebSocket.instances).toHaveLength(2);
});
it('recovers when a close goes unreported', () => {
const socket = openConnection();
// The watchdog is the only recovery path when no close event arrives.
socket.closeSilently();
vi.advanceTimersByTime(watchdogInterval + socketConfig.reconnectBaseInterval * 2);
expect(MockWebSocket.instances).toHaveLength(2);
});
it('ignores events from a socket which has been replaced', () => {
const stale = openConnection();
+15 -7
View File
@@ -320,13 +320,9 @@ function registerConnectionAttempt() {
* while paused, so extended silence is evidence of a dropped connection.
*/
function checkConnection() {
if (!websocket) {
return;
}
const silentFor = Date.now() - lastContact;
if (websocket.readyState === WebSocket.CONNECTING) {
if (websocket?.readyState === WebSocket.CONNECTING) {
// A connection attempt can otherwise hang indefinitely.
if (silentFor > socketConfig.connectTimeout) {
reconnectNow('WebSocket: connection attempt timed out');
@@ -334,8 +330,20 @@ function checkConnection() {
return;
}
if (websocket.readyState === WebSocket.OPEN && silentFor > socketConfig.silenceTimeout) {
reconnectNow('WebSocket: no data from server, reconnecting');
if (websocket?.readyState === WebSocket.OPEN) {
if (silentFor > socketConfig.silenceTimeout) {
reconnectNow('WebSocket: no data from server, reconnecting');
}
return;
}
/**
* The socket is closing, closed or was never created.
* Closing schedules its own reconnect, this is what covers us if that did not happen,
* so that there is no state the client can settle in without a way out.
*/
if (!reconnectTimeout) {
scheduleReconnect();
}
}