a1afd33004
移动壳 onMessage 按页面 URL 拦截非同源 HostBridge 消息 移动壳导航测试覆盖同源消息入口和异常来源丢弃 移动壳检查脚本要求保留消息来源校验 宿主壳方案和共享决策记录移动消息入口边界
115 lines
3.6 KiB
TypeScript
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);
|
|
});
|
|
});
|