* initial * refactor: centralize SSE plumbing for client and server * docs: add centralized SSE usage standards to agents guide * use sse to stream actions to the client * ui: align bulk progress toast with default sonner style * ui: remove hide action from bulk progress toast * full width progress bar * fix(stream): track client disconnect and writability * fix(stream): stop bulk loop when SSE client disconnects * fix(stream): avoid writing error/end to closed SSE response * fix(stream): gate started/progress frames on writable SSE socket * types(api): narrow SSE stream payload input contract * refactor(ui): share clamp helper for bulk progress * fix(stream): add heartbeat to bulk action SSE route * feat(stream): include completed count in bulk completion event * fix(client-sse): separate parse vs handler errors and cancel reader
46 lines
1.1 KiB
TypeScript
46 lines
1.1 KiB
TypeScript
import type { Response } from "express";
|
|
|
|
interface SetupSseOptions {
|
|
cacheControl?: string;
|
|
disableBuffering?: boolean;
|
|
flushHeaders?: boolean;
|
|
}
|
|
|
|
const DEFAULT_HEARTBEAT_MS = 30_000;
|
|
|
|
export function setupSse(res: Response, options: SetupSseOptions = {}): void {
|
|
res.status(200);
|
|
res.setHeader("Content-Type", "text/event-stream");
|
|
res.setHeader("Cache-Control", options.cacheControl ?? "no-cache");
|
|
res.setHeader("Connection", "keep-alive");
|
|
|
|
if (options.disableBuffering) {
|
|
res.setHeader("X-Accel-Buffering", "no");
|
|
}
|
|
|
|
if (options.flushHeaders) {
|
|
res.flushHeaders?.();
|
|
}
|
|
}
|
|
|
|
export function writeSseData(res: Response, data: unknown): void {
|
|
res.write(`data: ${JSON.stringify(data)}\n\n`);
|
|
}
|
|
|
|
export function writeSseComment(res: Response, comment: string): void {
|
|
res.write(`: ${comment}\n\n`);
|
|
}
|
|
|
|
export function startSseHeartbeat(
|
|
res: Response,
|
|
intervalMs = DEFAULT_HEARTBEAT_MS,
|
|
): () => void {
|
|
const heartbeat = setInterval(() => {
|
|
writeSseComment(res, "heartbeat");
|
|
}, intervalMs);
|
|
|
|
return () => {
|
|
clearInterval(heartbeat);
|
|
};
|
|
}
|