foxy-dev-team/frontend/src/api/useWebSocket.ts

65 lines
1.6 KiB
TypeScript

/**
* WebSocket hook for real-time updates from the backend.
*/
import { useEffect, useRef, useCallback, useState } from 'react';
export interface WSMessage {
type: 'agent_status' | 'log' | 'project_update' | 'pong';
data: Record<string, unknown>;
}
type WSCallback = (msg: WSMessage) => void;
export function useWebSocket(onMessage: WSCallback) {
const wsRef = useRef<WebSocket | null>(null);
const [connected, setConnected] = useState(false);
const reconnectTimer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
const connect = useCallback(() => {
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const url = `${protocol}//${window.location.host}/ws/live`;
const ws = new WebSocket(url);
wsRef.current = ws;
ws.onopen = () => {
setConnected(true);
// Start ping interval
const pingInterval = setInterval(() => {
if (ws.readyState === WebSocket.OPEN) ws.send('ping');
}, 30000);
ws.addEventListener('close', () => clearInterval(pingInterval));
};
ws.onmessage = (evt) => {
try {
const msg = JSON.parse(evt.data) as WSMessage;
onMessage(msg);
} catch {
// ignore parse errors
}
};
ws.onclose = () => {
setConnected(false);
// Auto-reconnect after 3s
reconnectTimer.current = setTimeout(connect, 3000);
};
ws.onerror = () => {
ws.close();
};
}, [onMessage]);
useEffect(() => {
connect();
return () => {
clearTimeout(reconnectTimer.current);
wsRef.current?.close();
};
}, [connect]);
return { connected };
}