收口原生壳公开主站来源
新增共享 HostBridge 公开主站 origin 和默认 URL 常量 移动壳启动 URL、分享和 WebView 策略改为引用共享主站来源 桌面壳配置检查反查 Rust WEB_APP_ORIGIN 与共享契约一致 同步原生壳方案和共享决策记录
This commit is contained in:
@@ -53,6 +53,11 @@ const desktopHostBridgeDispatchSource = fs.readFileSync(
|
||||
desktopHostBridgeDispatchPath,
|
||||
'utf8',
|
||||
);
|
||||
const desktopShellUrlPath = new URL(
|
||||
'../src-tauri/src/shell/url.rs',
|
||||
import.meta.url,
|
||||
);
|
||||
const desktopShellUrlSource = fs.readFileSync(desktopShellUrlPath, 'utf8');
|
||||
const productionSourceRoots = [
|
||||
new URL('../package.json', import.meta.url),
|
||||
new URL('../src-tauri/Cargo.toml', import.meta.url),
|
||||
@@ -932,6 +937,10 @@ const sharedHostBridgeVersion = extractTsNumberConst(
|
||||
sharedContractSource,
|
||||
'HOST_BRIDGE_VERSION',
|
||||
);
|
||||
const sharedPublicWebOrigin = extractTsStringConst(
|
||||
sharedContractSource,
|
||||
'HOST_BRIDGE_PUBLIC_WEB_ORIGIN',
|
||||
);
|
||||
const sharedHostBridgePayloadLimits = {
|
||||
HOST_BRIDGE_REQUEST_ID_MAX_LENGTH: extractTsNumberConst(
|
||||
sharedContractSource,
|
||||
@@ -1041,6 +1050,10 @@ const desktopHostBridgeVersion = extractRustNumberConst(
|
||||
rustHostSource,
|
||||
'HOST_BRIDGE_VERSION',
|
||||
);
|
||||
const desktopPublicWebOrigin = extractRustStringConst(
|
||||
desktopShellUrlSource,
|
||||
'WEB_APP_ORIGIN',
|
||||
);
|
||||
const desktopCapabilities = extractDesktopCapabilities(
|
||||
desktopHostBridgeCapabilitiesSource,
|
||||
);
|
||||
@@ -1060,6 +1073,12 @@ if (desktopHostBridgeVersion !== sharedHostBridgeVersion) {
|
||||
);
|
||||
}
|
||||
|
||||
if (desktopPublicWebOrigin !== sharedPublicWebOrigin) {
|
||||
throw new Error(
|
||||
`desktop shell public web origin drifted: expected ${sharedPublicWebOrigin} but got ${desktopPublicWebOrigin}`,
|
||||
);
|
||||
}
|
||||
|
||||
for (const [limitName, sharedLimit] of Object.entries(
|
||||
sharedHostBridgePayloadLimits,
|
||||
)) {
|
||||
|
||||
@@ -489,6 +489,14 @@ const sharedHostBridgeVersion = extractNumberConstExport(
|
||||
sharedContractSource,
|
||||
'HOST_BRIDGE_VERSION',
|
||||
);
|
||||
const sharedPublicWebOrigin = extractStringConstExport(
|
||||
sharedContractSource,
|
||||
'HOST_BRIDGE_PUBLIC_WEB_ORIGIN',
|
||||
);
|
||||
const sharedPublicWebUrl = extractStringConstExport(
|
||||
sharedContractSource,
|
||||
'HOST_BRIDGE_PUBLIC_WEB_URL',
|
||||
);
|
||||
const handledMobileMethods = extractMobileBridgeHandledMethods(dispatchSource);
|
||||
const mobileCapabilities = extractStringArrayExport(
|
||||
hostBridgeSource,
|
||||
@@ -926,16 +934,11 @@ if (
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
!urlSource.includes(
|
||||
"DEFAULT_MOBILE_SHELL_WEB_URL = 'https://app.genarrative.world/'",
|
||||
)
|
||||
) {
|
||||
throw new Error('mobile shell default H5 URL must point to the production web app');
|
||||
}
|
||||
|
||||
for (const snippet of [
|
||||
"ALLOWED_PRODUCTION_WEB_ORIGIN = 'https://app.genarrative.world'",
|
||||
'HOST_BRIDGE_PUBLIC_WEB_ORIGIN',
|
||||
'HOST_BRIDGE_PUBLIC_WEB_URL',
|
||||
'DEFAULT_MOBILE_SHELL_WEB_URL = HOST_BRIDGE_PUBLIC_WEB_URL',
|
||||
'ALLOWED_PRODUCTION_WEB_ORIGIN = HOST_BRIDGE_PUBLIC_WEB_ORIGIN',
|
||||
'LOCAL_DEVELOPMENT_WEB_HOSTS',
|
||||
"'127.0.0.1'",
|
||||
"'localhost'",
|
||||
@@ -947,6 +950,23 @@ for (const snippet of [
|
||||
}
|
||||
}
|
||||
|
||||
for (const localWebOriginDuplicate of [
|
||||
`DEFAULT_MOBILE_SHELL_WEB_URL = '${sharedPublicWebUrl}'`,
|
||||
`ALLOWED_PRODUCTION_WEB_ORIGIN = '${sharedPublicWebOrigin}'`,
|
||||
]) {
|
||||
if (urlSource.includes(localWebOriginDuplicate)) {
|
||||
throw new Error('mobile shell public web origin must come from shared HostBridge contract');
|
||||
}
|
||||
}
|
||||
|
||||
if (!webViewPolicySource.includes('DEFAULT_MOBILE_SHELL_WEB_URL')) {
|
||||
throw new Error('mobile shell WebView policy must use the shared default H5 URL');
|
||||
}
|
||||
|
||||
if (webViewPolicySource.includes(`'${sharedPublicWebUrl}'`)) {
|
||||
throw new Error('mobile shell WebView policy must not duplicate the public web URL');
|
||||
}
|
||||
|
||||
for (const snippet of [
|
||||
'ALLOWED_PRODUCTION_WEB_ORIGIN',
|
||||
'normalizePublicShareUrl',
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { describe, expect, test } from 'vitest';
|
||||
|
||||
import { HOST_BRIDGE_VERSION } from '../../../../packages/shared/src/contracts/hostBridge';
|
||||
import {
|
||||
HOST_BRIDGE_PUBLIC_WEB_ORIGIN,
|
||||
HOST_BRIDGE_PUBLIC_WEB_URL,
|
||||
HOST_BRIDGE_VERSION,
|
||||
} from '../../../../packages/shared/src/contracts/hostBridge';
|
||||
import {
|
||||
ALLOWED_PRODUCTION_WEB_ORIGIN,
|
||||
DEFAULT_MOBILE_SHELL_WEB_URL,
|
||||
@@ -10,8 +14,8 @@ import {
|
||||
|
||||
describe('buildMobileShellUrl', () => {
|
||||
test('默认 H5 地址指向真实主站', () => {
|
||||
expect(DEFAULT_MOBILE_SHELL_WEB_URL).toBe('https://app.genarrative.world/');
|
||||
expect(ALLOWED_PRODUCTION_WEB_ORIGIN).toBe('https://app.genarrative.world');
|
||||
expect(DEFAULT_MOBILE_SHELL_WEB_URL).toBe(HOST_BRIDGE_PUBLIC_WEB_URL);
|
||||
expect(ALLOWED_PRODUCTION_WEB_ORIGIN).toBe(HOST_BRIDGE_PUBLIC_WEB_ORIGIN);
|
||||
});
|
||||
|
||||
test('为 H5 附加原生移动壳上下文', () => {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import {
|
||||
HOST_BRIDGE_PUBLIC_WEB_ORIGIN,
|
||||
HOST_BRIDGE_PUBLIC_WEB_URL,
|
||||
HOST_BRIDGE_VERSION,
|
||||
type HostBridgeCapability,
|
||||
type NativeHostPlatform,
|
||||
@@ -10,8 +12,8 @@ export type MobileShellUrlOptions = {
|
||||
capabilities: HostBridgeCapability[];
|
||||
};
|
||||
|
||||
export const DEFAULT_MOBILE_SHELL_WEB_URL = 'https://app.genarrative.world/';
|
||||
export const ALLOWED_PRODUCTION_WEB_ORIGIN = 'https://app.genarrative.world';
|
||||
export const DEFAULT_MOBILE_SHELL_WEB_URL = HOST_BRIDGE_PUBLIC_WEB_URL;
|
||||
export const ALLOWED_PRODUCTION_WEB_ORIGIN = HOST_BRIDGE_PUBLIC_WEB_ORIGIN;
|
||||
const LOCAL_DEVELOPMENT_WEB_HOSTS = new Set([
|
||||
'127.0.0.1',
|
||||
'localhost',
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { DEFAULT_MOBILE_SHELL_WEB_URL } from './url';
|
||||
|
||||
const MOBILE_WEBVIEW_BLOCKED_DOWNLOAD_PROTOCOLS = new Set([
|
||||
'blob:',
|
||||
'data:',
|
||||
@@ -18,7 +20,7 @@ export function shouldBlockMobileWebViewDownloadUrl(
|
||||
|
||||
try {
|
||||
return MOBILE_WEBVIEW_BLOCKED_DOWNLOAD_PROTOCOLS.has(
|
||||
new URL(rawUrl, 'https://app.genarrative.world/').protocol,
|
||||
new URL(rawUrl, DEFAULT_MOBILE_SHELL_WEB_URL).protocol,
|
||||
);
|
||||
} catch {
|
||||
return false;
|
||||
|
||||
@@ -55,6 +55,7 @@
|
||||
- 2026-06-18 移动壳启动 URL 归一:Expo 壳的 `EXPO_PUBLIC_GENARRATIVE_WEB_URL` 和 deep link 基准地址只接受生产主站 `https://app.genarrative.world`,以及本机开发联调 `http://127.0.0.1`、`http://localhost`、`http://[::1]`;空值、相对路径、外域、`file:`、`javascript:` 等非法配置回退到默认 H5 地址后再附加 `native_app` 宿主上下文;deep link 仍只映射归一后基准 origin 的 H5 路径,禁止把外域或危险协议页面装进带完整 HostBridge 的 WebView。
|
||||
- 2026-06-18 移动壳主动导航上下文:Expo 壳的 `navigation.openNativePage` 与 deep link 都必须复用 `buildMobileShellUrl(...)` 补写 `native_app`、`expo_mobile`、真实平台、版本和 capability 清单;受控导航只接受当前允许 origin 的同源 H5 URL。移动壳配置检查会拒绝主动导航或 deep link 绕过该宿主上下文构造入口。
|
||||
- 2026-06-18 移动壳协议常量来源:Expo 壳的 HostBridge 事件注入、入口 URL `bridgeVersion`、`host.getRuntime` 回包和 Expo public config smoke 必须使用 `packages/shared/src/contracts/hostBridge.ts` 的 `HOST_BRIDGE_PROTOCOL` / `HOST_BRIDGE_VERSION`,不得在壳层重新写死协议名或版本字面量;配置检查会拒绝这些常量漂移。
|
||||
- 2026-06-19 公开 Web origin 单一来源:原生壳允许加载 / 分享 / 跳转的公开 H5 主站 origin 以 `packages/shared/src/contracts/hostBridge.ts` 的 `HOST_BRIDGE_PUBLIC_WEB_ORIGIN` / `HOST_BRIDGE_PUBLIC_WEB_URL` 为源;Expo 移动壳只能通过 `DEFAULT_MOBILE_SHELL_WEB_URL` / `ALLOWED_PRODUCTION_WEB_ORIGIN` 语义别名引用共享常量,Tauri 桌面壳 `WEB_APP_ORIGIN` 作为 Rust 运行时镜像常量必须由 `apps/desktop-shell/scripts/check-config.mjs` 反查同一共享值。两端不得在分享、WebView policy、启动 URL 或桌面导航里另行复刻 `https://app.genarrative.world` 作为独立真相。
|
||||
- 2026-06-18 桌面壳协议常量来源:Tauri Rust 侧 `host_bridge/protocol.rs` 的 `HOST_BRIDGE_PROTOCOL` / `HOST_BRIDGE_VERSION`、桌面入口 URL `bridgeVersion`、HostBridge event 注入和 runtime 回包必须与 `packages/shared/src/contracts/hostBridge.ts` 保持一致;桌面配置检查会反查共享契约并拒绝协议名或协议版本漂移。`tauri.conf.json` 只保留基础入口,`shell/url.rs` 统一补写桌面宿主上下文和真实 capability 清单,配置检查会拒绝把 `hostCapabilities` 等宿主 query 长串重新写回 Tauri 配置。
|
||||
- 2026-06-18 移动壳默认入口:Expo 壳默认 H5 地址固定为 `https://app.genarrative.world/`,开发联调本机 Vite 必须显式设置 `EXPO_PUBLIC_GENARRATIVE_WEB_URL=http://127.0.0.1:3000/`、`http://localhost:3000/` 或 `http://[::1]:3000/`;生产包不得在未配置环境变量时加载设备本机 localhost,也不得通过环境变量把第三方外域 H5 放入带完整 HostBridge 的 WebView。
|
||||
- 2026-06-18 移动壳安装包身份:Expo 移动壳的 iOS bundle identifier 与 Android package 统一固定为 `world.genarrative.mobile`,应用版本固定为 `0.1.0`,iOS `buildNumber` 从字符串 `"1"` 起步,Android `versionCode` 从整数 `1` 起步;后续分发安装包时递增构建号 / versionCode,产品版本号按发布节奏调整。移动壳配置检查会校验 `app.json` 与 `package.json` 版本一致,并拒绝缺失或漂移的包标识,当前不写入假商店元数据、假更新端点或占位渠道 SDK 配置。
|
||||
|
||||
@@ -326,6 +326,8 @@ GameBridge 禁止:
|
||||
|
||||
2026-06-18 追加:移动壳 HostBridge 协议名和协议版本统一从 `packages/shared/src/contracts/hostBridge.ts` 的 `HOST_BRIDGE_PROTOCOL` / `HOST_BRIDGE_VERSION` 读取。Expo 入口 query、WebView 事件注入、`host.getRuntime` 回包和 Expo public config smoke 都必须反查共享常量;移动壳配置检查会拒绝重新写死 `GenarrativeHostBridge` 或字面量版本。
|
||||
|
||||
2026-06-19 追加:公开 H5 主站 origin / 默认 URL 统一以 `packages/shared/src/contracts/hostBridge.ts` 的 `HOST_BRIDGE_PUBLIC_WEB_ORIGIN` / `HOST_BRIDGE_PUBLIC_WEB_URL` 为源。移动壳 `DEFAULT_MOBILE_SHELL_WEB_URL` 与 `ALLOWED_PRODUCTION_WEB_ORIGIN` 只是壳层语义别名,分享 URL 归一、WebView 下载协议判定和启动 URL 回退都不得重新写死 `https://app.genarrative.world`;桌面 Rust 侧 `WEB_APP_ORIGIN` 保留为运行时镜像常量,但 `apps/desktop-shell/scripts/check-config.mjs` 必须反查共享 origin。两端配置检查会拒绝本地复刻或漂移同值 origin。
|
||||
|
||||
2026-06-18 追加:桌面壳 HostBridge 协议名和协议版本也必须反查同一共享契约。Tauri Rust 侧仍保留 `host_bridge/protocol.rs` 常量作为运行时代码入口,但 `apps/desktop-shell/scripts/check-config.mjs` 会把 Rust `HOST_BRIDGE_PROTOCOL` / `HOST_BRIDGE_VERSION` 与 `packages/shared/src/contracts/hostBridge.ts` 对齐,避免桌面壳事件注入、runtime 回包和 H5 transport 分叉。
|
||||
|
||||
2026-06-18 追加:移动壳默认 H5 地址固定为 `https://app.genarrative.world/`。开发联调如需加载本机 Vite,必须显式设置 `EXPO_PUBLIC_GENARRATIVE_WEB_URL=http://127.0.0.1:3000/`、`http://localhost:3000/` 或 `http://[::1]:3000/`;生产包不得在未配置环境变量时默认加载设备本机 localhost,也不得通过环境变量把第三方外域 H5 放入带完整 HostBridge 的 WebView。
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { describe, expect, test } from 'vitest';
|
||||
|
||||
import {
|
||||
HOST_BRIDGE_PUBLIC_WEB_ORIGIN,
|
||||
HOST_BRIDGE_PUBLIC_WEB_URL,
|
||||
HOST_BRIDGE_TAURI_COMMAND,
|
||||
isHostBridgeMethod,
|
||||
isHostBridgeCapability,
|
||||
@@ -17,6 +19,11 @@ import {
|
||||
} from './hostBridge';
|
||||
|
||||
describe('HostBridge shared contract helpers', () => {
|
||||
test('固定公开 H5 主站入口', () => {
|
||||
expect(HOST_BRIDGE_PUBLIC_WEB_ORIGIN).toBe('https://app.genarrative.world');
|
||||
expect(HOST_BRIDGE_PUBLIC_WEB_URL).toBe('https://app.genarrative.world/');
|
||||
});
|
||||
|
||||
test('固定 Tauri 只暴露唯一 HostBridge command', () => {
|
||||
expect(HOST_BRIDGE_TAURI_COMMAND).toBe('host_bridge_request');
|
||||
});
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
export const HOST_BRIDGE_PROTOCOL = 'GenarrativeHostBridge';
|
||||
export const HOST_BRIDGE_VERSION = 1;
|
||||
export const HOST_BRIDGE_TAURI_COMMAND = 'host_bridge_request';
|
||||
export const HOST_BRIDGE_PUBLIC_WEB_ORIGIN = 'https://app.genarrative.world';
|
||||
export const HOST_BRIDGE_PUBLIC_WEB_URL = 'https://app.genarrative.world/';
|
||||
|
||||
export type HostShellKind = 'browser' | 'wechat_mini_program' | 'native_app';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user