From 15faa50992ca5016aca37faf91b229f82fa1c8fa Mon Sep 17 00:00:00 2001 From: kdletters Date: Sat, 20 Jun 2026 10:17:53 +0800 Subject: [PATCH] =?UTF-8?q?=E8=AE=B0=E5=BD=95=E6=A1=8C=E9=9D=A2=E7=BD=91?= =?UTF-8?q?=E7=BB=9C=E6=8E=A2=E6=B5=8B=E5=A4=B1=E8=B4=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 桌面网络状态探测保留 DNS 和 TCP 失败原因日志 桌面壳配置检查拒绝网络探测静默吞错 共享决策日志补充桌面网络探测失败边界 --- apps/desktop-shell/scripts/check-config.mjs | 6 ++ .../src-tauri/src/shell/network.rs | 70 +++++++++++++++---- .../shared-memory/decision-log.md | 7 ++ 3 files changed, 70 insertions(+), 13 deletions(-) diff --git a/apps/desktop-shell/scripts/check-config.mjs b/apps/desktop-shell/scripts/check-config.mjs index 84e626415..cc6388bef 100644 --- a/apps/desktop-shell/scripts/check-config.mjs +++ b/apps/desktop-shell/scripts/check-config.mjs @@ -2279,6 +2279,10 @@ const requiredRustHostSnippets = [ 'resolve_desktop_lifecycle_window_flag("minimized",', 'resolve_desktop_lifecycle_window_flag(\n "focused",', 'resolve_desktop_network_status', + 'resolve_desktop_network_reachability', + 'log_desktop_network_probe_failure', + 'desktop network reachability probe failed', + 'desktop_network_reachability_reports_missing_probe_target', 'network.statusChanged', 'register_desktop_navigation_events', 'desktop_navigation_state_script', @@ -2804,6 +2808,8 @@ for (const blockedLifecycleSnippet of [ 'let _ = close_window.hide()', 'catch (_error) {}', 'catch (error) {}', + '.to_socket_addrs().ok()', + 'TcpStream::connect_timeout(&address, timeout)\n .map(', ]) { if (rustHostSource.includes(blockedLifecycleSnippet)) { throw new Error( diff --git a/apps/desktop-shell/src-tauri/src/shell/network.rs b/apps/desktop-shell/src-tauri/src/shell/network.rs index cf2996218..09ee169f2 100644 --- a/apps/desktop-shell/src-tauri/src/shell/network.rs +++ b/apps/desktop-shell/src-tauri/src/shell/network.rs @@ -28,21 +28,57 @@ pub(crate) fn desktop_network_status_payload(is_online: bool) -> Value { }) } +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: {reason}"); +} + 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); + 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) } @@ -138,4 +174,12 @@ mod tests { }) ); } + + #[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()) + ); + } } diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index 04068afa5..0f991493a 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -2916,6 +2916,13 @@ - 影响范围:`apps/desktop-shell/src-tauri/src/shell/navigation.rs`、`apps/desktop-shell/scripts/check-config.mjs`。 - 验证方式:`cargo test --manifest-path apps/desktop-shell/src-tauri/Cargo.toml shell::navigation`、`npm run desktop-shell:typecheck`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。 +## 2026-06-20 桌面壳网络探测失败不可静默 + +- 背景:Tauri 桌面壳 `network.status` 会把主站 TCP 可达性作为当前宿主网络状态;如果探测目标解析、DNS 或 TCP 连接失败后只折叠为 offline,H5 可以收到离线状态,但开发侧无法判断是配置、解析还是连接问题。 +- 决策:`resolve_desktop_network_status()` 必须通过 `resolve_desktop_network_reachability(...)` 得到可达性或失败原因;失败时记录 `desktop network reachability probe failed: ...`,再继续按离线 payload 返回。配置检查拒绝网络探测重新用 `.to_socket_addrs().ok()` 或 `connect_timeout(...).map(...).unwrap_or(false)` 静默吞错。 +- 影响范围:`apps/desktop-shell/src-tauri/src/shell/network.rs`、`apps/desktop-shell/scripts/check-config.mjs`。 +- 验证方式:`cargo test --manifest-path apps/desktop-shell/src-tauri/Cargo.toml shell::network`、`npm run desktop-shell:typecheck`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。 + ## 2026-06-20 移动壳 EAS 原生包构建 profile - 背景:移动壳已能通过 Expo managed config 和 Metro production bundle smoke,但缺少原生安装包构建 profile;如果只保留 `expo export`,无法证明 Android / iOS 壳有进入原生分发链路的配置。