fix(webcrt): install websockets and connect WS to API in Vite dev

Uvicorn had no WebSocket implementation without the websockets package, causing browser websocket_error. Also bypass flaky Vite WS proxy by targeting :8890 in local UI.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-07-29 17:37:32 +08:00
parent 183cb7eec9
commit 1f2e028337
5 changed files with 23 additions and 3 deletions

View file

@ -12,6 +12,7 @@ license = { text = "MIT" }
dependencies = [
"fastapi>=0.115.0",
"uvicorn>=0.30.0",
"websockets>=12.0",
"httpx>=0.27.0",
"sqlalchemy>=2.0.0",
"psycopg[binary]>=3.2.0",

View file

@ -1,5 +1,6 @@
fastapi>=0.115.0
uvicorn>=0.30.0
websockets>=12.0
httpx>=0.27.0
sqlalchemy>=2.0.0
psycopg[binary]>=3.2.0

View file

@ -113,10 +113,14 @@ export function WebTerminal({ wsUrl, title, onStatus, onReady }: Props) {
ws.onerror = () => {
onStatusRef.current?.("error", "websocket_error");
term.writeln("\r\n\x1b[31m[websocket error]\x1b[0m");
term.writeln("\x1b[33mHint: ensure API has package 'websockets' and is restarted; Vite UI should reach ws://127.0.0.1:8890\x1b[0m");
};
ws.onclose = () => {
onStatusRef.current?.("closed", "websocket_closed");
ws.onclose = (ev) => {
onStatusRef.current?.("closed", `websocket_closed:${ev.code}`);
if (!ev.wasClean) {
term.writeln(`\r\n\x1b[33m[websocket closed code=${ev.code}]\x1b[0m`);
}
};
const dataDisposable = term.onData((data) => {

View file

@ -352,7 +352,19 @@ export const closeWebcrtSession = (sessionId: string) =>
export const webcrtWsUrl = (sessionId: string): string => {
const proto = window.location.protocol === "https:" ? "wss:" : "ws:";
return `${proto}//${window.location.host}/v1/webcrt/sessions/${encodeURIComponent(sessionId)}/ws`;
const path = `/v1/webcrt/sessions/${encodeURIComponent(sessionId)}/ws`;
// Optional override, e.g. ws://127.0.0.1:8890
const override = String((import.meta as ImportMeta & { env?: Record<string, string> }).env?.VITE_NETX_WS_BASE || "").trim();
if (override) {
return `${override.replace(/\/$/, "")}${path}`;
}
// Vite/preview: HTTP is proxied, but WS proxy is often flaky — hit API directly.
const port = window.location.port;
if (port === "5173" || port === "4173") {
const apiHost = window.location.hostname === "localhost" ? "127.0.0.1" : window.location.hostname;
return `${proto}//${apiHost}:8890${path}`;
}
return `${proto}//${window.location.host}${path}`;
};
export const fetchCliMeta = () => apiGet<CliMeta>("/v1/cli/meta");

View file

@ -11,6 +11,8 @@ export default defineConfig({
target: "http://127.0.0.1:8890",
changeOrigin: true,
ws: true,
// Keep Origin aligned with upstream for WS handshake through proxy.
rewriteWsOrigin: true,
},
},
},