UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

141 lines (140 loc) 5.21 kB
import { i as extractErrorCode } from "../../error-coercion-D_-xJ90S.js"; import { n as getRuntimeConfig } from "../../io.runtime-B9iJRs3w.js"; import { t as createSubsystemLogger } from "../../subsystem-Dy2tqXOS.js"; import "../../runtime-config-snapshot-u1Kj68kx.js"; import { a as createDeferred } from "../../extension-shared-BAZUCAhf.js"; import { a as resolveProfile, r as resolveBrowserConfig } from "../../config-BosO4Zbt.js"; import "../../subsystem-DbD06g1M.js"; import "../../errors-CesLEJB8.js"; import "../../config-C0cjs7WS.js"; import { n as readExtensionRelayToken } from "../../relay-auth-DPRfu8tu.js"; import { r as startExtensionRelayServer } from "../../relay-server-9erhkSvY.js"; //#region extensions/browser/src/browser/relay-daemon.ts const log = createSubsystemLogger("browser").child("relay-daemon"); /** Default grace before a daemon with no extension and no CDP clients exits. */ const RELAY_DAEMON_IDLE_EXIT_MS = 6e5; const IDLE_POLL_MS = 3e4; /** * Run the standalone extension relay daemon: one loopback relay server with no * Gateway. The daemon stays alive while the paired extension holds its relay * connection or a CDP client is attached, and exits once both sides have been * gone for the idle grace so an idle daemon never outlives Chrome. */ async function runExtensionRelayDaemon(params) { const readToken = params.readToken ?? readExtensionRelayToken; const allowLegacyAuth = params.allowLegacyAuth ?? false; const now = params.now ?? Date.now; const idleExitMs = params.idleExitMs ?? RELAY_DAEMON_IDLE_EXIT_MS; const pollMs = params.pollMs ?? IDLE_POLL_MS; const completion = createDeferred(); const token = readToken(); if (!token) { log.warn("relay daemon refused to start: no extension relay credential"); completion.resolve("no-credential"); return { done: completion.promise, port: null, stop: () => {} }; } let handle; try { const config = getRuntimeConfig(); const resolved = resolveBrowserConfig(config.browser, config); const profiles = Object.keys(resolved.profiles).filter((name) => { const profile = resolveProfile(resolved, name); return profile?.driver === "extension" && profile.cdpPort === params.port; }); const profileName = profiles.length === 1 ? profiles[0] : void 0; handle = await startExtensionRelayServer({ port: params.port, token, allowLegacyAuth, profileName }); } catch (error) { if (extractErrorCode(error) === "EADDRINUSE") { log.info(`relay port ${params.port} is already served; standalone daemon not needed`); completion.resolve("port-in-use"); return { done: completion.promise, port: null, stop: () => {} }; } throw error; } log.info(`standalone extension relay listening on 127.0.0.1:${handle.port}`); let lastActiveAtMs = now(); let stopped = false; const finish = (reason) => { if (stopped) return; stopped = true; clearInterval(idleTimer); handle.close().then(() => completion.resolve(reason), completion.reject); }; const idleTimer = setInterval(() => { if (handle.bridge.extensionConnected || handle.bridge.cdpClientCount > 0) { lastActiveAtMs = now(); return; } if (now() - lastActiveAtMs >= idleExitMs) { log.info("relay daemon idle (no extension, no CDP clients); exiting"); finish("idle"); } }, pollMs); idleTimer.unref?.(); return { done: completion.promise, port: handle.port, stop: () => finish("stopped") }; } //#endregion //#region extensions/browser/relay-daemon-entry.ts /** * Standalone extension relay daemon. Hosts the loopback relay the OpenClaw * Chrome extension dials, with no Gateway required — CDP clients (mcporter, * Playwright, chrome-devtools-mcp) attach through the same relay port. Spawned * on demand by the native messaging host, or run manually. */ const DEFAULT_RELAY_PORT = 18799; /** * The standalone daemon is v2-only by default. Honor an explicit * `browser.extensionRelay.allowLegacyAuth=true` opt-in, but never fall back to * legacy on a read/parse failure — fail closed to the stricter mode. Read the * raw config value (not the resolved `?? true` default) so an unset key stays * v2-only rather than inheriting the gateway's permissive default. */ function resolveAllowLegacyAuth() { try { return getRuntimeConfig().browser?.extensionRelay?.allowLegacyAuth === true; } catch { return false; } } function resolvePortArgument(argv) { const index = argv.indexOf("--port"); const raw = index >= 0 ? argv[index + 1] : void 0; if (raw === void 0) return DEFAULT_RELAY_PORT; const parsed = Number(raw); if (!Number.isSafeInteger(parsed) || parsed < 1 || parsed > 65535) throw new Error(`invalid --port value: ${raw}`); return parsed; } async function main() { const run = await runExtensionRelayDaemon({ port: resolvePortArgument(process.argv.slice(2)), allowLegacyAuth: resolveAllowLegacyAuth() }); const stop = () => run.stop(); process.once("SIGINT", stop); process.once("SIGTERM", stop); const reason = await run.done; process.exitCode = reason === "no-credential" ? 1 : 0; } main().catch((error) => { process.stderr.write(`openclaw relay daemon failed: ${String(error)}\n`); process.exitCode = 1; }); //#endregion export {};