58 lines
1.7 KiB
TypeScript
58 lines
1.7 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect } from "react";
|
|
import { useSWRConfig } from "swr";
|
|
|
|
/**
|
|
* A headless component that maintains a Server-Sent Events (SSE) connection
|
|
* to the Postgres database. When the backend agent mutates this project,
|
|
* this component receives a ping and aggressively revalidates SWR.
|
|
*/
|
|
export function ProjectStreamHandler({ projectId }: { projectId: string }) {
|
|
const { mutate } = useSWRConfig();
|
|
|
|
useEffect(() => {
|
|
if (!projectId) return;
|
|
|
|
let eventSource: EventSource | null = null;
|
|
let retryTimeout: NodeJS.Timeout;
|
|
|
|
const connect = () => {
|
|
eventSource = new EventSource(`/api/projects/${projectId}/stream`);
|
|
|
|
eventSource.onmessage = (event) => {
|
|
try {
|
|
const data = JSON.parse(event.data);
|
|
if (data.event === "updated") {
|
|
// The database row for this project was mutated!
|
|
// Find all SWR cache keys that belong to this project and invalidate them.
|
|
// This instantly updates the Plan tab, Task Kanban, and Anatomy overview.
|
|
mutate(
|
|
(key) => typeof key === "string" && key.includes(projectId),
|
|
undefined,
|
|
{ revalidate: true }
|
|
);
|
|
}
|
|
} catch (err) {
|
|
console.error("[SSE] Parse error", err);
|
|
}
|
|
};
|
|
|
|
eventSource.onerror = () => {
|
|
eventSource?.close();
|
|
// If the connection drops (e.g. server restart or network dip), back off and try again
|
|
retryTimeout = setTimeout(connect, 5000);
|
|
};
|
|
};
|
|
|
|
connect();
|
|
|
|
return () => {
|
|
eventSource?.close();
|
|
clearTimeout(retryTimeout);
|
|
};
|
|
}, [projectId, mutate]);
|
|
|
|
return null;
|
|
}
|