diff --git a/nodecar/src/camoufox-launcher.ts b/nodecar/src/camoufox-launcher.ts index 3618140..3a20c6a 100644 --- a/nodecar/src/camoufox-launcher.ts +++ b/nodecar/src/camoufox-launcher.ts @@ -123,11 +123,16 @@ export async function startCamoufoxProcess( // Generate a unique ID for this instance const id = generateCamoufoxId(); + // Ensure profile path is absolute if provided + const absoluteProfilePath = profilePath + ? path.resolve(profilePath) + : undefined; + // Create the Camoufox configuration const config: CamoufoxConfig = { id, - options, - profilePath, + options: JSON.parse(JSON.stringify(options)), // Deep clone to avoid reference sharing + profilePath: absoluteProfilePath, url, customConfig, }; diff --git a/nodecar/src/camoufox-storage.ts b/nodecar/src/camoufox-storage.ts index 931f50c..886f88d 100644 --- a/nodecar/src/camoufox-storage.ts +++ b/nodecar/src/camoufox-storage.ts @@ -106,6 +106,7 @@ export function listCamoufoxConfigs(): CamoufoxConfig[] { .filter((config): config is CamoufoxConfig => config !== null) .map((config) => { config.options = "Removed for logging" as any; + config.customConfig = "Removed for logging" as any; return config; }); } catch (error) { @@ -147,5 +148,6 @@ export function updateCamoufoxConfig(config: CamoufoxConfig): boolean { * @returns A unique ID string */ export function generateCamoufoxId(): string { - return `camoufox_${Date.now()}_${Math.floor(Math.random() * 10000)}`; + // Include process ID to ensure uniqueness across multiple processes + return `camoufox_${Date.now()}_${process.pid}_${Math.floor(Math.random() * 10000)}`; } diff --git a/nodecar/src/camoufox-worker.ts b/nodecar/src/camoufox-worker.ts index b3085fd..7dd3d64 100644 --- a/nodecar/src/camoufox-worker.ts +++ b/nodecar/src/camoufox-worker.ts @@ -74,14 +74,17 @@ export async function runCamoufoxWorker(id: string): Promise { process.on("unhandledRejection", () => void gracefulShutdown()); try { - // Prepare options for Camoufox - const camoufoxOptions: LaunchOptions = { ...config.options }; + // Deep clone to avoid reference sharing and ensure fresh configuration for each instance + const camoufoxOptions: LaunchOptions = JSON.parse( + JSON.stringify(config.options || {}), + ); // Add profile path if provided if (config.profilePath) { camoufoxOptions.user_data_dir = config.profilePath; } + // Ensure block options are properly set if (camoufoxOptions.block_images) { camoufoxOptions.block_images = true; } @@ -99,7 +102,7 @@ export async function runCamoufoxWorker(id: string): Promise { camoufoxOptions.headless = true; } - // Always set these defaults + // Always set these defaults - ensure they are applied for each instance camoufoxOptions.i_know_what_im_doing = true; camoufoxOptions.config = { disableTheming: true, @@ -107,12 +110,18 @@ export async function runCamoufoxWorker(id: string): Promise { ...(camoufoxOptions.config || {}), }; - // Generate the configuration using launchOptions + // Generate fresh options for this specific instance const generatedOptions = await launchOptions(camoufoxOptions); - // If we have a custom config from Rust, use it directly as environment variables - let finalEnv = generatedOptions.env || {}; + // Start with process environment to ensure proper inheritance + let finalEnv = { ...process.env }; + // Add generated options environment variables + if (generatedOptions.env) { + finalEnv = { ...finalEnv, ...generatedOptions.env }; + } + + // If we have a custom config from Rust, use it directly as environment variables if (config.customConfig) { try { // Parse the custom config JSON string @@ -125,16 +134,16 @@ export async function runCamoufoxWorker(id: string): Promise { finalEnv = { ...finalEnv, ...customEnvVars }; } catch (error) { console.error( - "Failed to parse custom config, using generated config:", + `Camoufox worker ${id}: Failed to parse custom config, using generated config:`, error, ); return; } } - - // Launch the server with the final configuration + // Launch the server with the final configuration - ensure unique wsPath for each instance const finalOptions: any = { ...generatedOptions, + user_data_dir: config.profilePath, wsPath: `/ws_${config.id}`, env: finalEnv, }; diff --git a/nodecar/src/utils.ts b/nodecar/src/utils.ts index b81afa0..f29df5f 100644 --- a/nodecar/src/utils.ts +++ b/nodecar/src/utils.ts @@ -10,12 +10,14 @@ const OS_NAME: "mac" | "win" | "lin" = OS_MAP[process.platform]; export function getEnvVars(configMap: Record) { const envVars: { - [key: string]: string | number | boolean; + [key: string]: string | undefined; } = {}; let updatedConfigData: Uint8Array; try { - updatedConfigData = new TextEncoder().encode(JSON.stringify(configMap)); + // Ensure we're working with a fresh copy of the config + const configCopy = JSON.parse(JSON.stringify(configMap)); + updatedConfigData = new TextEncoder().encode(JSON.stringify(configCopy)); } catch (e) { console.error(`Error updating config: ${e}`); process.exit(1); diff --git a/src-tauri/src/camoufox.rs b/src-tauri/src/camoufox.rs index 294226c..a6a9956 100644 --- a/src-tauri/src/camoufox.rs +++ b/src-tauri/src/camoufox.rs @@ -212,8 +212,13 @@ impl CamoufoxNodecarLauncher { // Build nodecar command arguments let mut args = vec!["camoufox".to_string(), "start".to_string()]; - // Add profile path - args.extend(["--profile-path".to_string(), profile_path.to_string()]); + // Add profile path - ensure it's an absolute path + let absolute_profile_path = std::path::Path::new(profile_path) + .canonicalize() + .unwrap_or_else(|_| std::path::Path::new(profile_path).to_path_buf()) + .to_string_lossy() + .to_string(); + args.extend(["--profile-path".to_string(), absolute_profile_path]); // Add URL if provided if let Some(url) = url {