接入原生壳本地通知能力

新增 notification.showLocal HostBridge 契约和 H5 facade

移动端通过 expo-notifications 发送即时本地通知

桌面端通过 Tauri notification 插件发送系统通知

更新壳能力检查、测试、方案文档和共享决策记录
This commit is contained in:
2026-06-18 04:24:55 +08:00
parent f34f98c1a0
commit bbfe4b7181
19 changed files with 685 additions and 7 deletions
@@ -31,6 +31,7 @@ import {
setHostAppBadgeCount,
setHostAppTitle,
setHostShareTarget,
showHostLocalNotification,
subscribeHostAppLifecycle,
subscribeHostImageDrop,
subscribeHostNetworkStatusChange,
@@ -623,6 +624,7 @@ describe('hostBridge', () => {
'file.exportImage',
'file.importImage',
'file.imageDropped',
'notification.showLocal',
]),
);
window.__TAURI__ = {
@@ -686,6 +688,12 @@ describe('hostBridge', () => {
mimeType: 'image/png',
bytes: 5,
});
await expect(
showHostLocalNotification({
title: ' 生成完成 ',
body: ' 作品已准备好 可以试玩 ',
}),
).resolves.toBe(true);
expect(invoke).toHaveBeenCalledWith('host_bridge_request', {
request: expect.objectContaining({
@@ -784,6 +792,15 @@ describe('hostBridge', () => {
timeoutMs: 30000,
}),
});
expect(invoke).toHaveBeenCalledWith('host_bridge_request', {
request: expect.objectContaining({
method: 'notification.showLocal',
payload: {
title: '生成完成',
body: '作品已准备好 可以试玩',
},
}),
});
});
test('原生 App 宿主不支持能力时回退到 H5 路径', async () => {
@@ -850,6 +867,11 @@ describe('hostBridge', () => {
}),
).resolves.toBe(false);
await expect(importHostImageFile()).resolves.toBe(false);
await expect(
showHostLocalNotification({
title: '生成完成',
}),
).resolves.toBe(false);
});
test('普通浏览器不处理宿主文件导出', async () => {
@@ -868,6 +890,33 @@ describe('hostBridge', () => {
}),
).resolves.toBe(false);
await expect(importHostImageFile()).resolves.toBe(false);
await expect(
showHostLocalNotification({
title: '生成完成',
}),
).resolves.toBe(false);
});
test('原生 App 宿主拒绝非法本地通知 payload', async () => {
const invoke = vi.fn();
window.history.replaceState(
null,
'',
nativeAppPath(['notification.showLocal']),
);
window.__TAURI__ = {
core: {
invoke: asTauriInvoke(invoke),
},
};
await expect(
showHostLocalNotification({
title: '生成\n完成',
}),
).resolves.toBe(false);
expect(invoke).not.toHaveBeenCalled();
});
test('原生 App 宿主通过 HostBridge 导出文本文件', async () => {
+26
View File
@@ -11,6 +11,7 @@ import type {
HostBridgeImageMimeType,
HostBridgeMethod,
HostBridgeRuntimeResult,
LocalNotificationPayload,
NetworkStatusResult,
OpenExternalUrlPayload,
SetBadgeCountPayload,
@@ -23,6 +24,7 @@ import {
normalizeHostBridgeConnectionType,
normalizeHostBridgeExternalUrl,
normalizeHostBridgeLifecycleState,
normalizeHostBridgeLocalNotification,
} from '../../../packages/shared/src/contracts/hostBridge';
import type {
WechatMiniProgramPayParams,
@@ -96,6 +98,8 @@ export type HostClipboardWriteTextRequest = {
export type HostHapticsImpactRequest = HapticsImpactPayload;
export type HostLocalNotificationRequest = LocalNotificationPayload;
export type HostAppTitleRequest = {
title: string;
};
@@ -818,6 +822,28 @@ export async function requestHostHapticsImpact(
}
}
export async function showHostLocalNotification(
params: HostLocalNotificationRequest,
) {
if (!canUseNativeHostCapability('notification.showLocal')) {
return false;
}
const notification = normalizeHostBridgeLocalNotification(params);
if (!notification) {
return false;
}
try {
return await requestNativeHostBoolean(
'notification.showLocal',
notification,
);
} catch {
return false;
}
}
export async function setHostAppTitle({ title }: HostAppTitleRequest) {
const normalizedTitle = title.trim();
if (!normalizedTitle || !canUseNativeHostCapability('app.setTitle')) {