Files
EpicNext-Cms/src/app/api/radio/stream/route.ts
T
openhands dc8fb8a6ed feat: speed up admin clone import and enable Cache Components
- Clone import: defer FurnitureData.json writes and append all entries in a
  single batched write instead of one read-modify-write per item, removing
  the main serialization bottleneck for large batches.
- Clone import: raise SSE batch concurrency cap from 5 to 10 and bump the
  clone client/route default from 2 to 6.
- Add a flush hook to runSseBatch so callers can batch deferred work before
  batch_complete is emitted, and surface flush errors as an error event.
- Enable Next.js Cache Components (instant: false opt-out) and silence the
  related build warnings in next.config.ts.
- Switch isomorphic-dompurify to dompurify and refresh dependencies.
2026-08-05 11:10:16 +02:00

68 lines
1.6 KiB
TypeScript

import { fetchListeners, fetchNowPlaying } from "@/lib/services/radio";
/**
* Server-Sent Events stream of live radio state (AtomCMS's radio SSE endpoint).
* Pushes { nowPlaying, listeners } every ~10s so players/widgets get real-time
* updates without polling. Closes cleanly when the client disconnects.
*/
export async function GET(req: Request) {
const encoder = new TextEncoder();
const stream = new ReadableStream<Uint8Array>({
async start(controller) {
let closed = false;
const send = async () => {
if (closed) return;
const [nowPlaying, listeners] = await Promise.all([
fetchNowPlaying().catch(() => null),
fetchListeners().catch(() => null),
]);
try {
controller.enqueue(
encoder.encode(
`data: ${JSON.stringify({ nowPlaying, listeners })}\n\n`,
),
);
} catch {
closed = true;
}
};
// Initial event immediately, then on an interval.
await send();
const interval = setInterval(() => void send(), 10_000);
// SSE comment as a keep-alive ping between data events.
const ping = setInterval(() => {
if (!closed) {
try {
controller.enqueue(encoder.encode(": ping\n\n"));
} catch {
closed = true;
}
}
}, 25_000);
const stop = () => {
closed = true;
clearInterval(interval);
clearInterval(ping);
try {
controller.close();
} catch {
/* already closed */
}
};
req.signal.addEventListener("abort", stop);
},
});
return new Response(stream, {
headers: {
"content-type": "text/event-stream; charset=utf-8",
"cache-control": "no-store, no-transform",
connection: "keep-alive",
},
});
}