This repository has been archived on 2026-06-07. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
master-ai/vibn-frontend/app/api/projects/[projectId]/stream/route.ts

79 lines
2.0 KiB
TypeScript

import { getPool } from "@/lib/db-postgres";
import { authSession } from "@/lib/auth/session-server";
export const dynamic = "force-dynamic";
export async function GET(
req: Request,
ctx: { params: Promise<{ projectId: string }> },
) {
const { projectId } = await ctx.params;
const session = await authSession();
if (!session?.user?.email) {
return new Response("Unauthorized", { status: 401 });
}
const encoder = new TextEncoder();
const stream = new ReadableStream({
async start(controller) {
const pool = getPool();
let client: import("pg").PoolClient;
try {
client = await pool.connect();
} catch (err) {
console.error("[SSE] Failed to connect to pg pool:", err);
controller.close();
return;
}
const notifyHandler = (msg: import("pg").Notification) => {
if (msg.payload === projectId) {
try {
controller.enqueue(encoder.encode(`data: {"event":"updated"}\n\n`));
} catch {
// controller might be closed
}
}
};
client.on("notification", notifyHandler);
await client.query("LISTEN project_updates");
// Keep alive ping every 15s to prevent browser/proxy from dropping the connection
const keepAlive = setInterval(() => {
try {
controller.enqueue(encoder.encode(`: ping\n\n`));
} catch {
cleanup();
}
}, 15000);
const cleanup = () => {
clearInterval(keepAlive);
if (client) {
client.removeListener("notification", notifyHandler);
client.release();
}
};
// When the client disconnects, clean up the DB connection
req.signal.addEventListener("abort", cleanup);
},
cancel() {
// Handled by abort listener
},
});
return new Response(stream, {
headers: {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache, no-transform",
Connection: "keep-alive",
"X-Accel-Buffering": "no",
},
});
}