mirror of
https://github.com/fccview/cronmaster.git
synced 2026-01-03 03:18:59 -05:00
127 lines
3.1 KiB
TypeScript
127 lines
3.1 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useRef, useCallback } from "react";
|
|
import { SSEEvent, SSEEventType } from "@/app/_utils/sse-events";
|
|
|
|
type SSEEventHandler = (event: SSEEvent) => void;
|
|
type SSEErrorHandler = (error: Event) => void;
|
|
|
|
interface UseSSEOptions {
|
|
enabled?: boolean;
|
|
onEvent?: SSEEventHandler;
|
|
onError?: SSEErrorHandler;
|
|
onConnect?: () => void;
|
|
onDisconnect?: () => void;
|
|
}
|
|
|
|
/**
|
|
* Custom hook for consuming Server-Sent Events
|
|
*
|
|
* @param options Configuration options
|
|
* @returns Object with connection status and manual control functions
|
|
*
|
|
* @example
|
|
* ```tsx
|
|
* const { isConnected } = useSSE({
|
|
* enabled: true,
|
|
* onEvent: (event) => {
|
|
* if (event.type === 'job-started') {
|
|
* console.log('Job started:', event.data);
|
|
* }
|
|
* },
|
|
* });
|
|
* ```
|
|
*/
|
|
export const useSSE = (options: UseSSEOptions = {}) => {
|
|
const { enabled = true, onEvent, onError, onConnect, onDisconnect } = options;
|
|
|
|
const eventSourceRef = useRef<EventSource | null>(null);
|
|
const isConnectedRef = useRef(false);
|
|
const onEventRef = useRef(onEvent);
|
|
const onErrorRef = useRef(onError);
|
|
const onConnectRef = useRef(onConnect);
|
|
const onDisconnectRef = useRef(onDisconnect);
|
|
|
|
useEffect(() => {
|
|
onEventRef.current = onEvent;
|
|
onErrorRef.current = onError;
|
|
onConnectRef.current = onConnect;
|
|
onDisconnectRef.current = onDisconnect;
|
|
});
|
|
|
|
const connect = useCallback(() => {
|
|
if (eventSourceRef.current || !enabled) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const eventSource = new EventSource("/api/events");
|
|
|
|
eventSource.onopen = () => {
|
|
isConnectedRef.current = true;
|
|
onConnectRef.current?.();
|
|
};
|
|
|
|
eventSource.onerror = (error) => {
|
|
isConnectedRef.current = false;
|
|
onErrorRef.current?.(error);
|
|
|
|
if (eventSource.readyState === EventSource.CLOSED) {
|
|
onDisconnectRef.current?.();
|
|
}
|
|
};
|
|
|
|
const eventTypes: SSEEventType[] = [
|
|
"job-started",
|
|
"job-completed",
|
|
"job-failed",
|
|
"log-line",
|
|
"system-stats",
|
|
"heartbeat",
|
|
];
|
|
|
|
eventTypes.forEach((eventType) => {
|
|
eventSource.addEventListener(eventType, (event: MessageEvent) => {
|
|
try {
|
|
const data = JSON.parse(event.data) as SSEEvent;
|
|
onEventRef.current?.(data);
|
|
} catch (error) {
|
|
console.error(`[SSE] Failed to parse ${eventType} event:`, error);
|
|
}
|
|
});
|
|
});
|
|
|
|
eventSourceRef.current = eventSource;
|
|
} catch (error) {
|
|
console.error("[SSE] Failed to create EventSource:", error);
|
|
}
|
|
}, [enabled]);
|
|
|
|
const disconnect = useCallback(() => {
|
|
if (eventSourceRef.current) {
|
|
eventSourceRef.current.close();
|
|
eventSourceRef.current = null;
|
|
isConnectedRef.current = false;
|
|
onDisconnectRef.current?.();
|
|
}
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (enabled) {
|
|
connect();
|
|
} else {
|
|
disconnect();
|
|
}
|
|
|
|
return () => {
|
|
disconnect();
|
|
};
|
|
}, [enabled, connect, disconnect]);
|
|
|
|
return {
|
|
isConnected: isConnectedRef.current,
|
|
connect,
|
|
disconnect,
|
|
};
|
|
};
|