@cjwddz/browser-server 0.1.0-alpha

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/README.md +148 -0
  2. package/dist/cli.d.ts +3 -0
  3. package/dist/cli.d.ts.map +1 -0
  4. package/dist/cli.js +161 -0
  5. package/dist/cli.js.map +1 -0
  6. package/dist/daemon.d.ts +2 -0
  7. package/dist/daemon.d.ts.map +1 -0
  8. package/dist/daemon.js +57 -0
  9. package/dist/daemon.js.map +1 -0
  10. package/dist/index.d.ts +8 -0
  11. package/dist/index.d.ts.map +1 -0
  12. package/dist/index.js +7 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/mcp/connection-pool.d.ts +14 -0
  15. package/dist/mcp/connection-pool.d.ts.map +1 -0
  16. package/dist/mcp/connection-pool.js +65 -0
  17. package/dist/mcp/connection-pool.js.map +1 -0
  18. package/dist/mcp/tools.d.ts +230 -0
  19. package/dist/mcp/tools.d.ts.map +1 -0
  20. package/dist/mcp/tools.js +219 -0
  21. package/dist/mcp/tools.js.map +1 -0
  22. package/dist/server/mcp-server.d.ts +15 -0
  23. package/dist/server/mcp-server.d.ts.map +1 -0
  24. package/dist/server/mcp-server.js +96 -0
  25. package/dist/server/mcp-server.js.map +1 -0
  26. package/dist/server/vnc-proxy.d.ts +7 -0
  27. package/dist/server/vnc-proxy.d.ts.map +1 -0
  28. package/dist/server/vnc-proxy.js +38 -0
  29. package/dist/server/vnc-proxy.js.map +1 -0
  30. package/dist/server/web-pages.d.ts +4 -0
  31. package/dist/server/web-pages.d.ts.map +1 -0
  32. package/dist/server/web-pages.js +157 -0
  33. package/dist/server/web-pages.js.map +1 -0
  34. package/dist/server/web-server.d.ts +18 -0
  35. package/dist/server/web-server.d.ts.map +1 -0
  36. package/dist/server/web-server.js +161 -0
  37. package/dist/server/web-server.js.map +1 -0
  38. package/dist/session/process-group.d.ts +33 -0
  39. package/dist/session/process-group.d.ts.map +1 -0
  40. package/dist/session/process-group.js +185 -0
  41. package/dist/session/process-group.js.map +1 -0
  42. package/dist/session/session-manager.d.ts +26 -0
  43. package/dist/session/session-manager.d.ts.map +1 -0
  44. package/dist/session/session-manager.js +133 -0
  45. package/dist/session/session-manager.js.map +1 -0
  46. package/dist/utils/config.d.ts +14 -0
  47. package/dist/utils/config.d.ts.map +1 -0
  48. package/dist/utils/config.js +72 -0
  49. package/dist/utils/config.js.map +1 -0
  50. package/dist/utils/logger.d.ts +9 -0
  51. package/dist/utils/logger.d.ts.map +1 -0
  52. package/dist/utils/logger.js +33 -0
  53. package/dist/utils/logger.js.map +1 -0
  54. package/dist/utils/types.d.ts +26 -0
  55. package/dist/utils/types.d.ts.map +1 -0
  56. package/dist/utils/types.js +2 -0
  57. package/dist/utils/types.js.map +1 -0
  58. package/package.json +42 -0
