补齐平台壳宿主消费门禁

新增平台壳草稿通知和角标 HostBridge 同步层

补充真实 Tauri transport 单测和跳一跳完成集成测试

扩展原生壳门禁覆盖 H5 宿主消费链路

更新宿主壳协议、方案和共享决策记录
This commit is contained in:
2026-06-19 03:38:24 +08:00
parent 5ab7b53bf2
commit 49fffbca74
8 changed files with 379 additions and 13 deletions
@@ -191,10 +191,6 @@ import {
saveBabyObjectMatchDraft,
} from '../../services/edutainment-baby-object';
import { getExternalGenerationQueueOverview } from '../../services/external-generation';
import {
setHostAppBadgeCount,
showHostLocalNotification,
} from '../../services/host-bridge/hostBridge';
import {
jumpHopClient,
type JumpHopGalleryCardResponse,
@@ -553,6 +549,10 @@ import {
type PlatformHostDraftNotification,
sendPlatformHostDraftNotificationOnce,
} from './platformHostNotificationModel';
import {
sendPlatformHostDraftNotificationToHost,
syncPlatformHostDraftBadgeCountToHost,
} from './platformHostBridgeSync';
import {
buildMatch3DProfileFromSession,
hasMatch3DRuntimeAsset,
@@ -2030,7 +2030,7 @@ export function PlatformEntryFlowShellImpl({
sendPlatformHostDraftNotificationOnce(
sentPlatformHostDraftNotificationKeysRef.current,
notification,
showHostLocalNotification,
sendPlatformHostDraftNotificationToHost,
);
},
[],
@@ -3340,7 +3340,7 @@ export function PlatformEntryFlowShellImpl({
[draftGenerationNotices, visibleDraftNoticeKeys],
);
useEffect(() => {
void setHostAppBadgeCount({ count: unreadDraftGenerationUpdateCount });
syncPlatformHostDraftBadgeCountToHost(unreadDraftGenerationUpdateCount);
}, [unreadDraftGenerationUpdateCount]);
const resultViewError =
autosaveCoordinator.customWorldAutoSaveError ??
@@ -0,0 +1,132 @@
/* @vitest-environment jsdom */
import { afterEach, describe, expect, test, vi } from 'vitest';
import type {
HostBridgeCapability,
HostBridgeRequest,
} from '../../../packages/shared/src/contracts/hostBridge';
import { resetHostRuntimeCacheForTest } from '../../services/host-bridge/hostBridge';
import { resetNativeAppHostBridgeForTest } from '../../services/host-bridge/nativeAppHostBridge';
import {
sendPlatformHostDraftNotificationToHost,
syncPlatformHostDraftBadgeCountToHost,
} from './platformHostBridgeSync';
function asTauriInvoke(
invoke: (command: string, args?: Record<string, unknown>) => Promise<unknown>,
) {
return async function tauriInvoke<Result = unknown>(
command: string,
args?: Record<string, unknown>,
) {
return (await invoke(command, args)) as Result;
};
}
function nativeAppPath(capabilities: HostBridgeCapability[] = []) {
const params = new URLSearchParams({
clientRuntime: 'native_app',
hostShell: 'tauri_desktop',
});
if (capabilities.length > 0) {
params.set('hostCapabilities', capabilities.join(','));
}
return `/?${params.toString()}`;
}
function installTauriHostBridgeRecorder() {
const requests: HostBridgeRequest[] = [];
const invoke = vi.fn(async (_command: string, args?: Record<string, unknown>) => {
const request = (args as { request: HostBridgeRequest }).request;
requests.push(request);
return {
bridge: 'GenarrativeHostBridge',
version: 1,
id: request.id,
ok: true,
result: true,
};
});
window.__TAURI__ = {
core: {
invoke: asTauriInvoke(invoke),
},
};
return { invoke, requests };
}
afterEach(() => {
vi.restoreAllMocks();
window.history.replaceState(null, '', '/');
delete window.__TAURI__;
resetNativeAppHostBridgeForTest();
resetHostRuntimeCacheForTest();
});
describe('platformHostBridgeSync', () => {
test('平台壳草稿通知通过真实 HostBridge 请求本地系统通知', async () => {
window.history.replaceState(
null,
'',
nativeAppPath(['notification.showLocal']),
);
const { invoke, requests } = installTauriHostBridgeRecorder();
await expect(
sendPlatformHostDraftNotificationToHost({
title: ' 生成完成 ',
body: ' 拼图草稿 puzzle-session-1 生成任务已完成 ',
}),
).resolves.toBe(true);
expect(invoke).toHaveBeenCalledWith('host_bridge_request', {
request: expect.objectContaining({
method: 'notification.showLocal',
payload: {
title: '生成完成',
body: '拼图草稿 puzzle-session-1 生成任务已完成',
},
}),
});
expect(requests).toHaveLength(1);
});
test('平台壳草稿未读计数通过真实 HostBridge 同步应用角标', async () => {
window.history.replaceState(
null,
'',
nativeAppPath(['app.setBadgeCount']),
);
const { invoke, requests } = installTauriHostBridgeRecorder();
await expect(syncPlatformHostDraftBadgeCountToHost(3)).resolves.toBe(true);
expect(invoke).toHaveBeenCalledWith('host_bridge_request', {
request: expect.objectContaining({
method: 'app.setBadgeCount',
payload: {
count: 3,
},
}),
});
expect(requests).toHaveLength(1);
});
test('宿主未声明草稿通知和角标能力时平台壳保持 H5 回退', async () => {
window.history.replaceState(null, '', nativeAppPath());
const { invoke } = installTauriHostBridgeRecorder();
await expect(
sendPlatformHostDraftNotificationToHost({
title: '生成完成',
body: '拼图草稿已完成。',
}),
).resolves.toBe(false);
await expect(syncPlatformHostDraftBadgeCountToHost(1)).resolves.toBe(false);
expect(invoke).not.toHaveBeenCalled();
});
});
@@ -0,0 +1,22 @@
import {
setHostAppBadgeCount,
showHostLocalNotification,
} from '../../services/host-bridge/hostBridge';
import type { PlatformHostDraftNotification } from './platformHostNotificationModel';
type PlatformHostDraftNotificationPayload = Pick<
PlatformHostDraftNotification,
'title' | 'body'
>;
// 中文注释:平台壳只把已派生出的通知和角标计数交给 HostBridge,不改业务事实。
export function sendPlatformHostDraftNotificationToHost({
title,
body,
}: PlatformHostDraftNotificationPayload) {
return showHostLocalNotification({ title, body });
}
export function syncPlatformHostDraftBadgeCountToHost(count: number) {
return setHostAppBadgeCount({ count });
}