Files
Genarrative/apps/desktop-shell/src-tauri/src/shell/network.rs
T
kdletters c14d699ee0 统一桌面网络探测超时契约
将桌面网络探测超时提升到共享 HostBridge 契约

让 Tauri 网络状态探测镜像共享超时边界

增加桌面壳配置门禁反查网络探测超时

补充宿主壳方案文档和共享决策记录
2026-06-19 14:10:56 +08:00

142 lines
4.4 KiB
Rust

use crate::shell::events::host_bridge_event_script;
use crate::shell::url::WEB_APP_ORIGIN;
use serde_json::{json, Value};
use std::net::{TcpStream, ToSocketAddrs};
use std::time::Duration;
use tauri::{Url, WebviewWindow};
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" },
})
}
pub(crate) fn resolve_desktop_network_status() -> Value {
let timeout = Duration::from_millis(DESKTOP_NETWORK_CHECK_TIMEOUT_MS);
let is_reachable = desktop_network_probe_target()
.and_then(|(host, port)| (host.as_str(), port).to_socket_addrs().ok())
.map(|addresses| {
addresses.into_iter().any(|address| {
TcpStream::connect_timeout(&address, timeout)
.map(|stream| {
drop(stream);
true
})
.unwrap_or(false)
})
})
.unwrap_or(false);
desktop_network_status_payload(is_reachable)
}
pub(crate) fn register_desktop_network_events(window: &WebviewWindow) -> tauri::Result<()> {
let online_script = host_bridge_event_script(
"network.statusChanged",
desktop_network_status_payload(true),
)
.map_err(tauri::Error::Json)?;
let offline_script = host_bridge_event_script(
"network.statusChanged",
desktop_network_status_payload(false),
)
.map_err(tauri::Error::Json)?;
let current_status_script = host_bridge_event_script(
"network.statusChanged",
json!({
"isConnected": "__GENARRATIVE_DESKTOP_ONLINE__",
"isInternetReachable": "__GENARRATIVE_DESKTOP_ONLINE__",
"connectionType": "__GENARRATIVE_DESKTOP_CONNECTION_TYPE__",
"nativeType": "__GENARRATIVE_DESKTOP_NATIVE_TYPE__",
}),
)
.map_err(tauri::Error::Json)?
.replace("\"__GENARRATIVE_DESKTOP_ONLINE__\"", "navigator.onLine")
.replace(
"\"__GENARRATIVE_DESKTOP_CONNECTION_TYPE__\"",
"(navigator.onLine ? 'unknown' : 'none')",
)
.replace(
"\"__GENARRATIVE_DESKTOP_NATIVE_TYPE__\"",
"(navigator.onLine ? 'online' : 'offline')",
);
let script = format!(
"(() => {{
if (window.__GENARRATIVE_DESKTOP_NETWORK_LISTENER_INSTALLED__) {{
return true;
}}
window.__GENARRATIVE_DESKTOP_NETWORK_LISTENER_INSTALLED__ = true;
const emitOnline = () => {{ {} }};
const emitOffline = () => {{ {} }};
window.addEventListener('online', emitOnline);
window.addEventListener('offline', emitOffline);
{}
return true;
}})();",
online_script, offline_script, current_status_script
);
window.eval(script)
}
#[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",
})
);
}
}