46 lines
2.5 KiB
TypeScript
46 lines
2.5 KiB
TypeScript
import { useQueryClient } from '@tanstack/react-query';
|
|
import { createContext, useContext, useEffect, useMemo, useRef, useState, type PropsWithChildren } from 'react';
|
|
import type { DashboardEvent } from '../../../../packages/api-contracts/src/index.js';
|
|
import {
|
|
createBrowserEventSource, createDashboardRealtimeClient, queryKeysForDashboardEvent,
|
|
type RealtimeConnectionSnapshot,
|
|
} from '../features/operations/realtime-client.js';
|
|
import { browserPollingScheduler, browserVisibilitySource, createVisibilityAwarePollingFallback } from '../features/operations/realtime-fallback.js';
|
|
import { repairRealtimeSnapshots } from './realtime-repair.js';
|
|
|
|
type EventListener = (event: DashboardEvent) => void;
|
|
interface RealtimeContextValue { connection: RealtimeConnectionSnapshot; subscribe(listener: EventListener): () => void; }
|
|
const RealtimeContext = createContext<RealtimeContextValue | null>(null);
|
|
|
|
export function DashboardRealtimeProvider({ children }: PropsWithChildren) {
|
|
const queryClient = useQueryClient();
|
|
const listeners = useRef(new Set<EventListener>());
|
|
const [connection, setConnection] = useState<RealtimeConnectionSnapshot>({ status: 'idle', isStale: true });
|
|
useEffect(() => {
|
|
const repair = () => { void repairRealtimeSnapshots(queryClient); };
|
|
const fallback = createVisibilityAwarePollingFallback({ intervalMs: 15_000, poll: repair, scheduler: browserPollingScheduler, visibility: browserVisibilitySource });
|
|
fallback.start();
|
|
if (typeof EventSource === 'undefined') { fallback.setStale(true); return () => fallback.stop(); }
|
|
const client = createDashboardRealtimeClient({
|
|
sourceFactory: createBrowserEventSource,
|
|
onConnected: repair,
|
|
onEvent(event) {
|
|
for (const queryKey of queryKeysForDashboardEvent(event)) void queryClient.invalidateQueries({ queryKey });
|
|
for (const listener of listeners.current) listener(event);
|
|
},
|
|
onStateChange(snapshot) { setConnection(snapshot); fallback.setStale(snapshot.isStale); },
|
|
});
|
|
client.start();
|
|
return () => { client.stop(); fallback.stop(); };
|
|
}, [queryClient]);
|
|
const value = useMemo<RealtimeContextValue>(() => ({
|
|
connection,
|
|
subscribe(listener) { listeners.current.add(listener); return () => listeners.current.delete(listener); },
|
|
}), [connection]);
|
|
return <RealtimeContext.Provider value={value}>{children}</RealtimeContext.Provider>;
|
|
}
|
|
|
|
export function useDashboardRealtime(): RealtimeContextValue {
|
|
return useContext(RealtimeContext) ?? { connection: { status: 'idle', isStale: true }, subscribe: () => () => undefined };
|
|
}
|