57 lines
2.1 KiB
TypeScript
57 lines
2.1 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useRef } from "react";
|
|
|
|
/** One stream per board/thread; open replies refresh through the board revision. */
|
|
export function useCommentEvents(scope: { target: string } | { admin: true }, refresh: (changed: boolean) => Promise<unknown>) {
|
|
const refreshRef = useRef(refresh);
|
|
useEffect(() => { refreshRef.current = refresh; }, [refresh]);
|
|
const query = "target" in scope ? `target=${encodeURIComponent(scope.target)}` : "scope=admin";
|
|
useEffect(() => {
|
|
let source: EventSource | null = null;
|
|
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
let refreshing = false;
|
|
let dirty = false;
|
|
let pendingChange = false;
|
|
let connectedBefore = false;
|
|
let disposed = false;
|
|
async function flush() {
|
|
timer = undefined;
|
|
if (disposed) return;
|
|
if (refreshing) { dirty = true; return; }
|
|
refreshing = true;
|
|
const changed = pendingChange;
|
|
pendingChange = false;
|
|
try { await refreshRef.current(changed); }
|
|
finally {
|
|
refreshing = false;
|
|
if (dirty && !disposed) { dirty = false; schedule(); }
|
|
}
|
|
}
|
|
function schedule(changed = false) {
|
|
pendingChange ||= changed;
|
|
if (!timer && !disposed) timer = setTimeout(() => { void flush(); }, 250);
|
|
}
|
|
function connect() {
|
|
source?.close(); source = null;
|
|
if (document.visibilityState !== "visible") return;
|
|
source = new EventSource(`/api/comments/events?${query}`);
|
|
source.addEventListener("changed", () => schedule(true));
|
|
// Catch updates between the initial page response and stream subscription,
|
|
// and updates missed while disconnected. EventSource handles reconnects.
|
|
source.addEventListener("open", () => {
|
|
// A reconnect can have missed reply edits even if the root is unchanged.
|
|
schedule(connectedBefore);
|
|
connectedBefore = true;
|
|
});
|
|
}
|
|
connect();
|
|
document.addEventListener("visibilitychange", connect);
|
|
return () => {
|
|
disposed = true; source?.close();
|
|
if (timer) clearTimeout(timer);
|
|
document.removeEventListener("visibilitychange", connect);
|
|
};
|
|
}, [query]);
|
|
}
|