@romanmatena/browsermonitor 2.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/LICENSE +21 -0
- package/README.md +558 -0
- package/package.json +53 -0
- package/src/agents.llm/browser-monitor-section.md +18 -0
- package/src/cli.mjs +202 -0
- package/src/http-server.mjs +536 -0
- package/src/init.mjs +162 -0
- package/src/intro.mjs +36 -0
- package/src/logging/LogBuffer.mjs +178 -0
- package/src/logging/constants.mjs +19 -0
- package/src/logging/dump.mjs +207 -0
- package/src/logging/index.mjs +13 -0
- package/src/logging/timestamps.mjs +13 -0
- package/src/monitor/README.md +10 -0
- package/src/monitor/index.mjs +18 -0
- package/src/monitor/interactive-mode.mjs +275 -0
- package/src/monitor/join-mode.mjs +654 -0
- package/src/monitor/open-mode.mjs +889 -0
- package/src/monitor/page-monitoring.mjs +199 -0
- package/src/monitor/tab-selection.mjs +53 -0
- package/src/monitor.mjs +39 -0
- package/src/os/README.md +4 -0
- package/src/os/wsl/chrome.mjs +503 -0
- package/src/os/wsl/detect.mjs +68 -0
- package/src/os/wsl/diagnostics.mjs +729 -0
- package/src/os/wsl/index.mjs +45 -0
- package/src/os/wsl/port-proxy.mjs +190 -0
- package/src/settings.mjs +101 -0
- package/src/templates/api-help.mjs +212 -0
- package/src/templates/cli-commands.mjs +51 -0
- package/src/templates/interactive-keys.mjs +33 -0
- package/src/templates/ready-help.mjs +33 -0
- package/src/templates/section-heading.mjs +141 -0
- package/src/templates/table-helper.mjs +73 -0
- package/src/templates/wait-for-chrome.mjs +19 -0
- package/src/utils/ask.mjs +49 -0
- package/src/utils/chrome-profile-path.mjs +37 -0
- package/src/utils/colors.mjs +49 -0
- package/src/utils/env.mjs +30 -0
- package/src/utils/profile-id.mjs +23 -0
- package/src/utils/status-line.mjs +47 -0
|
@@ -0,0 +1,729 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WSL2 to Windows connection diagnostics.
|
|
3
|
+
*
|
|
4
|
+
* Comprehensive diagnostics for troubleshooting Chrome DevTools connectivity
|
|
5
|
+
* from WSL2 to Windows. Checks all layers: Chrome process, port listening,
|
|
6
|
+
* firewall rules, port proxy, and actual network connectivity.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { execSync, spawn } from 'child_process';
|
|
10
|
+
import { C } from '../../utils/colors.mjs';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Run PowerShell command and return result.
|
|
14
|
+
* @param {string} cmd - PowerShell command
|
|
15
|
+
* @param {number} timeout - Timeout in ms
|
|
16
|
+
* @returns {string|null} Command output or null on error
|
|
17
|
+
*/
|
|
18
|
+
function runPowerShell(cmd, timeout = 5000) {
|
|
19
|
+
try {
|
|
20
|
+
return execSync(`powershell.exe -NoProfile -Command "${cmd.replace(/"/g, '\\"')}"`, { encoding: 'utf8', timeout }).trim();
|
|
21
|
+
} catch (e) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Print prominent notice that UAC will appear on Windows (so user knows the script is not stuck).
|
|
28
|
+
*/
|
|
29
|
+
function printUacNotice() {
|
|
30
|
+
console.log('');
|
|
31
|
+
console.log(`${C.bgMagenta}${C.bold}${C.white} ${C.reset}`);
|
|
32
|
+
console.log(`${C.bgMagenta}${C.bold}${C.white} ON WINDOWS: A UAC dialog will appear – click "Yes" to allow the fix. ${C.reset}`);
|
|
33
|
+
console.log(`${C.bgMagenta}${C.bold}${C.white} This window will wait up to 60 s. If you cancel UAC, manual steps will be shown. ${C.reset}`);
|
|
34
|
+
console.log(`${C.bgMagenta}${C.bold}${C.white} ${C.reset}`);
|
|
35
|
+
console.log('');
|
|
36
|
+
console.log(` ${C.magenta}Waiting for UAC approval on Windows...${C.reset}`);
|
|
37
|
+
console.log(` ${C.dim}(Still waiting? Look for the UAC window on Windows. Timeout 60 s.)${C.reset}`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Run PowerShell command with elevation (UAC prompt on Windows).
|
|
42
|
+
* From WSL we have no admin rights; this opens an elevated PowerShell window so the user can approve.
|
|
43
|
+
* Prints "Still waiting..." every 15 s so the terminal doesn't look stuck.
|
|
44
|
+
* @param {string} innerCmd - PowerShell command to run as admin (e.g. Set-NetFirewallRule ...)
|
|
45
|
+
* @param {number} timeout - Timeout in ms (elevated window can wait for user)
|
|
46
|
+
* @param {boolean} silent - If true, do not print UAC notice (caller prints it)
|
|
47
|
+
* @returns {boolean} true if the elevated process exited with code 0
|
|
48
|
+
*/
|
|
49
|
+
function runPowerShellElevated(innerCmd, timeout = 60000, silent = false) {
|
|
50
|
+
if (!silent) {
|
|
51
|
+
printUacNotice();
|
|
52
|
+
}
|
|
53
|
+
const innerEscaped = innerCmd.replace(/'/g, "''");
|
|
54
|
+
const outerCmd = `Start-Process powershell -Verb RunAs -ArgumentList '-NoProfile','-Command','${innerEscaped}' -Wait -PassThru | ForEach-Object { exit $_.ExitCode }`;
|
|
55
|
+
const stillWaitingInterval = 15000;
|
|
56
|
+
|
|
57
|
+
return new Promise((resolve) => {
|
|
58
|
+
const child = spawn('powershell.exe', ['-NoProfile', '-Command', outerCmd], {
|
|
59
|
+
windowsHide: true,
|
|
60
|
+
});
|
|
61
|
+
let resolved = false;
|
|
62
|
+
const timeoutId = setTimeout(() => {
|
|
63
|
+
if (resolved) return;
|
|
64
|
+
resolved = true;
|
|
65
|
+
clearInterval(interval);
|
|
66
|
+
try { child.kill('SIGTERM'); } catch (_) {}
|
|
67
|
+
console.log(` ${C.yellow}Timeout (60 s) – use manual steps below.${C.reset}`);
|
|
68
|
+
resolve(false);
|
|
69
|
+
}, timeout);
|
|
70
|
+
const interval = setInterval(() => {
|
|
71
|
+
if (resolved) return;
|
|
72
|
+
console.log(` ${C.dim}Still waiting for UAC / elevated command...${C.reset}`);
|
|
73
|
+
}, stillWaitingInterval);
|
|
74
|
+
child.on('close', (code) => {
|
|
75
|
+
if (resolved) return;
|
|
76
|
+
resolved = true;
|
|
77
|
+
clearTimeout(timeoutId);
|
|
78
|
+
clearInterval(interval);
|
|
79
|
+
resolve(code === 0);
|
|
80
|
+
});
|
|
81
|
+
child.on('error', () => {
|
|
82
|
+
if (resolved) return;
|
|
83
|
+
resolved = true;
|
|
84
|
+
clearTimeout(timeoutId);
|
|
85
|
+
clearInterval(interval);
|
|
86
|
+
resolve(false);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Run command and return result.
|
|
93
|
+
* @param {string} cmd - Shell command
|
|
94
|
+
* @param {number} timeout - Timeout in ms
|
|
95
|
+
* @returns {string|null} Command output or null on error
|
|
96
|
+
*/
|
|
97
|
+
function runCmd(cmd, timeout = 10000) {
|
|
98
|
+
try {
|
|
99
|
+
return execSync(cmd, { encoding: 'utf8', timeout }).trim();
|
|
100
|
+
} catch (e) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Comprehensive WSL2 to Windows connection diagnostics.
|
|
107
|
+
* Checks all layers: Chrome process, port listening, firewall rules, port proxy, WSL reachability.
|
|
108
|
+
*
|
|
109
|
+
* @param {number} port - Chrome debugging port
|
|
110
|
+
* @param {string} windowsHostIP - Windows host IP from WSL perspective
|
|
111
|
+
* @returns {Promise<{issues: Array<{level: string, message: string, fix?: string}>, canConnect: boolean, hasPortProxyConflict?: boolean, actualPort?: number}>}
|
|
112
|
+
*/
|
|
113
|
+
export async function runWslDiagnostics(port, windowsHostIP) {
|
|
114
|
+
const issues = [];
|
|
115
|
+
let canConnect = false;
|
|
116
|
+
|
|
117
|
+
console.log('');
|
|
118
|
+
console.log(`${C.bold}${C.cyan}╔═══════════════════════════════════════════════════════════════════════════╗${C.reset}`);
|
|
119
|
+
console.log(`${C.bold}${C.cyan}║${C.reset} ${C.bold}WSL2 → Windows Connection Diagnostics${C.reset} ${C.bold}${C.cyan}║${C.reset}`);
|
|
120
|
+
console.log(`${C.bold}${C.cyan}╚═══════════════════════════════════════════════════════════════════════════╝${C.reset}`);
|
|
121
|
+
console.log('');
|
|
122
|
+
|
|
123
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
124
|
+
// Step 1: Enumerate ALL Chrome debug instances
|
|
125
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
126
|
+
console.log(`${C.bold}[1/7]${C.reset} Scanning Chrome instances on Windows...`);
|
|
127
|
+
|
|
128
|
+
const chromePid = runPowerShell('Get-Process chrome -ErrorAction SilentlyContinue | Select-Object -First 1 | ForEach-Object { \\$_.Id }');
|
|
129
|
+
|
|
130
|
+
if (!chromePid) {
|
|
131
|
+
console.log(` ${C.red}✗${C.reset} Chrome is NOT running on Windows`);
|
|
132
|
+
issues.push({
|
|
133
|
+
level: 'error',
|
|
134
|
+
message: 'Chrome is not running on Windows',
|
|
135
|
+
fix: 'Start Chrome with remote debugging:\n' +
|
|
136
|
+
` chrome.exe --remote-debugging-port=${port}\n` +
|
|
137
|
+
` Note: Port proxy is required for WSL access (Chrome M113+ binds to 127.0.0.1 only)`
|
|
138
|
+
});
|
|
139
|
+
return { issues, canConnect: false };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Find all Chrome instances with debug ports (deduplicated by port)
|
|
143
|
+
const wmicAll = runCmd('wmic.exe process where "name=\'chrome.exe\'" get commandline /format:list 2>nul');
|
|
144
|
+
const debugInstances = [];
|
|
145
|
+
const seenPorts = new Set();
|
|
146
|
+
|
|
147
|
+
if (wmicAll) {
|
|
148
|
+
const lines = wmicAll.split('\n').filter(l => l.includes('--remote-debugging-port'));
|
|
149
|
+
for (const line of lines) {
|
|
150
|
+
const portMatch = line.match(/--remote-debugging-port=(\d+)/);
|
|
151
|
+
const addressMatch = line.match(/--remote-debugging-address=([^\s'"]+)/);
|
|
152
|
+
const userDataMatch = line.match(/--user-data-dir=(?:"([^"]+)"|'([^']+)'|([^\s]+))/i);
|
|
153
|
+
if (portMatch) {
|
|
154
|
+
const portNum = parseInt(portMatch[1], 10);
|
|
155
|
+
if (!seenPorts.has(portNum)) {
|
|
156
|
+
seenPorts.add(portNum);
|
|
157
|
+
const userDataDir = userDataMatch ? (userDataMatch[1] || userDataMatch[2] || userDataMatch[3]) : 'default';
|
|
158
|
+
debugInstances.push({
|
|
159
|
+
port: portNum,
|
|
160
|
+
bindAddress: addressMatch ? addressMatch[1] : '127.0.0.1',
|
|
161
|
+
userDataDir,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
console.log(` ${C.green}✓${C.reset} Chrome is running`);
|
|
169
|
+
|
|
170
|
+
if (debugInstances.length === 0) {
|
|
171
|
+
console.log(` ${C.red}✗${C.reset} No Chrome instances with --remote-debugging-port found`);
|
|
172
|
+
issues.push({
|
|
173
|
+
level: 'error',
|
|
174
|
+
message: 'Chrome is running but without remote debugging enabled',
|
|
175
|
+
fix: `${C.bold}Close ALL Chrome windows${C.reset} and let browsermonitor start a fresh instance.`
|
|
176
|
+
});
|
|
177
|
+
return { issues, canConnect: false };
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
console.log(` ${C.cyan}Found ${debugInstances.length} Chrome instance(s) with debug port:${C.reset}`);
|
|
181
|
+
for (const inst of debugInstances) {
|
|
182
|
+
const bindOk = inst.bindAddress === '0.0.0.0';
|
|
183
|
+
const bindIcon = bindOk ? C.green + '✓' : C.yellow + '!';
|
|
184
|
+
const profileShort = inst.userDataDir.length > 50 ? '...' + inst.userDataDir.slice(-47) : inst.userDataDir;
|
|
185
|
+
console.log(` ${bindIcon}${C.reset} Port ${C.brightYellow}${inst.port}${C.reset} → ${bindOk ? C.green : C.yellow}${inst.bindAddress}${C.reset}`);
|
|
186
|
+
console.log(` ${C.dim}Profile: ${profileShort}${C.reset}`);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
190
|
+
// Step 2: Check actual network bindings
|
|
191
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
192
|
+
console.log('');
|
|
193
|
+
console.log(`${C.bold}[2/7]${C.reset} Checking actual network bindings...`);
|
|
194
|
+
|
|
195
|
+
let portProxyConflict = false;
|
|
196
|
+
|
|
197
|
+
for (const inst of debugInstances) {
|
|
198
|
+
const netstatOutput = runCmd(`netstat.exe -ano 2>/dev/null | grep -E ":${inst.port}.*LISTEN"`);
|
|
199
|
+
const listeners = [];
|
|
200
|
+
|
|
201
|
+
if (netstatOutput) {
|
|
202
|
+
const lines = netstatOutput.split('\n').filter(l => l.includes('LISTEN'));
|
|
203
|
+
for (const line of lines) {
|
|
204
|
+
const parts = line.trim().split(/\s+/);
|
|
205
|
+
if (parts.length >= 5) {
|
|
206
|
+
const localAddr = parts[1];
|
|
207
|
+
const pid = parts[4];
|
|
208
|
+
const addrOnly = localAddr.replace(`:${inst.port}`, '').replace('[', '').replace(']', '');
|
|
209
|
+
listeners.push({ address: addrOnly, pid, raw: localAddr });
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const ipv4All = listeners.find(l => l.address === '0.0.0.0');
|
|
215
|
+
const ipv6All = listeners.find(l => l.address === '::');
|
|
216
|
+
const ipv4Local = listeners.find(l => l.address === '127.0.0.1');
|
|
217
|
+
const ipv6Local = listeners.find(l => l.address === '::1');
|
|
218
|
+
|
|
219
|
+
// Detect port proxy conflict
|
|
220
|
+
if (inst.bindAddress === '0.0.0.0' && ipv4All && ipv6Local && !ipv4Local) {
|
|
221
|
+
const ipv4Process = runCmd(`wmic.exe process where "processid=${ipv4All.pid}" get name 2>nul`);
|
|
222
|
+
if (ipv4Process && ipv4Process.toLowerCase().includes('svchost')) {
|
|
223
|
+
portProxyConflict = true;
|
|
224
|
+
console.log(` ${C.red}✗${C.reset} ${C.bold}PORT PROXY CONFLICT DETECTED!${C.reset}`);
|
|
225
|
+
console.log(` ${C.dim}Port proxy (svchost PID ${ipv4All.pid}) grabbed 0.0.0.0:${inst.port}${C.reset}`);
|
|
226
|
+
console.log(` ${C.dim}Chrome fell back to [::1]:${inst.port} (IPv6 localhost only)${C.reset}`);
|
|
227
|
+
console.log(` ${C.dim}Port proxy forwards to 127.0.0.1:${inst.port} but Chrome is on [::1]!${C.reset}`);
|
|
228
|
+
inst.accessible = false;
|
|
229
|
+
inst.portProxyConflict = true;
|
|
230
|
+
issues.push({
|
|
231
|
+
level: 'error',
|
|
232
|
+
message: 'Port proxy conflict: Chrome on IPv6 [::1], port proxy expects IPv4 127.0.0.1',
|
|
233
|
+
fix: `Remove port proxy, then restart browsermonitor Chrome ONLY:\n` +
|
|
234
|
+
` ${C.cyan}netsh interface portproxy delete v4tov4 listenport=${inst.port} listenaddress=0.0.0.0${C.reset}\n` +
|
|
235
|
+
` ${C.cyan}Get-WmiObject Win32_Process -Filter "name='chrome.exe'" | Where-Object { $_.CommandLine -match 'browsermonitor' } | ForEach-Object { Stop-Process -Id $_.ProcessId -Force }${C.reset}\n` +
|
|
236
|
+
` Then run browsermonitor again.`
|
|
237
|
+
});
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const listenOnAll = ipv4All || ipv6All;
|
|
243
|
+
const listenLocal = ipv4Local || ipv6Local;
|
|
244
|
+
|
|
245
|
+
if (listenOnAll && !portProxyConflict) {
|
|
246
|
+
console.log(` ${C.green}✓${C.reset} Port ${inst.port} listening on ${C.green}0.0.0.0${C.reset} (accessible from WSL)`);
|
|
247
|
+
inst.accessible = true;
|
|
248
|
+
} else if (listenLocal) {
|
|
249
|
+
const addr = ipv4Local ? '127.0.0.1' : '[::1]';
|
|
250
|
+
console.log(` ${C.yellow}!${C.reset} Port ${inst.port} listening on ${C.yellow}${addr} only${C.reset} (NOT accessible from WSL)`);
|
|
251
|
+
inst.accessible = false;
|
|
252
|
+
issues.push({
|
|
253
|
+
level: 'warn',
|
|
254
|
+
message: `Chrome on port ${inst.port} is bound to localhost only (expected behavior)`,
|
|
255
|
+
fix: `Port proxy is required for WSL access:\n` +
|
|
256
|
+
` netsh interface portproxy add v4tov4 listenport=${inst.port} listenaddress=0.0.0.0 connectport=${inst.port} connectaddress=127.0.0.1`
|
|
257
|
+
});
|
|
258
|
+
} else if (listeners.length > 0) {
|
|
259
|
+
console.log(` ${C.dim}○${C.reset} Port ${inst.port} - addresses: ${listeners.map(l => l.address).join(', ')}`);
|
|
260
|
+
inst.accessible = false;
|
|
261
|
+
} else {
|
|
262
|
+
console.log(` ${C.yellow}?${C.reset} Port ${inst.port} - could not determine bind address`);
|
|
263
|
+
inst.accessible = false;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Find the best instance to use
|
|
268
|
+
const accessibleInstances = debugInstances.filter(i => i.accessible);
|
|
269
|
+
const matchingPort = debugInstances.find(i => i.port === port);
|
|
270
|
+
const targetInstance = accessibleInstances.length > 0 ? accessibleInstances[0] :
|
|
271
|
+
matchingPort ? matchingPort : debugInstances[0];
|
|
272
|
+
|
|
273
|
+
let actualPort = targetInstance.port;
|
|
274
|
+
|
|
275
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
276
|
+
// Step 3: Port configuration analysis
|
|
277
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
278
|
+
console.log('');
|
|
279
|
+
console.log(`${C.bold}[3/7]${C.reset} Port configuration analysis...`);
|
|
280
|
+
|
|
281
|
+
if (actualPort !== port) {
|
|
282
|
+
console.log(` ${C.red}!${C.reset} ${C.bold}PORT MISMATCH:${C.reset} We requested port ${port}, but Chrome uses ${actualPort}`);
|
|
283
|
+
console.log(` ${C.yellow} → This means Chrome joined an EXISTING instance (singleton behavior)${C.reset}`);
|
|
284
|
+
issues.push({
|
|
285
|
+
level: 'error',
|
|
286
|
+
message: `Chrome singleton issue: requested port ${port}, but existing Chrome uses ${actualPort}`,
|
|
287
|
+
fix: `${C.bold}Close ALL Chrome windows${C.reset} and run browsermonitor again.\n` +
|
|
288
|
+
` Or use connect mode: browsermonitor --join=${actualPort}`
|
|
289
|
+
});
|
|
290
|
+
} else {
|
|
291
|
+
console.log(` ${C.green}✓${C.reset} Port matches: requested ${port}, Chrome uses ${actualPort}`);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (targetInstance.bindAddress === '0.0.0.0') {
|
|
295
|
+
console.log(` ${C.green}✓${C.reset} Chrome accessible via 0.0.0.0 (port proxy active or legacy Chrome)`);
|
|
296
|
+
} else {
|
|
297
|
+
console.log(` ${C.cyan}ℹ${C.reset} Chrome binds to 127.0.0.1 only (expected - Chrome M113+ security)`);
|
|
298
|
+
console.log(` ${C.cyan} → Port proxy is required for WSL access${C.reset}`);
|
|
299
|
+
issues.push({
|
|
300
|
+
level: 'info',
|
|
301
|
+
message: 'Chrome binds to 127.0.0.1 (normal behavior since Chrome M113)',
|
|
302
|
+
fix: `Port proxy is required for WSL access:\n` +
|
|
303
|
+
` netsh interface portproxy add v4tov4 listenport=${actualPort} listenaddress=0.0.0.0 connectport=${actualPort} connectaddress=127.0.0.1`
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const isListeningOnAll = targetInstance.accessible;
|
|
308
|
+
const isListeningOnLocalhost = !targetInstance.accessible && debugInstances.length > 0;
|
|
309
|
+
|
|
310
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
311
|
+
// Step 4: Check Windows Firewall rules
|
|
312
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
313
|
+
console.log('');
|
|
314
|
+
console.log(`${C.bold}[4/7]${C.reset} Checking Windows Firewall rules...`);
|
|
315
|
+
|
|
316
|
+
const firewallStatus = runPowerShell('(Get-NetFirewallProfile -Profile Domain,Public,Private | Where-Object {\\$_.Enabled -eq \\$true}).Count');
|
|
317
|
+
const enabledProfiles = parseInt(firewallStatus, 10) || 0;
|
|
318
|
+
let firewallRuleOk = false;
|
|
319
|
+
let existingRuleName = null;
|
|
320
|
+
|
|
321
|
+
if (enabledProfiles === 0) {
|
|
322
|
+
console.log(` ${C.dim}○${C.reset} Windows Firewall is disabled (no rule needed)`);
|
|
323
|
+
firewallRuleOk = true;
|
|
324
|
+
} else {
|
|
325
|
+
const firewallRulesJson = runPowerShell(`
|
|
326
|
+
\\$rules = Get-NetFirewallRule -Direction Inbound -Action Allow -ErrorAction SilentlyContinue | Where-Object { \\$_.Enabled -eq 'True' }
|
|
327
|
+
\\$matching = @()
|
|
328
|
+
foreach (\\$rule in \\$rules) {
|
|
329
|
+
\\$port = \\$rule | Get-NetFirewallPortFilter -ErrorAction SilentlyContinue
|
|
330
|
+
if (\\$port.LocalPort -match '${actualPort}' -or \\$port.LocalPort -match '9222-9299' -or \\$port.LocalPort -eq 'Any') {
|
|
331
|
+
\\$addr = \\$rule | Get-NetFirewallAddressFilter -ErrorAction SilentlyContinue
|
|
332
|
+
\\$matching += @{
|
|
333
|
+
Name = \\$rule.DisplayName
|
|
334
|
+
LocalPort = \\$port.LocalPort
|
|
335
|
+
RemoteAddress = \\$addr.RemoteAddress
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
\\$matching | ConvertTo-Json
|
|
340
|
+
`);
|
|
341
|
+
|
|
342
|
+
let existingRules = [];
|
|
343
|
+
try {
|
|
344
|
+
const parsed = JSON.parse(firewallRulesJson || '[]');
|
|
345
|
+
existingRules = Array.isArray(parsed) ? parsed : (parsed ? [parsed] : []);
|
|
346
|
+
} catch { }
|
|
347
|
+
|
|
348
|
+
if (existingRules.length > 0) {
|
|
349
|
+
const rule = existingRules[0];
|
|
350
|
+
existingRuleName = rule.Name;
|
|
351
|
+
const remoteAddr = String(rule.RemoteAddress || 'Any').toLowerCase();
|
|
352
|
+
const allowsAny = remoteAddr === 'any' || remoteAddr === '*';
|
|
353
|
+
const allowsWsl = remoteAddr.includes('172.') || remoteAddr.includes('localsubnet');
|
|
354
|
+
|
|
355
|
+
if (allowsAny) {
|
|
356
|
+
console.log(` ${C.green}✓${C.reset} Firewall rule "${rule.Name}" allows any remote address`);
|
|
357
|
+
firewallRuleOk = true;
|
|
358
|
+
} else if (allowsWsl) {
|
|
359
|
+
console.log(` ${C.green}✓${C.reset} Firewall rule "${rule.Name}" allows WSL subnet`);
|
|
360
|
+
console.log(` ${C.dim} RemoteAddress: ${rule.RemoteAddress}${C.reset}`);
|
|
361
|
+
firewallRuleOk = true;
|
|
362
|
+
} else {
|
|
363
|
+
console.log(` ${C.yellow}!${C.reset} Firewall rule "${rule.Name}" exists but may not allow WSL`);
|
|
364
|
+
console.log(` ${C.dim} RemoteAddress: ${rule.RemoteAddress}${C.reset}`);
|
|
365
|
+
console.log(` ${C.dim} WSL2 uses 172.x.x.x range which may not be covered${C.reset}`);
|
|
366
|
+
issues.push({
|
|
367
|
+
level: 'warn',
|
|
368
|
+
message: `Firewall rule exists but RemoteAddress (${rule.RemoteAddress}) may not include WSL subnet`,
|
|
369
|
+
fix: 'Update the rule to include WSL subnet:\n' +
|
|
370
|
+
` ${C.cyan}Set-NetFirewallRule -DisplayName "${rule.Name}" -RemoteAddress LocalSubnet,172.16.0.0/12${C.reset}\n` +
|
|
371
|
+
' Or create a new rule (see below)'
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
} else {
|
|
375
|
+
console.log(` ${C.red}✗${C.reset} No firewall rule found for port ${actualPort}`);
|
|
376
|
+
issues.push({
|
|
377
|
+
level: 'error',
|
|
378
|
+
message: 'Windows Firewall is blocking incoming connections',
|
|
379
|
+
fix: 'Run in PowerShell (Admin):\n' +
|
|
380
|
+
` ${C.cyan}New-NetFirewallRule -DisplayName "Chrome Debug (browsermonitor)" -Direction Inbound -LocalPort 9222-9299 -Protocol TCP -Action Allow -RemoteAddress LocalSubnet,172.16.0.0/12${C.reset}`
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
386
|
+
// Step 5: Check port proxy configuration
|
|
387
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
388
|
+
console.log('');
|
|
389
|
+
console.log(`${C.bold}[5/7]${C.reset} Checking port proxy configuration...`);
|
|
390
|
+
|
|
391
|
+
const portProxy = runPowerShell('netsh interface portproxy show v4tov4');
|
|
392
|
+
const hasPortProxy = portProxy && portProxy.includes(String(actualPort));
|
|
393
|
+
|
|
394
|
+
if (isListeningOnAll) {
|
|
395
|
+
console.log(` ${C.dim}○${C.reset} Port proxy not needed (Chrome bound to 0.0.0.0)`);
|
|
396
|
+
} else if (hasPortProxy) {
|
|
397
|
+
console.log(` ${C.green}✓${C.reset} Port proxy is configured for port ${actualPort}`);
|
|
398
|
+
const proxyLines = portProxy.split('\n').filter(l => l.includes(String(actualPort)));
|
|
399
|
+
for (const line of proxyLines) {
|
|
400
|
+
console.log(` ${C.dim} ${line.trim()}${C.reset}`);
|
|
401
|
+
}
|
|
402
|
+
} else if (isListeningOnLocalhost) {
|
|
403
|
+
console.log(` ${C.yellow}!${C.reset} Port proxy NOT configured (needed for localhost-bound Chrome)`);
|
|
404
|
+
issues.push({
|
|
405
|
+
level: 'warn',
|
|
406
|
+
message: 'Port proxy needed for localhost-bound Chrome',
|
|
407
|
+
fix: 'Run in PowerShell (Admin):\n' +
|
|
408
|
+
` ${C.cyan}netsh interface portproxy add v4tov4 listenport=${actualPort} listenaddress=0.0.0.0 connectport=${actualPort} connectaddress=127.0.0.1${C.reset}\n` +
|
|
409
|
+
' To remove later:\n' +
|
|
410
|
+
` ${C.dim}netsh interface portproxy delete v4tov4 listenport=${actualPort} listenaddress=0.0.0.0${C.reset}`
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
415
|
+
// Step 6: Show existing port proxy rules for debug port range
|
|
416
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
417
|
+
console.log('');
|
|
418
|
+
console.log(`${C.bold}[6/7]${C.reset} Existing port proxy rules (9222-9299 range)...`);
|
|
419
|
+
|
|
420
|
+
if (portProxy) {
|
|
421
|
+
const debugProxyLines = portProxy.split('\n').filter(l => {
|
|
422
|
+
const portMatch = l.match(/(\d{4,5})/);
|
|
423
|
+
if (portMatch) {
|
|
424
|
+
const p = parseInt(portMatch[1], 10);
|
|
425
|
+
return p >= 9222 && p <= 9299;
|
|
426
|
+
}
|
|
427
|
+
return false;
|
|
428
|
+
});
|
|
429
|
+
if (debugProxyLines.length > 0) {
|
|
430
|
+
for (const line of debugProxyLines) {
|
|
431
|
+
console.log(` ${C.cyan}→${C.reset} ${line.trim()}`);
|
|
432
|
+
}
|
|
433
|
+
} else {
|
|
434
|
+
console.log(` ${C.dim}○${C.reset} No port proxy rules in debug port range`);
|
|
435
|
+
}
|
|
436
|
+
} else {
|
|
437
|
+
console.log(` ${C.dim}○${C.reset} Could not query port proxy configuration`);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
441
|
+
// Step 7: Test actual connectivity from WSL
|
|
442
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
443
|
+
console.log('');
|
|
444
|
+
console.log(`${C.bold}[7/7]${C.reset} Testing connectivity from WSL to ${windowsHostIP}:${actualPort}...`);
|
|
445
|
+
|
|
446
|
+
try {
|
|
447
|
+
const response = await fetch(`http://${windowsHostIP}:${actualPort}/json/version`, {
|
|
448
|
+
signal: AbortSignal.timeout(3000)
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
if (response.ok) {
|
|
452
|
+
const data = await response.json();
|
|
453
|
+
console.log(` ${C.green}✓${C.reset} Connection successful!`);
|
|
454
|
+
console.log(` ${C.dim} Browser: ${data.Browser || 'Chrome'}${C.reset}`);
|
|
455
|
+
canConnect = true;
|
|
456
|
+
} else {
|
|
457
|
+
console.log(` ${C.red}✗${C.reset} HTTP ${response.status} - unexpected response`);
|
|
458
|
+
}
|
|
459
|
+
} catch (fetchErr) {
|
|
460
|
+
const errMsg = fetchErr.message || 'unknown';
|
|
461
|
+
|
|
462
|
+
if (errMsg.includes('ECONNREFUSED')) {
|
|
463
|
+
console.log(` ${C.red}✗${C.reset} Connection refused`);
|
|
464
|
+
const proxyIssue = {
|
|
465
|
+
level: 'error',
|
|
466
|
+
message: 'Connection refused from WSL',
|
|
467
|
+
fix: 'Chrome is not accepting connections on this IP. Port proxy may be missing.'
|
|
468
|
+
};
|
|
469
|
+
issues.push(proxyIssue);
|
|
470
|
+
|
|
471
|
+
if (!hasPortProxy && isListeningOnLocalhost) {
|
|
472
|
+
console.log('');
|
|
473
|
+
console.log(`${C.bold}[Auto-fix]${C.reset} Adding port proxy...`);
|
|
474
|
+
const proxyCmd = `netsh interface portproxy add v4tov4 listenport=${actualPort} listenaddress=0.0.0.0 connectport=${actualPort} connectaddress=127.0.0.1`;
|
|
475
|
+
let proxyOk = runPowerShell(proxyCmd, 5000) !== null;
|
|
476
|
+
if (!proxyOk) {
|
|
477
|
+
proxyOk = await runPowerShellElevated(proxyCmd, 60000);
|
|
478
|
+
}
|
|
479
|
+
if (proxyOk) {
|
|
480
|
+
console.log(` ${C.green}✓${C.reset} Port proxy configured for ${actualPort}`);
|
|
481
|
+
try {
|
|
482
|
+
const retryResponse = await fetch(`http://${windowsHostIP}:${actualPort}/json/version`, { signal: AbortSignal.timeout(5000) });
|
|
483
|
+
if (retryResponse.ok) {
|
|
484
|
+
canConnect = true;
|
|
485
|
+
console.log(` ${C.green}✓${C.reset} Connection successful after port proxy.`);
|
|
486
|
+
issues.pop();
|
|
487
|
+
}
|
|
488
|
+
} catch (_) {
|
|
489
|
+
console.log(` ${C.dim}Re-run the monitor.${C.reset}`);
|
|
490
|
+
}
|
|
491
|
+
} else {
|
|
492
|
+
console.log(` ${C.yellow}!${C.reset} Could not add port proxy (run PowerShell as Admin).`);
|
|
493
|
+
proxyIssue.fix += '\n Run in PowerShell (Admin):\n' +
|
|
494
|
+
` netsh interface portproxy add v4tov4 listenport=${actualPort} listenaddress=0.0.0.0 connectport=${actualPort} connectaddress=127.0.0.1`;
|
|
495
|
+
}
|
|
496
|
+
} else {
|
|
497
|
+
proxyIssue.fix += '\n (Chrome M113+ always binds to 127.0.0.1 - port proxy is required)';
|
|
498
|
+
}
|
|
499
|
+
} else if (errMsg.includes('ETIMEDOUT') || errMsg.includes('timeout')) {
|
|
500
|
+
console.log(` ${C.red}✗${C.reset} Connection timeout (firewall blocking)`);
|
|
501
|
+
const firewallIssue = {
|
|
502
|
+
level: 'error',
|
|
503
|
+
message: 'Connection timeout - firewall is blocking WSL',
|
|
504
|
+
fix: 'Windows Firewall is blocking connections from WSL subnet.'
|
|
505
|
+
};
|
|
506
|
+
issues.push(firewallIssue);
|
|
507
|
+
|
|
508
|
+
// Auto-fix: try to add/update firewall rule from WSL (may need Admin PowerShell if this fails)
|
|
509
|
+
const ruleName = 'Chrome Debug (browsermonitor)';
|
|
510
|
+
const ruleExists = runPowerShell(`Get-NetFirewallRule -DisplayName '${ruleName}' -ErrorAction SilentlyContinue | Select-Object -First 1`);
|
|
511
|
+
const hasRule = ruleExists && ruleExists.trim().length > 0;
|
|
512
|
+
|
|
513
|
+
console.log('');
|
|
514
|
+
console.log(`${C.bold}[Auto-fix]${C.reset} Applying firewall rule for WSL...`);
|
|
515
|
+
|
|
516
|
+
let fixOk = false;
|
|
517
|
+
const setCmd = `Set-NetFirewallRule -DisplayName '${ruleName}' -RemoteAddress LocalSubnet,172.16.0.0/12 -ErrorAction Stop`;
|
|
518
|
+
const newCmd = `New-NetFirewallRule -DisplayName '${ruleName}' -Direction Inbound -LocalPort 9222-9299 -Protocol TCP -Action Allow -RemoteAddress LocalSubnet,172.16.0.0/12 -ErrorAction Stop`;
|
|
519
|
+
if (hasRule) {
|
|
520
|
+
fixOk = runPowerShell(setCmd, 8000) !== null;
|
|
521
|
+
if (!fixOk) {
|
|
522
|
+
fixOk = await runPowerShellElevated(setCmd, 60000);
|
|
523
|
+
}
|
|
524
|
+
if (fixOk) {
|
|
525
|
+
console.log(` ${C.green}✓${C.reset} Updated existing rule "${ruleName}" to allow WSL subnet`);
|
|
526
|
+
} else {
|
|
527
|
+
console.log(` ${C.yellow}!${C.reset} Could not update rule (try PowerShell as Admin)`);
|
|
528
|
+
}
|
|
529
|
+
} else {
|
|
530
|
+
fixOk = runPowerShell(newCmd, 8000) !== null;
|
|
531
|
+
if (!fixOk) {
|
|
532
|
+
fixOk = await runPowerShellElevated(newCmd, 60000);
|
|
533
|
+
}
|
|
534
|
+
if (fixOk) {
|
|
535
|
+
console.log(` ${C.green}✓${C.reset} Created firewall rule "${ruleName}"`);
|
|
536
|
+
} else {
|
|
537
|
+
console.log(` ${C.yellow}!${C.reset} Could not create rule (try PowerShell as Admin)`);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
if (fixOk) {
|
|
542
|
+
console.log(` ${C.dim}Re-testing connectivity...${C.reset}`);
|
|
543
|
+
try {
|
|
544
|
+
const retryResponse = await fetch(`http://${windowsHostIP}:${actualPort}/json/version`, {
|
|
545
|
+
signal: AbortSignal.timeout(5000)
|
|
546
|
+
});
|
|
547
|
+
if (retryResponse.ok) {
|
|
548
|
+
canConnect = true;
|
|
549
|
+
console.log(` ${C.green}✓${C.reset} Connection successful after firewall fix.`);
|
|
550
|
+
issues.pop(); // remove firewall issue so summary is clean
|
|
551
|
+
}
|
|
552
|
+
} catch (_) {
|
|
553
|
+
console.log(` ${C.dim}Still unreachable - wait a few seconds and run the monitor again.${C.reset}`);
|
|
554
|
+
}
|
|
555
|
+
} else {
|
|
556
|
+
firewallIssue.fix += '\n Run in PowerShell as Administrator:\n' +
|
|
557
|
+
` Set-NetFirewallRule -DisplayName "${ruleName}" -RemoteAddress LocalSubnet,172.16.0.0/12\n` +
|
|
558
|
+
` Or create: New-NetFirewallRule -DisplayName "${ruleName}" -Direction Inbound -LocalPort 9222-9299 -Protocol TCP -Action Allow -RemoteAddress LocalSubnet,172.16.0.0/12`;
|
|
559
|
+
}
|
|
560
|
+
} else if (errMsg.includes('ENETUNREACH')) {
|
|
561
|
+
console.log(` ${C.red}✗${C.reset} Network unreachable`);
|
|
562
|
+
issues.push({
|
|
563
|
+
level: 'error',
|
|
564
|
+
message: 'Network unreachable - Windows host IP may be wrong',
|
|
565
|
+
fix: `Check Windows IP. Current: ${windowsHostIP}`
|
|
566
|
+
});
|
|
567
|
+
} else if (errMsg === 'fetch failed' || errMsg.includes('fetch failed')) {
|
|
568
|
+
console.log(` ${C.red}✗${C.reset} Connection failed (Chrome bound to localhost only)`);
|
|
569
|
+
issues.push({
|
|
570
|
+
level: 'error',
|
|
571
|
+
message: 'WSL cannot reach Chrome - bound to 127.0.0.1 (expected since Chrome M113)',
|
|
572
|
+
fix: 'Port proxy is required for WSL access:\n' +
|
|
573
|
+
` netsh interface portproxy add v4tov4 listenport=${actualPort} listenaddress=0.0.0.0 connectport=${actualPort} connectaddress=127.0.0.1\n` +
|
|
574
|
+
' (Chrome M113+ ignores --remote-debugging-address=0.0.0.0 for security)'
|
|
575
|
+
});
|
|
576
|
+
} else {
|
|
577
|
+
console.log(` ${C.red}✗${C.reset} ${errMsg.substring(0, 60)}`);
|
|
578
|
+
issues.push({
|
|
579
|
+
level: 'error',
|
|
580
|
+
message: `Connection failed: ${errMsg.substring(0, 60)}`
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
586
|
+
// Summary
|
|
587
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
588
|
+
console.log('');
|
|
589
|
+
console.log(`${C.bold}${C.cyan}─────────────────────────────────────────────────────────────────────────────${C.reset}`);
|
|
590
|
+
|
|
591
|
+
if (canConnect) {
|
|
592
|
+
console.log(`${C.bold}${C.green}✓ All checks passed - connection should work${C.reset}`);
|
|
593
|
+
} else if (issues.length === 0) {
|
|
594
|
+
console.log(`${C.bold}${C.yellow}? Diagnostics complete but connection failed - unknown issue${C.reset}`);
|
|
595
|
+
} else {
|
|
596
|
+
const errors = issues.filter(i => i.level === 'error');
|
|
597
|
+
const warnings = issues.filter(i => i.level === 'warn');
|
|
598
|
+
|
|
599
|
+
console.log(`${C.bold}${C.red}✗ Found ${errors.length} error(s) and ${warnings.length} warning(s)${C.reset}`);
|
|
600
|
+
console.log('');
|
|
601
|
+
|
|
602
|
+
for (const issue of errors) {
|
|
603
|
+
console.log(`${C.red}ERROR:${C.reset} ${issue.message}`);
|
|
604
|
+
if (issue.fix) {
|
|
605
|
+
console.log(`${C.green}FIX:${C.reset}`);
|
|
606
|
+
console.log(` ${issue.fix.split('\n').join('\n ')}`);
|
|
607
|
+
}
|
|
608
|
+
console.log('');
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
for (const issue of warnings) {
|
|
612
|
+
console.log(`${C.yellow}WARNING:${C.reset} ${issue.message}`);
|
|
613
|
+
if (issue.fix) {
|
|
614
|
+
console.log(`${C.dim}Suggestion:${C.reset}`);
|
|
615
|
+
console.log(` ${issue.fix.split('\n').join('\n ')}`);
|
|
616
|
+
}
|
|
617
|
+
console.log('');
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// Quick fix summary
|
|
621
|
+
if (errors.length > 0) {
|
|
622
|
+
const hasConflict = issues.some(i => i.message.includes('Port proxy conflict'));
|
|
623
|
+
|
|
624
|
+
if (hasConflict) {
|
|
625
|
+
console.log(`${C.bold}${C.yellow}═══════════════════════════════════════════════════════════════════════════════${C.reset}`);
|
|
626
|
+
console.log(`${C.bold}${C.yellow} AUTO-FIX AVAILABLE: Port Proxy Conflict${C.reset}`);
|
|
627
|
+
console.log(`${C.bold}${C.yellow}═══════════════════════════════════════════════════════════════════════════════${C.reset}`);
|
|
628
|
+
console.log('');
|
|
629
|
+
console.log(` The port proxy grabbed port ${actualPort} before Chrome could bind to it.`);
|
|
630
|
+
console.log(` Chrome fell back to IPv6 localhost [::1], but port proxy expects IPv4.`);
|
|
631
|
+
console.log('');
|
|
632
|
+
console.log(` ${C.bold}Solution:${C.reset} Remove port proxy, restart Chrome, then re-add port proxy.`);
|
|
633
|
+
console.log(` ${C.dim}(Port proxy must be added AFTER Chrome starts)${C.reset}`);
|
|
634
|
+
console.log('');
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
console.log(`${C.bold}Quick Fix Commands (run in PowerShell as Admin):${C.reset}`);
|
|
638
|
+
console.log('');
|
|
639
|
+
|
|
640
|
+
const needsFirewall = issues.some(i =>
|
|
641
|
+
i.message.toLowerCase().includes('firewall') ||
|
|
642
|
+
i.message.toLowerCase().includes('timeout')
|
|
643
|
+
);
|
|
644
|
+
const needsPortProxy = issues.some(i =>
|
|
645
|
+
i.message.toLowerCase().includes('port proxy needed') ||
|
|
646
|
+
i.message.toLowerCase().includes('connection refused') ||
|
|
647
|
+
i.message.toLowerCase().includes('bound to 127.0.0.1') ||
|
|
648
|
+
i.message.toLowerCase().includes('wsl cannot reach')
|
|
649
|
+
);
|
|
650
|
+
const needsChromeRestart = issues.some(i =>
|
|
651
|
+
i.message.toLowerCase().includes('restart chrome') ||
|
|
652
|
+
i.message.toLowerCase().includes('without remote debugging') ||
|
|
653
|
+
i.message.toLowerCase().includes('no chrome') ||
|
|
654
|
+
i.message.toLowerCase().includes('singleton')
|
|
655
|
+
);
|
|
656
|
+
|
|
657
|
+
let anyCommandPrinted = false;
|
|
658
|
+
|
|
659
|
+
if (hasConflict) {
|
|
660
|
+
console.log(` ${C.cyan}# Step 1: Remove conflicting port proxy:${C.reset}`);
|
|
661
|
+
console.log(` netsh interface portproxy delete v4tov4 listenport=${actualPort} listenaddress=0.0.0.0`);
|
|
662
|
+
console.log('');
|
|
663
|
+
console.log(` ${C.cyan}# Step 2: Kill browsermonitor Chrome only (not your browser!):${C.reset}`);
|
|
664
|
+
console.log(` Get-WmiObject Win32_Process -Filter "name='chrome.exe'" | Where-Object { $_.CommandLine -match 'browsermonitor' } | ForEach-Object { Stop-Process -Id $_.ProcessId -Force }`);
|
|
665
|
+
console.log('');
|
|
666
|
+
console.log(` ${C.cyan}# Step 3: Run browsermonitor again${C.reset}`);
|
|
667
|
+
console.log(` ${C.dim}(Port proxy will be set up automatically after Chrome starts)${C.reset}`);
|
|
668
|
+
console.log('');
|
|
669
|
+
anyCommandPrinted = true;
|
|
670
|
+
} else {
|
|
671
|
+
if (needsFirewall) {
|
|
672
|
+
const ourRuleName = 'Chrome Debug (browsermonitor)';
|
|
673
|
+
const ourRuleExists = runPowerShell(`Get-NetFirewallRule -DisplayName '${ourRuleName}' -ErrorAction SilentlyContinue | ConvertTo-Json`);
|
|
674
|
+
const hasOurRule = ourRuleExists && ourRuleExists !== 'null' && ourRuleExists !== '';
|
|
675
|
+
|
|
676
|
+
if (hasOurRule) {
|
|
677
|
+
console.log(` ${C.cyan}# Update existing rule to include WSL subnet:${C.reset}`);
|
|
678
|
+
console.log(` Set-NetFirewallRule -DisplayName "${ourRuleName}" -RemoteAddress LocalSubnet,172.16.0.0/12`);
|
|
679
|
+
} else if (existingRuleName) {
|
|
680
|
+
console.log(` ${C.cyan}# Found existing rule "${existingRuleName}" - update it:${C.reset}`);
|
|
681
|
+
console.log(` Set-NetFirewallRule -DisplayName "${existingRuleName}" -RemoteAddress LocalSubnet,172.16.0.0/12`);
|
|
682
|
+
console.log('');
|
|
683
|
+
console.log(` ${C.cyan}# Or create a dedicated rule for browsermonitor:${C.reset}`);
|
|
684
|
+
console.log(` New-NetFirewallRule -DisplayName "${ourRuleName}" -Direction Inbound -LocalPort 9222-9299 -Protocol TCP -Action Allow -RemoteAddress LocalSubnet,172.16.0.0/12`);
|
|
685
|
+
} else {
|
|
686
|
+
console.log(` ${C.cyan}# Create firewall rule (includes WSL subnet):${C.reset}`);
|
|
687
|
+
console.log(` New-NetFirewallRule -DisplayName "${ourRuleName}" -Direction Inbound -LocalPort 9222-9299 -Protocol TCP -Action Allow -RemoteAddress LocalSubnet,172.16.0.0/12`);
|
|
688
|
+
}
|
|
689
|
+
console.log(` ${C.dim}# Note: 172.16.0.0/12 covers WSL2 dynamic IP range${C.reset}`);
|
|
690
|
+
console.log('');
|
|
691
|
+
anyCommandPrinted = true;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
if (needsPortProxy && !isListeningOnAll) {
|
|
695
|
+
console.log(` ${C.cyan}# Add port proxy:${C.reset}`);
|
|
696
|
+
console.log(` netsh interface portproxy add v4tov4 listenport=${actualPort} listenaddress=0.0.0.0 connectport=${actualPort} connectaddress=127.0.0.1`);
|
|
697
|
+
console.log('');
|
|
698
|
+
anyCommandPrinted = true;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
if (needsChromeRestart) {
|
|
702
|
+
console.log(` ${C.cyan}# Close all Chrome and restart with debug port:${C.reset}`);
|
|
703
|
+
console.log(` chrome.exe --remote-debugging-port=${actualPort}`);
|
|
704
|
+
console.log(` ${C.dim}# Then add port proxy for WSL access:${C.reset}`);
|
|
705
|
+
console.log(` netsh interface portproxy add v4tov4 listenport=${actualPort} listenaddress=0.0.0.0 connectport=${actualPort} connectaddress=127.0.0.1`);
|
|
706
|
+
console.log('');
|
|
707
|
+
anyCommandPrinted = true;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
if (!anyCommandPrinted && issues.length > 0) {
|
|
712
|
+
console.log(` ${C.yellow}No automatic fix available. Issues found:${C.reset}`);
|
|
713
|
+
for (const issue of issues) {
|
|
714
|
+
console.log(` ${C.dim}• ${issue.message}${C.reset}`);
|
|
715
|
+
if (issue.fix) {
|
|
716
|
+
console.log(` ${issue.fix.split('\n').join('\n ')}`);
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
console.log('');
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
console.log(`${C.bold}${C.cyan}─────────────────────────────────────────────────────────────────────────────${C.reset}`);
|
|
725
|
+
console.log('');
|
|
726
|
+
|
|
727
|
+
const hasPortProxyConflictResult = issues.some(i => i.message.includes('Port proxy conflict'));
|
|
728
|
+
return { issues, canConnect, hasPortProxyConflict: hasPortProxyConflictResult, actualPort };
|
|
729
|
+
}
|