接入原生返回栈状态消费

H5 新增 navigation.canGoBack hook 并在直达二级页时补齐返回锚点

路由导航保留完整原生宿主上下文并标记应用历史状态

补齐 HostBridge 返回栈消费测试、门禁和文档
This commit is contained in:
2026-06-19 10:59:47 +08:00
parent 4a16263729
commit ce0765b298
13 changed files with 364 additions and 5 deletions
+37
View File
@@ -3,7 +3,9 @@
import { describe, expect, it } from 'vitest';
import {
isAppHistoryState,
pushAppHistoryPath,
replaceAppHistoryPath,
resolvePathForSelectionStage,
resolveSelectionStageFromPath,
} from './appPageRoutes';
@@ -193,6 +195,41 @@ describe('appPageRoutes', () => {
);
});
it('keeps native app runtime context and marks app history entries', () => {
window.history.replaceState(
null,
'',
'/?clientRuntime=native_app&hostShell=tauri_desktop&hostPlatform=linux&hostVersion=0.1.0&bridgeVersion=1&hostCapabilities=host.getRuntime%2Chost.events%2Cnavigation.canGoBack',
);
pushAppHistoryPath('/creation/puzzle');
expect(window.location.pathname).toBe('/creation/puzzle');
expect(window.location.search).toBe(
'?clientRuntime=native_app&hostShell=tauri_desktop&hostPlatform=linux&hostVersion=0.1.0&bridgeVersion=1&hostCapabilities=host.getRuntime%2Chost.events%2Cnavigation.canGoBack',
);
expect(isAppHistoryState(window.history.state)).toBe(true);
});
it('replaces app history paths through the same runtime context rules', () => {
window.history.replaceState(
{ existing: true },
'',
'/creation/puzzle?clientRuntime=native_app&hostShell=expo_mobile&hostCapabilities=host.events%2Cnavigation.canGoBack',
);
replaceAppHistoryPath('/');
expect(window.location.pathname).toBe('/');
expect(window.location.search).toBe(
'?clientRuntime=native_app&hostShell=expo_mobile&hostCapabilities=host.events%2Cnavigation.canGoBack',
);
expect(window.history.state).toMatchObject({
existing: true,
});
expect(isAppHistoryState(window.history.state)).toBe(true);
});
it('clears creation restore query params when leaving the flow or switching flows', () => {
window.history.replaceState(
null,
+34 -1
View File
@@ -79,6 +79,7 @@ const ROUTE_STAGE_BY_PATH = new Map(
const APP_RUNTIME_CONTEXT_QUERY_KEYS =
HOST_BRIDGE_PRESERVED_RUNTIME_CONTEXT_QUERY_KEYS;
export const APP_HISTORY_STATE_KEY = '__genarrativeAppHistoryEntry';
export function normalizeAppPath(pathname: string) {
const trimmedPathname = pathname.trim().toLowerCase();
@@ -141,6 +142,14 @@ export function isKnownMainAppPagePath(pathname: string) {
}
export function pushAppHistoryPath(path: string) {
writeAppHistoryPath(path, 'push');
}
export function replaceAppHistoryPath(path: string) {
writeAppHistoryPath(path, 'replace');
}
function writeAppHistoryPath(path: string, mode: 'push' | 'replace') {
const nextUrl = new URL(path, window.location.origin);
const normalizedPath = normalizeAppPath(nextUrl.pathname);
const nextSearch = buildPreservedAppSearch(
@@ -156,7 +165,31 @@ export function pushAppHistoryPath(path: string) {
}
// 页面阶段变化是用户可感知导航,写入 history 以支持前进后退。
window.history.pushState(null, '', nextRelativeUrl);
if (mode === 'replace') {
window.history.replaceState(buildAppHistoryState(), '', nextRelativeUrl);
return;
}
window.history.pushState(buildAppHistoryState(), '', nextRelativeUrl);
}
export function isAppHistoryState(state: unknown) {
return Boolean(
state &&
typeof state === 'object' &&
(state as Record<string, unknown>)[APP_HISTORY_STATE_KEY] === true,
);
}
function buildAppHistoryState() {
const currentState =
window.history.state && typeof window.history.state === 'object'
? window.history.state
: {};
return {
...currentState,
[APP_HISTORY_STATE_KEY]: true,
};
}
function buildPreservedAppSearch(