补齐桌面壳插件失败观测
桌面剪贴板和通知插件异常记录日志 Rust 单测覆盖稳定错误和日志边界 配置门禁和文档同步桌面插件观测约束
This commit is contained in:
@@ -2065,6 +2065,10 @@ for (const snippet of [
|
||||
'write_desktop_clipboard_text(app, text)',
|
||||
'read_desktop_clipboard_text(app)',
|
||||
'normalize_clipboard_text(&text)',
|
||||
'log_desktop_clipboard_failure',
|
||||
'desktop clipboard failed for {label}: {error}',
|
||||
'log_desktop_clipboard_failure("write.text", &error)',
|
||||
'log_desktop_clipboard_failure("read.text", &error)',
|
||||
'clipboard_write_unavailable_response',
|
||||
'clipboard_read_unavailable_response',
|
||||
'required_string_payload(request, "text")',
|
||||
@@ -2073,6 +2077,7 @@ for (const snippet of [
|
||||
'"clipboard write unavailable"',
|
||||
'"clipboard read unavailable"',
|
||||
'clipboard_unavailable_responses_are_stable',
|
||||
'clipboard_failures_are_logged_without_exposing_native_detail',
|
||||
]) {
|
||||
if (!desktopHostBridgeClipboardSource.includes(snippet)) {
|
||||
throw new Error(`desktop shell clipboard module is missing ${snippet}`);
|
||||
@@ -2699,6 +2704,11 @@ for (const snippet of [
|
||||
'desktop_notification_permission_action',
|
||||
'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())',
|
||||
'PermissionState::Granted',
|
||||
'PermissionState::Denied',
|
||||
'PermissionState::Prompt | PermissionState::PromptWithRationale',
|
||||
@@ -2707,6 +2717,7 @@ for (const snippet of [
|
||||
'local_notification_payload_truncates_to_shared_contract_limits',
|
||||
'notification_permission_unavailable_response_is_stable',
|
||||
'notification_delivery_unavailable_response_is_stable',
|
||||
'notification_failures_are_logged_without_exposing_native_detail',
|
||||
'"a".repeat(HOST_BRIDGE_LOCAL_NOTIFICATION_TITLE_MAX_LENGTH)',
|
||||
'"b".repeat(HOST_BRIDGE_LOCAL_NOTIFICATION_BODY_MAX_LENGTH)',
|
||||
'"notification permission unavailable"',
|
||||
|
||||
@@ -27,6 +27,14 @@ pub(crate) fn read_desktop_clipboard_text(app: &tauri::AppHandle) -> Result<Stri
|
||||
.map_err(|error| error.to_string())
|
||||
}
|
||||
|
||||
fn log_desktop_clipboard_failure(label: &str, error: &str) -> bool {
|
||||
if !error.is_empty() {
|
||||
eprintln!("desktop clipboard failed for {label}: {error}");
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub(crate) fn clipboard_write_unavailable_response(
|
||||
request: &HostBridgeRequest,
|
||||
) -> HostBridgeResponse {
|
||||
@@ -58,7 +66,10 @@ pub(crate) fn write_desktop_host_bridge_clipboard_text(
|
||||
|
||||
match write_desktop_clipboard_text(app, text) {
|
||||
Ok(()) => ok(request.id.clone(), json!(true)),
|
||||
Err(_) => clipboard_write_unavailable_response(request),
|
||||
Err(error) => {
|
||||
log_desktop_clipboard_failure("write.text", &error);
|
||||
clipboard_write_unavailable_response(request)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,7 +84,10 @@ pub(crate) fn read_desktop_host_bridge_clipboard_text(
|
||||
"text": text,
|
||||
}),
|
||||
),
|
||||
Err(_) => clipboard_read_unavailable_response(request),
|
||||
Err(error) => {
|
||||
log_desktop_clipboard_failure("read.text", &error);
|
||||
clipboard_read_unavailable_response(request)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,4 +154,16 @@ mod tests {
|
||||
assert_eq!(read_error.code, "host_error");
|
||||
assert_eq!(read_error.message, "clipboard read unavailable");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clipboard_failures_are_logged_without_exposing_native_detail() {
|
||||
assert!(!log_desktop_clipboard_failure(
|
||||
"write.text",
|
||||
"private clipboard detail"
|
||||
));
|
||||
assert!(!log_desktop_clipboard_failure(
|
||||
"read.text",
|
||||
"private clipboard detail"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,6 +105,14 @@ 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}");
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub(crate) fn show_desktop_local_notification(
|
||||
app: &tauri::AppHandle,
|
||||
request: &HostBridgeRequest,
|
||||
@@ -116,7 +124,10 @@ pub(crate) fn show_desktop_local_notification(
|
||||
let notification_manager = app.notification();
|
||||
let permission_state = notification_manager
|
||||
.permission_state()
|
||||
.map_err(|_| notification_permission_unavailable_response(request));
|
||||
.map_err(|error| {
|
||||
log_desktop_notification_failure("permission.state", &error.to_string());
|
||||
notification_permission_unavailable_response(request)
|
||||
});
|
||||
let permission_state = match permission_state {
|
||||
Ok(permission_state) => permission_state,
|
||||
Err(response) => return response,
|
||||
@@ -125,7 +136,10 @@ pub(crate) fn show_desktop_local_notification(
|
||||
if permission_action == DesktopNotificationPermissionAction::Request {
|
||||
let requested_state = notification_manager
|
||||
.request_permission()
|
||||
.map_err(|_| notification_permission_unavailable_response(request));
|
||||
.map_err(|error| {
|
||||
log_desktop_notification_failure("permission.request", &error.to_string());
|
||||
notification_permission_unavailable_response(request)
|
||||
});
|
||||
let requested_state = match requested_state {
|
||||
Ok(requested_state) => requested_state,
|
||||
Err(response) => return response,
|
||||
@@ -150,7 +164,10 @@ pub(crate) fn show_desktop_local_notification(
|
||||
request.id.clone(),
|
||||
desktop_notification_delivered_to_system_result(),
|
||||
),
|
||||
Err(_) => notification_delivery_unavailable_response(request),
|
||||
Err(error) => {
|
||||
log_desktop_notification_failure("delivery.show", &error.to_string());
|
||||
notification_delivery_unavailable_response(request)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,6 +236,22 @@ mod tests {
|
||||
assert_eq!(error.message, "notification delivery unavailable");
|
||||
}
|
||||
|
||||
#[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"
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn local_notification_payload_rejects_empty_and_control_text() {
|
||||
let mut empty = request("notification.showLocal");
|
||||
|
||||
@@ -3118,6 +3118,12 @@
|
||||
- 决策:`apps/mobile-shell/src/host-bridge/clipboard.ts` 必须在剪贴板写入 / 读取失败时记录 `mobile clipboard failed for ...` 日志;`apps/mobile-shell/src/host-bridge/haptics.ts` 必须在触觉派发失败时记录 `mobile haptics failed for ...` 日志;`apps/mobile-shell/src/host-bridge/network.ts` 必须在 HostBridge 网络状态查询失败时记录 `mobile network failed for ...` 日志。HostBridge 对 H5 仍只返回稳定错误语义;该约束不新增后台网络探测、任意系统能力或 H5 业务兜底路径。
|
||||
- 验证方式:`npm run mobile-shell:test -- src/host-bridge/clipboard.test.ts src/host-bridge/haptics.test.ts src/host-bridge/network.test.ts`、`npm run mobile-shell:typecheck`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
|
||||
|
||||
## 2026-06-20 桌面壳剪贴板与通知异常必须可观测
|
||||
|
||||
- 背景: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 插件。
|
||||
- 验证方式:`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 移动壳门禁脚本必须自登记自扫描
|
||||
|
||||
- 背景:Expo 移动壳单端检查已把 `apps/mobile-shell/scripts/` 纳入生产源码扫描入口,但 `check-config.mjs` 自身仍被排除在脚本清单和替身词扫描之外;这会让移动壳与桌面壳门禁结构不一致,也可能让后续门禁反查内容绕过生产替身词规则。
|
||||
|
||||
@@ -346,6 +346,8 @@ 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-18 追加:H5 账号状态刷新开始消费 `app.reloadWebView`。用户登录成功、退出登录、其它身份边界变化或登录状态异常页点击重新尝试时,`AuthGate` 会优先请求 Expo 壳刷新当前 WebView;宿主未声明或刷新失败时再回退浏览器刷新,避免在移动壳内绕过受控容器刷新入口。
|
||||
|
||||
2026-06-18 追加:移动壳 WebView 内容 / 渲染进程终止时复用同一受控刷新路径。iOS `onContentProcessDidTerminate` 和 Android `onRenderProcessGone` 只调用当前 `react-native-webview` 的 `reload()`,不改写 H5 URL、不注入额外脚本、不新增宿主恢复页面,避免系统回收 WebView 进程后留下空白容器。
|
||||
|
||||
Reference in New Issue
Block a user