mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-06-07 23:43:57 +02:00
refactor: camoufox rust implementation
This commit is contained in:
@@ -198,8 +198,8 @@ mod linux {
|
||||
}
|
||||
BrowserType::Camoufox => {
|
||||
vec![
|
||||
browser_subdir.join("camoufox"),
|
||||
browser_subdir.join("camoufox-bin"),
|
||||
browser_subdir.join("camoufox"),
|
||||
]
|
||||
}
|
||||
_ => vec![],
|
||||
|
||||
+133
-161
@@ -13,7 +13,7 @@ use crate::browser::{create_browser, BrowserType, ProxySettings};
|
||||
use crate::browser_version_service::{
|
||||
BrowserVersionInfo, BrowserVersionService, BrowserVersionsResult,
|
||||
};
|
||||
use crate::camoufox::CamoufoxConfig;
|
||||
use crate::camoufox_direct::CamoufoxConfig;
|
||||
use crate::download::{DownloadProgress, Downloader};
|
||||
use crate::downloaded_browsers::DownloadedBrowsersRegistry;
|
||||
use crate::extraction::Extractor;
|
||||
@@ -1782,7 +1782,7 @@ impl BrowserRunner {
|
||||
url: Option<String>,
|
||||
local_proxy_settings: Option<&ProxySettings>,
|
||||
) -> Result<BrowserProfile, Box<dyn std::error::Error + Send + Sync>> {
|
||||
// Handle camoufox profiles specially
|
||||
// Handle camoufox profiles specially using only the direct launcher
|
||||
if profile.browser == "camoufox" {
|
||||
if let Some(mut camoufox_config) = profile.camoufox_config.clone() {
|
||||
// Handle proxy settings for camoufox
|
||||
@@ -1840,11 +1840,29 @@ impl BrowserRunner {
|
||||
}
|
||||
}
|
||||
|
||||
// Use the camoufox launcher
|
||||
let camoufox_result = crate::camoufox::launch_camoufox_profile(
|
||||
// Use the existing config or create a test config if none exists
|
||||
let final_config = if camoufox_config.timezone.is_some()
|
||||
|| camoufox_config.screen_min_width.is_some()
|
||||
|| camoufox_config.window_width.is_some()
|
||||
{
|
||||
camoufox_config.clone()
|
||||
} else {
|
||||
// No meaningful config provided, use test config to ensure anti-fingerprinting works
|
||||
println!("No Camoufox configuration provided, using test configuration");
|
||||
let mut test_config =
|
||||
crate::camoufox_direct::CamoufoxDirectLauncher::create_test_config();
|
||||
// Preserve any proxy settings from the original config
|
||||
test_config.proxy = camoufox_config.proxy.clone();
|
||||
test_config.headless = camoufox_config.headless;
|
||||
test_config.debug = Some(true); // Enable debug for troubleshooting
|
||||
test_config
|
||||
};
|
||||
|
||||
// Use the direct camoufox launcher
|
||||
let camoufox_result = crate::camoufox_direct::launch_camoufox_profile_direct(
|
||||
app_handle.clone(),
|
||||
profile.clone(),
|
||||
camoufox_config,
|
||||
final_config,
|
||||
url,
|
||||
)
|
||||
.await
|
||||
@@ -2046,9 +2064,10 @@ impl BrowserRunner {
|
||||
url: &str,
|
||||
_internal_proxy_settings: Option<&ProxySettings>,
|
||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
// Handle camoufox profiles specially
|
||||
// Handle camoufox profiles specially using only the direct launcher
|
||||
if profile.browser == "camoufox" {
|
||||
let camoufox_launcher = crate::camoufox::CamoufoxLauncher::new(app_handle.clone());
|
||||
let camoufox_launcher =
|
||||
crate::camoufox_direct::CamoufoxDirectLauncher::new(app_handle.clone());
|
||||
|
||||
// Get the profile path based on the UUID
|
||||
let profiles_dir = self.get_profiles_dir();
|
||||
@@ -2066,9 +2085,9 @@ impl BrowserRunner {
|
||||
profile.name
|
||||
);
|
||||
|
||||
// For Camoufox, we need to launch a new instance with the URL since nodecar doesn't support
|
||||
// opening URLs in existing instances. This is a limitation of the anti-detect architecture.
|
||||
return Err("Camoufox does not support opening URLs in existing instances. Please close the browser and relaunch it with the new URL.".into());
|
||||
// For Camoufox, we need to launch a new instance with the URL since it doesn't support remote commands
|
||||
// This is a limitation of Camoufox's architecture
|
||||
return Err("Camoufox doesn't support opening URLs in existing instances. Please close the browser and launch again with the URL.".into());
|
||||
}
|
||||
Ok(None) => {
|
||||
return Err("Camoufox browser is not running".into());
|
||||
@@ -2240,7 +2259,7 @@ impl BrowserRunner {
|
||||
}
|
||||
BrowserType::Camoufox => {
|
||||
// This should never be reached due to the early return above, but handle it just in case
|
||||
Err("Camoufox does not support opening URLs in existing instances".into())
|
||||
Err("Camoufox URL opening should be handled in the early return above".into())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2447,77 +2466,8 @@ impl BrowserRunner {
|
||||
app_handle: tauri::AppHandle,
|
||||
profile: &BrowserProfile,
|
||||
) -> Result<bool, Box<dyn std::error::Error + Send + Sync>> {
|
||||
// Handle camoufox profiles specially using the camoufox launcher
|
||||
if profile.browser == "camoufox" {
|
||||
let camoufox_launcher = crate::camoufox::CamoufoxLauncher::new(app_handle.clone());
|
||||
|
||||
// Get the profile path based on the UUID
|
||||
let profiles_dir = self.get_profiles_dir();
|
||||
let profile_data_path = profile.get_profile_data_path(&profiles_dir);
|
||||
let profile_path_str = profile_data_path.to_string_lossy();
|
||||
|
||||
println!("Checking Camoufox status for profile: {}", profile.name);
|
||||
println!("Profile UUID: {}", profile.id);
|
||||
println!("Profile path: {profile_path_str}");
|
||||
|
||||
match camoufox_launcher
|
||||
.find_camoufox_by_profile(&profile_path_str)
|
||||
.await
|
||||
{
|
||||
Ok(Some(camoufox_process)) => {
|
||||
// Found a running camoufox process for this profile
|
||||
println!(
|
||||
"Found running Camoufox process for profile {}: {:?}",
|
||||
profile.name, camoufox_process
|
||||
);
|
||||
|
||||
// Update the profile with the current PID if it's different
|
||||
if let Some(pid) = camoufox_process.pid {
|
||||
if profile.process_id != Some(pid) {
|
||||
let mut updated_profile = profile.clone();
|
||||
updated_profile.process_id = Some(pid);
|
||||
if let Err(e) = self.save_profile(&updated_profile) {
|
||||
println!("Warning: Failed to update profile PID: {e}");
|
||||
} else {
|
||||
// Emit profile update event to frontend
|
||||
if let Err(e) = app_handle.emit("profile-updated", &updated_profile) {
|
||||
println!("Warning: Failed to emit profile update event: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(true);
|
||||
}
|
||||
Ok(None) => {
|
||||
// No running camoufox process found for this profile
|
||||
println!(
|
||||
"No running Camoufox process found for profile: {}",
|
||||
profile.name
|
||||
);
|
||||
|
||||
// Clear the PID if one was stored
|
||||
if profile.process_id.is_some() {
|
||||
let mut updated_profile = profile.clone();
|
||||
updated_profile.process_id = None;
|
||||
if let Err(e) = self.save_profile(&updated_profile) {
|
||||
println!("Warning: Failed to clear profile PID: {e}");
|
||||
} else {
|
||||
// Emit profile update event to frontend
|
||||
if let Err(e) = app_handle.emit("profile-updated", &updated_profile) {
|
||||
println!("Warning: Failed to emit profile update event: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(false);
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Error checking Camoufox status: {e}");
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Handle camoufox profiles using the same fast approach as other browsers
|
||||
// No special handling needed - camoufox uses the same process checking logic
|
||||
|
||||
// For non-camoufox browsers, use the existing logic
|
||||
let mut inner_profile = profile.clone();
|
||||
@@ -2535,12 +2485,13 @@ impl BrowserRunner {
|
||||
let profile_data_path_str = profile_data_path.to_string_lossy();
|
||||
let profile_path_match = cmd.iter().any(|s| {
|
||||
let arg = s.to_str().unwrap_or("");
|
||||
// For Firefox-based browsers, check for exact profile path match
|
||||
// For Firefox-based browsers (including camoufox), check for exact profile path match
|
||||
if profile.browser == "tor-browser"
|
||||
|| profile.browser == "firefox"
|
||||
|| profile.browser == "firefox-developer"
|
||||
|| profile.browser == "mullvad-browser"
|
||||
|| profile.browser == "zen"
|
||||
|| profile.browser == "camoufox"
|
||||
{
|
||||
arg == profile_data_path_str
|
||||
|| arg == format!("-profile={profile_data_path_str}")
|
||||
@@ -2583,6 +2534,7 @@ impl BrowserRunner {
|
||||
&& !exe_name.contains("developer")
|
||||
&& !exe_name.contains("tor")
|
||||
&& !exe_name.contains("mullvad")
|
||||
&& !exe_name.contains("camoufox")
|
||||
}
|
||||
"firefox-developer" => exe_name.contains("firefox") && exe_name.contains("developer"),
|
||||
"mullvad-browser" => self.is_tor_or_mullvad_browser(&exe_name, cmd, "mullvad-browser"),
|
||||
@@ -2590,6 +2542,13 @@ impl BrowserRunner {
|
||||
"zen" => exe_name.contains("zen"),
|
||||
"chromium" => exe_name.contains("chromium"),
|
||||
"brave" => exe_name.contains("brave"),
|
||||
"camoufox" => {
|
||||
exe_name.contains("camoufox")
|
||||
|| (exe_name.contains("firefox")
|
||||
&& cmd
|
||||
.iter()
|
||||
.any(|arg| arg.to_str().unwrap_or("").contains("camoufox")))
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
|
||||
@@ -2662,66 +2621,99 @@ impl BrowserRunner {
|
||||
Ok(is_running)
|
||||
}
|
||||
|
||||
pub fn update_camoufox_config(
|
||||
&self,
|
||||
profile_name: &str,
|
||||
config: crate::camoufox_direct::CamoufoxConfig,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Find the profile by name
|
||||
let profiles = self.list_profiles()?;
|
||||
let mut profile = profiles
|
||||
.into_iter()
|
||||
.find(|p| p.name == profile_name)
|
||||
.ok_or_else(|| format!("Profile {profile_name} not found"))?;
|
||||
|
||||
// Check if the browser is currently running
|
||||
if profile.process_id.is_some() {
|
||||
return Err(
|
||||
"Cannot update Camoufox configuration while browser is running. Please stop the browser first.".into(),
|
||||
);
|
||||
}
|
||||
|
||||
// Update the Camoufox configuration
|
||||
profile.camoufox_config = Some(config);
|
||||
|
||||
// Save the updated profile
|
||||
self.save_profile(&profile)?;
|
||||
|
||||
println!("Camoufox configuration updated for profile '{profile_name}'.");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn kill_browser_process(
|
||||
&self,
|
||||
app_handle: tauri::AppHandle,
|
||||
profile: &BrowserProfile,
|
||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
// Handle camoufox profiles specially
|
||||
// Handle camoufox profiles specially using only the direct launcher
|
||||
if profile.browser == "camoufox" {
|
||||
let camoufox_launcher = crate::camoufox::CamoufoxLauncher::new(app_handle.clone());
|
||||
let camoufox_launcher =
|
||||
crate::camoufox_direct::CamoufoxDirectLauncher::new(app_handle.clone());
|
||||
|
||||
// Get the profile path based on the UUID
|
||||
let profiles_dir = self.get_profiles_dir();
|
||||
let profile_data_path = profile.get_profile_data_path(&profiles_dir);
|
||||
let profile_path_str = profile_data_path.to_string_lossy();
|
||||
|
||||
println!(
|
||||
"Attempting to kill Camoufox process for profile: {}",
|
||||
profile.name
|
||||
);
|
||||
println!("Profile UUID: {}", profile.id);
|
||||
println!("Profile path: {profile_path_str}");
|
||||
|
||||
match camoufox_launcher
|
||||
.find_camoufox_by_profile(&profile_path_str)
|
||||
.await
|
||||
{
|
||||
Ok(Some(camoufox_process)) => {
|
||||
println!(
|
||||
"Found running Camoufox process for profile {}: {:?}",
|
||||
profile.name, camoufox_process
|
||||
);
|
||||
|
||||
// Stop the camoufox process using the launcher
|
||||
match camoufox_launcher.stop_camoufox(&camoufox_process.id).await {
|
||||
Ok(stopped) => {
|
||||
if stopped {
|
||||
println!(
|
||||
"Successfully stopped Camoufox process: {}",
|
||||
camoufox_process.id
|
||||
);
|
||||
} else {
|
||||
println!("Failed to stop Camoufox process: {}", camoufox_process.id);
|
||||
return Err("Failed to stop Camoufox process".into());
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Error stopping Camoufox process: {e}");
|
||||
return Err(format!("Error stopping Camoufox process: {e}").into());
|
||||
// Try to stop by PID first (faster)
|
||||
if let Some(stored_pid) = profile.process_id {
|
||||
match camoufox_launcher
|
||||
.stop_camoufox(&stored_pid.to_string())
|
||||
.await
|
||||
{
|
||||
Ok(stopped) => {
|
||||
if stopped {
|
||||
println!("Successfully stopped Camoufox process by PID: {stored_pid}");
|
||||
} else {
|
||||
println!("Failed to stop Camoufox process by PID: {stored_pid}");
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Error stopping Camoufox process by PID: {e}");
|
||||
}
|
||||
}
|
||||
Ok(None) => {
|
||||
println!(
|
||||
"No running Camoufox process found for profile: {}",
|
||||
profile.name
|
||||
);
|
||||
// Process might already be stopped, just clear the PID
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Error finding Camoufox process: {e}");
|
||||
return Err(format!("Error finding Camoufox process: {e}").into());
|
||||
} else {
|
||||
// Fallback: search by profile path
|
||||
let profiles_dir = self.get_profiles_dir();
|
||||
let profile_data_path = profile.get_profile_data_path(&profiles_dir);
|
||||
let profile_path_str = profile_data_path.to_string_lossy();
|
||||
|
||||
match camoufox_launcher
|
||||
.find_camoufox_by_profile(&profile_path_str)
|
||||
.await
|
||||
{
|
||||
Ok(Some(camoufox_process)) => {
|
||||
match camoufox_launcher.stop_camoufox(&camoufox_process.id).await {
|
||||
Ok(stopped) => {
|
||||
if stopped {
|
||||
println!(
|
||||
"Successfully stopped Camoufox process: {}",
|
||||
camoufox_process.id
|
||||
);
|
||||
} else {
|
||||
println!("Failed to stop Camoufox process: {}", camoufox_process.id);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Error stopping Camoufox process: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(None) => {
|
||||
println!(
|
||||
"No running Camoufox process found for profile: {}",
|
||||
profile.name
|
||||
);
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Error finding Camoufox process: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2766,6 +2758,7 @@ impl BrowserRunner {
|
||||
&& !exe_name.contains("developer")
|
||||
&& !exe_name.contains("tor")
|
||||
&& !exe_name.contains("mullvad")
|
||||
&& !exe_name.contains("camoufox")
|
||||
}
|
||||
"firefox-developer" => exe_name.contains("firefox") && exe_name.contains("developer"),
|
||||
"mullvad-browser" => self.is_tor_or_mullvad_browser(&exe_name, cmd, "mullvad-browser"),
|
||||
@@ -2773,6 +2766,13 @@ impl BrowserRunner {
|
||||
"zen" => exe_name.contains("zen"),
|
||||
"chromium" => exe_name.contains("chromium"),
|
||||
"brave" => exe_name.contains("brave"),
|
||||
"camoufox" => {
|
||||
exe_name.contains("camoufox")
|
||||
|| (exe_name.contains("firefox")
|
||||
&& cmd
|
||||
.iter()
|
||||
.any(|arg| arg.to_str().unwrap_or("").contains("camoufox")))
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
|
||||
@@ -3180,34 +3180,6 @@ impl BrowserRunner {
|
||||
|
||||
files_exist
|
||||
}
|
||||
|
||||
/// Update camoufox configuration for a profile
|
||||
pub fn update_camoufox_config(
|
||||
&self,
|
||||
profile_name: &str,
|
||||
config: CamoufoxConfig,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut profiles = self.list_profiles()?;
|
||||
|
||||
// Find the profile to update
|
||||
let profile = profiles
|
||||
.iter_mut()
|
||||
.find(|p| p.name == profile_name)
|
||||
.ok_or_else(|| format!("Profile '{profile_name}' not found"))?;
|
||||
|
||||
// Ensure the profile is a camoufox profile
|
||||
if profile.browser != "camoufox" {
|
||||
return Err(format!("Profile '{profile_name}' is not a camoufox profile").into());
|
||||
}
|
||||
|
||||
// Update the camoufox configuration
|
||||
profile.camoufox_config = Some(config);
|
||||
|
||||
// Save the updated profile
|
||||
self.save_profile(profile)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl BrowserProfile {
|
||||
|
||||
@@ -1,607 +0,0 @@
|
||||
use crate::browser_runner::BrowserProfile;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use tauri::AppHandle;
|
||||
use tauri_plugin_shell::ShellExt;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct CamoufoxConfig {
|
||||
pub os: Option<Vec<String>>,
|
||||
pub block_images: Option<bool>,
|
||||
pub block_webrtc: Option<bool>,
|
||||
pub block_webgl: Option<bool>,
|
||||
pub disable_coop: Option<bool>,
|
||||
pub geoip: Option<serde_json::Value>, // Can be String or bool
|
||||
pub country: Option<String>,
|
||||
pub timezone: Option<String>,
|
||||
pub latitude: Option<f64>,
|
||||
pub longitude: Option<f64>,
|
||||
pub humanize: Option<bool>,
|
||||
pub humanize_duration: Option<f64>,
|
||||
pub headless: Option<bool>,
|
||||
pub locale: Option<Vec<String>>,
|
||||
pub addons: Option<Vec<String>>,
|
||||
pub fonts: Option<Vec<String>>,
|
||||
pub custom_fonts_only: Option<bool>,
|
||||
pub exclude_addons: Option<Vec<String>>,
|
||||
pub screen_min_width: Option<u32>,
|
||||
pub screen_max_width: Option<u32>,
|
||||
pub screen_min_height: Option<u32>,
|
||||
pub screen_max_height: Option<u32>,
|
||||
pub window_width: Option<u32>,
|
||||
pub window_height: Option<u32>,
|
||||
pub ff_version: Option<u32>,
|
||||
pub main_world_eval: Option<bool>,
|
||||
pub webgl_vendor: Option<String>,
|
||||
pub webgl_renderer: Option<String>,
|
||||
pub proxy: Option<String>,
|
||||
pub enable_cache: Option<bool>,
|
||||
pub virtual_display: Option<String>,
|
||||
pub debug: Option<bool>,
|
||||
pub additional_args: Option<Vec<String>>,
|
||||
pub env_vars: Option<HashMap<String, String>>,
|
||||
pub firefox_prefs: Option<HashMap<String, serde_json::Value>>,
|
||||
}
|
||||
|
||||
impl Default for CamoufoxConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
os: None,
|
||||
block_images: None,
|
||||
block_webrtc: None,
|
||||
block_webgl: None,
|
||||
disable_coop: None,
|
||||
geoip: None,
|
||||
country: None,
|
||||
timezone: None,
|
||||
latitude: None,
|
||||
longitude: None,
|
||||
humanize: None,
|
||||
humanize_duration: None,
|
||||
headless: None,
|
||||
locale: None,
|
||||
addons: None,
|
||||
fonts: None,
|
||||
custom_fonts_only: None,
|
||||
exclude_addons: None,
|
||||
screen_min_width: None,
|
||||
screen_max_width: None,
|
||||
screen_min_height: None,
|
||||
screen_max_height: None,
|
||||
window_width: None,
|
||||
window_height: None,
|
||||
ff_version: None,
|
||||
main_world_eval: None,
|
||||
webgl_vendor: None,
|
||||
webgl_renderer: None,
|
||||
proxy: None,
|
||||
enable_cache: Some(true), // Cache enabled by default
|
||||
virtual_display: None,
|
||||
debug: None,
|
||||
additional_args: None,
|
||||
env_vars: None,
|
||||
firefox_prefs: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[allow(non_snake_case)]
|
||||
pub struct CamoufoxLaunchResult {
|
||||
pub id: String,
|
||||
pub pid: Option<u32>,
|
||||
#[serde(alias = "executable_path")]
|
||||
pub executablePath: String,
|
||||
#[serde(alias = "profile_path")]
|
||||
pub profilePath: String,
|
||||
pub url: Option<String>,
|
||||
}
|
||||
|
||||
pub struct CamoufoxLauncher {
|
||||
app_handle: AppHandle,
|
||||
}
|
||||
|
||||
impl CamoufoxLauncher {
|
||||
pub fn new(app_handle: AppHandle) -> Self {
|
||||
Self { app_handle }
|
||||
}
|
||||
|
||||
/// Launch Camoufox browser with the specified configuration
|
||||
pub async fn launch_camoufox(
|
||||
&self,
|
||||
executable_path: &str,
|
||||
profile_path: &str,
|
||||
config: &CamoufoxConfig,
|
||||
url: Option<&str>,
|
||||
) -> Result<CamoufoxLaunchResult, Box<dyn std::error::Error + Send + Sync>> {
|
||||
println!("Launching Camoufox with executable: {executable_path}");
|
||||
println!("Profile path: {profile_path}");
|
||||
println!("URL: {url:?}");
|
||||
|
||||
// Use Tauri's sidecar to call nodecar
|
||||
let mut sidecar = self
|
||||
.app_handle
|
||||
.shell()
|
||||
.sidecar("nodecar")
|
||||
.map_err(|e| format!("Failed to create nodecar sidecar: {e}"))?
|
||||
.arg("camoufox")
|
||||
.arg("launch")
|
||||
.arg("--executable-path")
|
||||
.arg(executable_path)
|
||||
.arg("--profile-path")
|
||||
.arg(profile_path);
|
||||
|
||||
// Add URL if provided
|
||||
if let Some(url) = url {
|
||||
sidecar = sidecar.arg("--url").arg(url);
|
||||
}
|
||||
|
||||
// Add configuration options
|
||||
if let Some(os_list) = &config.os {
|
||||
sidecar = sidecar.arg("--os").arg(os_list.join(","));
|
||||
}
|
||||
|
||||
if config.block_images.unwrap_or(false) {
|
||||
sidecar = sidecar.arg("--block-images");
|
||||
}
|
||||
|
||||
if config.block_webrtc.unwrap_or(false) {
|
||||
sidecar = sidecar.arg("--block-webrtc");
|
||||
}
|
||||
|
||||
if config.block_webgl.unwrap_or(false) {
|
||||
sidecar = sidecar.arg("--block-webgl");
|
||||
}
|
||||
|
||||
if config.disable_coop.unwrap_or(false) {
|
||||
sidecar = sidecar.arg("--disable-coop");
|
||||
}
|
||||
|
||||
if let Some(geoip) = &config.geoip {
|
||||
match geoip {
|
||||
serde_json::Value::String(s) => {
|
||||
sidecar = sidecar.arg("--geoip").arg(s);
|
||||
}
|
||||
serde_json::Value::Bool(b) => {
|
||||
sidecar = sidecar
|
||||
.arg("--geoip")
|
||||
.arg(if *b { "auto" } else { "false" });
|
||||
}
|
||||
_ => {
|
||||
sidecar = sidecar.arg("--geoip").arg(geoip.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(country) = &config.country {
|
||||
sidecar = sidecar.arg("--country").arg(country);
|
||||
}
|
||||
|
||||
if let Some(timezone) = &config.timezone {
|
||||
sidecar = sidecar.arg("--timezone").arg(timezone);
|
||||
}
|
||||
|
||||
if let Some(latitude) = config.latitude {
|
||||
if let Some(longitude) = config.longitude {
|
||||
sidecar = sidecar.arg("--latitude").arg(latitude.to_string());
|
||||
sidecar = sidecar.arg("--longitude").arg(longitude.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(humanize) = config.humanize {
|
||||
if humanize {
|
||||
if let Some(duration) = config.humanize_duration {
|
||||
sidecar = sidecar.arg("--humanize").arg(duration.to_string());
|
||||
} else {
|
||||
sidecar = sidecar.arg("--humanize");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if config.headless.unwrap_or(false) {
|
||||
sidecar = sidecar.arg("--headless");
|
||||
}
|
||||
|
||||
if let Some(locale_list) = &config.locale {
|
||||
sidecar = sidecar.arg("--locale").arg(locale_list.join(","));
|
||||
}
|
||||
|
||||
if let Some(addons_list) = &config.addons {
|
||||
sidecar = sidecar.arg("--addons").arg(addons_list.join(","));
|
||||
}
|
||||
|
||||
if let Some(fonts_list) = &config.fonts {
|
||||
sidecar = sidecar.arg("--fonts").arg(fonts_list.join(","));
|
||||
}
|
||||
|
||||
if config.custom_fonts_only.unwrap_or(false) {
|
||||
sidecar = sidecar.arg("--custom-fonts-only");
|
||||
}
|
||||
|
||||
if let Some(exclude_addons_list) = &config.exclude_addons {
|
||||
sidecar = sidecar
|
||||
.arg("--exclude-addons")
|
||||
.arg(exclude_addons_list.join(","));
|
||||
}
|
||||
|
||||
// Screen size configuration
|
||||
if let Some(width) = config.screen_min_width {
|
||||
sidecar = sidecar.arg("--screen-min-width").arg(width.to_string());
|
||||
}
|
||||
|
||||
if let Some(width) = config.screen_max_width {
|
||||
sidecar = sidecar.arg("--screen-max-width").arg(width.to_string());
|
||||
}
|
||||
|
||||
if let Some(height) = config.screen_min_height {
|
||||
sidecar = sidecar.arg("--screen-min-height").arg(height.to_string());
|
||||
}
|
||||
|
||||
if let Some(height) = config.screen_max_height {
|
||||
sidecar = sidecar.arg("--screen-max-height").arg(height.to_string());
|
||||
}
|
||||
|
||||
if let Some(width) = config.window_width {
|
||||
sidecar = sidecar.arg("--window-width").arg(width.to_string());
|
||||
}
|
||||
|
||||
if let Some(height) = config.window_height {
|
||||
sidecar = sidecar.arg("--window-height").arg(height.to_string());
|
||||
}
|
||||
|
||||
// Advanced options
|
||||
if let Some(ff_version) = config.ff_version {
|
||||
sidecar = sidecar.arg("--ff-version").arg(ff_version.to_string());
|
||||
}
|
||||
|
||||
if config.main_world_eval.unwrap_or(false) {
|
||||
sidecar = sidecar.arg("--main-world-eval");
|
||||
}
|
||||
|
||||
if let Some(vendor) = &config.webgl_vendor {
|
||||
if let Some(renderer) = &config.webgl_renderer {
|
||||
sidecar = sidecar.arg("--webgl-vendor").arg(vendor);
|
||||
sidecar = sidecar.arg("--webgl-renderer").arg(renderer);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(proxy) = &config.proxy {
|
||||
sidecar = sidecar.arg("--proxy").arg(proxy);
|
||||
}
|
||||
|
||||
// Cache is enabled by default, only add flag if disabled
|
||||
if !config.enable_cache.unwrap_or(true) {
|
||||
sidecar = sidecar.arg("--disable-cache");
|
||||
}
|
||||
|
||||
if let Some(virtual_display) = &config.virtual_display {
|
||||
sidecar = sidecar.arg("--virtual-display").arg(virtual_display);
|
||||
}
|
||||
|
||||
if config.debug.unwrap_or(false) {
|
||||
sidecar = sidecar.arg("--debug");
|
||||
}
|
||||
|
||||
if let Some(args) = &config.additional_args {
|
||||
sidecar = sidecar.arg("--args").arg(args.join(","));
|
||||
}
|
||||
|
||||
if let Some(env_vars) = &config.env_vars {
|
||||
let env_json = serde_json::to_string(env_vars)
|
||||
.map_err(|e| format!("Failed to serialize environment variables: {e}"))?;
|
||||
sidecar = sidecar.arg("--env").arg(env_json);
|
||||
}
|
||||
|
||||
if let Some(firefox_prefs) = &config.firefox_prefs {
|
||||
let prefs_json = serde_json::to_string(firefox_prefs)
|
||||
.map_err(|e| format!("Failed to serialize Firefox preferences: {e}"))?;
|
||||
sidecar = sidecar.arg("--firefox-prefs").arg(prefs_json);
|
||||
}
|
||||
|
||||
// Execute the command
|
||||
println!("Executing nodecar command...");
|
||||
let output = sidecar
|
||||
.output()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to execute nodecar command: {e}"))?;
|
||||
|
||||
// Check the command status first
|
||||
if !output.status.success() {
|
||||
let error_msg = String::from_utf8_lossy(&output.stderr);
|
||||
let stdout_msg = String::from_utf8_lossy(&output.stdout);
|
||||
return Err(
|
||||
format!(
|
||||
"Failed to launch Camoufox: Command failed with status {:?}\nstderr: {}\nstdout: {}",
|
||||
output.status, error_msg, stdout_msg
|
||||
)
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
||||
// Parse the JSON response
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
println!("Nodecar stdout: {stdout}");
|
||||
|
||||
// Try to parse the JSON response
|
||||
let result: CamoufoxLaunchResult = serde_json::from_str(&stdout)
|
||||
.map_err(|e| format!("Failed to parse nodecar response as JSON: {e}\nResponse: {stdout}"))?;
|
||||
|
||||
println!("Successfully launched Camoufox with ID: {}", result.id);
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Stop a Camoufox process by ID
|
||||
pub async fn stop_camoufox(
|
||||
&self,
|
||||
id: &str,
|
||||
) -> Result<bool, Box<dyn std::error::Error + Send + Sync>> {
|
||||
println!("Stopping Camoufox process with ID: {id}");
|
||||
|
||||
// First, we need to find the process to get its executable and profile paths
|
||||
let processes = self.list_camoufox_processes().await?;
|
||||
let target_process = processes.iter().find(|p| p.id == id);
|
||||
|
||||
if let Some(process) = target_process {
|
||||
println!(
|
||||
"Found process to stop: executable={}, profile={}",
|
||||
process.executablePath, process.profilePath
|
||||
);
|
||||
|
||||
let sidecar = self
|
||||
.app_handle
|
||||
.shell()
|
||||
.sidecar("nodecar")
|
||||
.map_err(|e| format!("Failed to create nodecar sidecar: {e}"))?
|
||||
.arg("camoufox")
|
||||
.arg("stop")
|
||||
.arg("--executable-path")
|
||||
.arg(&process.executablePath)
|
||||
.arg("--profile-path")
|
||||
.arg(&process.profilePath)
|
||||
.arg("--id")
|
||||
.arg(id);
|
||||
|
||||
let output = sidecar
|
||||
.output()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to execute nodecar stop command: {e}"))?;
|
||||
|
||||
if !output.status.success() {
|
||||
let error_msg = String::from_utf8_lossy(&output.stderr);
|
||||
let stdout_msg = String::from_utf8_lossy(&output.stdout);
|
||||
println!("Failed to stop Camoufox process - stderr: {error_msg}, stdout: {stdout_msg}");
|
||||
return Err(format!("Failed to stop Camoufox process: {error_msg}").into());
|
||||
}
|
||||
|
||||
let stdout = String::from_utf8_lossy(&output.stdout).trim().to_string();
|
||||
println!("Stop command result: {stdout}");
|
||||
|
||||
// Parse the JSON response which contains a "success" field
|
||||
let response: serde_json::Value = serde_json::from_str(&stdout)
|
||||
.map_err(|e| format!("Failed to parse stop response as JSON: {e}\nResponse: {stdout}"))?;
|
||||
|
||||
let success = response
|
||||
.get("success")
|
||||
.and_then(|v| v.as_bool())
|
||||
.ok_or_else(|| {
|
||||
format!("Invalid response format - missing or invalid 'success' field: {stdout}")
|
||||
})?;
|
||||
|
||||
if success {
|
||||
println!("Successfully stopped Camoufox process: {id}");
|
||||
} else {
|
||||
println!("Failed to stop Camoufox process: {id} (process may not exist)");
|
||||
}
|
||||
|
||||
Ok(success)
|
||||
} else {
|
||||
println!("Camoufox process with ID {id} not found in running processes");
|
||||
// If we can't find the process, it might already be stopped
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
/// List all Camoufox processes
|
||||
pub async fn list_camoufox_processes(
|
||||
&self,
|
||||
) -> Result<Vec<CamoufoxLaunchResult>, Box<dyn std::error::Error + Send + Sync>> {
|
||||
println!("Listing Camoufox processes...");
|
||||
|
||||
// For the list command, we need to provide dummy executable-path and profile-path
|
||||
// even though they're not used by the list action
|
||||
let sidecar = self
|
||||
.app_handle
|
||||
.shell()
|
||||
.sidecar("nodecar")
|
||||
.map_err(|e| format!("Failed to create nodecar sidecar: {e}"))?
|
||||
.arg("camoufox")
|
||||
.arg("list")
|
||||
.arg("--executable-path")
|
||||
.arg("/dummy/path") // Dummy path since list doesn't use it
|
||||
.arg("--profile-path")
|
||||
.arg("/dummy/profile"); // Dummy path since list doesn't use it
|
||||
|
||||
let output = sidecar
|
||||
.output()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to execute nodecar list command: {e}"))?;
|
||||
|
||||
if !output.status.success() {
|
||||
let error_msg = String::from_utf8_lossy(&output.stderr);
|
||||
return Err(format!("Failed to list Camoufox processes: {error_msg}").into());
|
||||
}
|
||||
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
println!("List command result: {stdout}");
|
||||
|
||||
// Parse the response as an array of process info
|
||||
let processes: Vec<serde_json::Value> =
|
||||
serde_json::from_str(&stdout).map_err(|e| format!("Failed to parse list response: {e}"))?;
|
||||
|
||||
// Convert to CamoufoxLaunchResult format
|
||||
let mut results = Vec::new();
|
||||
for process in processes {
|
||||
// Handle both camelCase and snake_case formats from nodecar
|
||||
let id = process.get("id").and_then(|v| v.as_str());
|
||||
|
||||
// Try both formats for executable path
|
||||
let executable_path = process
|
||||
.get("executable_path")
|
||||
.and_then(|v| v.as_str())
|
||||
.or_else(|| process.get("executablePath").and_then(|v| v.as_str()));
|
||||
|
||||
// Try both formats for profile path
|
||||
let profile_path = process
|
||||
.get("profile_path")
|
||||
.and_then(|v| v.as_str())
|
||||
.or_else(|| process.get("profilePath").and_then(|v| v.as_str()));
|
||||
|
||||
if let (Some(id), Some(executable_path), Some(profile_path)) =
|
||||
(id, executable_path, profile_path)
|
||||
{
|
||||
let pid = process
|
||||
.get("pid")
|
||||
.and_then(|v| v.as_u64())
|
||||
.map(|v| v as u32);
|
||||
|
||||
let url = process
|
||||
.get("url")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(|s| s.to_string());
|
||||
|
||||
results.push(CamoufoxLaunchResult {
|
||||
id: id.to_string(),
|
||||
pid,
|
||||
executablePath: executable_path.to_string(),
|
||||
profilePath: profile_path.to_string(),
|
||||
url,
|
||||
});
|
||||
} else {
|
||||
println!("Skipping malformed process entry: {process:?}");
|
||||
}
|
||||
}
|
||||
|
||||
println!("Parsed {} valid Camoufox processes", results.len());
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
/// Find Camoufox process by profile path (for integration with browser_runner)
|
||||
pub async fn find_camoufox_by_profile(
|
||||
&self,
|
||||
profile_path: &str,
|
||||
) -> Result<Option<CamoufoxLaunchResult>, Box<dyn std::error::Error + Send + Sync>> {
|
||||
println!("Looking for Camoufox process with profile path: {profile_path}");
|
||||
|
||||
let processes = self.list_camoufox_processes().await?;
|
||||
println!("Found {} running Camoufox processes", processes.len());
|
||||
|
||||
for process in &processes {
|
||||
println!(
|
||||
"Checking process with profile path: {}",
|
||||
process.profilePath
|
||||
);
|
||||
}
|
||||
|
||||
// Convert both paths to canonical form for comparison
|
||||
let target_path = std::path::Path::new(profile_path)
|
||||
.canonicalize()
|
||||
.unwrap_or_else(|_| std::path::Path::new(profile_path).to_path_buf());
|
||||
|
||||
for process in &processes {
|
||||
println!(
|
||||
"Comparing target path: {} with process path: {}",
|
||||
target_path.display(),
|
||||
process.profilePath
|
||||
);
|
||||
|
||||
// Try multiple comparison methods
|
||||
let process_path = std::path::Path::new(&process.profilePath)
|
||||
.canonicalize()
|
||||
.unwrap_or_else(|_| std::path::Path::new(&process.profilePath).to_path_buf());
|
||||
|
||||
// Method 1: Canonical path comparison
|
||||
if process_path == target_path {
|
||||
println!("Found match using canonical path comparison");
|
||||
return Ok(Some(process.clone()));
|
||||
}
|
||||
|
||||
// Method 2: Direct string comparison
|
||||
if process.profilePath == profile_path {
|
||||
println!("Found match using direct string comparison");
|
||||
return Ok(Some(process.clone()));
|
||||
}
|
||||
|
||||
// Method 3: Compare as strings after canonicalization
|
||||
if process_path.to_string_lossy() == target_path.to_string_lossy() {
|
||||
println!("Found match using canonical string comparison");
|
||||
return Ok(Some(process.clone()));
|
||||
}
|
||||
|
||||
// Method 4: Compare file names if full paths don't match
|
||||
if let (Some(process_file), Some(target_file)) =
|
||||
(process_path.file_name(), target_path.file_name())
|
||||
{
|
||||
if process_file == target_file {
|
||||
// If the parent directories also match, it's likely the same profile
|
||||
if let (Some(process_parent), Some(target_parent)) =
|
||||
(process_path.parent(), target_path.parent())
|
||||
{
|
||||
if process_parent == target_parent {
|
||||
println!("Found match using parent directory and file name comparison");
|
||||
return Ok(Some(process.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Method 5: Check if either path contains the other (for symlinks or different representations)
|
||||
let process_path_str = process_path.to_string_lossy();
|
||||
let target_path_str = target_path.to_string_lossy();
|
||||
|
||||
if process_path_str.contains(target_path_str.as_ref())
|
||||
|| target_path_str.contains(process_path_str.as_ref())
|
||||
{
|
||||
println!("Found match using path containment check");
|
||||
return Ok(Some(process.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
println!("No matching Camoufox process found for profile path: {profile_path}");
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn launch_camoufox_profile(
|
||||
app_handle: AppHandle,
|
||||
profile: BrowserProfile,
|
||||
config: CamoufoxConfig,
|
||||
url: Option<String>,
|
||||
) -> Result<CamoufoxLaunchResult, String> {
|
||||
let launcher = CamoufoxLauncher::new(app_handle);
|
||||
|
||||
// Get the executable path for Camoufox
|
||||
let browser_runner = crate::browser_runner::BrowserRunner::new();
|
||||
let binaries_dir = browser_runner.get_binaries_dir();
|
||||
let browser_dir = binaries_dir.join("camoufox").join(&profile.version);
|
||||
|
||||
// Get executable path
|
||||
let browser = crate::browser::create_browser(crate::browser::BrowserType::Camoufox);
|
||||
let executable_path = browser
|
||||
.get_executable_path(&browser_dir)
|
||||
.map_err(|e| format!("Failed to get Camoufox executable path: {e}"))?;
|
||||
|
||||
// Get profile path
|
||||
let profiles_dir = browser_runner.get_profiles_dir();
|
||||
let profile_path = profile.get_profile_data_path(&profiles_dir);
|
||||
|
||||
launcher
|
||||
.launch_camoufox(
|
||||
&executable_path.to_string_lossy(),
|
||||
&profile_path.to_string_lossy(),
|
||||
&config,
|
||||
url.as_deref(),
|
||||
)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to launch Camoufox: {e}"))
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
+27
-2
@@ -13,7 +13,7 @@ mod auto_updater;
|
||||
mod browser;
|
||||
mod browser_runner;
|
||||
mod browser_version_service;
|
||||
mod camoufox;
|
||||
mod camoufox_direct;
|
||||
mod default_browser;
|
||||
mod download;
|
||||
mod downloaded_browsers;
|
||||
@@ -216,7 +216,7 @@ async fn delete_stored_proxy(proxy_id: String) -> Result<(), String> {
|
||||
#[tauri::command]
|
||||
async fn update_camoufox_config(
|
||||
profile_name: String,
|
||||
config: crate::camoufox::CamoufoxConfig,
|
||||
config: crate::camoufox_direct::CamoufoxConfig,
|
||||
) -> Result<(), String> {
|
||||
let browser_runner = browser_runner::BrowserRunner::new();
|
||||
browser_runner
|
||||
@@ -400,6 +400,31 @@ pub fn run() {
|
||||
}
|
||||
});
|
||||
|
||||
// Start Camoufox cleanup task
|
||||
let app_handle_cleanup = app.handle().clone();
|
||||
tauri::async_runtime::spawn(async move {
|
||||
let launcher = crate::camoufox_direct::CamoufoxDirectLauncher::new(app_handle_cleanup);
|
||||
let mut interval = tokio::time::interval(tokio::time::Duration::from_secs(5));
|
||||
|
||||
loop {
|
||||
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()
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Error during Camoufox cleanup: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
|
||||
Reference in New Issue
Block a user