diff --git a/apps/desktop-shell/scripts/check-config.mjs b/apps/desktop-shell/scripts/check-config.mjs index 7f7a8de90..562c920ee 100644 --- a/apps/desktop-shell/scripts/check-config.mjs +++ b/apps/desktop-shell/scripts/check-config.mjs @@ -2992,10 +2992,10 @@ for (const snippet of [ 'notification_permission_unavailable_response', 'notification_delivery_unavailable_response', 'log_desktop_notification_failure', - 'desktop notification failed for {label}: {error}', - 'log_desktop_notification_failure("permission.state", &error.to_string())', - 'log_desktop_notification_failure("permission.request", &error.to_string())', - 'log_desktop_notification_failure("delivery.show", &error.to_string())', + 'desktop notification failed for {label}', + 'log_desktop_notification_failure("permission.state")', + 'log_desktop_notification_failure("permission.request")', + 'log_desktop_notification_failure("delivery.show")', 'PermissionState::Granted', 'PermissionState::Denied', 'PermissionState::Prompt | PermissionState::PromptWithRationale', @@ -3005,6 +3005,7 @@ for (const snippet of [ 'notification_permission_unavailable_response_is_stable', 'notification_delivery_unavailable_response_is_stable', 'notification_failures_are_logged_without_exposing_native_detail', + 'notification_failures_log_stable_label_only', '"a".repeat(HOST_BRIDGE_LOCAL_NOTIFICATION_TITLE_MAX_LENGTH)', '"b".repeat(HOST_BRIDGE_LOCAL_NOTIFICATION_BODY_MAX_LENGTH)', '"notification permission unavailable"', @@ -3028,6 +3029,14 @@ if ( 'desktop shell notification module must check and request notification permission', ); } +if ( + desktopHostBridgeNotificationsSource.includes('desktop notification failed for {label}: {error}') || + desktopHostBridgeNotificationsSource.includes('log_desktop_notification_failure("permission.state", &error.to_string())') || + desktopHostBridgeNotificationsSource.includes('log_desktop_notification_failure("permission.request", &error.to_string())') || + desktopHostBridgeNotificationsSource.includes('log_desktop_notification_failure("delivery.show", &error.to_string())') +) { + throw new Error('desktop shell notification module must hide native notification errors'); +} if (!h5HostBridgeSource.includes('HOST_BRIDGE_RUNTIME_REFRESH_TIMEOUT_MS,')) { throw new Error('H5 HostBridge facade must import shared runtime refresh timeout'); } diff --git a/apps/desktop-shell/src-tauri/src/host_bridge/notifications.rs b/apps/desktop-shell/src-tauri/src/host_bridge/notifications.rs index 65a224646..9bdd60f4f 100644 --- a/apps/desktop-shell/src-tauri/src/host_bridge/notifications.rs +++ b/apps/desktop-shell/src-tauri/src/host_bridge/notifications.rs @@ -105,11 +105,8 @@ fn notification_delivery_unavailable_response(request: &HostBridgeRequest) -> Ho ) } -fn log_desktop_notification_failure(label: &str, error: &str) -> bool { - if !error.is_empty() { - eprintln!("desktop notification failed for {label}: {error}"); - } - +fn log_desktop_notification_failure(label: &str) -> bool { + eprintln!("desktop notification failed for {label}"); false } @@ -124,8 +121,8 @@ pub(crate) fn show_desktop_local_notification( let notification_manager = app.notification(); let permission_state = notification_manager .permission_state() - .map_err(|error| { - log_desktop_notification_failure("permission.state", &error.to_string()); + .map_err(|_error| { + log_desktop_notification_failure("permission.state"); notification_permission_unavailable_response(request) }); let permission_state = match permission_state { @@ -136,8 +133,8 @@ pub(crate) fn show_desktop_local_notification( if permission_action == DesktopNotificationPermissionAction::Request { let requested_state = notification_manager .request_permission() - .map_err(|error| { - log_desktop_notification_failure("permission.request", &error.to_string()); + .map_err(|_error| { + log_desktop_notification_failure("permission.request"); notification_permission_unavailable_response(request) }); let requested_state = match requested_state { @@ -164,8 +161,8 @@ pub(crate) fn show_desktop_local_notification( request.id.clone(), desktop_notification_delivered_to_system_result(), ), - Err(error) => { - log_desktop_notification_failure("delivery.show", &error.to_string()); + Err(_error) => { + log_desktop_notification_failure("delivery.show"); notification_delivery_unavailable_response(request) } } @@ -238,18 +235,14 @@ mod tests { #[test] fn notification_failures_are_logged_without_exposing_native_detail() { - assert!(!log_desktop_notification_failure( - "permission.state", - "private notification detail" - )); - assert!(!log_desktop_notification_failure( - "permission.request", - "private notification detail" - )); - assert!(!log_desktop_notification_failure( - "delivery.show", - "private notification detail" - )); + assert!(!log_desktop_notification_failure("permission.state")); + assert!(!log_desktop_notification_failure("permission.request")); + assert!(!log_desktop_notification_failure("delivery.show")); + } + + #[test] + fn notification_failures_log_stable_label_only() { + assert!(!log_desktop_notification_failure("delivery.show")); } #[test] diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index 24c6dda28..18c255a81 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -3150,7 +3150,7 @@ - 背景:Tauri 桌面壳已声明剪贴板读写和即时本地通知;这些能力会触发 Tauri clipboard-manager 和 notification 插件。如果插件异常只被折叠成稳定 HostBridge 错误,H5 语义安全,但开发侧无法区分系统剪贴板不可用、通知权限查询异常或系统通知投递失败。 - 决策:`apps/desktop-shell/src-tauri/src/host_bridge/clipboard.rs` 必须在剪贴板写入 / 读取失败时记录 `desktop clipboard failed for ...` 日志;`apps/desktop-shell/src-tauri/src/host_bridge/notifications.rs` 必须在通知权限状态读取、权限请求和通知投递失败时记录 `desktop notification failed for ...` 日志。HostBridge 对 H5 仍只返回稳定错误语义;该约束不新增遥测 SDK、后台通知、自动更新或 H5 直连 Tauri JS 插件。 -- 2026-06-21 调整:桌面剪贴板失败日志只记录 `desktop clipboard failed for ` 固定标签,不输出 clipboard-manager 插件错误、系统剪贴板细节或其它平台异常字符串;配置检查拒绝 `clipboard.rs` 重新拼接 `: {error}` 或把写入 / 读取错误传给日志函数。 +- 2026-06-21 调整:桌面剪贴板和本地通知失败日志只记录 `desktop clipboard failed for ` / `desktop notification failed for ` 固定标签,不输出 clipboard-manager 插件错误、notification permission / delivery 插件错误、系统剪贴板细节或其它平台异常字符串;配置检查拒绝 `clipboard.rs` / `notifications.rs` 重新拼接 `: {error}` 或把写入 / 读取 / 权限 / 投递错误传给日志函数。 - 验证方式:`cargo test --manifest-path apps/desktop-shell/src-tauri/Cargo.toml host_bridge::clipboard host_bridge::notifications`、`npm run desktop-shell:typecheck`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。 ## 2026-06-20 桌面壳文件能力系统异常必须可观测 diff --git a/docs/【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md b/docs/【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md index b9de539b6..2a8acbf78 100644 --- a/docs/【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md +++ b/docs/【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md @@ -350,7 +350,7 @@ GameBridge 禁止: 2026-06-19 追加:移动壳 `haptics.impact` 的 HostBridge payload 解析、共享 style 归一、Expo style 映射和 `Haptics.impactAsync(...)` 真实调用统一收口在 `apps/mobile-shell/src/host-bridge/haptics.ts`;`dispatch.ts` 只按 method 委托 `runMobileHostBridgeHapticsImpact(request.payload)`。 -2026-06-20 追加:桌面壳剪贴板和本地通知插件异常必须可观测。Tauri clipboard-manager 读写失败、notification permission state / request 失败、系统通知 show 失败时,桌面壳必须分别记录 `desktop clipboard failed for ...` 或 `desktop notification failed for ...` 日志;HostBridge 回包仍只暴露稳定 `clipboard write unavailable`、`clipboard read unavailable`、`notification permission unavailable` 或 `notification delivery unavailable` 语义。该约束不新增遥测 SDK、后台通知、自动更新或 H5 直连 Tauri JS 插件。2026-06-21 调整:桌面剪贴板失败日志只记录 `desktop clipboard failed for ` 固定标签,不把 clipboard-manager 插件错误或系统剪贴板细节写入 stderr。 +2026-06-20 追加:桌面壳剪贴板和本地通知插件异常必须可观测。Tauri clipboard-manager 读写失败、notification permission state / request 失败、系统通知 show 失败时,桌面壳必须分别记录 `desktop clipboard failed for ...` 或 `desktop notification failed for ...` 日志;HostBridge 回包仍只暴露稳定 `clipboard write unavailable`、`clipboard read unavailable`、`notification permission unavailable` 或 `notification delivery unavailable` 语义。该约束不新增遥测 SDK、后台通知、自动更新或 H5 直连 Tauri JS 插件。2026-06-21 调整:桌面剪贴板和本地通知失败日志只记录 `desktop clipboard failed for ` / `desktop notification failed for ` 固定标签,不把 clipboard-manager、notification 插件错误或系统细节写入 stderr。 2026-06-20 追加:桌面壳文件导入导出的系统异常必须可观测。Tauri dialog 路径转换失败、后台文件读写任务失败或任务 join 失败时,桌面壳必须分别记录 `desktop file export failed for ...` 或 `desktop file import failed for ...` 日志;HostBridge 回包仍只暴露稳定 `file export unavailable` 或 `file import unavailable` 语义,不透传本地路径、系统错误或线程细节。用户取消系统文件对话框仍返回 `cancelled`,不记录为异常。