统一移动壳宿主版本来源
移动壳入口 query 和 runtime 回包共用宿主版本常量 移动壳配置检查拒绝 HostBridge 版本散落硬编码 宿主壳方案和共享决策记录版本单一来源约束
This commit is contained in:
@@ -25,12 +25,12 @@ import {
|
||||
shouldOpenInMobileShellWebView,
|
||||
} from './src/mobileShellNavigation';
|
||||
import { subscribeMobileNetworkStatus } from './src/mobileShellNetwork';
|
||||
import { MOBILE_SHELL_HOST_VERSION } from './src/mobileShellRuntime';
|
||||
import { MOBILE_SHELL_SAFE_AREA_EDGES } from './src/mobileShellSafeArea';
|
||||
import {
|
||||
buildMobileShellUrl,
|
||||
resolveMobileShellBaseWebUrl,
|
||||
} from './src/mobileShellUrl';
|
||||
const hostVersion = '0.1.0';
|
||||
|
||||
function buildHostBridgeMessageScript(message: unknown) {
|
||||
return `window.dispatchEvent(new MessageEvent('message', { data: ${JSON.stringify(
|
||||
@@ -47,7 +47,7 @@ export default function App() {
|
||||
const mobileShellUrlOptions = useMemo(
|
||||
() => ({
|
||||
platform: Platform.OS === 'ios' ? 'ios' as const : 'android' as const,
|
||||
hostVersion,
|
||||
hostVersion: MOBILE_SHELL_HOST_VERSION,
|
||||
capabilities: resolveMobileHostCapabilities(),
|
||||
}),
|
||||
[],
|
||||
|
||||
@@ -10,6 +10,8 @@ const bridgePath = new URL('../src/mobileHostBridge.ts', import.meta.url);
|
||||
const bridgeSource = fs.readFileSync(bridgePath, 'utf8');
|
||||
const mobileShellUrlPath = new URL('../src/mobileShellUrl.ts', import.meta.url);
|
||||
const mobileShellUrlSource = fs.readFileSync(mobileShellUrlPath, 'utf8');
|
||||
const mobileShellRuntimePath = new URL('../src/mobileShellRuntime.ts', import.meta.url);
|
||||
const mobileShellRuntimeSource = fs.readFileSync(mobileShellRuntimePath, 'utf8');
|
||||
const sharedContractPath = new URL(
|
||||
'../../../packages/shared/src/contracts/hostBridge.ts',
|
||||
import.meta.url,
|
||||
@@ -66,6 +68,17 @@ function extractStringArrayExport(source, exportName) {
|
||||
return entries;
|
||||
}
|
||||
|
||||
function extractStringConstExport(source, exportName) {
|
||||
const match = source.match(
|
||||
new RegExp(`export const ${exportName}\\s*=\\s*'([^']+)';`),
|
||||
);
|
||||
if (!match) {
|
||||
throw new Error(`unable to read ${exportName}`);
|
||||
}
|
||||
|
||||
return match[1];
|
||||
}
|
||||
|
||||
function collectProductionSourceFiles(entry) {
|
||||
const stats = fs.statSync(entry);
|
||||
if (stats.isDirectory()) {
|
||||
@@ -202,6 +215,14 @@ if (appConfig.version !== '0.1.0') {
|
||||
throw new Error('mobile shell app version must be 0.1.0');
|
||||
}
|
||||
|
||||
const mobileShellHostVersion = extractStringConstExport(
|
||||
mobileShellRuntimeSource,
|
||||
'MOBILE_SHELL_HOST_VERSION',
|
||||
);
|
||||
if (mobileShellHostVersion !== appConfig.version) {
|
||||
throw new Error('mobile shell HostBridge host version must match app.json version');
|
||||
}
|
||||
|
||||
if (appConfig.orientation !== 'default') {
|
||||
throw new Error('mobile shell must allow device orientation for landscape-capable H5 games');
|
||||
}
|
||||
@@ -459,6 +480,18 @@ if (!appSource.includes('capabilities: resolveMobileHostCapabilities()')) {
|
||||
throw new Error('mobile shell URL must use resolveMobileHostCapabilities()');
|
||||
}
|
||||
|
||||
if (!appSource.includes('hostVersion: MOBILE_SHELL_HOST_VERSION')) {
|
||||
throw new Error('mobile shell URL must use the shared mobile shell host version');
|
||||
}
|
||||
|
||||
if (!bridgeSource.includes('hostVersion: MOBILE_SHELL_HOST_VERSION')) {
|
||||
throw new Error('mobile shell runtime response must use the shared mobile shell host version');
|
||||
}
|
||||
|
||||
if (appSource.includes("hostVersion: '0.1.0'") || bridgeSource.includes("hostVersion: '0.1.0'")) {
|
||||
throw new Error('mobile shell HostBridge version must not be duplicated in app or bridge source');
|
||||
}
|
||||
|
||||
for (const capability of sdkBackedCapabilities) {
|
||||
if (appSource.includes(`'${capability}'`) || appSource.includes(`"${capability}"`)) {
|
||||
throw new Error(
|
||||
|
||||
@@ -49,6 +49,7 @@ import {
|
||||
} from '../../../packages/shared/src/contracts/hostBridge';
|
||||
import { resolveMobileShellWebViewUrl } from './mobileShellNavigation';
|
||||
import { getMobileNetworkStatus } from './mobileShellNetwork';
|
||||
import { MOBILE_SHELL_HOST_VERSION } from './mobileShellRuntime';
|
||||
|
||||
const WEB_APP_ORIGIN = 'https://app.genarrative.world';
|
||||
const EXPORT_TEXT_MAX_BYTES = 5 * 1024 * 1024;
|
||||
@@ -913,7 +914,7 @@ async function handleRequest(request: HostBridgeRequest) {
|
||||
return ok(request, {
|
||||
shell: 'expo_mobile',
|
||||
platform: Platform.OS === 'ios' ? 'ios' : 'android',
|
||||
hostVersion: '0.1.0',
|
||||
hostVersion: MOBILE_SHELL_HOST_VERSION,
|
||||
bridgeVersion: HOST_BRIDGE_VERSION,
|
||||
capabilities: resolveMobileHostCapabilities(),
|
||||
});
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export const MOBILE_SHELL_HOST_VERSION = '0.1.0';
|
||||
@@ -46,6 +46,7 @@
|
||||
- 2026-06-18 移动壳启动 URL 归一:Expo 壳的 `EXPO_PUBLIC_GENARRATIVE_WEB_URL` 和 deep link 基准地址只接受 `http:` / `https:` 绝对 URL,空值、相对路径、`file:`、`javascript:` 等非法配置回退到默认 H5 地址后再附加 `native_app` 宿主上下文;deep link 仍只映射同源 H5 路径,禁止把外域或危险协议页面装进带完整 HostBridge 的 WebView。
|
||||
- 2026-06-18 移动壳默认入口:Expo 壳默认 H5 地址固定为 `https://app.genarrative.world/`,开发联调本机 Vite 必须显式设置 `EXPO_PUBLIC_GENARRATIVE_WEB_URL=http://127.0.0.1:3000/` 或其它允许的 `http:` / `https:` 地址;生产包不得在未配置环境变量时加载设备本机 localhost。
|
||||
- 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 配置。
|
||||
- 2026-06-18 移动壳 HostBridge 版本单一来源:Expo 移动壳的 H5 入口 query 和 `host.getRuntime` 回包都读取 `MOBILE_SHELL_HOST_VERSION`,该常量必须与 `app.json` / `package.json` 版本一致;配置检查会拒绝 `App.tsx` 或 `mobileHostBridge.ts` 重新散落硬编码版本,避免安装包版本升级时 H5 首屏上下文与 runtime 回读分叉。
|
||||
- 2026-06-18 桌面图片拖入接入主图槽位:`CreativeImageInputPanel` 在桌面壳声明 `file.imageDropped` 时订阅宿主拖入事件,只在拖入坐标命中当前主图卡片且未被上层元素遮挡时消费事件,避免窗口级拖入被多个创作面板同时接收;成功后仍转换为现有 `File` 上传回调。
|
||||
- 2026-06-18 H5 背景音乐接入宿主生命周期:`useBackgroundMusic` 通过 `useHostLifecycleActive()` 消费 `subscribeHostAppLifecycle()` 的归一结果,宿主进入后台、inactive 或桌面窗口失焦时降低音量并暂停音频循环,同时 `suspend` WebAudio context;回到 `active + focused` 且用户原本开启音乐时再恢复播放,不改变用户音量设置。
|
||||
- 2026-06-18 固定玩法音频接入宿主生命周期:前端新增 `useHostLifecycleActive()` 统一消费 `subscribeHostAppLifecycle()`,`useBackgroundMusic`、拼图运行态和抓大鹅运行态都只依赖该归一状态判断音频可播放性;宿主 inactive、background 或窗口失焦时暂停 `<audio>` / WebAudio,回到 `active + focused` 后仅在运行态仍在播放、音源存在且用户音乐音量大于 0 时恢复,不改变用户音量设置。
|
||||
|
||||
@@ -292,6 +292,8 @@ GameBridge 禁止:
|
||||
|
||||
2026-06-18 追加:移动壳安装包身份固定为 `world.genarrative.mobile`。Expo `app.json` 中的 `ios.bundleIdentifier` 与 `android.package` 使用同一包标识,应用版本为 `0.1.0`,iOS `buildNumber` 从字符串 `"1"` 起步,Android `versionCode` 从整数 `1` 起步;后续每次生成可分发安装包时只递增构建号 / versionCode,产品版本号按发布节奏单独调整。`apps/mobile-shell/scripts/check-config.mjs` 会校验这些字段与 `package.json` 版本一致,避免 iOS、Android 和 H5 HostBridge `hostVersion` 发生静默漂移;`npm run mobile-shell:config` 会调用真实 Expo CLI 解析 public managed config,确认最终 Expo 配置仍保留同一包身份、深链、安全字段、插件权限和 HostBridge 版本。当前仍不写入假商店上架信息、假更新端点或占位渠道 SDK 配置。
|
||||
|
||||
2026-06-18 追加:移动壳 H5 入口 query 和 `host.getRuntime` 回包统一读取 `MOBILE_SHELL_HOST_VERSION`,该常量必须与 Expo `app.json` / `package.json` 版本一致。配置检查会拒绝在 `App.tsx` 或 `mobileHostBridge.ts` 内重新散落硬编码版本,避免升级移动安装包时 H5 首屏上下文和宿主 runtime 回读版本不一致。
|
||||
|
||||
### Phase 3:Tauri 桌面壳 MVP
|
||||
|
||||
- 新增 `apps/desktop-shell/`。
|
||||
|
||||
Reference in New Issue
Block a user