fix(chat): prevent stale websocket close handler from triggering reconnect loop

This commit is contained in:
zwitschi committed 2026-08-15 16:28:32 +02:00
1 parent cef002862e
commit b6705e6dbd
2 files changed
+36 -5

No files matched your search

+19
View File
@@ -253,6 +253,25 @@ describe("WebSocket reconnect", () => {
expect(websocketInstances).toHaveLength(2); expect(websocketInstances).toHaveLength(2);
expect(websocketInstances[1].url).toContain("room=lobby"); expect(websocketInstances[1].url).toContain("room=lobby");
}); });
it("does not reconnect from a socket that was intentionally replaced", async () => {
vi.useFakeTimers();
window.switchRoom("lobby");
expect(websocketInstances).toHaveLength(1);
const replaced = websocketInstances[0];
// A room switch intentionally replaces the socket; the old socket must not
// schedule its own reconnect, otherwise history is re-pulled on a loop.
window.switchRoom("main");
expect(websocketInstances).toHaveLength(2);
expect(replaced.onclose).toBeNull();
replaced.onclose?.();
await vi.advanceTimersByTimeAsync(3000);
expect(websocketInstances).toHaveLength(2);
});
}); });
describe("URL resolution", () => { describe("URL resolution", () => {
+17 -5
View File
@@ -79,19 +79,28 @@ export function resolveManifestUrl() {
} }
function connectWebSocket(room: string) { function connectWebSocket(room: string) {
if (ws) ws.close(); // Detach the old socket's close handler before closing it. Otherwise an
// intentional reconnect (login/logout/room switch/session refresh) would
// have the replaced socket schedule yet another reconnect, and each new
// connection re-pulls the room's history — filling the panel with old
// messages on a loop.
if (ws) {
ws.onclose = null;
ws.close();
}
const socketUrl = new URL(resolveWebSocketBaseUrl()); const socketUrl = new URL(resolveWebSocketBaseUrl());
socketUrl.searchParams.set("username", username); socketUrl.searchParams.set("username", username);
socketUrl.searchParams.set("room", room); socketUrl.searchParams.set("room", room);
ws = new WebSocket(socketUrl); const socket = new WebSocket(socketUrl);
ws = socket;
ws.onopen = () => { socket.onopen = () => {
console.log(`Connected to chat room: ${room}`); console.log(`Connected to chat room: ${room}`);
addMessage("system", "Connected to " + room); addMessage("system", "Connected to " + room);
}; };
ws.onmessage = (event) => { socket.onmessage = (event) => {
const msg = JSON.parse(event.data); const msg = JSON.parse(event.data);
if (msg.type === "typing") { if (msg.type === "typing") {
showTyping(msg.username); showTyping(msg.username);
@@ -119,7 +128,10 @@ function connectWebSocket(room: string) {
addMessage(msg.type || "public", msg.text, msg.username, msg.timestamp); addMessage(msg.type || "public", msg.text, msg.username, msg.timestamp);
}; };
ws.onclose = () => { socket.onclose = () => {
// Ignore close events from sockets that were intentionally replaced.
if (ws !== socket) return;
ws = null;
console.log("Chat disconnected. Reconnecting in 3s..."); console.log("Chat disconnected. Reconnecting in 3s...");
setTimeout(() => connectWebSocket(room), 3000); setTimeout(() => connectWebSocket(room), 3000);
}; };