fix(browser): robust fetch with incomplete-cache cleanup + retry + binary verification
Root cause: camoufox-js@0.11.0 fetch can exit 0 but silently fail to download the browser binary when: 1. A partial/incomplete cache from a previous run exists 2. GitHub API rate-limits the unauthenticated release query 3. Network drops mid-download Fix: 1. run_fetch() now DELETE incomplete cache (dir exists but no version.json) before fetching — prevents 'already cached' no-op 2. After fetch, VERIFY version.json exists — if missing, RETRY once 3. After retry, verify Camoufox.app/Contents/MacOS/camoufox exists 4. Log stdout and stderr in INFO traces for debugging
This commit is contained in:
@@ -243,15 +243,30 @@ pub async fn init_browser(node_path: Option<&str>) -> BrowserInitResult {
|
||||
|
||||
/// Fetch the browser binary using pinned camoufox-js.
|
||||
async fn run_fetch(npx: &str, js_version: &str) -> Result<String, String> {
|
||||
let cache = browser_cache_dir();
|
||||
|
||||
// If cache dir exists but is incomplete, nuke it first
|
||||
// (partial downloads from prior aborted runs can confuse camoufox-js)
|
||||
if cache.exists() && !cache.join("version.json").exists() {
|
||||
info!("Removing incomplete cache at {}", cache.display());
|
||||
let _ = std::fs::remove_dir_all(&cache);
|
||||
info!("Will re-download fresh");
|
||||
}
|
||||
|
||||
let output = Command::new(npx)
|
||||
.args(&["--yes", &format!("camoufox-js@{}", js_version), "fetch"])
|
||||
.args(&["--yes", &format!("camoufox-js@{js_version}"), "fetch"])
|
||||
.output()
|
||||
.await
|
||||
.map_err(|e| format!("'{} camoufox-js' not found: {}", npx, e))?;
|
||||
.map_err(|e| format!("'{npx} camoufox-js' not found: {e}"))?;
|
||||
|
||||
let stdout = String::from_utf8_lossy(&output.stdout).trim().to_string();
|
||||
let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();
|
||||
|
||||
info!("camoufox-js fetch stdout: {}", stdout.lines().last().unwrap_or("(empty)"));
|
||||
if !stderr.is_empty() {
|
||||
info!("camoufox-js fetch stderr: {}", stderr.lines().last().unwrap_or("(empty)"));
|
||||
}
|
||||
|
||||
if !output.status.success() {
|
||||
let detail = if !stderr.is_empty() {
|
||||
stderr.lines().filter(|l| l.contains("Error") || l.contains("error")).last().unwrap_or("(check output)")
|
||||
@@ -261,7 +276,51 @@ async fn run_fetch(npx: &str, js_version: &str) -> Result<String, String> {
|
||||
return Err(format!("exit {}: {}", output.status.code().unwrap_or(-1), detail));
|
||||
}
|
||||
|
||||
Ok(stdout.lines().filter(|l| l.contains("complete") || l.contains("Installed") || l.contains("Downloaded"))
|
||||
// Verify the download actually produced version.json
|
||||
if !cache.join("version.json").exists() {
|
||||
warn!("camoufox-js exited 0 but version.json not found at {}", cache.display());
|
||||
warn!("This usually means the GitHub release download failed. Retrying once...");
|
||||
|
||||
// Retry once with full output visible
|
||||
let retry_output = Command::new(npx)
|
||||
.args(&["--yes", &format!("camoufox-js@{js_version}"), "fetch"])
|
||||
.output()
|
||||
.await
|
||||
.map_err(|e| format!("Retry failed: {e}"))?;
|
||||
|
||||
let retry_stdout = String::from_utf8_lossy(&retry_output.stdout).trim().to_string();
|
||||
let retry_stderr = String::from_utf8_lossy(&retry_output.stderr).trim().to_string();
|
||||
|
||||
info!("Retry stdout: {}", retry_stdout.lines().last().unwrap_or("(empty)"));
|
||||
if !retry_stderr.is_empty() {
|
||||
info!("Retry stderr: {}", retry_stderr.lines().last().unwrap_or("(empty)"));
|
||||
}
|
||||
|
||||
if !retry_output.status.success() || !cache.join("version.json").exists() {
|
||||
return Err(format!(
|
||||
"Download failed after retry.\n stdout: {}\n stderr: {}\n cache: {}\n\nThis is likely a network/GitHub rate-limit issue.\nTry manually:\n export GITHUB_TOKEN=your_token\n {} --yes camoufox-js@{js_version} fetch",
|
||||
retry_stdout.lines().last().unwrap_or("(empty)"),
|
||||
retry_stderr.lines().last().unwrap_or("(empty)"),
|
||||
cache.display(),
|
||||
npx,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Verify the binary exists too
|
||||
let binary_ok = if cfg!(target_os = "macos") {
|
||||
cache.join("Camoufox.app").join("Contents").join("MacOS").join("camoufox").exists()
|
||||
} else {
|
||||
cache.join("camoufox").exists()
|
||||
};
|
||||
|
||||
if !binary_ok {
|
||||
warn!("version.json exists but browser binary missing — possibly corrupted download");
|
||||
}
|
||||
|
||||
Ok(stdout.lines()
|
||||
.chain(stderr.lines())
|
||||
.filter(|l| l.contains("complete") || l.contains("Installed") || l.contains("Downloaded") || l.contains("Camoufox"))
|
||||
.last()
|
||||
.unwrap_or("Fetch completed")
|
||||
.to_string())
|
||||
|
||||
Reference in New Issue
Block a user