use crate::shell::url::WEB_APP_ORIGIN; use serde_json::{json, Value}; use std::net::{TcpStream, ToSocketAddrs}; use std::time::Duration; use tauri::Url; pub(crate) const DESKTOP_NETWORK_CHECK_TIMEOUT_MS: u64 = 1200; fn desktop_network_probe_target() -> Option<(String, u16)> { let url = Url::parse(WEB_APP_ORIGIN).ok()?; if url.scheme() != "https" { return None; } Some(( url.host_str()?.to_string(), url.port_or_known_default()?, )) } pub(crate) fn desktop_network_status_payload(is_online: bool) -> Value { json!({ "isConnected": is_online, "isInternetReachable": is_online, "connectionType": if is_online { "unknown" } else { "none" }, "nativeType": if is_online { "online" } else { "offline" }, }) } fn resolve_desktop_network_reachability( target: Option<(String, u16)>, timeout: Duration, ) -> Result { let Some((host, port)) = target else { return Err("network probe target unavailable".to_string()); }; let addresses = (host.as_str(), port) .to_socket_addrs() .map_err(|error| format!("resolve {host}:{port} failed: {error}"))?; let mut resolved_address = false; let mut last_connect_error: Option = None; for address in addresses { resolved_address = true; match TcpStream::connect_timeout(&address, timeout) { Ok(stream) => { drop(stream); return Ok(true); } Err(error) => { last_connect_error = Some(error.to_string()); } } } if !resolved_address { return Err(format!("resolve {host}:{port} returned no addresses")); } Err(format!( "connect {host}:{port} failed: {}", last_connect_error.unwrap_or_else(|| "unknown connection error".to_string()) )) } fn log_desktop_network_probe_failure(_reason: &str) { eprintln!("desktop network reachability probe failed"); } pub(crate) fn resolve_desktop_network_status() -> Value { let timeout = Duration::from_millis(DESKTOP_NETWORK_CHECK_TIMEOUT_MS); let is_reachable = match resolve_desktop_network_reachability(desktop_network_probe_target(), timeout) { Ok(is_reachable) => is_reachable, Err(reason) => { log_desktop_network_probe_failure(&reason); false } }; desktop_network_status_payload(is_reachable) } #[cfg(test)] mod tests { use super::*; use serde_json::json; #[test] fn desktop_network_probe_target_uses_public_web_origin() { let origin = Url::parse(WEB_APP_ORIGIN).expect("desktop web origin"); assert_eq!( desktop_network_probe_target(), Some(( origin.host_str().expect("desktop web host").to_string(), origin .port_or_known_default() .expect("desktop web default port") )) ); } #[test] fn desktop_network_status_payload_reports_reachability() { assert_eq!( desktop_network_status_payload(true), json!({ "isConnected": true, "isInternetReachable": true, "connectionType": "unknown", "nativeType": "online", }) ); assert_eq!( desktop_network_status_payload(false), json!({ "isConnected": false, "isInternetReachable": false, "connectionType": "none", "nativeType": "offline", }) ); } #[test] fn desktop_network_reachability_reports_missing_probe_target() { assert_eq!( resolve_desktop_network_reachability(None, Duration::from_millis(1)), Err("network probe target unavailable".to_string()) ); } #[test] fn desktop_network_probe_failure_logs_stable_label_only() { log_desktop_network_probe_failure("private network detail"); } }