feat: add anti-detect functionality

This commit is contained in:
zhom
2025-07-07 06:19:43 +04:00
parent 198046fca9
commit 703ca2c50b
30 changed files with 5844 additions and 759 deletions
+480 -157
View File
@@ -13,6 +13,7 @@ use crate::browser::{create_browser, BrowserType, ProxySettings};
use crate::browser_version_service::{
BrowserVersionInfo, BrowserVersionService, BrowserVersionsResult,
};
use crate::camoufox::CamoufoxConfig;
use crate::download::{DownloadProgress, Downloader};
use crate::downloaded_browsers::DownloadedBrowsersRegistry;
use crate::extraction::Extractor;
@@ -31,13 +32,15 @@ pub struct BrowserProfile {
pub last_launch: Option<u64>,
#[serde(default = "default_release_type")]
pub release_type: String, // "stable" or "nightly"
#[serde(default)]
pub camoufox_config: Option<CamoufoxConfig>, // Camoufox configuration
}
fn default_release_type() -> String {
"stable".to_string()
}
// Global state to track currently downloading browsers
// Global state to track currently downloading browser-version pairs
lazy_static::lazy_static! {
static ref DOWNLOADING_BROWSERS: Arc<Mutex<HashSet<String>>> = Arc::new(Mutex::new(HashSet::new()));
}
@@ -1347,6 +1350,7 @@ impl BrowserRunner {
version: &str,
release_type: &str,
proxy_id: Option<String>,
camoufox_config: Option<CamoufoxConfig>,
) -> Result<BrowserProfile, Box<dyn std::error::Error>> {
println!("Attempting to create profile: {name}");
@@ -1379,6 +1383,7 @@ impl BrowserRunner {
process_id: None,
last_launch: None,
release_type: release_type.to_string(),
camoufox_config: camoufox_config.clone(),
};
// Save profile info
@@ -1772,10 +1777,109 @@ impl BrowserRunner {
pub async fn launch_browser(
&self,
app_handle: tauri::AppHandle,
profile: &BrowserProfile,
url: Option<String>,
local_proxy_settings: Option<&ProxySettings>,
) -> Result<BrowserProfile, Box<dyn std::error::Error + Send + Sync>> {
// Handle camoufox profiles specially
if profile.browser == "camoufox" {
if let Some(mut camoufox_config) = profile.camoufox_config.clone() {
// Handle proxy settings for camoufox
if let Some(proxy_id) = &profile.proxy_id {
if let Some(stored_proxy) = PROXY_MANAGER.get_proxy_settings_by_id(proxy_id) {
println!("Starting proxy for Camoufox profile: {}", profile.name);
// Start the proxy and get local proxy settings
let local_proxy = PROXY_MANAGER
.start_proxy(
app_handle.clone(),
&stored_proxy,
0, // Use 0 as temporary PID, will be updated later
Some(&profile.name),
)
.await
.map_err(|e| format!("Failed to start proxy for Camoufox: {e}"))?;
// Format proxy URL for camoufox
let proxy_url = format!(
"{}://{}:{}",
if stored_proxy.proxy_type == "socks5" || stored_proxy.proxy_type == "socks4" {
&stored_proxy.proxy_type
} else {
"http"
},
local_proxy.host,
local_proxy.port
);
// Add username and password if available
let proxy_url = if let (Some(username), Some(password)) =
(&stored_proxy.username, &stored_proxy.password)
{
format!(
"{}://{}:{}@{}:{}",
if stored_proxy.proxy_type == "socks5" || stored_proxy.proxy_type == "socks4" {
&stored_proxy.proxy_type
} else {
"http"
},
username,
password,
local_proxy.host,
local_proxy.port
)
} else {
proxy_url
};
// Set proxy in camoufox config
camoufox_config.proxy = Some(proxy_url);
println!("Configured proxy for Camoufox: {:?}", camoufox_config.proxy);
}
}
// Use the camoufox launcher
let camoufox_result = crate::camoufox::launch_camoufox_profile(
app_handle.clone(),
profile.clone(),
camoufox_config,
url,
)
.await
.map_err(|e| -> Box<dyn std::error::Error + Send + Sync> {
format!("Failed to launch camoufox: {e}").into()
})?;
// Update proxy with actual PID if proxy was started
if let Some(pid) = camoufox_result.pid {
if profile.proxy_id.is_some() {
if let Err(e) = PROXY_MANAGER.update_proxy_pid(0, pid) {
println!("Warning: Failed to update proxy PID: {e}");
}
}
}
// Update profile with the process info from camoufox result
let mut updated_profile = profile.clone();
updated_profile.process_id = camoufox_result.pid;
updated_profile.last_launch = Some(SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs());
// Save the updated profile
self.save_process_info(&updated_profile)?;
// 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(updated_profile);
} else {
return Err("Camoufox profile missing configuration".into());
}
}
// Create browser instance
let browser_type = BrowserType::from_str(&profile.browser)
.map_err(|_| format!("Invalid browser type: {}", profile.browser))?;
@@ -1853,91 +1957,85 @@ impl BrowserRunner {
// For TOR and Mullvad browsers, we need to find the actual browser process
// because they use launcher scripts that spawn the real browser process
let actual_pid = if matches!(
let mut actual_pid = launcher_pid;
if matches!(
browser_type,
BrowserType::TorBrowser | BrowserType::MullvadBrowser
) {
println!("Waiting for TOR/Mullvad browser to fully start...");
// Wait a bit for the browser to fully start
// Wait a moment for the actual browser process to start
tokio::time::sleep(tokio::time::Duration::from_millis(3000)).await;
// Search for the actual browser process
// Find the actual browser process
let system = System::new_all();
let mut found_pid: Option<u32> = None;
for (pid, process) in system.processes() {
let process_name = process.name().to_str().unwrap_or("");
let process_cmd = process.cmd();
let pid_u32 = pid.as_u32();
// Try multiple times to find the process as it might take time to start
for attempt in 1..=5 {
println!("Attempt {attempt} to find actual browser process...");
for (pid, process) in system.processes() {
let cmd = process.cmd();
if cmd.len() >= 2 {
// Check if this is the right browser executable
let exe_name = process.name().to_string_lossy().to_lowercase();
let is_correct_browser = match profile.browser.as_str() {
"mullvad-browser" => {
self.is_tor_or_mullvad_browser(&exe_name, cmd, "mullvad-browser")
}
"tor-browser" => self.is_tor_or_mullvad_browser(&exe_name, cmd, "tor-browser"),
_ => false,
};
if !is_correct_browser {
continue;
}
// Check for profile path match
let profile_path_match = cmd.iter().any(|s| {
let arg = s.to_str().unwrap_or("");
arg == profile_data_path.to_string_lossy()
|| arg == format!("-profile={}", profile_data_path.to_string_lossy())
|| (arg == "-profile"
&& cmd
.iter()
.any(|s2| s2.to_str().unwrap_or("") == profile_data_path.to_string_lossy()))
});
if profile_path_match {
found_pid = Some(pid.as_u32());
println!(
"Found actual browser process with PID: {} for profile: {}",
pid.as_u32(),
profile.name
);
break;
}
}
// Skip if this is the launcher process itself
if pid_u32 == launcher_pid {
continue;
}
if found_pid.is_some() {
if self.is_tor_or_mullvad_browser(process_name, process_cmd, &profile.browser) {
println!(
"Found actual {} browser process: PID {} ({})",
profile.browser, pid_u32, process_name
);
actual_pid = pid_u32;
break;
}
// Wait before next attempt
tokio::time::sleep(tokio::time::Duration::from_millis(1000)).await;
}
}
found_pid.unwrap_or(launcher_pid)
} else {
// For other browsers, the launcher PID is usually the actual browser PID
launcher_pid
};
// Update profile with process info
// Update profile with process info and save
let mut updated_profile = profile.clone();
updated_profile.process_id = Some(actual_pid);
updated_profile.last_launch = Some(SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs());
// Save the updated profile
self
.save_process_info(&updated_profile)
.expect("Failed to save process info");
self.save_process_info(&updated_profile)?;
// Apply proxy settings if needed (for Firefox-based browsers)
if profile.proxy_id.is_some()
&& matches!(
browser_type,
BrowserType::Firefox
| BrowserType::FirefoxDeveloper
| BrowserType::Zen
| BrowserType::TorBrowser
| BrowserType::MullvadBrowser
)
{
// Proxy settings for Firefox-based browsers are applied via user.js file
// which is already handled in the profile creation process
}
// Start proxy if configured and needed (for Chromium-based browsers)
if let Some(proxy_id) = &profile.proxy_id {
if let Some(stored_proxy) = PROXY_MANAGER.get_proxy_settings_by_id(proxy_id) {
println!("Starting proxy for profile: {}", profile.name);
match PROXY_MANAGER
.start_proxy(
app_handle.clone(),
&stored_proxy,
actual_pid,
Some(&profile.name),
)
.await
{
Ok(_) => println!("Proxy started successfully for profile: {}", profile.name),
Err(e) => println!("Warning: Failed to start proxy: {e}"),
}
}
}
// 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}");
}
println!(
"Browser launched successfully with PID: {} for profile: {}",
actual_pid, profile.name
);
Ok(updated_profile)
}
@@ -1948,8 +2046,43 @@ impl BrowserRunner {
url: &str,
_internal_proxy_settings: Option<&ProxySettings>,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
// Use the comprehensive browser status check
let is_running = self.check_browser_status(app_handle, profile).await?;
// Handle camoufox profiles specially
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();
// Check if the process is running
match camoufox_launcher
.find_camoufox_by_profile(&profile_path_str)
.await
{
Ok(Some(_camoufox_process)) => {
println!(
"Opening URL in existing Camoufox process for profile: {}",
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());
}
Ok(None) => {
return Err("Camoufox browser is not running".into());
}
Err(e) => {
return Err(format!("Error checking Camoufox process: {e}").into());
}
}
}
// Use the comprehensive browser status check for non-camoufox browsers
let is_running = self
.check_browser_status(app_handle.clone(), profile)
.await?;
if !is_running {
return Err("Browser is not running".into());
@@ -2105,6 +2238,10 @@ impl BrowserRunner {
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
return Err("Unsupported platform".into());
}
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())
}
}
}
@@ -2159,7 +2296,7 @@ impl BrowserRunner {
}
match self
.open_url_in_existing_browser(
app_handle,
app_handle.clone(),
&final_profile,
url_ref,
internal_proxy_settings,
@@ -2188,7 +2325,7 @@ impl BrowserRunner {
final_profile.browser
);
// Fallback to launching a new instance for other browsers
self.launch_browser(&final_profile, url, internal_proxy_settings).await
self.launch_browser(app_handle.clone(), &final_profile, url, internal_proxy_settings).await
}
}
}
@@ -2197,7 +2334,12 @@ impl BrowserRunner {
// This case shouldn't happen since we checked is_some() above, but handle it gracefully
println!("URL was unexpectedly None, launching new browser instance");
self
.launch_browser(&final_profile, url, internal_proxy_settings)
.launch_browser(
app_handle.clone(),
&final_profile,
url,
internal_proxy_settings,
)
.await
}
} else {
@@ -2208,7 +2350,12 @@ impl BrowserRunner {
println!("Launching new browser instance - no URL provided");
}
self
.launch_browser(&final_profile, url, internal_proxy_settings)
.launch_browser(
app_handle.clone(),
&final_profile,
url,
internal_proxy_settings,
)
.await
}
}
@@ -2242,9 +2389,15 @@ impl BrowserRunner {
Ok(profile)
}
fn save_process_info(&self, profile: &BrowserProfile) -> Result<(), Box<dyn std::error::Error>> {
fn save_process_info(
&self,
profile: &BrowserProfile,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
// Use the regular save_profile method which handles the UUID structure
self.save_profile(profile)
self.save_profile(profile).map_err(|e| {
let error_string = e.to_string();
Box::new(std::io::Error::other(error_string)) as Box<dyn std::error::Error + Send + Sync>
})
}
pub fn delete_profile(&self, profile_name: &str) -> Result<(), Box<dyn std::error::Error>> {
@@ -2294,6 +2447,79 @@ 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);
}
}
}
// For non-camoufox browsers, use the existing logic
let mut inner_profile = profile.clone();
let system = System::new_all();
let mut is_running = false;
@@ -2416,67 +2642,21 @@ impl BrowserRunner {
if let Some(pid) = found_pid {
if inner_profile.process_id != Some(pid) {
inner_profile.process_id = Some(pid);
if let Err(e) = self.save_process_info(&inner_profile) {
println!("Warning: Failed to update process info: {e}");
} else {
println!(
"Updated process ID for profile '{}' to: {}",
inner_profile.name, pid
);
if let Err(e) = self.save_profile(&inner_profile) {
println!("Warning: Failed to update profile with new PID: {e}");
}
}
} else if is_running {
println!("Browser is running but no PID found - this shouldn't happen");
} else {
// Browser is not running, clear the PID if it was set
if inner_profile.process_id.is_some() {
inner_profile.process_id = None;
if let Err(e) = self.save_process_info(&inner_profile) {
println!("Warning: Failed to clear process info: {e}");
} else {
println!("Cleared process ID for profile '{}'", inner_profile.name);
}
} else if inner_profile.process_id.is_some() {
// Clear the PID if no process found
inner_profile.process_id = None;
if let Err(e) = self.save_profile(&inner_profile) {
println!("Warning: Failed to clear profile PID: {e}");
}
}
// Handle proxy management based on browser status
if let Some(proxy_id) = &inner_profile.proxy_id {
if let Some(proxy) = PROXY_MANAGER.get_proxy_settings_by_id(proxy_id) {
if is_running {
// Browser is running, check if proxy is active
let proxy_active = PROXY_MANAGER
.get_proxy_settings(inner_profile.process_id.unwrap_or(0))
.is_some();
if !proxy_active {
// Browser is running but proxy is not - restart the proxy
match PROXY_MANAGER
.start_proxy(
app_handle,
&proxy,
inner_profile.process_id.unwrap(),
Some(&inner_profile.name),
)
.await
{
Ok(_) => {
println!("Restarted proxy for profile {}", inner_profile.name);
}
Err(e) => {
eprintln!(
"Failed to restart proxy for profile {}: {}",
inner_profile.name, e
);
}
}
}
} else {
// Browser is not running, stop the proxy if it exists
if let Some(pid) = profile.process_id {
let _ = PROXY_MANAGER.stop_proxy(app_handle, pid).await;
}
}
}
// Emit profile update event to frontend
if let Err(e) = app_handle.emit("profile-updated", &inner_profile) {
println!("Warning: Failed to emit profile update event: {e}");
}
Ok(is_running)
@@ -2487,7 +2667,87 @@ impl BrowserRunner {
app_handle: tauri::AppHandle,
profile: &BrowserProfile,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
// Get the current process ID
// Handle camoufox profiles specially
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!(
"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());
}
}
}
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());
}
}
// Stop proxy if one was running for this profile
if let Some(pid) = profile.process_id {
if let Err(e) = PROXY_MANAGER.stop_proxy(app_handle.clone(), pid).await {
println!("Warning: Failed to stop proxy for Camoufox profile: {e}");
}
}
// Clear the process ID from the profile
let mut updated_profile = profile.clone();
updated_profile.process_id = None;
self
.save_process_info(&updated_profile)
.map_err(|e| format!("Failed to update profile: {e}"))?;
// 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(());
}
// For non-camoufox browsers, use the existing logic
let pid = if let Some(pid) = profile.process_id {
pid
} else {
@@ -2554,7 +2814,7 @@ impl BrowserRunner {
println!("Attempting to kill browser process with PID: {pid}");
// Stop any associated proxy first
if let Err(e) = PROXY_MANAGER.stop_proxy(app_handle, pid).await {
if let Err(e) = PROXY_MANAGER.stop_proxy(app_handle.clone(), pid).await {
println!("Warning: Failed to stop proxy for PID {pid}: {e}");
}
@@ -2667,16 +2927,17 @@ impl BrowserRunner {
browser_str: String,
version: String,
) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
// Check if this browser type is already being downloaded
// Check if this browser-version pair is already being downloaded
let download_key = format!("{browser_str}-{version}");
{
let mut downloading = DOWNLOADING_BROWSERS.lock().unwrap();
if downloading.contains(&browser_str) {
if downloading.contains(&download_key) {
return Err(format!(
"Browser '{browser_str}' is already being downloaded. Please wait for the current download to complete."
"Browser '{browser_str}' version '{version}' is already being downloaded. Please wait for the current download to complete."
).into());
}
// Mark this browser as being downloaded
downloading.insert(browser_str.clone());
// Mark this browser-version pair as being downloaded
downloading.insert(download_key.clone());
}
let browser_type =
@@ -2762,10 +3023,10 @@ impl BrowserRunner {
// Clean up failed download
let _ = registry.cleanup_failed_download(&browser_str, &version);
let _ = registry.save();
// Remove browser from downloading set on error
// Remove browser-version pair from downloading set on error
{
let mut downloading = DOWNLOADING_BROWSERS.lock().unwrap();
downloading.remove(&browser_str);
downloading.remove(&download_key);
}
return Err(format!("Failed to download browser: {e}").into());
}
@@ -2794,10 +3055,10 @@ impl BrowserRunner {
// Clean up failed download
let _ = registry.cleanup_failed_download(&browser_str, &version);
let _ = registry.save();
// Remove browser from downloading set on error
// Remove browser-version pair from downloading set on error
{
let mut downloading = DOWNLOADING_BROWSERS.lock().unwrap();
downloading.remove(&browser_str);
downloading.remove(&download_key);
}
return Err(format!("Failed to extract browser: {e}").into());
}
@@ -2828,10 +3089,10 @@ impl BrowserRunner {
if !browser.is_version_downloaded(&version, &binaries_dir) {
let _ = registry.cleanup_failed_download(&browser_str, &version);
let _ = registry.save();
// Remove browser from downloading set on verification failure
// Remove browser-version pair from downloading set on verification failure
{
let mut downloading = DOWNLOADING_BROWSERS.lock().unwrap();
downloading.remove(&browser_str);
downloading.remove(&download_key);
}
return Err("Browser download completed but verification failed".into());
}
@@ -2850,6 +3111,26 @@ impl BrowserRunner {
.save()
.map_err(|e| format!("Failed to save registry: {e}"))?;
// If this is Camoufox, automatically download GeoIP database
if browser_str == "camoufox" {
use crate::geoip_downloader::GeoIPDownloader;
// Check if GeoIP database is already available
if !GeoIPDownloader::is_geoip_database_available() {
println!("Downloading GeoIP database for Camoufox...");
let geoip_downloader = GeoIPDownloader::new();
if let Err(e) = geoip_downloader.download_geoip_database(&app_handle).await {
eprintln!("Warning: Failed to download GeoIP database: {e}");
// Don't fail the browser download if GeoIP download fails
} else {
println!("GeoIP database downloaded successfully");
}
} else {
println!("GeoIP database already available");
}
}
// Emit completion
let progress = DownloadProgress {
browser: browser_str.clone(),
@@ -2863,10 +3144,10 @@ impl BrowserRunner {
};
let _ = app_handle.emit("download-progress", &progress);
// Remove browser from downloading set
// Remove browser-version pair from downloading set
{
let mut downloading = DOWNLOADING_BROWSERS.lock().unwrap();
downloading.remove(&browser_str);
downloading.remove(&download_key);
}
Ok(version)
@@ -2899,6 +3180,34 @@ 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 {
@@ -2915,10 +3224,18 @@ pub fn create_browser_profile(
version: String,
release_type: String,
proxy_id: Option<String>,
camoufox_config: Option<CamoufoxConfig>,
) -> Result<BrowserProfile, String> {
let browser_runner = BrowserRunner::new();
browser_runner
.create_profile(&name, &browser, &version, &release_type, proxy_id)
.create_profile(
&name,
&browser,
&version,
&release_type,
proxy_id,
camoufox_config,
)
.map_err(|e| format!("Failed to create profile: {e}"))
}
@@ -3207,6 +3524,7 @@ pub fn create_browser_profile_new(
version: String,
release_type: String,
proxy_id: Option<String>,
camoufox_config: Option<CamoufoxConfig>,
) -> Result<BrowserProfile, String> {
let browser_type =
BrowserType::from_str(&browser_str).map_err(|e| format!("Invalid browser type: {e}"))?;
@@ -3216,6 +3534,7 @@ pub fn create_browser_profile_new(
version,
release_type,
proxy_id,
camoufox_config,
)
}
@@ -3313,7 +3632,7 @@ mod tests {
let (runner, _temp_dir) = create_test_browser_runner();
let profile = runner
.create_profile("Test Profile", "firefox", "139.0", "stable", None)
.create_profile("Test Profile", "firefox", "139.0", "stable", None, None)
.unwrap();
assert_eq!(profile.name, "Test Profile");
@@ -3342,6 +3661,7 @@ mod tests {
"139.0",
"stable",
None, // Tests now use separate proxy storage system
None, // No camoufox config for this test
)
.unwrap();
@@ -3355,7 +3675,7 @@ mod tests {
let (runner, _temp_dir) = create_test_browser_runner();
let profile = runner
.create_profile("Test Save Load", "firefox", "139.0", "stable", None)
.create_profile("Test Save Load", "firefox", "139.0", "stable", None, None)
.unwrap();
// Save the profile
@@ -3375,7 +3695,7 @@ mod tests {
// Create profile
let _ = runner
.create_profile("Original Name", "firefox", "139.0", "stable", None)
.create_profile("Original Name", "firefox", "139.0", "stable", None, None)
.unwrap();
// Rename profile
@@ -3395,7 +3715,7 @@ mod tests {
// Create profile
let _ = runner
.create_profile("To Delete", "firefox", "139.0", "stable", None)
.create_profile("To Delete", "firefox", "139.0", "stable", None, None)
.unwrap();
// Verify profile exists
@@ -3422,6 +3742,7 @@ mod tests {
"139.0",
"stable",
None,
None,
)
.unwrap();
@@ -3444,13 +3765,13 @@ mod tests {
// Create multiple profiles
let _ = runner
.create_profile("Profile 1", "firefox", "139.0", "stable", None)
.create_profile("Profile 1", "firefox", "139.0", "stable", None, None)
.unwrap();
let _ = runner
.create_profile("Profile 2", "chromium", "1465660", "stable", None)
.create_profile("Profile 2", "chromium", "1465660", "stable", None, None)
.unwrap();
let _ = runner
.create_profile("Profile 3", "brave", "v1.81.9", "stable", None)
.create_profile("Profile 3", "brave", "v1.81.9", "stable", None, None)
.unwrap();
// List profiles
@@ -3469,10 +3790,10 @@ mod tests {
// Test that we can't rename to an existing profile name
let _ = runner
.create_profile("Profile 1", "firefox", "139.0", "stable", None)
.create_profile("Profile 1", "firefox", "139.0", "stable", None, None)
.unwrap();
let _ = runner
.create_profile("Profile 2", "firefox", "139.0", "stable", None)
.create_profile("Profile 2", "firefox", "139.0", "stable", None, None)
.unwrap();
// Try to rename profile2 to profile1's name (should fail)
@@ -3493,6 +3814,7 @@ mod tests {
"139.0",
"stable",
None,
None,
)
.unwrap();
@@ -3526,6 +3848,7 @@ mod tests {
"139.0",
"stable",
None, // Tests now use separate proxy storage system
None, // No camoufox config for this test
)
.unwrap();