fix(chat): prevent stale websocket close handler from triggering reconnect loop
This commit is contained in:
1 parent
cef002862e
commit
b6705e6dbd
2 files changed
+36
-5
No files matched your search
@@ -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
@@ -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);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in new issue
Block a user