/** * Heartbeat Web Worker * * Runs the session heartbeat interval in a Web Worker to avoid browser * tab throttling. Chrome throttles `setInterval` in background tabs to * ~1 execution per minute, which causes the 19-second heartbeat interval * to miss the 120-second drone timeout. Web Workers are NOT subject to * this throttling and will fire reliably regardless of tab visibility. * * Protocol: * - Main thread sends { type: "start" } to begin the heartbeat interval * - Main thread sends { type: "stop" } to stop the interval * - Worker posts { type: "tick" } to the main thread on each interval */ const HEARTBEAT_INTERVAL_MS = 19_000; // 19 seconds let intervalId: ReturnType | null = null; function start(): void { if (intervalId !== null) return; // already running intervalId = setInterval(() => { self.postMessage({ type: "tick" }); }, HEARTBEAT_INTERVAL_MS); } function stop(): void { if (intervalId !== null) { clearInterval(intervalId); intervalId = null; } } self.onmessage = (event: MessageEvent) => { const { type } = event.data; switch (type) { case "start": start(); break; case "stop": stop(); break; default: console.warn(`heartbeat.worker: unknown message type "${type}"`); } };