mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-06-02 13:21:36 +02:00
feat: ephemeral profiles
This commit is contained in:
@@ -605,6 +605,7 @@ async fn create_profile(
|
||||
camoufox_config,
|
||||
wayfern_config,
|
||||
request.group_id.clone(),
|
||||
false,
|
||||
)
|
||||
.await
|
||||
{
|
||||
|
||||
@@ -522,6 +522,7 @@ mod tests {
|
||||
sync_enabled: false,
|
||||
last_sync: None,
|
||||
host_os: None,
|
||||
ephemeral: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -229,6 +229,15 @@ impl BrowserRunner {
|
||||
log::warn!("Failed to configure Camoufox search engine: {e}");
|
||||
}
|
||||
|
||||
// Create ephemeral dir for ephemeral profiles
|
||||
let override_profile_path = if profile.ephemeral {
|
||||
let dir = crate::ephemeral_dirs::create_ephemeral_dir(&profile.id.to_string())
|
||||
.map_err(|e| -> Box<dyn std::error::Error + Send + Sync> { e.into() })?;
|
||||
Some(dir)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Launch Camoufox browser
|
||||
log::info!("Launching Camoufox for profile: {}", profile.name);
|
||||
let camoufox_result = self
|
||||
@@ -238,6 +247,7 @@ impl BrowserRunner {
|
||||
updated_profile.clone(),
|
||||
camoufox_config,
|
||||
url,
|
||||
override_profile_path,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| -> Box<dyn std::error::Error + Send + Sync> {
|
||||
@@ -441,12 +451,19 @@ impl BrowserRunner {
|
||||
);
|
||||
}
|
||||
|
||||
// Create ephemeral dir for ephemeral profiles
|
||||
if profile.ephemeral {
|
||||
crate::ephemeral_dirs::create_ephemeral_dir(&profile.id.to_string())
|
||||
.map_err(|e| -> Box<dyn std::error::Error + Send + Sync> { e.into() })?;
|
||||
}
|
||||
|
||||
// Launch Wayfern browser
|
||||
log::info!("Launching Wayfern for profile: {}", profile.name);
|
||||
|
||||
// Get profile path for Wayfern
|
||||
let profiles_dir = self.profile_manager.get_profiles_dir();
|
||||
let profile_data_path = updated_profile.get_profile_data_path(&profiles_dir);
|
||||
let profile_data_path =
|
||||
crate::ephemeral_dirs::get_effective_profile_path(&updated_profile, &profiles_dir);
|
||||
let profile_path_str = profile_data_path.to_string_lossy().to_string();
|
||||
|
||||
// Get proxy URL from config
|
||||
@@ -461,6 +478,7 @@ impl BrowserRunner {
|
||||
&wayfern_config,
|
||||
url.as_deref(),
|
||||
proxy_url,
|
||||
profile.ephemeral,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| -> Box<dyn std::error::Error + Send + Sync> {
|
||||
@@ -793,7 +811,8 @@ impl BrowserRunner {
|
||||
if profile.browser == "camoufox" {
|
||||
// Get the profile path based on the UUID
|
||||
let profiles_dir = self.profile_manager.get_profiles_dir();
|
||||
let profile_data_path = profile.get_profile_data_path(&profiles_dir);
|
||||
let profile_data_path =
|
||||
crate::ephemeral_dirs::get_effective_profile_path(profile, &profiles_dir);
|
||||
let profile_path_str = profile_data_path.to_string_lossy();
|
||||
|
||||
// Check if the process is running
|
||||
@@ -847,7 +866,8 @@ impl BrowserRunner {
|
||||
// Handle Wayfern profiles using WayfernManager
|
||||
if profile.browser == "wayfern" {
|
||||
let profiles_dir = self.profile_manager.get_profiles_dir();
|
||||
let profile_data_path = profile.get_profile_data_path(&profiles_dir);
|
||||
let profile_data_path =
|
||||
crate::ephemeral_dirs::get_effective_profile_path(profile, &profiles_dir);
|
||||
let profile_path_str = profile_data_path.to_string_lossy();
|
||||
|
||||
// Check if the process is running
|
||||
@@ -1245,7 +1265,8 @@ impl BrowserRunner {
|
||||
if profile.browser == "camoufox" {
|
||||
// Search by profile path to find the running Camoufox instance
|
||||
let profiles_dir = self.profile_manager.get_profiles_dir();
|
||||
let profile_data_path = profile.get_profile_data_path(&profiles_dir);
|
||||
let profile_data_path =
|
||||
crate::ephemeral_dirs::get_effective_profile_path(profile, &profiles_dir);
|
||||
let profile_path_str = profile_data_path.to_string_lossy();
|
||||
|
||||
log::info!(
|
||||
@@ -1663,6 +1684,10 @@ impl BrowserRunner {
|
||||
);
|
||||
}
|
||||
|
||||
if profile.ephemeral {
|
||||
crate::ephemeral_dirs::remove_ephemeral_dir(&profile.id.to_string());
|
||||
}
|
||||
|
||||
log::info!(
|
||||
"Camoufox process cleanup completed for profile: {} (ID: {})",
|
||||
profile.name,
|
||||
@@ -1688,7 +1713,8 @@ impl BrowserRunner {
|
||||
// Handle Wayfern profiles using WayfernManager
|
||||
if profile.browser == "wayfern" {
|
||||
let profiles_dir = self.profile_manager.get_profiles_dir();
|
||||
let profile_data_path = profile.get_profile_data_path(&profiles_dir);
|
||||
let profile_data_path =
|
||||
crate::ephemeral_dirs::get_effective_profile_path(profile, &profiles_dir);
|
||||
let profile_path_str = profile_data_path.to_string_lossy();
|
||||
|
||||
log::info!(
|
||||
@@ -1981,6 +2007,10 @@ impl BrowserRunner {
|
||||
);
|
||||
}
|
||||
|
||||
if profile.ephemeral {
|
||||
crate::ephemeral_dirs::remove_ephemeral_dir(&profile.id.to_string());
|
||||
}
|
||||
|
||||
log::info!(
|
||||
"Wayfern process cleanup completed for profile: {} (ID: {})",
|
||||
profile.name,
|
||||
|
||||
@@ -582,10 +582,15 @@ impl CamoufoxManager {
|
||||
profile: BrowserProfile,
|
||||
config: CamoufoxConfig,
|
||||
url: Option<String>,
|
||||
override_profile_path: Option<std::path::PathBuf>,
|
||||
) -> Result<CamoufoxLaunchResult, String> {
|
||||
// Get profile path
|
||||
let profiles_dir = self.get_profiles_dir();
|
||||
let profile_path = profile.get_profile_data_path(&profiles_dir);
|
||||
let profile_path = if let Some(ref override_path) = override_profile_path {
|
||||
override_path.clone()
|
||||
} else {
|
||||
let profiles_dir = self.get_profiles_dir();
|
||||
profile.get_profile_data_path(&profiles_dir)
|
||||
};
|
||||
let profile_path_str = profile_path.to_string_lossy();
|
||||
|
||||
// Check if there's already a running instance for this profile
|
||||
@@ -597,6 +602,24 @@ impl CamoufoxManager {
|
||||
// Clean up any dead instances before launching
|
||||
let _ = self.cleanup_dead_instances().await;
|
||||
|
||||
// For ephemeral profiles, write Firefox prefs to keep all data inside the profile dir
|
||||
if override_profile_path.is_some() {
|
||||
let cache_dir = profile_path.join("cache2");
|
||||
let user_js_path = profile_path.join("user.js");
|
||||
let prefs = format!(
|
||||
concat!(
|
||||
"user_pref(\"browser.cache.disk.parent_directory\", \"{}\");\n",
|
||||
"user_pref(\"browser.cache.disk.enable\", false);\n",
|
||||
"user_pref(\"browser.cache.memory.enable\", true);\n",
|
||||
"user_pref(\"browser.privatebrowsing.autostart\", true);\n",
|
||||
),
|
||||
cache_dir.to_string_lossy().replace('\\', "\\\\"),
|
||||
);
|
||||
if let Err(e) = std::fs::write(&user_js_path, prefs) {
|
||||
log::warn!("Failed to write ephemeral user.js: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
self
|
||||
.launch_camoufox(
|
||||
&app_handle,
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
use std::collections::HashMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Mutex;
|
||||
|
||||
use crate::profile::BrowserProfile;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref EPHEMERAL_DIRS: Mutex<HashMap<String, PathBuf>> = Mutex::new(HashMap::new());
|
||||
}
|
||||
|
||||
pub fn create_ephemeral_dir(profile_id: &str) -> Result<PathBuf, String> {
|
||||
let dir_name = format!("donut-ephemeral-{profile_id}");
|
||||
let dir_path = std::env::temp_dir().join(dir_name);
|
||||
|
||||
std::fs::create_dir_all(&dir_path).map_err(|e| format!("Failed to create ephemeral dir: {e}"))?;
|
||||
|
||||
EPHEMERAL_DIRS
|
||||
.lock()
|
||||
.map_err(|e| format!("Failed to lock ephemeral dirs: {e}"))?
|
||||
.insert(profile_id.to_string(), dir_path.clone());
|
||||
|
||||
log::info!(
|
||||
"Created ephemeral dir for profile {}: {}",
|
||||
profile_id,
|
||||
dir_path.display()
|
||||
);
|
||||
|
||||
Ok(dir_path)
|
||||
}
|
||||
|
||||
pub fn get_ephemeral_dir(profile_id: &str) -> Option<PathBuf> {
|
||||
EPHEMERAL_DIRS.lock().ok()?.get(profile_id).cloned()
|
||||
}
|
||||
|
||||
pub fn remove_ephemeral_dir(profile_id: &str) {
|
||||
let dir = EPHEMERAL_DIRS
|
||||
.lock()
|
||||
.ok()
|
||||
.and_then(|mut map| map.remove(profile_id));
|
||||
|
||||
if let Some(dir_path) = dir {
|
||||
if dir_path.exists() {
|
||||
if let Err(e) = std::fs::remove_dir_all(&dir_path) {
|
||||
log::warn!("Failed to remove ephemeral dir {}: {e}", dir_path.display());
|
||||
} else {
|
||||
log::info!(
|
||||
"Removed ephemeral dir for profile {}: {}",
|
||||
profile_id,
|
||||
dir_path.display()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cleanup_stale_dirs() {
|
||||
let temp_dir = std::env::temp_dir();
|
||||
let entries = match std::fs::read_dir(&temp_dir) {
|
||||
Ok(entries) => entries,
|
||||
Err(e) => {
|
||||
log::warn!("Failed to read temp dir for ephemeral cleanup: {e}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
for entry in entries.flatten() {
|
||||
if let Some(name) = entry.file_name().to_str() {
|
||||
if name.starts_with("donut-ephemeral-") && entry.path().is_dir() {
|
||||
if let Err(e) = std::fs::remove_dir_all(entry.path()) {
|
||||
log::warn!(
|
||||
"Failed to clean up stale ephemeral dir {}: {e}",
|
||||
entry.path().display()
|
||||
);
|
||||
} else {
|
||||
log::info!("Cleaned up stale ephemeral dir: {}", entry.path().display());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_effective_profile_path(profile: &BrowserProfile, profiles_dir: &Path) -> PathBuf {
|
||||
if profile.ephemeral {
|
||||
if let Some(dir) = get_ephemeral_dir(&profile.id.to_string()) {
|
||||
return dir;
|
||||
}
|
||||
}
|
||||
profile.get_profile_data_path(profiles_dir)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn make_test_profile(id: uuid::Uuid, ephemeral: bool) -> BrowserProfile {
|
||||
BrowserProfile {
|
||||
id,
|
||||
name: "test".to_string(),
|
||||
browser: "camoufox".to_string(),
|
||||
version: "1.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_enabled: false,
|
||||
last_sync: None,
|
||||
host_os: None,
|
||||
ephemeral,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ephemeral_dir_lifecycle() {
|
||||
// Test create, get, effective path, remove, and cleanup all in sequence
|
||||
// to avoid race conditions between parallel tests.
|
||||
|
||||
// 1. Create and get
|
||||
let profile_id = uuid::Uuid::new_v4();
|
||||
let id_str = profile_id.to_string();
|
||||
let dir = create_ephemeral_dir(&id_str).unwrap();
|
||||
assert!(dir.is_dir());
|
||||
assert_eq!(get_ephemeral_dir(&id_str), Some(dir.clone()));
|
||||
|
||||
// 2. Effective path for ephemeral profile returns ephemeral dir
|
||||
let ephemeral_profile = make_test_profile(profile_id, true);
|
||||
let profiles_dir = std::env::temp_dir().join("test_profiles_ephemeral");
|
||||
assert_eq!(
|
||||
get_effective_profile_path(&ephemeral_profile, &profiles_dir),
|
||||
dir
|
||||
);
|
||||
|
||||
// 3. Remove cleans up dir and map entry
|
||||
remove_ephemeral_dir(&id_str);
|
||||
assert!(!dir.exists());
|
||||
assert!(get_ephemeral_dir(&id_str).is_none());
|
||||
|
||||
// 4. Effective path for persistent profile returns normal path
|
||||
let persistent_profile = make_test_profile(uuid::Uuid::new_v4(), false);
|
||||
let expected = persistent_profile.get_profile_data_path(&profiles_dir);
|
||||
assert_eq!(
|
||||
get_effective_profile_path(&persistent_profile, &profiles_dir),
|
||||
expected
|
||||
);
|
||||
|
||||
// 5. Cleanup stale dirs
|
||||
let stale_id = uuid::Uuid::new_v4().to_string();
|
||||
let stale_dir = std::env::temp_dir().join(format!("donut-ephemeral-{stale_id}"));
|
||||
std::fs::create_dir_all(&stale_dir).unwrap();
|
||||
assert!(stale_dir.exists());
|
||||
cleanup_stale_dirs();
|
||||
assert!(!stale_dir.exists());
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ mod camoufox_manager;
|
||||
mod default_browser;
|
||||
mod downloaded_browsers_registry;
|
||||
mod downloader;
|
||||
mod ephemeral_dirs;
|
||||
mod extraction;
|
||||
mod geoip_downloader;
|
||||
mod group_manager;
|
||||
@@ -759,6 +760,9 @@ pub fn run() {
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
.plugin(tauri_plugin_macos_permissions::init())
|
||||
.setup(|app| {
|
||||
// Clean up stale ephemeral profile dirs from previous sessions
|
||||
ephemeral_dirs::cleanup_stale_dirs();
|
||||
|
||||
// Start the daemon for tray icon
|
||||
if let Err(e) = daemon_spawn::ensure_daemon_running() {
|
||||
log::warn!("Failed to start daemon: {e}");
|
||||
|
||||
@@ -48,6 +48,7 @@ impl ProfileManager {
|
||||
camoufox_config: Option<CamoufoxConfig>,
|
||||
wayfern_config: Option<WayfernConfig>,
|
||||
group_id: Option<String>,
|
||||
ephemeral: bool,
|
||||
) -> Result<BrowserProfile, Box<dyn std::error::Error>> {
|
||||
if proxy_id.is_some() && vpn_id.is_some() {
|
||||
return Err("Cannot set both proxy_id and vpn_id".into());
|
||||
@@ -72,7 +73,9 @@ impl ProfileManager {
|
||||
|
||||
// Create profile directory with UUID and profile subdirectory
|
||||
create_dir_all(&profile_uuid_dir)?;
|
||||
create_dir_all(&profile_data_dir)?;
|
||||
if !ephemeral {
|
||||
create_dir_all(&profile_data_dir)?;
|
||||
}
|
||||
|
||||
// For Camoufox profiles, generate fingerprint during creation
|
||||
let final_camoufox_config = if browser == "camoufox" {
|
||||
@@ -162,6 +165,7 @@ impl ProfileManager {
|
||||
sync_enabled: false,
|
||||
last_sync: None,
|
||||
host_os: None,
|
||||
ephemeral: false,
|
||||
};
|
||||
|
||||
match self
|
||||
@@ -277,6 +281,7 @@ impl ProfileManager {
|
||||
sync_enabled: false,
|
||||
last_sync: None,
|
||||
host_os: None,
|
||||
ephemeral: false,
|
||||
};
|
||||
|
||||
match self
|
||||
@@ -324,6 +329,7 @@ impl ProfileManager {
|
||||
sync_enabled: false,
|
||||
last_sync: None,
|
||||
host_os: Some(get_host_os()),
|
||||
ephemeral,
|
||||
};
|
||||
|
||||
// Save profile info
|
||||
@@ -337,16 +343,19 @@ impl ProfileManager {
|
||||
log::info!("Profile '{name}' created successfully with ID: {profile_id}");
|
||||
|
||||
// Create user.js with common Firefox preferences and apply proxy settings if provided
|
||||
if let Some(proxy_id_ref) = &proxy_id {
|
||||
if let Some(proxy_settings) = PROXY_MANAGER.get_proxy_settings_by_id(proxy_id_ref) {
|
||||
self.apply_proxy_settings_to_profile(&profile_data_dir, &proxy_settings, None)?;
|
||||
// Skip for ephemeral profiles since the data dir is created at launch time
|
||||
if !ephemeral {
|
||||
if let Some(proxy_id_ref) = &proxy_id {
|
||||
if let Some(proxy_settings) = PROXY_MANAGER.get_proxy_settings_by_id(proxy_id_ref) {
|
||||
self.apply_proxy_settings_to_profile(&profile_data_dir, &proxy_settings, None)?;
|
||||
} else {
|
||||
// Proxy ID provided but not found, disable proxy
|
||||
self.disable_proxy_settings_in_profile(&profile_data_dir)?;
|
||||
}
|
||||
} else {
|
||||
// Proxy ID provided but not found, disable proxy
|
||||
// Create user.js with common Firefox preferences but no proxy
|
||||
self.disable_proxy_settings_in_profile(&profile_data_dir)?;
|
||||
}
|
||||
} else {
|
||||
// Create user.js with common Firefox preferences but no proxy
|
||||
self.disable_proxy_settings_in_profile(&profile_data_dir)?;
|
||||
}
|
||||
|
||||
// Emit profile creation event
|
||||
@@ -842,6 +851,7 @@ impl ProfileManager {
|
||||
sync_enabled: false,
|
||||
last_sync: None,
|
||||
host_os: Some(get_host_os()),
|
||||
ephemeral: false,
|
||||
};
|
||||
|
||||
self.save_profile(&new_profile)?;
|
||||
@@ -1290,7 +1300,8 @@ impl ProfileManager {
|
||||
) -> Result<bool, Box<dyn std::error::Error + Send + Sync>> {
|
||||
let launcher = self.camoufox_manager;
|
||||
let profiles_dir = self.get_profiles_dir();
|
||||
let profile_data_path = profile.get_profile_data_path(&profiles_dir);
|
||||
let profile_data_path =
|
||||
crate::ephemeral_dirs::get_effective_profile_path(profile, &profiles_dir);
|
||||
let profile_path_str = profile_data_path.to_string_lossy();
|
||||
|
||||
// Check if there's a running Camoufox instance for this profile
|
||||
@@ -1334,6 +1345,10 @@ impl ProfileManager {
|
||||
}
|
||||
Ok(None) => {
|
||||
// No running instance found, clear process ID if set and stop proxy
|
||||
if profile.ephemeral {
|
||||
crate::ephemeral_dirs::remove_ephemeral_dir(&profile.id.to_string());
|
||||
}
|
||||
|
||||
let profiles_dir = self.get_profiles_dir();
|
||||
let profile_uuid_dir = profiles_dir.join(profile.id.to_string());
|
||||
let metadata_file = profile_uuid_dir.join("metadata.json");
|
||||
@@ -1364,6 +1379,10 @@ impl ProfileManager {
|
||||
Err(e) => {
|
||||
// Error checking status, assume not running and clear process ID
|
||||
log::warn!("Warning: Failed to check Camoufox status: {e}");
|
||||
if profile.ephemeral {
|
||||
crate::ephemeral_dirs::remove_ephemeral_dir(&profile.id.to_string());
|
||||
}
|
||||
|
||||
let profiles_dir = self.get_profiles_dir();
|
||||
let profile_uuid_dir = profiles_dir.join(profile.id.to_string());
|
||||
let metadata_file = profile_uuid_dir.join("metadata.json");
|
||||
@@ -1405,7 +1424,8 @@ impl ProfileManager {
|
||||
) -> Result<bool, Box<dyn std::error::Error + Send + Sync>> {
|
||||
let manager = self.wayfern_manager;
|
||||
let profiles_dir = self.get_profiles_dir();
|
||||
let profile_data_path = profile.get_profile_data_path(&profiles_dir);
|
||||
let profile_data_path =
|
||||
crate::ephemeral_dirs::get_effective_profile_path(profile, &profiles_dir);
|
||||
let profile_path_str = profile_data_path.to_string_lossy();
|
||||
|
||||
// Check if there's a running Wayfern instance for this profile
|
||||
@@ -1449,6 +1469,10 @@ impl ProfileManager {
|
||||
}
|
||||
None => {
|
||||
// No running instance found, clear process ID if set
|
||||
if profile.ephemeral {
|
||||
crate::ephemeral_dirs::remove_ephemeral_dir(&profile.id.to_string());
|
||||
}
|
||||
|
||||
let profiles_dir = self.get_profiles_dir();
|
||||
let profile_uuid_dir = profiles_dir.join(profile.id.to_string());
|
||||
let metadata_file = profile_uuid_dir.join("metadata.json");
|
||||
@@ -1870,6 +1894,7 @@ pub async fn create_browser_profile_with_group(
|
||||
camoufox_config: Option<CamoufoxConfig>,
|
||||
wayfern_config: Option<WayfernConfig>,
|
||||
group_id: Option<String>,
|
||||
ephemeral: bool,
|
||||
) -> Result<BrowserProfile, String> {
|
||||
let profile_manager = ProfileManager::instance();
|
||||
profile_manager
|
||||
@@ -1884,6 +1909,7 @@ pub async fn create_browser_profile_with_group(
|
||||
camoufox_config,
|
||||
wayfern_config,
|
||||
group_id,
|
||||
ephemeral,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to create profile: {e}"))
|
||||
@@ -1984,6 +2010,7 @@ pub async fn create_browser_profile_new(
|
||||
camoufox_config: Option<CamoufoxConfig>,
|
||||
wayfern_config: Option<WayfernConfig>,
|
||||
group_id: Option<String>,
|
||||
ephemeral: Option<bool>,
|
||||
) -> Result<BrowserProfile, String> {
|
||||
let fingerprint_os = camoufox_config
|
||||
.as_ref()
|
||||
@@ -2010,6 +2037,7 @@ pub async fn create_browser_profile_new(
|
||||
camoufox_config,
|
||||
wayfern_config,
|
||||
group_id,
|
||||
ephemeral.unwrap_or(false),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -45,6 +45,8 @@ pub struct BrowserProfile {
|
||||
pub last_sync: Option<u64>, // Timestamp of last successful sync (epoch seconds)
|
||||
#[serde(default)]
|
||||
pub host_os: Option<String>, // OS where profile was created ("macos", "windows", "linux")
|
||||
#[serde(default)]
|
||||
pub ephemeral: bool,
|
||||
}
|
||||
|
||||
pub fn default_release_type() -> String {
|
||||
|
||||
@@ -557,6 +557,7 @@ impl ProfileImporter {
|
||||
sync_enabled: false,
|
||||
last_sync: None,
|
||||
host_os: Some(crate::profile::types::get_host_os()),
|
||||
ephemeral: false,
|
||||
};
|
||||
|
||||
// Save the profile metadata
|
||||
|
||||
@@ -380,6 +380,7 @@ impl WayfernManager {
|
||||
Ok(fingerprint_json)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn launch_wayfern(
|
||||
&self,
|
||||
_app_handle: &AppHandle,
|
||||
@@ -388,6 +389,7 @@ impl WayfernManager {
|
||||
config: &WayfernConfig,
|
||||
url: Option<&str>,
|
||||
proxy_url: Option<&str>,
|
||||
ephemeral: bool,
|
||||
) -> Result<WayfernLaunchResult, Box<dyn std::error::Error + Send + Sync>> {
|
||||
let executable_path = if let Some(path) = &config.executable_path {
|
||||
let p = PathBuf::from(path);
|
||||
@@ -432,6 +434,11 @@ impl WayfernManager {
|
||||
args.push(format!("--proxy-server={proxy}"));
|
||||
}
|
||||
|
||||
if ephemeral {
|
||||
args.push(format!("--disk-cache-dir={}/cache", profile_path));
|
||||
args.push("--incognito".to_string());
|
||||
}
|
||||
|
||||
// Don't add URL to args - we'll navigate via CDP after setting fingerprint
|
||||
// This ensures fingerprint is applied at navigation commit time
|
||||
|
||||
@@ -713,6 +720,7 @@ impl WayfernManager {
|
||||
config,
|
||||
url,
|
||||
proxy_url,
|
||||
profile.ephemeral,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -494,6 +494,7 @@ export default function Home() {
|
||||
camoufoxConfig?: CamoufoxConfig;
|
||||
wayfernConfig?: WayfernConfig;
|
||||
groupId?: string;
|
||||
ephemeral?: boolean;
|
||||
}) => {
|
||||
try {
|
||||
await invoke<BrowserProfile>("create_browser_profile_new", {
|
||||
@@ -508,6 +509,7 @@ export default function Home() {
|
||||
groupId:
|
||||
profileData.groupId ||
|
||||
(selectedGroupId !== "default" ? selectedGroupId : undefined),
|
||||
ephemeral: profileData.ephemeral,
|
||||
});
|
||||
|
||||
// No need to manually reload - useProfileEvents will handle the update
|
||||
|
||||
@@ -8,6 +8,7 @@ import { LoadingButton } from "@/components/loading-button";
|
||||
import { ProxyFormDialog } from "@/components/proxy-form-dialog";
|
||||
import { SharedCamoufoxConfigForm } from "@/components/shared-camoufox-config-form";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -75,6 +76,7 @@ interface CreateProfileDialogProps {
|
||||
camoufoxConfig?: CamoufoxConfig;
|
||||
wayfernConfig?: WayfernConfig;
|
||||
groupId?: string;
|
||||
ephemeral?: boolean;
|
||||
}) => Promise<void>;
|
||||
selectedGroupId?: string;
|
||||
crossOsUnlocked?: boolean;
|
||||
@@ -164,6 +166,7 @@ export function CreateProfileDialog({
|
||||
const { vpnConfigs } = useVpnEvents();
|
||||
const [showProxyForm, setShowProxyForm] = useState(false);
|
||||
const [isCreating, setIsCreating] = useState(false);
|
||||
const [ephemeral, setEphemeral] = useState(false);
|
||||
const [releaseTypes, setReleaseTypes] = useState<BrowserReleaseTypes>();
|
||||
const [isLoadingReleaseTypes, setIsLoadingReleaseTypes] = useState(false);
|
||||
const [releaseTypesError, setReleaseTypesError] = useState<string | null>(
|
||||
@@ -382,6 +385,7 @@ export function CreateProfileDialog({
|
||||
wayfernConfig: finalWayfernConfig,
|
||||
groupId:
|
||||
selectedGroupId !== "default" ? selectedGroupId : undefined,
|
||||
ephemeral,
|
||||
});
|
||||
} else {
|
||||
// Default to Camoufox
|
||||
@@ -405,6 +409,7 @@ export function CreateProfileDialog({
|
||||
camoufoxConfig: finalCamoufoxConfig,
|
||||
groupId:
|
||||
selectedGroupId !== "default" ? selectedGroupId : undefined,
|
||||
ephemeral,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
@@ -459,6 +464,7 @@ export function CreateProfileDialog({
|
||||
setWayfernConfig({
|
||||
os: getCurrentOS() as WayfernOS, // Reset to current OS
|
||||
});
|
||||
setEphemeral(false);
|
||||
onClose();
|
||||
};
|
||||
|
||||
@@ -660,6 +666,28 @@ export function CreateProfileDialog({
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Ephemeral Toggle */}
|
||||
<div className="flex items-center gap-2">
|
||||
<Checkbox
|
||||
id="ephemeral"
|
||||
checked={ephemeral}
|
||||
onCheckedChange={(checked) =>
|
||||
setEphemeral(checked === true)
|
||||
}
|
||||
/>
|
||||
<div className="flex flex-col">
|
||||
<Label
|
||||
htmlFor="ephemeral"
|
||||
className="cursor-pointer"
|
||||
>
|
||||
Ephemeral
|
||||
</Label>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
Browser data is deleted when closed
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{selectedBrowser === "wayfern" ? (
|
||||
// Wayfern Configuration
|
||||
<div className="space-y-6">
|
||||
|
||||
@@ -1940,7 +1940,14 @@ export function ProfilesDataTable({
|
||||
}
|
||||
}}
|
||||
>
|
||||
{display}
|
||||
<span className="flex items-center gap-1">
|
||||
{display}
|
||||
{profile.ephemeral && (
|
||||
<span className="px-1 py-0.5 text-[10px] leading-none rounded bg-muted text-muted-foreground font-medium">
|
||||
Ephemeral
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
},
|
||||
@@ -2397,6 +2404,7 @@ export function ProfilesDataTable({
|
||||
)}
|
||||
{(profile.browser === "camoufox" ||
|
||||
profile.browser === "wayfern") &&
|
||||
!profile.ephemeral &&
|
||||
meta.onCopyCookiesToProfile && (
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
@@ -2414,6 +2422,7 @@ export function ProfilesDataTable({
|
||||
)}
|
||||
{(profile.browser === "camoufox" ||
|
||||
profile.browser === "wayfern") &&
|
||||
!profile.ephemeral &&
|
||||
meta.onImportCookies && (
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
@@ -2431,6 +2440,7 @@ export function ProfilesDataTable({
|
||||
)}
|
||||
{(profile.browser === "camoufox" ||
|
||||
profile.browser === "wayfern") &&
|
||||
!profile.ephemeral &&
|
||||
meta.onExportCookies && (
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
@@ -2446,14 +2456,16 @@ export function ProfilesDataTable({
|
||||
</span>
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
meta.onCloneProfile?.(profile);
|
||||
}}
|
||||
disabled={isDisabled}
|
||||
>
|
||||
Clone Profile
|
||||
</DropdownMenuItem>
|
||||
{!profile.ephemeral && (
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
meta.onCloneProfile?.(profile);
|
||||
}}
|
||||
disabled={isDisabled}
|
||||
>
|
||||
Clone Profile
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
setProfileToDelete(profile);
|
||||
|
||||
@@ -158,7 +158,10 @@
|
||||
"copyCookies": "Copy Cookies",
|
||||
"configure": "Configure",
|
||||
"clone": "Clone Profile"
|
||||
}
|
||||
},
|
||||
"ephemeral": "Ephemeral",
|
||||
"ephemeralDescription": "Browser data is deleted when closed",
|
||||
"ephemeralBadge": "Ephemeral"
|
||||
},
|
||||
"createProfile": {
|
||||
"title": "Create New Profile",
|
||||
|
||||
@@ -158,7 +158,10 @@
|
||||
"copyCookies": "Copiar Cookies",
|
||||
"configure": "Configurar",
|
||||
"clone": "Clonar perfil"
|
||||
}
|
||||
},
|
||||
"ephemeral": "Efímero",
|
||||
"ephemeralDescription": "Los datos del navegador se eliminan al cerrarlo",
|
||||
"ephemeralBadge": "Efímero"
|
||||
},
|
||||
"createProfile": {
|
||||
"title": "Crear Nuevo Perfil",
|
||||
|
||||
@@ -158,7 +158,10 @@
|
||||
"copyCookies": "Copier les cookies",
|
||||
"configure": "Configurer",
|
||||
"clone": "Cloner le profil"
|
||||
}
|
||||
},
|
||||
"ephemeral": "Éphémère",
|
||||
"ephemeralDescription": "Les données du navigateur sont supprimées à la fermeture",
|
||||
"ephemeralBadge": "Éphémère"
|
||||
},
|
||||
"createProfile": {
|
||||
"title": "Créer un nouveau profil",
|
||||
|
||||
@@ -158,7 +158,10 @@
|
||||
"copyCookies": "Cookieをコピー",
|
||||
"configure": "設定",
|
||||
"clone": "プロファイルを複製"
|
||||
}
|
||||
},
|
||||
"ephemeral": "一時的",
|
||||
"ephemeralDescription": "ブラウザを閉じるとデータが削除されます",
|
||||
"ephemeralBadge": "一時的"
|
||||
},
|
||||
"createProfile": {
|
||||
"title": "新しいプロファイルを作成",
|
||||
|
||||
@@ -158,7 +158,10 @@
|
||||
"copyCookies": "Copiar Cookies",
|
||||
"configure": "Configurar",
|
||||
"clone": "Clonar perfil"
|
||||
}
|
||||
},
|
||||
"ephemeral": "Efêmero",
|
||||
"ephemeralDescription": "Os dados do navegador são excluídos ao fechar",
|
||||
"ephemeralBadge": "Efêmero"
|
||||
},
|
||||
"createProfile": {
|
||||
"title": "Criar Novo Perfil",
|
||||
|
||||
@@ -158,7 +158,10 @@
|
||||
"copyCookies": "Копировать Cookie",
|
||||
"configure": "Настроить",
|
||||
"clone": "Клонировать профиль"
|
||||
}
|
||||
},
|
||||
"ephemeral": "Временный",
|
||||
"ephemeralDescription": "Данные браузера удаляются при закрытии",
|
||||
"ephemeralBadge": "Временный"
|
||||
},
|
||||
"createProfile": {
|
||||
"title": "Создать новый профиль",
|
||||
|
||||
@@ -158,7 +158,10 @@
|
||||
"copyCookies": "复制 Cookies",
|
||||
"configure": "配置",
|
||||
"clone": "克隆配置文件"
|
||||
}
|
||||
},
|
||||
"ephemeral": "临时",
|
||||
"ephemeralDescription": "关闭浏览器时数据将被删除",
|
||||
"ephemeralBadge": "临时"
|
||||
},
|
||||
"createProfile": {
|
||||
"title": "创建新配置文件",
|
||||
|
||||
@@ -29,6 +29,7 @@ export interface BrowserProfile {
|
||||
sync_enabled?: boolean; // Whether sync is enabled for this profile
|
||||
last_sync?: number; // Timestamp of last successful sync (epoch seconds)
|
||||
host_os?: string; // OS where profile was created ("macos", "windows", "linux")
|
||||
ephemeral?: boolean;
|
||||
}
|
||||
|
||||
export type SyncStatus = "Disabled" | "Syncing" | "Synced" | "Error";
|
||||
|
||||
Reference in New Issue
Block a user