Files
HiveGrid-Wireframes/03 Quellen und Referenzen/hivegrid-cloud/apps/web/app/utils/stream/useBoardLiveUpdates.ts
T

49 lines
2.1 KiB
TypeScript

// R-P8b (RA-C2) — client hook that turns the pipeline-board SSE stream into live
// board updates. Opens an EventSource on the board's `sseHref`, and on every real
// `projects.board.changed` change-event revalidates the route so the server
// rebuilds the KanbanBoardVM (loader/VM boundary intact — no client re-compute).
// Snapshot-on-connect and the heartbeat are ignored here; only real change-events
// trigger a revalidation, so there is no polling loop. Route-side wiring keeps the
// screen component pure (no router hooks in the presentational board).
import { useEffect, useRef } from "react";
import { useRevalidator } from "react-router";
import { PROJECT_BOARD_SSE_EVENT } from "~/utils/integrations/projects/projectBoardEvents.shared";
export type BoardLiveUpdatesOptions = {
/**
* The route is already loading/submitting. Its own completion revalidation
* will include the event, so starting another loader here only duplicates
* work and can make an optimistic drop stutter.
*/
paused?: boolean;
};
export function useBoardLiveUpdates(
sseHref: string | null | undefined,
options: BoardLiveUpdatesOptions = {},
): void {
const revalidator = useRevalidator();
// Keep the latest revalidate/state in a ref so the EventSource effect does not
// re-subscribe on every revalidator tick (which would tear down the stream).
const revalidateRef = useRef(revalidator.revalidate);
revalidateRef.current = revalidator.revalidate;
const stateRef = useRef(revalidator.state);
stateRef.current = revalidator.state;
const pausedRef = useRef(Boolean(options.paused));
pausedRef.current = Boolean(options.paused);
useEffect(() => {
if (!sseHref || typeof window === "undefined" || typeof EventSource === "undefined") return;
const source = new EventSource(sseHref);
const handler = () => {
if (!pausedRef.current && stateRef.current === "idle") revalidateRef.current();
};
source.addEventListener(PROJECT_BOARD_SSE_EVENT, handler);
return () => {
source.removeEventListener(PROJECT_BOARD_SSE_EVENT, handler);
source.close();
};
}, [sseHref]);
}