import { spawn, ChildProcess } from "child_process";
export interface CDPChromeOptions {
headless: boolean;
port: number;
userDataDir?: string;
}
export interface ChromeProcess {
endpointURL: string;
port: number;
proc: ChildProcess;
}
class CDPManager {
private chromeProcess: ChromeProcess | null = null;
async startChrome(options: CDPChromeOptions): Promise<ChromeProcess> {
if (this.chromeProcess) {
await this.shutdown();
}
const args = [
`--remote-debugging-port=${options.port}`,
"--no-first-run",
"--no-default-browser-check",
"--disable-extensions",
"--disable-default-apps",
"--disable-sync",
"--metrics-recording-only",
"--mute-audio",
"--no-zygote",
"--single-process",
"--disable-gpu",
...(options.headless ? ["--headless=new"] : [])
];
if (options.userDataDir) {
args.push(`--user-data-dir=${options.userDataDir}`);
}
const proc = spawn("google-chrome", args, {
detached: true,
stdio: "ignore"
});
await new Promise<void>((resolve) => {
setTimeout(() => resolve(), 2000);
});
this.chromeProcess = {
endpointURL: `http://localhost:${options.port}`,
port: options.port,
proc
};
return this.chromeProcess;
}
async isHealthy(): Promise<boolean> {
if (!this.chromeProcess) return false;
try {
const response = await fetch(this.chromeProcess.endpointURL + "/json/version");
return response.ok;
} catch {
return false;
}
}
async shutdown(): Promise<void> {
if (this.chromeProcess) {
try {
this.chromeProcess.proc.kill();
} catch { /* ignore kill errors */ }
this.chromeProcess = null;
}
}
get chrome(): ChromeProcess | null {
return this.chromeProcess;
}
async attach() {
if (!this.chromeProcess) {
throw new Error("Chrome not running");
}
const { chromium } = await import("playwright-core");
return await chromium.connectOverCDP(this.chromeProcess.endpointURL);
}
}
export const cdpManager = new CDPManager();