新增 Expo 与 Tauri 原生宿主壳

新增 HostBridge 原生宿主契约和 H5 native_app transport

新增 Expo React Native 移动壳并收紧 WebView 外链边界

新增 Tauri 桌面壳并用 capability 收口受控命令

更新宿主壳方案、文档索引和共享记忆
This commit is contained in:
2026-06-17 21:39:34 +08:00
parent f92e791464
commit 9b7da18879
35 changed files with 16229 additions and 308 deletions

View File

@@ -5,6 +5,7 @@ import { afterEach, describe, expect, test, vi } from 'vitest';
import {
canUseHostShareGrid,
getHostRuntime,
getNativeAppHostRuntime,
isWechatMiniProgramWebViewRuntime,
navigateHostNativePage,
openHostShareGrid,
@@ -17,11 +18,26 @@ import {
resolveHostRuntime,
setHostShareTarget,
} from './hostBridge';
import { resetNativeAppHostBridgeForTest } from './nativeAppHostBridge';
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;
};
}
afterEach(() => {
vi.restoreAllMocks();
window.history.replaceState(null, '', '/');
window.wx = undefined;
delete window.ReactNativeWebView;
delete window.__TAURI__;
resetNativeAppHostBridgeForTest();
});
describe('hostBridge', () => {
@@ -43,7 +59,26 @@ describe('hostBridge', () => {
expect(
resolveHostRuntime({
location: { search: '?clientRuntime=native_app' },
location: {
search:
'?clientRuntime=native_app&hostShell=expo_mobile&hostPlatform=ios&hostVersion=0.1.0',
},
}),
).toMatchObject({
kind: 'native_app',
hostShell: 'expo_mobile',
hostPlatform: 'ios',
hostVersion: '0.1.0',
});
expect(
resolveHostRuntime({
tauri: {
core: {
invoke: asTauriInvoke(vi.fn(async () => null)),
},
},
location: { search: '' },
}).kind,
).toBe('native_app');
@@ -227,4 +262,100 @@ describe('hostBridge', () => {
expect(canUseHostShareGrid()).toBe(false);
expect(setHostShareTarget({ type: 'test' })).toBe(false);
});
test('原生 App 宿主通过 HostBridge 处理导航、登录和支付', async () => {
const invoke = vi.fn(async (_command: string, args?: Record<string, unknown>) => {
const request = (args as { request: { id: string; method: string } })
.request;
return {
bridge: 'GenarrativeHostBridge',
version: 1,
id: request.id,
ok: true,
result: request.method === 'host.getRuntime'
? {
shell: 'tauri_desktop',
platform: 'linux',
hostVersion: '0.1.0',
bridgeVersion: 1,
capabilities: ['host.getRuntime'],
}
: true,
};
});
window.history.replaceState(
null,
'',
'/?clientRuntime=native_app&hostShell=tauri_desktop',
);
window.__TAURI__ = {
core: {
invoke: asTauriInvoke(invoke),
},
};
await expect(navigateHostNativePage('/settings')).resolves.toBe(true);
await expect(requestHostLogin()).resolves.toBe(true);
await expect(
requestHostPayment({
payload: null,
orderId: 'order-1',
}),
).resolves.toBe(true);
await expect(getNativeAppHostRuntime()).resolves.toMatchObject({
shell: 'tauri_desktop',
platform: 'linux',
});
expect(invoke).toHaveBeenCalledWith('host_bridge_request', {
request: expect.objectContaining({
method: 'navigation.openNativePage',
payload: { url: '/settings' },
}),
});
expect(invoke).toHaveBeenCalledWith('host_bridge_request', {
request: expect.objectContaining({
method: 'auth.requestLogin',
}),
});
expect(invoke).toHaveBeenCalledWith('host_bridge_request', {
request: expect.objectContaining({
method: 'payment.request',
payload: {
payload: null,
orderId: 'order-1',
},
}),
});
});
test('原生 App 宿主不支持能力时回退到 H5 路径', async () => {
window.history.replaceState(null, '', '/?clientRuntime=native_app');
window.__TAURI__ = {
core: {
invoke: asTauriInvoke(vi.fn(async (_command: string, args?: Record<string, unknown>) => {
const request = (args as { request: { id: string } }).request;
return {
bridge: 'GenarrativeHostBridge',
version: 1,
id: request.id,
ok: false,
error: {
code: 'unsupported_method',
message: 'unsupported_method',
},
};
})),
},
};
await expect(navigateHostNativePage('/settings')).resolves.toBe(false);
await expect(requestHostLogin()).resolves.toBe(false);
await expect(
requestHostPayment({
payload: null,
orderId: 'order-1',
}),
).resolves.toBe(false);
});
});