补齐移动壳分享失败观测

移动系统分享失败记录原生异常日志

移动分享单测覆盖稳定错误和日志边界

配置门禁和文档同步分享观测约束
This commit is contained in:
2026-06-20 20:08:11 +08:00
parent 16f248b965
commit 2742daa832
5 changed files with 27 additions and 2 deletions
@@ -1708,10 +1708,13 @@ for (const snippet of [
'let currentShareTarget: ShareOpenPayload | null = null;',
'currentShareTarget = normalizedTarget.payload;',
'setMobileHostBridgeShareTarget',
'logMobileShareFailure',
'mobile share failed for',
'request: HostBridgeRequest',
"throw invalidRequest('target is required')",
'const normalizedTarget = normalizeHostBridgeShareOpenPayload(target);',
"'share target is invalid'",
"logMobileShareFailure('open.share', error)",
"message: 'share unavailable'",
'ok(request, true)',
'resetMobileHostBridgeShareTargetForTest',
@@ -1738,6 +1741,8 @@ for (const snippet of [
'stores only the normalized cached share payload',
'keeps the previous cached target when a new target is missing or invalid',
'maps native share sheet failures to stable host errors',
"vi.spyOn(console, 'warn')",
'mobile share failed for open.share',
'does not fall back to cached target when explicit payload is unsafe',
'rejects empty share requests before opening native share sheet',
"message: 'share unavailable'",
@@ -32,6 +32,7 @@ function request(
}
beforeEach(() => {
vi.restoreAllMocks();
vi.mocked(Share.share).mockReset();
resetMobileHostBridgeShareTargetForTest();
});
@@ -187,7 +188,9 @@ describe('mobile share helpers', () => {
});
test('maps native share sheet failures to stable host errors', async () => {
vi.mocked(Share.share).mockRejectedValueOnce(new Error('native share failed'));
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
const nativeError = new Error('native share failed');
vi.mocked(Share.share).mockRejectedValueOnce(nativeError);
await expect(
openShare(
@@ -200,6 +203,10 @@ describe('mobile share helpers', () => {
code: 'host_error',
message: 'share unavailable',
});
expect(warnSpy).toHaveBeenCalledWith(
'mobile share failed for open.share',
nativeError,
);
});
test.each([
+6 -1
View File
@@ -10,6 +10,10 @@ import { invalidRequest, ok } from './protocol';
let currentShareTarget: ShareOpenPayload | null = null;
function logMobileShareFailure(label: string, error: unknown) {
console.warn(`mobile share failed for ${label}`, error);
}
export function setMobileHostBridgeShareTarget(request: HostBridgeRequest) {
const payload = request.payload;
const target =
@@ -65,7 +69,8 @@ export async function openShare(request: HostBridgeRequest) {
message: message || url || sharePayload?.title || '',
url,
});
} catch {
} catch (error) {
logMobileShareFailure('open.share', error);
throw {
code: 'host_error',
message: 'share unavailable',
@@ -3148,6 +3148,12 @@
- 决策:`apps/desktop-shell/src-tauri/src/host_bridge/network.rs` 必须在网络状态解析失败时记录 `desktop network failed for status.resolve` 日志。HostBridge 对 H5 仍只返回稳定 `network status unavailable`,不透传 resolver、线程或系统错误细节。
- 验证方式:`cargo test --manifest-path apps/desktop-shell/src-tauri/Cargo.toml host_bridge::network``npm run desktop-shell:typecheck``npm run check:native-shells``npm run check:encoding``git diff --check`
## 2026-06-20 移动壳系统分享异常必须可观测
- 背景:移动壳 `share.open` 调用 React Native 系统分享面板,失败时 H5 只需要知道分享不可用并保留复制链接等回退;但如果原生分享面板 reject 没有日志,开发侧无法区分分享面板不可用、系统取消异常或平台分享模块异常。
- 决策:`apps/mobile-shell/src/host-bridge/share.ts` 必须在 `Share.share(...)` reject 时记录 `mobile share failed for open.share` 日志。HostBridge 对 H5 仍只返回稳定 `share unavailable`,不透传原生分享面板异常明细。
- 验证方式:`npm run mobile-shell:test -- --run src/host-bridge/share.test.ts``npm run mobile-shell:typecheck``npm run check:native-shells``npm run check:encoding``git diff --check`
## 2026-06-20 移动壳门禁脚本必须自登记自扫描
- 背景:Expo 移动壳单端检查已把 `apps/mobile-shell/scripts/` 纳入生产源码扫描入口,但 `check-config.mjs` 自身仍被排除在脚本清单和替身词扫描之外;这会让移动壳与桌面壳门禁结构不一致,也可能让后续门禁反查内容绕过生产替身词规则。
@@ -320,6 +320,8 @@ GameBridge 禁止:
2026-06-20 追加:移动壳文件能力的原生系统异常必须可观测。Expo Sharing 可用性 / 分享面板、DocumentPicker、文本 / base64 文件读取、相册 / 相机权限请求和相册 / 相机打开失败时,`apps/mobile-shell/src/host-bridge/files.ts` 必须记录 `mobile HostBridge file failed for ...` 日志;HostBridge 回包仍只返回稳定的 `host_error` / `unsupported_capability` / `cancelled` / `invalid_request` 语义,不把原生异常明细透传给 H5。移动壳配置检查反查这些日志 label 和对应单测,避免用户可见文件导入导出动作失败后只剩静默兜底。
2026-06-20 追加:移动壳系统分享失败必须可观测。React Native `Share.share(...)` reject 时,`apps/mobile-shell/src/host-bridge/share.ts` 必须记录 `mobile share failed for open.share` 日志;HostBridge 回包仍只暴露稳定 `share unavailable` 语义,不透传原生分享面板异常明细。
2026-06-19 追加:视觉小说结果页素材选择弹窗在移动壳声明 `file.importImage` / `file.importAudio` 时优先打开宿主系统图片或音频选择器,把 HostBridge 返回的 base64 副本转换成浏览器 `File` 后继续调用 `uploadVisualNovelAsset`,不绕过平台素材上传、历史素材、AI 图片生成或草稿写回链路;用户取消原生选择时停留在壳流程内,不连带弹出浏览器文件输入。普通浏览器、小程序和未声明能力的裁剪壳继续使用原隐藏文件输入。
2026-06-19 追加:帮助与反馈页在移动壳声明 `file.captureImage` 时展示“拍摄凭证”入口,调用 Expo 相机拍摄后把 HostBridge 返回的图片副本转换成浏览器 `File`,继续复用反馈页最多 4 张、单张 1 MiB、总 4 MiB、图片 MIME、data URL 预览和提交 payload 校验。拍摄取消时停留在壳流程内,不触发浏览器文件输入;Tauri 桌面壳不声明拍摄能力,普通浏览器、小程序和未声明能力的裁剪壳不显示该入口。