收口桌面角标通知响应边界
将 Tauri 角标成功响应包装收口到 badge 模块 将 Tauri 本地通知响应映射收口到 notifications 模块 让桌面 dispatch 只委托角标和通知请求 同步桌面壳配置门禁和共享记忆
This commit is contained in:
@@ -2272,6 +2272,8 @@ if (
|
||||
!desktopHostBridgeDispatchSource.includes(
|
||||
'set_desktop_app_badge_count(&app, &request)',
|
||||
) ||
|
||||
desktopHostBridgeDispatchSource.includes('"app.setBadgeCount" => match') ||
|
||||
desktopHostBridgeDispatchSource.includes('Ok(()) => ok(request.id, json!(true))') ||
|
||||
desktopHostBridgeDispatchSource.includes('set_badge_count') ||
|
||||
desktopHostBridgeDispatchSource.includes('BADGE_COUNT_MAX')
|
||||
) {
|
||||
@@ -2281,6 +2283,7 @@ for (const snippet of [
|
||||
'BADGE_COUNT_MAX',
|
||||
'fn badge_count_payload',
|
||||
'set_desktop_app_badge_count',
|
||||
'ok(request.id.clone(), json!(true))',
|
||||
'"count must be an integer between 0 and 99999"',
|
||||
]) {
|
||||
if (!desktopHostBridgeBadgeSource.includes(snippet)) {
|
||||
@@ -2315,6 +2318,8 @@ if (
|
||||
!desktopHostBridgeDispatchSource.includes(
|
||||
'show_desktop_local_notification(&app, &request)',
|
||||
) ||
|
||||
desktopHostBridgeDispatchSource.includes('"notification.showLocal" => match') ||
|
||||
desktopHostBridgeDispatchSource.includes('Ok(()) => ok(request.id, json!(true))') ||
|
||||
desktopHostBridgeDispatchSource.includes('app.notification()') ||
|
||||
desktopHostBridgeDispatchSource.includes('NotificationExt') ||
|
||||
desktopHostBridgeDispatchSource.includes('PermissionState')
|
||||
@@ -2332,6 +2337,7 @@ for (const snippet of [
|
||||
'PermissionState::Granted',
|
||||
'PermissionState::Denied',
|
||||
'PermissionState::Prompt | PermissionState::PromptWithRationale',
|
||||
'ok(request.id.clone(), json!(true))',
|
||||
'"notification permission denied"',
|
||||
'app.notification().builder()',
|
||||
]) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::host_bridge::protocol::{failed, HostBridgeRequest, HostBridgeResponse};
|
||||
use crate::host_bridge::protocol::{failed, ok, HostBridgeRequest, HostBridgeResponse};
|
||||
use serde_json::json;
|
||||
use serde_json::Value;
|
||||
use tauri::Manager;
|
||||
|
||||
@@ -32,15 +33,19 @@ fn badge_count_payload(request: &HostBridgeRequest) -> Result<Option<i64>, HostB
|
||||
pub(crate) fn set_desktop_app_badge_count(
|
||||
app: &tauri::AppHandle,
|
||||
request: &HostBridgeRequest,
|
||||
) -> Result<(), HostBridgeResponse> {
|
||||
let count = badge_count_payload(request)?;
|
||||
) -> HostBridgeResponse {
|
||||
let count = match badge_count_payload(request) {
|
||||
Ok(count) => count,
|
||||
Err(response) => return response,
|
||||
};
|
||||
let Some(window) = app.get_webview_window("main") else {
|
||||
return Err(failed(request.id.clone(), "host_error", "main window not found"));
|
||||
return failed(request.id.clone(), "host_error", "main window not found");
|
||||
};
|
||||
|
||||
window
|
||||
.set_badge_count(count)
|
||||
.map_err(|error| failed(request.id.clone(), "host_error", error.to_string()))
|
||||
match window.set_badge_count(count) {
|
||||
Ok(()) => ok(request.id.clone(), json!(true)),
|
||||
Err(error) => failed(request.id.clone(), "host_error", error.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -63,15 +63,9 @@ pub(super) async fn execute_host_bridge_request(
|
||||
"file.importAudio" => import_desktop_host_bridge_audio_file(&app, &request).await,
|
||||
"file.exportAudio" => export_desktop_host_bridge_audio_file(&app, &request).await,
|
||||
"app.setTitle" => set_desktop_host_bridge_window_title(&app, &request),
|
||||
"app.setBadgeCount" => match set_desktop_app_badge_count(&app, &request) {
|
||||
Ok(()) => ok(request.id, json!(true)),
|
||||
Err(response) => response,
|
||||
},
|
||||
"app.setBadgeCount" => set_desktop_app_badge_count(&app, &request),
|
||||
"network.status" => resolve_desktop_host_bridge_network_status(&request).await,
|
||||
"notification.showLocal" => match show_desktop_local_notification(&app, &request) {
|
||||
Ok(()) => ok(request.id, json!(true)),
|
||||
Err(response) => response,
|
||||
},
|
||||
"notification.showLocal" => show_desktop_local_notification(&app, &request),
|
||||
"share.setTarget" => set_desktop_host_bridge_share_target(&app, &request),
|
||||
"share.open" => open_desktop_host_bridge_share(&app, &request),
|
||||
_ => resolve_host_bridge_request(request),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::host_bridge::protocol::{failed, HostBridgeRequest, HostBridgeResponse};
|
||||
use crate::host_bridge::protocol::{failed, ok, HostBridgeRequest, HostBridgeResponse};
|
||||
use serde_json::json;
|
||||
use serde_json::Value;
|
||||
use tauri_plugin_notification::{NotificationExt, PermissionState};
|
||||
|
||||
@@ -86,25 +87,36 @@ fn desktop_notification_permission_action(
|
||||
pub(crate) fn show_desktop_local_notification(
|
||||
app: &tauri::AppHandle,
|
||||
request: &HostBridgeRequest,
|
||||
) -> Result<(), HostBridgeResponse> {
|
||||
let (title, body) = local_notification_payload(request)?;
|
||||
) -> HostBridgeResponse {
|
||||
let (title, body) = match local_notification_payload(request) {
|
||||
Ok(payload) => payload,
|
||||
Err(response) => return response,
|
||||
};
|
||||
let notification_manager = app.notification();
|
||||
let permission_state = notification_manager
|
||||
.permission_state()
|
||||
.map_err(|error| failed(request.id.clone(), "host_error", error.to_string()))?;
|
||||
.map_err(|error| failed(request.id.clone(), "host_error", error.to_string()));
|
||||
let permission_state = match permission_state {
|
||||
Ok(permission_state) => permission_state,
|
||||
Err(response) => return response,
|
||||
};
|
||||
let mut permission_action = desktop_notification_permission_action(permission_state);
|
||||
if permission_action == DesktopNotificationPermissionAction::Request {
|
||||
let requested_state = notification_manager
|
||||
.request_permission()
|
||||
.map_err(|error| failed(request.id.clone(), "host_error", error.to_string()))?;
|
||||
.map_err(|error| failed(request.id.clone(), "host_error", error.to_string()));
|
||||
let requested_state = match requested_state {
|
||||
Ok(requested_state) => requested_state,
|
||||
Err(response) => return response,
|
||||
};
|
||||
permission_action = desktop_notification_permission_action(requested_state);
|
||||
}
|
||||
if permission_action != DesktopNotificationPermissionAction::Show {
|
||||
return Err(failed(
|
||||
return failed(
|
||||
request.id.clone(),
|
||||
"host_error",
|
||||
"notification permission denied",
|
||||
));
|
||||
);
|
||||
}
|
||||
|
||||
let mut notification = app.notification().builder().title(title);
|
||||
@@ -112,9 +124,10 @@ pub(crate) fn show_desktop_local_notification(
|
||||
notification = notification.body(body);
|
||||
}
|
||||
|
||||
notification
|
||||
.show()
|
||||
.map_err(|error| failed(request.id.clone(), "host_error", error.to_string()))
|
||||
match notification.show() {
|
||||
Ok(()) => ok(request.id.clone(), json!(true)),
|
||||
Err(error) => failed(request.id.clone(), "host_error", error.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -2668,7 +2668,7 @@
|
||||
## 2026-06-18 桌面壳通知权限门禁
|
||||
|
||||
- 背景:桌面壳已经声明并实现 `notification.showLocal`,且 Tauri capability 只授权 `allow-host-bridge-request`;但 Rust handler 在清洗 payload 后直接调用 `notification.show()`,没有显式检查系统通知权限,也没有把权限拒绝固定成 HostBridge 失败语义。
|
||||
- 决策:`apps/desktop-shell/src-tauri/src/host_bridge/notifications.rs` 在发送即时本地通知前先调用 `permission_state()`,已授权才发送;处于 prompt / prompt-with-rationale 时只在 Rust 侧调用 `request_permission()` 后复判;最终未授权返回 `host_error: notification permission denied`。`dispatch.rs` 只保留 method 委托和 HostBridge 响应映射。桌面壳仍不把 `notification:*` 插件命令加入 capability permissions,不向 H5 暴露 notification 插件 JS guest API、远程推送、定时提醒或通知 token。
|
||||
- 决策:`apps/desktop-shell/src-tauri/src/host_bridge/notifications.rs` 在发送即时本地通知前先调用 `permission_state()`,已授权才发送;处于 prompt / prompt-with-rationale 时只在 Rust 侧调用 `request_permission()` 后复判;最终未授权返回 `host_error: notification permission denied`。`notifications.rs` 统一承接 payload 校验、权限检查、系统通知调用和 HostBridge 成功 / 失败响应映射,`dispatch.rs` 只保留 method 委托。桌面壳仍不把 `notification:*` 插件命令加入 capability permissions,不向 H5 暴露 notification 插件 JS guest API、远程推送、定时提醒或通知 token。
|
||||
- 影响范围:`apps/desktop-shell/src-tauri/src/host_bridge/notifications.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/dispatch.rs`、`apps/desktop-shell/scripts/check-config.mjs`、宿主壳能力统一协议文档、Expo / Tauri HostBridge 方案文档。
|
||||
- 验证方式:`npm run desktop-shell:typecheck`、`npm run desktop-shell:test`、`npm run check:native-shells`、`npm run typecheck -- --pretty false`、`npm run check:encoding`、`git diff --check`。
|
||||
|
||||
@@ -2804,3 +2804,10 @@
|
||||
- 决策:Expo 移动壳新增 `apps/mobile-shell/src/host-bridge/network.ts`,统一承接 `network.status` HostBridge 查询和成功响应包装,并复用 `src/shell/network.ts`;Tauri 桌面壳新增 `apps/desktop-shell/src-tauri/src/host_bridge/network.rs`,统一承接 `network.status` HostBridge 查询、成功响应和 `host_error` 失败映射,并复用 `shell::network::resolve_desktop_network_status`。两端 `dispatch` 只保留 method 委托,配置检查会拒绝分发层直接导入 shell network、直接执行 `resolve_desktop_network_status`、包装移动网络成功响应或重新映射桌面网络错误。
|
||||
- 影响范围:`apps/mobile-shell/src/host-bridge/network.ts`、`apps/mobile-shell/src/host-bridge/dispatch.ts`、`apps/mobile-shell/scripts/check-config.mjs`、`apps/desktop-shell/src-tauri/src/host_bridge/network.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/dispatch.rs`、`apps/desktop-shell/scripts/check-config.mjs`、`scripts/check-native-shells.mjs`、宿主壳能力统一协议文档、Expo / Tauri HostBridge 方案文档。
|
||||
- 验证方式:`npm run mobile-shell:typecheck`、`npm run mobile-shell:test -- src/host-bridge/bridge.test.ts`、`npm run desktop-shell:typecheck`、`cargo test --manifest-path apps/desktop-shell/src-tauri/Cargo.toml`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
|
||||
|
||||
## 2026-06-20 桌面角标与通知响应边界
|
||||
|
||||
- 背景:Tauri `app.setBadgeCount` 和 `notification.showLocal` 的系统调用已分别收在 `badge.rs` 与 `notifications.rs`,但 `dispatch.rs` 仍把 `Result<(), HostBridgeResponse>` 映射成 `ok(true)`,继续让分发层知道能力成功响应形状。
|
||||
- 决策:`apps/desktop-shell/src-tauri/src/host_bridge/badge.rs` 和 `apps/desktop-shell/src-tauri/src/host_bridge/notifications.rs` 统一返回 `HostBridgeResponse`,各自承接 payload 校验、系统调用、成功响应和失败响应映射;`dispatch.rs` 只保留 method 委托。桌面壳配置检查会拒绝 `dispatch.rs` 重新对这两个 method 做 `match` 或包装 `ok(true)`。
|
||||
- 影响范围:`apps/desktop-shell/src-tauri/src/host_bridge/badge.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/notifications.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/dispatch.rs`、`apps/desktop-shell/scripts/check-config.mjs`、`docs/project-memory/shared-memory/decision-log.md`。
|
||||
- 验证方式:`npm run desktop-shell:typecheck`、`npm run desktop-shell:test`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
|
||||
|
||||
Reference in New Issue
Block a user