import { subscribeAnnotateEvents } from "@/lib/annotate-events"; // SSE endpoint (PLAN.md §4): client เปิด EventSource ไปที่นี่เพื่อรับ annotate event // ของไฟล์เดียวกันแบบ realtime — event ถูก publish โดย Server Actions ผ่าน lib/annotate-events const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; export const dynamic = "force-dynamic"; export async function GET(req: Request): Promise { const fileId = new URL(req.url).searchParams.get("fileId") ?? ""; if (!UUID_RE.test(fileId)) { return new Response("fileId ต้องเป็น UUID", { status: 400 }); } const encoder = new TextEncoder(); let cleanup: () => void = () => {}; const stream = new ReadableStream({ start(controller) { const send = (event: unknown) => { controller.enqueue(encoder.encode(`data: ${JSON.stringify(event)}\n\n`)); }; send({ type: "connected" }); const unsubscribe = subscribeAnnotateEvents(fileId, send); // keep-alive กัน connection ถูกตัดเมื่อเงียบนาน (comment line — client ไม่เห็น) const ping = setInterval(() => { try { controller.enqueue(encoder.encode(": ping\n\n")); } catch { // stream ปิดไปแล้ว — cleanup ทำหน้าที่ตัด interval } }, 30_000); let done = false; cleanup = () => { if (done) return; done = true; clearInterval(ping); unsubscribe(); try { controller.close(); } catch { // ปิดอยู่แล้ว } }; // client ปิดแท็บ/ตัดการเชื่อมต่อ → unsubscribe คืน memory (rules/realtime.md) req.signal.addEventListener("abort", cleanup); }, cancel() { cleanup(); }, }); return new Response(stream, { headers: { "Content-Type": "text/event-stream", "Cache-Control": "no-cache, no-transform", Connection: "keep-alive", }, }); }