加固宿主事件订阅门禁

新增原生壳检查脚本对 H5 HostBridge 事件订阅双能力门控的静态反查

补齐 HostBridge 事件订阅测试矩阵里的返回栈事件负向覆盖

更新宿主壳协议、壳方案和共享记忆中的事件门禁说明
This commit is contained in:
2026-06-19 08:41:42 +08:00
parent 153cd8db9c
commit c054e51580
6 changed files with 162 additions and 5 deletions
+121
View File
@@ -92,6 +92,24 @@ const h5HostBridgeRequiredCallChainFiles = [
'src/services/wechatMiniProgramSubscribe.ts',
'src/services/wechatMiniProgramShareGrid.ts',
];
const h5HostBridgeEventSubscriptionFacades = [
{
functionName: 'subscribeHostAppLifecycle',
eventName: 'app.lifecycle',
},
{
functionName: 'subscribeHostNetworkStatusChange',
eventName: 'network.statusChanged',
},
{
functionName: 'subscribeHostNavigationCanGoBack',
eventName: 'navigation.canGoBack',
},
{
functionName: 'subscribeHostImageDrop',
eventName: 'file.imageDropped',
},
];
const expectedWechatHostBridgeFiles = [
'dispatch.js',
'payment.js',
@@ -716,6 +734,41 @@ function extractStringConst(source, constName) {
return match[1];
}
function escapeRegExp(value) {
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
function extractFunctionSource(source, functionName) {
const declarationStart = source.indexOf(`function ${functionName}`);
const exportedDeclarationStart = source.indexOf(`export function ${functionName}`);
const start =
exportedDeclarationStart === -1
? declarationStart
: exportedDeclarationStart;
if (start === -1) {
throw new Error(`unable to read function ${functionName}`);
}
const openBrace = source.indexOf('{', start);
if (openBrace === -1) {
throw new Error(`unable to read function body ${functionName}`);
}
let depth = 0;
for (let index = openBrace; index < source.length; index += 1) {
if (source[index] === '{') {
depth += 1;
} else if (source[index] === '}') {
depth -= 1;
if (depth === 0) {
return source.slice(start, index + 1);
}
}
}
throw new Error(`unterminated function body ${functionName}`);
}
function extractTsStringObject(source, exportName) {
const match = source.match(
new RegExp(`export const ${exportName}\\s*=\\s*\\{([\\s\\S]*?)\\}\\s*as const;`),
@@ -732,6 +785,71 @@ function extractTsStringObject(source, exportName) {
);
}
function assertH5HostBridgeEventSubscriptionGates() {
const sharedContractSource = fs.readFileSync(
sharedHostBridgeContractPath,
'utf8',
);
const h5HostBridgeSource = fs.readFileSync(
'src/services/host-bridge/hostBridge.ts',
'utf8',
);
const sharedEvents = extractTsStringArray(
sharedContractSource,
'HOST_BRIDGE_EVENTS',
);
assertSameList(
h5HostBridgeEventSubscriptionFacades.map((entry) => entry.eventName),
sharedEvents,
'H5 HostBridge event subscription facade coverage',
);
const helperSource = extractFunctionSource(
h5HostBridgeSource,
'canUseNativeHostEventCapability',
);
if (
!helperSource.includes("canUseNativeHostCapability('host.events')") ||
!helperSource.includes('canUseNativeHostCapability(capability)')
) {
throw new Error(
'H5 HostBridge event capability helper must require host.events and the event capability',
);
}
const subscribedEvents = [
...h5HostBridgeSource.matchAll(
/subscribeNativeAppHostBridgeEvent(?:<[^>]+>)?\(\s*['"]([^'"]+)['"]/g,
),
].map((entry) => entry[1]);
assertSameList(
subscribedEvents,
sharedEvents,
'H5 HostBridge subscribed event list',
);
for (const { functionName, eventName } of h5HostBridgeEventSubscriptionFacades) {
const functionSource = extractFunctionSource(h5HostBridgeSource, functionName);
if (
!functionSource.includes(`canUseNativeHostEventCapability('${eventName}')`)
) {
throw new Error(
`${functionName} must gate ${eventName} with host.events and the event capability`,
);
}
const directCapabilityPattern = new RegExp(
`canUseNativeHostCapability\\('${escapeRegExp(eventName)}'\\)`,
);
if (directCapabilityPattern.test(functionSource)) {
throw new Error(
`${functionName} must not bypass canUseNativeHostEventCapability for ${eventName}`,
);
}
}
}
function extractDocumentCapabilityList(source, marker) {
const markerIndex = source.indexOf(marker);
if (markerIndex === -1) {
@@ -1294,6 +1412,9 @@ assertExternalUrlProtocolParity();
console.log('[check:native-shells] wechat-mini-program-route-parity');
assertWechatMiniProgramRouteParity();
console.log('[check:native-shells] h5-host-bridge-event-subscription-gates');
assertH5HostBridgeEventSubscriptionGates();
console.log('[check:native-shells] production-shell-dev-scaffold-scan');
assertNoProductionShellDevScaffoldTerms();