package browser import ( "encoding/json" "fmt" "net/http" "os/exec" "runtime" "time" ) func launchChrome() (string, *exec.Cmd, error) { var chromePath string switch runtime.GOOS { case "darwin": chromePath = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" case "linux": chromePath = "google-chrome" case "windows": chromePath = "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe" default: return "", nil, fmt.Errorf("unsupported OS: %s", runtime.GOOS) } // Kill any existing Chrome debug instances first exec.Command("pkill", "-f", "remote-debugging-port=9222").Run() time.Sleep(500 * time.Millisecond) cmd := exec.Command(chromePath, "--remote-debugging-port=9222", "--no-first-run", "--no-default-browser-check", "--user-data-dir=/tmp/chrome-debug-profile", // Anti-detection flags "--disable-blink-features=AutomationControlled", "--disable-infobars", "--disable-dev-shm-usage", "--disable-browser-side-navigation", "--disable-gpu", "--no-sandbox", "--disable-setuid-sandbox", // Hide automation indicators "--disable-extensions", "--disable-plugins-discovery", "--disable-bundled-ppapi-flash", // Reduce fingerprinting "--disable-background-networking", "--disable-sync", "--disable-translate", "--metrics-recording-only", "--safebrowsing-disable-auto-update", "--disable-client-side-phishing-detection", "--disable-default-apps", "--disable-hang-monitor", "--disable-popup-blocking", "--disable-prompt-on-repost", // Window size to look like a real browser "--window-size=1920,1080", "--start-maximized", ) if err := cmd.Start(); err != nil { return "", nil, fmt.Errorf("failed to start Chrome: %w", err) } // Poll the debug endpoint to get the WebSocket URL var wsURL string for range 50 { time.Sleep(200 * time.Millisecond) resp, err := http.Get("http://127.0.0.1:9222/json/version") if err != nil { continue } var result struct { WebSocketDebuggerURL string `json:"webSocketDebuggerUrl"` } if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { resp.Body.Close() continue } resp.Body.Close() if result.WebSocketDebuggerURL != "" { wsURL = result.WebSocketDebuggerURL break } } if wsURL == "" { cmd.Process.Kill() return "", nil, fmt.Errorf("timeout waiting for Chrome WebSocket URL") } // Wait for Chrome to be fully ready to accept connections time.Sleep(1 * time.Second) return wsURL, cmd, nil }