| | |
| | |
| | |
| | |
| | |
| | import { useEffect, useRef, useCallback } from "react"; |
| |
|
| | export type WSEventType = |
| | | "game_start" |
| | | "move" |
| | | "coaching_request" |
| | | "coaching_result" |
| | | "game_end" |
| | | "training_step" |
| | | "economy_update" |
| | | "pong"; |
| |
|
| | export interface WSMessage { |
| | type: WSEventType; |
| | data: Record<string, unknown>; |
| | } |
| |
|
| | interface UseBackendWSOptions { |
| | url: string; |
| | onMessage: (msg: WSMessage) => void; |
| | onOpen?: () => void; |
| | onClose?: () => void; |
| | enabled?: boolean; |
| | } |
| |
|
| | export function useBackendWS({ |
| | url, |
| | onMessage, |
| | onOpen, |
| | onClose, |
| | enabled = true, |
| | }: UseBackendWSOptions) { |
| | const wsRef = useRef<WebSocket | null>(null); |
| | const reconnectTimer = useRef<ReturnType<typeof setTimeout> | null>(null); |
| | const mountedRef = useRef(true); |
| |
|
| | const connect = useCallback(() => { |
| | if (!enabled || !mountedRef.current) return; |
| | if (wsRef.current?.readyState === WebSocket.OPEN) return; |
| |
|
| | try { |
| | const ws = new WebSocket(url); |
| | wsRef.current = ws; |
| |
|
| | ws.onopen = () => { |
| | if (onOpen) onOpen(); |
| | |
| | const ping = setInterval(() => { |
| | if (ws.readyState === WebSocket.OPEN) { |
| | ws.send(JSON.stringify({ action: "ping" })); |
| | } else { |
| | clearInterval(ping); |
| | } |
| | }, 30_000); |
| | }; |
| |
|
| | ws.onmessage = (event) => { |
| | try { |
| | const msg = JSON.parse(event.data) as WSMessage; |
| | onMessage(msg); |
| | } catch { |
| | |
| | } |
| | }; |
| |
|
| | ws.onclose = () => { |
| | if (onClose) onClose(); |
| | |
| | if (mountedRef.current && enabled) { |
| | reconnectTimer.current = setTimeout(connect, 3_000); |
| | } |
| | }; |
| |
|
| | ws.onerror = () => { |
| | ws.close(); |
| | }; |
| | } catch { |
| | |
| | } |
| | }, [url, onMessage, onOpen, onClose, enabled]); |
| |
|
| | const send = useCallback((action: string, payload: Record<string, unknown> = {}) => { |
| | if (wsRef.current?.readyState === WebSocket.OPEN) { |
| | wsRef.current.send(JSON.stringify({ action, ...payload })); |
| | } |
| | }, []); |
| |
|
| | const disconnect = useCallback(() => { |
| | if (reconnectTimer.current) clearTimeout(reconnectTimer.current); |
| | wsRef.current?.close(); |
| | }, []); |
| |
|
| | useEffect(() => { |
| | mountedRef.current = true; |
| | if (enabled) connect(); |
| | return () => { |
| | mountedRef.current = false; |
| | disconnect(); |
| | }; |
| | }, [connect, disconnect, enabled]); |
| |
|
| | return { send, disconnect, connect }; |
| | } |
| |
|