diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 287b65f..d024e4a 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -118,6 +118,28 @@ impl WindowExt for WebviewWindow { } } +#[tauri::command] +async fn warm_up_nodecar() -> Result<(), String> { + use tokio::process::Command; + use tokio::time::{timeout, Duration}; + + let start_time = std::time::Instant::now(); + + let warmup_future = Command::new("nodecar").output(); + match timeout(Duration::from_secs(15), warmup_future).await { + Ok(Ok(_output)) => { + let duration = start_time.elapsed(); + println!( + "Nodecar warm-up (frontend-triggered) completed in {:.2}s", + duration.as_secs_f64() + ); + Ok(()) + } + Ok(Err(e)) => Err(format!("Failed to start nodecar warm-up: {e}")), + Err(_) => Err("Nodecar warm-up timed out after 15s".to_string()), + } +} + #[tauri::command] async fn handle_url_open(app: tauri::AppHandle, url: String) -> Result<(), String> { println!("handle_url_open called with URL: {url}"); @@ -460,36 +482,7 @@ pub fn run() { } }); - // Warm up nodecar binary in the background - tauri::async_runtime::spawn(async move { - println!("Starting nodecar warm-up..."); - let start_time = std::time::Instant::now(); - - // Send a ping request to nodecar to trigger unpacking/warm-up - match tokio::process::Command::new("nodecar").output().await { - Ok(output) => { - let duration = start_time.elapsed(); - if output.status.success() { - println!( - "Nodecar warm-up completed successfully in {:.2}s", - duration.as_secs_f64() - ); - } else { - println!( - "Nodecar warm-up completed with non-zero exit code in {:.2}s", - duration.as_secs_f64() - ); - } - } - Err(e) => { - let duration = start_time.elapsed(); - println!( - "Nodecar warm-up failed after {:.2}s: {e}", - duration.as_secs_f64() - ); - } - } - }); + // Nodecar warm-up is now triggered from the frontend to allow UI blocking overlay Ok(()) }) @@ -549,6 +542,7 @@ pub fn run() { delete_selected_profiles, is_geoip_database_available, download_geoip_database, + warm_up_nodecar, ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); diff --git a/src/app/page.tsx b/src/app/page.tsx index a18f8b2..d68579c 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -40,6 +40,7 @@ interface PendingUrl { } export default function Home() { + const [isInitializing, setIsInitializing] = useState(true); const [profiles, setProfiles] = useState([]); const [error, setError] = useState(null); const [createProfileDialogOpen, setCreateProfileDialogOpen] = useState(false); @@ -260,6 +261,28 @@ export default function Home() { } }, [hasCheckedStartupPrompt]); + // Warm up nodecar at startup and block UI until complete + useEffect(() => { + let cancelled = false; + (async () => { + try { + await invoke("warm_up_nodecar"); + } catch (err) { + if (!cancelled) { + setError( + `Initialization failed: ${err instanceof Error ? err.message : String(err)}`, + ); + } + } finally { + if (!cancelled) setIsInitializing(false); + } + })(); + + return () => { + cancelled = true; + }; + }, []); + const checkAllPermissions = useCallback(async () => { try { // Wait for permissions to be initialized before checking @@ -778,6 +801,17 @@ export default function Home() { + {isInitializing && ( +
+
+
+
+ Initialization, please don't close the app +
+
+
+ )} + {