收紧移动壳观测渠道初始化边界

移动壳配置检查拒绝观测、渠道、更新 SDK 相关依赖。

移动壳配置检查拒绝生产入口和 HostBridge 提前初始化观测、渠道、更新 SDK。

更新 Expo React Native 与 Tauri 宿主壳方案中的 Phase 4 能力边界。

更新共享决策日志记录移动壳观测与渠道 SDK 初始化前置条件。
This commit is contained in:
2026-06-18 11:39:05 +08:00
parent 242f872ac0
commit 25b140d63e
3 changed files with 75 additions and 18 deletions
+72 -16
View File
@@ -40,6 +40,37 @@ const devScaffoldTerms = [
'模' + '拟',
'伪' + '造',
];
const blockedMobileChannelDependencies = [
'@react-native-firebase/analytics',
'@react-native-firebase/app',
'@segment/analytics-react-native',
'@sentry/react-native',
'amplitude-react-native',
'expo-application',
'expo-updates',
'posthog-react-native',
'react-native-code-push',
];
const blockedMobileChannelSnippets = [
'@react-native-firebase/analytics',
'@react-native-firebase/app',
'@segment/analytics-react-native',
'@sentry/react-native',
'Amplitude.getInstance',
'Analytics.screen',
'Analytics.track',
'CodePush.sync',
'PostHogProvider',
'Sentry.init',
'Updates.checkForUpdateAsync',
'Updates.fetchUpdateAsync',
'Updates.reloadAsync',
'analytics().logEvent',
'amplitude.init',
'codePush(',
'posthog.capture',
'posthog.init',
];
const blockedAndroidPermissions = [
'android.permission.RECORD_AUDIO',
'android.permission.RECEIVE_BOOT_COMPLETED',
@@ -79,6 +110,45 @@ function extractStringConstExport(source, exportName) {
return match[1];
}
function assertNoBlockedMobileChannelDependencies() {
const dependencySections = [
'dependencies',
'devDependencies',
'optionalDependencies',
'peerDependencies',
];
for (const dependency of blockedMobileChannelDependencies) {
for (const section of dependencySections) {
if (packageConfig[section]?.[dependency]) {
throw new Error(
`mobile shell must not depend on ${dependency} before the real channel contract exists`,
);
}
}
}
}
function assertNoBlockedMobileChannelSnippets() {
const sources = [
['app.json', JSON.stringify(appConfig)],
['App.tsx', appSource],
['mobileHostBridge.ts', bridgeSource],
['mobileShellUrl.ts', mobileShellUrlSource],
['mobileShellRuntime.ts', mobileShellRuntimeSource],
];
for (const [sourceName, source] of sources) {
for (const snippet of blockedMobileChannelSnippets) {
if (source.includes(snippet)) {
throw new Error(
`mobile shell ${sourceName} must not initialize ${snippet} before the real channel contract exists`,
);
}
}
}
}
function collectProductionSourceFiles(entry) {
const stats = fs.statSync(entry);
if (stats.isDirectory()) {
@@ -148,6 +218,8 @@ function countAlphaPixels(png) {
assertNoDevScaffoldTerms(
productionSourceRoots.flatMap((root) => collectProductionSourceFiles(root)),
);
assertNoBlockedMobileChannelDependencies();
assertNoBlockedMobileChannelSnippets();
const sharedCapabilities = extractStringArrayExport(
sharedContractSource,
@@ -405,22 +477,6 @@ for (const dependency of [
}
}
for (const dependency of [
'expo-application',
'expo-updates',
'@sentry/react-native',
'@react-native-firebase/app',
'@react-native-firebase/analytics',
'react-native-code-push',
'posthog-react-native',
'amplitude-react-native',
'@segment/analytics-react-native',
]) {
if (packageConfig.dependencies?.[dependency] || packageConfig.devDependencies?.[dependency]) {
throw new Error(`mobile shell must not depend on ${dependency} before the real channel contract exists`);
}
}
const imagePickerPlugin = appConfig.plugins?.find((plugin) =>
Array.isArray(plugin) ? plugin[0] === 'expo-image-picker' : plugin === 'expo-image-picker',
);
@@ -48,6 +48,7 @@
- 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 移动壳发布通道边界:Expo 移动壳默认显式关闭 OTA 更新,只允许 `updates.enabled=false`;在真实发布通道、更新端点、签名 / 回滚策略和团队发布流程落地前,不得配置 `runtimeVersion`、release channel、EAS channel、`expo-updates` 插件或移动端 crash / analytics / CodePush 依赖。移动壳配置检查和 Expo public config smoke 会拒绝这些发布通道能力被提前打开。
- 2026-06-18 移动壳观测与渠道 SDK 初始化边界:移动壳生产入口、HostBridge、启动 URL 和 runtime 配置不得提前初始化 Sentry、Firebase Analytics、PostHog、Amplitude、Segment、CodePush 或 Expo Updates;这些 SDK 必须等真实发布通道、采集字段、用户授权、隐私披露、签名 / 回滚策略和团队发布流程落地后逐项接入。配置检查会同时拒绝相关依赖、Expo 配置和源码初始化片段。
- 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 时恢复,不改变用户音量设置。
@@ -296,7 +296,7 @@ GameBridge 禁止:
2026-06-18 追加:移动壳 H5 入口 query 和 `host.getRuntime` 回包统一读取 `MOBILE_SHELL_HOST_VERSION`,该常量必须与 Expo `app.json` / `package.json` 版本一致。配置检查会拒绝在 `App.tsx``mobileHostBridge.ts` 内重新散落硬编码版本,避免升级移动安装包时 H5 首屏上下文和宿主 runtime 回读版本不一致。
2026-06-18 追加:移动壳默认显式关闭 Expo OTA 更新,直到存在真实发布通道、更新端点、签名 / 回滚策略和团队发布流程后再接入。`app.json` 只允许 `updates.enabled=false`,不得配置 `runtimeVersion`、release channel、EAS channel、`expo-updates` 插件或移动端 crash / analytics / CodePush 依赖;`apps/mobile-shell/scripts/check-config.mjs` 和 Expo public config smoke 会共同拒绝这些发布通道能力被提前打开。
2026-06-18 追加:移动壳默认显式关闭 Expo OTA 更新,直到存在真实发布通道、更新端点、签名 / 回滚策略和团队发布流程后再接入。`app.json` 只允许 `updates.enabled=false`,不得配置 `runtimeVersion`、release channel、EAS channel、`expo-updates` 插件或移动端 crash / analytics / CodePush 依赖;`apps/mobile-shell/scripts/check-config.mjs` 和 Expo public config smoke 会共同拒绝这些发布通道能力被提前打开,移动壳生产入口、HostBridge 和 URL/runtime 配置也不得提前初始化 Sentry、Firebase Analytics、PostHog、Amplitude、Segment、CodePush 或 Expo Updates
### Phase 3Tauri 桌面壳 MVP
@@ -366,7 +366,7 @@ GameBridge 禁止:
### Phase 4:宿主能力扩展
- 移动端接入系统分享、推送、原生登录和渠道支付。
- 桌面端继续补齐自动更新和后续桌面分发能力;文件导出、图片拖拽导入系统托盘已按真实宿主能力逐项接入。
- 移动端和桌面端的自动更新、崩溃上报、analytics、渠道分发、原生登录和渠道支付都必须等真实 SDK、后端契约、发布流程和隐私口径确定后逐项接入;文件导出、图片拖拽导入系统托盘、即时本地通知和系统分享已按真实宿主能力逐项接入。
- 所有新增能力先更新 HostBridge 契约和测试,再落壳实现。
### Phase 5AI H5 sandbox