|
1 | 1 | import fs from "node:fs"; |
2 | 2 | import path from "node:path"; |
3 | 3 | import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime"; |
4 | | -import type { BrowserProfileConfig } from "../config/config.js"; |
5 | | -import { getRuntimeConfig, mutateConfigFile } from "../config/config.js"; |
6 | | -import { deriveDefaultBrowserCdpPortRange } from "../config/port-defaults.js"; |
| 4 | +import { getRuntimeConfig } from "../config/config.js"; |
7 | 5 | import { formatErrorMessage } from "../infra/errors.js"; |
8 | 6 | import { resolveUserPath } from "../utils.js"; |
9 | 7 | import { assertCdpEndpointAllowed } from "./cdp.helpers.js"; |
10 | 8 | import { resolveOpenClawUserDataDir } from "./chrome.js"; |
11 | | -import { parseHttpUrl, resolveBrowserConfig, resolveProfile } from "./config.js"; |
| 9 | +import { createBrowserProfileConfig, deleteBrowserProfileConfig } from "./config-mutations.js"; |
| 10 | +import { parseHttpUrl, resolveProfile } from "./config.js"; |
12 | 11 | import { |
13 | 12 | BrowserConflictError, |
14 | 13 | BrowserProfileNotFoundError, |
15 | | - BrowserResourceExhaustedError, |
16 | 14 | BrowserValidationError, |
17 | 15 | } from "./errors.js"; |
18 | 16 | import { getBrowserProfileCapabilities } from "./profile-capabilities.js"; |
19 | | -import { |
20 | | - allocateCdpPort, |
21 | | - allocateColor, |
22 | | - getUsedColors, |
23 | | - getUsedPorts, |
24 | | - isValidProfileName, |
25 | | -} from "./profiles.js"; |
| 17 | +import { isValidProfileName } from "./profiles.js"; |
26 | 18 | import type { BrowserRouteContext, ProfileStatus } from "./server-context.js"; |
27 | 19 | import { movePathToTrash } from "./trash.js"; |
28 | 20 |
|
@@ -53,30 +45,6 @@ export type DeleteProfileResult = { |
53 | 45 |
|
54 | 46 | const HEX_COLOR_RE = /^#[0-9A-Fa-f]{6}$/; |
55 | 47 |
|
56 | | -const cdpPortRange = (resolved: { |
57 | | - controlPort: number; |
58 | | - cdpPortRangeStart?: number; |
59 | | - cdpPortRangeEnd?: number; |
60 | | -}): { start: number; end: number } => { |
61 | | - const start = resolved.cdpPortRangeStart; |
62 | | - const end = resolved.cdpPortRangeEnd; |
63 | | - if ( |
64 | | - typeof start === "number" && |
65 | | - Number.isFinite(start) && |
66 | | - Number.isInteger(start) && |
67 | | - typeof end === "number" && |
68 | | - Number.isFinite(end) && |
69 | | - Number.isInteger(end) && |
70 | | - start > 0 && |
71 | | - end >= start && |
72 | | - end <= 65535 |
73 | | - ) { |
74 | | - return { start, end }; |
75 | | - } |
76 | | - |
77 | | - return deriveDefaultBrowserCdpPortRange(resolved.controlPort); |
78 | | -}; |
79 | | - |
80 | 48 | export function createBrowserProfilesService(ctx: BrowserRouteContext) { |
81 | 49 | const listProfiles = async (): Promise<ProfileStatus[]> => { |
82 | 50 | return await ctx.listProfiles(); |
@@ -138,76 +106,14 @@ export function createBrowserProfilesService(ctx: BrowserRouteContext) { |
138 | 106 | parsedCdpUrl = parsed.normalized; |
139 | 107 | } |
140 | 108 |
|
141 | | - const mutation = await mutateConfigFile<BrowserProfileConfig>({ |
142 | | - afterWrite: { mode: "auto" }, |
143 | | - mutate: async (draft) => { |
144 | | - const latestResolved = resolveBrowserConfig({ |
145 | | - ...state.resolved, |
146 | | - ...draft.browser, |
147 | | - profiles: draft.browser?.profiles ?? state.resolved.profiles, |
148 | | - }); |
149 | | - const latestProfiles = draft.browser?.profiles ?? {}; |
150 | | - if (name in latestProfiles || name in latestResolved.profiles) { |
151 | | - throw new BrowserConflictError(`profile "${name}" already exists`); |
152 | | - } |
153 | | - |
154 | | - const profileColor = |
155 | | - explicitProfileColor ?? allocateColor(getUsedColors(latestResolved.profiles)); |
156 | | - |
157 | | - let nextProfileConfig: BrowserProfileConfig; |
158 | | - if (parsedCdpUrl) { |
159 | | - try { |
160 | | - await assertCdpEndpointAllowed(parsedCdpUrl, latestResolved.ssrfPolicy); |
161 | | - } catch (err) { |
162 | | - throw new BrowserValidationError(formatErrorMessage(err)); |
163 | | - } |
164 | | - nextProfileConfig = { |
165 | | - cdpUrl: parsedCdpUrl, |
166 | | - ...(driver ? { driver } : {}), |
167 | | - color: profileColor, |
168 | | - }; |
169 | | - } else if (driver === "existing-session") { |
170 | | - // existing-session uses Chrome MCP auto-connect; no CDP port needed. |
171 | | - nextProfileConfig = { |
172 | | - driver, |
173 | | - attachOnly: true, |
174 | | - ...(normalizedUserDataDir ? { userDataDir: normalizedUserDataDir } : {}), |
175 | | - color: profileColor, |
176 | | - }; |
177 | | - } else { |
178 | | - const usedPorts = getUsedPorts(latestResolved.profiles); |
179 | | - const rangeStart = draft.browser?.cdpPortRangeStart ?? state.resolved.cdpPortRangeStart; |
180 | | - const range = cdpPortRange({ |
181 | | - controlPort: state.resolved.controlPort, |
182 | | - cdpPortRangeStart: rangeStart, |
183 | | - cdpPortRangeEnd: |
184 | | - draft.browser?.cdpPortRangeStart === undefined |
185 | | - ? state.resolved.cdpPortRangeEnd |
186 | | - : latestResolved.cdpPortRangeEnd, |
187 | | - }); |
188 | | - const cdpPort = allocateCdpPort(usedPorts, range); |
189 | | - if (cdpPort === null) { |
190 | | - throw new BrowserResourceExhaustedError("no available CDP ports in range"); |
191 | | - } |
192 | | - nextProfileConfig = { |
193 | | - cdpPort, |
194 | | - ...(driver ? { driver } : {}), |
195 | | - color: profileColor, |
196 | | - }; |
197 | | - } |
198 | | - |
199 | | - draft.browser = { |
200 | | - ...draft.browser, |
201 | | - profiles: { |
202 | | - ...draft.browser?.profiles, |
203 | | - [name]: nextProfileConfig, |
204 | | - }, |
205 | | - }; |
206 | | - return nextProfileConfig; |
207 | | - }, |
| 109 | + const profileConfig = await createBrowserProfileConfig({ |
| 110 | + name, |
| 111 | + resolved: state.resolved, |
| 112 | + ...(explicitProfileColor ? { color: explicitProfileColor } : {}), |
| 113 | + ...(parsedCdpUrl ? { parsedCdpUrl } : {}), |
| 114 | + ...(normalizedUserDataDir ? { userDataDir: normalizedUserDataDir } : {}), |
| 115 | + ...(driver ? { driver } : {}), |
208 | 116 | }); |
209 | | - |
210 | | - const profileConfig = mutation.result; |
211 | 117 | if (!profileConfig) { |
212 | 118 | throw new BrowserProfileNotFoundError(`profile "${name}" not found after creation`); |
213 | 119 | } |
@@ -270,22 +176,7 @@ export function createBrowserProfilesService(ctx: BrowserRouteContext) { |
270 | 176 | } |
271 | 177 | } |
272 | 178 |
|
273 | | - await mutateConfigFile({ |
274 | | - afterWrite: { mode: "auto" }, |
275 | | - mutate: (draft) => { |
276 | | - const { [name]: _removed, ...remainingProfiles } = draft.browser?.profiles ?? {}; |
277 | | - const nextBrowser = { |
278 | | - ...draft.browser, |
279 | | - profiles: remainingProfiles, |
280 | | - }; |
281 | | - if (nextBrowser.defaultProfile === name) { |
282 | | - delete nextBrowser.defaultProfile; |
283 | | - } |
284 | | - draft.browser = { |
285 | | - ...nextBrowser, |
286 | | - }; |
287 | | - }, |
288 | | - }); |
| 179 | + await deleteBrowserProfileConfig(name); |
289 | 180 |
|
290 | 181 | delete state.resolved.profiles[name]; |
291 | 182 | state.profiles.delete(name); |
|
0 commit comments