fix(client): reconnect the gateway socket when switching servers #113

Merged
icub3d merged 1 commit from fix/gateway-server-switch into main 2026-07-11 04:39:55 +00:00
Owner

Fixes live message updates silently dying after the first server switch.

Symptom

A chatbot reply (or any other user's message) never appeared in the client until the user clicked to another server and back — i.e. until a REST refetch. Verified server-side delivery was fine: a raw gateway socket subscribed to the channel received the MESSAGE_CREATE broadcasts instantly.

Root cause

Switching servers runs serverStore.connect(newAddress)gateway.reconnect(), but GatewayClient.connect() returned early whenever any socket was open:

if (this.socket && this.socket.readyState <= WebSocket.OPEN) {
  return;
}

That open socket is the one still connected to the previous server (nothing on the server-switch path ever calls disconnect() — only account switches do). From then on every SUBSCRIBE went down the stale socket and no live events from the current server arrived. REST fetches still worked, which is why switching away and back "fixed" it.

Fix

Track the address each socket was dialed for (socketAddress). When connect() finds an open socket whose address no longer matches the store, it drops it via a new dropSocket() — detaching all handlers first so the abandoned socket's late onclose can't null out the replacement or schedule a spurious reconnect — and dials the new server. Same-address reconnects still reuse the open socket as before.

Tests

New gateway.test.ts (stubbed WebSocket): dials store address with token subprotocol, reuses open socket for same address, drops + redials on address change, survives a late close from the abandoned socket, subscribes to the current channel on open. Full client suite: 94 passed; eslint + tsc clean.

🤖 Generated with Claude Code

Fixes live message updates silently dying after the first server switch. ## Symptom A chatbot reply (or any other user's message) never appeared in the client until the user clicked to another server and back — i.e. until a REST refetch. Verified server-side delivery was fine: a raw gateway socket subscribed to the channel received the `MESSAGE_CREATE` broadcasts instantly. ## Root cause Switching servers runs `serverStore.connect(newAddress)` → `gateway.reconnect()`, but `GatewayClient.connect()` returned early whenever *any* socket was open: ```ts if (this.socket && this.socket.readyState <= WebSocket.OPEN) { return; } ``` That open socket is the one still connected to the **previous** server (nothing on the server-switch path ever calls `disconnect()` — only account switches do). From then on every `SUBSCRIBE` went down the stale socket and no live events from the current server arrived. REST fetches still worked, which is why switching away and back "fixed" it. ## Fix Track the address each socket was dialed for (`socketAddress`). When `connect()` finds an open socket whose address no longer matches the store, it drops it via a new `dropSocket()` — detaching all handlers first so the abandoned socket's late `onclose` can't null out the replacement or schedule a spurious reconnect — and dials the new server. Same-address reconnects still reuse the open socket as before. ## Tests New `gateway.test.ts` (stubbed WebSocket): dials store address with token subprotocol, reuses open socket for same address, **drops + redials on address change**, survives a late close from the abandoned socket, subscribes to the current channel on open. Full client suite: 94 passed; eslint + tsc clean. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
fix(client): reconnect the gateway socket when switching servers
Some checks failed
Security Audit / Gitleaks (Secrets) (pull_request) Successful in 7s
Security Audit / Trivy (SCA & IaC) (pull_request) Failing after 18s
Security Audit / PNPM Audit (JS SCA) (pull_request) Failing after 19s
CI / Client — test & lint (pull_request) Successful in 38s
Security Audit / Semgrep (SAST) (pull_request) Failing after 1m5s
CI / Server — test & lint (pull_request) Successful in 2m59s
Security Audit / Cargo Audit (Rust SCA) (pull_request) Failing after 4m26s
58b71fbe06
Switching servers calls serverStore.connect() -> gateway.reconnect(),
but GatewayClient.connect() bailed out whenever any socket was open --
including the one still pointed at the previous server. After the first
server switch, SUBSCRIBEs went down the stale socket and no live events
from the current server ever arrived; messages only appeared on a REST
refetch (e.g. switching channels or servers).

Track the address each socket was dialed for and, when the store's
address no longer matches, drop the old socket (handlers detached so
its late close can't null out the replacement or schedule a spurious
reconnect) and dial the new server.

Adds GatewayClient unit tests with a stubbed WebSocket covering the
redial-on-switch, socket reuse, late-close, and subscribe-on-open
behaviors.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
icub3d merged commit 8b8ec734c5 into main 2026-07-11 04:39:55 +00:00
icub3d deleted branch fix/gateway-server-switch 2026-07-11 04:39:56 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
icub3d/decentcom!113
No description provided.