App Preview
This commit is contained in:
+86
-1
@@ -7,6 +7,7 @@ import { verify } from './servers/jwt';
|
||||
import { officerdb, TokenBlacklist } from 'officerdb';
|
||||
import { terminalWebsocket, initTerminalSidecars } from './servers/api/terminal/websocket';
|
||||
import { piWebsocket } from './servers/api/pi/websocket';
|
||||
import { findEntryBySlug, touchEntry } from './servers/api/dev-server/router';
|
||||
import officerWeb from './apps/officer-web/index.html';
|
||||
|
||||
const { PORT = '5000' } = process.env;
|
||||
@@ -15,13 +16,16 @@ type WSData = {
|
||||
userId: number;
|
||||
email: string;
|
||||
role: string;
|
||||
provider: 'terminal' | 'pi';
|
||||
provider: 'terminal' | 'pi' | 'dev-server';
|
||||
sandboxed: boolean;
|
||||
sessionId?: string;
|
||||
cwd?: string;
|
||||
command?: string;
|
||||
cols?: number;
|
||||
rows?: number;
|
||||
devServerPort?: number;
|
||||
devServerSlug?: string;
|
||||
wsProxyPath?: string;
|
||||
};
|
||||
|
||||
const handlers: Record<string, any> = {
|
||||
@@ -29,6 +33,56 @@ const handlers: Record<string, any> = {
|
||||
pi: piWebsocket,
|
||||
};
|
||||
|
||||
// Dev-server WebSocket proxy: bridges client WS ↔ upstream dev server WS (for HMR etc.)
|
||||
type UpstreamState = { ws: WebSocket; queue: (string | Buffer)[]; ready: boolean };
|
||||
const devServerUpstreams = new Map<ServerWebSocket<WSData>, UpstreamState>();
|
||||
|
||||
const devServerWebsocket = {
|
||||
open(ws: ServerWebSocket<WSData>) {
|
||||
const { devServerPort, wsProxyPath } = ws.data;
|
||||
const upstream = new WebSocket(`ws://localhost:${devServerPort}${wsProxyPath}`);
|
||||
const state: UpstreamState = { ws: upstream, queue: [], ready: false };
|
||||
devServerUpstreams.set(ws, state);
|
||||
|
||||
upstream.addEventListener('open', () => {
|
||||
state.ready = true;
|
||||
for (const msg of state.queue) upstream.send(msg);
|
||||
state.queue.length = 0;
|
||||
});
|
||||
|
||||
upstream.addEventListener('message', (event) => {
|
||||
ws.send(event.data as string | ArrayBuffer);
|
||||
});
|
||||
|
||||
upstream.addEventListener('close', () => {
|
||||
devServerUpstreams.delete(ws);
|
||||
ws.close();
|
||||
});
|
||||
|
||||
upstream.addEventListener('error', () => {
|
||||
devServerUpstreams.delete(ws);
|
||||
ws.close();
|
||||
});
|
||||
},
|
||||
message(ws: ServerWebSocket<WSData>, raw: string | Buffer) {
|
||||
const state = devServerUpstreams.get(ws);
|
||||
if (!state) return;
|
||||
if (state.ready) {
|
||||
state.ws.send(raw);
|
||||
} else {
|
||||
state.queue.push(raw);
|
||||
}
|
||||
},
|
||||
close(ws: ServerWebSocket<WSData>) {
|
||||
const state = devServerUpstreams.get(ws);
|
||||
if (state) {
|
||||
state.ws.close();
|
||||
devServerUpstreams.delete(ws);
|
||||
}
|
||||
},
|
||||
};
|
||||
handlers['dev-server'] = devServerWebsocket;
|
||||
|
||||
async function upgradeWs(req: Request, server: any, provider: 'terminal' | 'pi') {
|
||||
const token = new URL(req.url).searchParams.get('token');
|
||||
if (!token) return new Response('Unauthorized', { status: 401 });
|
||||
@@ -60,6 +114,33 @@ async function upgradeWs(req: Request, server: any, provider: 'terminal' | 'pi')
|
||||
}
|
||||
}
|
||||
|
||||
function upgradeDevServerWs(req: Request, server: any) {
|
||||
const url = new URL(req.url);
|
||||
const match = url.pathname.match(/^\/api\/dev-server-proxy\/([^/]+)(\/.*)?$/);
|
||||
if (!match) return new Response('Not found', { status: 404 });
|
||||
|
||||
const slug = match[1]!;
|
||||
const entry = findEntryBySlug(slug);
|
||||
if (!entry) return new Response('No dev server running', { status: 404 });
|
||||
|
||||
touchEntry(entry);
|
||||
|
||||
const wsProxyPath = match[2] || '/';
|
||||
const ok = server.upgrade(req, {
|
||||
data: {
|
||||
userId: 0,
|
||||
email: '',
|
||||
role: '',
|
||||
provider: 'dev-server' as const,
|
||||
sandboxed: false,
|
||||
devServerPort: entry.port,
|
||||
devServerSlug: slug,
|
||||
wsProxyPath,
|
||||
},
|
||||
});
|
||||
if (!ok) return new Response('Upgrade failed', { status: 500 });
|
||||
}
|
||||
|
||||
const server = serve({
|
||||
port: Number(PORT),
|
||||
idleTimeout: 60,
|
||||
@@ -71,6 +152,10 @@ const server = serve({
|
||||
if (await file.exists()) return new Response(file);
|
||||
return new Response(null, { status: 404 });
|
||||
},
|
||||
'/api/dev-server-proxy/*': (req, server) => {
|
||||
if (req.headers.get('upgrade') === 'websocket') return upgradeDevServerWs(req, server);
|
||||
return honoServer.fetch(req, server);
|
||||
},
|
||||
'/api/terminal/ws': (req, server) => upgradeWs(req, server, 'terminal'),
|
||||
'/api/pi/chat/ws': (req, server) => upgradeWs(req, server, 'pi'),
|
||||
'/': officerWeb,
|
||||
|
||||
Reference in New Issue
Block a user