import { buildRoomStatus } from "@/lib/buildRoomStatus"; import { getRoomData } from "@/lib/store"; import { subscribe } from "@/lib/roomEvents"; export const dynamic = "force-dynamic"; export async function GET( req: Request, { params }: { params: Promise<{ id: string }> }, ) { const { id } = await params; const url = new URL(req.url); const userId = url.searchParams.get("userId"); if (userId) { const data = await getRoomData(id); if (data && !data.users.includes(userId)) { return new Response(JSON.stringify({ error: "not_a_member" }), { status: 403, headers: { "Content-Type": "application/json" }, }); } } const encoder = new TextEncoder(); const stream = new ReadableStream({ start(controller) { const send = (obj: object) => { try { controller.enqueue( encoder.encode(`data: ${JSON.stringify(obj)}\n\n`), ); } catch { /* controller already closed */ } }; let alive = true; (async () => { try { const status = await buildRoomStatus(id); if (!status) { send({ error: "room_not_found" }); controller.close(); return; } if (alive) send(status); } catch { send({ error: "load_failed" }); controller.close(); } })(); const unsubscribe = subscribe(id, async () => { if (!alive) return; try { const status = await buildRoomStatus(id); if (status && alive) send(status); } catch { /* ignore transient read errors */ } }); const heartbeat = setInterval(() => { try { controller.enqueue(encoder.encode(": heartbeat\n\n")); } catch { clearInterval(heartbeat); } }, 30_000); req.signal.addEventListener("abort", () => { alive = false; unsubscribe(); clearInterval(heartbeat); try { controller.close(); } catch { /* already closed */ } }); }, }); return new Response(stream, { headers: { "Content-Type": "text/event-stream", "Cache-Control": "no-cache, no-transform", Connection: "keep-alive", }, }); }