use chromiumoxide::browser::{Browser, BrowserConfig};
use futures_util::stream::StreamExt;
use std::net::SocketAddr;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpListener;
fn acquire_browser_launch_lock() {
let lock_path = std::path::Path::new("/tmp/captchaforge-browser.lock");
let max_attempts = 600; for _ in 0..max_attempts {
match std::fs::OpenOptions::new()
.write(true)
.create_new(true)
.open(lock_path)
{
Ok(file) => {
let _ = file;
return;
}
Err(_) => {
std::thread::sleep(std::time::Duration::from_millis(100));
}
}
}
panic!("Timed out waiting for browser launch lock");
}
fn release_browser_launch_lock() {
let _ = std::fs::remove_file("/tmp/captchaforge-browser.lock");
}
pub async fn launch_browser() -> Browser {
acquire_browser_launch_lock();
let _ = std::fs::remove_dir_all("/tmp/chromiumoxide-runner");
let temp_dir = std::env::temp_dir().join(format!(
"captchaforge-test-{}-{}",
std::process::id(),
rand::random::<u32>()
));
let config = BrowserConfig::builder()
.chrome_executable(
std::env::var("CHROME_BIN")
.ok()
.or_else(|| {
which::which("google-chrome")
.ok()
.map(|p| p.to_string_lossy().to_string())
})
.or_else(|| {
which::which("chromium")
.ok()
.map(|p| p.to_string_lossy().to_string())
})
.expect("Chrome/Chromium not found. Set CHROME_BIN or install Chrome."),
)
.arg("--no-sandbox")
.arg("--disable-setuid-sandbox")
.arg("--disable-dev-shm-usage")
.arg("--disable-gpu")
.arg("--single-process")
.arg("--no-zygote")
.arg(format!("--user-data-dir={}", temp_dir.display()))
.build()
.unwrap();
let result = Browser::launch(config).await;
release_browser_launch_lock();
let (browser, mut handler) = result.unwrap();
tokio::spawn(async move { while let Some(_evt) = handler.next().await {} });
browser
}
#[allow(dead_code)]
pub async fn serve_once(html: String) -> SocketAddr {
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
let html_clone = html.clone();
tokio::spawn(async move {
let mut served = 0;
let deadline = tokio::time::Instant::now() + std::time::Duration::from_secs(30);
while served < 2 && tokio::time::Instant::now() < deadline {
let timeout =
tokio::time::timeout(std::time::Duration::from_millis(500), listener.accept());
match timeout.await {
Ok(Ok((mut stream, _))) => {
let mut buf = [0u8; 1024];
let _ = stream.read(&mut buf).await;
let req = String::from_utf8_lossy(&buf);
let response = if req.starts_with("GET / ") || req.starts_with("GET / HTTP") {
served += 1;
format!(
"HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}",
html_clone.len(),
html_clone
)
} else {
"HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\nConnection: close\r\n\r\n"
.to_string()
};
let _ = stream.write_all(response.as_bytes()).await;
}
_ => continue,
}
}
});
addr
}