58 lines
1.8 KiB
TypeScript
58 lines
1.8 KiB
TypeScript
import { createContext, useCallback, useContext, useEffect, useRef, useState } from 'react';
|
|
|
|
export type WsStatus = 'connecting' | 'connected' | 'disconnected';
|
|
|
|
interface WsContextValue {
|
|
status: WsStatus;
|
|
send: (msg: string) => void;
|
|
subscribe: (handler: (msg: string) => void) => () => void;
|
|
}
|
|
|
|
const WsContext = createContext<WsContextValue>({
|
|
status: 'disconnected',
|
|
send: () => {},
|
|
subscribe: () => () => {},
|
|
});
|
|
|
|
export function WsProvider({ children }: { children: React.ReactNode }) {
|
|
const wsRef = useRef<WebSocket | null>(null);
|
|
const listeners = useRef<Set<(msg: string) => void>>(new Set());
|
|
const [status, setStatus] = useState<WsStatus>('connecting');
|
|
|
|
useEffect(() => {
|
|
let cancelled = false;
|
|
let ws: WebSocket | null = null;
|
|
|
|
const connect = () => {
|
|
if (cancelled) return;
|
|
ws = new WebSocket(`ws://${window.location.hostname}/ws`);
|
|
wsRef.current = ws;
|
|
setStatus('connecting');
|
|
ws.onopen = () => { if (!cancelled) setStatus('connected'); };
|
|
ws.onmessage = (e) => { if (!cancelled) listeners.current.forEach(h => h(String(e.data))); };
|
|
ws.onclose = () => { if (!cancelled) { setStatus('disconnected'); setTimeout(connect, 3000); } };
|
|
ws.onerror = () => ws?.close();
|
|
};
|
|
|
|
connect();
|
|
return () => { cancelled = true; ws?.close(); };
|
|
}, []);
|
|
|
|
const send = useCallback((msg: string) => {
|
|
if (wsRef.current?.readyState === WebSocket.OPEN) wsRef.current.send(msg);
|
|
}, []);
|
|
|
|
const subscribe = useCallback((handler: (msg: string) => void) => {
|
|
listeners.current.add(handler);
|
|
return () => { listeners.current.delete(handler); };
|
|
}, []);
|
|
|
|
return (
|
|
<WsContext.Provider value={{ status, send, subscribe }}>
|
|
{children}
|
|
</WsContext.Provider>
|
|
);
|
|
}
|
|
|
|
export const useWs = () => useContext(WsContext);
|