加固宿主事件订阅门禁
新增原生壳检查脚本对 H5 HostBridge 事件订阅双能力门控的静态反查 补齐 HostBridge 事件订阅测试矩阵里的返回栈事件负向覆盖 更新宿主壳协议、壳方案和共享记忆中的事件门禁说明
This commit is contained in:
@@ -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();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user