feat(frontend): useGameSocket hook with auto-reconnect
This commit is contained in:
72
Frontend/src/hooks/useGameSocket.ts
Normal file
72
Frontend/src/hooks/useGameSocket.ts
Normal file
@@ -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<InitFrame | null>(null);
|
||||
const [state, setState] = useState<StateFrame | null>(null);
|
||||
const wsRef = useRef<WebSocket | null>(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 };
|
||||
}
|
||||
Reference in New Issue
Block a user