diff --git a/ui/package.json b/ui/package.json index e0162770999..3799b1e0800 100644 --- a/ui/package.json +++ b/ui/package.json @@ -21,9 +21,11 @@ "vite": "8.0.0" }, "devDependencies": { + "@types/ws": "^8.18.1", "@vitest/browser-playwright": "4.1.0", "jsdom": "^28.1.0", "playwright": "^1.58.2", - "vitest": "4.1.0" + "vitest": "4.1.0", + "ws": "^8.19.0" } } diff --git a/ui/src/ui/server/proxy.ts b/ui/src/ui/server/proxy.ts deleted file mode 100644 index c4e47296eef..00000000000 --- a/ui/src/ui/server/proxy.ts +++ /dev/null @@ -1,33 +0,0 @@ -import http from "http"; -// server/proxy.ts -import { WebSocket, WebSocketServer } from "ws"; - -const PORT = 8081; -const TARGET_VNC = "ws://10.75.171.0:25900"; // ← 改成你的真實 VNC 位址 - -const server = http.createServer(); -const wss = new WebSocketServer({ server }); - -wss.on("connection", (clientWs) => { - console.log("[Proxy] Client connected"); - - const targetWs = new WebSocket(TARGET_VNC); - - targetWs.on("open", () => console.log("[Proxy] 已連線到真實 VNC")); - - clientWs.on("message", (data) => targetWs.readyState === WebSocket.OPEN && targetWs.send(data)); - targetWs.on("message", (data) => clientWs.readyState === WebSocket.OPEN && clientWs.send(data)); - - const cleanup = () => { - targetWs.close(); - clientWs.close(); - }; - clientWs.on("close", cleanup); - targetWs.on("close", cleanup); - clientWs.on("error", cleanup); - targetWs.on("error", cleanup); -}); - -server.listen(PORT, () => { - console.log(`✅ noVNC Proxy 啟動成功 → ws://localhost:${PORT}`); -}); diff --git a/ui/vite.config.ts b/ui/vite.config.ts index e5a525f9ab7..7804f369740 100644 --- a/ui/vite.config.ts +++ b/ui/vite.config.ts @@ -1,6 +1,7 @@ import path from "node:path"; import { fileURLToPath } from "node:url"; import { defineConfig } from "vite"; +import { vncProxyPlugin } from "./vnc-proxy-plugin.ts"; const here = path.dirname(fileURLToPath(import.meta.url)); @@ -40,6 +41,7 @@ export default defineConfig(() => { strictPort: true, }, plugins: [ + vncProxyPlugin(), { name: "control-ui-dev-stubs", configureServer(server) { diff --git a/ui/vnc-proxy-plugin.ts b/ui/vnc-proxy-plugin.ts new file mode 100644 index 00000000000..91bcc433513 --- /dev/null +++ b/ui/vnc-proxy-plugin.ts @@ -0,0 +1,72 @@ +import * as net from "net"; +import type { PluginOption } from "vite"; +import { WebSocketServer, type RawData } from "ws"; + +// Use environment variables or hardcoded defaults from the external script +const VNC_HOST = process.env.OPENCLAW_VNC_HOST || "10.75.171.0"; +const VNC_PORT = parseInt(process.env.OPENCLAW_VNC_PORT || "25900", 10); +const WS_PATH = "/vnc"; + +export function vncProxyPlugin(): PluginOption { + return { + name: "openclaw-vnc-proxy", + configureServer(server) { + // Create a WebSocket server that shares the Vite HTTP server + const wss = new WebSocketServer({ + noServer: true, + path: WS_PATH, + perMessageDeflate: false, + }); + + console.log(`🚀 [Proxy] VNC WebSocket proxy injected at ${WS_PATH}`); + console.log(` Forwarding to: ${VNC_HOST}:${VNC_PORT}`); + + wss.on("connection", (ws) => { + console.log(`[VNC Proxy] Client connected to ${WS_PATH}`); + + const tcpSocket = net.connect(VNC_PORT, VNC_HOST); + + tcpSocket.on("data", (data) => { + if (ws.readyState === ws.OPEN) { + ws.send(data); + } + }); + + ws.on("message", (data: RawData) => { + if (!tcpSocket.writable) { + return; + } + + if (Buffer.isBuffer(data)) { + tcpSocket.write(data); + } else if (Array.isArray(data)) { + tcpSocket.write(Buffer.concat(data)); + } else { + tcpSocket.write(Buffer.from(data)); + } + }); + + ws.on("close", () => tcpSocket.end()); + tcpSocket.on("close", () => ws.close()); + + tcpSocket.on("error", (e) => { + console.error("[VNC Proxy] TCP Error:", e.message); + ws.close(); + }); + ws.on("error", (e) => { + console.error("[VNC Proxy] WebSocket Error:", e.message); + tcpSocket.end(); + }); + }); + + // Hook into Vite's HTTP server upgrade event + server.httpServer?.on("upgrade", (req, socket, head) => { + if (req.url === WS_PATH) { + wss.handleUpgrade(req, socket, head, (ws) => { + wss.emit("connection", ws, req); + }); + } + }); + }, + }; +}