UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

155 lines (154 loc) 5.49 kB
import { l as normalizeOptionalString } from "./string-coerce-CIXf7egm.js"; import { s as isLoopbackHost } from "./net-DbNPs6Xm.js"; import "./string-coerce-runtime-GQa0ehRA.js"; import "./ssrf-runtime-Bum5C6NN.js"; import { r as setBridgeAuthForPort, t as deleteBridgeAuthForPort } from "./bridge-auth-registry-BWnKc4cV.js"; import { i as listenBrowserHttpServer, n as installBrowserAuthMiddleware, r as installBrowserCommonMiddleware, t as hasVerifiedBrowserAuth } from "./server-middleware-CwDqkWQc.js"; import { n as stopBrowserBridgeRuntime } from "./runtime-lifecycle-DhKurfgJ.js"; import express from "express"; //#region extensions/browser/src/browser/bridge-server.ts const bridgeStates = /* @__PURE__ */ new WeakMap(); const bridgeStopPromises = /* @__PURE__ */ new WeakMap(); async function closeBridgeHttpServer(server) { if (!server.listening) return; await new Promise((resolve, reject) => { server.close((error) => { if (error) { reject(error); return; } resolve(); }); }); } function buildNoVncBootstrapHtml(params) { const hash = new URLSearchParams({ autoconnect: "1", resize: "remote" }); const password = normalizeOptionalString(params.password); if (password) hash.set("password", password); const targetUrl = `http://127.0.0.1:${params.noVncPort}/vnc.html#${hash.toString()}`; return `<!doctype html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="referrer" content="no-referrer" /> <title>OpenClaw noVNC Observer</title> </head> <body> <p>Opening sandbox observer...</p> <script> const target = ${JSON.stringify(targetUrl)}; window.location.replace(target); <\/script> </body> </html>`; } /** Start an authenticated loopback browser bridge and register browser routes. */ async function startBrowserBridgeServer(params) { const host = params.host ?? "127.0.0.1"; if (!isLoopbackHost(host)) throw new Error(`bridge server must bind to loopback host (got ${host})`); const port = params.port ?? 0; const app = express(); installBrowserCommonMiddleware(app); const authToken = normalizeOptionalString(params.authToken); const authPassword = normalizeOptionalString(params.authPassword); if (!authToken && !authPassword) throw new Error("bridge server requires auth (authToken/authPassword missing)"); installBrowserAuthMiddleware(app, { token: authToken, password: authPassword }); if (params.resolveSandboxNoVncToken) app.get("/sandbox/novnc", (req, res) => { if (!hasVerifiedBrowserAuth(req)) { res.status(401).send("Unauthorized"); return; } res.setHeader("Cache-Control", "no-store, no-cache, must-revalidate, proxy-revalidate"); res.setHeader("Pragma", "no-cache"); res.setHeader("Expires", "0"); res.setHeader("Referrer-Policy", "no-referrer"); const rawToken = normalizeOptionalString(req.query?.token); if (!rawToken) { res.status(400).send("Missing token"); return; } const resolved = params.resolveSandboxNoVncToken?.(rawToken); if (!resolved) { res.status(404).send("Invalid or expired token"); return; } res.type("html").status(200).send(buildNoVncBootstrapHtml(resolved)); }); const state = { server: null, port, resolved: params.resolved, profiles: /* @__PURE__ */ new Map() }; const [{ createBrowserRouteContext }, { registerBrowserRoutes }] = await Promise.all([import("./server-context-hM7kqE1D.js"), import("./routes-C1oSq6Sp.js")]); registerBrowserRoutes(app, createBrowserRouteContext({ getState: () => state, onEnsureAttachTarget: params.onEnsureAttachTarget })); const server = await listenBrowserHttpServer(app, port, host); const resolvedPort = server.address()?.port ?? port; state.server = server; state.port = resolvedPort; state.resolved.controlPort = resolvedPort; bridgeStates.set(server, state); setBridgeAuthForPort(resolvedPort, { token: authToken, password: authPassword }); return { server, port: resolvedPort, baseUrl: `http://${host}:${resolvedPort}`, state }; } async function stopBrowserBridgeServerOnce(server) { let port; try { const address = server.address(); if (address?.port) port = address.port; } catch {} const state = bridgeStates.get(server); const httpClose = closeBridgeHttpServer(server); if (state) deleteBridgeAuthForPort(state.port); else if (port) deleteBridgeAuthForPort(port); if (!state) { await httpClose; return; } const runtimeClose = stopBrowserBridgeRuntime({ current: state, getState: () => bridgeStates.get(server) ?? null, clearState: () => {}, onWarn: () => {} }); const failed = (await Promise.allSettled([httpClose, runtimeClose])).find((result) => result.status === "rejected"); if (failed) throw failed.reason; bridgeStates.delete(server); } /** Stop a browser bridge server and clear its ephemeral port auth. */ function stopBrowserBridgeServer(server) { const current = bridgeStopPromises.get(server); if (current) return current; let resolveStop; let rejectStop; const stopping = new Promise((resolve, reject) => { resolveStop = resolve; rejectStop = reject; }); bridgeStopPromises.set(server, stopping); stopBrowserBridgeServerOnce(server).then(resolveStop, rejectStop); stopping.finally(() => { if (bridgeStopPromises.get(server) === stopping) bridgeStopPromises.delete(server); }).catch(() => {}); return stopping; } //#endregion export { stopBrowserBridgeServer as n, startBrowserBridgeServer as t };