Complete rewrite removing old HTTP REST client. The latest camoufox-browser npm package (v0.1.0) uses Unix socket JSON-line IPC, not HTTP REST on :9377. Changes: - gyandoot-browser/client.rs: Full daemon IPC via Unix socket (~/.camoufox-browser/*.sock) - JSON-line protocol matching ensureDaemonRunning + daemon.js - Auto-launches browser on first command (daemon manages lifecycle) - Supports navigate, click, type, scroll, snapshot, search macros, tab management - Proper HeathResponse, NavigationResult, PageSnapshot types - gyandoot-browser/lib.rs: Exports new client API (no SearchMacro enum, no SessionManager) - gyandoot-browser/Cargo.toml: reqwest -> tokio Unix socket deps - Removed: macros.rs, session.rs, snapshot.rs (all merged into client.rs) - browser_manager.rs: Triggers daemon via npx, waits for Unix socket - browser_tools.rs: Simplified API (no tab_id, no create_tab/close_tab) Fix: camoufox-browser process exited with status 1 error on fresh install
145 lines
5.4 KiB
Rust
145 lines
5.4 KiB
Rust
//! BrowserManager — Start, connect, and manage lifecycle of the camoufox-browser daemon.
|
|
//!
|
|
//! The `camoufox-browser@0.1.0` CLI auto-starts a background daemon
|
|
//! (Node.js process) on any command. The daemon listens on a Unix socket at
|
|
//! `~/.camoufox-browser/<session>.sock` and auto-launches the camoufox browser
|
|
//! on the first browser-dependent command.
|
|
//!
|
|
//! On `gyandoot run`, the BrowserManager:
|
|
//! 1. Checks if the daemon socket already exists and is connected
|
|
//! 2. If not, runs a quick CLI command to trigger daemon auto-start
|
|
//! 3. Waits for the Unix socket to appear (up to 30 seconds)
|
|
//! 4. Returns a ready-to-use `BrowserClient`
|
|
|
|
use std::process::Stdio;
|
|
use std::time::Duration;
|
|
|
|
use tokio::process::Command;
|
|
use tracing::{info, warn};
|
|
|
|
use gyandoot_browser::{check_daemon_socket, default_socket_path, BrowserClient};
|
|
|
|
/// Default session name.
|
|
const DEFAULT_SESSION: &str = "default";
|
|
|
|
/// Maximum time to wait for the daemon socket to appear.
|
|
const DAEMON_STARTUP_TIMEOUT_SECS: u64 = 30;
|
|
|
|
/// Poll interval while waiting for daemon to start.
|
|
const DAEMON_POLL_MS: u64 = 500;
|
|
|
|
/// Result of browser initialization.
|
|
#[derive(Debug)]
|
|
pub enum BrowserInitResult {
|
|
/// Browser daemon is running and client is ready.
|
|
Ready(BrowserClient),
|
|
/// Could not start daemon.
|
|
Unavailable(String),
|
|
}
|
|
|
|
/// Initialize the camoufox-browser daemon and return a ready client.
|
|
pub async fn init_browser() -> BrowserInitResult {
|
|
let session = DEFAULT_SESSION;
|
|
let socket_path = default_socket_path(session);
|
|
|
|
// 1. Quick check — is the daemon already running?
|
|
if check_daemon_socket(session).await {
|
|
info!("Camoufox daemon already running on socket: {}", socket_path.display());
|
|
let client = BrowserClient::new().with_session(session);
|
|
return BrowserInitResult::Ready(client);
|
|
}
|
|
|
|
info!("Camoufox daemon not running, triggering startup...");
|
|
|
|
// 2. Trigger daemon auto-start via CLI
|
|
// The CLI calls `ensureDaemonRunning()` internally which spawns the daemon
|
|
match trigger_daemon_start(session).await {
|
|
Ok(()) => {
|
|
// 3. Wait for socket to appear
|
|
info!("Waiting for daemon socket at {}...", socket_path.display());
|
|
match wait_for_socket(session).await {
|
|
Ok(true) => {
|
|
info!("Camoufox daemon started successfully");
|
|
let client = BrowserClient::new().with_session(session);
|
|
// Brief pause for daemon to fully initialize
|
|
tokio::time::sleep(Duration::from_millis(1000)).await;
|
|
BrowserInitResult::Ready(client)
|
|
}
|
|
Ok(false) => {
|
|
let msg = format!(
|
|
"Daemon did not start within {}s — socket not found at {}",
|
|
DAEMON_STARTUP_TIMEOUT_SECS,
|
|
socket_path.display()
|
|
);
|
|
warn!("{}", msg);
|
|
BrowserInitResult::Unavailable(msg)
|
|
}
|
|
Err(e) => {
|
|
let msg = format!("Daemon socket error: {}", e);
|
|
warn!("{}", msg);
|
|
BrowserInitResult::Unavailable(msg)
|
|
}
|
|
}
|
|
}
|
|
Err(e) => {
|
|
let msg = format!(
|
|
"Could not start camoufox daemon: {}\n\n\
|
|
Make sure Node.js v20+ and the package are installed:\n \
|
|
npm install -g camoufox-browser\n \
|
|
npx camoufox-browser@latest install\n\n\
|
|
Then try again.",
|
|
e
|
|
);
|
|
warn!("{}", msg);
|
|
BrowserInitResult::Unavailable(msg)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Trigger daemon startup by running a quick CLI command.
|
|
///
|
|
/// The `camoufox-browser@latest` CLI auto-starts the daemon via
|
|
/// `ensureDaemonRunning()` on any command. We run a harmless
|
|
/// `tab_list` which spawns the daemon in the background and
|
|
/// returns once complete.
|
|
async fn trigger_daemon_start(session: &str) -> Result<(), String> {
|
|
// Create log directory for any stderr output
|
|
let home = dirs::home_dir().ok_or("Cannot determine home directory")?;
|
|
let log_dir = home.join(".gyandoot");
|
|
std::fs::create_dir_all(&log_dir)
|
|
.map_err(|e| format!("Cannot create {}: {}", log_dir.display(), e))?;
|
|
let log_file = log_dir.join("camoufox-daemon-spawn.log");
|
|
|
|
let log = std::fs::File::create(&log_file)
|
|
.map_err(|e| format!("Cannot create log file: {}", e))?;
|
|
|
|
// Run a command — the CLI auto-starts the daemon as a side effect
|
|
let _output = Command::new("npx")
|
|
.args(&[
|
|
"camoufox-browser@latest",
|
|
"tab_list",
|
|
"--session", session,
|
|
])
|
|
.stdout(Stdio::from(log.try_clone().map_err(|e| e.to_string())?))
|
|
.stderr(Stdio::from(log))
|
|
.output()
|
|
.await
|
|
.map_err(|e| format!("npx failed: {}. Is Node.js installed?", e))?;
|
|
|
|
// Return immediately — socket polling in the caller will detect the daemon
|
|
Ok(())
|
|
}
|
|
|
|
/// Poll for the daemon Unix socket to appear.
|
|
async fn wait_for_socket(session: &str) -> Result<bool, String> {
|
|
let deadline = tokio::time::Instant::now() + Duration::from_secs(DAEMON_STARTUP_TIMEOUT_SECS);
|
|
|
|
while tokio::time::Instant::now() < deadline {
|
|
if check_daemon_socket(session).await {
|
|
return Ok(true);
|
|
}
|
|
tokio::time::sleep(Duration::from_millis(DAEMON_POLL_MS)).await;
|
|
}
|
|
|
|
Ok(false)
|
|
} |