refactor: nodecar cleanup

This commit is contained in:
zhom
2025-08-01 00:58:15 +04:00
parent f4c33ad96e
commit da7f791274
10 changed files with 52 additions and 85 deletions
+1
View File
@@ -33,6 +33,7 @@
"devedition",
"doctest",
"doesn",
"domcontentloaded",
"donutbrowser",
"dpkg",
"dtolnay",
+7 -12
View File
@@ -87,14 +87,13 @@ export async function startCamoufoxProcess(
if (line.trim()) {
try {
const parsed = JSON.parse(line.trim());
if (parsed.success && parsed.id === id && parsed.port) {
if (parsed.success && parsed.id === id && parsed.processId) {
if (!resolved) {
resolved = true;
clearTimeout(timeout);
// Update config with server details
config.port = parsed.port;
config.wsEndpoint = parsed.wsEndpoint;
config.processId = parsed.processId;
saveCamoufoxConfig(config);
// Unref immediately after success to detach properly
child.unref();
resolve(config);
@@ -177,25 +176,21 @@ export async function stopCamoufoxProcess(id: string): Promise<boolean> {
}
try {
// Try to find and kill the worker process using multiple methods
const { spawn } = await import("node:child_process");
// Method 1: Kill by process pattern
const killByPattern = spawn("pkill", ["-f", `camoufox-worker.*${id}`], {
stdio: "ignore",
});
// Method 2: If we have a port (which is actually the process PID), kill by PID
if (config.port) {
// Method 2: If we have a process ID, kill by PID
if (config.processId) {
try {
process.kill(config.port, "SIGTERM");
process.kill(config.processId, "SIGTERM");
// Give it a moment to terminate gracefully
await new Promise((resolve) => setTimeout(resolve, 2000));
// Force kill if still running
try {
process.kill(config.port, "SIGKILL");
process.kill(config.processId, "SIGKILL");
} catch {
// Process already terminated
}
+1 -19
View File
@@ -8,8 +8,7 @@ export interface CamoufoxConfig {
options: LaunchOptions;
profilePath?: string;
url?: string;
port?: number;
wsEndpoint?: string;
processId?: number;
}
const STORAGE_DIR = path.join(tmp.tmpdir, "donutbrowser", "camoufox");
@@ -126,23 +125,6 @@ export function updateCamoufoxConfig(config: CamoufoxConfig): boolean {
}
}
/**
* Check if a Camoufox server is running
* @param port The port to check
* @returns True if running, false otherwise
*/
export async function isServerRunning(port: number): Promise<boolean> {
try {
const response = await fetch(`http://localhost:${port}/json/version`, {
method: "GET",
signal: AbortSignal.timeout(1000),
});
return response.ok;
} catch {
return false;
}
}
/**
* Generate a unique ID for a Camoufox instance
* @returns A unique ID string
+9 -17
View File
@@ -27,16 +27,14 @@ export async function runCamoufoxWorker(id: string): Promise<void> {
JSON.stringify({
success: true,
id: id,
port: processId,
wsEndpoint: `ws://localhost:0/camoufox-${id}`,
processId,
profilePath: config.profilePath,
message: "Camoufox worker started successfully",
}),
);
// Update config with process details
config.port = processId;
config.wsEndpoint = `ws://localhost:0/camoufox-${id}`;
config.processId = processId;
saveCamoufoxConfig(config);
// Handle process termination gracefully
@@ -60,11 +58,16 @@ export async function runCamoufoxWorker(id: string): Promise<void> {
camoufoxOptions.user_data_dir = config.profilePath;
}
// Theming
// Remove custom properties before passing to Camoufox
camoufoxOptions.disableTheming = true;
camoufoxOptions.showcursor = false;
// Default to headless for tests
// Set Firefox preferences for theming
if (!camoufoxOptions.firefox_user_prefs) {
camoufoxOptions.firefox_user_prefs = {};
}
// Default to non-headless for visibility
if (camoufoxOptions.headless === undefined) {
camoufoxOptions.headless = false;
}
@@ -72,17 +75,6 @@ export async function runCamoufoxWorker(id: string): Promise<void> {
const browser = await Camoufox(camoufoxOptions);
const context = await browser.newContext();
// Update config with actual browser details
let wsEndpoint: string | undefined;
try {
const browserWithWs = browser as any;
wsEndpoint =
browserWithWs.wsEndpoint?.() || `ws://localhost:0/camoufox-${id}`;
} catch {
wsEndpoint = `ws://localhost:0/camoufox-${id}`;
}
config.wsEndpoint = wsEndpoint;
saveCamoufoxConfig(config);
// Handle URL opening if provided
+3 -4
View File
@@ -396,9 +396,9 @@ program
}
}
// Theming
// Theming and cursor - these are custom properties for camoufox-js
if (options.disableTheming) camoufoxOptions.disableTheming = true;
if (options.noShowcursor) camoufoxOptions.showcursor = false;
if (options.showcursor === false) camoufoxOptions.showcursor = false;
// Use the launcher to start Camoufox properly
const config = await startCamoufoxProcess(
@@ -413,8 +413,7 @@ program
console.log(
JSON.stringify({
id: config.id,
port: config.port,
wsEndpoint: config.wsEndpoint,
processId: config.processId,
profilePath: config.profilePath,
url: config.url,
}),
+5 -5
View File
@@ -239,8 +239,8 @@ impl BrowserRunner {
format!("Failed to launch camoufox via nodecar: {e}").into()
})?;
// For server-based Camoufox, we use the port as a unique identifier (which is actually the PID)
let process_id = camoufox_result.port.unwrap_or(0);
// For server-based Camoufox, we use the process_id
let process_id = camoufox_result.processId.unwrap_or(0);
println!("Camoufox launched successfully with PID: {process_id}");
// Update profile with the process info from camoufox result
@@ -808,7 +808,7 @@ impl BrowserRunner {
Ok(Some(camoufox_process)) => {
println!(
"Found Camoufox process: {} (PID: {:?})",
camoufox_process.id, camoufox_process.port
camoufox_process.id, camoufox_process.processId
);
match camoufox_launcher
@@ -819,12 +819,12 @@ impl BrowserRunner {
if stopped {
println!(
"Successfully stopped Camoufox process: {} (PID: {:?})",
camoufox_process.id, camoufox_process.port
camoufox_process.id, camoufox_process.processId
);
} else {
println!(
"Failed to stop Camoufox process: {} (PID: {:?})",
camoufox_process.id, camoufox_process.port
camoufox_process.id, camoufox_process.processId
);
}
}
+2 -2
View File
@@ -967,8 +967,8 @@ impl BrowserVersionService {
#[cfg(test)]
mod tests {
use super::*;
use wiremock::matchers::{method, path};
use wiremock::{Mock, MockServer, ResponseTemplate};
use wiremock::MockServer;
async fn setup_mock_server() -> MockServer {
MockServer::start().await
+21 -22
View File
@@ -96,9 +96,8 @@ impl Default for CamoufoxConfig {
#[allow(non_snake_case)]
pub struct CamoufoxLaunchResult {
pub id: String,
pub port: Option<u32>,
#[serde(alias = "ws_endpoint")]
pub wsEndpoint: Option<String>,
#[serde(alias = "process_id")]
pub processId: Option<u32>,
#[serde(alias = "profile_path")]
pub profilePath: Option<String>,
pub url: Option<String>,
@@ -108,8 +107,7 @@ pub struct CamoufoxLaunchResult {
struct CamoufoxInstance {
#[allow(dead_code)]
id: String,
port: Option<u32>,
ws_endpoint: Option<String>,
process_id: Option<u32>,
profile_path: Option<String>,
url: Option<String>,
}
@@ -190,6 +188,8 @@ impl CamoufoxNodecarLauncher {
debug: Some(true),
enable_cache: Some(true),
headless: Some(false), // Not headless for testing
disable_theming: Some(true),
showcursor: Some(false),
..Default::default()
}
@@ -422,7 +422,9 @@ impl CamoufoxNodecarLauncher {
}
if let Some(showcursor) = config.showcursor {
if !showcursor {
if showcursor {
args.push("--showcursor".to_string());
} else {
args.push("--no-showcursor".to_string());
}
}
@@ -456,8 +458,7 @@ impl CamoufoxNodecarLauncher {
// Store the instance
let instance = CamoufoxInstance {
id: launch_result.id.clone(),
port: launch_result.port,
ws_endpoint: launch_result.wsEndpoint.clone(),
process_id: launch_result.processId,
profile_path: launch_result.profilePath.clone(),
url: launch_result.url.clone(),
};
@@ -533,13 +534,12 @@ impl CamoufoxNodecarLauncher {
if instance_path == target_path {
// Verify the server is actually running by checking the process
if let Some(port) = instance.port {
if self.is_server_running(port).await {
if let Some(process_id) = instance.process_id {
if self.is_server_running(process_id).await {
println!("Found running Camoufox instance for profile: {profile_path}");
return Ok(Some(CamoufoxLaunchResult {
id: id.clone(),
port: instance.port,
wsEndpoint: instance.ws_endpoint.clone(),
processId: instance.process_id,
profilePath: instance.profile_path.clone(),
url: instance.url.clone(),
}));
@@ -566,16 +566,16 @@ impl CamoufoxNodecarLauncher {
let inner = self.inner.lock().await;
for (id, instance) in inner.instances.iter() {
if let Some(port) = instance.port {
// Check if the process is still alive (port is actually PID)
if !self.is_server_running(port).await {
if let Some(process_id) = instance.process_id {
// Check if the process is still alive
if !self.is_server_running(process_id).await {
// Process is dead
println!("Camoufox instance {id} (PID: {port}) is no longer running");
println!("Camoufox instance {id} (PID: {process_id}) is no longer running");
dead_instances.push(id.clone());
instances_to_remove.push(id.clone());
}
} else {
// No port/PID means it's likely a dead instance
// No process_id means it's likely a dead instance
println!("Camoufox instance {id} has no PID, marking as dead");
dead_instances.push(id.clone());
instances_to_remove.push(id.clone());
@@ -595,14 +595,13 @@ impl CamoufoxNodecarLauncher {
Ok(dead_instances)
}
/// Check if a Camoufox server is running on the given port (which is actually a PID)
async fn is_server_running(&self, port: u32) -> bool {
// For Camoufox, the "port" is actually the process PID
/// Check if a Camoufox server is running with the given process ID
async fn is_server_running(&self, process_id: u32) -> bool {
// Check if the process is still running
use sysinfo::{Pid, System};
let system = System::new_all();
if let Some(process) = system.process(Pid::from(port as usize)) {
if let Some(process) = system.process(Pid::from(process_id as usize)) {
// Check if this is actually a Camoufox process by looking at the command line
let cmd = process.cmd();
let is_camoufox = cmd.iter().any(|arg| {
@@ -611,7 +610,7 @@ impl CamoufoxNodecarLauncher {
});
if is_camoufox {
println!("Found running Camoufox process with PID: {port}");
println!("Found running Camoufox process with PID: {process_id}");
return true;
}
}
+2 -2
View File
@@ -683,7 +683,7 @@ impl ProfileManager {
Ok(Some(camoufox_process)) => {
// Found a running instance, update profile with process info
let mut updated_profile = profile.clone();
updated_profile.process_id = camoufox_process.port;
updated_profile.process_id = camoufox_process.processId;
if let Err(e) = self.save_profile(&updated_profile) {
println!("Warning: Failed to update Camoufox profile with process info: {e}");
}
@@ -695,7 +695,7 @@ impl ProfileManager {
println!(
"Camoufox profile '{}' is running with PID: {:?}",
profile.name, camoufox_process.port
profile.name, camoufox_process.processId
);
Ok(true)
}
+1 -2
View File
@@ -110,8 +110,7 @@ export interface CamoufoxConfig {
export interface CamoufoxLaunchResult {
id: string;
port?: number;
wsEndpoint?: string;
processId?: number;
profilePath?: string;
url?: string;
}