chrome-bridge-mcp 1.23.2 → 1.23.4
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/README.md +17 -0
- package/extension/lib/command-outcome.js +57 -0
- package/extension/manifest.json +1 -1
- package/extension/service-worker.js +35 -23
- package/package.json +1 -1
- package/server/index.js +3 -1
- package/server/launcher.js +46 -10
- package/server/observe.js +41 -2
- package/server/protocol.js +1 -1
- package/server/ws-manager.js +3 -1
package/README.md
CHANGED
|
@@ -42,6 +42,16 @@ element refs, `click(ref="n1")` follows with no discovery turn in between.
|
|
|
42
42
|
> instead: an unpacked extension is dropped on every reboot, because the
|
|
43
43
|
> container isn't mounted when Chrome starts.
|
|
44
44
|
|
|
45
|
+
> **On Windows** `install.sh` does not run from PowerShell or cmd. Use the
|
|
46
|
+
> plugin path below (`claude plugin marketplace add frsorrentino/chrome-bridge`,
|
|
47
|
+
> then `claude plugin install chrome-bridge@chrome-bridge`) with the extension
|
|
48
|
+
> from the Web Store, or run `install.sh` from Git Bash. The plugin's error
|
|
49
|
+
> log hooks run in Git Bash, as Claude Code runs every hook, and need Python
|
|
50
|
+
> 3.8+ as `python3`, `python` or `py` (the Microsoft Store `python3` alias is
|
|
51
|
+
> skipped); without Python they stay silent and the tools work the same. For
|
|
52
|
+
> launch mode see `CHROME_BRIDGE_BROWSER` below: Google Chrome cannot load the
|
|
53
|
+
> extension there, Microsoft Edge can.
|
|
54
|
+
|
|
45
55
|
`install.sh` registers the MCP server with `--scope user`. To do it by hand:
|
|
46
56
|
`claude mcp add --scope user chrome-bridge node /path/to/server/index.js`.
|
|
47
57
|
For `execute_js`, enable **Allow user scripts** in `chrome://extensions` →
|
|
@@ -138,6 +148,12 @@ isolated sessions or CI:
|
|
|
138
148
|
node server/index.js --launch --headless
|
|
139
149
|
```
|
|
140
150
|
|
|
151
|
+
It looks for Chromium, Microsoft Edge, Brave and last Google Chrome in their
|
|
152
|
+
standard paths on Linux, macOS and Windows; `CHROME_BRIDGE_BROWSER` picks
|
|
153
|
+
another binary. Google Chrome 137+ ignores `--load-extension`, so the
|
|
154
|
+
extension never connects in it: use Chromium, Edge, Brave or Chrome for
|
|
155
|
+
Testing.
|
|
156
|
+
|
|
141
157
|
Pair it with `session_record` + `replay` for smoke tests with no model in the
|
|
142
158
|
loop. In launch mode `execute_js` falls back to `new Function` when the
|
|
143
159
|
user-script toggle isn't available.
|
|
@@ -186,6 +202,7 @@ Environment variables, each with a matching CLI flag:
|
|
|
186
202
|
| `CHROME_BRIDGE_CAPS` / `--caps` | `core` | `core`, `audits`, `visual`, `network`, `storage`, `dom`, `files`, `all`. `install.sh` uses `all` |
|
|
187
203
|
| `CHROME_BRIDGE_NO_JS` / `--no-js` | unset | No arbitrary JavaScript in the page: `execute_js` and `modify_dom` leave the schema, `wait_for(condition=function)` and `javascript:`/`data:` URLs are refused. `get_status` reports `js_evaluation` |
|
|
188
204
|
| `CHROME_BRIDGE_READ_ROOT` / `--read-root` | unset | Where `upload_file` may read from. Unset: any file except keys and credentials (`~/.ssh`, `~/.aws`, `~/.gnupg`, `.env*`, `id_*`, `*.pem`, `*.key`, …), symlinks resolved first. Set: only files under this directory, whatever their name — the way to upload a certificate key on purpose |
|
|
205
|
+
| `CHROME_BRIDGE_BROWSER` | unset | Launch mode only: the browser binary, e.g. `C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe`. Unset: the first of Chromium, Edge, Brave, Google Chrome found in the standard paths (on Linux Chromium, then Google Chrome). Google Chrome 137+ cannot load the extension |
|
|
189
206
|
| `CHROME_BRIDGE_OBSERVE` | unset | `off` stops the local error log below. The plugin sets `hook`: its hooks keep the log and the server stays out of it |
|
|
190
207
|
| `CHROME_BRIDGE_WRITE_ROOT` / `--write-root` | unset | Every path the model chooses (`save_to`, `output_path`, exports) must be under this directory, checked before the browser does any work; the server's own state under `~/.config/chrome-bridge` stays writable. The CLI is your shell and is not restricted |
|
|
191
208
|
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Esiti dei comandi che prima si fidavano di un valore di ripiego.
|
|
3
|
+
*
|
|
4
|
+
* Due casi osservati nel test Windows del 27/09/2026, validi su ogni OS:
|
|
5
|
+
* - `click #checkout-button` con `query_dom` a count 0 rispondeva
|
|
6
|
+
* `{"clicked":true}` (type_text, hover e press_key allo stesso modo): la
|
|
7
|
+
* funzione iniettata lancia «Element not found», `executeScript` risolve
|
|
8
|
+
* con `result` undefined e il ripiego `?? { clicked: true }` lo
|
|
9
|
+
* trasformava in un successo.
|
|
10
|
+
* - `navigate` verso una porta chiusa rispondeva `{url, title}` senza errore:
|
|
11
|
+
* la scheda mostra la pagina d'errore di Chrome, che arriva comunque a
|
|
12
|
+
* status 'complete'. L'errore vero passa solo da webNavigation.onErrorOccurred.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Risultato di click, type_text, hover e press_key: senza un risultato della
|
|
17
|
+
* funzione iniettata il comando non è avvenuto, qualunque sia il motivo. Tutte
|
|
18
|
+
* e quattro rispondevano con un successo di ripiego ({clicked|typed|hovered|
|
|
19
|
+
* pressed: true}) su un selettore che non trova niente.
|
|
20
|
+
*/
|
|
21
|
+
export function elementOutcome(results, selector) {
|
|
22
|
+
const first = results?.[0];
|
|
23
|
+
if (first?.result != null) return first.result;
|
|
24
|
+
if (first?.error?.message) throw new Error(first.error.message);
|
|
25
|
+
if (!selector) throw new Error('The page returned no result for the command');
|
|
26
|
+
throw new Error(`Element not found: ${selector} (no element matches; check it with query_dom)`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* net::ERR_ABORTED non è un fallimento da riferire: è un download, una
|
|
31
|
+
* risposta 204 o una navigazione sostituita da un'altra.
|
|
32
|
+
*/
|
|
33
|
+
export function isReportableNavError(details) {
|
|
34
|
+
return details?.frameId === 0 && !!details.error && details.error !== 'net::ERR_ABORTED';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Raccoglie gli errori di rete del frame principale, per scheda, dal momento
|
|
39
|
+
* della chiamata. Va aperta PRIMA di tabs.create/tabs.update: un rifiuto di
|
|
40
|
+
* connessione in locale arriva in pochi millisecondi, prima che l'id della
|
|
41
|
+
* scheda nuova sia noto.
|
|
42
|
+
*/
|
|
43
|
+
export function watchNavErrors(onErrorOccurred) {
|
|
44
|
+
const byTab = new Map();
|
|
45
|
+
const listener = (details) => {
|
|
46
|
+
if (isReportableNavError(details)) byTab.set(details.tabId, details);
|
|
47
|
+
};
|
|
48
|
+
onErrorOccurred.addListener(listener);
|
|
49
|
+
return {
|
|
50
|
+
errorFor: (tabId) => byTab.get(tabId) ?? null,
|
|
51
|
+
stop: () => onErrorOccurred.removeListener(listener),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function navErrorMessage(details, tabId) {
|
|
56
|
+
return `${details.error} loading ${details.url} (tab ${tabId} shows the browser's error page)`;
|
|
57
|
+
}
|
package/extension/manifest.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest_version": 3,
|
|
3
3
|
"name": "Chrome Bridge for Claude Code",
|
|
4
|
-
"version": "1.23.
|
|
4
|
+
"version": "1.23.4",
|
|
5
5
|
"description": "Bridge your browser to Claude Code: 60 token-efficient web-dev automation tools over a local WebSocket. ChromeOS included.",
|
|
6
6
|
"homepage_url": "https://github.com/frsorrentino/chrome-bridge",
|
|
7
7
|
"minimum_chrome_version": "135",
|
|
@@ -12,6 +12,7 @@ import { createPacer } from './lib/capture-pacing.js';
|
|
|
12
12
|
import { computeTiles } from './lib/tile-layout.js';
|
|
13
13
|
import { classifyDownload } from './lib/download-state.js';
|
|
14
14
|
import { findTextInPage } from './lib/find-text.js';
|
|
15
|
+
import { elementOutcome, watchNavErrors, navErrorMessage } from './lib/command-outcome.js';
|
|
15
16
|
const { pushError } = globalThis.__cbTelemetry;
|
|
16
17
|
|
|
17
18
|
const DEFAULT_PORT = 8765;
|
|
@@ -779,29 +780,40 @@ async function cmdGetTabs({ include_windows = false, ended } = {}) {
|
|
|
779
780
|
async function cmdNavigate({ url, tab_id }) {
|
|
780
781
|
if (!url) throw new Error('Missing required parameter: url');
|
|
781
782
|
|
|
782
|
-
//
|
|
783
|
-
//
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
//
|
|
788
|
-
|
|
789
|
-
|
|
783
|
+
// La pagina d'errore di Chrome (connessione rifiutata, DNS) arriva a
|
|
784
|
+
// 'complete' come una pagina vera: l'errore si legge solo qui.
|
|
785
|
+
const navErrors = watchNavErrors(chrome.webNavigation.onErrorOccurred);
|
|
786
|
+
try {
|
|
787
|
+
// Senza tab_id esplicito: apri SEMPRE in una nuova scheda (attiva) per non
|
|
788
|
+
// sovrascrivere la scheda/sessione corrente dell'utente. Con tab_id: naviga
|
|
789
|
+
// quella scheda specifica.
|
|
790
|
+
if (!tab_id) {
|
|
791
|
+
const newTab = await chrome.tabs.create({ url, active: true });
|
|
792
|
+
// Se la pagina è già 'complete' (es. cache) non aspettare il listener
|
|
793
|
+
if (newTab.status !== 'complete') {
|
|
794
|
+
await waitForComplete(newTab.id);
|
|
795
|
+
}
|
|
796
|
+
const failed = navErrors.errorFor(newTab.id);
|
|
797
|
+
if (failed) throw new Error(navErrorMessage(failed, newTab.id));
|
|
798
|
+
const createdTab = await chrome.tabs.get(newTab.id);
|
|
799
|
+
return { url: createdTab.url, title: createdTab.title, tabId: newTab.id };
|
|
790
800
|
}
|
|
791
|
-
const createdTab = await chrome.tabs.get(newTab.id);
|
|
792
|
-
return { url: createdTab.url, title: createdTab.title, tabId: newTab.id };
|
|
793
|
-
}
|
|
794
801
|
|
|
795
|
-
|
|
802
|
+
const tabId = await resolveTabId(tab_id);
|
|
796
803
|
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
804
|
+
// Registra il listener PRIMA di tabs.update: una navigazione veloce (cache)
|
|
805
|
+
// può emettere 'complete' prima che il listener esista
|
|
806
|
+
const done = waitForComplete(tabId);
|
|
807
|
+
await chrome.tabs.update(tabId, { url });
|
|
808
|
+
await done;
|
|
802
809
|
|
|
803
|
-
|
|
804
|
-
|
|
810
|
+
const failed = navErrors.errorFor(tabId);
|
|
811
|
+
if (failed) throw new Error(navErrorMessage(failed, tabId));
|
|
812
|
+
const updatedTab = await chrome.tabs.get(tabId);
|
|
813
|
+
return { url: updatedTab.url, title: updatedTab.title, tabId };
|
|
814
|
+
} finally {
|
|
815
|
+
navErrors.stop();
|
|
816
|
+
}
|
|
805
817
|
}
|
|
806
818
|
|
|
807
819
|
async function cmdScreenshot({ tab_id }) {
|
|
@@ -907,7 +919,7 @@ async function cmdClick({ selector, tab_id, frame_id, force = false, button = 'l
|
|
|
907
919
|
world: 'MAIN',
|
|
908
920
|
});
|
|
909
921
|
|
|
910
|
-
return results
|
|
922
|
+
return elementOutcome(results, selector);
|
|
911
923
|
}
|
|
912
924
|
|
|
913
925
|
async function cmdTypeText({ selector, text, mode = 'set', tab_id, frame_id }) {
|
|
@@ -982,7 +994,7 @@ async function cmdTypeText({ selector, text, mode = 'set', tab_id, frame_id }) {
|
|
|
982
994
|
world: 'MAIN',
|
|
983
995
|
});
|
|
984
996
|
|
|
985
|
-
return results
|
|
997
|
+
return elementOutcome(results, selector);
|
|
986
998
|
}
|
|
987
999
|
|
|
988
1000
|
// Impronta per page_changed: pochi interi letti nell'ISOLATED world (nessuna
|
|
@@ -3331,7 +3343,7 @@ async function cmdHover({ selector, tab_id, frame_id }) {
|
|
|
3331
3343
|
args: [selector],
|
|
3332
3344
|
world: 'MAIN',
|
|
3333
3345
|
});
|
|
3334
|
-
return results
|
|
3346
|
+
return elementOutcome(results, selector);
|
|
3335
3347
|
}
|
|
3336
3348
|
|
|
3337
3349
|
// --- press_key ---
|
|
@@ -3398,7 +3410,7 @@ async function cmdPressKey({ key, selector, ctrl = false, shift = false, alt = f
|
|
|
3398
3410
|
args: [key, selector || null, { ctrl, shift, alt, meta }],
|
|
3399
3411
|
world: 'MAIN',
|
|
3400
3412
|
});
|
|
3401
|
-
return results
|
|
3413
|
+
return elementOutcome(results, selector);
|
|
3402
3414
|
}
|
|
3403
3415
|
|
|
3404
3416
|
// --- get_frames ---
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chrome-bridge-mcp",
|
|
3
3
|
"mcpName": "io.github.frsorrentino/chrome-bridge",
|
|
4
|
-
"version": "1.23.
|
|
4
|
+
"version": "1.23.4",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Chrome Bridge by frsorrentino: MCP server that lets Claude Code borrow a tab from your own logged-in Chrome and hand it back — logins, 2FA and CAPTCHAs stay with you (handoff). 60 tools; about half the cost of Claude in Chrome, measured; ChromeOS included.",
|
|
7
7
|
"main": "server/index.js",
|
package/server/index.js
CHANGED
|
@@ -13,7 +13,7 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
|
|
13
13
|
import { WSManager } from './ws-manager.js';
|
|
14
14
|
import { registerTools } from './tools.js';
|
|
15
15
|
import { createObserver } from './observe.js';
|
|
16
|
-
import { launchBrowser } from './launcher.js';
|
|
16
|
+
import { BRANDED_CHROME_HINT, launchBrowser } from './launcher.js';
|
|
17
17
|
import { DEFAULT_PORT, VERSION } from './protocol.js';
|
|
18
18
|
|
|
19
19
|
// Launch mode: browser dedicato (profilo effimero + estensione unpacked).
|
|
@@ -108,6 +108,8 @@ async function main() {
|
|
|
108
108
|
throw new Error(`--launch requires a dedicated port, but ${wsManager.port} is owned by another chrome-bridge. Unset CHROME_BRIDGE_PORT (ephemeral) or pick a free one.`);
|
|
109
109
|
}
|
|
110
110
|
browser = await launchBrowser({ port: wsManager.port, headless: HEADLESS });
|
|
111
|
+
// Il Chrome brandizzato parte ma resta muto: l'errore di connessione dice perche'
|
|
112
|
+
if (browser.branded) wsManager.notConnectedHint = BRANDED_CHROME_HINT;
|
|
111
113
|
}
|
|
112
114
|
|
|
113
115
|
// 3. Registra i tool MCP (filtrati per capability)
|
package/server/launcher.js
CHANGED
|
@@ -11,17 +11,50 @@ import { spawn } from 'node:child_process';
|
|
|
11
11
|
import { cp, mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
12
12
|
import { existsSync } from 'node:fs';
|
|
13
13
|
import { tmpdir } from 'node:os';
|
|
14
|
-
import { dirname, join } from 'node:path';
|
|
14
|
+
import { dirname, join, win32 } from 'node:path';
|
|
15
15
|
import { fileURLToPath } from 'node:url';
|
|
16
16
|
|
|
17
17
|
const EXTENSION_SRC = join(dirname(fileURLToPath(import.meta.url)), '..', 'extension');
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Percorsi standard per piattaforma. Chromium, Edge e Brave prima di Google
|
|
21
|
+
* Chrome: il Chrome brandizzato dalla 137 ignora --load-extension, parte ma
|
|
22
|
+
* l'estensione non si connette mai (test Windows del 27/09: Chrome 155 muto,
|
|
23
|
+
* Edge 154 connesso in meno di 6 s).
|
|
24
|
+
*/
|
|
25
|
+
export function browserCandidates(platform = process.platform, env = process.env) {
|
|
26
|
+
if (platform === 'win32') {
|
|
27
|
+
const bases = [env.LOCALAPPDATA, env.PROGRAMFILES, env['PROGRAMFILES(X86)']].filter(Boolean);
|
|
28
|
+
return [
|
|
29
|
+
['Chromium', 'Application', 'chrome.exe'],
|
|
30
|
+
['Microsoft', 'Edge', 'Application', 'msedge.exe'],
|
|
31
|
+
['BraveSoftware', 'Brave-Browser', 'Application', 'brave.exe'],
|
|
32
|
+
['Google', 'Chrome', 'Application', 'chrome.exe'],
|
|
33
|
+
].flatMap((parts) => bases.map((b) => win32.join(b, ...parts)));
|
|
34
|
+
}
|
|
35
|
+
if (platform === 'darwin') {
|
|
36
|
+
return [
|
|
37
|
+
'/Applications/Chromium.app/Contents/MacOS/Chromium',
|
|
38
|
+
'/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge',
|
|
39
|
+
'/Applications/Brave Browser.app/Contents/MacOS/Brave Browser',
|
|
40
|
+
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
|
|
41
|
+
];
|
|
42
|
+
}
|
|
43
|
+
return [
|
|
44
|
+
'/usr/bin/chromium',
|
|
45
|
+
'/usr/bin/chromium-browser',
|
|
46
|
+
'/usr/bin/google-chrome',
|
|
47
|
+
'/usr/bin/google-chrome-stable',
|
|
48
|
+
];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Google Chrome brandizzato (non Chromium, non Chrome for Testing). */
|
|
52
|
+
export function isBrandedChrome(path) {
|
|
53
|
+
return /google[\\/ _-]?chrome/i.test(path) && !/for[ _-]?testing/i.test(path);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const BRANDED_CHROME_HINT = 'Google Chrome 137+ ignores --load-extension, so launch mode cannot load the extension into it:'
|
|
57
|
+
+ ' set CHROME_BRIDGE_BROWSER to Chromium, Microsoft Edge, Brave or Chrome for Testing';
|
|
25
58
|
|
|
26
59
|
export function findBrowser() {
|
|
27
60
|
if (process.env.CHROME_BRIDGE_BROWSER) {
|
|
@@ -30,9 +63,10 @@ export function findBrowser() {
|
|
|
30
63
|
}
|
|
31
64
|
return process.env.CHROME_BRIDGE_BROWSER;
|
|
32
65
|
}
|
|
33
|
-
const
|
|
66
|
+
const candidates = browserCandidates();
|
|
67
|
+
const found = candidates.find((p) => existsSync(p));
|
|
34
68
|
if (!found) {
|
|
35
|
-
throw new Error(`No Chromium
|
|
69
|
+
throw new Error(`No Chromium-based browser found (tried: ${candidates.join(', ')}). Set CHROME_BRIDGE_BROWSER to Chromium, Edge, Brave or Chrome for Testing.`);
|
|
36
70
|
}
|
|
37
71
|
return found;
|
|
38
72
|
}
|
|
@@ -93,5 +127,7 @@ export async function launchBrowser({ port, headless = false }) {
|
|
|
93
127
|
};
|
|
94
128
|
|
|
95
129
|
console.error(`[chrome-bridge] launched ${browser}${headless ? ' (headless)' : ''} pid=${proc.pid}, ws port ${port}`);
|
|
96
|
-
|
|
130
|
+
const branded = isBrandedChrome(browser);
|
|
131
|
+
if (branded) console.error(`[chrome-bridge] warning: ${BRANDED_CHROME_HINT}`);
|
|
132
|
+
return { pid: proc.pid, stop, branded };
|
|
97
133
|
}
|
package/server/observe.js
CHANGED
|
@@ -40,12 +40,51 @@ const DEFAULTS = { enabled: true, dir: '', max_records: 2000, max_days: 90 };
|
|
|
40
40
|
// \w di Python è Unicode: qui lo stesso insieme, esplicito.
|
|
41
41
|
const W = '[\\p{L}\\p{N}_]';
|
|
42
42
|
|
|
43
|
+
// Una home in qualunque forma, come observe.py (claude-observe d3fa492, invio
|
|
44
|
+
// anonimo da Windows): C:\Users\x, C:/Users/x, /c/Users/x, /mnt/c/Users/x,
|
|
45
|
+
// /cygdrive/c/Users/x, \\?\C:\Users\x, i \\ dentro JSON, /home/x, /Users/x.
|
|
46
|
+
const SEP = '[\\\\/]+';
|
|
47
|
+
const DRIVE = '(?:(?:[\\\\/]{2}\\?[\\\\/]+)?\\b[a-z]:|/mnt/[a-z]|/cygdrive/[a-z]|/[a-z](?=[\\\\/]))';
|
|
48
|
+
const SEG = '[^\\\\/\\s"\'`<>|:*?,;()\\[\\]]';
|
|
49
|
+
// il nome della cartella puo' avere spazi: con gli spazi solo se dopo viene un separatore
|
|
50
|
+
const HOME_ANY_RE = new RegExp(`${DRIVE}?${SEP}(?:users|home|documents and settings)${SEP}(?:${SEG}+(?: ${SEG}+)+(?=[\\\\/])|${SEG}+)`, 'giu');
|
|
51
|
+
const reEscape = (x) => x.replace(/[.*+?^${}()|[\]\\/]/gu, '\\$&');
|
|
52
|
+
|
|
53
|
+
// Le home di questa macchina senza distinzione di separatore, unita' e
|
|
54
|
+
// maiuscole (anche una USERPROFILE fuori da Users), e il nome utente come
|
|
55
|
+
// segmento di percorso.
|
|
56
|
+
function homeRes(env = process.env) {
|
|
57
|
+
const homes = [...new Set([homedir(), env.USERPROFILE || '', env.HOME || ''])];
|
|
58
|
+
const pats = [];
|
|
59
|
+
for (const h of homes.sort((a, b) => b.length - a.length)) {
|
|
60
|
+
let parts = h.split(/[\\/]+/u).filter((x) => x && x !== '?');
|
|
61
|
+
if (!parts.length) continue;
|
|
62
|
+
const drive = parts.length > 1 ? /^([A-Za-z]):?$/u.exec(parts[0]) : null;
|
|
63
|
+
let head = '';
|
|
64
|
+
if (drive && (parts[0].endsWith(':') || parts[0].length === 1)) {
|
|
65
|
+
const d = drive[1];
|
|
66
|
+
head = `(?:(?:[\\\\/]{2}\\?[\\\\/]+)?\\b${d}:|/mnt/${d}|/cygdrive/${d}|/${d}(?=[\\\\/]))`;
|
|
67
|
+
parts = parts.slice(1);
|
|
68
|
+
}
|
|
69
|
+
pats.push(head + parts.map((x) => SEP + reEscape(x)).join(''));
|
|
70
|
+
}
|
|
71
|
+
const names = new Set(homes.filter(Boolean).map((h) => basename(h.replace(/\\/gu, '/').replace(/\/+$/u, ''))));
|
|
72
|
+
for (const k of ['USERNAME', 'USER', 'LOGNAME']) names.add(env[k] || '');
|
|
73
|
+
const list = [...names].filter((n) => n.length >= 2 && !['users', 'home'].includes(n.toLowerCase())).sort((a, b) => b.length - a.length);
|
|
74
|
+
return [
|
|
75
|
+
pats.map((p) => new RegExp(`${p}(?![^\\\\/\\s"'\`<>|:*?,;()\\[\\]])`, 'giu')),
|
|
76
|
+
list.length ? new RegExp(`(?<=[\\\\/])(?:${list.map(reEscape).join('|')})(?=[\\\\/\\s"'\`<>|:*?,;()\\[\\]]|$)`, 'giu') : null,
|
|
77
|
+
];
|
|
78
|
+
}
|
|
79
|
+
const [HOME_RES, USER_SEG_RE] = homeRes();
|
|
80
|
+
|
|
43
81
|
export function scrub(text, limit = 300) {
|
|
44
82
|
let t = String(text ?? '');
|
|
45
|
-
const home = homedir();
|
|
46
|
-
if (home && home !== '/') t = t.split(home).join('~');
|
|
47
83
|
t = t.replace(new RegExp(`(?:${W}|[.+-])+@(?:${W}|-)+\\.(?:${W}|[.-])+`, 'gu'), '<EMAIL>');
|
|
48
84
|
t = t.replace(/(https?:\/\/[^/\s?#"'»]+)[^\s"'»]*/gu, '$1/…');
|
|
85
|
+
for (const r of HOME_RES) t = t.replace(r, '~');
|
|
86
|
+
t = t.replace(HOME_ANY_RE, '~');
|
|
87
|
+
if (USER_SEG_RE) t = t.replace(USER_SEG_RE, '<USER>');
|
|
49
88
|
t = t.replace(/\b(token|password|passwd|secret|api[_-]?key|authorization|bearer)("?\s*[:=]\s*|\s+)\S+/giu, '$1$2<SECRET>');
|
|
50
89
|
t = t.replace(/\b(value_after|value|text)("?\s*[:=]?\s*)("[^"]*"|'[^']*'|«[^»]*»)/giu, '$1$2<STR>');
|
|
51
90
|
t = t.replace(/\b(?=[A-Za-z0-9_\-+/]*\d)(?=[A-Za-z0-9_\-+/]*[A-Za-z])[A-Za-z0-9_\-+/=]{24,}\b/gu, '<SECRET>');
|
package/server/protocol.js
CHANGED
package/server/ws-manager.js
CHANGED
|
@@ -42,6 +42,8 @@ export class WSManager {
|
|
|
42
42
|
// arrivava prima che il Chrome appena avviato si collegasse (4 run su 10
|
|
43
43
|
// del benchmark del 27/09/2026), e il modello si arrendeva o indagava.
|
|
44
44
|
this.connectWaitMs = opts.connectWait ?? (Number(process.env.CHROME_BRIDGE_CONNECT_WAIT_MS) || 10000);
|
|
45
|
+
// Launch mode con un browser che non puo' caricare l'estensione: sostituisce il consiglio generico
|
|
46
|
+
this.notConnectedHint = null;
|
|
45
47
|
this.stopped = false;
|
|
46
48
|
this.mode = null; // 'primary' | 'relay'
|
|
47
49
|
this.relayExtConnected = undefined; // relay mode: stato estensione riportato dal primary
|
|
@@ -115,7 +117,7 @@ export class WSManager {
|
|
|
115
117
|
`Chrome extension not connected (server ${this.mode} on ${this.host}:${this.port}`
|
|
116
118
|
+ `${this.mode === 'relay' ? ', reached through another chrome-bridge instance' : ''}`
|
|
117
119
|
+ `, waited ${Math.round(this.connectWaitMs / 1000)}s)`
|
|
118
|
-
+
|
|
120
|
+
+ ` — ${this.notConnectedHint || 'open Chrome, check the chrome-bridge extension is enabled and its port matches'}`,
|
|
119
121
|
);
|
|
120
122
|
}
|
|
121
123
|
return this._send(type, params);
|