Files
Genarrative/apps/desktop-shell/src-tauri/src/shell/network.rs
T
kdletters a6f160b03b 收口桌面网络探测诊断日志
桌面网络可达性探测失败只输出固定标签

桌面壳门禁拒绝输出网络探测详细原因

同步宿主壳方案和共享决策记录
2026-06-21 16:15:02 +08:00

140 lines
4.0 KiB
Rust

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<bool, String> {
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<String> = 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");
}
}