browser-ipc-cdp 1.0.0 → 1.1.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/lib/browser.js CHANGED
@@ -4,43 +4,144 @@ const path = require('path');
4
4
  const http = require('http');
5
5
  const { log, success, warn } = require('./logger');
6
6
 
7
- const BROWSER_PATHS = {
8
- brave: [
9
- 'C:\\Program Files\\BraveSoftware\\Brave-Browser\\Application\\brave.exe',
10
- 'C:\\Program Files (x86)\\BraveSoftware\\Brave-Browser\\Application\\brave.exe',
11
- path.join(process.env.LOCALAPPDATA || '', 'BraveSoftware', 'Brave-Browser', 'Application', 'brave.exe'),
12
- ],
13
- chrome: [
14
- 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
15
- 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
16
- path.join(process.env.LOCALAPPDATA || '', 'Google', 'Chrome', 'Application', 'chrome.exe'),
17
- ],
18
- edge: [
19
- 'C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe',
20
- 'C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe',
21
- ],
22
- };
7
+ const IS_WIN = process.platform === 'win32';
8
+ const IS_MAC = process.platform === 'darwin';
9
+ const HOME = process.env.HOME || process.env.USERPROFILE || '';
10
+ const LOCALAPPDATA = process.env.LOCALAPPDATA || '';
11
+ const PROGRAMFILES = process.env.PROGRAMFILES || 'C:\\Program Files';
12
+ const PROGRAMFILES86 = process.env['PROGRAMFILES(X86)'] || 'C:\\Program Files (x86)';
23
13
 
