65 lines
1.6 KiB
TypeScript
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 };
|
|
}
|