import http from 'node:http'; import type { BrowserHost } from './browser-host'; /** * 供 Python 后端调用的本地控制接口(只监听 127.0.0.1): * GET /health * POST /views {task_id, partition, allowed_domains} -> {target_id, cdp_url} * DELETE /views/:id * POST /views/:id/reveal | /hide 让渲染层展开 / 收起浏览器面板 * POST /views/:id/mode {mode: 'agent' | 'user'} */ export function startControlServer(port: number, cdpPort: number, host: BrowserHost): http.Server { const server = http.createServer((req, res) => { void handle(req, res).catch(error => { respond(res, 500, { error: (error as Error).message }); }); }); async function handle(req: http.IncomingMessage, res: http.ServerResponse): Promise { const url = new URL(req.url ?? '/', 'http://127.0.0.1'); const method = req.method ?? 'GET'; const body = await readJson(req); if (method === 'GET' && url.pathname === '/health') { respond(res, 200, { ok: true, views: host.list() }); return; } if (method === 'POST' && url.pathname === '/views') { const taskId = str(body.task_id); if (!taskId) { respond(res, 400, { error: 'task_id 必填' }); return; } const allowed = Array.isArray(body.allowed_domains) ? body.allowed_domains.filter((d): d is string => typeof d === 'string') : []; const { targetId } = await host.create({ taskId, partition: str(body.partition) || `task-${taskId}`, allowedDomains: allowed }); respond(res, 200, { target_id: targetId, cdp_url: `http://127.0.0.1:${cdpPort}` }); return; } const match = url.pathname.match(/^\/views\/([^/]+)(?:\/(reveal|hide|mode))?$/); if (match) { const taskId = decodeURIComponent(match[1]); const action = match[2]; if (method === 'DELETE' && !action) { host.dispose(taskId); respond(res, 200, { ok: true }); return; } if (method === 'POST' && action === 'reveal') { host.reveal(taskId); respond(res, 200, { ok: true }); return; } if (method === 'POST' && action === 'hide') { host.hide(taskId); respond(res, 200, { ok: true }); return; } if (method === 'POST' && action === 'mode') { const mode = str(body.mode) === 'user' ? 'user' : 'agent'; host.setMode(taskId, mode); respond(res, 200, { ok: true, mode }); return; } } respond(res, 404, { error: 'not found' }); } server.listen(port, '127.0.0.1', () => console.log(`[electron] 控制接口 http://127.0.0.1:${port}`)); return server; } function str(value: unknown): string { return typeof value === 'string' ? value : ''; } function respond(res: http.ServerResponse, status: number, payload: unknown): void { const text = JSON.stringify(payload); res.writeHead(status, { 'Content-Type': 'application/json; charset=utf-8', 'Content-Length': Buffer.byteLength(text) }); res.end(text); } function readJson(req: http.IncomingMessage): Promise> { return new Promise((resolve, reject) => { const chunks: Buffer[] = []; let size = 0; req.on('data', (chunk: Buffer) => { size += chunk.length; if (size > 65536) { reject(new Error('请求体过大')); req.destroy(); return; } chunks.push(chunk); }); req.on('end', () => { if (!chunks.length) { resolve({}); return; } try { const parsed = JSON.parse(Buffer.concat(chunks).toString('utf8')) as unknown; resolve(parsed && typeof parsed === 'object' ? parsed as Record : {}); } catch (error) { reject(error as Error); } }); req.on('error', reject); }); }