diff --git a/.vscode/settings.json b/.vscode/settings.json index a24694b..24c33e0 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -14,6 +14,7 @@ "cmdk", "codegen", "devedition", + "doesn", "donutbrowser", "dpkg", "dtolnay", diff --git a/nodecar/copy-binary.sh b/nodecar/copy-binary.sh index f125dc7..8b9c640 100755 --- a/nodecar/copy-binary.sh +++ b/nodecar/copy-binary.sh @@ -21,5 +21,8 @@ if [ -z "$TARGET_TRIPLE" ]; then exit 1 fi -# Copy the file -cp "dist/nodecar${EXT}" "../src-tauri/binaries/nodecar-${TARGET_TRIPLE}${EXT}" \ No newline at end of file +# Copy the file with target triple suffix +cp "dist/nodecar${EXT}" "../src-tauri/binaries/nodecar-${TARGET_TRIPLE}${EXT}" + +# Also copy a generic version for Tauri to find +cp "dist/nodecar${EXT}" "../src-tauri/binaries/nodecar${EXT}" \ No newline at end of file diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 95692ed..d1e2efc 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -1023,6 +1023,7 @@ dependencies = [ "tokio-test", "tower", "tower-http", + "urlencoding", "windows", "winreg", "wiremock", @@ -5087,6 +5088,12 @@ dependencies = [ "serde", ] +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "urlpattern" version = "0.3.0" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index e1d6bcc..24fdfd7 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -37,6 +37,7 @@ base64 = "0.22" zip = "4" async-trait = "0.1" futures-util = "0.3" +urlencoding = "2.1" [target.'cfg(target_os = "macos")'.dependencies] core-foundation="0.10" diff --git a/src-tauri/src/browser_runner.rs b/src-tauri/src/browser_runner.rs index 2fab261..0ae5a3d 100644 --- a/src-tauri/src/browser_runner.rs +++ b/src-tauri/src/browser_runner.rs @@ -1091,11 +1091,12 @@ impl BrowserRunner { Ok(profile) } - pub fn update_profile_proxy( + pub async fn update_profile_proxy( &self, + app_handle: tauri::AppHandle, profile_name: &str, proxy: Option, - ) -> Result> { + ) -> Result> { let profiles_dir = self.get_profiles_dir(); let profile_file = profiles_dir.join(format!( "{}.json", @@ -1111,30 +1112,97 @@ impl BrowserRunner { let content = fs::read_to_string(&profile_file)?; let mut profile: BrowserProfile = serde_json::from_str(&content)?; + // Check if browser is running to manage proxy accordingly + let browser_is_running = profile.process_id.is_some() + && self + .check_browser_status(app_handle.clone(), &profile) + .await?; + + // If browser is running, stop existing proxy + if browser_is_running && profile.proxy.is_some() { + if let Some(pid) = profile.process_id { + let _ = PROXY_MANAGER.stop_proxy(app_handle.clone(), pid).await; + } + } + // Update proxy settings profile.proxy = proxy.clone(); // Save the updated profile - self.save_profile(&profile)?; + self + .save_profile(&profile) + .map_err(|e| -> Box { + format!("Failed to save profile: {e}").into() + })?; - // Get internal proxy if the browser is running - let internal_proxy = if let Some(pid) = profile.process_id { - PROXY_MANAGER.get_proxy_settings(pid) + // Handle proxy startup/configuration + if let Some(proxy_settings) = &proxy { + if proxy_settings.enabled && browser_is_running { + // Browser is running and proxy is enabled, start new proxy + if let Some(pid) = profile.process_id { + match PROXY_MANAGER + .start_proxy(app_handle.clone(), proxy_settings, pid, Some(profile_name)) + .await + { + Ok(internal_proxy_settings) => { + let browser_runner = BrowserRunner::new(); + let profiles_dir = browser_runner.get_profiles_dir(); + let profile_path = profiles_dir.join(profile.name.to_lowercase().replace(" ", "_")); + + // Apply the proxy settings with the internal proxy to the profile directory + browser_runner + .apply_proxy_settings_to_profile( + &profile_path, + proxy_settings, + Some(&internal_proxy_settings), + ) + .map_err(|e| format!("Failed to update profile proxy: {e}"))?; + + println!("Successfully started proxy for profile: {}", profile.name); + + // Give the proxy a moment to fully start up + tokio::time::sleep(tokio::time::Duration::from_millis(500)).await; + Some(internal_proxy_settings) + } + Err(e) => { + eprintln!("Failed to start proxy: {e}"); + // Apply proxy settings without internal proxy + self + .apply_proxy_settings_to_profile(&profile_path, proxy_settings, None) + .map_err(|e| -> Box { + format!("Failed to apply proxy settings: {e}").into() + })?; + None + } + } + } else { + // No PID available, apply proxy settings without internal proxy + self + .apply_proxy_settings_to_profile(&profile_path, proxy_settings, None) + .map_err(|e| -> Box { + format!("Failed to apply proxy settings: {e}").into() + })?; + None + } + } else { + // Proxy disabled or browser not running, just apply settings + self + .apply_proxy_settings_to_profile(&profile_path, proxy_settings, None) + .map_err(|e| -> Box { + format!("Failed to apply proxy settings: {e}").into() + })?; + None + } } else { + // No proxy settings, disable proxy + self + .disable_proxy_settings_in_profile(&profile_path) + .map_err(|e| -> Box { + format!("Failed to disable proxy settings: {e}").into() + })?; None }; - // Apply proxy settings if provided - if let Some(proxy_settings) = &proxy { - self.apply_proxy_settings_to_profile( - &profile_path, - proxy_settings, - internal_proxy.as_ref(), - )?; - } else { - self.disable_proxy_settings_in_profile(&profile_path)?; - } - Ok(profile) } @@ -1249,9 +1317,10 @@ impl BrowserRunner { preferences.extend(self.get_common_firefox_preferences()); if proxy.enabled { - // Create PAC file from template - let template_path = Path::new("assets/template.pac"); - let pac_content = fs::read_to_string(template_path)?; + // Use embedded PAC template instead of reading from file + const PAC_TEMPLATE: &str = r#"function FindProxyForURL(url, host) { + return "{{proxy_url}}"; +}"#; // Format proxy URL based on type and whether we have an internal proxy let proxy_url = if let Some(internal) = internal_proxy { @@ -1269,7 +1338,7 @@ impl BrowserRunner { }; // Replace placeholders in PAC file - let pac_content = pac_content + let pac_content = PAC_TEMPLATE .replace("{{proxy_url}}", &proxy_url) .replace("{{proxy_credentials}}", ""); // Credentials are now handled by the PAC file @@ -1388,9 +1457,9 @@ impl BrowserRunner { // Continue anyway, the error might not be critical } - // Get launch arguments + // Get launch arguments (proxy settings will be handled later if needed) let browser_args = browser - .create_launch_args(&profile.profile_path, profile.proxy.as_ref(), url) + .create_launch_args(&profile.profile_path, None, url) .expect("Failed to create launch arguments"); // Launch browser using platform-specific method @@ -2150,9 +2219,53 @@ pub async fn launch_browser_profile( ) -> Result { let browser_runner = BrowserRunner::new(); + // If the profile has proxy settings, we need to start the proxy first + // and update the profile with proxy settings before launching + let profile_for_launch = profile.clone(); + if let Some(proxy) = &profile.proxy { + if proxy.enabled { + // Use a temporary PID (1) to start the proxy, we'll update it after browser launch + let temp_pid = 1u32; + + // Start the proxy first + match PROXY_MANAGER + .start_proxy(app_handle.clone(), proxy, temp_pid, Some(&profile.name)) + .await + { + Ok(internal_proxy_settings) => { + let browser_runner = BrowserRunner::new(); + let profiles_dir = browser_runner.get_profiles_dir(); + let profile_path = profiles_dir.join(profile.name.to_lowercase().replace(" ", "_")); + + // Apply the proxy settings with the internal proxy to the profile directory + browser_runner + .apply_proxy_settings_to_profile(&profile_path, proxy, Some(&internal_proxy_settings)) + .map_err(|e| format!("Failed to update profile proxy: {e}"))?; + + println!("Successfully started proxy for profile: {}", profile.name); + + // Give the proxy a moment to fully start up + tokio::time::sleep(tokio::time::Duration::from_millis(500)).await; + } + Err(e) => { + eprintln!("Failed to start proxy: {e}"); + // Still continue with browser launch, but without proxy + let browser_runner = BrowserRunner::new(); + let profiles_dir = browser_runner.get_profiles_dir(); + let profile_path = profiles_dir.join(profile.name.to_lowercase().replace(" ", "_")); + + // Apply proxy settings without internal proxy + browser_runner + .apply_proxy_settings_to_profile(&profile_path, proxy, None) + .map_err(|e| format!("Failed to update profile proxy: {e}"))?; + } + } + } + } + // Launch browser or open URL in existing instance let updated_profile = browser_runner - .launch_or_open_url(app_handle.clone(), &profile, url) + .launch_or_open_url(app_handle.clone(), &profile_for_launch, url) .await .map_err(|e| { // Check if this is an architecture compatibility issue @@ -2165,29 +2278,17 @@ pub async fn launch_browser_profile( format!("Failed to launch browser or open URL: {e}") })?; - // If the profile has proxy settings, start a proxy for it + // Now update the proxy with the correct PID if we have one if let Some(proxy) = &profile.proxy { if proxy.enabled { - // Get the process ID - if let Some(pid) = updated_profile.process_id { - // Start the proxy - match PROXY_MANAGER - .start_proxy(app_handle.clone(), proxy, pid, Some(&profile.name)) - .await - { - Ok(internal_proxy_settings) => { - let browser_runner = BrowserRunner::new(); - let profiles_dir = browser_runner.get_profiles_dir(); - let profile_path = profiles_dir.join(profile.name.to_lowercase().replace(" ", "_")); - - // Apply the proxy settings with the internal proxy - browser_runner - .apply_proxy_settings_to_profile(&profile_path, proxy, Some(&internal_proxy_settings)) - .map_err(|e| format!("Failed to update profile proxy: {e}"))?; + if let Some(actual_pid) = updated_profile.process_id { + // Update the proxy manager with the correct PID + match PROXY_MANAGER.update_proxy_pid(1u32, actual_pid) { + Ok(()) => { + println!("Updated proxy PID mapping from temp (1) to actual PID: {actual_pid}"); } Err(e) => { - eprintln!("Failed to start proxy: {e}"); - // Continue without proxy + eprintln!("Failed to update proxy PID mapping: {e}"); } } } @@ -2198,13 +2299,15 @@ pub async fn launch_browser_profile( } #[tauri::command] -pub fn update_profile_proxy( +pub async fn update_profile_proxy( + app_handle: tauri::AppHandle, profile_name: String, proxy: Option, ) -> Result { let browser_runner = BrowserRunner::new(); browser_runner - .update_profile_proxy(&profile_name, proxy) + .update_profile_proxy(app_handle, &profile_name, proxy) + .await .map_err(|e| format!("Failed to update profile: {e}")) } @@ -2664,38 +2767,6 @@ mod tests { assert_eq!(profiles[0].version, "139.0"); } - #[test] - fn test_update_profile_proxy() { - let (runner, _temp_dir) = create_test_browser_runner(); - - // Create profile without proxy - let profile = runner - .create_profile("Test Update Proxy", "firefox", "139.0", None) - .unwrap(); - - assert!(profile.proxy.is_none()); - - // Update with proxy - let proxy = ProxySettings { - enabled: true, - proxy_type: "socks5".to_string(), - host: "192.168.1.1".to_string(), - port: 1080, - username: None, - password: None, - }; - - let updated_profile = runner - .update_profile_proxy("Test Update Proxy", Some(proxy.clone())) - .unwrap(); - - assert!(updated_profile.proxy.is_some()); - let profile_proxy = updated_profile.proxy.unwrap(); - assert_eq!(profile_proxy.proxy_type, "socks5"); - assert_eq!(profile_proxy.host, "192.168.1.1"); - assert_eq!(profile_proxy.port, 1080); - } - #[test] fn test_rename_profile() { let (runner, _temp_dir) = create_test_browser_runner(); @@ -2793,24 +2864,6 @@ mod tests { assert!(result.unwrap_err().to_string().contains("already exists")); } - #[test] - fn test_error_handling() { - let (runner, _temp_dir) = create_test_browser_runner(); - - // Test updating non-existent profile - let result = runner.update_profile_proxy("Non Existent", None); - assert!(result.is_err()); - assert!(result.unwrap_err().to_string().contains("not found")); - - // Test deleting non-existent profile - let result = runner.delete_profile("Non Existent"); - assert!(result.is_ok()); // Delete should be idempotent - - // Test renaming non-existent profile - let result = runner.rename_profile("Non Existent", "New Name"); - assert!(result.is_err()); - } - #[test] fn test_firefox_default_browser_preferences() { let (runner, _temp_dir) = create_test_browser_runner(); diff --git a/src-tauri/src/proxy_manager.rs b/src-tauri/src/proxy_manager.rs index 47aaca5..b9e1be9 100644 --- a/src-tauri/src/proxy_manager.rs +++ b/src-tauri/src/proxy_manager.rs @@ -216,6 +216,17 @@ impl ProxyManager { let profile_proxies = self.profile_proxies.lock().unwrap(); profile_proxies.get(profile_name).cloned() } + + // Update the PID mapping for an existing proxy + pub fn update_proxy_pid(&self, old_pid: u32, new_pid: u32) -> Result<(), String> { + let mut proxies = self.active_proxies.lock().unwrap(); + if let Some(proxy_info) = proxies.remove(&old_pid) { + proxies.insert(new_pid, proxy_info); + Ok(()) + } else { + Err(format!("No proxy found for PID {old_pid}")) + } + } } // Create a singleton instance of the proxy manager @@ -618,7 +629,7 @@ mod tests { if cmd_output.status.success() { let stdout = String::from_utf8(cmd_output.stdout)?; let config: serde_json::Value = serde_json::from_str(&stdout)?; - + // Clean up - try to stop the proxy if let Some(proxy_id) = config["id"].as_str() { let mut stop_cmd = Command::new(&nodecar_path);