mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-08-05 18:58:40 +02:00
refactor: remove executable_path
This commit is contained in:
@@ -689,7 +689,6 @@ impl Browser for WayfernBrowser {
|
|||||||
"--disable-session-crashed-bubble".to_string(),
|
"--disable-session-crashed-bubble".to_string(),
|
||||||
"--hide-crash-restore-bubble".to_string(),
|
"--hide-crash-restore-bubble".to_string(),
|
||||||
"--disable-infobars".to_string(),
|
"--disable-infobars".to_string(),
|
||||||
"--disable-quic".to_string(),
|
|
||||||
// Wayfern-specific args for automation
|
// Wayfern-specific args for automation
|
||||||
"--disable-features=DialMediaRouteProvider".to_string(),
|
"--disable-features=DialMediaRouteProvider".to_string(),
|
||||||
"--use-mock-keychain".to_string(),
|
"--use-mock-keychain".to_string(),
|
||||||
@@ -1166,6 +1165,67 @@ mod tests {
|
|||||||
assert_eq!(deserialized.host, proxy.host, "Host should match");
|
assert_eq!(deserialized.host, proxy.host, "Host should match");
|
||||||
assert_eq!(deserialized.port, proxy.port, "Port should match");
|
assert_eq!(deserialized.port, proxy.port, "Port should match");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_wayfern_config_has_no_executable_path() {
|
||||||
|
// Verify WayfernConfig does not store executable_path
|
||||||
|
let config = crate::wayfern_manager::WayfernConfig::default();
|
||||||
|
let json = serde_json::to_value(&config).unwrap();
|
||||||
|
assert!(
|
||||||
|
json.get("executable_path").is_none(),
|
||||||
|
"WayfernConfig should not have executable_path field"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_camoufox_config_has_no_executable_path() {
|
||||||
|
// Verify CamoufoxConfig does not store executable_path
|
||||||
|
let config = crate::camoufox_manager::CamoufoxConfig::default();
|
||||||
|
let json = serde_json::to_value(&config).unwrap();
|
||||||
|
assert!(
|
||||||
|
json.get("executable_path").is_none(),
|
||||||
|
"CamoufoxConfig should not have executable_path field"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_profile_data_path_is_dynamic() {
|
||||||
|
use crate::profile::BrowserProfile;
|
||||||
|
let profiles_dir = std::path::PathBuf::from("/fake/profiles");
|
||||||
|
let profile = BrowserProfile {
|
||||||
|
id: uuid::Uuid::parse_str("12345678-1234-1234-1234-123456789abc").unwrap(),
|
||||||
|
name: "test".to_string(),
|
||||||
|
browser: "wayfern".to_string(),
|
||||||
|
version: "1.0.0".to_string(),
|
||||||
|
proxy_id: None,
|
||||||
|
vpn_id: None,
|
||||||
|
process_id: None,
|
||||||
|
last_launch: None,
|
||||||
|
release_type: "stable".to_string(),
|
||||||
|
camoufox_config: None,
|
||||||
|
wayfern_config: None,
|
||||||
|
group_id: None,
|
||||||
|
tags: Vec::new(),
|
||||||
|
note: None,
|
||||||
|
sync_mode: crate::profile::types::SyncMode::Disabled,
|
||||||
|
encryption_salt: None,
|
||||||
|
last_sync: None,
|
||||||
|
host_os: None,
|
||||||
|
ephemeral: false,
|
||||||
|
extension_group_id: None,
|
||||||
|
proxy_bypass_rules: Vec::new(),
|
||||||
|
created_by_id: None,
|
||||||
|
created_by_email: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let path = profile.get_profile_data_path(&profiles_dir);
|
||||||
|
assert_eq!(
|
||||||
|
path,
|
||||||
|
profiles_dir
|
||||||
|
.join("12345678-1234-1234-1234-123456789abc")
|
||||||
|
.join("profile")
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Global singleton instance
|
// Global singleton instance
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ pub struct CamoufoxConfig {
|
|||||||
pub block_images: Option<bool>,
|
pub block_images: Option<bool>,
|
||||||
pub block_webrtc: Option<bool>,
|
pub block_webrtc: Option<bool>,
|
||||||
pub block_webgl: Option<bool>,
|
pub block_webgl: Option<bool>,
|
||||||
pub executable_path: Option<String>,
|
|
||||||
pub fingerprint: Option<String>, // JSON string of the complete fingerprint config
|
pub fingerprint: Option<String>, // JSON string of the complete fingerprint config
|
||||||
pub randomize_fingerprint_on_launch: Option<bool>, // Generate new fingerprint on every launch
|
pub randomize_fingerprint_on_launch: Option<bool>, // Generate new fingerprint on every launch
|
||||||
pub os: Option<String>, // Operating system for fingerprint generation: "windows", "macos", or "linux"
|
pub os: Option<String>, // Operating system for fingerprint generation: "windows", "macos", or "linux"
|
||||||
@@ -39,7 +38,6 @@ impl Default for CamoufoxConfig {
|
|||||||
block_images: None,
|
block_images: None,
|
||||||
block_webrtc: None,
|
block_webrtc: None,
|
||||||
block_webgl: None,
|
block_webgl: None,
|
||||||
executable_path: None,
|
|
||||||
fingerprint: None,
|
fingerprint: None,
|
||||||
randomize_fingerprint_on_launch: None,
|
randomize_fingerprint_on_launch: None,
|
||||||
os: None,
|
os: None,
|
||||||
@@ -129,21 +127,9 @@ impl CamoufoxManager {
|
|||||||
config: &CamoufoxConfig,
|
config: &CamoufoxConfig,
|
||||||
) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
|
) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
|
||||||
// Get executable path
|
// Get executable path
|
||||||
let executable_path = if let Some(path) = &config.executable_path {
|
let executable_path = BrowserRunner::instance()
|
||||||
let p = PathBuf::from(path);
|
.get_browser_executable_path(profile)
|
||||||
if p.exists() {
|
.map_err(|e| format!("Failed to get Camoufox executable path: {e}"))?;
|
||||||
p
|
|
||||||
} else {
|
|
||||||
log::warn!("Stored Camoufox executable path does not exist: {path}, falling back to dynamic resolution");
|
|
||||||
BrowserRunner::instance()
|
|
||||||
.get_browser_executable_path(profile)
|
|
||||||
.map_err(|e| format!("Failed to get Camoufox executable path: {e}"))?
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
BrowserRunner::instance()
|
|
||||||
.get_browser_executable_path(profile)
|
|
||||||
.map_err(|e| format!("Failed to get Camoufox executable path: {e}"))?
|
|
||||||
};
|
|
||||||
|
|
||||||
// Build the config using CamoufoxConfigBuilder
|
// Build the config using CamoufoxConfigBuilder
|
||||||
let mut builder = CamoufoxConfigBuilder::new()
|
let mut builder = CamoufoxConfigBuilder::new()
|
||||||
@@ -230,21 +216,9 @@ impl CamoufoxManager {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Get executable path
|
// Get executable path
|
||||||
let executable_path = if let Some(path) = &config.executable_path {
|
let executable_path = BrowserRunner::instance()
|
||||||
let p = PathBuf::from(path);
|
.get_browser_executable_path(profile)
|
||||||
if p.exists() {
|
.map_err(|e| format!("Failed to get Camoufox executable path: {e}"))?;
|
||||||
p
|
|
||||||
} else {
|
|
||||||
log::warn!("Stored Camoufox executable path does not exist: {path}, falling back to dynamic resolution");
|
|
||||||
BrowserRunner::instance()
|
|
||||||
.get_browser_executable_path(profile)
|
|
||||||
.map_err(|e| format!("Failed to get Camoufox executable path: {e}"))?
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
BrowserRunner::instance()
|
|
||||||
.get_browser_executable_path(profile)
|
|
||||||
.map_err(|e| format!("Failed to get Camoufox executable path: {e}"))?
|
|
||||||
};
|
|
||||||
|
|
||||||
// Parse the fingerprint config JSON
|
// Parse the fingerprint config JSON
|
||||||
let fingerprint_config: HashMap<String, serde_json::Value> =
|
let fingerprint_config: HashMap<String, serde_json::Value> =
|
||||||
|
|||||||
@@ -207,6 +207,20 @@ impl Extractor {
|
|||||||
|
|
||||||
match extraction_result {
|
match extraction_result {
|
||||||
Ok(path) => {
|
Ok(path) => {
|
||||||
|
// Remove quarantine attributes on macOS to prevent
|
||||||
|
// "app was prevented from modifying data" prompts
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
{
|
||||||
|
let _ = tokio::process::Command::new("xattr")
|
||||||
|
.args([
|
||||||
|
"-dr",
|
||||||
|
"com.apple.quarantine",
|
||||||
|
dest_dir.to_str().unwrap_or("."),
|
||||||
|
])
|
||||||
|
.output()
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
log::info!(
|
log::info!(
|
||||||
"Successfully extracted {} {} to: {}",
|
"Successfully extracted {} {} to: {}",
|
||||||
browser_type.as_str(),
|
browser_type.as_str(),
|
||||||
|
|||||||
@@ -356,12 +356,6 @@ async fn copy_profile_cookies(
|
|||||||
app_handle: tauri::AppHandle,
|
app_handle: tauri::AppHandle,
|
||||||
request: cookie_manager::CookieCopyRequest,
|
request: cookie_manager::CookieCopyRequest,
|
||||||
) -> Result<Vec<cookie_manager::CookieCopyResult>, String> {
|
) -> Result<Vec<cookie_manager::CookieCopyResult>, String> {
|
||||||
if !crate::cloud_auth::CLOUD_AUTH
|
|
||||||
.has_active_paid_subscription()
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
return Err("Cookie copying requires an active Pro subscription".to_string());
|
|
||||||
}
|
|
||||||
let target_ids = request.target_profile_ids.clone();
|
let target_ids = request.target_profile_ids.clone();
|
||||||
let results = cookie_manager::CookieManager::copy_cookies(&app_handle, request).await?;
|
let results = cookie_manager::CookieManager::copy_cookies(&app_handle, request).await?;
|
||||||
|
|
||||||
@@ -397,12 +391,6 @@ async fn import_cookies_from_file(
|
|||||||
profile_id: String,
|
profile_id: String,
|
||||||
content: String,
|
content: String,
|
||||||
) -> Result<cookie_manager::CookieImportResult, String> {
|
) -> Result<cookie_manager::CookieImportResult, String> {
|
||||||
if !crate::cloud_auth::CLOUD_AUTH
|
|
||||||
.has_active_paid_subscription()
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
return Err("Cookie import requires an active Pro subscription".to_string());
|
|
||||||
}
|
|
||||||
let result =
|
let result =
|
||||||
cookie_manager::CookieManager::import_cookies(&app_handle, &profile_id, &content).await?;
|
cookie_manager::CookieManager::import_cookies(&app_handle, &profile_id, &content).await?;
|
||||||
|
|
||||||
@@ -426,12 +414,6 @@ async fn import_cookies_from_file(
|
|||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn export_profile_cookies(profile_id: String, format: String) -> Result<String, String> {
|
async fn export_profile_cookies(profile_id: String, format: String) -> Result<String, String> {
|
||||||
if !crate::cloud_auth::CLOUD_AUTH
|
|
||||||
.has_active_paid_subscription()
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
return Err("Cookie export requires an active Pro subscription".to_string());
|
|
||||||
}
|
|
||||||
cookie_manager::CookieManager::export_cookies(&profile_id, &format)
|
cookie_manager::CookieManager::export_cookies(&profile_id, &format)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -532,27 +532,6 @@ impl ProfileImporter {
|
|||||||
let final_camoufox_config = if mapped == "camoufox" {
|
let final_camoufox_config = if mapped == "camoufox" {
|
||||||
let mut config = camoufox_config.unwrap_or_default();
|
let mut config = camoufox_config.unwrap_or_default();
|
||||||
|
|
||||||
if config.executable_path.is_none() {
|
|
||||||
let mut browser_dir = self.profile_manager.get_binaries_dir();
|
|
||||||
browser_dir.push(mapped);
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(ref proxy_id_val) = proxy_id {
|
if let Some(ref proxy_id_val) = proxy_id {
|
||||||
if let Some(proxy_settings) = PROXY_MANAGER.get_proxy_settings_by_id(proxy_id_val) {
|
if let Some(proxy_settings) = PROXY_MANAGER.get_proxy_settings_by_id(proxy_id_val) {
|
||||||
let proxy_url = if let (Some(username), Some(password)) =
|
let proxy_url = if let (Some(username), Some(password)) =
|
||||||
@@ -631,27 +610,6 @@ impl ProfileImporter {
|
|||||||
let final_wayfern_config = if mapped == "wayfern" {
|
let final_wayfern_config = if mapped == "wayfern" {
|
||||||
let mut config = wayfern_config.unwrap_or_default();
|
let mut config = wayfern_config.unwrap_or_default();
|
||||||
|
|
||||||
if config.executable_path.is_none() {
|
|
||||||
let mut browser_dir = self.profile_manager.get_binaries_dir();
|
|
||||||
browser_dir.push(mapped);
|
|
||||||
browser_dir.push(&version);
|
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
let binary_path = browser_dir
|
|
||||||
.join("Chromium.app")
|
|
||||||
.join("Contents")
|
|
||||||
.join("MacOS")
|
|
||||||
.join("Chromium");
|
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
let binary_path = browser_dir.join("chrome.exe");
|
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
let binary_path = browser_dir.join("chrome");
|
|
||||||
|
|
||||||
config.executable_path = Some(binary_path.to_string_lossy().to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(ref proxy_id_val) = proxy_id {
|
if let Some(ref proxy_id_val) = proxy_id {
|
||||||
if let Some(proxy_settings) = PROXY_MANAGER.get_proxy_settings_by_id(proxy_id_val) {
|
if let Some(proxy_settings) = PROXY_MANAGER.get_proxy_settings_by_id(proxy_id_val) {
|
||||||
let proxy_url = if let (Some(username), Some(password)) =
|
let proxy_url = if let (Some(username), Some(password)) =
|
||||||
|
|||||||
@@ -1802,8 +1802,11 @@ export function ProfilesDataTable({
|
|||||||
const isLaunching = meta.launchingProfiles.has(profile.id);
|
const isLaunching = meta.launchingProfiles.has(profile.id);
|
||||||
const isStopping = meta.stoppingProfiles.has(profile.id);
|
const isStopping = meta.stoppingProfiles.has(profile.id);
|
||||||
const isLockedByAnother = meta.isProfileLockedByAnother(profile.id);
|
const isLockedByAnother = meta.isProfileLockedByAnother(profile.id);
|
||||||
|
const isSyncing = meta.syncStatuses[profile.id]?.status === "syncing";
|
||||||
const canLaunch =
|
const canLaunch =
|
||||||
meta.browserState.canLaunchProfile(profile) && !isLockedByAnother;
|
meta.browserState.canLaunchProfile(profile) &&
|
||||||
|
!isLockedByAnother &&
|
||||||
|
!isSyncing;
|
||||||
const lockEmail = meta.getProfileLockEmail(profile.id);
|
const lockEmail = meta.getProfileLockEmail(profile.id);
|
||||||
const tooltipContent = isLockedByAnother
|
const tooltipContent = isLockedByAnother
|
||||||
? meta.t("sync.team.cannotLaunchLocked", { email: lockEmail })
|
? meta.t("sync.team.cannotLaunchLocked", { email: lockEmail })
|
||||||
@@ -1831,13 +1834,14 @@ export function ProfilesDataTable({
|
|||||||
);
|
);
|
||||||
try {
|
try {
|
||||||
await meta.onLaunchProfile(profile);
|
await meta.onLaunchProfile(profile);
|
||||||
} catch (error) {
|
} finally {
|
||||||
|
// Always clear launching state — the running state is tracked
|
||||||
|
// separately via profile-running-changed events
|
||||||
meta.setLaunchingProfiles((prev: Set<string>) => {
|
meta.setLaunchingProfiles((prev: Set<string>) => {
|
||||||
const next = new Set(prev);
|
const next = new Set(prev);
|
||||||
next.delete(profile.id);
|
next.delete(profile.id);
|
||||||
return next;
|
return next;
|
||||||
});
|
});
|
||||||
throw error;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -2665,40 +2669,20 @@ export function ProfilesDataTable({
|
|||||||
)}
|
)}
|
||||||
{onBulkExtensionGroupAssignment && (
|
{onBulkExtensionGroupAssignment && (
|
||||||
<DataTableActionBarAction
|
<DataTableActionBarAction
|
||||||
tooltip={
|
tooltip="Assign Extension Group"
|
||||||
crossOsUnlocked
|
|
||||||
? "Assign Extension Group"
|
|
||||||
: "Assign Extension Group (Pro)"
|
|
||||||
}
|
|
||||||
onClick={onBulkExtensionGroupAssignment}
|
onClick={onBulkExtensionGroupAssignment}
|
||||||
size="icon"
|
size="icon"
|
||||||
disabled={!crossOsUnlocked}
|
|
||||||
>
|
>
|
||||||
<span className="relative">
|
<LuPuzzle />
|
||||||
<LuPuzzle />
|
|
||||||
{!crossOsUnlocked && (
|
|
||||||
<span className="absolute -bottom-1.5 left-1/2 -translate-x-1/2 text-[6px] font-bold leading-none bg-primary text-primary-foreground px-0.5 rounded-sm">
|
|
||||||
PRO
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
</DataTableActionBarAction>
|
</DataTableActionBarAction>
|
||||||
)}
|
)}
|
||||||
{onBulkCopyCookies && (
|
{onBulkCopyCookies && (
|
||||||
<DataTableActionBarAction
|
<DataTableActionBarAction
|
||||||
tooltip={crossOsUnlocked ? "Copy Cookies" : "Copy Cookies (Pro)"}
|
tooltip="Copy Cookies"
|
||||||
onClick={onBulkCopyCookies}
|
onClick={onBulkCopyCookies}
|
||||||
size="icon"
|
size="icon"
|
||||||
disabled={!crossOsUnlocked}
|
|
||||||
>
|
>
|
||||||
<span className="relative">
|
<LuCookie />
|
||||||
<LuCookie />
|
|
||||||
{!crossOsUnlocked && (
|
|
||||||
<span className="absolute -bottom-1.5 left-1/2 -translate-x-1/2 text-[6px] font-bold leading-none bg-primary text-primary-foreground px-0.5 rounded-sm">
|
|
||||||
PRO
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
</DataTableActionBarAction>
|
</DataTableActionBarAction>
|
||||||
)}
|
)}
|
||||||
{onBulkDelete && (
|
{onBulkDelete && (
|
||||||
|
|||||||
@@ -266,9 +266,8 @@ export function ProfileInfoDialog({
|
|||||||
icon: <LuCopy className="w-4 h-4" />,
|
icon: <LuCopy className="w-4 h-4" />,
|
||||||
label: t("profiles.actions.copyCookiesToProfile"),
|
label: t("profiles.actions.copyCookiesToProfile"),
|
||||||
onClick: () => handleAction(() => onCopyCookiesToProfile?.(profile)),
|
onClick: () => handleAction(() => onCopyCookiesToProfile?.(profile)),
|
||||||
disabled: isDisabled || !crossOsUnlocked,
|
disabled: isDisabled,
|
||||||
proBadge: !crossOsUnlocked,
|
runningBadge: isRunning,
|
||||||
runningBadge: isRunning && crossOsUnlocked,
|
|
||||||
hidden:
|
hidden:
|
||||||
!isCamoufoxOrWayfern ||
|
!isCamoufoxOrWayfern ||
|
||||||
profile.ephemeral === true ||
|
profile.ephemeral === true ||
|
||||||
@@ -278,9 +277,8 @@ export function ProfileInfoDialog({
|
|||||||
icon: <LuCookie className="w-4 h-4" />,
|
icon: <LuCookie className="w-4 h-4" />,
|
||||||
label: t("profileInfo.actions.manageCookies"),
|
label: t("profileInfo.actions.manageCookies"),
|
||||||
onClick: () => handleAction(() => onOpenCookieManagement?.(profile)),
|
onClick: () => handleAction(() => onOpenCookieManagement?.(profile)),
|
||||||
disabled: isDisabled || !crossOsUnlocked,
|
disabled: isDisabled,
|
||||||
proBadge: !crossOsUnlocked,
|
runningBadge: isRunning,
|
||||||
runningBadge: isRunning && crossOsUnlocked,
|
|
||||||
hidden:
|
hidden:
|
||||||
!isCamoufoxOrWayfern ||
|
!isCamoufoxOrWayfern ||
|
||||||
profile.ephemeral === true ||
|
profile.ephemeral === true ||
|
||||||
|
|||||||
Reference in New Issue
Block a user