puck / frontend /src /lib /daemon.ts
vu1n's picture
Puck — desktop fairy familiar (HF Build Small)
3c124f3
Raw
History Blame Contribute Delete
2.03 kB
// Poll the local daemon for real wire events. Offline-tolerant by design:
// the sim is fully alive without a daemon, so failures are silent and the
// poll backs off until the daemon answers again.
import { isWireEvent, type WireEvent } from "../engine";
const POLL_MS = 3000;
const OFFLINE_BACKOFF_MS = 15000;
export function startDaemonPoll(onEvents: (events: WireEvent[]) => void): () => void {
let timer: ReturnType<typeof setTimeout> | undefined;
let stopped = false;
const tick = async () => {
if (stopped) return; // unmounted before first tick — never drain for a dead poller
let delay = POLL_MS;
try {
const res = await fetch("/api/events/pending", { signal: AbortSignal.timeout(2000) });
if (res.ok) {
const body: unknown = await res.json();
const list = (body as { events?: unknown[] }).events ?? [];
const valid = list.filter(isWireEvent);
// a malformed event on this path means the daemon let contract drift through — be loud
if (valid.length !== list.length)
console.error("puck: daemon sent events violating the wire schema", list);
// drain is destructive — a stopped poller (StrictMode's first mount) must not
// swallow events meant for the live one. Losing one drain to an unlucky unmount
// race is acceptable; silently eating every event on dev double-mount is not.
if (valid.length && !stopped) onEvents(valid);
} else {
delay = OFFLINE_BACKOFF_MS;
}
} catch {
delay = OFFLINE_BACKOFF_MS; // daemon not running — normal in Space/demo mode
}
if (!stopped) timer = setTimeout(tick, delay);
};
// drain is destructive, so the first fetch must not race React StrictMode's
// mount→unmount→mount: the doomed first instance is cleaned up synchronously,
// well inside this delay, so only the surviving poller ever touches the queue.
timer = setTimeout(tick, 350);
return () => {
stopped = true;
clearTimeout(timer);
};
}