Files
live-hub-py/web/frontend/src/hooks/useWebSocketLogs.ts
T

105 lines
2.6 KiB
TypeScript

import { useCallback, useEffect, useRef, useState } from 'react';
export interface RealtimeLog {
level: string;
message: string;
}
interface ConnectOptions {
clear?: boolean;
onClose?: () => void;
onError?: () => void;
onResult?: () => void;
}
const MAX_LOGS = 1000;
function toWebSocketUrl(pathOrUrl: string): string {
if (pathOrUrl.startsWith('ws://') || pathOrUrl.startsWith('wss://')) {
return pathOrUrl;
}
const protocol = window.location.protocol === 'https:' ? 'wss' : 'ws';
const path = pathOrUrl.startsWith('/') ? pathOrUrl : `/${pathOrUrl}`;
return `${protocol}://${window.location.host}${path}`;
}
export function useWebSocketLogs() {
const [logs, setLogs] = useState<RealtimeLog[]>([]);
const [connected, setConnected] = useState(false);
const wsRef = useRef<WebSocket | null>(null);
const callbacksRef = useRef<ConnectOptions>({});
const suppressCloseRef = useRef(false);
const clearLogs = useCallback(() => {
setLogs([]);
}, []);
const close = useCallback((notify = false) => {
if (!wsRef.current) {
setConnected(false);
return;
}
suppressCloseRef.current = !notify;
wsRef.current.close();
wsRef.current = null;
setConnected(false);
}, []);
const connect = useCallback((pathOrUrl: string, options: ConnectOptions = {}) => {
close(false);
suppressCloseRef.current = false;
callbacksRef.current = options;
if (options.clear ?? true) {
setLogs([]);
}
const ws = new WebSocket(toWebSocketUrl(pathOrUrl));
wsRef.current = ws;
setConnected(true);
ws.onmessage = (event) => {
try {
const msg = JSON.parse(event.data) as RealtimeLog;
if (msg.level === 'heartbeat') return;
if (msg.level === 'result') {
callbacksRef.current.onResult?.();
return;
}
setLogs((prev) => [...prev, msg].slice(-MAX_LOGS));
} catch {
setLogs((prev) => [...prev, { level: 'info', message: String(event.data) }].slice(-MAX_LOGS));
}
};
ws.onclose = () => {
const isCurrent = wsRef.current === ws;
const shouldNotify = isCurrent && !suppressCloseRef.current;
if (isCurrent) {
wsRef.current = null;
setConnected(false);
suppressCloseRef.current = false;
}
if (shouldNotify) {
callbacksRef.current.onClose?.();
}
};
ws.onerror = () => {
setConnected(false);
callbacksRef.current.onError?.();
};
}, [close]);
useEffect(() => () => {
close(false);
}, [close]);
return {
logs,
connected,
clearLogs,
connect,
close,
};
}