a6f160b03b
桌面网络可达性探测失败只输出固定标签 桌面壳门禁拒绝输出网络探测详细原因 同步宿主壳方案和共享决策记录
140 lines
4.0 KiB
Rust
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");
|
|
}
|
|
}
|