加固移动壳隐私清单验收
移动壳 Expo 配置烟测反查 PrivacyInfo 插件消费路径 宿主壳方案和项目记忆同步隐私清单验收口径
This commit is contained in:
@@ -4,6 +4,14 @@ import fs from 'node:fs';
|
||||
|
||||
const appConfigPath = new URL('../app.json', import.meta.url);
|
||||
const packagePath = new URL('../package.json', import.meta.url);
|
||||
const configPluginsPackagePath = new URL(
|
||||
'../../../node_modules/@expo/config-plugins/package.json',
|
||||
import.meta.url,
|
||||
);
|
||||
const expoPrivacyInfoPluginPath = new URL(
|
||||
'../../../node_modules/@expo/config-plugins/build/ios/PrivacyInfo.js',
|
||||
import.meta.url,
|
||||
);
|
||||
const sharedContractPath = new URL(
|
||||
'../../../packages/shared/src/contracts/hostBridge.ts',
|
||||
import.meta.url,
|
||||
@@ -12,6 +20,13 @@ const shellRoot = new URL('../', import.meta.url);
|
||||
|
||||
const appConfig = JSON.parse(fs.readFileSync(appConfigPath, 'utf8')).expo;
|
||||
const packageConfig = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
|
||||
const configPluginsPackageConfig = JSON.parse(
|
||||
fs.readFileSync(configPluginsPackagePath, 'utf8'),
|
||||
);
|
||||
const expoPrivacyInfoPluginSource = fs.readFileSync(
|
||||
expoPrivacyInfoPluginPath,
|
||||
'utf8',
|
||||
);
|
||||
const sharedContractSource = fs.readFileSync(sharedContractPath, 'utf8');
|
||||
const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
||||
|
||||
@@ -92,6 +107,56 @@ function assertSameSet(actual, expected, label) {
|
||||
}
|
||||
}
|
||||
|
||||
function assertPrivacyManifest(privacyManifests, label) {
|
||||
if (!privacyManifests) {
|
||||
throw new Error(`${label} missing iOS privacy manifest`);
|
||||
}
|
||||
|
||||
assertEqual(
|
||||
privacyManifests.NSPrivacyTracking,
|
||||
false,
|
||||
`${label} privacy tracking flag`,
|
||||
);
|
||||
assertSameList(
|
||||
privacyManifests.NSPrivacyCollectedDataTypes ?? [],
|
||||
[],
|
||||
`${label} collected data types`,
|
||||
);
|
||||
assertSameList(
|
||||
privacyManifests.NSPrivacyTrackingDomains ?? [],
|
||||
[],
|
||||
`${label} tracking domains`,
|
||||
);
|
||||
|
||||
const expectedAccessedApiTypes = new Map([
|
||||
[
|
||||
'NSPrivacyAccessedAPICategoryFileTimestamp',
|
||||
['0A2A.1', '3B52.1', 'C617.1'],
|
||||
],
|
||||
['NSPrivacyAccessedAPICategoryDiskSpace', ['85F4.1', 'E174.1']],
|
||||
['NSPrivacyAccessedAPICategorySystemBootTime', ['35F9.1']],
|
||||
['NSPrivacyAccessedAPICategoryUserDefaults', ['CA92.1']],
|
||||
]);
|
||||
const accessedApiTypes = privacyManifests.NSPrivacyAccessedAPITypes ?? [];
|
||||
if (accessedApiTypes.length !== expectedAccessedApiTypes.size) {
|
||||
throw new Error(`${label} accessed API type count drifted`);
|
||||
}
|
||||
|
||||
for (const [apiType, reasons] of expectedAccessedApiTypes) {
|
||||
const entry = accessedApiTypes.find(
|
||||
(candidate) => candidate.NSPrivacyAccessedAPIType === apiType,
|
||||
);
|
||||
if (!entry) {
|
||||
throw new Error(`${label} missing ${apiType}`);
|
||||
}
|
||||
assertSameList(
|
||||
entry.NSPrivacyAccessedAPITypeReasons ?? [],
|
||||
reasons,
|
||||
`${label} reasons for ${apiType}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function findPlugin(name) {
|
||||
return expoConfig.plugins?.find((plugin) =>
|
||||
Array.isArray(plugin) ? plugin[0] === name : plugin === name,
|
||||
@@ -189,6 +254,25 @@ assertEqual(
|
||||
'允许 Genarrative 使用麦克风运行需要实时声音输入的玩法。',
|
||||
'iOS microphone permission text',
|
||||
);
|
||||
assertPrivacyManifest(appConfig.ios?.privacyManifests, 'Expo source config');
|
||||
|
||||
const configPluginsMajor = Number(
|
||||
String(configPluginsPackageConfig.version).split('.')[0],
|
||||
);
|
||||
if (!Number.isFinite(configPluginsMajor) || configPluginsMajor < 50) {
|
||||
throw new Error('Expo config plugins must support built-in iOS privacy manifests');
|
||||
}
|
||||
for (const snippet of [
|
||||
'function withPrivacyInfo(config)',
|
||||
'config.ios?.privacyManifests',
|
||||
'setPrivacyInfo(projectConfig, privacyManifests)',
|
||||
'PrivacyInfo.xcprivacy',
|
||||
'mergePrivacyInfo(existing, privacyManifests)',
|
||||
]) {
|
||||
if (!expoPrivacyInfoPluginSource.includes(snippet)) {
|
||||
throw new Error(`Expo config plugins PrivacyInfo support missing ${snippet}`);
|
||||
}
|
||||
}
|
||||
|
||||
assertEqual(
|
||||
expoConfig.android?.package,
|
||||
|
||||
Reference in New Issue
Block a user