直连移动壳载荷边界契约

移动壳文件能力直接导入共享 HostBridge MIME 和大小边界
移动壳配置检查禁止重新本地声明文件载荷边界
更新原生壳方案和共享决策记录
This commit is contained in:
2026-06-19 00:36:28 +08:00
parent 759b2cb388
commit bb85986d1f
4 changed files with 74 additions and 165 deletions
+38 -119
View File
@@ -182,52 +182,6 @@ function extractNumberConstExport(source, exportName) {
return Number(match[1]);
}
function evaluateNumberExpression(expression) {
const tokens = expression
.split('*')
.map((token) => token.trim())
.filter(Boolean);
if (
tokens.length === 0 ||
tokens.some((token) => !/^\d+$/.test(token))
) {
throw new Error(`unsupported numeric expression ${expression}`);
}
return tokens.reduce((value, token) => value * Number(token), 1);
}
function extractNumberExpressionConstExport(source, exportName) {
const match = source.match(
new RegExp(`export const ${exportName}\\s*=\\s*([^;]+);`),
);
if (!match) {
throw new Error(`unable to read ${exportName}`);
}
return evaluateNumberExpression(match[1]);
}
function extractLocalNumberConst(source, constName) {
const match = source.match(new RegExp(`const ${constName}\\s*=\\s*([^;]+);`));
if (!match) {
throw new Error(`unable to read local const ${constName}`);
}
return evaluateNumberExpression(match[1]);
}
function extractStringSetConst(source, constName) {
const match = source.match(
new RegExp(`const ${constName}[^=]*= new Set[^\\[]*\\[([\\s\\S]*?)\\]\\);`),
);
if (!match) {
throw new Error(`unable to read string Set ${constName}`);
}
return [...match[1].matchAll(/'([^']+)'/g)].map((entry) => entry[1]);
}
function extractMobileBridgeHandledMethods(source) {
const match = source.match(
/async function dispatchMobileHostBridgeRequest[\s\S]*?switch \(request\.method\) \{([\s\S]*?)\n \}/,
@@ -533,58 +487,6 @@ const sharedHostBridgeVersion = extractNumberConstExport(
sharedContractSource,
'HOST_BRIDGE_VERSION',
);
const sharedHostBridgePayloadLimits = {
HOST_BRIDGE_EXPORT_TEXT_MAX_BYTES: extractNumberExpressionConstExport(
sharedContractSource,
'HOST_BRIDGE_EXPORT_TEXT_MAX_BYTES',
),
HOST_BRIDGE_IMPORT_TEXT_MAX_BYTES: extractNumberExpressionConstExport(
sharedContractSource,
'HOST_BRIDGE_IMPORT_TEXT_MAX_BYTES',
),
HOST_BRIDGE_EXPORT_IMAGE_MAX_BYTES: extractNumberExpressionConstExport(
sharedContractSource,
'HOST_BRIDGE_EXPORT_IMAGE_MAX_BYTES',
),
HOST_BRIDGE_IMPORT_IMAGE_MAX_BYTES: extractNumberExpressionConstExport(
sharedContractSource,
'HOST_BRIDGE_IMPORT_IMAGE_MAX_BYTES',
),
HOST_BRIDGE_EXPORT_AUDIO_MAX_BYTES: extractNumberExpressionConstExport(
sharedContractSource,
'HOST_BRIDGE_EXPORT_AUDIO_MAX_BYTES',
),
HOST_BRIDGE_IMPORT_AUDIO_MAX_BYTES: extractNumberExpressionConstExport(
sharedContractSource,
'HOST_BRIDGE_IMPORT_AUDIO_MAX_BYTES',
),
};
const mobileHostBridgePayloadLimits = {
HOST_BRIDGE_EXPORT_TEXT_MAX_BYTES: extractLocalNumberConst(
hostBridgeSource,
'EXPORT_TEXT_MAX_BYTES',
),
HOST_BRIDGE_IMPORT_TEXT_MAX_BYTES: extractLocalNumberConst(
hostBridgeSource,
'IMPORT_TEXT_MAX_BYTES',
),
HOST_BRIDGE_EXPORT_IMAGE_MAX_BYTES: extractLocalNumberConst(
hostBridgeSource,
'EXPORT_IMAGE_MAX_BYTES',
),
HOST_BRIDGE_IMPORT_IMAGE_MAX_BYTES: extractLocalNumberConst(
hostBridgeSource,
'IMPORT_IMAGE_MAX_BYTES',
),
HOST_BRIDGE_EXPORT_AUDIO_MAX_BYTES: extractLocalNumberConst(
hostBridgeSource,
'EXPORT_AUDIO_MAX_BYTES',
),
HOST_BRIDGE_IMPORT_AUDIO_MAX_BYTES: extractLocalNumberConst(
hostBridgeSource,
'IMPORT_AUDIO_MAX_BYTES',
),
};
const handledMobileMethods = extractMobileBridgeHandledMethods(dispatchSource);
const mobileCapabilities = extractStringArrayExport(
hostBridgeSource,
@@ -597,32 +499,49 @@ const iosMobileCapabilities = extractStringArrayExport(
const mobileCapabilitySet = new Set(mobileCapabilities);
const iosMobileCapabilitySet = new Set(iosMobileCapabilities);
const sdkBackedCapabilities = ['auth.requestLogin', 'payment.request'];
for (const [limitName, sharedLimit] of Object.entries(
sharedHostBridgePayloadLimits,
)) {
const mobileLimit = mobileHostBridgePayloadLimits[limitName];
if (mobileLimit !== sharedLimit) {
const sharedPayloadBoundaryImports = [
'HOST_BRIDGE_AUDIO_MIME_TYPES',
'HOST_BRIDGE_EXPORT_AUDIO_MAX_BYTES',
'HOST_BRIDGE_EXPORT_IMAGE_MAX_BYTES',
'HOST_BRIDGE_EXPORT_TEXT_MAX_BYTES',
'HOST_BRIDGE_IMAGE_MIME_TYPES',
'HOST_BRIDGE_IMPORT_AUDIO_MAX_BYTES',
'HOST_BRIDGE_IMPORT_IMAGE_MAX_BYTES',
'HOST_BRIDGE_IMPORT_TEXT_MAX_BYTES',
'HOST_BRIDGE_TEXT_MIME_TYPES',
];
for (const boundaryImport of sharedPayloadBoundaryImports) {
if (!hostBridgeSource.includes(boundaryImport)) {
throw new Error(
`mobile shell ${limitName} drifted: expected ${sharedLimit} but got ${mobileLimit}`,
`mobile shell must import shared HostBridge payload boundary ${boundaryImport}`,
);
}
}
assertSameList(
extractStringSetConst(hostBridgeSource, 'HOST_BRIDGE_TEXT_MIME_TYPES'),
extractStringArrayExport(sharedContractSource, 'HOST_BRIDGE_TEXT_MIME_TYPES'),
'mobile shell text MIME types',
);
assertSameList(
extractStringSetConst(hostBridgeSource, 'HOST_BRIDGE_IMAGE_MIME_TYPES'),
extractStringArrayExport(sharedContractSource, 'HOST_BRIDGE_IMAGE_MIME_TYPES'),
'mobile shell image MIME types',
);
assertSameList(
extractStringSetConst(hostBridgeSource, 'HOST_BRIDGE_AUDIO_MIME_TYPES'),
extractStringArrayExport(sharedContractSource, 'HOST_BRIDGE_AUDIO_MIME_TYPES'),
'mobile shell audio MIME types',
);
const forbiddenLocalPayloadBoundaryDeclarations = [
'EXPORT_TEXT_MAX_BYTES',
'IMPORT_TEXT_MAX_BYTES',
'EXPORT_IMAGE_MAX_BYTES',
'IMPORT_IMAGE_MAX_BYTES',
'EXPORT_AUDIO_MAX_BYTES',
'IMPORT_AUDIO_MAX_BYTES',
'HOST_BRIDGE_EXPORT_TEXT_MAX_BYTES',
'HOST_BRIDGE_IMPORT_TEXT_MAX_BYTES',
'HOST_BRIDGE_EXPORT_IMAGE_MAX_BYTES',
'HOST_BRIDGE_IMPORT_IMAGE_MAX_BYTES',
'HOST_BRIDGE_EXPORT_AUDIO_MAX_BYTES',
'HOST_BRIDGE_IMPORT_AUDIO_MAX_BYTES',
'HOST_BRIDGE_TEXT_MIME_TYPES',
'HOST_BRIDGE_IMAGE_MIME_TYPES',
'HOST_BRIDGE_AUDIO_MIME_TYPES',
];
for (const localBoundary of forbiddenLocalPayloadBoundaryDeclarations) {
if (new RegExp(`const ${localBoundary}\\s*=`).test(hostBridgeSource)) {
throw new Error(
`mobile shell must not redeclare HostBridge payload boundary ${localBoundary}`,
);
}
}
const unknownHandledMobileMethods = handledMobileMethods.filter(
(method) => !sharedMethods.includes(method),