Files
Genarrative/apps/mobile-shell/src/mobileShellNavigation.test.ts
T
kdletters a1afd33004 校验移动壳HostBridge消息来源
移动壳 onMessage 按页面 URL 拦截非同源 HostBridge 消息

移动壳导航测试覆盖同源消息入口和异常来源丢弃

移动壳检查脚本要求保留消息来源校验

宿主壳方案和共享决策记录移动消息入口边界
2026-06-18 09:23:53 +08:00

115 lines
3.6 KiB
TypeScript

import { describe, expect, test } from 'vitest';
import {
resolveMobileShellExternalUrl,
resolveMobileShellWebViewUrl,
shouldAcceptMobileShellHostBridgeMessage,
shouldOpenInMobileShellWebView,
} from './mobileShellNavigation';
describe('shouldOpenInMobileShellWebView', () => {
test('只允许主站同源页面留在移动壳 WebView 内', () => {
const allowedOrigin = 'https://app.genarrative.world';
expect(
shouldOpenInMobileShellWebView(
'https://app.genarrative.world/works/detail?work=PZ-1',
allowedOrigin,
),
).toBe(true);
expect(
shouldOpenInMobileShellWebView('/creation/puzzle', allowedOrigin),
).toBe(true);
expect(
shouldOpenInMobileShellWebView(
'http://app.genarrative.world/works/detail?work=PZ-1',
allowedOrigin,
),
).toBe(false);
expect(
shouldOpenInMobileShellWebView('about:blank', allowedOrigin),
).toBe(true);
});
test('外链和非网页协议必须离开带 HostBridge 的 WebView', () => {
const allowedOrigin = 'https://app.genarrative.world';
expect(
shouldOpenInMobileShellWebView('https://example.com/', allowedOrigin),
).toBe(false);
expect(
shouldOpenInMobileShellWebView('mailto:hi@example.com', allowedOrigin),
).toBe(false);
expect(
shouldOpenInMobileShellWebView('//example.com/evil', allowedOrigin),
).toBe(false);
expect(
shouldOpenInMobileShellWebView('javascript:alert(1)', allowedOrigin),
).toBe(false);
expect(shouldOpenInMobileShellWebView('not a url', allowedOrigin)).toBe(
false,
);
});
test('只有允许协议能交给系统外部应用打开', () => {
expect(resolveMobileShellExternalUrl(' https://example.com/path ')).toBe(
'https://example.com/path',
);
expect(resolveMobileShellExternalUrl('mailto:hi@example.com')).toBe(
'mailto:hi@example.com',
);
expect(resolveMobileShellExternalUrl('tel:+12345678')).toBe(
'tel:+12345678',
);
expect(resolveMobileShellExternalUrl('javascript:alert(1)')).toBeNull();
expect(resolveMobileShellExternalUrl('file:///etc/passwd')).toBeNull();
expect(resolveMobileShellExternalUrl('/relative/path')).toBeNull();
});
test('HostBridge 主动导航只解析同源网页目标', () => {
const allowedOrigin = 'https://app.genarrative.world';
expect(
resolveMobileShellWebViewUrl('/works/detail?work=PZ-1', allowedOrigin),
).toBe('https://app.genarrative.world/works/detail?work=PZ-1');
expect(
resolveMobileShellWebViewUrl(
'https://app.genarrative.world/creation/puzzle#draft',
allowedOrigin,
),
).toBe('https://app.genarrative.world/creation/puzzle#draft');
expect(
resolveMobileShellWebViewUrl('https://example.com/', allowedOrigin),
).toBeNull();
expect(
resolveMobileShellWebViewUrl('about:blank', allowedOrigin),
).toBeNull();
});
test('HostBridge 消息只接受同源主站页面', () => {
const allowedOrigin = 'https://app.genarrative.world';
expect(
shouldAcceptMobileShellHostBridgeMessage(
'https://app.genarrative.world/creation/puzzle',
allowedOrigin,
),
).toBe(true);
expect(
shouldAcceptMobileShellHostBridgeMessage('about:blank', allowedOrigin),
).toBe(false);
expect(
shouldAcceptMobileShellHostBridgeMessage(
'https://example.com/evil',
allowedOrigin,
),
).toBe(false);
expect(
shouldAcceptMobileShellHostBridgeMessage(
'javascript:alert(1)',
allowedOrigin,
),
).toBe(false);
});
});