diff --git a/.vscode/settings.json b/.vscode/settings.json index d648f1f..f686b55 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -99,6 +99,7 @@ "orhun", "orjson", "osascript", + "oscpu", "outpath", "pathex", "pathlib", diff --git a/nodecar/package.json b/nodecar/package.json index c1d8317..50ab9e3 100644 --- a/nodecar/package.json +++ b/nodecar/package.json @@ -23,8 +23,8 @@ "license": "AGPL-3.0", "dependencies": { "@types/node": "^24.1.0", - "camoufox-js": "^0.6.2", "commander": "^14.0.0", + "donutbrowser-camoufox-js": "^0.6.2", "dotenv": "^17.2.1", "fingerprint-generator": "^2.1.69", "get-port": "^7.1.0", diff --git a/nodecar/src/camoufox-launcher.ts b/nodecar/src/camoufox-launcher.ts index 8a62f7e..72dc555 100644 --- a/nodecar/src/camoufox-launcher.ts +++ b/nodecar/src/camoufox-launcher.ts @@ -1,7 +1,7 @@ import { spawn } from "node:child_process"; import path from "node:path"; -import { launchOptions } from "camoufox-js"; -import type { LaunchOptions } from "camoufox-js/dist/utils.js"; +import { launchOptions } from "donutbrowser-camoufox-js"; +import type { LaunchOptions } from "donutbrowser-camoufox-js/dist/utils.js"; import { type CamoufoxConfig, deleteCamoufoxConfig, @@ -196,7 +196,7 @@ function convertCamoufoxToFingerprintGenerator( fingerprintObj.fonts = camoufoxFingerprint.fonts; } - return fingerprintObj; + return { ...camoufoxFingerprint, ...fingerprintObj }; } /** @@ -465,7 +465,7 @@ export async function generateCamoufoxConfig( options: GenerateConfigOptions, ): Promise { try { - const launchOpts: LaunchOptions = { + const launchOpts: any = { headless: false, i_know_what_im_doing: true, config: { @@ -474,8 +474,8 @@ export async function generateCamoufoxConfig( }, }; - if (options.geoip !== undefined) { - launchOpts.geoip = options.geoip; + if (options.geoip) { + launchOpts.geoip = true; } if (options.blockImages) { @@ -492,11 +492,19 @@ export async function generateCamoufoxConfig( launchOpts.executable_path = options.executablePath; } + if (options.proxy) { + launchOpts.proxy = options.proxy; + } + // If fingerprint is provided, use it and ignore other options except executable_path and block_* if (options.fingerprint) { try { const camoufoxFingerprint = JSON.parse(options.fingerprint); + if (camoufoxFingerprint.timezone) { + launchOpts.config.timezone = camoufoxFingerprint.timezone; + } + // Convert camoufox fingerprint format to fingerprint-generator format const fingerprintObj = convertCamoufoxToFingerprintGenerator(camoufoxFingerprint); @@ -506,9 +514,6 @@ export async function generateCamoufoxConfig( } } else { // Use individual options to build configuration - if (options.proxy) { - launchOpts.proxy = options.proxy; - } if (options.maxWidth && options.maxHeight) { launchOpts.screen = { diff --git a/nodecar/src/camoufox-storage.ts b/nodecar/src/camoufox-storage.ts index 669b9f2..cb06f4f 100644 --- a/nodecar/src/camoufox-storage.ts +++ b/nodecar/src/camoufox-storage.ts @@ -1,6 +1,6 @@ import fs from "node:fs"; import path from "node:path"; -import type { LaunchOptions } from "camoufox-js/dist/utils.js"; +import type { LaunchOptions } from "donutbrowser-camoufox-js/dist/utils.js"; import tmp from "tmp"; export interface CamoufoxConfig { diff --git a/nodecar/src/camoufox-worker.ts b/nodecar/src/camoufox-worker.ts index c307e68..b3085fd 100644 --- a/nodecar/src/camoufox-worker.ts +++ b/nodecar/src/camoufox-worker.ts @@ -1,8 +1,8 @@ -import { launchOptions } from "camoufox-js"; -import type { LaunchOptions } from "camoufox-js/dist/utils.js"; +import { launchOptions } from "donutbrowser-camoufox-js"; +import type { LaunchOptions } from "donutbrowser-camoufox-js/dist/utils.js"; import { type Browser, type BrowserServer, firefox } from "playwright-core"; import { getCamoufoxConfig, saveCamoufoxConfig } from "./camoufox-storage.js"; -import { getEnvVars } from "./utils.js"; +import { getEnvVars, parseProxyString } from "./utils.js"; /** * Run a Camoufox browser server as a worker process @@ -128,15 +128,31 @@ export async function runCamoufoxWorker(id: string): Promise { "Failed to parse custom config, using generated config:", error, ); + return; } } // Launch the server with the final configuration - server = await firefox.launchServer({ + const finalOptions: any = { ...generatedOptions, wsPath: `/ws_${config.id}`, env: finalEnv, - }); + }; + + // Only add proxy if it exists and is valid + if (camoufoxOptions.proxy) { + try { + finalOptions.proxy = parseProxyString(camoufoxOptions.proxy); + } catch (error) { + console.error({ + message: "Failed to parse proxy, launching without proxy", + error, + }); + return; + } + } + + server = await firefox.launchServer(finalOptions); // Connect to the server browser = await firefox.connect(server.wsEndpoint()); diff --git a/nodecar/src/index.ts b/nodecar/src/index.ts index f9fffb4..96e9fff 100644 --- a/nodecar/src/index.ts +++ b/nodecar/src/index.ts @@ -1,5 +1,5 @@ -import type { LaunchOptions } from "camoufox-js/dist/utils.js"; import { program } from "commander"; +import type { LaunchOptions } from "donutbrowser-camoufox-js/dist/utils.js"; import { generateCamoufoxConfig, startCamoufoxProcess, @@ -165,7 +165,7 @@ program .option("--proxy ", "proxy URL for config generation") .option("--max-width ", "maximum screen width", parseInt) .option("--max-height ", "maximum screen height", parseInt) - .option("--geoip [ip]", "enable geoip or specify IP") + .option("--geoip", "enable geoip") .option("--block-images", "block images") .option("--block-webrtc", "block WebRTC") .option("--block-webgl", "block WebGL") @@ -325,7 +325,6 @@ program }), ); process.exit(1); - return; } } @@ -374,22 +373,6 @@ program process.exit(0); } else if (action === "generate-config") { try { - // Handle geoip option properly - let geoipValue: string | boolean = true; // Default to true - if (options.geoip !== undefined) { - if (typeof options.geoip === "boolean") { - geoipValue = options.geoip; - } else if (typeof options.geoip === "string") { - if (options.geoip === "true") { - geoipValue = true; - } else if (options.geoip === "false") { - geoipValue = false; - } else { - geoipValue = options.geoip; // IP address - } - } - } - const config = await generateCamoufoxConfig({ proxy: typeof options.proxy === "string" ? options.proxy : undefined, @@ -401,7 +384,7 @@ program typeof options.maxHeight === "number" ? options.maxHeight : undefined, - geoip: geoipValue, + geoip: Boolean(options.geoip), blockImages: typeof options.blockImages === "boolean" ? options.blockImages @@ -426,17 +409,18 @@ program console.log(config); process.exit(0); } catch (error: unknown) { - console.error( - `Failed to generate config: ${ - error instanceof Error ? error.message : JSON.stringify(error) - }`, - ); + console.error({ + error: "Failed to generate config", + message: + error instanceof Error ? error.message : JSON.stringify(error), + }); process.exit(1); } } else { - console.error( - "Invalid action. Use 'start', 'stop', 'list', or 'generate-config'", - ); + console.error({ + error: "Invalid action", + message: "Use 'start', 'stop', 'list', or 'generate-config'", + }); process.exit(1); } }, @@ -452,7 +436,10 @@ program if (action === "start") { await runCamoufoxWorker(options.id); } else { - console.error("Invalid action for camoufox-worker. Use 'start'"); + console.error({ + error: "Invalid action for camoufox-worker", + message: "Use 'start'", + }); process.exit(1); } }); diff --git a/nodecar/src/utils.ts b/nodecar/src/utils.ts index 2442e1b..b81afa0 100644 --- a/nodecar/src/utils.ts +++ b/nodecar/src/utils.ts @@ -1,3 +1,5 @@ +import type { LaunchOptions } from "playwright-core"; + const OS_MAP: { [key: string]: "mac" | "win" | "lin" } = { darwin: "mac", linux: "lin", @@ -35,3 +37,81 @@ export function getEnvVars(configMap: Record) { return envVars; } + +export function parseProxyString(proxyString: LaunchOptions["proxy"] | string) { + if (typeof proxyString === "object") { + return proxyString; + } + + if (!proxyString || typeof proxyString !== "string") { + throw new Error("Invalid proxy string provided"); + } + + // Remove any leading/trailing whitespace + const trimmed = proxyString.trim(); + + // Handle different proxy string formats: + // 1. http://username:password@host:port + // 2. host:port + // 3. protocol://host:port + // 4. username:password@host:port + + let server = ""; + let username: string | undefined; + let password: string | undefined; + + try { + // Try parsing as URL first (handles protocol://username:password@host:port) + if (trimmed.includes("://")) { + const url = new URL(trimmed); + server = `${url.hostname}:${url.port}`; + + if (url.username) { + username = decodeURIComponent(url.username); + } + if (url.password) { + password = decodeURIComponent(url.password); + } + } else { + // Handle formats without protocol + let workingString = trimmed; + + // Check for username:password@ prefix + const authMatch = workingString.match(/^([^:@]+):([^@]+)@(.+)$/); + if (authMatch) { + username = authMatch[1]; + password = authMatch[2]; + workingString = authMatch[3]; + } + + // The remaining part should be host:port + server = workingString; + } + + // Validate that we have a server + if (!server) { + throw new Error("Could not extract server information"); + } + + // Basic validation for host:port format + if (!server.includes(":") || server.split(":").length !== 2) { + throw new Error("Server must be in host:port format"); + } + + const result: LaunchOptions["proxy"] = { server }; + + if (username !== undefined) { + result.username = username; + } + + if (password !== undefined) { + result.password = password; + } + + return result; + } catch (error) { + throw new Error( + `Failed to parse proxy string: ${error instanceof Error ? error.message : "Unknown error"}`, + ); + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 694ad32..d63fcf6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -144,12 +144,12 @@ importers: '@types/node': specifier: ^24.1.0 version: 24.1.0 - camoufox-js: - specifier: ^0.6.2 - version: 0.6.2(encoding@0.1.13)(playwright-core@1.54.2) commander: specifier: ^14.0.0 version: 14.0.0 + donutbrowser-camoufox-js: + specifier: ^0.6.2 + version: 0.6.2(encoding@0.1.13)(playwright-core@1.54.2) dotenv: specifier: ^17.2.1 version: 17.2.1 @@ -1614,12 +1614,6 @@ packages: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} - camoufox-js@0.6.2: - resolution: {integrity: sha512-oeQigZ8+TsV34kJguKnwqALiL+go9IXA/3Opyw2P+VQSDWoFDTfc/pKEmkhrpX7xBPtcbarcnU7PnPmQyebNIg==} - hasBin: true - peerDependencies: - playwright-core: '*' - caniuse-lite@1.0.30001731: resolution: {integrity: sha512-lDdp2/wrOmTRWuoB5DpfNkC0rJDU8DqRa6nYL6HK6sytw70QMopt/NIc/9SM7ylItlBWfACXk0tEn37UWM/+mg==} @@ -1763,6 +1757,12 @@ packages: resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} engines: {node: '>=0.3.1'} + donutbrowser-camoufox-js@0.6.2: + resolution: {integrity: sha512-nIgofMkGnPJgLSqmclOS6wYtcpkfsjnkCALeF4gNjuB9KWZBBHoEyxr9Lb5flG1Fwrx8X3C0gmyvDaQ/s/Vgjw==} + hasBin: true + peerDependencies: + playwright-core: '*' + dot-prop@6.0.1: resolution: {integrity: sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==} engines: {node: '>=10'} @@ -2256,8 +2256,8 @@ packages: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} - maxmind@4.3.28: - resolution: {integrity: sha512-K3PcTVjhrSU6xzY7niQf/CHPmx/qzkMIpMumd3x0JXMkIDJMerL4LY9RsEhArHb4T3IK0NBxQcUlwjsxRm9rIw==} + maxmind@4.3.29: + resolution: {integrity: sha512-Vxx0rh7omBekjZnsDxpw35SrGGM3Uy7NoIKWzZlvh3UcjihGaySR8n+YSQ8YBseCvhEn+yehA98rZkTDW+uhPw==} engines: {node: '>=12', npm: '>=6'} micromatch@4.0.8: @@ -2774,8 +2774,8 @@ packages: tauri-plugin-macos-permissions-api@2.3.0: resolution: {integrity: sha512-pZp0jmDySysBqrGueknd1a7Rr4XEO9aXpMv9TNrT2PDHP0MSH20njieOagsFYJ5MCVb8A+wcaK0cIkjUC2dOww==} - tiny-lru@11.3.3: - resolution: {integrity: sha512-/ShxBZOgHXDdZi7FxajcsH0MfcBqwP+t7i4T3PGjI//NUA5aCpC7cB9bbdAYrAeQLBUTJfg2rk191fzZGeo7DA==} + tiny-lru@11.3.4: + resolution: {integrity: sha512-UxWEfRKpFCabAf6fkTNdlfSw/RDUJ/4C6i1aLZaDnGF82PERHyYhz5CMCVYXtLt34LbqgfpJ2bjmgGKgxuF/6A==} engines: {node: '>=12'} tinyglobby@0.2.14: @@ -4258,25 +4258,6 @@ snapshots: callsites@3.1.0: {} - camoufox-js@0.6.2(encoding@0.1.13)(playwright-core@1.54.2): - dependencies: - adm-zip: 0.5.16 - commander: 13.1.0 - fingerprint-generator: 2.1.69 - impit: 0.5.3 - js-yaml: 4.1.0 - language-tags: 2.1.0 - maxmind: 4.3.28 - playwright-core: 1.54.2 - progress: 2.0.3 - sqlite3: 5.1.7 - ua-parser-js: 2.0.4(encoding@0.1.13) - xml2js: 0.6.2 - transitivePeerDependencies: - - bluebird - - encoding - - supports-color - caniuse-lite@1.0.30001731: {} chalk@4.1.2: @@ -4405,6 +4386,25 @@ snapshots: diff@4.0.2: {} + donutbrowser-camoufox-js@0.6.2(encoding@0.1.13)(playwright-core@1.54.2): + dependencies: + adm-zip: 0.5.16 + commander: 13.1.0 + fingerprint-generator: 2.1.69 + impit: 0.5.3 + js-yaml: 4.1.0 + language-tags: 2.1.0 + maxmind: 4.3.29 + playwright-core: 1.54.2 + progress: 2.0.3 + sqlite3: 5.1.7 + ua-parser-js: 2.0.4(encoding@0.1.13) + xml2js: 0.6.2 + transitivePeerDependencies: + - bluebird + - encoding + - supports-color + dot-prop@6.0.1: dependencies: is-obj: 2.0.0 @@ -4901,10 +4901,10 @@ snapshots: math-intrinsics@1.1.0: {} - maxmind@4.3.28: + maxmind@4.3.29: dependencies: mmdb-lib: 2.2.1 - tiny-lru: 11.3.3 + tiny-lru: 11.3.4 micromatch@4.0.8: dependencies: @@ -5488,7 +5488,7 @@ snapshots: dependencies: '@tauri-apps/api': 2.7.0 - tiny-lru@11.3.3: {} + tiny-lru@11.3.4: {} tinyglobby@0.2.14: dependencies: diff --git a/src-tauri/src/browser_runner.rs b/src-tauri/src/browser_runner.rs index bac5eec..2d56e5f 100644 --- a/src-tauri/src/browser_runner.rs +++ b/src-tauri/src/browser_runner.rs @@ -77,6 +77,28 @@ impl BrowserRunner { profile_manager.get_profiles_dir() } + /// Get the executable path for a browser profile + /// This is a common helper to eliminate code duplication across the codebase + pub fn get_browser_executable_path( + &self, + profile: &BrowserProfile, + ) -> Result> { + // Create browser instance to get executable path + let browser_type = crate::browser::BrowserType::from_str(&profile.browser) + .map_err(|e| format!("Invalid browser type: {e}"))?; + let browser = crate::browser::create_browser(browser_type); + + // Construct browser directory path: binaries/// + let mut browser_dir = self.get_binaries_dir(); + browser_dir.push(&profile.browser); + browser_dir.push(&profile.version); + + // Get platform-specific executable path + browser + .get_executable_path(&browser_dir) + .map_err(|e| format!("Failed to get executable path for {}: {e}", profile.browser).into()) + } + /// Internal method to cleanup unused binaries (used by auto-cleanup) pub fn cleanup_unused_binaries_internal( &self, @@ -242,16 +264,13 @@ impl BrowserRunner { .map_err(|_| format!("Invalid browser type: {}", profile.browser))?; let browser = create_browser(browser_type.clone()); - // Get executable path - path structure: binaries/// - let mut browser_dir = self.get_binaries_dir(); - browser_dir.push(&profile.browser); - browser_dir.push(&profile.version); - - println!("Browser directory: {browser_dir:?}"); - let executable_path = browser - .get_executable_path(&browser_dir) + // Get executable path using common helper + let executable_path = self + .get_browser_executable_path(profile) .expect("Failed to get executable path"); + println!("Executable path: {executable_path:?}"); + // Prepare the executable (set permissions, etc.) if let Err(e) = browser.prepare_executable(&executable_path) { println!("Warning: Failed to prepare executable: {e}"); diff --git a/src-tauri/src/camoufox.rs b/src-tauri/src/camoufox.rs index 809096b..129fa82 100644 --- a/src-tauri/src/camoufox.rs +++ b/src-tauri/src/camoufox.rs @@ -92,30 +92,32 @@ impl CamoufoxNodecarLauncher { pub async fn generate_fingerprint_config( &self, app_handle: &AppHandle, + profile: &crate::profile::BrowserProfile, config: &CamoufoxConfig, ) -> Result> { let mut config_args = vec!["camoufox".to_string(), "generate-config".to_string()]; - // For fingerprint generation during profile creation, we can pass proxy directly - // but we set geoip to false during tests to avoid network requests - if std::env::var("CAMOUFOX_TEST").is_ok() { - config_args.extend(["--geoip".to_string(), "false".to_string()]); - } else if let Some(geoip) = &config.geoip { - match geoip { - serde_json::Value::Bool(true) => { - config_args.extend(["--geoip".to_string(), "true".to_string()]); - } - serde_json::Value::Bool(false) => { - config_args.extend(["--geoip".to_string(), "false".to_string()]); - } - serde_json::Value::String(ip) => { - config_args.extend(["--geoip".to_string(), ip.clone()]); - } - _ => {} - } + // Always ensure executable_path is set to the user's binary location + let executable_path = if let Some(path) = &config.executable_path { + path.clone() } else { - // Default to true for fingerprint generation - config_args.extend(["--geoip".to_string(), "true".to_string()]); + // Use the browser runner helper with the real profile + let browser_runner = crate::browser_runner::BrowserRunner::instance(); + browser_runner + .get_browser_executable_path(profile) + .map_err(|e| format!("Failed to get Camoufox executable path: {e}"))? + .to_string_lossy() + .to_string() + }; + config_args.extend(["--executable-path".to_string(), executable_path]); + + // Pass existing fingerprint if provided (for advanced form partial fingerprints) + if let Some(fingerprint) = &config.fingerprint { + config_args.extend(["--fingerprint".to_string(), fingerprint.clone()]); + } + + if let Some(serde_json::Value::Bool(true)) = &config.geoip { + config_args.push("--geoip".to_string()); } // Add proxy if provided (can be passed directly during fingerprint generation) @@ -132,7 +134,7 @@ impl CamoufoxNodecarLauncher { config_args.extend(["--max-height".to_string(), max_height.to_string()]); } - // Add block_* and executable_path options + // Add block_* options if let Some(block_images) = config.block_images { if block_images { config_args.push("--block-images".to_string()); @@ -151,10 +153,6 @@ impl CamoufoxNodecarLauncher { } } - if let Some(executable_path) = &config.executable_path { - config_args.extend(["--executable-path".to_string(), executable_path.clone()]); - } - // Execute config generation command let mut config_sidecar = self.get_nodecar_sidecar(app_handle)?; for arg in &config_args { @@ -186,84 +184,29 @@ impl CamoufoxNodecarLauncher { pub async fn launch_camoufox( &self, app_handle: &AppHandle, + profile: &crate::profile::BrowserProfile, profile_path: &str, config: &CamoufoxConfig, url: Option<&str>, ) -> Result> { - // Generate or use existing configuration let custom_config = if let Some(existing_fingerprint) = &config.fingerprint { - // Use existing fingerprint from profile metadata println!("Using existing fingerprint from profile metadata"); existing_fingerprint.clone() } else { - // Generate new configuration using nodecar generate-config command - println!("Generating new fingerprint configuration"); - let mut config_args = vec!["camoufox".to_string(), "generate-config".to_string()]; + return Err("No fingerprint provided".into()); + }; - // Use individual options to build configuration - if let Some(proxy) = &config.proxy { - config_args.extend(["--proxy".to_string(), proxy.clone()]); - } - - if let Some(max_width) = config.screen_max_width { - config_args.extend(["--max-width".to_string(), max_width.to_string()]); - } - - if let Some(max_height) = config.screen_max_height { - config_args.extend(["--max-height".to_string(), max_height.to_string()]); - } - - if let Some(geoip) = &config.geoip { - match geoip { - serde_json::Value::Bool(true) => { - config_args.extend(["--geoip".to_string(), "true".to_string()]); - } - serde_json::Value::Bool(false) => { - config_args.extend(["--geoip".to_string(), "false".to_string()]); - } - serde_json::Value::String(ip) => { - config_args.extend(["--geoip".to_string(), ip.clone()]); - } - _ => {} - } - } - - // Always add block_* and executable_path options - if let Some(block_images) = config.block_images { - if block_images { - config_args.push("--block-images".to_string()); - } - } - - if let Some(block_webrtc) = config.block_webrtc { - if block_webrtc { - config_args.push("--block-webrtc".to_string()); - } - } - - if let Some(block_webgl) = config.block_webgl { - if block_webgl { - config_args.push("--block-webgl".to_string()); - } - } - - if let Some(executable_path) = &config.executable_path { - config_args.extend(["--executable-path".to_string(), executable_path.clone()]); - } - - // Execute config generation command - let mut config_sidecar = self.get_nodecar_sidecar(app_handle)?; - for arg in &config_args { - config_sidecar = config_sidecar.arg(arg); - } - - let config_output = config_sidecar.output().await?; - if !config_output.status.success() { - let stderr = String::from_utf8_lossy(&config_output.stderr); - return Err(format!("Failed to generate camoufox config: {stderr}").into()); - } - - String::from_utf8_lossy(&config_output.stdout).to_string() + // Always ensure executable_path is set to the user's binary location + let executable_path = if let Some(path) = &config.executable_path { + path.clone() + } else { + // Use the browser runner helper with the real profile + let browser_runner = crate::browser_runner::BrowserRunner::instance(); + browser_runner + .get_browser_executable_path(profile) + .map_err(|e| format!("Failed to get Camoufox executable path: {e}"))? + .to_string_lossy() + .to_string() }; // Build nodecar command arguments @@ -277,6 +220,9 @@ impl CamoufoxNodecarLauncher { args.extend(["--url".to_string(), url.to_string()]); } + // Always add the executable path + args.extend(["--executable-path".to_string(), executable_path]); + // Always add the generated custom config args.extend(["--custom-config".to_string(), custom_config]); @@ -392,7 +338,7 @@ impl CamoufoxNodecarLauncher { // Verify the server is actually running by checking the process 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}"); + // Found running Camoufox instance return Ok(Some(CamoufoxLaunchResult { id: id.clone(), processId: instance.process_id, @@ -400,14 +346,13 @@ impl CamoufoxNodecarLauncher { url: instance.url.clone(), })); } else { - println!("Camoufox instance found but process is not running: {id}"); + // Camoufox instance found but process is not running } } } } } - println!("No running Camoufox instance found for profile: {profile_path}"); Ok(None) } @@ -426,13 +371,13 @@ impl CamoufoxNodecarLauncher { // Check if the process is still alive if !self.is_server_running(process_id).await { // Process is dead - println!("Camoufox instance {id} (PID: {process_id}) is no longer running"); + // Camoufox instance is no longer running dead_instances.push(id.clone()); instances_to_remove.push(id.clone()); } } else { // No process_id means it's likely a dead instance - println!("Camoufox instance {id} has no PID, marking as dead"); + // Camoufox instance has no PID, marking as dead dead_instances.push(id.clone()); instances_to_remove.push(id.clone()); } @@ -444,7 +389,7 @@ impl CamoufoxNodecarLauncher { let mut inner = self.inner.lock().await; for id in &instances_to_remove { inner.instances.remove(id); - println!("Removed dead Camoufox instance: {id}"); + // Removed dead Camoufox instance } } @@ -466,7 +411,7 @@ impl CamoufoxNodecarLauncher { }); if is_camoufox { - println!("Found running Camoufox process with PID: {process_id}"); + // Found running Camoufox process return true; } } @@ -499,7 +444,13 @@ impl CamoufoxNodecarLauncher { let _ = self.cleanup_dead_instances().await; self - .launch_camoufox(&app_handle, &profile_path_str, &config, url.as_deref()) + .launch_camoufox( + &app_handle, + &profile, + &profile_path_str, + &config, + url.as_deref(), + ) .await .map_err(|e| format!("Failed to launch Camoufox via nodecar: {e}")) } diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 383d1ab..eb13c6c 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -376,13 +376,8 @@ pub fn run() { interval.tick().await; match launcher.cleanup_dead_instances().await { - Ok(dead_instances) => { - if !dead_instances.is_empty() { - println!( - "Cleaned up {} dead Camoufox instances", - dead_instances.len() - ); - } + Ok(_dead_instances) => { + // Cleanup completed silently } Err(e) => { eprintln!("Error during Camoufox cleanup: {e}"); diff --git a/src-tauri/src/profile/manager.rs b/src-tauri/src/profile/manager.rs index 51bef9c..8d10901 100644 --- a/src-tauri/src/profile/manager.rs +++ b/src-tauri/src/profile/manager.rs @@ -100,11 +100,37 @@ impl ProfileManager { crate::camoufox::CamoufoxConfig::default() }); + // Always ensure executable_path is set to the user's binary location + if config.executable_path.is_none() { + let browser_runner = crate::browser_runner::BrowserRunner::instance(); + let mut browser_dir = browser_runner.get_binaries_dir(); + browser_dir.push(browser); + browser_dir.push(version); + + #[cfg(target_os = "macos")] + let binary_path = browser_dir + .join("Camoufox.app") + .join("Contents") + .join("MacOS") + .join("camoufox"); + + #[cfg(target_os = "windows")] + let binary_path = browser_dir.join("camoufox.exe"); + + #[cfg(target_os = "linux")] + let binary_path = browser_dir.join("camoufox"); + + config.executable_path = Some(binary_path.to_string_lossy().to_string()); + println!("Set Camoufox executable path: {:?}", config.executable_path); + } + // Pass upstream proxy information to config for fingerprint generation if let Some(proxy_id_ref) = &proxy_id { if let Some(proxy_settings) = PROXY_MANAGER.get_proxy_settings_by_id(proxy_id_ref) { // For fingerprint generation, pass upstream proxy directly with credentials if present - let proxy_url = if let (Some(username), Some(password)) = (&proxy_settings.username, &proxy_settings.password) { + let proxy_url = if let (Some(username), Some(password)) = + (&proxy_settings.username, &proxy_settings.password) + { format!( "{}://{}:{}@{}:{}", proxy_settings.proxy_type.to_lowercase(), @@ -137,8 +163,23 @@ impl ProfileManager { // Use the camoufox launcher to generate the config let camoufox_launcher = crate::camoufox::CamoufoxNodecarLauncher::instance(); + + // Create a temporary profile for fingerprint generation + let temp_profile = BrowserProfile { + id: uuid::Uuid::new_v4(), + name: name.to_string(), + browser: browser.to_string(), + version: version.to_string(), + proxy_id: proxy_id.clone(), + process_id: None, + last_launch: None, + release_type: release_type.to_string(), + camoufox_config: None, + group_id: group_id.clone(), + }; + match camoufox_launcher - .generate_fingerprint_config(app_handle, &config) + .generate_fingerprint_config(app_handle, &temp_profile, &config) .await { Ok(generated_fingerprint) => { @@ -146,7 +187,9 @@ impl ProfileManager { println!("Successfully generated fingerprint for profile: {name}"); } Err(e) => { - return Err(format!("Failed to generate fingerprint for Camoufox profile '{name}': {e}").into()); + return Err( + format!("Failed to generate fingerprint for Camoufox profile '{name}': {e}").into(), + ); } } } else { @@ -639,10 +682,7 @@ impl ProfileManager { if profile_path_match { is_running = true; found_pid = Some(pid); - println!( - "Found existing browser process with PID: {} for profile: {}", - pid, profile.name - ); + // Found existing browser process } else { println!("PID {pid} exists but doesn't match our profile path exactly, searching for correct process..."); } diff --git a/src/components/proxy-settings-dialog.tsx b/src/components/proxy-settings-dialog.tsx index 45d25a3..98e4849 100644 --- a/src/components/proxy-settings-dialog.tsx +++ b/src/components/proxy-settings-dialog.tsx @@ -67,9 +67,12 @@ export function ProxySettingsDialog({ loadStoredProxies(); if (isProxyDisabled) { setSelectedProxyId(null); + } else { + // Reset to initial proxy ID when dialog opens + setSelectedProxyId(initialProxyId || null); } } - }, [isOpen, isProxyDisabled, loadStoredProxies]); + }, [isOpen, isProxyDisabled, loadStoredProxies, initialProxyId]); const handleCreateProxy = useCallback(() => { setShowProxyForm(true);