use std::path::{Path, PathBuf};
#[derive(Debug, Clone)]
pub enum LaunchSpec {
Launch {
headless: bool,
from_profile: Option<PathBuf>,
clone_state_from: Option<String>,
no_stealth: bool,
proxy: Option<String>,
},
Connect { ws_url: String },
}
pub fn resolve_proxy_spec(
proxy: Option<&str>,
proxy_file: Option<&Path>,
) -> Result<Option<String>, String> {
if let Some(proxy) = proxy {
return Ok(Some(proxy.to_owned()));
}
let Some(path) = proxy_file else {
return Ok(None);
};
let contents = std::fs::read_to_string(path)
.map_err(|e| format!("Failed to read --proxy-file '{}': {}", path.display(), e))?;
let lines: Vec<&str> = contents
.lines()
.map(str::trim)
.filter(|line| !line.is_empty() && !line.starts_with('#'))
.collect();
if lines.is_empty() {
return Err(format!(
"--proxy-file '{}' does not contain a proxy",
path.display()
));
}
use std::time::{SystemTime, UNIX_EPOCH};
let idx = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_nanos() as usize % lines.len())
.unwrap_or(0);
Ok(Some(lines[idx].to_owned()))
}
impl LaunchSpec {
pub fn is_live(&self) -> bool {
matches!(self, LaunchSpec::Connect { .. })
}
}
pub fn resolve_cdp_spec(spec: &str) -> Result<String, String> {
let trimmed = spec.trim();
if trimmed.eq_ignore_ascii_case("auto") {
return auto_connect().map(|(_, url)| url);
}
if trimmed.starts_with("ws://") || trimmed.starts_with("wss://") {
return Ok(trimmed.to_string());
}
let port: u16 = trimmed.parse().map_err(|_| {
format!(
"Invalid --cdp spec '{}': expected port, ws:// URL, or 'auto'",
spec
)
})?;
eoka::cdp::discover::discover_browser_ws("127.0.0.1", port)
.map_err(|e| format!("Failed to discover Chrome on 127.0.0.1:{}: {}", port, e))
}
pub fn auto_connect() -> Result<(u16, String), String> {
eoka::cdp::discover::auto_connect(9222, 9229)
.ok_or_else(|| "No running Chrome found on ports 9222-9229".to_string())
}
pub fn session_suffix(spec: &LaunchSpec) -> &'static str {
match spec {
LaunchSpec::Launch { headless: true, .. } => "",
LaunchSpec::Launch {
headless: false, ..
} => "-headed",
LaunchSpec::Connect { .. } => "-live",
}
}