-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathserver.ts
More file actions
108 lines (96 loc) · 3.25 KB
/
server.ts
File metadata and controls
108 lines (96 loc) · 3.25 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
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
import { createServer } from "node:http";
import { createNodeLogger } from "@engram/logger";
import type { ServerWebSocket } from "bun";
import next from "next";
import { handleLogsConnection, handleMetricsConnection } from "./lib/websocket-server";
const logger = createNodeLogger({ service: "console", base: { component: "server" } });
const port = parseInt(process.env.PORT || "6185", 10);
const nextPort = port + 1; // Next.js runs on port+1 internally
const dev = process.env.NODE_ENV !== "production";
const app = next({ dev, hostname: "localhost", port: nextPort });
const handle = app.getRequestHandler();
/**
* WebSocket connection data types for Console
*/
interface WebSocketData {
type: "logs" | "metrics";
service?: string;
unsubscribe?: () => Promise<void>;
messageHandler?: (message: string | Buffer) => void;
}
app.prepare().then(() => {
// Start Next.js on internal port
const httpServer = createServer((req, res) => {
handle(req, res);
});
httpServer.listen(nextPort);
// Bun.serve handles WebSocket upgrades and proxies other requests to Next.js
Bun.serve<WebSocketData>({
port,
async fetch(req, server) {
const url = new URL(req.url);
const { pathname, searchParams } = url;
// Match /api/ws/logs (global log stream)
// Query params: ?service=api,ingestion,memory to filter by service
if (pathname === "/api/ws/logs") {
const service = searchParams.get("service") || undefined;
const upgraded = server.upgrade(req, {
data: { type: "logs", service },
});
if (upgraded) return undefined;
return new Response("WebSocket upgrade failed", { status: 400 });
}
// Match /api/ws/logs/:service (service-specific log stream)
if (pathname?.startsWith("/api/ws/logs/")) {
const parts = pathname.split("/");
// /api/ws/logs/api -> ['', 'api', 'ws', 'logs', 'api']
const service = parts[4];
if (service) {
const upgraded = server.upgrade(req, {
data: { type: "logs", service },
});
if (upgraded) return undefined;
return new Response("WebSocket upgrade failed", { status: 400 });
}
}
// Match /api/ws/metrics (real-time metrics stream)
if (pathname === "/api/ws/metrics") {
const upgraded = server.upgrade(req, {
data: { type: "metrics" },
});
if (upgraded) return undefined;
return new Response("WebSocket upgrade failed", { status: 400 });
}
// Proxy all other requests to Next.js
const nextUrl = new URL(req.url.replace(`:${port}`, `:${nextPort}`));
return fetch(nextUrl, {
method: req.method,
headers: req.headers,
body: req.body,
});
},
websocket: {
open(ws: ServerWebSocket<WebSocketData>) {
const { type, service } = ws.data;
if (type === "logs") {
handleLogsConnection(ws, service);
} else if (type === "metrics") {
handleMetricsConnection(ws);
}
},
message(ws: ServerWebSocket<WebSocketData>, message: string | Buffer) {
// Delegate to the handler stored in ws.data
if (ws.data.messageHandler) {
ws.data.messageHandler(message);
}
},
close(ws: ServerWebSocket<WebSocketData>) {
// Call cleanup callback if set
if (ws.data.unsubscribe) {
ws.data.unsubscribe();
}
},
},
});
logger.info({ port }, "Console server ready with WebSocket support");
});