收口移动壳导入文件名归一
新增共享 HostBridge 导入文件名清洗函数 移动文件导入改用导入专用文件名归一边界 补充契约测试、移动配置门禁和协议文档
This commit is contained in:
@@ -702,6 +702,7 @@ const sharedPayloadBoundaryImports = [
|
||||
'HOST_BRIDGE_IMPORT_IMAGE_MAX_BYTES',
|
||||
'HOST_BRIDGE_IMPORT_TEXT_MAX_BYTES',
|
||||
'HOST_BRIDGE_MOBILE_LOCAL_NOTIFICATION_CHANNEL_ID',
|
||||
'normalizeHostBridgeImportFileName',
|
||||
'normalizeHostBridgeQrCodeValue',
|
||||
'HOST_BRIDGE_TEXT_MIME_TYPES',
|
||||
];
|
||||
|
||||
@@ -32,6 +32,7 @@ import {
|
||||
HOST_BRIDGE_IMPORT_TEXT_MAX_BYTES,
|
||||
HOST_BRIDGE_TEXT_MIME_TYPES,
|
||||
normalizeHostBridgeExportFileName,
|
||||
normalizeHostBridgeImportFileName,
|
||||
} from '../../../../packages/shared/src/contracts/hostBridge';
|
||||
import { invalidRequest, ok } from './protocol';
|
||||
|
||||
@@ -239,6 +240,17 @@ function normalizeExportedImageFileName(
|
||||
: `${fileName}.${extension}`;
|
||||
}
|
||||
|
||||
function normalizeImportedImageFileName(
|
||||
rawFileName: unknown,
|
||||
mimeType: HostBridgeImageMimeType,
|
||||
) {
|
||||
const fileName = normalizeHostBridgeImportFileName(rawFileName);
|
||||
const extension = imageFileExtension(mimeType);
|
||||
return fileName.toLowerCase().endsWith(`.${extension}`)
|
||||
? fileName
|
||||
: `${fileName}.${extension}`;
|
||||
}
|
||||
|
||||
export async function exportTextFile(
|
||||
payload: unknown,
|
||||
): Promise<FileExportTextResult> {
|
||||
@@ -392,7 +404,7 @@ export async function importTextFile(): Promise<FileImportTextResult> {
|
||||
throw invalidRequest('text file is required');
|
||||
}
|
||||
|
||||
const fileName = normalizeHostBridgeExportFileName(
|
||||
const fileName = normalizeHostBridgeImportFileName(
|
||||
asset.name || 'genarrative-import.txt',
|
||||
);
|
||||
const mimeType = normalizeImportedTextMimeType(asset.mimeType, fileName);
|
||||
@@ -446,7 +458,7 @@ export async function importDocumentFile(): Promise<FileImportDocumentResult> {
|
||||
throw invalidRequest('document file is required');
|
||||
}
|
||||
|
||||
const fileName = normalizeHostBridgeExportFileName(
|
||||
const fileName = normalizeHostBridgeImportFileName(
|
||||
asset.name || 'genarrative-import-document.txt',
|
||||
);
|
||||
const mimeType = normalizeImportedDocumentMimeType(asset.mimeType, fileName);
|
||||
@@ -603,8 +615,9 @@ function imagePickerResultToImportPayload(
|
||||
|
||||
return {
|
||||
action,
|
||||
fileName: normalizeHostBridgeExportFileName(
|
||||
fileName: normalizeImportedImageFileName(
|
||||
asset.fileName || fallbackImportedImageFileName(mimeType),
|
||||
mimeType,
|
||||
),
|
||||
base64Data,
|
||||
mimeType,
|
||||
@@ -692,6 +705,17 @@ function normalizeExportedAudioFileName(
|
||||
: `${fileName}.${extension}`;
|
||||
}
|
||||
|
||||
function normalizeImportedAudioFileName(
|
||||
rawFileName: unknown,
|
||||
mimeType: HostBridgeAudioMimeType,
|
||||
) {
|
||||
const fileName = normalizeHostBridgeImportFileName(rawFileName);
|
||||
const extension = audioFileExtension(mimeType);
|
||||
return fileName.toLowerCase().endsWith(`.${extension}`)
|
||||
? fileName
|
||||
: `${fileName}.${extension}`;
|
||||
}
|
||||
|
||||
function normalizeImportedAudioMimeType(
|
||||
value: unknown,
|
||||
fileName: string,
|
||||
@@ -796,7 +820,7 @@ export async function importAudioFile(): Promise<FileImportAudioResult> {
|
||||
throw invalidRequest('audio file is required');
|
||||
}
|
||||
|
||||
const fileName = normalizeHostBridgeExportFileName(
|
||||
const fileName = normalizeHostBridgeImportFileName(
|
||||
asset.name || 'genarrative-import-audio.webm',
|
||||
);
|
||||
const mimeType = normalizeImportedAudioMimeType(asset.mimeType, fileName);
|
||||
|
||||
@@ -2791,6 +2791,13 @@
|
||||
- 影响范围:`apps/mobile-shell/scripts/check-config.mjs`、宿主壳能力统一协议文档和共享开发流程记忆。
|
||||
- 验证方式:`npm run mobile-shell:typecheck`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
|
||||
|
||||
## 2026-06-20 移动壳导入文件名归一来源收口
|
||||
|
||||
- 背景:HostBridge 导出文件名已由共享 `normalizeHostBridgeExportFileName()` 清洗路径字符、非法字符、空白和长度;导入结果此前只在共享契约里 trim,Expo 移动壳却复用导出清洗器返回清洗后的导入文件名,导致 H5 复核与壳返回语义存在隐性差异。
|
||||
- 决策:共享契约新增导出 `normalizeHostBridgeImportFileName()`,导入文本、文档、图片和音频结果都通过该函数清洗文件名;Expo 移动壳文件导入实现必须直接使用该导入专用函数,配置检查强制反查,避免继续混用导出函数或本地文件名规则。
|
||||
- 影响范围:`packages/shared/src/contracts/hostBridge.ts`、`packages/shared/src/contracts/hostBridge.test.ts`、`apps/mobile-shell/src/host-bridge/files.ts`、`apps/mobile-shell/scripts/check-config.mjs` 和宿主壳能力统一协议文档。
|
||||
- 验证方式:`npm run mobile-shell:test`、`npm run mobile-shell:typecheck`、`npm run test -- packages/shared/src/contracts/hostBridge.test.ts`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
|
||||
|
||||
## 2026-06-19 原生宿主二维码扫码超时单一来源
|
||||
|
||||
- 背景:二维码扫码属于真实相机交互,等待时间应长于普通宿主请求;此前 H5 `scanHostQrCode()` 直接手写 `timeoutMs: 60000`,会让扫码等待边界和共享 HostBridge 契约漂移。
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -46,6 +46,7 @@ import {
|
||||
normalizeHostBridgeHapticsImpactStyle,
|
||||
normalizeHostBridgeImportAudioResult,
|
||||
normalizeHostBridgeImportDocumentResult,
|
||||
normalizeHostBridgeImportFileName,
|
||||
normalizeHostBridgeImportImageResult,
|
||||
normalizeHostBridgeImportTextResult,
|
||||
normalizeHostBridgeLifecycleState,
|
||||
@@ -423,17 +424,20 @@ describe('HostBridge shared contract helpers', () => {
|
||||
});
|
||||
|
||||
test('归一化宿主文件导入结果', () => {
|
||||
expect(normalizeHostBridgeImportFileName(' ../剧情:草稿?.md ')).toBe(
|
||||
'剧情-草稿-.md',
|
||||
);
|
||||
expect(
|
||||
normalizeHostBridgeImportTextResult({
|
||||
action: 'selected',
|
||||
fileName: ' 剧情.md ',
|
||||
fileName: ' ../剧情:草稿?.md ',
|
||||
content: '暖灯猫街',
|
||||
mimeType: 'text/markdown',
|
||||
bytes: 12,
|
||||
}),
|
||||
).toEqual({
|
||||
action: 'selected',
|
||||
fileName: '剧情.md',
|
||||
fileName: '剧情-草稿-.md',
|
||||
content: '暖灯猫街',
|
||||
mimeType: 'text/markdown',
|
||||
bytes: 12,
|
||||
|
||||
@@ -721,17 +721,8 @@ function normalizeHostBridgeImportedBytes(rawBytes: unknown, maxBytes: number) {
|
||||
return rawBytes;
|
||||
}
|
||||
|
||||
function normalizeHostBridgeImportFileName(rawFileName: unknown) {
|
||||
if (typeof rawFileName !== 'string') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const fileName = rawFileName.trim();
|
||||
if (!fileName || hasHostBridgeControlCharacter(fileName)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return fileName;
|
||||
export function normalizeHostBridgeImportFileName(rawFileName: unknown) {
|
||||
return normalizeHostBridgeExportFileName(rawFileName);
|
||||
}
|
||||
|
||||
export function normalizeHostBridgeImportTextResult(
|
||||
|
||||
Reference in New Issue
Block a user