From dc35a3b1b4f553ec26cf2112a697b0b5317b7021 Mon Sep 17 00:00:00 2001 From: meelstorm Date: Fri, 17 Jul 2026 17:54:34 +0000 Subject: [PATCH] feat(frontend): useGameSocket hook with auto-reconnect --- Frontend/src/hooks/useGameSocket.ts | 72 +++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 Frontend/src/hooks/useGameSocket.ts diff --git a/Frontend/src/hooks/useGameSocket.ts b/Frontend/src/hooks/useGameSocket.ts new file mode 100644 index 0000000..5a86677 --- /dev/null +++ b/Frontend/src/hooks/useGameSocket.ts @@ -0,0 +1,72 @@ +import { useEffect, useRef, useState } from 'react'; +import type { InitFrame, ServerFrame, StateFrame, CursorMessage } from '../protocol'; +import { isInit, isState } from '../protocol'; + +export interface GameSocketState { + connected: boolean; + init: InitFrame | null; + state: StateFrame | null; + sendCursor: (x: number, y: number) => void; +} + +/** + * Opens a WebSocket to the given URL, tracks the latest init/state frames. + * Auto-reconnects with exponential backoff on close. + */ +export function useGameSocket(url: string): GameSocketState { + const [connected, setConnected] = useState(false); + const [init, setInit] = useState(null); + const [state, setState] = useState(null); + const wsRef = useRef(null); + + useEffect(() => { + let closed = false; + let backoffMs = 500; + let reconnectTimer: number | null = null; + + const open = () => { + const ws = new WebSocket(url); + wsRef.current = ws; + + ws.onopen = () => { + setConnected(true); + backoffMs = 500; // reset backoff on successful connect + }; + ws.onclose = () => { + setConnected(false); + wsRef.current = null; + if (!closed) { + reconnectTimer = window.setTimeout(open, backoffMs); + backoffMs = Math.min(backoffMs * 2, 8000); + } + }; + ws.onerror = () => { /* let onclose handle it */ }; + ws.onmessage = (ev) => { + let frame: ServerFrame; + try { + frame = JSON.parse(ev.data) as ServerFrame; + } catch { + return; + } + if (isInit(frame)) setInit(frame); + else if (isState(frame)) setState(frame); + }; + }; + + open(); + return () => { + closed = true; + if (reconnectTimer !== null) window.clearTimeout(reconnectTimer); + wsRef.current?.close(); + }; + }, [url]); + + const sendCursor = (x: number, y: number) => { + const ws = wsRef.current; + if (!ws || ws.readyState !== WebSocket.OPEN) return; + const msg: CursorMessage = { type: 'cursor', x, y }; + ws.send(JSON.stringify(msg)); + }; + + return { connected, init, state, sendCursor }; +}