package/README.md ADDED
@@ -0,0 +1,148 @@
1
+ # @cjwddz/browser-server
2
+
3
+ 面向 AI Agent 的自托管常驻浏览器服务。
4
+
5
+ ## 解决的问题
6
+
7
+ AI Agent 在无 GUI 的远程服务器上操作浏览器时面临的核心难题:
8
+
9
+ - 浏览器生命周期绑定 Agent 对话,对话结束状态全丢
10
+ - 跨天登录态无法保持,每次都要重新登录
11
+ - 验证码、密码等场景 AI 无法处理,卡住无解
12
+ - 多 Agent 共用浏览器互相干扰
13
+
14
+ ## 核心特性
15
+
16
+ - **常驻浏览器** — 浏览器 7×24 运行,不因 Agent 会话结束而关闭
17
+ - **状态完整保持** — Cookie、localStorage、页面 JS 上下文持续存活,跨天登录态不丢
18
+ - **Web 端实时操控** — 用户随时从浏览器打开管理页面,直接查看和操作浏览器(验证码、密码等)
19
+ - **Agent 隔离** — 每个会话独立 BrowserContext,Cookie/存储完全隔离
20
+ - **MCP 集成** — 15 个浏览器操控工具,支持 Streamable HTTP 协议
21
+ - **进程守护** — Chromium 崩溃自动重启
22
+
23
+ ## 系统依赖
24
+
25
+ ```bash
26
+ # Ubuntu/Debian
27
+ sudo apt-get install -y xvfb x11vnc chromium-browser
28
+ ```
29
+
30
+ ## 安装
31
+
32
+ ```bash
33
+ npm install -g @cjwddz/browser-server
34
+ ```
35
+
36
+ ## 使用
37
+
38
+ ### 启动服务
39
+
40
+ ```bash
41
+ browser-server start -u admin -p mypassword
42
+ ```
43
+
44
+ ### 完整参数
45
+
46
+ ```bash
47
+ browser-server start \
48
+ -u admin \ # Web 管理登录用户名(必填)
49
+ -p mypassword \ # Web 管理登录密码(必填)
50
+ -w 33000 \ # Web 管理端口(默认 33000)
51
+ -m 33001 \ # MCP 服务端口(默认 33001)
52
+ -d ~/.browser-server \ # 服务根目录(默认 ~/.browser-server)
53
+ --host 0.0.0.0 # 监听地址(默认 0.0.0.0)
54
+ ```
55
+
56
+ ### 其他命令
57
+
58
+ ```bash
59
+ browser-server stop # 停止服务
60
+ browser-server restart -u admin -p mypassword # 重启
61
+ browser-server status # 查看状态
62
+ ```
63
+
64
+ ## 使用流程
65
+
66
+ 1. 启动服务
67
+ 2. 浏览器打开 `http://localhost:33000`,输入账号密码
68
+ 3. 点「新建会话」,输入名称
69
+ 4. 点「MCP 配置」,复制 JSON 配置
70
+ 5. 粘贴到 Cursor / Claude Desktop 的 MCP 配置中
71
+ 6. Agent 开始操作浏览器
72
+ 7. 需要人工介入时(验证码等),在管理页面点「查看浏览器」直接操作
73
+
74
+ ## 架构
75
+
76
+ ```
77
+ browser-server
78
+ ├── Web 管理 (:33000) ← 用户浏览器访问
79
+ │ ├── 会话列表(创建/删除)
80
+ │ ├── 查看浏览器(noVNC 实时画面 + 操作)
81
+ │ └── MCP 配置(一键复制)
82
+
83
+ ├── MCP 服务 (:33001) ← Agent 连接
84
+ │ └── 15 个浏览器操控工具
85
+
86
+ └── 每个会话 = 独立进程组
87
+ ├── Xvfb (虚拟显示器)
88
+ ├── Chromium (--user-data-dir 持久化)
89
+ └── x11vnc (VNC 服务)
90
+ ```
91
+
92
+ ## MCP 工具列表
93
+
94
+ | 工具 | 说明 |
95
+ |------|------|
96
+ | navigate_page | 导航到指定 URL |
97
+ | click | 点击页面元素 |
98
+ | fill | 填写输入框 |
99
+ | hover | 悬停在元素上 |
100
+ | press_key | 按下键盘按键 |
101
+ | take_screenshot | 截取页面截图 |
102
+ | take_snapshot | 获取页面 DOM 快照 |
103
+ | evaluate_script | 执行 JavaScript |
104
+ | list_pages | 列出所有标签页 |
105
+ | new_page | 打开新标签页 |
106
+ | close_page | 关闭当前标签页 |
107
+ | select_page | 切换标签页 |
108
+ | wait_for | 等待元素出现或指定时间 |
109
+ | emulate | 模拟移动设备 |
110
+ | resize_page | 调整视口大小 |
111
+
112
+ ## 数据目录结构
113
+
114
+ ```
115
+ ~/.browser-server/
116
+ ├── server.pid # 守护进程 PID
117
+ ├── server.json # 运行配置
118
+ ├── sessions.json # 会话列表
119
+ ├── server.log # 服务日志
120
+ ├── <session-id>/ # 会话目录
121
+ │ ├── chrome-data/ # Chromium 用户数据(cookie/storage 等)
122
+ │ └── chrome.log # Chromium 日志
123
+ └── ...
124
+ ```
125
+
126
+ ## Docker 部署
127
+
128
+ 适用于 macOS / Windows / 任何不想手动安装系统依赖的场景:
129
+
130
+ ```bash
131
+ # 构建镜像
132
+ cd packages/browser-server
133
+ npm run build
134
+ docker build -t browser-server .
135
+
136
+ # 运行
137
+ docker run -d \
138
+ -p 33000:33000 \
139
+ -p 33001:33001 \
140
+ -v ~/.browser-server:/data \
141
+ browser-server start -u admin -p mypassword -d /data --host 0.0.0.0
142
+ ```
143
+
144
+ 镜像内已包含 Chromium、Xvfb、x11vnc 和中文字体,无需额外配置。
145
+
146
+ ## License
147
+
148
+ MIT
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
package/dist/cli.js ADDED
@@ -0,0 +1,161 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import { fork } from 'node:child_process';
4
+ import * as path from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { resolveDataDir, ensureDataDir, loadPid, removePid, isProcessRunning, loadServerConfig, saveServerConfig, } from './utils/config.js';
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = path.dirname(__filename);
9
+ const program = new Command();
10
+ program.name('browser-server').description('Self-hosted headless browser service for AI agents').version('0.1.0');
11
+ program
12
+ .command('start')
13
+ .description('启动浏览器服务(守护进程模式)')
14
+ .requiredOption('-u, --user <username>', 'Web 管理登录用户名')
15
+ .requiredOption('-p, --pass <password>', 'Web 管理登录密码')
16
+ .option('-w, --web-port <port>', 'Web 管理端口', '33000')
17
+ .option('-m, --mcp-port <port>', 'MCP 服务端口', '33001')
18
+ .option('-d, --data-dir <path>', '服务根目录', resolveDataDir())
19
+ .option('--host <host>', '监听地址', '0.0.0.0')
20
+ .action(async (opts) => {
21
+ const dataDir = resolveDataDir(opts.dataDir);
22
+ const existingPid = loadPid(dataDir);
23
+ if (existingPid && isProcessRunning(existingPid)) {
24
+ console.error(`服务已在运行 (PID: ${existingPid})。如需重启请使用 restart 命令。`);
25
+ process.exit(1);
26
+ }
27
+ const config = {
28
+ webPort: parseInt(opts.webPort, 10),
29
+ mcpPort: parseInt(opts.mcpPort, 10),
30
+ host: opts.host,
31
+ user: opts.user,
32
+ pass: opts.pass,
33
+ dataDir,
34
+ };
35
+ ensureDataDir(dataDir);
36
+ saveServerConfig(config);
37
+ const daemonScript = path.join(__dirname, 'daemon.js');
38
+ const child = fork(daemonScript, [], {
39
+ detached: true,
40
+ stdio: 'ignore',
41
+ env: { ...process.env, BROWSER_SERVER_DATA_DIR: dataDir },
42
+ });
43
+ child.unref();
44
+ console.log(`browser-server 已启动 (PID: ${child.pid})`);
45
+ console.log(` Web 管理: http://${config.host}:${config.webPort}`);
46
+ console.log(` MCP 服务: http://${config.host}:${config.mcpPort}`);
47
+ console.log(` 数据目录: ${dataDir}`);
48
+ process.exit(0);
49
+ });
50
+ program
51
+ .command('stop')
52
+ .description('停止浏览器服务')
53
+ .option('-d, --data-dir <path>', '服务根目录', resolveDataDir())
54
+ .action(async (opts) => {
55
+ const dataDir = resolveDataDir(opts.dataDir);
56
+ const pid = loadPid(dataDir);
57
+ if (!pid || !isProcessRunning(pid)) {
58
+ console.log('browser-server 未运行');
59
+ removePid(dataDir);
60
+ process.exit(0);
61
+ }
62
+ console.log(`正在停止 browser-server (PID: ${pid})...`);
63
+ process.kill(pid, 'SIGTERM');
64
+ const timeout = 10_000;
65
+ const start = Date.now();
66
+ while (Date.now() - start < timeout) {
67
+ if (!isProcessRunning(pid)) {
68
+ removePid(dataDir);
69
+ console.log('browser-server 已停止');
70
+ process.exit(0);
71
+ }
72
+ await sleep(500);
73
+ }
74
+ console.warn('优雅退出超时,强制终止...');
75
+ try {
76
+ process.kill(pid, 'SIGKILL');
77
+ }
78
+ catch {
79
+ // already dead
80
+ }
81
+ removePid(dataDir);
82
+ console.log('browser-server 已强制停止');
83
+ process.exit(0);
84
+ });
85
+ program
86
+ .command('restart')
87
+ .description('重启浏览器服务(等同于 stop + start)')
88
+ .requiredOption('-u, --user <username>', 'Web 管理登录用户名')
89
+ .requiredOption('-p, --pass <password>', 'Web 管理登录密码')
90
+ .option('-w, --web-port <port>', 'Web 管理端口', '33000')
91
+ .option('-m, --mcp-port <port>', 'MCP 服务端口', '33001')
92
+ .option('-d, --data-dir <path>', '服务根目录', resolveDataDir())
93
+ .option('--host <host>', '监听地址', '0.0.0.0')
94
+ .action(async (opts) => {
95
+ const dataDir = resolveDataDir(opts.dataDir);
96
+ const pid = loadPid(dataDir);
97
+ if (pid && isProcessRunning(pid)) {
98
+ console.log(`正在停止 browser-server (PID: ${pid})...`);
99
+ process.kill(pid, 'SIGTERM');
100
+ const start = Date.now();
101
+ while (Date.now() - start < 10_000) {
102
+ if (!isProcessRunning(pid))
103
+ break;
104
+ await sleep(500);
105
+ }
106
+ if (isProcessRunning(pid)) {
107
+ try {
108
+ process.kill(pid, 'SIGKILL');
109
+ }
110
+ catch { /* */ }
111
+ }
112
+ removePid(dataDir);
113
+ console.log('已停止');
114
+ }
115
+ // re-trigger start
116
+ const config = {
117
+ webPort: parseInt(opts.webPort, 10),
118
+ mcpPort: parseInt(opts.mcpPort, 10),
119
+ host: opts.host,
120
+ user: opts.user,
121
+ pass: opts.pass,
122
+ dataDir,
123
+ };
124
+ ensureDataDir(dataDir);
125
+ saveServerConfig(config);
126
+ const daemonScript = path.join(__dirname, 'daemon.js');
127
+ const child = fork(daemonScript, [], {
128
+ detached: true,
129
+ stdio: 'ignore',
130
+ env: { ...process.env, BROWSER_SERVER_DATA_DIR: dataDir },
131
+ });
132
+ child.unref();
133
+ console.log(`browser-server 已重启 (PID: ${child.pid})`);
134
+ console.log(` Web 管理: http://${config.host}:${config.webPort}`);
135
+ console.log(` MCP 服务: http://${config.host}:${config.mcpPort}`);
136
+ process.exit(0);
137
+ });
138
+ program
139
+ .command('status')
140
+ .description('查看服务状态')
141
+ .option('-d, --data-dir <path>', '服务根目录', resolveDataDir())
142
+ .action((opts) => {
143
+ const dataDir = resolveDataDir(opts.dataDir);
144
+ const pid = loadPid(dataDir);
145
+ if (!pid || !isProcessRunning(pid)) {
146
+ console.log('browser-server 未运行');
147
+ process.exit(0);
148
+ }
149
+ const config = loadServerConfig(dataDir);
150
+ console.log(`browser-server 运行中 (PID: ${pid})`);
151
+ if (config) {
152
+ console.log(` Web 管理: http://${config.host}:${config.webPort}`);
153
+ console.log(` MCP 服务: http://${config.host}:${config.mcpPort}`);
154
+ console.log(` 数据目录: ${config.dataDir}`);
155
+ }
156
+ });
157
+ program.parse();
158
+ function sleep(ms) {
159
+ return new Promise(resolve => setTimeout(resolve, ms));
160
+ }
161
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=daemon.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"daemon.d.ts","sourceRoot":"","sources":["../src/daemon.ts"],"names":[],"mappings":""}
package/dist/daemon.js ADDED
@@ -0,0 +1,57 @@
1
+ import { loadServerConfig, savePid, removePid, } from './utils/config.js';
2
+ import { initLogger, log, closeLogger } from './utils/logger.js';
3
+ import { SessionManager } from './session/session-manager.js';
4
+ import { WebServer } from './server/web-server.js';
5
+ import { McpServer } from './server/mcp-server.js';
6
+ const dataDir = process.env.BROWSER_SERVER_DATA_DIR;
7
+ if (!dataDir) {
8
+ console.error('BROWSER_SERVER_DATA_DIR 环境变量未设置');
9
+ process.exit(1);
10
+ }
11
+ const config = loadServerConfig(dataDir);
12
+ if (!config) {
13
+ console.error('无法读取服务配置');
14
+ process.exit(1);
15
+ }
16
+ initLogger(dataDir);
17
+ savePid(dataDir, process.pid);
18
+ const sessionManager = new SessionManager(dataDir);
19
+ const webServer = new WebServer(config, sessionManager);
20
+ const mcpServer = new McpServer(config, sessionManager);
21
+ async function startup() {
22
+ log.info('browser-server 启动中...');
23
+ log.info(`PID: ${process.pid}`);
24
+ log.info(`Web 端口: ${config.webPort}, MCP 端口: ${config.mcpPort}`);
25
+ await sessionManager.restoreSessions();
26
+ await webServer.start();
27
+ await mcpServer.start();
28
+ log.info('browser-server 启动完成');
29
+ }
30
+ async function shutdown() {
31
+ log.info('browser-server 正在关闭...');
32
+ await mcpServer.stop();
33
+ await webServer.stop();
34
+ await sessionManager.stopAllSessions();
35
+ removePid(dataDir);
36
+ closeLogger();
37
+ }
38
+ process.on('SIGTERM', async () => {
39
+ await shutdown();
40
+ process.exit(0);
41
+ });
42
+ process.on('SIGINT', async () => {
43
+ await shutdown();
44
+ process.exit(0);
45
+ });
46
+ process.on('uncaughtException', (err) => {
47
+ log.error('未捕获的异常:', err.message);
48
+ });
49
+ process.on('unhandledRejection', (reason) => {
50
+ log.error('未处理的 Promise rejection:', reason);
51
+ });
52
+ startup().catch((err) => {
53
+ log.error('启动失败:', err.message);
54
+ removePid(dataDir);
55
+ process.exit(1);
56
+ });
57
+ //# sourceMappingURL=daemon.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"daemon.js","sourceRoot":"","sources":["../src/daemon.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ export { SessionManager } from './session/session-manager.js';
2
+ export { ProcessGroup } from './session/process-group.js';
3
+ export { WebServer } from './server/web-server.js';
4
+ export { McpServer } from './server/mcp-server.js';
5
+ export { ConnectionPool } from './mcp/connection-pool.js';
6
+ export { defineTools } from './mcp/tools.js';
7
+ export type { ServerConfig, SessionInfo, SessionsData } from './utils/types.js';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAC9D,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC1D,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ export { SessionManager } from './session/session-manager.js';
2
+ export { ProcessGroup } from './session/process-group.js';
3
+ export { WebServer } from './server/web-server.js';
4
+ export { McpServer } from './server/mcp-server.js';
5
+ export { ConnectionPool } from './mcp/connection-pool.js';
6
+ export { defineTools } from './mcp/tools.js';
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAC9D,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC1D,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,14 @@
1
+ import type { BrowserConnection } from './tools.js';
2
+ /**
3
+ * 管理到各 session 的 Playwright CDP 连接。
4
+ * 懒加载:首次访问某 session 时建立连接,之后缓存复用。
5
+ */
6
+ export declare class ConnectionPool {
7
+ private connections;
8
+ private connecting;
9
+ getConnection(sessionId: string, cdpPort: number): Promise<BrowserConnection>;
10
+ private doConnect;
11
+ disconnect(sessionId: string): Promise<void>;
12
+ disconnectAll(): Promise<void>;
13
+ }
14
+ //# sourceMappingURL=connection-pool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection-pool.d.ts","sourceRoot":"","sources":["../../src/mcp/connection-pool.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAEpD;;;GAGG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,WAAW,CAA6C;IAChE,OAAO,CAAC,UAAU,CAAsD;IAElE,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC;YA0BrE,SAAS;IAcjB,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAY5C,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;CAKrC"}
@@ -0,0 +1,65 @@
1
+ import { chromium } from 'playwright-core';
2
+ import { log } from '../utils/logger.js';
3
+ /**
4
+ * 管理到各 session 的 Playwright CDP 连接。
5
+ * 懒加载:首次访问某 session 时建立连接,之后缓存复用。
6
+ */
7
+ export class ConnectionPool {
8
+ connections = new Map();
9
+ connecting = new Map();
10
+ async getConnection(sessionId, cdpPort) {
11
+ const existing = this.connections.get(sessionId);
12
+ if (existing) {
13
+ try {
14
+ await existing.activePage.evaluate('1');
15
+ return existing;
16
+ }
17
+ catch {
18
+ log.warn(`[${sessionId}] CDP 连接失效,重新连接...`);
19
+ this.connections.delete(sessionId);
20
+ }
21
+ }
22
+ const inflight = this.connecting.get(sessionId);
23
+ if (inflight)
24
+ return inflight;
25
+ const promise = this.doConnect(sessionId, cdpPort);
26
+ this.connecting.set(sessionId, promise);
27
+ try {
28
+ const conn = await promise;
29
+ this.connections.set(sessionId, conn);
30
+ return conn;
31
+ }
32
+ finally {
33
+ this.connecting.delete(sessionId);
34
+ }
35
+ }
36
+ async doConnect(sessionId, cdpPort) {
37
+ const cdpUrl = `http://127.0.0.1:${cdpPort}`;
38
+ log.info(`[${sessionId}] 连接 CDP: ${cdpUrl}`);
39
+ const browser = await chromium.connectOverCDP(cdpUrl, { timeout: 10_000 });
40
+ const contexts = browser.contexts();
41
+ const context = contexts[0] || await browser.newContext();
42
+ const pages = context.pages();
43
+ const activePage = pages[0] || await context.newPage();
44
+ log.info(`[${sessionId}] CDP 连接成功,${pages.length} 个标签页`);
45
+ return { browser, context, activePage, pages };
46
+ }
47
+ async disconnect(sessionId) {
48
+ const conn = this.connections.get(sessionId);
49
+ if (conn) {
50
+ try {
51
+ await conn.browser.close();
52
+ }
53
+ catch {
54
+ // ignore
55
+ }
56
+ this.connections.delete(sessionId);
57
+ }
58
+ }
59
+ async disconnectAll() {
60
+ for (const [id] of this.connections) {
61
+ await this.disconnect(id);
62
+ }
63
+ }
64
+ }
65
+ //# sourceMappingURL=connection-pool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection-pool.js","sourceRoot":"","sources":["../../src/mcp/connection-pool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAiC,MAAM,iBAAiB,CAAC;AAC1E,OAAO,EAAE,GAAG,EAAE,MAAM,oBAAoB,CAAC;AAGzC;;;GAGG;AACH,MAAM,OAAO,cAAc;IACjB,WAAW,GAAmC,IAAI,GAAG,EAAE,CAAC;IACxD,UAAU,GAA4C,IAAI,GAAG,EAAE,CAAC;IAExE,KAAK,CAAC,aAAa,CAAC,SAAiB,EAAE,OAAe;QACpD,MAAM,QAAQ,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACjD,IAAI,QAAQ,EAAE,CAAC;YACb,IAAI,CAAC;gBACH,MAAM,QAAQ,CAAC,UAAU,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;gBACxC,OAAO,QAAQ,CAAC;YAClB,CAAC;YAAC,MAAM,CAAC;gBACP,GAAG,CAAC,IAAI,CAAC,IAAI,SAAS,oBAAoB,CAAC,CAAC;gBAC5C,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;YACrC,CAAC;QACH,CAAC;QAED,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAChD,IAAI,QAAQ;YAAE,OAAO,QAAQ,CAAC;QAE9B,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;QACnD,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;QACxC,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC;YAC3B,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;YACtC,OAAO,IAAI,CAAC;QACd,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QACpC,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,SAAS,CAAC,SAAiB,EAAE,OAAe;QACxD,MAAM,MAAM,GAAG,oBAAoB,OAAO,EAAE,CAAC;QAC7C,GAAG,CAAC,IAAI,CAAC,IAAI,SAAS,aAAa,MAAM,EAAE,CAAC,CAAC;QAE7C,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,cAAc,CAAC,MAAM,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;QAC3E,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;QACpC,MAAM,OAAO,GAAG,QAAQ,CAAC,CAAC,CAAC,IAAI,MAAM,OAAO,CAAC,UAAU,EAAE,CAAC;QAC1D,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC;QAC9B,MAAM,UAAU,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC;QAEvD,GAAG,CAAC,IAAI,CAAC,IAAI,SAAS,cAAc,KAAK,CAAC,MAAM,OAAO,CAAC,CAAC;QACzD,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC;IACjD,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,SAAiB;QAChC,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAC7C,IAAI,IAAI,EAAE,CAAC;YACT,IAAI,CAAC;gBACH,MAAM,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;YAC7B,CAAC;YAAC,MAAM,CAAC;gBACP,SAAS;YACX,CAAC;YACD,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QACrC,CAAC;IACH,CAAC;IAED,KAAK,CAAC,aAAa;QACjB,KAAK,MAAM,CAAC,EAAE,CAAC,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;YACpC,MAAM,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC;CACF"}