-
Notifications
You must be signed in to change notification settings - Fork 807
Expand file tree
/
Copy pathroute.js
More file actions
79 lines (67 loc) · 2.34 KB
/
route.js
File metadata and controls
79 lines (67 loc) · 2.34 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
import { getConsoleLogs, getConsoleEmitter, initConsoleLogCapture } from "@/lib/consoleLogBuffer";
export const dynamic = "force-dynamic";
initConsoleLogCapture();
export async function GET(request) {
const encoder = new TextEncoder();
const emitter = getConsoleEmitter();
const state = { closed: false, send: null, sendClear: null, keepalive: null };
// Idempotent: safe to call from request.signal abort, cancel(), or enqueue failure.
const cleanup = () => {
if (state.closed) return;
state.closed = true;
if (state.send) emitter.off("line", state.send);
if (state.sendClear) emitter.off("clear", state.sendClear);
if (state.keepalive) clearInterval(state.keepalive);
};
// request.signal fires reliably on client disconnect; ReadableStream.cancel()
// is not always invoked in Next.js, which caused listeners to accumulate.
request.signal.addEventListener("abort", cleanup, { once: true });
const stream = new ReadableStream({
start(controller) {
// Send all buffered logs immediately on connect
const buffered = getConsoleLogs();
if (buffered.length > 0) {
controller.enqueue(encoder.encode(`data: ${JSON.stringify({ type: "init", logs: buffered })}\n\n`));
}
// Push new lines as they arrive
state.send = (line) => {
if (state.closed) return;
try {
controller.enqueue(encoder.encode(`data: ${JSON.stringify({ type: "line", line })}\n\n`));
} catch {
cleanup();
}
};
// Notify client when cleared
state.sendClear = () => {
if (state.closed) return;
try {
controller.enqueue(encoder.encode(`data: ${JSON.stringify({ type: "clear" })}\n\n`));
} catch {
cleanup();
}
};
emitter.on("line", state.send);
emitter.on("clear", state.sendClear);
// Keepalive ping every 25s
state.keepalive = setInterval(() => {
if (state.closed) { clearInterval(state.keepalive); return; }
try {
controller.enqueue(encoder.encode(": ping\n\n"));
} catch {
cleanup();
}
}, 25000);
},
cancel() {
cleanup();
},
});
return new Response(stream, {
headers: {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
"Connection": "keep-alive",
},
});
}