WebSockets
Real-time updates (opponent moves, presence) use API Gateway WebSockets.
Key files
MyWebSocket.js— connection lifecycle, subscribe, presence handling, periodic resyncGameWatch.js/useGameWatch.js— sync active game subscriptions to the serverstores/index.js—wsSend,desiredWatchGames,syncWatchGames,applyPresenceMessage,connectionswithseq
Mounted from Skeleton while the app runs.
Connection flow
- Obtain JWT via
getAuthToken(). - Open WebSocket to
WS_ENDPOINTfrom config. - On
open, send subscribe withtoken,invisible,watchVersion: 1, and optionalgames(the current desired watch list). - Server stores the connection (including
watchingGameswhengamesis provided), sends a presence snapshot, and debounces a join broadcast. GameWatchkeepsdesiredWatchGamesin Zustand and sendswatchGameswhenever the list changes, with retries at 250ms and 1s after connect or tab return.
Backend flow: WebSockets.
Game subscriptions
useGameWatch builds the watch set from:
- Active dashboard games (
globalMe.gamesin "my turn" or "their turn" — not completed) - The current game page (
/move/:metaGame/:cbits/:gameID)
When the set changes, the client sends watchGames with the full desired list. The server only delivers game events to matching subscribers.
Resync: scheduleWatchGamesSync() sends watchGames immediately and retries at 250ms and 1s. It runs on connect, when the watch list changes, and when the tab becomes visible while already connected. Pending retries are cancelled on disconnect.
On game messages, MyWebSocket dispatches:
refresh-me— dashboard refetchesme()for any watched-game updaterefresh-data— carries{ meta, id }from the WS payload; the open game page (GameMove) refetches only when those match the current route
Reconnection
Exponential backoff on disconnect:
- Initial delay: 2 seconds
- Maximum delay: 30 seconds
- Guards prevent duplicate connections while connecting or already open
- Tab visibility triggers reconnect after idle timeout (code 1001)
Presence
Incoming connections messages use snapshot/delta format:
// snapshot (subscribe, syncPresence)
{ type: "snapshot", seq, totalCount, visibleUserIds }
// delta (debounced join/leave)
{ type: "delta", seq, joins, leaves }
applyPresenceMessage updates Zustand connections (totalCount, visibleUserIds, seq):
- Snapshot — replace state
- Delta — apply joins/leaves; if
seqgap detected, callsyncPresence
Periodic resync: every 10 minutes while the tab is visible and the socket is open, send syncPresence (also on tab becoming visible). This caps drift from missed deltas.
Users can set invisible to opt out of visible presence (stored in Zustand).
Related
- Configuration —
WS_ENDPOINT - Authentication
- Dashboard
- Game move