use crate::host_bridge::{DesktopShareState, HostBridgeReplayState}; use crate::shell::deep_link::{ register_desktop_deep_link_events, register_desktop_deep_link_schemes, }; use crate::shell::tray::{ register_desktop_tray, register_desktop_window_close_events, resolve_desktop_single_instance_action, show_main_window, DesktopSingleInstanceAction, }; use crate::shell::webview::{ desktop_external_navigation_url, desktop_window_config_with_runtime_platform, emit_current_desktop_lifecycle_event, handle_desktop_webview_download, log_desktop_host_event_result, open_desktop_external_navigation, open_normalized_desktop_external_url, register_desktop_file_drop_events, register_desktop_lifecycle_events, register_desktop_navigation_events, replay_desktop_webview_state, should_allow_desktop_webview_navigation, should_replay_desktop_webview_state_on_page_load, }; use crate::shell::window_state::desktop_window_state_plugin; use tauri::webview::NewWindowResponse; const DESKTOP_MAIN_WINDOW_LABEL: &str = "main"; fn desktop_main_window_config_from_windows( windows: &[tauri::utils::config::WindowConfig], ) -> tauri::Result { windows .iter() .find(|window| window.label == DESKTOP_MAIN_WINDOW_LABEL) .cloned() .map(desktop_window_config_with_runtime_platform) .ok_or(tauri::Error::WindowNotFound) } #[cfg(dev)] fn apply_desktop_dev_url_to_main_window_config( mut config: tauri::utils::config::WindowConfig, dev_url: &tauri::Url, ) -> tauri::utils::config::WindowConfig { config.url = tauri::WebviewUrl::External(dev_url.clone()); desktop_window_config_with_runtime_platform(config) } fn desktop_main_window_config( app: &tauri::App, ) -> tauri::Result { let config = desktop_main_window_config_from_windows(&app.config().app.windows)?; #[cfg(dev)] if let Some(dev_url) = app.config().build.dev_url.as_ref() { return Ok(apply_desktop_dev_url_to_main_window_config(config, dev_url)); } Ok(config) } fn desktop_new_window_external_url(url: &tauri::Url) -> Option { desktop_external_navigation_url(url) } fn desktop_new_window_response() -> NewWindowResponse { NewWindowResponse::Deny } pub(crate) fn run() { tauri::Builder::default() .manage(DesktopShareState::default()) .manage(HostBridgeReplayState::default()) .plugin(tauri_plugin_single_instance::init(|app, _argv, _cwd| { match resolve_desktop_single_instance_action() { DesktopSingleInstanceAction::ShowMainWindow => { log_desktop_host_event_result("single_instance.show", show_main_window(app)); } } })) .plugin(desktop_window_state_plugin()) .plugin(tauri_plugin_deep_link::init()) .plugin(tauri_plugin_clipboard_manager::init()) .plugin(tauri_plugin_dialog::init()) .plugin(tauri_plugin_notification::init()) .plugin(tauri_plugin_opener::init()) .setup(|app| { let tray_registered = match register_desktop_tray(app) { Ok(()) => true, Err(_error) => { eprintln!("desktop tray registration failed"); false } }; let config = desktop_main_window_config(app)?; let app_handle = app.handle().clone(); let new_window_app_handle = app.handle().clone(); let window = tauri::WebviewWindowBuilder::from_config(app.handle(), &config)? .on_navigation(move |url| { if should_allow_desktop_webview_navigation(url) { true } else { open_desktop_external_navigation(&app_handle, url); false } }) .on_new_window(move |url, _features| { if let Some(external_url) = desktop_new_window_external_url(&url) { log_desktop_host_event_result( "webview.new_window.external", open_normalized_desktop_external_url( &new_window_app_handle, external_url, ), ); } desktop_new_window_response::() }) .on_page_load(|window, payload| { if should_replay_desktop_webview_state_on_page_load(payload.event()) { replay_desktop_webview_state(&window); } }) .on_download(|_webview, event| handle_desktop_webview_download(&event)) .build()?; register_desktop_window_close_events(&window, tray_registered); register_desktop_lifecycle_events(&window); log_desktop_host_event_result( "app.lifecycle", emit_current_desktop_lifecycle_event(&window), ); register_desktop_navigation_events(&window)?; register_desktop_file_drop_events(&window); register_desktop_deep_link_events(app)?; register_desktop_deep_link_schemes(app); Ok(()) }) .invoke_handler(tauri::generate_handler![ crate::host_bridge::host_bridge_request ]) .run(tauri::generate_context!()) .expect("failed to run Genarrative desktop shell"); } #[cfg(test)] mod tests { use super::*; use std::path::PathBuf; #[test] fn desktop_main_window_config_uses_labeled_main_window() { let mut secondary_window = tauri::utils::config::WindowConfig::default(); secondary_window.label = "secondary".to_string(); secondary_window.url = tauri::WebviewUrl::App(PathBuf::from("secondary.html")); let mut main_window = tauri::utils::config::WindowConfig::default(); main_window.label = "main".to_string(); main_window.url = tauri::WebviewUrl::App(PathBuf::from("index.html")); let config = desktop_main_window_config_from_windows(&[secondary_window, main_window]) .expect("main window config"); assert_eq!(config.label, "main"); match config.url { tauri::WebviewUrl::App(path) => { let path = path.to_string_lossy(); assert!(path.starts_with("index.html?")); assert!(path.contains("clientRuntime=native_app")); assert!(path.contains("clientType=native_app")); assert!(path.contains("hostShell=tauri_desktop")); assert!(path.contains("hostPlatform=")); assert!(path.contains(&format!("hostVersion={}", env!("CARGO_PKG_VERSION")))); assert!(path.contains("bridgeVersion=")); assert!(path.contains("hostCapabilities=")); } other => panic!("unexpected main window url {other:?}"), } } #[cfg(dev)] #[test] fn desktop_main_window_config_uses_dev_url_in_dev_builds() { let mut main_window = tauri::utils::config::WindowConfig::default(); main_window.label = "main".to_string(); main_window.url = tauri::WebviewUrl::App(PathBuf::from("index.html")); let mut config = tauri::utils::config::Config::default(); config.app.windows = vec![main_window]; config.build.dev_url = Some(tauri::Url::parse("http://127.0.0.1:3000/").expect("desktop dev url")); let mut resolved = desktop_main_window_config_from_windows(&config.app.windows) .expect("main window config"); if let Some(dev_url) = config.build.dev_url.as_ref() { resolved = apply_desktop_dev_url_to_main_window_config(resolved, dev_url); } match resolved.url { tauri::WebviewUrl::External(url) => { assert_eq!(url.origin().ascii_serialization(), "http://127.0.0.1:3000"); assert_eq!(url.path(), "/"); assert_eq!( url.query_pairs() .find(|(key, _)| key == "hostShell") .map(|(_, value)| value.into_owned()), Some("tauri_desktop".to_string()) ); } other => panic!("unexpected dev main window url {other:?}"), } } #[test] fn desktop_main_window_config_rejects_missing_main_window() { let mut secondary_window = tauri::utils::config::WindowConfig::default(); secondary_window.label = "secondary".to_string(); let error = desktop_main_window_config_from_windows(&[secondary_window]) .expect_err("missing main window should fail startup"); assert!(matches!(error, tauri::Error::WindowNotFound)); } #[test] fn desktop_new_window_policy_denies_embedded_windows() { assert!(matches!( desktop_new_window_response::(), NewWindowResponse::Deny )); } #[test] fn desktop_new_window_policy_opens_only_safe_external_urls() { let external_url = tauri::Url::parse("https://example.com/share") .expect("external desktop new window url"); assert_eq!( desktop_new_window_external_url(&external_url), Some("https://example.com/share".to_string()) ); let same_origin_url = tauri::Url::parse("https://www.genarrative.world/works/detail") .expect("same-origin desktop new window url"); assert_eq!(desktop_new_window_external_url(&same_origin_url), None); let unsafe_url = tauri::Url::parse("javascript:alert(1)").expect("unsafe desktop new window url"); assert_eq!(desktop_new_window_external_url(&unsafe_url), None); } }