收紧原生壳调试与通知权限
桌面壳调试脚本固定 WEB_PORT 以匹配 Tauri devUrl 移动壳 Android 显式声明本地通知权限并纳入门禁 同步原生壳方案与共享决策记忆
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "tauri dev",
|
||||
"dev": "WEB_PORT=3000 tauri dev",
|
||||
"build": "tauri build",
|
||||
"typecheck": "node scripts/check-config.mjs"
|
||||
},
|
||||
|
||||
@@ -629,7 +629,7 @@ assertNoBlockedDesktopSdkSnippets();
|
||||
assertDesktopSourceLayout();
|
||||
|
||||
for (const [scriptName, expected] of Object.entries({
|
||||
dev: 'tauri dev',
|
||||
dev: 'WEB_PORT=3000 tauri dev',
|
||||
build: 'tauri build',
|
||||
typecheck: 'node scripts/check-config.mjs',
|
||||
})) {
|
||||
@@ -2091,6 +2091,15 @@ if (config.build?.beforeDevCommand !== 'npm --prefix ../.. run dev:web') {
|
||||
throw new Error('desktop shell dev command must run the root H5 Vite dev server');
|
||||
}
|
||||
|
||||
if (
|
||||
packageConfig.scripts?.dev !== 'WEB_PORT=3000 tauri dev' ||
|
||||
config.build?.devUrl !== 'http://127.0.0.1:3000/'
|
||||
) {
|
||||
throw new Error(
|
||||
'desktop shell dev script must pin WEB_PORT=3000 to match Tauri devUrl',
|
||||
);
|
||||
}
|
||||
|
||||
const [mainWindow] = config.app?.windows ?? [];
|
||||
if (!mainWindow || mainWindow.create !== false) {
|
||||
throw new Error('desktop shell must create the main window from Rust setup');
|
||||
|
||||
@@ -98,6 +98,7 @@
|
||||
"allowBackup": false,
|
||||
"softwareKeyboardLayoutMode": "resize",
|
||||
"permissions": [
|
||||
"android.permission.POST_NOTIFICATIONS",
|
||||
"android.permission.RECORD_AUDIO"
|
||||
],
|
||||
"blockedPermissions": [
|
||||
|
||||
@@ -1312,10 +1312,11 @@ if (appConfig.android?.softwareKeyboardLayoutMode !== 'resize') {
|
||||
|
||||
if (
|
||||
!Array.isArray(appConfig.android?.permissions) ||
|
||||
appConfig.android.permissions.length !== 1 ||
|
||||
appConfig.android.permissions[0] !== 'android.permission.RECORD_AUDIO'
|
||||
appConfig.android.permissions.length !== 2 ||
|
||||
appConfig.android.permissions[0] !== 'android.permission.POST_NOTIFICATIONS' ||
|
||||
appConfig.android.permissions[1] !== 'android.permission.RECORD_AUDIO'
|
||||
) {
|
||||
throw new Error('mobile shell Android package must request only RECORD_AUDIO for same-origin H5 microphone gameplay');
|
||||
throw new Error('mobile shell Android package must request only POST_NOTIFICATIONS and RECORD_AUDIO for real local notifications and same-origin H5 microphone gameplay');
|
||||
}
|
||||
if (appConfig.android?.permissions?.includes('android.permission.CAMERA')) {
|
||||
throw new Error('mobile shell Android CAMERA permission must come from real camera plugins only');
|
||||
@@ -1336,6 +1337,9 @@ for (const permission of blockedAndroidPermissions) {
|
||||
if (appConfig.android?.blockedPermissions?.includes('android.permission.RECORD_AUDIO')) {
|
||||
throw new Error('mobile shell Android package must not block RECORD_AUDIO needed by same-origin H5 microphone gameplay');
|
||||
}
|
||||
if (appConfig.android?.blockedPermissions?.includes('android.permission.POST_NOTIFICATIONS')) {
|
||||
throw new Error('mobile shell Android package must not block POST_NOTIFICATIONS needed by local notification.showLocal delivery');
|
||||
}
|
||||
|
||||
if (
|
||||
appConfig.android?.adaptiveIcon?.foregroundImage !== './assets/icon.png' ||
|
||||
|
||||
@@ -293,7 +293,11 @@ assertEqual(
|
||||
);
|
||||
assertSameSet(
|
||||
expoConfig.android?.permissions ?? [],
|
||||
['android.permission.CAMERA', 'android.permission.RECORD_AUDIO'],
|
||||
[
|
||||
'android.permission.CAMERA',
|
||||
'android.permission.POST_NOTIFICATIONS',
|
||||
'android.permission.RECORD_AUDIO',
|
||||
],
|
||||
'Android explicit permissions',
|
||||
);
|
||||
if (appConfig.android?.permissions?.includes('android.permission.CAMERA')) {
|
||||
@@ -312,6 +316,9 @@ assertIncludes(
|
||||
if (expoConfig.android?.blockedPermissions?.includes('android.permission.RECORD_AUDIO')) {
|
||||
throw new Error('Expo config Android permissions must not block RECORD_AUDIO needed by same-origin H5 microphone gameplay');
|
||||
}
|
||||
if (expoConfig.android?.blockedPermissions?.includes('android.permission.POST_NOTIFICATIONS')) {
|
||||
throw new Error('Expo config Android permissions must not block POST_NOTIFICATIONS needed by local notification.showLocal delivery');
|
||||
}
|
||||
assertIncludes(
|
||||
expoConfig.android?.blockedPermissions,
|
||||
'android.permission.REQUEST_INSTALL_PACKAGES',
|
||||
|
||||
@@ -142,7 +142,7 @@
|
||||
- 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 时恢复,不改变用户音量设置。
|
||||
- 2026-06-18 本地通知能力:新增 `notification.showLocal` HostBridge capability,H5 只能传必填 `title` 和可选 `body`,共享契约负责修剪、折叠普通空白、限制长度并拒绝控制字符;Expo 壳通过 `expo-notifications` 请求系统通知权限、创建 Android 本地 channel 并发送即时本地通知,Android channel id 由共享契约 `HOST_BRIDGE_MOBILE_LOCAL_NOTIFICATION_CHANNEL_ID` 固定,Tauri 壳通过 Rust 侧 `tauri-plugin-notification` 发送系统通知且不开放插件 JS guest API。该能力不包含远程推送、token 注册、定时提醒或后台远程通知,权限拒绝、系统失败或宿主未声明时由 H5 视作失败并继续主流程。
|
||||
- 2026-06-18 移动壳通知权限边界:Expo 移动壳的 Android 包配置必须阻断 `RECEIVE_BOOT_COMPLETED`、`SCHEDULE_EXACT_ALARM` 和 `USE_EXACT_ALARM`,只保留即时本地通知所需权限和前台展示 handler;移动壳源码不得调用 Expo push token、设备 push token、push token listener、通知响应跳转 listener、定时 / 周期通知 API 或 `seconds` / `repeats` / `timeInterval` / `date` / `calendar` / `daily` / `weekly` / `monthly` / `yearly` 触发字段。`Notifications.scheduleNotificationAsync` 只能保留 iOS / 默认 `trigger: null` 和 Android 使用共享 channel id 的即时通知结构;配置检查和 Expo public config smoke 会拒绝这些权限或远程 / 后台 / 定时通知流程被重新打开。
|
||||
- 2026-06-18 移动壳通知权限边界:Expo 移动壳的 Android 包配置必须显式声明 `POST_NOTIFICATIONS`,并阻断 `RECEIVE_BOOT_COMPLETED`、`SCHEDULE_EXACT_ALARM` 和 `USE_EXACT_ALARM`,只保留即时本地通知所需权限和前台展示 handler;移动壳源码不得调用 Expo push token、设备 push token、push token listener、通知响应跳转 listener、定时 / 周期通知 API 或 `seconds` / `repeats` / `timeInterval` / `date` / `calendar` / `daily` / `weekly` / `monthly` / `yearly` 触发字段。`Notifications.scheduleNotificationAsync` 只能保留 iOS / 默认 `trigger: null` 和 Android 使用共享 channel id 的即时通知结构;配置检查和 Expo public config smoke 会拒绝这些权限或远程 / 后台 / 定时通知流程被重新打开。
|
||||
- 2026-06-18 草稿生成完成 / 失败通知:平台壳层的 `markDraftReady` / `markDraftFailed` 统一收口会在原生壳声明 `notification.showLocal` 时请求即时本地通知;通知 payload 只包含生成完成 / 失败标题和草稿来源正文,按草稿来源去重,同一草稿重新进入生成中后才允许再次通知。该能力不替代现有完成 / 错误弹窗、作品架红点、队列概览或后端状态回读,通知失败不阻断主流程。根级原生壳门禁必须覆盖平台壳同步层通过真实 HostBridge transport 发出 `notification.showLocal`,避免只测模型文案。
|
||||
- 2026-06-19 草稿生成 HostBridge 消费门禁:`PlatformEntryFlowShellImpl` 只负责派生草稿通知和未读数量,实际宿主同步经 `platformHostBridgeSync.ts` 调用 `showHostLocalNotification` / `setHostAppBadgeCount`;`npm run check:native-shells` 必须运行该同步层的真实 Tauri transport 测试,并继续覆盖通知模型、未读计数模型、音频导入和文档导入等 H5 HostBridge 消费测试。
|
||||
- 2026-06-18 剪贴板读取能力:新增 `clipboard.readText` HostBridge capability,H5 只能读取纯文本结果,契约限制返回文本最多 100000 字符;Expo 壳通过 `expo-clipboard` 读取系统剪贴板文本,Tauri 壳通过 Rust 侧 `tauri-plugin-clipboard-manager` 读取文本且不开放插件 JS guest API。该能力不读取图片、HTML、文件列表或剪贴板监听事件,宿主未声明或读取失败时由 H5 视作失败并保留原流程。
|
||||
@@ -2457,6 +2457,13 @@
|
||||
- 影响范围:`packages/shared/src/contracts/hostBridge.ts`、`src/services/host-bridge/hostBridge.ts`、`src/components/common/CreativeAudioInputPanel.tsx`、`apps/mobile-shell/`、`apps/desktop-shell/`、原生壳能力检查脚本和 HostBridge 架构文档。
|
||||
- 验证方式:`npm run check:native-shells`、`npm run test -- src/components/common/CreativeAudioInputPanel.test.tsx`、针对变更文件执行 ESLint、`npm run typecheck`、`npm run check:encoding`、`git diff --check`。
|
||||
|
||||
## 2026-06-20 桌面壳 dev 端口固定
|
||||
|
||||
- 背景:Linux 多用户 dev 脚本会把 `npm run dev:web` 自动映射到用户端口段,而 Tauri `devUrl` 固定为 `http://127.0.0.1:3000/`;如果桌面壳直接运行 `tauri dev`,`beforeDevCommand` 启动的 H5 可能不在 Tauri 加载的端口上。
|
||||
- 决策:桌面壳 `apps/desktop-shell/package.json` 的 `dev` 脚本显式设置 `WEB_PORT=3000 tauri dev`,让根 H5 dev server 与 Tauri `devUrl` 使用同一固定本地入口。该固定只作用于桌面壳调试,不改变普通 `npm run dev` / `npm run dev:web` 的 Linux 多用户端口段机制。
|
||||
- 影响范围:`apps/desktop-shell/package.json`、`apps/desktop-shell/scripts/check-config.mjs`、桌面壳方案文档。
|
||||
- 验证方式:`npm run desktop-shell:typecheck`、`npm run check:native-shells`。
|
||||
|
||||
## 2026-06-18 创作 Agent 文档上传接入原生壳文本导入
|
||||
|
||||
- 背景:创作 Agent 工作台已有“上传文档”入口,但在 Expo / Tauri 壳内仍只触发浏览器隐藏文件输入;移动和桌面壳已经具备 `file.importText` 的真实系统文档选择能力。
|
||||
@@ -2495,7 +2502,7 @@
|
||||
## 2026-06-18 原生壳身份与 capability 作用域门禁
|
||||
|
||||
- 背景:Expo 移动壳和 Tauri 桌面壳已经具备首批真实 HostBridge 能力,但如果包身份、桥版本、Android 默认权限、Tauri 窗口列表或 capability 文件在后续迭代中漂移,会把 H5 主站装进更宽的宿主权限面。
|
||||
- 决策:移动壳配置门禁固定 Expo `name`、`slug`、`userInterfaceStyle`、`assetBundlePatterns`、`extra.genarrativeHostBridgeVersion`;Android 源 `app.json.permissions` 只能手写 `RECORD_AUDIO` 供同源 H5 实时声音玩法使用,`CAMERA` 必须只由真实 `expo-camera` / `expo-image-picker` 插件为扫码和拍摄能力生成到最终 Expo public config,仍通过 `blockedPermissions` 阻断当前不需要的高风险权限;相册、相机、麦克风权限说明必须锁定为对应真实能力的最小描述,不能退化成泛化采集或后台能力说明;`apps/mobile-shell/scripts/check-config.mjs` 检查源 `app.json`,`apps/mobile-shell/scripts/check-expo-config.mjs` 检查 Expo CLI 最终解析出的 public config,防止 config plugin 或解析阶段引入身份、资源、权限和权限文案漂移。新增权限必须先有真实宿主能力、系统权限说明和 H5 fallback。桌面壳配置门禁固定唯一 `label=main` 主窗口,`src-tauri/capabilities/` 只能存在 `main.json`,且该 capability 只能绑定 `windows=["main"]`、`permissions=["allow-host-bridge-request"]`,继续只暴露 `host_bridge_request` 一个受控入口。
|
||||
- 决策:移动壳配置门禁固定 Expo `name`、`slug`、`userInterfaceStyle`、`assetBundlePatterns`、`extra.genarrativeHostBridgeVersion`;Android 源 `app.json.permissions` 只能手写 `POST_NOTIFICATIONS` 供 `notification.showLocal` 即时本地通知使用、手写 `RECORD_AUDIO` 供同源 H5 实时声音玩法使用,`CAMERA` 必须只由真实 `expo-camera` / `expo-image-picker` 插件为扫码和拍摄能力生成到最终 Expo public config,仍通过 `blockedPermissions` 阻断当前不需要的高风险权限;相册、相机、麦克风、通知权限说明必须锁定为对应真实能力的最小描述,不能退化成泛化采集、后台或远程推送能力说明;`apps/mobile-shell/scripts/check-config.mjs` 检查源 `app.json`,`apps/mobile-shell/scripts/check-expo-config.mjs` 检查 Expo CLI 最终解析出的 public config,防止 config plugin 或解析阶段引入身份、资源、权限和权限文案漂移。新增权限必须先有真实宿主能力、系统权限说明和 H5 fallback。桌面壳配置门禁固定唯一 `label=main` 主窗口,`src-tauri/capabilities/` 只能存在 `main.json`,且该 capability 只能绑定 `windows=["main"]`、`permissions=["allow-host-bridge-request"]`,继续只暴露 `host_bridge_request` 一个受控入口。
|
||||
- 影响范围:`apps/mobile-shell/scripts/check-config.mjs`、`apps/mobile-shell/scripts/check-expo-config.mjs`、`apps/desktop-shell/scripts/check-config.mjs`、Expo / Tauri HostBridge 方案文档。
|
||||
- 验证方式:`npm run check:native-shells`、`npm run typecheck`、`npm run check:encoding`、`git diff --check`。
|
||||
|
||||
|
||||
@@ -330,7 +330,7 @@ GameBridge 禁止:
|
||||
|
||||
2026-06-18 追加:H5 的草稿生成完成 / 失败收口开始消费 `notification.showLocal`。Expo 壳仍只发送即时本地通知,不注册远程推送 token、不做定时提醒;H5 按草稿来源对完成和失败通知去重,同一草稿重新进入生成中后才允许再次通知,通知失败不阻断弹窗、作品架和后端状态回读。根级原生壳门禁必须覆盖平台壳同步层通过真实 HostBridge transport 发出该 method,避免只保留模型文案测试。
|
||||
|
||||
2026-06-18 追加:移动壳 Android 包配置显式阻断 `RECEIVE_BOOT_COMPLETED`、`SCHEDULE_EXACT_ALARM` 和 `USE_EXACT_ALARM`,保留即时本地通知所需的普通通知权限和前台展示 handler,但不允许重启后恢复通知、精确定时提醒、远程推送 token 或通知响应跳转流程进入当前壳。配置检查会同时扫描 Expo public config 和移动壳通知源码,拒绝接入 `getExpoPushTokenAsync`、设备 push token、push token listener、通知响应 listener,以及 `timeInterval`、`date`、`calendar`、`daily`、`weekly`、`monthly`、`yearly`、`seconds`、`repeats` 等定时 / 周期触发;`Notifications.scheduleNotificationAsync` 只能保留 iOS / 默认 `trigger: null` 和 Android 共享契约固定 `channelId` 的即时通知结构。
|
||||
2026-06-18 追加:移动壳 Android 包配置显式声明 `POST_NOTIFICATIONS` 承接 `notification.showLocal`,并阻断 `RECEIVE_BOOT_COMPLETED`、`SCHEDULE_EXACT_ALARM` 和 `USE_EXACT_ALARM`,只保留即时本地通知所需的普通通知权限和前台展示 handler,但不允许重启后恢复通知、精确定时提醒、远程推送 token 或通知响应跳转流程进入当前壳。配置检查会同时扫描 Expo public config 和移动壳通知源码,拒绝接入 `getExpoPushTokenAsync`、设备 push token、push token listener、通知响应 listener,以及 `timeInterval`、`date`、`calendar`、`daily`、`weekly`、`monthly`、`yearly`、`seconds`、`repeats` 等定时 / 周期触发;`Notifications.scheduleNotificationAsync` 只能保留 iOS / 默认 `trigger: null` 和 Android 共享契约固定 `channelId` 的即时通知结构。
|
||||
|
||||
2026-06-18 追加:H5 的作品架未读草稿生成完成更新开始消费 `app.setBadgeCount`。Expo 移动壳仍只在 iOS 声明该能力,Android 不声明、不伪造成功;H5 只同步可见作品架内未读完成草稿数量,同一草稿多恢复 ID 只计 1,宿主不支持或设置失败不影响 H5 红点与作品架状态。根级原生壳门禁必须覆盖平台壳同步层通过真实 HostBridge transport 发出该 method,避免未读计数模型和宿主消费链路脱节。
|
||||
|
||||
@@ -435,7 +435,7 @@ GameBridge 禁止:
|
||||
|
||||
2026-06-18 追加:桌面壳安装包身份固定为 `world.genarrative.desktop`,产品名为 `Genarrative`,Tauri、Node package 与 Cargo package 版本统一为 `0.1.0`。Release 主窗口只能从打包进二进制的 `index.html` 进入根 `dist` H5 资产,dev URL 只能指向本机 Vite 调试入口;CSP 必须保持 `script-src 'self'`,不得加入 `unsafe-eval`、`tauri:` 或 `file:` 这类扩大桌面攻击面的来源。当前不配置自动更新器,直到存在真实更新端点、签名密钥和发布流程再接入;`apps/desktop-shell/scripts/check-config.mjs` 会校验这些包身份、版本、CSP 和 updater 禁用约束。
|
||||
|
||||
2026-06-18 追加:桌面壳命令入口进入配置门禁。`apps/desktop-shell/package.json` 的 `dev`、`build`、`typecheck`,根 `package.json` 的 `desktop-shell:*` 入口,以及 Tauri `beforeDevCommand` / `beforeBuildCommand` 都必须保持在真实 Tauri dev / build、根 H5 `dev:web`、根 H5 `build:raw` 和桌面壳配置检查路径上;本地桌面调试不能改成加载外部 H5、跳过根 Vite 或绕过 Tauri release build smoke 的命令。
|
||||
2026-06-18 追加:桌面壳命令入口进入配置门禁。`apps/desktop-shell/package.json` 的 `dev`、`build`、`typecheck`,根 `package.json` 的 `desktop-shell:*` 入口,以及 Tauri `beforeDevCommand` / `beforeBuildCommand` 都必须保持在真实 Tauri dev / build、根 H5 `dev:web`、根 H5 `build:raw` 和桌面壳配置检查路径上;本地桌面调试不能改成加载外部 H5、跳过根 Vite 或绕过 Tauri release build smoke 的命令。桌面壳 `dev` 脚本必须显式设置 `WEB_PORT=3000`,与 Tauri `devUrl=http://127.0.0.1:3000/` 保持一致,避免 Linux 多用户端口段把 `npm run dev:web` 自动映射到其它端口后桌面壳仍加载固定 3000。
|
||||
|
||||
2026-06-18 追加:桌面壳默认不接入崩溃上报、analytics、遥测日志或渠道分发 SDK。`apps/desktop-shell/scripts/check-config.mjs` 会拒绝 Sentry、Datadog、PostHog、Segment、Amplitude、Bugsnag、OpenTelemetry、Tauri log / updater 等 Node / Cargo 依赖、`package-lock.json` / `Cargo.lock` 解析包和 Rust 初始化片段;后续只有在真实采集端点、数据字段、用户授权、隐私披露、签名和发布流程确定后,才能按单项能力补充方案与实现。
|
||||
|
||||
@@ -509,7 +509,7 @@ GameBridge 禁止:
|
||||
|
||||
2026-06-18 追加:移动壳生产包网络安全元数据显式收紧。Android `usesCleartextTraffic=false`,iOS `NSAppTransportSecurity.NSAllowsArbitraryLoads=false`,并设置 `ITSAppUsesNonExemptEncryption=false` 作为当前未接入自定义加密能力的出口合规声明;开发联调本机 Vite 仍通过显式 `EXPO_PUBLIC_GENARRATIVE_WEB_URL=http://127.0.0.1:3000/` 进入 development build,不把任意明文流量开关带进默认包配置。`apps/mobile-shell/scripts/check-config.mjs` 会校验这些网络安全字段。
|
||||
|
||||
2026-06-19 追加:移动壳麦克风权限只服务同源 H5 实时玩法。汪汪声浪正式 `published` runtime 必须通过浏览器标准 `getUserMedia({ audio: true })` 采样真实音量;Expo 壳因此显式配置 iOS 麦克风用途文案、Android `RECORD_AUDIO` 权限和 `react-native-webview` 的 `mediaCapturePermissionGrantType="grantIfSameHostElsePrompt"`。该权限不新增 HostBridge method,不开放后台录音、远程语音 SDK、任意文件系统或 AI H5 sandbox 直连宿主能力;只有通过同源主站 WebView 留壳规则的 H5 页面能触发系统授权。`expo-camera` 与 `expo-image-picker` 的麦克风用途文案必须保持同一条真实玩法说明,`android.blockedPermissions` 不得再移除 `RECORD_AUDIO`,最终 public config 只允许 `CAMERA` 和 `RECORD_AUDIO` 两类显式权限。移动拍照、扫码、相册、音频导入和本地通知仍按各自受控能力边界执行,不把麦克风权限当作通用录音入口。`apps/mobile-shell/scripts/check-config.mjs` 与 `check-expo-config.mjs` 会拒绝权限、文案或 WebView 媒体捕获策略漂移。
|
||||
2026-06-19 追加:移动壳麦克风权限只服务同源 H5 实时玩法。汪汪声浪正式 `published` runtime 必须通过浏览器标准 `getUserMedia({ audio: true })` 采样真实音量;Expo 壳因此显式配置 iOS 麦克风用途文案、Android `RECORD_AUDIO` 权限和 `react-native-webview` 的 `mediaCapturePermissionGrantType="grantIfSameHostElsePrompt"`。该权限不新增 HostBridge method,不开放后台录音、远程语音 SDK、任意文件系统或 AI H5 sandbox 直连宿主能力;只有通过同源主站 WebView 留壳规则的 H5 页面能触发系统授权。`expo-camera` 与 `expo-image-picker` 的麦克风用途文案必须保持同一条真实玩法说明,`android.blockedPermissions` 不得再移除 `RECORD_AUDIO`,最终 public config 只允许 `CAMERA`、`POST_NOTIFICATIONS` 和 `RECORD_AUDIO` 三类显式权限。移动拍照、扫码、相册、音频导入和本地通知仍按各自受控能力边界执行,不把麦克风权限当作通用录音入口。`apps/mobile-shell/scripts/check-config.mjs` 与 `check-expo-config.mjs` 会拒绝权限、文案或 WebView 媒体捕获策略漂移。
|
||||
|
||||
2026-06-18 追加:移动壳 Android 自动备份显式关闭。Expo `android.allowBackup=false`,避免 WebView cookie、localStorage、缓存文件、系统剪贴板导入中间文件等本地宿主状态进入 Google Drive 自动备份 / 恢复链路;正式业务事实仍以后端账号、作品、钱包和草稿状态为准,不依赖设备备份恢复。`apps/mobile-shell/scripts/check-config.mjs` 会拒绝恢复默认允许备份的 Android 包配置。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user