24
- const BROWSER_USER_DATA = {
25
- brave: path.join(process.env.LOCALAPPDATA || '', 'BraveSoftware', 'Brave-Browser', 'User Data'),
26
- chrome: path.join(process.env.LOCALAPPDATA || '', 'Google', 'Chrome', 'User Data'),
27
- edge: path.join(process.env.LOCALAPPDATA || '', 'Microsoft', 'Edge', 'User Data'),
14
+ // Rutas dinámicas por plataforma
15
+ const BROWSER_REGISTRY = {
16
+ brave: {
17
+ win: [
18
+ path.join(PROGRAMFILES, 'BraveSoftware', 'Brave-Browser', 'Application', 'brave.exe'),
19
+ path.join(PROGRAMFILES86, 'BraveSoftware', 'Brave-Browser', 'Application', 'brave.exe'),
20
+ path.join(LOCALAPPDATA, 'BraveSoftware', 'Brave-Browser', 'Application', 'brave.exe'),
21
+ ],
22
+ mac: [
23
+ '/Applications/Brave Browser.app/Contents/MacOS/Brave Browser',
24
+ path.join(HOME, 'Applications', 'Brave Browser.app', 'Contents', 'MacOS', 'Brave Browser'),
25
+ ],
26
+ linux: [
27
+ '/usr/bin/brave-browser',
28
+ '/usr/bin/brave',
29
+ '/snap/bin/brave',
30
+ '/opt/brave.com/brave/brave-browser',
31
+ ],
32
+ userData: {
33
+ win: path.join(LOCALAPPDATA, 'BraveSoftware', 'Brave-Browser', 'User Data'),
34
+ mac: path.join(HOME, 'Library', 'Application Support', 'BraveSoftware', 'Brave-Browser'),
35
+ linux: path.join(HOME, '.config', 'BraveSoftware', 'Brave-Browser'),
36
+ },
37
+ processName: { win: 'brave.exe', mac: 'Brave Browser', linux: 'brave' },
38
+ },
39
+ chrome: {
40
+ win: [
41
+ path.join(PROGRAMFILES, 'Google', 'Chrome', 'Application', 'chrome.exe'),
42
+ path.join(PROGRAMFILES86, 'Google', 'Chrome', 'Application', 'chrome.exe'),
43
+ path.join(LOCALAPPDATA, 'Google', 'Chrome', 'Application', 'chrome.exe'),
44
+ ],
45
+ mac: [
46
+ '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
47
+ path.join(HOME, 'Applications', 'Google Chrome.app', 'Contents', 'MacOS', 'Google Chrome'),
48
+ ],
49
+ linux: [
50
+ '/usr/bin/google-chrome',
51
+ '/usr/bin/google-chrome-stable',
52
+ '/snap/bin/chromium',
53
+ '/usr/bin/chromium-browser',
54
+ '/usr/bin/chromium',
55
+ ],
56
+ userData: {
57
+ win: path.join(LOCALAPPDATA, 'Google', 'Chrome', 'User Data'),
58
+ mac: path.join(HOME, 'Library', 'Application Support', 'Google', 'Chrome'),
59
+ linux: path.join(HOME, '.config', 'google-chrome'),
60
+ },
61
+ processName: { win: 'chrome.exe', mac: 'Google Chrome', linux: 'chrome' },
62
+ },
63
+ edge: {
64
+ win: [
65
+ path.join(PROGRAMFILES86, 'Microsoft', 'Edge', 'Application', 'msedge.exe'),
66
+ path.join(PROGRAMFILES, 'Microsoft', 'Edge', 'Application', 'msedge.exe'),
67
+ ],
68
+ mac: [
69
+ '/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge',
70
+ ],
71
+ linux: [
72
+ '/usr/bin/microsoft-edge',
73
+ '/usr/bin/microsoft-edge-stable',
74
+ ],
75
+ userData: {
76
+ win: path.join(LOCALAPPDATA, 'Microsoft', 'Edge', 'User Data'),
77
+ mac: path.join(HOME, 'Library', 'Application Support', 'Microsoft Edge'),
78
+ linux: path.join(HOME, '.config', 'microsoft-edge'),
79
+ },
80
+ processName: { win: 'msedge.exe', mac: 'Microsoft Edge', linux: 'msedge' },
81
+ },
82
+ chromium: {
83
+ win: [
84
+ path.join(LOCALAPPDATA, 'Chromium', 'Application', 'chrome.exe'),
85
+ ],
86
+ mac: [
87
+ '/Applications/Chromium.app/Contents/MacOS/Chromium',
88
+ ],
89
+ linux: [
90
+ '/usr/bin/chromium',
91
+ '/usr/bin/chromium-browser',
92
+ '/snap/bin/chromium',
93
+ ],
94
+ userData: {
95
+ win: path.join(LOCALAPPDATA, 'Chromium', 'User Data'),
96
+ mac: path.join(HOME, 'Library', 'Application Support', 'Chromium'),
97
+ linux: path.join(HOME, '.config', 'chromium'),
98
+ },
99
+ processName: { win: 'chrome.exe', mac: 'Chromium', linux: 'chromium' },
100
+ },
28
101
  };
29
102
 
103
+ function getPlatform() {
104
+ if (IS_WIN) return 'win';
105
+ if (IS_MAC) return 'mac';
106
+ return 'linux';
107
+ }
108
+
109
+ function getBrowserPaths(name) {
110
+ const entry = BROWSER_REGISTRY[name];
111
+ if (!entry) return { paths: [], userData: '', processName: '' };
112
+ const plat = getPlatform();
113
+ return {
114
+ paths: entry[plat] || [],
115
+ userData: (entry.userData && entry.userData[plat]) || '',
116
+ processName: (entry.processName && entry.processName[plat]) || name,
117
+ };
118
+ }
119
+
30
120
  function detectBrowsers() {
31
121
  const found = [];
32
- for (const [name, paths] of Object.entries(BROWSER_PATHS)) {
122
+ for (const name of Object.keys(BROWSER_REGISTRY)) {
123
+ const { paths, userData } = getBrowserPaths(name);
33
124
  for (const p of paths) {
34
125
  if (fs.existsSync(p)) {
35
- found.push({
36
- name,
37
- exe: p,
38
- userData: BROWSER_USER_DATA[name] || '',
39
- });
126
+ found.push({ name, exe: p, userData });
40
127
  break;
41
128
  }
42
129
  }
43
130
  }
131
+ // Fallback: buscar en PATH
132
+ const cmds = IS_WIN
133
+ ? ['brave', 'chrome', 'msedge']
134
+ : ['brave-browser', 'brave', 'google-chrome', 'google-chrome-stable', 'chromium', 'chromium-browser', 'microsoft-edge'];
135
+ for (const cmd of cmds) {
136
+ try {
137
+ const which = IS_WIN
138
+ ? execSync(`where ${cmd} 2>nul`, { encoding: 'utf-8', timeout: 5000 }).trim().split('\n')[0]
139
+ : execSync(`which ${cmd} 2>/dev/null`, { encoding: 'utf-8', timeout: 5000 }).trim();
140
+ if (which && fs.existsSync(which) && !found.some(b => b.exe === which)) {
141
+ found.push({ name: cmd, exe: which, userData: '' });
142
+ }
143
+ } catch (e) {}
144
+ }
44
145
  return found;
45
146
  }
46
147
 
@@ -85,13 +186,18 @@ function testCdp(port, timeout = 3000) {
85
186
  }
86
187
 
87
188
  function isBrowserRunning(exe) {
88
- const exeName = path.basename(exe).toLowerCase();
189
+ const exeName = path.basename(exe);
89
190
  try {
90
- const result = execSync(
91
- `tasklist /FI "IMAGENAME eq ${exeName}" /FO CSV /NH`,
92
- { timeout: 10000, encoding: 'utf-8' }
93
- );
94
- return result.toLowerCase().includes(exeName);
191
+ if (IS_WIN) {
192
+ const result = execSync(
193
+ `tasklist /FI "IMAGENAME eq ${exeName}" /FO CSV /NH`,
194
+ { timeout: 10000, encoding: 'utf-8', stdio: 'pipe' }
195
+ );
196
+ return result.toLowerCase().includes(exeName.toLowerCase());
197
+ } else {
198
+ const result = execSync(`pgrep -f "${exeName}"`, { timeout: 5000, stdio: 'pipe' });
199
+ return result.length > 0;
200
+ }
95
201
  } catch (e) {
96
202
  return false;
97
203
  }
@@ -115,14 +221,22 @@ async function detectExistingCDP(browser) {
115
221
  }
116
222
  }
117
223
 
118
- // 2. Command line
224
+ // 2. Command line scan
119
225
  try {
120
- const exeName = path.basename(browser.exe).toLowerCase();
121
- const result = execSync(
122
- `wmic process where "name='${exeName}'" get commandline /format:list`,
123
- { timeout: 10000, encoding: 'utf-8' }
124
- );
125
- const match = result.match(/--remote-debugging-port[=\s](\d{2,5})/);
226
+ const exeName = path.basename(browser.exe);
227
+ let cmdOutput = '';
228
+ if (IS_WIN) {
229
+ cmdOutput = execSync(
230
+ `wmic process where "name='${exeName}'" get commandline /format:list`,
231
+ { timeout: 10000, encoding: 'utf-8', stdio: 'pipe' }
232
+ );
233
+ } else {
234
+ cmdOutput = execSync(
235
+ `ps aux | grep "${exeName}" | grep -v grep`,
236
+ { timeout: 10000, encoding: 'utf-8', stdio: 'pipe' }
237
+ );
238
+ }
239
+ const match = cmdOutput.match(/--remote-debugging-port[=\s](\d{2,5})/);
126
240
  if (match) {
127
241
  const port = parseInt(match[1]);
128
242
  if (port > 0) {
@@ -132,37 +246,67 @@ async function detectExistingCDP(browser) {
132
246
  }
133
247
  } catch (e) {}
134
248
 
135
- // 3. Scan ports via netstat
249
+ // 3. Scan ports
136
250
  try {
137
- const exeName = path.basename(browser.exe).toLowerCase();
138
- const taskResult = execSync(
139
- `tasklist /FI "IMAGENAME eq ${exeName}" /FO CSV /NH`,
140
- { timeout: 10000, encoding: 'utf-8' }
141
- );
251
+ const exeName = path.basename(browser.exe);
142
252
  const pids = new Set();
143
- taskResult.split('\n').forEach(line => {
144
- const parts = line.trim().replace(/"/g, '').split(',');
145
- if (parts.length >= 2) {
146
- const pid = parseInt(parts[1]);
253
+
254
+ if (IS_WIN) {
255
+ const taskResult = execSync(
256
+ `tasklist /FI "IMAGENAME eq ${exeName}" /FO CSV /NH`,
257
+ { timeout: 10000, encoding: 'utf-8', stdio: 'pipe' }
258
+ );
259
+ taskResult.split('\n').forEach(line => {
260
+ const parts = line.trim().replace(/"/g, '').split(',');
261
+ if (parts.length >= 2) {
262
+ const pid = parseInt(parts[1]);
263
+ if (!isNaN(pid)) pids.add(pid);
264
+ }
265
+ });
266
+ } else {
267
+ const pgrepResult = execSync(`pgrep -f "${exeName}"`,
268
+ { timeout: 5000, encoding: 'utf-8', stdio: 'pipe' });
269
+ pgrepResult.trim().split('\n').forEach(p => {
270
+ const pid = parseInt(p.trim());
147
271
  if (!isNaN(pid)) pids.add(pid);
148
- }
149
- });
272
+ });
273
+ }
150
274
 
151
275
  if (pids.size > 0) {
152
- const netstat = execSync('netstat -ano -p tcp', { timeout: 10000, encoding: 'utf-8' });
153
- for (const line of netstat.split('\n')) {
154
- const tokens = line.trim().split(/\s+/);
155
- if (tokens.length >= 5 && tokens[3] === 'LISTENING') {
156
- const pid = parseInt(tokens[4]);
157
- if (pids.has(pid)) {
158
- const portStr = tokens[1].split(':').pop();
159
- const port = parseInt(portStr);
160
- if (port > 1024) {
161
- const cdp = await testCdp(port);
162
- if (cdp) return port;
276
+ if (IS_WIN) {
277
+ const netstat = execSync('netstat -ano -p tcp', { timeout: 10000, encoding: 'utf-8', stdio: 'pipe' });
278
+ for (const line of netstat.split('\n')) {
279
+ const tokens = line.trim().split(/\s+/);
280
+ if (tokens.length >= 5 && tokens[3] === 'LISTENING') {
281
+ const pid = parseInt(tokens[4]);
282
+ if (pids.has(pid)) {
283
+ const portStr = tokens[1].split(':').pop();
284
+ const port = parseInt(portStr);
285
+ if (port > 1024) {
286
+ const cdp = await testCdp(port);
287
+ if (cdp) return port;
288
+ }
163
289
  }
164
290
  }
165
291
  }
292
+ } else {
293
+ // Mac/Linux: use lsof or ss
294
+ for (const pid of pids) {
295
+ try {
296
+ const lsof = execSync(`lsof -i -P -n -p ${pid} 2>/dev/null | grep LISTEN`,
297
+ { timeout: 10000, encoding: 'utf-8', stdio: 'pipe' });
298
+ for (const line of lsof.split('\n')) {
299
+ const match = line.match(/:(\d+)\s/);
300
+ if (match) {
301
+ const port = parseInt(match[1]);
302
+ if (port > 1024) {
303
+ const cdp = await testCdp(port);
304
+ if (cdp) return port;
305
+ }
306
+ }
307
+ }
308
+ } catch (e) {}
309
+ }
166
310
  }
167
311
  }
168
312
  } catch (e) {}
@@ -173,7 +317,11 @@ async function detectExistingCDP(browser) {
173
317
  function killBrowser(exe) {
174
318
  const exeName = path.basename(exe);
175
319
  try {
176
- execSync(`taskkill /F /IM ${exeName}`, { timeout: 10000, stdio: 'pipe' });
320
+ if (IS_WIN) {
321
+ execSync(`taskkill /F /IM ${exeName}`, { timeout: 10000, stdio: 'pipe' });
322
+ } else {
323
+ execSync(`pkill -f "${exeName}"`, { timeout: 10000, stdio: 'pipe' });
324
+ }
177
325
  } catch (e) {}
178
326
  }
179
327
 
@@ -198,10 +346,14 @@ function launchBrowser(browser, { port = 0, clean = false } = {}) {
198
346
  ];
199
347
  if (clean) args.push(`--user-data-dir=${userData}`);
200
348
 
201
- const child = spawn(browser.exe, args, {
202
- detached: true,
203
- stdio: 'ignore',
204
- });
349
+ const spawnOpts = { detached: true, stdio: 'ignore' };
350
+ // En Mac, si el exe es un .app, necesita 'open' como wrapper
351
+ let cmd = browser.exe;
352
+ let spawnArgs = args;
353
+ if (IS_MAC && browser.exe.includes('.app/')) {
354
+ // Ejecutar directo el binario dentro del .app
355
+ }
356
+ const child = spawn(cmd, spawnArgs, spawnOpts);
205
357
  child.unref();
206
358
 
207
359
  log(` PID: ${child.pid}`);
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
- if (process.platform === 'win32') {
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
- const result = execSync('wsl.exe -e grep nameserver /etc/resolv.conf',
20
- { timeout: 10000, encoding: 'utf-8', stdio: 'pipe' });
21
- const match = result.match(/nameserver\s+(\d+\.\d+\.\d+\.\d+)/);
22
- if (match) return match[1];
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,10 +1,16 @@
1
1
  const { execSync } = require('child_process');
2
2
  const { log, success, warn } = require('./logger');
3
3
 
4
+ const IS_WIN = process.platform === 'win32';
4
5
  const FIREWALL_RULE = 'CDP All Ports (IPC)';
5
6
 
6
7
  function setupFirewall() {
7
- if (process.platform !== 'win32') return false;
8
+ // Solo necesario en Windows (WSL requiere reglas de firewall)
9
+ // Mac/Linux no necesitan firewall para localhost
10
+ if (!IS_WIN) {
11
+ log(' Firewall: no necesario en esta plataforma');
12
+ return true;
13
+ }
8
14
 
9
15
  // Verificar si ya existe
10
16
  try {
@@ -30,7 +36,12 @@ function setupFirewall() {
30
36
  }
31
37
 
32
38
  function setupPortproxy(port) {
33
- if (process.platform !== 'win32') return false;
39
+ // Solo necesario en Windows (WSL no alcanza 127.0.0.1 de Windows)
40
+ // Mac/Linux: localhost funciona directo
41
+ if (!IS_WIN) {
42
+ log(' Portproxy: no necesario en esta plataforma');
43
+ return true;
44
+ }
34
45
 
35
46
  // Verificar si ya existe
36
47
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "browser-ipc-cdp",
3
- "version": "1.0.0",
3
+ "version": "1.1.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"