cdp-tunnel 1.0.0
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.
- package/.github/workflows/publish.yml +92 -0
- package/.github/workflows/release-assets.yml +50 -0
- package/LICENSE +81 -0
- package/PUBLISH.md +65 -0
- package/README.md +228 -0
- package/cli/guide.html +753 -0
- package/cli/icon.svg +13 -0
- package/cli/icon128.png +0 -0
- package/cli/index.js +357 -0
- package/docs/README_CN.md +204 -0
- package/docs/config-page-screenshot.png +0 -0
- package/extension-new/background.js +294 -0
- package/extension-new/cdp/handler/forward.js +44 -0
- package/extension-new/cdp/handler/local.js +233 -0
- package/extension-new/cdp/handler/special.js +442 -0
- package/extension-new/cdp/index.js +104 -0
- package/extension-new/cdp/response.js +49 -0
- package/extension-new/config-page-preview.html +769 -0
- package/extension-new/config-page.js +318 -0
- package/extension-new/core/debugger.js +310 -0
- package/extension-new/core/state.js +384 -0
- package/extension-new/core/websocket.js +326 -0
- package/extension-new/features/automation-badge.js +113 -0
- package/extension-new/features/screencast.js +221 -0
- package/extension-new/icons/icon128.png +0 -0
- package/extension-new/icons/icon16.png +0 -0
- package/extension-new/icons/icon48.png +0 -0
- package/extension-new/manifest.json +39 -0
- package/extension-new/popup.html +72 -0
- package/extension-new/popup.js +34 -0
- package/extension-new/utils/config.js +20 -0
- package/extension-new/utils/diagnostics.js +560 -0
- package/extension-new/utils/helpers.js +25 -0
- package/extension-new/utils/logger.js +64 -0
- package/package.json +42 -0
- package/server/modules/config.js +28 -0
- package/server/modules/logger.js +197 -0
- package/server/proxy-server.js +1431 -0
- package/tests/playwright-demo.js +45 -0
- package/tests/playwright-interactive.js +261 -0
- package/tests/playwright-multi-demo.js +60 -0
- package/tests/playwright-multi.js +85 -0
- package/tests/playwright-single.js +41 -0
- package/tests/screenshot-config.js +35 -0
- package/tests/test-client.js +89 -0
- package/tests/test-multi-client.js +129 -0
package/cli/icon.svg
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="bg" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
4
|
+
<stop offset="0%" style="stop-color:#4285f4"/>
|
|
5
|
+
<stop offset="100%" style="stop-color:#34a853"/>
|
|
6
|
+
</linearGradient>
|
|
7
|
+
</defs>
|
|
8
|
+
<rect width="128" height="128" rx="24" fill="url(#bg)"/>
|
|
9
|
+
<circle cx="35" cy="64" r="18" fill="white"/>
|
|
10
|
+
<circle cx="93" cy="64" r="18" fill="white"/>
|
|
11
|
+
<rect x="35" y="56" width="58" height="16" fill="white"/>
|
|
12
|
+
<circle cx="64" cy="35" r="8" fill="white"/>
|
|
13
|
+
</svg>
|
package/cli/icon128.png
ADDED
|
Binary file
|
package/cli/index.js
ADDED
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { Command } = require('commander');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const os = require('os');
|
|
7
|
+
const { spawn, execSync } = require('child_process');
|
|
8
|
+
|
|
9
|
+
const CONFIG_DIR = path.join(os.homedir(), '.cdp-tunnel');
|
|
10
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
11
|
+
const PID_FILE = path.join(CONFIG_DIR, 'server.pid');
|
|
12
|
+
const LOG_FILE = path.join(CONFIG_DIR, 'server.log');
|
|
13
|
+
const EXTENSION_STATE_FILE = path.join(CONFIG_DIR, 'extension-state.json');
|
|
14
|
+
|
|
15
|
+
const program = new Command();
|
|
16
|
+
|
|
17
|
+
program
|
|
18
|
+
.name('cdp-tunnel')
|
|
19
|
+
.description('Chrome DevTools Protocol Tunnel')
|
|
20
|
+
.version('1.0.0');
|
|
21
|
+
|
|
22
|
+
function log(color, ...args) {
|
|
23
|
+
const colors = {
|
|
24
|
+
green: '\x1b[32m',
|
|
25
|
+
red: '\x1b[31m',
|
|
26
|
+
yellow: '\x1b[33m',
|
|
27
|
+
cyan: '\x1b[36m',
|
|
28
|
+
gray: '\x1b[90m',
|
|
29
|
+
bold: '\x1b[1m',
|
|
30
|
+
reset: '\x1b[0m'
|
|
31
|
+
};
|
|
32
|
+
console.log(colors[color] || '', ...args, colors.reset);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function ensureConfigDir() {
|
|
36
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
37
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function getConfig() {
|
|
42
|
+
ensureConfigDir();
|
|
43
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
44
|
+
return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
|
|
45
|
+
}
|
|
46
|
+
return { port: 9221 };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function saveConfig(config) {
|
|
50
|
+
ensureConfigDir();
|
|
51
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function isServerRunning() {
|
|
55
|
+
if (!fs.existsSync(PID_FILE)) return false;
|
|
56
|
+
const pid = parseInt(fs.readFileSync(PID_FILE, 'utf8'));
|
|
57
|
+
try {
|
|
58
|
+
process.kill(pid, 0);
|
|
59
|
+
return true;
|
|
60
|
+
} catch {
|
|
61
|
+
fs.unlinkSync(PID_FILE);
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function getServerPid() {
|
|
67
|
+
if (!fs.existsSync(PID_FILE)) return null;
|
|
68
|
+
return parseInt(fs.readFileSync(PID_FILE, 'utf8'));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function checkChromeExtension() {
|
|
72
|
+
if (!fs.existsSync(EXTENSION_STATE_FILE)) {
|
|
73
|
+
return { installed: false };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
const state = JSON.parse(fs.readFileSync(EXTENSION_STATE_FILE, 'utf8'));
|
|
78
|
+
// 检查连接状态,允许5分钟的时间差容错
|
|
79
|
+
const timeDiff = Math.abs(Date.now() - state.lastSeen);
|
|
80
|
+
if (state.connected && timeDiff < 300000) {
|
|
81
|
+
return { installed: true, connected: true };
|
|
82
|
+
}
|
|
83
|
+
return { installed: true, connected: false };
|
|
84
|
+
} catch (e) {
|
|
85
|
+
return { installed: false };
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function getExtensionPath() {
|
|
90
|
+
const cliDir = __dirname;
|
|
91
|
+
return path.join(cliDir, '..', 'extension-new');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function printExtensionGuide() {
|
|
95
|
+
const extPath = getExtensionPath();
|
|
96
|
+
|
|
97
|
+
console.log('');
|
|
98
|
+
log('yellow', '❌ Chrome 扩展未安装');
|
|
99
|
+
console.log('');
|
|
100
|
+
log('bold', '请按以下步骤安装:');
|
|
101
|
+
console.log('');
|
|
102
|
+
log('cyan', ' 1.'), console.log(' 已自动打开 Chrome 扩展页面');
|
|
103
|
+
log('cyan', ' 2.'), console.log(' 开启右上角「开发者模式」');
|
|
104
|
+
log('cyan', ' 3.'), console.log(' 点击「加载已解压的扩展程序」');
|
|
105
|
+
log('cyan', ' 4.'), console.log(' 选择以下目录:');
|
|
106
|
+
console.log('');
|
|
107
|
+
log('gray', ' ' + extPath);
|
|
108
|
+
console.log('');
|
|
109
|
+
log('cyan', ' 5.'), console.log(' 安装完成后,点击扩展图标连接服务器');
|
|
110
|
+
console.log('');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function openChromeExtensions() {
|
|
114
|
+
const platform = os.platform();
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
if (platform === 'darwin') {
|
|
118
|
+
execSync('open "chrome://extensions/"');
|
|
119
|
+
} else if (platform === 'win32') {
|
|
120
|
+
execSync('start chrome://extensions/');
|
|
121
|
+
} else {
|
|
122
|
+
execSync('xdg-open "chrome://extensions/"');
|
|
123
|
+
}
|
|
124
|
+
} catch (e) {
|
|
125
|
+
console.log('请手动打开: chrome://extensions/');
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
program
|
|
130
|
+
.command('start')
|
|
131
|
+
.description('启动 CDP Tunnel 服务器')
|
|
132
|
+
.option('-p, --port <port>', '指定端口', parseInt)
|
|
133
|
+
.action((options) => {
|
|
134
|
+
const config = getConfig();
|
|
135
|
+
const port = options.port || config.port;
|
|
136
|
+
|
|
137
|
+
if (isServerRunning()) {
|
|
138
|
+
console.log('');
|
|
139
|
+
log('yellow', '⚠️ 服务器已在运行');
|
|
140
|
+
log('cyan', ' 端口: ') + console.log(port);
|
|
141
|
+
log('cyan', ' PID: ') + console.log(getServerPid());
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const extStatus = checkChromeExtension();
|
|
146
|
+
if (!extStatus.installed) {
|
|
147
|
+
printExtensionGuide();
|
|
148
|
+
openChromeExtensions();
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
ensureConfigDir();
|
|
152
|
+
|
|
153
|
+
const serverPath = path.join(__dirname, '..', 'server', 'proxy-server.js');
|
|
154
|
+
|
|
155
|
+
const child = spawn('node', [serverPath], {
|
|
156
|
+
detached: true,
|
|
157
|
+
stdio: ['ignore', fs.openSync(LOG_FILE, 'a'), fs.openSync(LOG_FILE, 'a')],
|
|
158
|
+
env: { ...process.env, PORT: port.toString() }
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
child.unref();
|
|
162
|
+
|
|
163
|
+
fs.writeFileSync(PID_FILE, child.pid.toString());
|
|
164
|
+
|
|
165
|
+
if (port !== config.port) {
|
|
166
|
+
config.port = port;
|
|
167
|
+
saveConfig(config);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
console.log('');
|
|
171
|
+
log('green', '✓ CDP Tunnel 服务器已启动');
|
|
172
|
+
console.log('');
|
|
173
|
+
console.log(' 端口: ' + port);
|
|
174
|
+
console.log(' 插件: ws://localhost:' + port + '/plugin');
|
|
175
|
+
console.log(' CDP: http://localhost:' + port);
|
|
176
|
+
console.log('');
|
|
177
|
+
log('gray', ' 日志: ' + LOG_FILE);
|
|
178
|
+
console.log('');
|
|
179
|
+
|
|
180
|
+
if (!extStatus.installed) {
|
|
181
|
+
console.log('请先安装扩展,然后点击扩展图标连接服务器');
|
|
182
|
+
console.log('');
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
program
|
|
187
|
+
.command('stop')
|
|
188
|
+
.description('停止 CDP Tunnel 服务器')
|
|
189
|
+
.action(() => {
|
|
190
|
+
if (!isServerRunning()) {
|
|
191
|
+
log('yellow', '⚠️ 服务器未运行');
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const pid = getServerPid();
|
|
196
|
+
try {
|
|
197
|
+
process.kill(pid, 'SIGTERM');
|
|
198
|
+
fs.unlinkSync(PID_FILE);
|
|
199
|
+
log('green', '✓ 服务器已停止');
|
|
200
|
+
} catch (e) {
|
|
201
|
+
log('red', '✗ 停止服务器失败: ' + e.message);
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
program
|
|
206
|
+
.command('status')
|
|
207
|
+
.description('查看服务器状态')
|
|
208
|
+
.action(() => {
|
|
209
|
+
const config = getConfig();
|
|
210
|
+
const running = isServerRunning();
|
|
211
|
+
const extStatus = checkChromeExtension();
|
|
212
|
+
|
|
213
|
+
console.log('');
|
|
214
|
+
console.log('CDP Tunnel 状态');
|
|
215
|
+
console.log('─'.repeat(30));
|
|
216
|
+
console.log('');
|
|
217
|
+
console.log(' 服务器: ' + (running ? '\x1b[32m运行中\x1b[0m' : '\x1b[31m已停止\x1b[0m'));
|
|
218
|
+
console.log(' 端口: ' + config.port);
|
|
219
|
+
|
|
220
|
+
if (running) {
|
|
221
|
+
console.log(' PID: ' + getServerPid());
|
|
222
|
+
console.log(' CDP: http://localhost:' + config.port);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
console.log('');
|
|
226
|
+
if (extStatus.installed && extStatus.connected) {
|
|
227
|
+
console.log(' 扩展: \x1b[32m已连接\x1b[0m');
|
|
228
|
+
} else if (extStatus.installed) {
|
|
229
|
+
console.log(' 扩展: \x1b[33m已安装但未连接\x1b[0m');
|
|
230
|
+
console.log(' 提示: 请点击扩展图标连接服务器');
|
|
231
|
+
} else {
|
|
232
|
+
console.log(' 扩展: \x1b[31m未安装\x1b[0m');
|
|
233
|
+
console.log(' 提示: 运行 cdp-tunnel extension 安装扩展');
|
|
234
|
+
}
|
|
235
|
+
console.log('');
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
function generateGuideHtml() {
|
|
239
|
+
const extensionPath = path.join(__dirname, '..', 'extension-new');
|
|
240
|
+
const realPath = fs.realpathSync(extensionPath);
|
|
241
|
+
|
|
242
|
+
const templatePath = path.join(__dirname, 'guide.html');
|
|
243
|
+
let html = fs.readFileSync(templatePath, 'utf8');
|
|
244
|
+
|
|
245
|
+
// 替换路径占位符
|
|
246
|
+
html = html.replace(
|
|
247
|
+
/{{EXTENSION_PATH}}/g,
|
|
248
|
+
realPath
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
// 生成临时 HTML 文件
|
|
252
|
+
const tempHtmlPath = path.join(CONFIG_DIR, 'guide.html');
|
|
253
|
+
ensureConfigDir();
|
|
254
|
+
fs.writeFileSync(tempHtmlPath, html);
|
|
255
|
+
|
|
256
|
+
// 复制 icon 文件到同一目录
|
|
257
|
+
const iconSource = path.join(__dirname, 'icon128.png');
|
|
258
|
+
const iconTarget = path.join(CONFIG_DIR, 'icon128.png');
|
|
259
|
+
if (fs.existsSync(iconSource)) {
|
|
260
|
+
fs.copyFileSync(iconSource, iconTarget);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return tempHtmlPath;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
program
|
|
267
|
+
.command('extension')
|
|
268
|
+
.description('检测/安装 Chrome 扩展')
|
|
269
|
+
.action(() => {
|
|
270
|
+
const extStatus = checkChromeExtension();
|
|
271
|
+
|
|
272
|
+
if (extStatus.installed && extStatus.connected) {
|
|
273
|
+
log('green', '✓ Chrome 扩展已连接');
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (extStatus.installed) {
|
|
278
|
+
log('yellow', '⚠️ 扩展已安装但未连接');
|
|
279
|
+
console.log('正在打开连接指南...');
|
|
280
|
+
|
|
281
|
+
const guidePath = generateGuideHtml();
|
|
282
|
+
const platform = os.platform();
|
|
283
|
+
|
|
284
|
+
try {
|
|
285
|
+
if (platform === 'darwin') {
|
|
286
|
+
execSync('open "' + guidePath + '"');
|
|
287
|
+
} else if (platform === 'win32') {
|
|
288
|
+
execSync('start "" "' + guidePath + '"');
|
|
289
|
+
} else {
|
|
290
|
+
execSync('xdg-open "' + guidePath + '"');
|
|
291
|
+
}
|
|
292
|
+
console.log('已打开连接指南页面');
|
|
293
|
+
} catch (e) {
|
|
294
|
+
console.log('请手动打开: ' + guidePath);
|
|
295
|
+
}
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// 扩展未安装,生成指南并打开
|
|
300
|
+
console.log('');
|
|
301
|
+
log('yellow', '⚠️ Chrome 扩展未安装');
|
|
302
|
+
console.log('');
|
|
303
|
+
console.log('正在打开安装指南...');
|
|
304
|
+
|
|
305
|
+
const guidePath = generateGuideHtml();
|
|
306
|
+
const platform = os.platform();
|
|
307
|
+
|
|
308
|
+
try {
|
|
309
|
+
if (platform === 'darwin') {
|
|
310
|
+
execSync('open "' + guidePath + '"');
|
|
311
|
+
} else if (platform === 'win32') {
|
|
312
|
+
execSync('start "" "' + guidePath + '"');
|
|
313
|
+
} else {
|
|
314
|
+
execSync('xdg-open "' + guidePath + '"');
|
|
315
|
+
}
|
|
316
|
+
console.log('已打开安装指南页面');
|
|
317
|
+
} catch (e) {
|
|
318
|
+
console.log('请手动打开: ' + guidePath);
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
program
|
|
323
|
+
.command('config')
|
|
324
|
+
.description('配置管理')
|
|
325
|
+
.argument('<action>', 'get/set')
|
|
326
|
+
.argument('[key]', '配置项')
|
|
327
|
+
.argument('[value]', '配置值')
|
|
328
|
+
.action((action, key, value) => {
|
|
329
|
+
const config = getConfig();
|
|
330
|
+
|
|
331
|
+
if (action === 'get') {
|
|
332
|
+
if (key) {
|
|
333
|
+
console.log(config[key] || '');
|
|
334
|
+
} else {
|
|
335
|
+
console.log(JSON.stringify(config, null, 2));
|
|
336
|
+
}
|
|
337
|
+
} else if (action === 'set') {
|
|
338
|
+
if (!key || value === undefined) {
|
|
339
|
+
console.log('用法: cdp-tunnel config set <key> <value>');
|
|
340
|
+
console.log('示例: cdp-tunnel config set port 9221');
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
if (key === 'port') {
|
|
345
|
+
config[key] = parseInt(value);
|
|
346
|
+
} else {
|
|
347
|
+
config[key] = value;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
saveConfig(config);
|
|
351
|
+
log('green', '✓ 已保存: ' + key + ' = ' + config[key]);
|
|
352
|
+
} else {
|
|
353
|
+
console.log('用法: cdp-tunnel config <get|set> [key] [value]');
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
program.parse();
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
# CDP Tunnel
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
<img src="../extension-new/icons/icon128.png" alt="CDP Tunnel Logo" width="128" height="128">
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<strong>Chrome DevTools Protocol 桥接器</strong>
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
<p align="center">
|
|
12
|
+
Chrome 扩展,将浏览器暴露为 CDP 端点,<br>
|
|
13
|
+
支持多个 Playwright/Puppeteer 客户端同时连接和控制。
|
|
14
|
+
</p>
|
|
15
|
+
|
|
16
|
+
<p align="center">
|
|
17
|
+
<a href="../README.md">English</a> |
|
|
18
|
+
<a href="https://github.com/dyyz1993/cdp-tunnel">GitHub</a>
|
|
19
|
+
</p>
|
|
20
|
+
|
|
21
|
+
<p align="center">
|
|
22
|
+
<img src="https://img.shields.io/github/stars/dyyz1993/cdp-tunnel?style=social" alt="GitHub stars">
|
|
23
|
+
<img src="https://img.shields.io/github/forks/dyyz1993/cdp-tunnel?style=social" alt="GitHub forks">
|
|
24
|
+
<img src="https://img.shields.io/github/watchers/dyyz1993/cdp-tunnel?style=social" alt="GitHub watchers">
|
|
25
|
+
</p>
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## 架构
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
33
|
+
│ 代理服务器 │
|
|
34
|
+
│ (localhost:9221) │
|
|
35
|
+
│ │
|
|
36
|
+
│ /plugin ←─── Chrome 扩展 (WebSocket) │
|
|
37
|
+
│ HTTP ←─── Playwright/Puppeteer 客户端 │
|
|
38
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
39
|
+
↑ ↑ ↑
|
|
40
|
+
│ │ │
|
|
41
|
+
Client 1 Client 2 Client 3
|
|
42
|
+
(clientId_1) (clientId_2) (clientId_3)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## 功能特性
|
|
46
|
+
|
|
47
|
+
- **多客户端支持** - 多个 Playwright/Puppeteer 可同时连接
|
|
48
|
+
- **消息隔离** - 每个客户端创建的页面归该客户端所有
|
|
49
|
+
- **配置页面** - 可视化查看连接状态、客户端列表、受控页面
|
|
50
|
+
- **自动重连** - 扩展断开后自动重连服务器
|
|
51
|
+
|
|
52
|
+
## 截图
|
|
53
|
+
|
|
54
|
+

|
|
55
|
+
|
|
56
|
+
## 快速开始
|
|
57
|
+
|
|
58
|
+
### 方式一:npm 全局安装(推荐)
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
# 全局安装
|
|
62
|
+
npm install -g cdp-tunnel
|
|
63
|
+
|
|
64
|
+
# 启动服务器
|
|
65
|
+
cdp-tunnel start
|
|
66
|
+
|
|
67
|
+
# 查看状态
|
|
68
|
+
cdp-tunnel status
|
|
69
|
+
|
|
70
|
+
# 安装 Chrome 扩展(打开交互式安装向导)
|
|
71
|
+
cdp-tunnel extension
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### 方式二:从 GitHub Releases 下载
|
|
75
|
+
|
|
76
|
+
1. 访问 [GitHub Releases](https://github.com/dyyz1993/cdp-tunnel/releases)
|
|
77
|
+
2. 下载最新版本的 `cdp-tunnel-extension.zip`
|
|
78
|
+
3. 解压文件
|
|
79
|
+
4. 打开 Chrome 扩展页面 `chrome://extensions/`
|
|
80
|
+
5. 开启「开发者模式」
|
|
81
|
+
6. 点击「加载已解压的扩展程序」
|
|
82
|
+
7. 选择解压后的扩展目录
|
|
83
|
+
|
|
84
|
+
### 方式三:手动安装
|
|
85
|
+
|
|
86
|
+
#### 1. 启动代理服务器
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
cd server
|
|
90
|
+
npm install
|
|
91
|
+
node proxy-server.js
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
服务器将在 `localhost:9221` 启动。
|
|
95
|
+
|
|
96
|
+
#### 2. 安装 Chrome 扩展
|
|
97
|
+
|
|
98
|
+
1. 打开 `chrome://extensions/`
|
|
99
|
+
2. 开启「开发者模式」
|
|
100
|
+
3. 点击「加载已解压的扩展程序」
|
|
101
|
+
4. 选择 `extension-new` 目录
|
|
102
|
+
|
|
103
|
+
#### 3. 连接扩展
|
|
104
|
+
|
|
105
|
+
点击扩展图标,在配置页面输入服务器地址,点击「保存并连接」。
|
|
106
|
+
|
|
107
|
+
### 客户端连接
|
|
108
|
+
|
|
109
|
+
```javascript
|
|
110
|
+
// Playwright
|
|
111
|
+
const { chromium } = require('playwright');
|
|
112
|
+
|
|
113
|
+
const browser = await chromium.connectOverCDP('http://localhost:9221');
|
|
114
|
+
const context = browser.contexts()[0];
|
|
115
|
+
const page = await context.newPage();
|
|
116
|
+
await page.goto('https://example.com');
|
|
117
|
+
|
|
118
|
+
// Puppeteer
|
|
119
|
+
const puppeteer = require('puppeteer');
|
|
120
|
+
|
|
121
|
+
const browser = await puppeteer.connect({
|
|
122
|
+
browserWSEndpoint: 'ws://localhost:9221'
|
|
123
|
+
});
|
|
124
|
+
const page = await browser.newPage();
|
|
125
|
+
await page.goto('https://example.com');
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## 多客户端使用
|
|
129
|
+
|
|
130
|
+
所有客户端连接同一个端点 `http://localhost:9221`,服务器自动为每个连接分配唯一的 `clientId`。
|
|
131
|
+
|
|
132
|
+
```javascript
|
|
133
|
+
// 多个客户端可以同时连接
|
|
134
|
+
const browser1 = await chromium.connectOverCDP('http://localhost:9221');
|
|
135
|
+
const browser2 = await chromium.connectOverCDP('http://localhost:9221');
|
|
136
|
+
const browser3 = await chromium.connectOverCDP('http://localhost:9221');
|
|
137
|
+
|
|
138
|
+
// 每个客户端创建的页面互不干扰
|
|
139
|
+
const page1 = await browser1.contexts()[0].newPage();
|
|
140
|
+
const page2 = await browser2.contexts()[0].newPage();
|
|
141
|
+
const page3 = await browser3.contexts()[0].newPage();
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## 配置页面
|
|
145
|
+
|
|
146
|
+
点击扩展图标打开配置页面,可以查看:
|
|
147
|
+
|
|
148
|
+
- **CDP 客户端列表** - 显示连接的 Playwright/Puppeteer 客户端
|
|
149
|
+
- **受控页面列表** - 显示被控制的页面,支持点击跳转
|
|
150
|
+
- **活动日志** - 连接状态变化记录
|
|
151
|
+
|
|
152
|
+
## 项目结构
|
|
153
|
+
|
|
154
|
+
```
|
|
155
|
+
cdp-tunnel/
|
|
156
|
+
├── server/
|
|
157
|
+
│ └── proxy-server.js # 代理服务器
|
|
158
|
+
├── extension-new/
|
|
159
|
+
│ ├── background.js # 扩展 Service Worker
|
|
160
|
+
│ ├── config-page-preview.html # 配置页面
|
|
161
|
+
│ ├── config-page.js # 配置页面脚本
|
|
162
|
+
│ ├── core/
|
|
163
|
+
│ │ ├── state.js # 状态管理
|
|
164
|
+
│ │ └── websocket.js # WebSocket 连接管理
|
|
165
|
+
│ └── features/
|
|
166
|
+
│ ├── cdp-router.js # CDP 消息路由
|
|
167
|
+
│ └── screencast.js # 截图功能
|
|
168
|
+
└── tests/
|
|
169
|
+
├── playwright-single.js # 单客户端测试
|
|
170
|
+
├── playwright-multi.js # 多客户端测试
|
|
171
|
+
└── playwright-interactive.js # 交互式测试
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## 测试
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
# 单客户端测试
|
|
178
|
+
node tests/playwright-single.js
|
|
179
|
+
|
|
180
|
+
# 多客户端测试
|
|
181
|
+
node tests/playwright-multi.js
|
|
182
|
+
|
|
183
|
+
# 交互式测试
|
|
184
|
+
node tests/playwright-interactive.js
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## 注意事项
|
|
188
|
+
|
|
189
|
+
1. **端口占用** - 确保 9221 端口未被占用
|
|
190
|
+
2. **扩展权限** - 扩展需要 `debugger`、`tabs` 等权限
|
|
191
|
+
3. **浏览器限制** - 同一浏览器只能被一个扩展通过 debugger 控制
|
|
192
|
+
|
|
193
|
+
## 许可证
|
|
194
|
+
|
|
195
|
+
本项目采用 Apache License 2.0 协议,并附加署名要求。
|
|
196
|
+
|
|
197
|
+
详见 [LICENSE](../LICENSE)。
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
如果你在工作中使用了本项目,请注明来源:
|
|
202
|
+
- 项目:CDP Tunnel
|
|
203
|
+
- 作者:dyyz1993
|
|
204
|
+
- 来源:https://github.com/dyyz1993/cdp-tunnel
|
|
Binary file
|