import { onChat, recentChat, type ChatMessage } from "@/lib/chat"; export const dynamic = "force-dynamic"; /** SSE: 접속 시 최근 20개 → 이후 새 메시지 실시간 푸시 */ export async function GET(req: Request) { const encoder = new TextEncoder(); let unsubscribe: (() => void) | null = null; let heartbeat: ReturnType | null = null; const stream = new ReadableStream({ async start(controller) { const push = (msg: ChatMessage) => { try { controller.enqueue( encoder.encode(`id: ${msg.id}\ndata: ${JSON.stringify(msg)}\n\n`), ); } catch { /* 이미 닫힘 */ } }; // 실시간 구독을 먼저 연결 (백로그 DB 조회가 느려도 스트림은 즉시 열림) unsubscribe = onChat(push); controller.enqueue(encoder.encode(": connected\n\n")); void recentChat(20) .then((backlog) => backlog.forEach(push)) .catch(() => { /* DB 불가 시 백로그 생략, 실시간만 */ }); // Traefik/프록시 유휴 타임아웃 방지 heartbeat = setInterval(() => { try { controller.enqueue(encoder.encode(": ping\n\n")); } catch { /* ignore */ } }, 25000); req.signal.addEventListener("abort", () => { unsubscribe?.(); if (heartbeat) clearInterval(heartbeat); try { controller.close(); } catch { /* ignore */ } }); }, cancel() { unsubscribe?.(); if (heartbeat) clearInterval(heartbeat); }, }); return new Response(stream, { headers: { "Content-Type": "text/event-stream", "Cache-Control": "no-cache, no-transform", Connection: "keep-alive", }, }); }