diff --git a/apps/api/src/routes/servers.ts b/apps/api/src/routes/servers.ts index fad546c..b746de3 100644 --- a/apps/api/src/routes/servers.ts +++ b/apps/api/src/routes/servers.ts @@ -227,6 +227,19 @@ export async function serverRoutes(app: FastifyInstance): Promise { // so each chunk lands at the client immediately rather than after the // full response is built — critical for the keepalive-vs-CF-100s logic // to actually work. + // + // CORS note: @fastify/cors injects Access-Control-Allow-* in the onSend + // hook, which never runs once we go straight to reply.raw — that's why + // the browser saw "blocked by CORS policy". Set the headers manually + // here, mirroring what the plugin would have added: credentials:true + + // the configured app origin. The exact reflected Origin is fine because + // we already pinned it in the cors plugin registration. + const origin = req.headers.origin; + if (origin && origin === config.NEXT_PUBLIC_APP_URL) { + reply.raw.setHeader('Access-Control-Allow-Origin', origin); + reply.raw.setHeader('Access-Control-Allow-Credentials', 'true'); + reply.raw.setHeader('Vary', 'Origin'); + } reply.raw.setHeader('Content-Type', 'text/event-stream'); reply.raw.setHeader('Cache-Control', 'no-cache, no-transform'); reply.raw.setHeader('Connection', 'keep-alive');