fix(baoyu-chrome-cdp): support Chrome 146 native remote debugging (approval mode)
Chrome 146+ blocks all HTTP endpoints (/json/version) in approval mode. Read DevToolsActivePort ws path directly and use TCP port check instead of HTTP discovery. Add WebSocket connect retry loop for approval dialog. Unify findExistingChromeDebugPort to use the same mechanism. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
126b3040e6
commit
d9f9da639d
|
|
@ -45,6 +45,11 @@ type FindExistingChromeDebugPortOptions = {
|
||||||
|
|
||||||
export type ChromeChannel = "stable" | "beta" | "canary" | "dev";
|
export type ChromeChannel = "stable" | "beta" | "canary" | "dev";
|
||||||
|
|
||||||
|
export type DiscoveredChrome = {
|
||||||
|
port: number;
|
||||||
|
wsUrl: string;
|
||||||
|
};
|
||||||
|
|
||||||
type DiscoverRunningChromeOptions = {
|
type DiscoverRunningChromeOptions = {
|
||||||
channels?: ChromeChannel[];
|
channels?: ChromeChannel[];
|
||||||
timeoutMs?: number;
|
timeoutMs?: number;
|
||||||
|
|
@ -180,16 +185,33 @@ async function isDebugPortReady(port: number, timeoutMs = 3_000): Promise<boolea
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isPortListening(port: number, timeoutMs = 3_000): Promise<boolean> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const socket = new net.Socket();
|
||||||
|
const timer = setTimeout(() => { socket.destroy(); resolve(false); }, timeoutMs);
|
||||||
|
socket.once("connect", () => { clearTimeout(timer); socket.destroy(); resolve(true); });
|
||||||
|
socket.once("error", () => { clearTimeout(timer); resolve(false); });
|
||||||
|
socket.connect(port, "127.0.0.1");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseDevToolsActivePort(filePath: string): { port: number; wsPath: string } | null {
|
||||||
|
try {
|
||||||
|
const content = fs.readFileSync(filePath, "utf-8");
|
||||||
|
const lines = content.split(/\r?\n/);
|
||||||
|
const port = Number.parseInt(lines[0]?.trim() ?? "", 10);
|
||||||
|
const wsPath = lines[1]?.trim();
|
||||||
|
if (port > 0 && wsPath) return { port, wsPath };
|
||||||
|
} catch {}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
export async function findExistingChromeDebugPort(options: FindExistingChromeDebugPortOptions): Promise<number | null> {
|
export async function findExistingChromeDebugPort(options: FindExistingChromeDebugPortOptions): Promise<number | null> {
|
||||||
const timeoutMs = options.timeoutMs ?? 3_000;
|
const timeoutMs = options.timeoutMs ?? 3_000;
|
||||||
const portFile = path.join(options.profileDir, "DevToolsActivePort");
|
const parsed = parseDevToolsActivePort(path.join(options.profileDir, "DevToolsActivePort"));
|
||||||
|
|
||||||
try {
|
if (parsed && await isPortListening(parsed.port, timeoutMs)) return parsed.port;
|
||||||
const content = fs.readFileSync(portFile, "utf-8");
|
if (parsed && parsed.port > 0 && await isDebugPortReady(parsed.port, timeoutMs)) return parsed.port;
|
||||||
const [portLine] = content.split(/\r?\n/);
|
|
||||||
const port = Number.parseInt(portLine?.trim() ?? "", 10);
|
|
||||||
if (port > 0 && await isDebugPortReady(port, timeoutMs)) return port;
|
|
||||||
} catch {}
|
|
||||||
|
|
||||||
if (process.platform === "win32") return null;
|
if (process.platform === "win32") return null;
|
||||||
|
|
||||||
|
|
@ -248,19 +270,17 @@ export function getDefaultChromeUserDataDirs(channels: ChromeChannel[] = ["stabl
|
||||||
return dirs;
|
return dirs;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function discoverRunningChromeDebugPort(options: DiscoverRunningChromeOptions = {}): Promise<number | null> {
|
export async function discoverRunningChromeDebugPort(options: DiscoverRunningChromeOptions = {}): Promise<DiscoveredChrome | null> {
|
||||||
const channels = options.channels ?? ["stable", "beta", "canary", "dev"];
|
const channels = options.channels ?? ["stable", "beta", "canary", "dev"];
|
||||||
const timeoutMs = options.timeoutMs ?? 3_000;
|
const timeoutMs = options.timeoutMs ?? 3_000;
|
||||||
|
|
||||||
const userDataDirs = getDefaultChromeUserDataDirs(channels);
|
const userDataDirs = getDefaultChromeUserDataDirs(channels);
|
||||||
for (const dir of userDataDirs) {
|
for (const dir of userDataDirs) {
|
||||||
const portFile = path.join(dir, "DevToolsActivePort");
|
const parsed = parseDevToolsActivePort(path.join(dir, "DevToolsActivePort"));
|
||||||
try {
|
if (!parsed) continue;
|
||||||
const content = fs.readFileSync(portFile, "utf-8");
|
if (await isPortListening(parsed.port, timeoutMs)) {
|
||||||
const [portLine] = content.split(/\r?\n/);
|
return { port: parsed.port, wsUrl: `ws://127.0.0.1:${parsed.port}${parsed.wsPath}` };
|
||||||
const port = Number.parseInt(portLine?.trim() ?? "", 10);
|
}
|
||||||
if (port > 0 && await isDebugPortReady(port, timeoutMs)) return port;
|
|
||||||
} catch {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.platform !== "win32") {
|
if (process.platform !== "win32") {
|
||||||
|
|
@ -274,7 +294,12 @@ export async function discoverRunningChromeDebugPort(options: DiscoverRunningChr
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
const portMatch = line.match(/--remote-debugging-port=(\d+)/);
|
const portMatch = line.match(/--remote-debugging-port=(\d+)/);
|
||||||
const port = Number.parseInt(portMatch?.[1] ?? "", 10);
|
const port = Number.parseInt(portMatch?.[1] ?? "", 10);
|
||||||
if (port > 0 && await isDebugPortReady(port, timeoutMs)) return port;
|
if (port > 0 && await isDebugPortReady(port, timeoutMs)) {
|
||||||
|
try {
|
||||||
|
const version = await fetchJson<{ webSocketDebuggerUrl?: string }>(`http://127.0.0.1:${port}/json/version`, { timeoutMs });
|
||||||
|
if (version.webSocketDebuggerUrl) return { port, wsUrl: version.webSocketDebuggerUrl };
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch {}
|
} catch {}
|
||||||
|
|
|
||||||
|
|
@ -102,17 +102,32 @@ async function fetch_cookies_from_existing_chrome(
|
||||||
timeoutMs: number,
|
timeoutMs: number,
|
||||||
verbose: boolean,
|
verbose: boolean,
|
||||||
): Promise<CookieMap | null> {
|
): Promise<CookieMap | null> {
|
||||||
const port = await discoverRunningChromeDebugPort();
|
const discovered = await discoverRunningChromeDebugPort();
|
||||||
if (port === null) return null;
|
if (discovered === null) return null;
|
||||||
|
|
||||||
if (verbose) logger.info(`Found existing Chrome on port ${port}. Extracting cookies...`);
|
if (verbose) logger.info(`Found existing Chrome on port ${discovered.port}. Connecting via WebSocket...`);
|
||||||
|
|
||||||
let cdp: CdpConnection | null = null;
|
let cdp: CdpConnection | null = null;
|
||||||
let createdTab = false;
|
let createdTab = false;
|
||||||
let targetId: string | null = null;
|
let targetId: string | null = null;
|
||||||
try {
|
try {
|
||||||
const wsUrl = await waitForChromeDebugPort(port, 10_000, { includeLastError: true });
|
const connectStart = Date.now();
|
||||||
cdp = await CdpConnection.connect(wsUrl, 15_000);
|
const connectTimeout = 30_000;
|
||||||
|
let lastConnErr: unknown = null;
|
||||||
|
while (Date.now() - connectStart < connectTimeout) {
|
||||||
|
try {
|
||||||
|
cdp = await CdpConnection.connect(discovered.wsUrl, 5_000);
|
||||||
|
break;
|
||||||
|
} catch (e) {
|
||||||
|
lastConnErr = e;
|
||||||
|
if (verbose) logger.debug(`WebSocket connect attempt failed: ${e instanceof Error ? e.message : String(e)}, retrying...`);
|
||||||
|
await sleep(1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!cdp) {
|
||||||
|
if (verbose) logger.debug(`Could not connect to Chrome after ${connectTimeout / 1000}s: ${lastConnErr instanceof Error ? lastConnErr.message : String(lastConnErr)}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const targets = await cdp.send<{ targetInfos: Array<{ targetId: string; url: string; type: string }> }>('Target.getTargets');
|
const targets = await cdp.send<{ targetInfos: Array<{ targetId: string; url: string; type: string }> }>('Target.getTargets');
|
||||||
const hasGeminiTab = targets.targetInfos.some(
|
const hasGeminiTab = targets.targetInfos.some(
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,11 @@ type FindExistingChromeDebugPortOptions = {
|
||||||
|
|
||||||
export type ChromeChannel = "stable" | "beta" | "canary" | "dev";
|
export type ChromeChannel = "stable" | "beta" | "canary" | "dev";
|
||||||
|
|
||||||
|
export type DiscoveredChrome = {
|
||||||
|
port: number;
|
||||||
|
wsUrl: string;
|
||||||
|
};
|
||||||
|
|
||||||
type DiscoverRunningChromeOptions = {
|
type DiscoverRunningChromeOptions = {
|
||||||
channels?: ChromeChannel[];
|
channels?: ChromeChannel[];
|
||||||
timeoutMs?: number;
|
timeoutMs?: number;
|
||||||
|
|
@ -180,16 +185,33 @@ async function isDebugPortReady(port: number, timeoutMs = 3_000): Promise<boolea
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isPortListening(port: number, timeoutMs = 3_000): Promise<boolean> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const socket = new net.Socket();
|
||||||
|
const timer = setTimeout(() => { socket.destroy(); resolve(false); }, timeoutMs);
|
||||||
|
socket.once("connect", () => { clearTimeout(timer); socket.destroy(); resolve(true); });
|
||||||
|
socket.once("error", () => { clearTimeout(timer); resolve(false); });
|
||||||
|
socket.connect(port, "127.0.0.1");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseDevToolsActivePort(filePath: string): { port: number; wsPath: string } | null {
|
||||||
|
try {
|
||||||
|
const content = fs.readFileSync(filePath, "utf-8");
|
||||||
|
const lines = content.split(/\r?\n/);
|
||||||
|
const port = Number.parseInt(lines[0]?.trim() ?? "", 10);
|
||||||
|
const wsPath = lines[1]?.trim();
|
||||||
|
if (port > 0 && wsPath) return { port, wsPath };
|
||||||
|
} catch {}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
export async function findExistingChromeDebugPort(options: FindExistingChromeDebugPortOptions): Promise<number | null> {
|
export async function findExistingChromeDebugPort(options: FindExistingChromeDebugPortOptions): Promise<number | null> {
|
||||||
const timeoutMs = options.timeoutMs ?? 3_000;
|
const timeoutMs = options.timeoutMs ?? 3_000;
|
||||||
const portFile = path.join(options.profileDir, "DevToolsActivePort");
|
const parsed = parseDevToolsActivePort(path.join(options.profileDir, "DevToolsActivePort"));
|
||||||
|
|
||||||
try {
|
if (parsed && await isPortListening(parsed.port, timeoutMs)) return parsed.port;
|
||||||
const content = fs.readFileSync(portFile, "utf-8");
|
if (parsed && parsed.port > 0 && await isDebugPortReady(parsed.port, timeoutMs)) return parsed.port;
|
||||||
const [portLine] = content.split(/\r?\n/);
|
|
||||||
const port = Number.parseInt(portLine?.trim() ?? "", 10);
|
|
||||||
if (port > 0 && await isDebugPortReady(port, timeoutMs)) return port;
|
|
||||||
} catch {}
|
|
||||||
|
|
||||||
if (process.platform === "win32") return null;
|
if (process.platform === "win32") return null;
|
||||||
|
|
||||||
|
|
@ -248,19 +270,17 @@ export function getDefaultChromeUserDataDirs(channels: ChromeChannel[] = ["stabl
|
||||||
return dirs;
|
return dirs;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function discoverRunningChromeDebugPort(options: DiscoverRunningChromeOptions = {}): Promise<number | null> {
|
export async function discoverRunningChromeDebugPort(options: DiscoverRunningChromeOptions = {}): Promise<DiscoveredChrome | null> {
|
||||||
const channels = options.channels ?? ["stable", "beta", "canary", "dev"];
|
const channels = options.channels ?? ["stable", "beta", "canary", "dev"];
|
||||||
const timeoutMs = options.timeoutMs ?? 3_000;
|
const timeoutMs = options.timeoutMs ?? 3_000;
|
||||||
|
|
||||||
const userDataDirs = getDefaultChromeUserDataDirs(channels);
|
const userDataDirs = getDefaultChromeUserDataDirs(channels);
|
||||||
for (const dir of userDataDirs) {
|
for (const dir of userDataDirs) {
|
||||||
const portFile = path.join(dir, "DevToolsActivePort");
|
const parsed = parseDevToolsActivePort(path.join(dir, "DevToolsActivePort"));
|
||||||
try {
|
if (!parsed) continue;
|
||||||
const content = fs.readFileSync(portFile, "utf-8");
|
if (await isPortListening(parsed.port, timeoutMs)) {
|
||||||
const [portLine] = content.split(/\r?\n/);
|
return { port: parsed.port, wsUrl: `ws://127.0.0.1:${parsed.port}${parsed.wsPath}` };
|
||||||
const port = Number.parseInt(portLine?.trim() ?? "", 10);
|
}
|
||||||
if (port > 0 && await isDebugPortReady(port, timeoutMs)) return port;
|
|
||||||
} catch {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.platform !== "win32") {
|
if (process.platform !== "win32") {
|
||||||
|
|
@ -274,7 +294,12 @@ export async function discoverRunningChromeDebugPort(options: DiscoverRunningChr
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
const portMatch = line.match(/--remote-debugging-port=(\d+)/);
|
const portMatch = line.match(/--remote-debugging-port=(\d+)/);
|
||||||
const port = Number.parseInt(portMatch?.[1] ?? "", 10);
|
const port = Number.parseInt(portMatch?.[1] ?? "", 10);
|
||||||
if (port > 0 && await isDebugPortReady(port, timeoutMs)) return port;
|
if (port > 0 && await isDebugPortReady(port, timeoutMs)) {
|
||||||
|
try {
|
||||||
|
const version = await fetchJson<{ webSocketDebuggerUrl?: string }>(`http://127.0.0.1:${port}/json/version`, { timeoutMs });
|
||||||
|
if (version.webSocketDebuggerUrl) return { port, wsUrl: version.webSocketDebuggerUrl };
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch {}
|
} catch {}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue