browser-ipc-cdp 1.0.0 → 1.2.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/bin/cli.js +3 -1
- package/lib/browser.js +293 -68
- package/lib/mcp.js +23 -18
- package/lib/network.js +69 -6
- package/package.json +1 -1
package/bin/cli.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* npx browser-ipc-cdp --status # Ver estado actual
|
|
13
13
|
* npx browser-ipc-cdp --uninstall # Desinstalar
|
|
14
14
|
*/
|
|
15
|
-
const { detectBrowsers, findBrowser, launchBrowser, detectExistingCDP } = require('../lib/browser');
|
|
15
|
+
const { detectBrowsers, findBrowser, launchBrowser, detectExistingCDP, IS_WSL, IS_WIN, IS_MAC } = require('../lib/browser');
|
|
16
16
|
const { setupPortproxy, setupFirewall } = require('../lib/network');
|
|
17
17
|
const { updateMcpJson, getWslHostIp } = require('../lib/mcp');
|
|
18
18
|
const { saveCdpInfo, loadCdpInfo } = require('../lib/config');
|
|
@@ -30,6 +30,8 @@ for (let i = 0; i < args.length; i++) {
|
|
|
30
30
|
|
|
31
31
|
async function main() {
|
|
32
32
|
banner();
|
|
33
|
+
const platform = IS_WIN ? 'Windows' : IS_WSL ? 'WSL (Windows host)' : IS_MAC ? 'macOS' : 'Linux';
|
|
34
|
+
log(`Plataforma: ${platform}`);
|
|
33
35
|
|
|
34
36
|
// --list: listar navegadores
|
|
35
37
|
if (flags.list) {
|
package/lib/browser.js
CHANGED
|
@@ -4,43 +4,203 @@ const path = require('path');
|
|
|
4
4
|
const http = require('http');
|
|
5
5
|
const { log, success, warn } = require('./logger');
|
|
6
6
|
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
'
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
7
|
+
const IS_WIN = process.platform === 'win32';
|
|
8
|
+
const IS_MAC = process.platform === 'darwin';
|
|
9
|
+
|
|
10
|
+
// Detectar WSL: Linux pero con acceso a Windows
|
|
11
|
+
function isWSL() {
|
|
12
|
+
if (IS_WIN || IS_MAC) return false;
|
|
13
|
+
try {
|
|
14
|
+
const version = fs.readFileSync('/proc/version', 'utf-8').toLowerCase();
|
|
15
|
+
if (version.includes('microsoft') || version.includes('wsl')) return true;
|
|
16
|
+
} catch (e) {}
|
|
17
|
+
try {
|
|
18
|
+
return fs.existsSync('/proc/sys/fs/binfmt_misc/WSLInterop');
|
|
19
|
+
} catch (e) {}
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const IS_WSL = isWSL();
|
|
24
|
+
const HOME = process.env.HOME || process.env.USERPROFILE || '';
|
|
25
|
+
|
|
26
|
+
// En WSL: obtener paths de Windows via /mnt/c/
|
|
27
|
+
function getWindowsEnv(varName) {
|
|
28
|
+
if (IS_WIN) return process.env[varName] || '';
|
|
29
|
+
if (IS_WSL) {
|
|
30
|
+
try {
|
|
31
|
+
const result = execSync(`cmd.exe /c echo %${varName}%`, { encoding: 'utf-8', timeout: 5000, stdio: 'pipe' });
|
|
32
|
+
const winPath = result.trim();
|
|
33
|
+
if (winPath && !winPath.includes('%')) {
|
|
34
|
+
// Convertir C:\Users\... a /mnt/c/Users/...
|
|
35
|
+
return winPath.replace(/\\/g, '/').replace(/^([A-Za-z]):/, (_, d) => `/mnt/${d.toLowerCase()}`);
|
|
36
|
+
}
|
|
37
|
+
} catch (e) {}
|
|
38
|
+
}
|
|
39
|
+
return '';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const LOCALAPPDATA = IS_WIN ? (process.env.LOCALAPPDATA || '') : getWindowsEnv('LOCALAPPDATA');
|
|
43
|
+
const USERPROFILE = IS_WIN ? (process.env.USERPROFILE || '') : getWindowsEnv('USERPROFILE');
|
|
23
44
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
45
|
+
// Program Files: en WSL usar /mnt/c/
|
|
46
|
+
const PROGRAMFILES = IS_WIN ? (process.env.PROGRAMFILES || 'C:\\Program Files')
|
|
47
|
+
: IS_WSL ? '/mnt/c/Program Files' : '';
|
|
48
|
+
const PROGRAMFILES86 = IS_WIN ? (process.env['PROGRAMFILES(X86)'] || 'C:\\Program Files (x86)')
|
|
49
|
+
: IS_WSL ? '/mnt/c/Program Files (x86)' : '';
|
|
50
|
+
|
|
51
|
+
// Rutas dinámicas por plataforma
|
|
52
|
+
const BROWSER_REGISTRY = {
|
|
53
|
+
brave: {
|
|
54
|
+
win: [
|
|
55
|
+
path.join(PROGRAMFILES, 'BraveSoftware', 'Brave-Browser', 'Application', 'brave.exe'),
|
|
56
|
+
path.join(PROGRAMFILES86, 'BraveSoftware', 'Brave-Browser', 'Application', 'brave.exe'),
|
|
57
|
+
path.join(LOCALAPPDATA, 'BraveSoftware', 'Brave-Browser', 'Application', 'brave.exe'),
|
|
58
|
+
],
|
|
59
|
+
wsl: [
|
|
60
|
+
path.join(PROGRAMFILES, 'BraveSoftware', 'Brave-Browser', 'Application', 'brave.exe'),
|
|
61
|
+
path.join(PROGRAMFILES86, 'BraveSoftware', 'Brave-Browser', 'Application', 'brave.exe'),
|
|
62
|
+
...(LOCALAPPDATA ? [path.join(LOCALAPPDATA, 'BraveSoftware', 'Brave-Browser', 'Application', 'brave.exe')] : []),
|
|
63
|
+
],
|
|
64
|
+
mac: [
|
|
65
|
+
'/Applications/Brave Browser.app/Contents/MacOS/Brave Browser',
|
|
66
|
+
path.join(HOME, 'Applications', 'Brave Browser.app', 'Contents', 'MacOS', 'Brave Browser'),
|
|
67
|
+
],
|
|
68
|
+
linux: [
|
|
69
|
+
'/usr/bin/brave-browser',
|
|
70
|
+
'/usr/bin/brave',
|
|
71
|
+
'/snap/bin/brave',
|
|
72
|
+
'/opt/brave.com/brave/brave-browser',
|
|
73
|
+
],
|
|
74
|
+
userData: {
|
|
75
|
+
win: path.join(LOCALAPPDATA, 'BraveSoftware', 'Brave-Browser', 'User Data'),
|
|
76
|
+
wsl: LOCALAPPDATA ? path.join(LOCALAPPDATA, 'BraveSoftware', 'Brave-Browser', 'User Data') : '',
|
|
77
|
+
mac: path.join(HOME, 'Library', 'Application Support', 'BraveSoftware', 'Brave-Browser'),
|
|
78
|
+
linux: path.join(HOME, '.config', 'BraveSoftware', 'Brave-Browser'),
|
|
79
|
+
},
|
|
80
|
+
processName: { win: 'brave.exe', wsl: 'brave.exe', mac: 'Brave Browser', linux: 'brave' },
|
|
81
|
+
},
|
|
82
|
+
chrome: {
|
|
83
|
+
win: [
|
|
84
|
+
path.join(PROGRAMFILES, 'Google', 'Chrome', 'Application', 'chrome.exe'),
|
|
85
|
+
path.join(PROGRAMFILES86, 'Google', 'Chrome', 'Application', 'chrome.exe'),
|
|
86
|
+
path.join(LOCALAPPDATA, 'Google', 'Chrome', 'Application', 'chrome.exe'),
|
|
87
|
+
],
|
|
88
|
+
wsl: [
|
|
89
|
+
path.join(PROGRAMFILES, 'Google', 'Chrome', 'Application', 'chrome.exe'),
|
|
90
|
+
path.join(PROGRAMFILES86, 'Google', 'Chrome', 'Application', 'chrome.exe'),
|
|
91
|
+
...(LOCALAPPDATA ? [path.join(LOCALAPPDATA, 'Google', 'Chrome', 'Application', 'chrome.exe')] : []),
|
|
92
|
+
],
|
|
93
|
+
mac: [
|
|
94
|
+
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
|
|
95
|
+
path.join(HOME, 'Applications', 'Google Chrome.app', 'Contents', 'MacOS', 'Google Chrome'),
|
|
96
|
+
],
|
|
97
|
+
linux: [
|
|
98
|
+
'/usr/bin/google-chrome',
|
|
99
|
+
'/usr/bin/google-chrome-stable',
|
|
100
|
+
'/snap/bin/chromium',
|
|
101
|
+
'/usr/bin/chromium-browser',
|
|
102
|
+
'/usr/bin/chromium',
|
|
103
|
+
],
|
|
104
|
+
userData: {
|
|
105
|
+
win: path.join(LOCALAPPDATA, 'Google', 'Chrome', 'User Data'),
|
|
106
|
+
wsl: LOCALAPPDATA ? path.join(LOCALAPPDATA, 'Google', 'Chrome', 'User Data') : '',
|
|
107
|
+
mac: path.join(HOME, 'Library', 'Application Support', 'Google', 'Chrome'),
|
|
108
|
+
linux: path.join(HOME, '.config', 'google-chrome'),
|
|
109
|
+
},
|
|
110
|
+
processName: { win: 'chrome.exe', wsl: 'chrome.exe', mac: 'Google Chrome', linux: 'chrome' },
|
|
111
|
+
},
|
|
112
|
+
edge: {
|
|
113
|
+
win: [
|
|
114
|
+
path.join(PROGRAMFILES86, 'Microsoft', 'Edge', 'Application', 'msedge.exe'),
|
|
115
|
+
path.join(PROGRAMFILES, 'Microsoft', 'Edge', 'Application', 'msedge.exe'),
|
|
116
|
+
],
|
|
117
|
+
wsl: [
|
|
118
|
+
path.join(PROGRAMFILES86, 'Microsoft', 'Edge', 'Application', 'msedge.exe'),
|
|
119
|
+
path.join(PROGRAMFILES, 'Microsoft', 'Edge', 'Application', 'msedge.exe'),
|
|
120
|
+
],
|
|
121
|
+
mac: [
|
|
122
|
+
'/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge',
|
|
123
|
+
],
|
|
124
|
+
linux: [
|
|
125
|
+
'/usr/bin/microsoft-edge',
|
|
126
|
+
'/usr/bin/microsoft-edge-stable',
|
|
127
|
+
],
|
|
128
|
+
userData: {
|
|
129
|
+
win: path.join(LOCALAPPDATA, 'Microsoft', 'Edge', 'User Data'),
|
|
130
|
+
wsl: LOCALAPPDATA ? path.join(LOCALAPPDATA, 'Microsoft', 'Edge', 'User Data') : '',
|
|
131
|
+
mac: path.join(HOME, 'Library', 'Application Support', 'Microsoft Edge'),
|
|
132
|
+
linux: path.join(HOME, '.config', 'microsoft-edge'),
|
|
133
|
+
},
|
|
134
|
+
processName: { win: 'msedge.exe', wsl: 'msedge.exe', mac: 'Microsoft Edge', linux: 'msedge' },
|
|
135
|
+
},
|
|
136
|
+
chromium: {
|
|
137
|
+
win: [
|
|
138
|
+
path.join(LOCALAPPDATA, 'Chromium', 'Application', 'chrome.exe'),
|
|
139
|
+
],
|
|
140
|
+
wsl: [
|
|
141
|
+
...(LOCALAPPDATA ? [path.join(LOCALAPPDATA, 'Chromium', 'Application', 'chrome.exe')] : []),
|
|
142
|
+
],
|
|
143
|
+
mac: [
|
|
144
|
+
'/Applications/Chromium.app/Contents/MacOS/Chromium',
|
|
145
|
+
],
|
|
146
|
+
linux: [
|
|
147
|
+
'/usr/bin/chromium',
|
|
148
|
+
'/usr/bin/chromium-browser',
|
|
149
|
+
'/snap/bin/chromium',
|
|
150
|
+
],
|
|
151
|
+
userData: {
|
|
152
|
+
win: path.join(LOCALAPPDATA, 'Chromium', 'User Data'),
|
|
153
|
+
wsl: LOCALAPPDATA ? path.join(LOCALAPPDATA, 'Chromium', 'User Data') : '',
|
|
154
|
+
mac: path.join(HOME, 'Library', 'Application Support', 'Chromium'),
|
|
155
|
+
linux: path.join(HOME, '.config', 'chromium'),
|
|
156
|
+
},
|
|
157
|
+
processName: { win: 'chrome.exe', wsl: 'chrome.exe', mac: 'Chromium', linux: 'chromium' },
|
|
158
|
+
},
|
|
28
159
|
};
|
|
29
160
|
|
|
161
|
+
function getPlatform() {
|
|
162
|
+
if (IS_WIN) return 'win';
|
|
163
|
+
if (IS_WSL) return 'wsl';
|
|
164
|
+
if (IS_MAC) return 'mac';
|
|
165
|
+
return 'linux';
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function getBrowserPaths(name) {
|
|
169
|
+
const entry = BROWSER_REGISTRY[name];
|
|
170
|
+
if (!entry) return { paths: [], userData: '', processName: '' };
|
|
171
|
+
const plat = getPlatform();
|
|
172
|
+
return {
|
|
173
|
+
paths: entry[plat] || [],
|
|
174
|
+
userData: (entry.userData && entry.userData[plat]) || '',
|
|
175
|
+
processName: (entry.processName && entry.processName[plat]) || name,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
30
179
|
function detectBrowsers() {
|
|
31
180
|
const found = [];
|
|
32
|
-
for (const
|
|
181
|
+
for (const name of Object.keys(BROWSER_REGISTRY)) {
|
|
182
|
+
const { paths, userData } = getBrowserPaths(name);
|
|
33
183
|
for (const p of paths) {
|
|
34
184
|
if (fs.existsSync(p)) {
|
|
35
|
-
found.push({
|
|
36
|
-
name,
|
|
37
|
-
exe: p,
|
|
38
|
-
userData: BROWSER_USER_DATA[name] || '',
|
|
39
|
-
});
|
|
185
|
+
found.push({ name, exe: p, userData });
|
|
40
186
|
break;
|
|
41
187
|
}
|
|
42
188
|
}
|
|
43
189
|
}
|
|
190
|
+
// Fallback: buscar en PATH
|
|
191
|
+
const cmds = IS_WIN
|
|
192
|
+
? ['brave', 'chrome', 'msedge']
|
|
193
|
+
: ['brave-browser', 'brave', 'google-chrome', 'google-chrome-stable', 'chromium', 'chromium-browser', 'microsoft-edge'];
|
|
194
|
+
for (const cmd of cmds) {
|
|
195
|
+
try {
|
|
196
|
+
const which = IS_WIN
|
|
197
|
+
? execSync(`where ${cmd} 2>nul`, { encoding: 'utf-8', timeout: 5000 }).trim().split('\n')[0]
|
|
198
|
+
: execSync(`which ${cmd} 2>/dev/null`, { encoding: 'utf-8', timeout: 5000 }).trim();
|
|
199
|
+
if (which && fs.existsSync(which) && !found.some(b => b.exe === which)) {
|
|
200
|
+
found.push({ name: cmd, exe: which, userData: '' });
|
|
201
|
+
}
|
|
202
|
+
} catch (e) {}
|
|
203
|
+
}
|
|
44
204
|
return found;
|
|
45
205
|
}
|
|
46
206
|
|
|
@@ -85,13 +245,19 @@ function testCdp(port, timeout = 3000) {
|
|
|
85
245
|
}
|
|
86
246
|
|
|
87
247
|
function isBrowserRunning(exe) {
|
|
88
|
-
const exeName = path.basename(exe)
|
|
248
|
+
const exeName = path.basename(exe);
|
|
89
249
|
try {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
250
|
+
if (IS_WIN || IS_WSL) {
|
|
251
|
+
// Windows y WSL: usar tasklist de Windows
|
|
252
|
+
const cmd = IS_WSL
|
|
253
|
+
? `cmd.exe /c tasklist /FI "IMAGENAME eq ${exeName}" /FO CSV /NH`
|
|
254
|
+
: `tasklist /FI "IMAGENAME eq ${exeName}" /FO CSV /NH`;
|
|
255
|
+
const result = execSync(cmd, { timeout: 10000, encoding: 'utf-8', stdio: 'pipe' });
|
|
256
|
+
return result.toLowerCase().includes(exeName.toLowerCase());
|
|
257
|
+
} else {
|
|
258
|
+
const result = execSync(`pgrep -f "${exeName}"`, { timeout: 5000, stdio: 'pipe' });
|
|
259
|
+
return result.length > 0;
|
|
260
|
+
}
|
|
95
261
|
} catch (e) {
|
|
96
262
|
return false;
|
|
97
263
|
}
|
|
@@ -115,14 +281,22 @@ async function detectExistingCDP(browser) {
|
|
|
115
281
|
}
|
|
116
282
|
}
|
|
117
283
|
|
|
118
|
-
// 2. Command line
|
|
284
|
+
// 2. Command line scan
|
|
119
285
|
try {
|
|
120
|
-
const exeName = path.basename(browser.exe)
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
286
|
+
const exeName = path.basename(browser.exe);
|
|
287
|
+
let cmdOutput = '';
|
|
288
|
+
if (IS_WIN || IS_WSL) {
|
|
289
|
+
const wmicCmd = `wmic process where "name='${exeName}'" get commandline /format:list`;
|
|
290
|
+
cmdOutput = IS_WSL
|
|
291
|
+
? execSync(`cmd.exe /c ${wmicCmd}`, { timeout: 10000, encoding: 'utf-8', stdio: 'pipe' })
|
|
292
|
+
: execSync(wmicCmd, { timeout: 10000, encoding: 'utf-8', stdio: 'pipe' });
|
|
293
|
+
} else {
|
|
294
|
+
cmdOutput = execSync(
|
|
295
|
+
`ps aux | grep "${exeName}" | grep -v grep`,
|
|
296
|
+
{ timeout: 10000, encoding: 'utf-8', stdio: 'pipe' }
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
const match = cmdOutput.match(/--remote-debugging-port[=\s](\d{2,5})/);
|
|
126
300
|
if (match) {
|
|
127
301
|
const port = parseInt(match[1]);
|
|
128
302
|
if (port > 0) {
|
|
@@ -132,37 +306,70 @@ async function detectExistingCDP(browser) {
|
|
|
132
306
|
}
|
|
133
307
|
} catch (e) {}
|
|
134
308
|
|
|
135
|
-
// 3. Scan ports
|
|
309
|
+
// 3. Scan ports
|
|
136
310
|
try {
|
|
137
|
-
const exeName = path.basename(browser.exe)
|
|
138
|
-
const taskResult = execSync(
|
|
139
|
-
`tasklist /FI "IMAGENAME eq ${exeName}" /FO CSV /NH`,
|
|
140
|
-
{ timeout: 10000, encoding: 'utf-8' }
|
|
141
|
-
);
|
|
311
|
+
const exeName = path.basename(browser.exe);
|
|
142
312
|
const pids = new Set();
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
313
|
+
|
|
314
|
+
if (IS_WIN || IS_WSL) {
|
|
315
|
+
const taskCmd = `tasklist /FI "IMAGENAME eq ${exeName}" /FO CSV /NH`;
|
|
316
|
+
const taskResult = IS_WSL
|
|
317
|
+
? execSync(`cmd.exe /c ${taskCmd}`, { timeout: 10000, encoding: 'utf-8', stdio: 'pipe' })
|
|
318
|
+
: execSync(taskCmd, { timeout: 10000, encoding: 'utf-8', stdio: 'pipe' });
|
|
319
|
+
taskResult.split('\n').forEach(line => {
|
|
320
|
+
const parts = line.trim().replace(/"/g, '').split(',');
|
|
321
|
+
if (parts.length >= 2) {
|
|
322
|
+
const pid = parseInt(parts[1]);
|
|
323
|
+
if (!isNaN(pid)) pids.add(pid);
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
} else {
|
|
327
|
+
const pgrepResult = execSync(`pgrep -f "${exeName}"`,
|
|
328
|
+
{ timeout: 5000, encoding: 'utf-8', stdio: 'pipe' });
|
|
329
|
+
pgrepResult.trim().split('\n').forEach(p => {
|
|
330
|
+
const pid = parseInt(p.trim());
|
|
147
331
|
if (!isNaN(pid)) pids.add(pid);
|
|
148
|
-
}
|
|
149
|
-
}
|
|
332
|
+
});
|
|
333
|
+
}
|
|
150
334
|
|
|
151
335
|
if (pids.size > 0) {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
336
|
+
if (IS_WIN || IS_WSL) {
|
|
337
|
+
const netstatCmd = 'netstat -ano -p tcp';
|
|
338
|
+
const netstat = IS_WSL
|
|
339
|
+
? execSync(`cmd.exe /c ${netstatCmd}`, { timeout: 10000, encoding: 'utf-8', stdio: 'pipe' })
|
|
340
|
+
: execSync(netstatCmd, { timeout: 10000, encoding: 'utf-8', stdio: 'pipe' });
|
|
341
|
+
for (const line of netstat.split('\n')) {
|
|
342
|
+
const tokens = line.trim().split(/\s+/);
|
|
343
|
+
if (tokens.length >= 5 && tokens[3] === 'LISTENING') {
|
|
344
|
+
const pid = parseInt(tokens[4]);
|
|
345
|
+
if (pids.has(pid)) {
|
|
346
|
+
const portStr = tokens[1].split(':').pop();
|
|
347
|
+
const port = parseInt(portStr);
|
|
348
|
+
if (port > 1024) {
|
|
349
|
+
const cdp = await testCdp(port);
|
|
350
|
+
if (cdp) return port;
|
|
351
|
+
}
|
|
163
352
|
}
|
|
164
353
|
}
|
|
165
354
|
}
|
|
355
|
+
} else {
|
|
356
|
+
// Mac/Linux: use lsof or ss
|
|
357
|
+
for (const pid of pids) {
|
|
358
|
+
try {
|
|
359
|
+
const lsof = execSync(`lsof -i -P -n -p ${pid} 2>/dev/null | grep LISTEN`,
|
|
360
|
+
{ timeout: 10000, encoding: 'utf-8', stdio: 'pipe' });
|
|
361
|
+
for (const line of lsof.split('\n')) {
|
|
362
|
+
const match = line.match(/:(\d+)\s/);
|
|
363
|
+
if (match) {
|
|
364
|
+
const port = parseInt(match[1]);
|
|
365
|
+
if (port > 1024) {
|
|
366
|
+
const cdp = await testCdp(port);
|
|
367
|
+
if (cdp) return port;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
} catch (e) {}
|
|
372
|
+
}
|
|
166
373
|
}
|
|
167
374
|
}
|
|
168
375
|
} catch (e) {}
|
|
@@ -173,7 +380,13 @@ async function detectExistingCDP(browser) {
|
|
|
173
380
|
function killBrowser(exe) {
|
|
174
381
|
const exeName = path.basename(exe);
|
|
175
382
|
try {
|
|
176
|
-
|
|
383
|
+
if (IS_WIN) {
|
|
384
|
+
execSync(`taskkill /F /IM ${exeName}`, { timeout: 10000, stdio: 'pipe' });
|
|
385
|
+
} else if (IS_WSL) {
|
|
386
|
+
execSync(`cmd.exe /c taskkill /F /IM ${exeName}`, { timeout: 10000, stdio: 'pipe' });
|
|
387
|
+
} else {
|
|
388
|
+
execSync(`pkill -f "${exeName}"`, { timeout: 10000, stdio: 'pipe' });
|
|
389
|
+
}
|
|
177
390
|
} catch (e) {}
|
|
178
391
|
}
|
|
179
392
|
|
|
@@ -198,10 +411,22 @@ function launchBrowser(browser, { port = 0, clean = false } = {}) {
|
|
|
198
411
|
];
|
|
199
412
|
if (clean) args.push(`--user-data-dir=${userData}`);
|
|
200
413
|
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
414
|
+
const spawnOpts = { detached: true, stdio: 'ignore' };
|
|
415
|
+
let child;
|
|
416
|
+
|
|
417
|
+
if (IS_WSL) {
|
|
418
|
+
// WSL: convertir ruta /mnt/c/... a C:\... y lanzar via cmd.exe
|
|
419
|
+
const winExe = browser.exe.replace(/^\/mnt\/([a-z])\//, (_, d) => `${d.toUpperCase()}:\\`).replace(/\//g, '\\');
|
|
420
|
+
const winArgs = args.map(a => {
|
|
421
|
+
if (a.startsWith('--user-data-dir=/mnt/')) {
|
|
422
|
+
return a.replace(/^--user-data-dir=\/mnt\/([a-z])\//, (_, d) => `--user-data-dir=${d.toUpperCase()}:\\`).replace(/\//g, '\\');
|
|
423
|
+
}
|
|
424
|
+
return a;
|
|
425
|
+
});
|
|
426
|
+
child = spawn('cmd.exe', ['/c', 'start', '', winExe, ...winArgs], spawnOpts);
|
|
427
|
+
} else {
|
|
428
|
+
child = spawn(browser.exe, args, spawnOpts);
|
|
429
|
+
}
|
|
205
430
|
child.unref();
|
|
206
431
|
|
|
207
432
|
log(` PID: ${child.pid}`);
|
|
@@ -243,4 +468,4 @@ function launchBrowser(browser, { port = 0, clean = false } = {}) {
|
|
|
243
468
|
});
|
|
244
469
|
}
|
|
245
470
|
|
|
246
|
-
module.exports = { detectBrowsers, findBrowser, detectExistingCDP, launchBrowser, testCdp };
|
|
471
|
+
module.exports = { detectBrowsers, findBrowser, detectExistingCDP, launchBrowser, testCdp, IS_WSL, IS_WIN, IS_MAC };
|
package/lib/mcp.js
CHANGED
|
@@ -4,6 +4,13 @@ const path = require('path');
|
|
|
4
4
|
const { log, success, warn } = require('./logger');
|
|
5
5
|
|
|
6
6
|
function getWslHostIp() {
|
|
7
|
+
const IS_WIN = process.platform === 'win32';
|
|
8
|
+
|
|
9
|
+
// Mac/Linux: localhost funciona directo, no necesita IP especial
|
|
10
|
+
if (!IS_WIN) return '127.0.0.1';
|
|
11
|
+
|
|
12
|
+
// Windows: detectar IP para WSL
|
|
13
|
+
|
|
7
14
|
// 1. Desde WSL: leer resolv.conf directamente
|
|
8
15
|
try {
|
|
9
16
|
if (fs.existsSync('/etc/resolv.conf')) {
|
|
@@ -14,26 +21,24 @@ function getWslHostIp() {
|
|
|
14
21
|
} catch (e) {}
|
|
15
22
|
|
|
16
23
|
// 2. Desde Windows: via wsl.exe
|
|
17
|
-
|
|
24
|
+
try {
|
|
25
|
+
const result = execSync('wsl.exe -e grep nameserver /etc/resolv.conf',
|
|
26
|
+
{ timeout: 10000, encoding: 'utf-8', stdio: 'pipe' });
|
|
27
|
+
const match = result.match(/nameserver\s+(\d+\.\d+\.\d+\.\d+)/);
|
|
28
|
+
if (match) return match[1];
|
|
29
|
+
} catch (e) {}
|
|
30
|
+
|
|
31
|
+
// 3. Via \\wsl$
|
|
32
|
+
const distros = ['Ubuntu', 'Ubuntu-22.04', 'Ubuntu-24.04', 'Debian'];
|
|
33
|
+
for (const distro of distros) {
|
|
34
|
+
const resolvPath = `\\\\wsl$\\${distro}\\etc\\resolv.conf`;
|
|
18
35
|
try {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
36
|
+
if (fs.existsSync(resolvPath)) {
|
|
37
|
+
const content = fs.readFileSync(resolvPath, 'utf-8');
|
|
38
|
+
const match = content.match(/nameserver\s+(\d+\.\d+\.\d+\.\d+)/);
|
|
39
|
+
if (match) return match[1];
|
|
40
|
+
}
|
|
23
41
|
} catch (e) {}
|
|
24
|
-
|
|
25
|
-
// 3. Via \\wsl$
|
|
26
|
-
const distros = ['Ubuntu', 'Ubuntu-22.04', 'Ubuntu-24.04', 'Debian'];
|
|
27
|
-
for (const distro of distros) {
|
|
28
|
-
const resolvPath = `\\\\wsl$\\${distro}\\etc\\resolv.conf`;
|
|
29
|
-
try {
|
|
30
|
-
if (fs.existsSync(resolvPath)) {
|
|
31
|
-
const content = fs.readFileSync(resolvPath, 'utf-8');
|
|
32
|
-
const match = content.match(/nameserver\s+(\d+\.\d+\.\d+\.\d+)/);
|
|
33
|
-
if (match) return match[1];
|
|
34
|
-
}
|
|
35
|
-
} catch (e) {}
|
|
36
|
-
}
|
|
37
42
|
}
|
|
38
43
|
|
|
39
44
|
return '127.0.0.1';
|
package/lib/network.js
CHANGED
|
@@ -1,12 +1,50 @@
|
|
|
1
1
|
const { execSync } = require('child_process');
|
|
2
2
|
const { log, success, warn } = require('./logger');
|
|
3
3
|
|
|
4
|
+
const IS_WIN = process.platform === 'win32';
|
|
5
|
+
let IS_WSL = false;
|
|
6
|
+
try { const { IS_WSL: w } = require('./browser'); IS_WSL = w; } catch (e) {
|
|
7
|
+
try {
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const v = fs.readFileSync('/proc/version', 'utf-8').toLowerCase();
|
|
10
|
+
IS_WSL = v.includes('microsoft') || v.includes('wsl');
|
|
11
|
+
} catch (e2) {}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const NEEDS_NETSH = IS_WIN; // WSL no necesita netsh, usa host IP directo
|
|
4
15
|
const FIREWALL_RULE = 'CDP All Ports (IPC)';
|
|
5
16
|
|
|
6
17
|
function setupFirewall() {
|
|
7
|
-
|
|
18
|
+
// Solo Windows nativo necesita firewall
|
|
19
|
+
// WSL: el navegador corre en Windows pero WSL accede via host IP, firewall ya debe estar abierto
|
|
20
|
+
// Mac/Linux: localhost directo
|
|
21
|
+
if (!NEEDS_NETSH) {
|
|
22
|
+
if (IS_WSL) {
|
|
23
|
+
// En WSL: ejecutar netsh via cmd.exe para crear la regla en Windows
|
|
24
|
+
try {
|
|
25
|
+
const check = execSync(
|
|
26
|
+
`cmd.exe /c netsh advfirewall firewall show rule name="${FIREWALL_RULE}"`,
|
|
27
|
+
{ timeout: 10000, encoding: 'utf-8', stdio: 'pipe' }
|
|
28
|
+
);
|
|
29
|
+
if (check.includes(FIREWALL_RULE)) return true;
|
|
30
|
+
} catch (e) {}
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
execSync(
|
|
34
|
+
`cmd.exe /c netsh advfirewall firewall add rule name="${FIREWALL_RULE}" dir=in action=allow protocol=TCP localport=1024-65535`,
|
|
35
|
+
{ timeout: 10000, stdio: 'pipe' }
|
|
36
|
+
);
|
|
37
|
+
success('Firewall: regla universal creada (via WSL)');
|
|
38
|
+
return true;
|
|
39
|
+
} catch (e) {
|
|
40
|
+
warn('Firewall: no se pudo crear regla desde WSL (ejecuta como Admin en Windows)');
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
8
46
|
|
|
9
|
-
//
|
|
47
|
+
// Windows nativo
|
|
10
48
|
try {
|
|
11
49
|
const check = execSync(
|
|
12
50
|
`netsh advfirewall firewall show rule name="${FIREWALL_RULE}"`,
|
|
@@ -15,7 +53,6 @@ function setupFirewall() {
|
|
|
15
53
|
if (check.includes(FIREWALL_RULE)) return true;
|
|
16
54
|
} catch (e) {}
|
|
17
55
|
|
|
18
|
-
// Crear regla universal
|
|
19
56
|
try {
|
|
20
57
|
execSync(
|
|
21
58
|
`netsh advfirewall firewall add rule name="${FIREWALL_RULE}" dir=in action=allow protocol=TCP localport=1024-65535`,
|
|
@@ -30,9 +67,36 @@ function setupFirewall() {
|
|
|
30
67
|
}
|
|
31
68
|
|
|
32
69
|
function setupPortproxy(port) {
|
|
33
|
-
|
|
70
|
+
// Mac/Linux nativo: no necesita portproxy
|
|
71
|
+
if (!IS_WIN && !IS_WSL) {
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// WSL: ejecutar netsh via cmd.exe
|
|
76
|
+
if (IS_WSL) {
|
|
77
|
+
try {
|
|
78
|
+
const check = execSync('cmd.exe /c netsh interface portproxy show all',
|
|
79
|
+
{ timeout: 10000, encoding: 'utf-8', stdio: 'pipe' });
|
|
80
|
+
if (check.includes(`0.0.0.0 ${port}`)) {
|
|
81
|
+
log(` Portproxy ya existe para puerto ${port}`);
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
} catch (e) {}
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
execSync(
|
|
88
|
+
`cmd.exe /c netsh interface portproxy add v4tov4 listenaddress=0.0.0.0 listenport=${port} connectaddress=127.0.0.1 connectport=${port}`,
|
|
89
|
+
{ timeout: 10000, stdio: 'pipe' }
|
|
90
|
+
);
|
|
91
|
+
success(`Portproxy: 0.0.0.0:${port} -> 127.0.0.1:${port} (via WSL)`);
|
|
92
|
+
return true;
|
|
93
|
+
} catch (e) {
|
|
94
|
+
warn(`Portproxy: fallo desde WSL (ejecuta como Admin en Windows)`);
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
34
98
|
|
|
35
|
-
//
|
|
99
|
+
// Windows nativo
|
|
36
100
|
try {
|
|
37
101
|
const check = execSync('netsh interface portproxy show all',
|
|
38
102
|
{ timeout: 10000, encoding: 'utf-8', stdio: 'pipe' });
|
|
@@ -42,7 +106,6 @@ function setupPortproxy(port) {
|
|
|
42
106
|
}
|
|
43
107
|
} catch (e) {}
|
|
44
108
|
|
|
45
|
-
// Crear portproxy
|
|
46
109
|
try {
|
|
47
110
|
execSync(
|
|
48
111
|
`netsh interface portproxy add v4tov4 listenaddress=0.0.0.0 listenport=${port} connectaddress=127.0.0.1 connectport=${port}`,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "browser-ipc-cdp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Control remoto de navegadores Chromium (Brave, Chrome, Edge) via IPC + CDP dinamico. Un comando para conectar Claude Code a tu navegador real.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"browser-ipc-cdp": "bin/cli.js"
|