收口原生能力可信边界
H5 原生能力只信任真实 host.getRuntime 回包 移除桌面壳未声明网络事件白名单 补齐原生壳能力门控测试和共享决策记录
This commit is contained in:
@@ -87,6 +87,7 @@ const hostBridgeMocks = vi.hoisted(() => ({
|
||||
hostPlatform: null as string | null,
|
||||
hostVersion: null as string | null,
|
||||
hostCapabilities: [],
|
||||
nativeRuntimeTrusted: false,
|
||||
miniProgramEnv: null as string | null,
|
||||
})),
|
||||
requestHostLogin: vi.fn(),
|
||||
@@ -195,6 +196,7 @@ beforeEach(() => {
|
||||
hostPlatform: null,
|
||||
hostVersion: null,
|
||||
hostCapabilities: [],
|
||||
nativeRuntimeTrusted: false,
|
||||
miniProgramEnv: null,
|
||||
});
|
||||
hostBridgeMocks.requestHostLogin.mockResolvedValue(true);
|
||||
@@ -486,6 +488,7 @@ test('auth gate uses mini program auth bridge instead of opening login modal in
|
||||
hostPlatform: null,
|
||||
hostVersion: null,
|
||||
hostCapabilities: [],
|
||||
nativeRuntimeTrusted: false,
|
||||
miniProgramEnv: null,
|
||||
});
|
||||
authMocks.getAuthLoginOptions.mockResolvedValue({
|
||||
|
||||
@@ -4,7 +4,10 @@ import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||
import type { ComponentProps } from 'react';
|
||||
import { afterEach, expect, test, vi } from 'vitest';
|
||||
|
||||
import { resetHostRuntimeCacheForTest } from '../../services/host-bridge/hostBridge';
|
||||
import {
|
||||
resetHostRuntimeCacheForTest,
|
||||
setHostRuntimeCacheForTest,
|
||||
} from '../../services/host-bridge/hostBridge';
|
||||
import { resetNativeAppHostBridgeForTest } from '../../services/host-bridge/nativeAppHostBridge';
|
||||
import type { CreativeAudioAsset } from './creativeAudioFileAsset';
|
||||
import { CreativeAudioInputPanel } from './CreativeAudioInputPanel';
|
||||
@@ -57,6 +60,16 @@ function buildExportableAsset(
|
||||
};
|
||||
}
|
||||
|
||||
function trustNativeHostRuntime(capabilities: Parameters<typeof setHostRuntimeCacheForTest>[0]['capabilities']) {
|
||||
setHostRuntimeCacheForTest({
|
||||
shell: 'tauri_desktop',
|
||||
platform: 'linux',
|
||||
hostVersion: '0.1.0',
|
||||
bridgeVersion: 1,
|
||||
capabilities,
|
||||
});
|
||||
}
|
||||
|
||||
function renderPanel(
|
||||
overrides: Partial<
|
||||
ComponentProps<typeof CreativeAudioInputPanel<TestAudioAsset>>
|
||||
@@ -206,6 +219,7 @@ test('原生 App 宿主可用时上传按钮走 HostBridge 音频导入', async
|
||||
'',
|
||||
'/?clientRuntime=native_app&hostCapabilities=file.importAudio',
|
||||
);
|
||||
trustNativeHostRuntime(['file.importAudio']);
|
||||
window.__TAURI__ = {
|
||||
core: {
|
||||
invoke: async <Result,>(
|
||||
@@ -256,6 +270,7 @@ test('原生 App 宿主可用且当前音频为本地资产时可以导出音频
|
||||
'',
|
||||
'/?clientRuntime=native_app&hostCapabilities=file.exportAudio',
|
||||
);
|
||||
trustNativeHostRuntime(['file.exportAudio']);
|
||||
window.__TAURI__ = {
|
||||
core: {
|
||||
invoke: async <Result,>(
|
||||
@@ -298,6 +313,7 @@ test('非本地音频资产或未声明能力时不显示导出入口', () => {
|
||||
'',
|
||||
'/?clientRuntime=native_app&hostCapabilities=file.exportAudio',
|
||||
);
|
||||
trustNativeHostRuntime(['file.exportAudio']);
|
||||
rerender(
|
||||
<CreativeAudioInputPanel<TestAudioAsset>
|
||||
title="敲击音效"
|
||||
@@ -333,6 +349,7 @@ test('导出音频失败时提示错误', async () => {
|
||||
'',
|
||||
'/?clientRuntime=native_app&hostCapabilities=file.exportAudio',
|
||||
);
|
||||
trustNativeHostRuntime(['file.exportAudio']);
|
||||
window.__TAURI__ = {
|
||||
core: {
|
||||
invoke: async <Result,>(
|
||||
|
||||
@@ -11,6 +11,10 @@ import { afterEach, describe, expect, test, vi } from 'vitest';
|
||||
|
||||
import { HOST_BRIDGE_PUBLIC_WEB_ORIGIN } from '../../../packages/shared/src/contracts/hostBridge';
|
||||
import * as clipboardService from '../../services/clipboard';
|
||||
import {
|
||||
resetHostRuntimeCacheForTest,
|
||||
setHostRuntimeCacheForTest,
|
||||
} from '../../services/host-bridge/hostBridge';
|
||||
import { PublishShareModal } from './PublishShareModal';
|
||||
import {
|
||||
buildMiniProgramPublishSharePath,
|
||||
@@ -39,6 +43,7 @@ afterEach(() => {
|
||||
delete window.ReactNativeWebView;
|
||||
delete window.__TAURI__;
|
||||
window.wx = undefined;
|
||||
resetHostRuntimeCacheForTest();
|
||||
});
|
||||
|
||||
function asTauriInvoke(
|
||||
@@ -52,6 +57,19 @@ function asTauriInvoke(
|
||||
};
|
||||
}
|
||||
|
||||
function trustNativeHostRuntime(
|
||||
shell: 'expo_mobile' | 'tauri_desktop',
|
||||
capabilities: Parameters<typeof setHostRuntimeCacheForTest>[0]['capabilities'],
|
||||
) {
|
||||
setHostRuntimeCacheForTest({
|
||||
shell,
|
||||
platform: shell === 'expo_mobile' ? 'ios' : 'linux',
|
||||
hostVersion: '0.1.0',
|
||||
bridgeVersion: 1,
|
||||
capabilities,
|
||||
});
|
||||
}
|
||||
|
||||
describe('PublishShareModal', () => {
|
||||
test('builds the publish share text with title, code and public url', () => {
|
||||
const text = buildPublishShareText(payload);
|
||||
@@ -180,6 +198,7 @@ describe('PublishShareModal', () => {
|
||||
'',
|
||||
'/?clientRuntime=native_app&hostShell=tauri_desktop&hostCapabilities=share.open',
|
||||
);
|
||||
trustNativeHostRuntime('tauri_desktop', ['share.open']);
|
||||
window.__TAURI__ = {
|
||||
core: {
|
||||
invoke: asTauriInvoke(invoke),
|
||||
@@ -222,6 +241,7 @@ describe('PublishShareModal', () => {
|
||||
'',
|
||||
'/?clientRuntime=native_app&hostShell=expo_mobile&hostCapabilities=share.open',
|
||||
);
|
||||
trustNativeHostRuntime('expo_mobile', ['share.open']);
|
||||
const postMessage = vi.fn((rawMessage: string) => {
|
||||
const request = JSON.parse(rawMessage) as { id: string };
|
||||
window.dispatchEvent(
|
||||
@@ -328,6 +348,7 @@ describe('PublishShareModal', () => {
|
||||
'',
|
||||
'/?clientRuntime=native_app&hostShell=tauri_desktop&hostCapabilities=file.exportImage',
|
||||
);
|
||||
trustNativeHostRuntime('tauri_desktop', ['file.exportImage']);
|
||||
window.__TAURI__ = {
|
||||
core: {
|
||||
invoke: asTauriInvoke(invoke),
|
||||
|
||||
@@ -26,6 +26,7 @@ vi.mock('../../services/host-bridge/hostBridge', () => ({
|
||||
hostPlatform: null,
|
||||
hostVersion: null,
|
||||
hostCapabilities: [],
|
||||
nativeRuntimeTrusted: false,
|
||||
miniProgramEnv: null,
|
||||
})),
|
||||
openHostExternalUrl: vi.fn(async () => false),
|
||||
@@ -49,6 +50,7 @@ beforeEach(() => {
|
||||
hostPlatform: null,
|
||||
hostVersion: null,
|
||||
hostCapabilities: [],
|
||||
nativeRuntimeTrusted: false,
|
||||
miniProgramEnv: null,
|
||||
});
|
||||
vi.mocked(openHostExternalUrl).mockResolvedValue(false);
|
||||
@@ -132,6 +134,7 @@ describe('PlatformProfilePrimitives', () => {
|
||||
hostPlatform: 'ios',
|
||||
hostVersion: '0.1.0',
|
||||
hostCapabilities: ['app.openExternalUrl'],
|
||||
nativeRuntimeTrusted: true,
|
||||
miniProgramEnv: null,
|
||||
});
|
||||
vi.mocked(openHostExternalUrl).mockResolvedValue(true);
|
||||
|
||||
@@ -6,7 +6,10 @@ import type {
|
||||
HostBridgeCapability,
|
||||
HostBridgeRequest,
|
||||
} from '../../../packages/shared/src/contracts/hostBridge';
|
||||
import { resetHostRuntimeCacheForTest } from '../../services/host-bridge/hostBridge';
|
||||
import {
|
||||
resetHostRuntimeCacheForTest,
|
||||
setHostRuntimeCacheForTest,
|
||||
} from '../../services/host-bridge/hostBridge';
|
||||
import { resetNativeAppHostBridgeForTest } from '../../services/host-bridge/nativeAppHostBridge';
|
||||
import {
|
||||
sendPlatformHostDraftNotificationToHost,
|
||||
@@ -35,6 +38,16 @@ function nativeAppPath(capabilities: HostBridgeCapability[] = []) {
|
||||
return `/?${params.toString()}`;
|
||||
}
|
||||
|
||||
function trustNativeHostRuntime(capabilities: HostBridgeCapability[]) {
|
||||
setHostRuntimeCacheForTest({
|
||||
shell: 'tauri_desktop',
|
||||
platform: 'linux',
|
||||
hostVersion: '0.1.0',
|
||||
bridgeVersion: 1,
|
||||
capabilities,
|
||||
});
|
||||
}
|
||||
|
||||
function installTauriHostBridgeRecorder() {
|
||||
const requests: HostBridgeRequest[] = [];
|
||||
const invoke = vi.fn(async (_command: string, args?: Record<string, unknown>) => {
|
||||
@@ -73,6 +86,7 @@ describe('platformHostBridgeSync', () => {
|
||||
'',
|
||||
nativeAppPath(['notification.showLocal']),
|
||||
);
|
||||
trustNativeHostRuntime(['notification.showLocal']);
|
||||
const { invoke, requests } = installTauriHostBridgeRecorder();
|
||||
|
||||
await expect(
|
||||
@@ -100,6 +114,7 @@ describe('platformHostBridgeSync', () => {
|
||||
'',
|
||||
nativeAppPath(['app.setBadgeCount']),
|
||||
);
|
||||
trustNativeHostRuntime(['app.setBadgeCount']);
|
||||
const { invoke, requests } = installTauriHostBridgeRecorder();
|
||||
|
||||
await expect(syncPlatformHostDraftBadgeCountToHost(3)).resolves.toBe(true);
|
||||
|
||||
@@ -20,6 +20,7 @@ vi.mock('../../services/host-bridge/hostBridge', () => ({
|
||||
hostPlatform: null,
|
||||
hostVersion: null,
|
||||
hostCapabilities: [],
|
||||
nativeRuntimeTrusted: false,
|
||||
miniProgramEnv: null,
|
||||
})),
|
||||
openHostExternalUrl: vi.fn(async () => false),
|
||||
@@ -39,6 +40,7 @@ beforeEach(() => {
|
||||
hostPlatform: null,
|
||||
hostVersion: null,
|
||||
hostCapabilities: [],
|
||||
nativeRuntimeTrusted: false,
|
||||
miniProgramEnv: null,
|
||||
});
|
||||
vi.mocked(openHostExternalUrl).mockResolvedValue(false);
|
||||
@@ -217,6 +219,7 @@ test('RPG asset debug panel 在原生 App 中通过宿主打开原图外链', as
|
||||
hostPlatform: 'linux',
|
||||
hostVersion: '0.1.0',
|
||||
hostCapabilities: ['app.openExternalUrl'],
|
||||
nativeRuntimeTrusted: true,
|
||||
miniProgramEnv: null,
|
||||
});
|
||||
vi.mocked(openHostExternalUrl).mockResolvedValue(true);
|
||||
|
||||
@@ -98,7 +98,10 @@ import {
|
||||
regenerateBabyObjectMatchDraftAssets,
|
||||
saveBabyObjectMatchDraft,
|
||||
} from '../../services/edutainment-baby-object';
|
||||
import { resetHostRuntimeCacheForTest } from '../../services/host-bridge/hostBridge';
|
||||
import {
|
||||
resetHostRuntimeCacheForTest,
|
||||
setHostRuntimeCacheForTest,
|
||||
} from '../../services/host-bridge/hostBridge';
|
||||
import { resetNativeAppHostBridgeForTest } from '../../services/host-bridge/nativeAppHostBridge';
|
||||
import { jumpHopClient } from '../../services/jump-hop/jumpHopClient';
|
||||
import { match3dCreationClient } from '../../services/match3d-creation';
|
||||
@@ -315,11 +318,26 @@ function nativeAppPath(capabilities: HostBridgeCapability[] = []) {
|
||||
return `/?${params.toString()}`;
|
||||
}
|
||||
|
||||
function installTauriHostBridgeRecorder() {
|
||||
function installTauriHostBridgeRecorder(capabilities: HostBridgeCapability[] = []) {
|
||||
const requests: HostBridgeRequest[] = [];
|
||||
const invoke = vi.fn(async (_command: string, args?: Record<string, unknown>) => {
|
||||
const request = (args as { request: HostBridgeRequest }).request;
|
||||
requests.push(request);
|
||||
if (request.method === 'host.getRuntime') {
|
||||
return {
|
||||
bridge: 'GenarrativeHostBridge',
|
||||
version: 1,
|
||||
id: request.id,
|
||||
ok: true,
|
||||
result: {
|
||||
shell: 'tauri_desktop',
|
||||
platform: 'linux',
|
||||
hostVersion: '0.1.0',
|
||||
bridgeVersion: 1,
|
||||
capabilities: ['host.getRuntime', ...capabilities],
|
||||
},
|
||||
};
|
||||
}
|
||||
return {
|
||||
bridge: 'GenarrativeHostBridge',
|
||||
version: 1,
|
||||
@@ -9199,7 +9217,17 @@ test('native app jump hop draft completion sends host notification and badge fro
|
||||
resolveCompile = resolve;
|
||||
}),
|
||||
);
|
||||
const { requests } = installTauriHostBridgeRecorder();
|
||||
const { requests } = installTauriHostBridgeRecorder([
|
||||
'app.setBadgeCount',
|
||||
'notification.showLocal',
|
||||
]);
|
||||
setHostRuntimeCacheForTest({
|
||||
shell: 'tauri_desktop',
|
||||
platform: 'linux',
|
||||
hostVersion: '0.1.0',
|
||||
bridgeVersion: 1,
|
||||
capabilities: ['app.setBadgeCount', 'notification.showLocal'],
|
||||
});
|
||||
window.history.replaceState(
|
||||
null,
|
||||
'',
|
||||
@@ -9250,7 +9278,16 @@ test('native app jump hop draft completion sends host notification and badge fro
|
||||
|
||||
test('native app opens child motion demo through host navigation bridge', async () => {
|
||||
const user = userEvent.setup();
|
||||
const { requests } = installTauriHostBridgeRecorder();
|
||||
const { requests } = installTauriHostBridgeRecorder([
|
||||
'navigation.openNativePage',
|
||||
]);
|
||||
setHostRuntimeCacheForTest({
|
||||
shell: 'tauri_desktop',
|
||||
platform: 'linux',
|
||||
hostVersion: '0.1.0',
|
||||
bridgeVersion: 1,
|
||||
capabilities: ['navigation.openNativePage'],
|
||||
});
|
||||
window.history.replaceState(
|
||||
null,
|
||||
'',
|
||||
|
||||
Reference in New Issue
Block a user