UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

179 lines (178 loc) 6.98 kB
import { i as formatErrorMessage } from "./errors-BXgSefBE.js"; import { n as mutateConfigFile } from "./config-C9RxTsn1.js"; import { t as parseBrowserHttpUrl } from "./browser-config-CB1dJM4Y.js"; import { g as BrowserConflictError, n as assertCdpEndpointAllowed, w as BrowserValidationError, x as BrowserResourceExhaustedError } from "./cdp.helpers-CTQ-1blZ.js"; import { n as resolveBrowserConfig, u as deriveDefaultBrowserCdpPortRange } from "./config-C2rsUqn7.js"; import "./config-DGptl9kI.js"; import "./errors-Bxt8NtDz.js"; const MAX_TCP_PORT = 65535; const PROFILE_NAME_REGEX = /^[a-z0-9][a-z0-9-]*$/; /** Return true when a profile name matches the supported config key format. */ function isValidProfileName(name) { if (!name || name.length > 64) return false; return PROFILE_NAME_REGEX.test(name); } /** Allocate the first unused CDP port in the configured range. */ function allocateCdpPort(usedPorts, range) { const start = range?.start ?? 18800; const end = range?.end ?? 18899; if (!isValidTcpPort(start) || !isValidTcpPort(end)) return null; if (start > end) return null; for (let port = start; port <= end; port++) if (!usedPorts.has(port)) return port; return null; } function isValidTcpPort(port) { return Number.isSafeInteger(port) && port > 0 && port <= MAX_TCP_PORT; } /** Extract currently used CDP ports from profile config. */ function getUsedPorts(profiles) { if (!profiles) return /* @__PURE__ */ new Set(); const used = /* @__PURE__ */ new Set(); for (const profile of Object.values(profiles)) { if (typeof profile.cdpPort === "number" && isValidTcpPort(profile.cdpPort)) { used.add(profile.cdpPort); continue; } const rawUrl = profile.cdpUrl?.trim(); if (!rawUrl) continue; try { used.add(parseBrowserHttpUrl(rawUrl, "browser.profiles.*.cdpUrl").port); } catch {} } return used; } /** Default browser profile color palette. */ const PROFILE_COLORS = [ "#FF4500", "#0066CC", "#00AA00", "#9933FF", "#FF6699", "#00CCCC", "#FF9900", "#6666FF", "#CC3366", "#339966" ]; /** Allocate the first unused profile color, cycling when all are used. */ function allocateColor(usedColors) { for (const color of PROFILE_COLORS) if (!usedColors.has(color.toUpperCase())) return color; return PROFILE_COLORS[usedColors.size % PROFILE_COLORS.length] ?? PROFILE_COLORS[0]; } /** Extract currently used profile colors from profile config. */ function getUsedColors(profiles) { if (!profiles) return /* @__PURE__ */ new Set(); return new Set(Object.values(profiles).map((p) => p.color.toUpperCase())); } //#endregion //#region extensions/browser/src/browser/config-mutations.ts /** * Browser config mutation helpers. * * Persists browser-control credentials and profile config changes through the * canonical config writer while preserving port/color allocation rules. */ const cdpPortRange = (resolved) => { const start = resolved.cdpPortRangeStart; const end = resolved.cdpPortRangeEnd; if (typeof start === "number" && Number.isFinite(start) && Number.isInteger(start) && typeof end === "number" && Number.isFinite(end) && Number.isInteger(end) && start > 0 && end >= start && end <= 65535) return { start, end }; return deriveDefaultBrowserCdpPortRange(resolved.controlPort); }; /** Persist the generated browser-control token or password in gateway auth config. */ async function persistBrowserControlCredential(credential) { await mutateConfigFile({ afterWrite: { mode: "auto" }, mutate: (draft) => { draft.gateway = { ...draft.gateway, auth: { ...draft.gateway?.auth, [credential.kind]: credential.value } }; } }); } /** Create and persist a browser profile config with allocated color and CDP port. */ async function createBrowserProfileConfig(params) { return (await mutateConfigFile({ afterWrite: { mode: "auto" }, mutate: async (draft) => { const rawDraftBrowser = draft.browser; const draftCdpPortRangeEnd = typeof rawDraftBrowser?.cdpPortRangeEnd === "number" ? rawDraftBrowser.cdpPortRangeEnd : void 0; const useRebasedPortRange = draft.gateway?.port !== void 0 || draft.browser?.cdpPortRangeStart !== void 0 || draftCdpPortRangeEnd !== void 0; const latestResolved = resolveBrowserConfig({ ...params.resolved, ...draft.browser, profiles: draft.browser?.profiles ?? params.resolved.profiles }, draft); const latestRootResolved = resolveBrowserConfig(draft.browser, draft); const latestProfileSource = useRebasedPortRange ? latestRootResolved : latestResolved; const latestProfiles = draft.browser?.profiles ?? {}; if (params.name in latestProfiles || params.name in latestProfileSource.profiles) throw new BrowserConflictError(`profile "${params.name}" already exists`); const profileColor = params.color ?? allocateColor(getUsedColors(latestProfileSource.profiles)); let nextProfileConfig; if (params.parsedCdpUrl) { try { await assertCdpEndpointAllowed(params.parsedCdpUrl, latestResolved.ssrfPolicy); } catch (err) { throw new BrowserValidationError(formatErrorMessage(err)); } nextProfileConfig = { cdpUrl: params.parsedCdpUrl, ...params.driver ? { driver: params.driver } : {}, ...params.driver === "existing-session" ? { attachOnly: true } : {}, color: profileColor }; } else if (params.driver === "existing-session") nextProfileConfig = { driver: params.driver, attachOnly: true, ...params.userDataDir ? { userDataDir: params.userDataDir } : {}, color: profileColor }; else { const usedPorts = getUsedPorts(latestProfileSource.profiles); const rangeSource = useRebasedPortRange ? latestRootResolved : params.resolved; const cdpPort = allocateCdpPort(usedPorts, cdpPortRange({ controlPort: rangeSource.controlPort, cdpPortRangeStart: rangeSource.cdpPortRangeStart, cdpPortRangeEnd: draftCdpPortRangeEnd ?? rangeSource.cdpPortRangeEnd })); if (cdpPort === null) throw new BrowserResourceExhaustedError("no available CDP ports in range"); nextProfileConfig = { cdpPort, ...params.driver ? { driver: params.driver } : {}, color: profileColor }; } draft.browser = { ...draft.browser, profiles: { ...draft.browser?.profiles, [params.name]: nextProfileConfig } }; return nextProfileConfig; } })).result; } /** Delete a persisted browser profile config by name. */ async function deleteBrowserProfileConfig(name) { await mutateConfigFile({ afterWrite: { mode: "auto" }, mutate: (draft) => { const { [name]: _removed, ...remainingProfiles } = draft.browser?.profiles ?? {}; const nextBrowser = { ...draft.browser, profiles: remainingProfiles }; if (nextBrowser.defaultProfile === name) delete nextBrowser.defaultProfile; draft.browser = nextBrowser; } }); } //#endregion export { isValidProfileName as i, deleteBrowserProfileConfig as n, persistBrowserControlCredential as r, createBrowserProfileConfig as t };