From 410ed0692076afdc8b924e6d9beeaf45b1a43616 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sat, 31 Oct 2026 19:02:42 +0100 Subject: [PATCH] Changed the app window to open the app via 127.0.0.1 --- app/MindWork AI Studio/Program.cs | 9 +++++++-- runtime/src/app_window.rs | 19 +++++++++++------- runtime/src/dotnet.rs | 32 +++++++++++++++++++++++++++++-- 3 files changed, 49 insertions(+), 11 deletions(-) diff --git a/app/MindWork AI Studio/Program.cs b/app/MindWork AI Studio/Program.cs index 6c4f5ee9f..f5af7adc8 100644 --- a/app/MindWork AI Studio/Program.cs +++ b/app/MindWork AI Studio/Program.cs @@ -285,10 +285,15 @@ internal sealed class Program builder.Services.AddSingleton(new HttpClient { - BaseAddress = new Uri($"http://localhost:{appPort}") + BaseAddress = new Uri($"http://127.0.0.1:{appPort}") }); - builder.WebHost.UseUrls($"http://localhost:{appPort}"); + // + // We listen on the IPv4 loopback address only, where the app window goes. With localhost, the + // server would listen on the IPv6 loopback address as well, and a client which resolves + // localhost could end up with another process holding that port there. + // + builder.WebHost.UseUrls($"http://127.0.0.1:{appPort}"); #if DEBUG builder.WebHost.UseWebRoot("wwwroot"); diff --git a/runtime/src/app_window.rs b/runtime/src/app_window.rs index a068c7550..e6611dc30 100644 --- a/runtime/src/app_window.rs +++ b/runtime/src/app_window.rs @@ -312,7 +312,7 @@ fn is_tauri_asset_url(url: &tauri::Url) -> bool { matches!(url.scheme(), "http" | "https") && is_tauri_asset_host(url.host_str()) } -fn is_local_http_url(url: &tauri::Url) -> bool { +pub(crate) fn is_local_http_url(url: &tauri::Url) -> bool { matches!(url.scheme(), "http" | "https") && is_local_host(url.host_str()) } @@ -678,7 +678,7 @@ pub fn send_event(event: Event) { } /// Changes the location of the main window to the given URL. -pub async fn change_location_to(url: &str) { +pub async fn change_location_to(url: tauri::Url) { // Try to get the main window. If it is not available yet, wait for it: let mut main_window_ready = false; let mut main_window_status_reported = false; @@ -700,13 +700,18 @@ pub async fn change_location_to(url: &str) { } } - if let Ok(parsed_url) = tauri::Url::parse(url) && is_local_http_url(&parsed_url) { - *APPROVED_APP_URL.lock().unwrap() = Some(parsed_url); + if is_local_http_url(&url) { + *APPROVED_APP_URL.lock().unwrap() = Some(url.clone()); } - let js_location_change = format!("window.location = '{url}';"); - let main_window = main_window_spawn_clone.lock().unwrap(); - let location_change_result = main_window.as_ref().unwrap().eval(js_location_change.as_str()); + // + // The window navigates by itself instead of through a script of the page it shows. That page + // comes from Tauri, which is another site than the app, so the app would count as opened by + // another site. We take the window out of the lock first: navigating needs the main thread, + // which may wait for this lock meanwhile. + // + let main_window = main_window_spawn_clone.lock().unwrap().clone().unwrap(); + let location_change_result = main_window.navigate(url.clone()); match location_change_result { Ok(_) => info!("The app location was changed to {url}."), Err(e) => error!("Failed to change the app location to {url}: {e}."), diff --git a/runtime/src/dotnet.rs b/runtime/src/dotnet.rs index cbee8c284..48d0d888d 100644 --- a/runtime/src/dotnet.rs +++ b/runtime/src/dotnet.rs @@ -303,7 +303,7 @@ pub async fn dotnet_ready() { } let dotnet_server_port = *DOTNET_SERVER_PORT; - let url = match Url::parse(format!("http://localhost:{dotnet_server_port}").as_str()) + let url = match Url::parse(app_address(dotnet_server_port).as_str()) { Ok(url) => url, Err(msg) => { @@ -312,7 +312,14 @@ pub async fn dotnet_ready() { } }; - change_location_to(url.as_str()).await; + change_location_to(url).await; +} + +/// Returns the address of the app on the .NET server. The server listens on the IPv4 loopback +/// address only, so the window goes there directly instead of resolving `localhost`, which may +/// answer with the IPv6 loopback address first. +fn app_address(port: u16) -> String { + format!("http://127.0.0.1:{port}/") } /// Stops the .NET server process. @@ -336,4 +343,25 @@ pub fn cleanup_dotnet_server() { if let Err(e) = kill_stale_process(pid_path, SIDECAR_TYPE) { warn!(Source = ".NET"; "Error during the cleanup of .NET: {}", e); } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::app_window::is_local_http_url; + + #[test] + fn the_app_address_is_the_ipv4_loopback_address() { + let url = Url::parse(app_address(61153).as_str()).unwrap(); + + assert_eq!(url.host_str(), Some("127.0.0.1")); + assert_eq!(url.port(), Some(61153)); + assert_eq!(url.path(), "/"); + } + + #[test] + fn the_app_address_counts_as_local() { + let url = Url::parse(app_address(61153).as_str()).unwrap(); + assert!(is_local_http_url(&url)); + } } \ No newline at end of file