UNPKG

expo-privacy-manifest-polyfill-plugin

Version:

Config plugin for configuring Apple privacy manifests in SDK49 and below

165 lines (146 loc) 4.79 kB
import { ConfigPlugin, withXcodeProject, XcodeProject, ExportedConfigWithProps, } from "@expo/config-plugins"; import { addResourceFileToGroup, getProjectName, } from "@expo/config-plugins/build/ios/utils/Xcodeproj"; import plist from "@expo/plist"; import * as fs from "fs"; import * as path from "path"; export type PrivacyInfo = { NSPrivacyAccessedAPITypes: { NSPrivacyAccessedAPIType: string; NSPrivacyAccessedAPITypeReasons: string[]; }[]; NSPrivacyCollectedDataTypes: { NSPrivacyCollectedDataType: string; NSPrivacyCollectedDataTypeLinked: boolean; NSPrivacyCollectedDataTypeTracking: boolean; NSPrivacyCollectedDataTypePurposes: string[]; }[]; NSPrivacyTracking: boolean; NSPrivacyTrackingDomains: string[]; }; export function setPrivacyInfo( projectConfig: ExportedConfigWithProps<XcodeProject>, privacyManifests: Partial<PrivacyInfo> ) { const { projectRoot, platformProjectRoot } = projectConfig.modRequest; const projectName = getProjectName(projectRoot); const privacyFilePath = path.join( platformProjectRoot, projectName, "PrivacyInfo.xcprivacy" ); const existingFileContent = getFileContents(privacyFilePath); const parsedContent = existingFileContent ? plist.parse(existingFileContent) : {}; const mergedContent = mergePrivacyInfo(parsedContent, privacyManifests); const contents = plist.build(mergedContent); ensureFileExists(privacyFilePath, contents); if (!projectConfig.modResults.hasFile(privacyFilePath)) { projectConfig.modResults = addResourceFileToGroup({ filepath: path.join(projectName, "PrivacyInfo.xcprivacy"), groupName: projectName, project: projectConfig.modResults, isBuildFile: true, verbose: true, }); } return projectConfig; } function getFileContents(filePath: string): string | null { if (!fs.existsSync(filePath)) { return null; } return fs.readFileSync(filePath, { encoding: "utf8" }); } function ensureFileExists(filePath: string, contents: string) { if (!fs.existsSync(path.dirname(filePath))) { fs.mkdirSync(path.dirname(filePath), { recursive: true }); } fs.writeFileSync(filePath, contents); } export function mergePrivacyInfo( existing: Partial<PrivacyInfo>, privacyManifests: Partial<PrivacyInfo> ): PrivacyInfo { let { NSPrivacyAccessedAPITypes = [], NSPrivacyCollectedDataTypes = [], NSPrivacyTracking = false, NSPrivacyTrackingDomains = [], } = structuredClone(existing); // tracking is a boolean, so we can just overwrite it NSPrivacyTracking = privacyManifests.NSPrivacyTracking ?? existing.NSPrivacyTracking ?? false; // merge the api types – for each type ensure the key is in the array, and if it is add the reason if it's not there privacyManifests.NSPrivacyAccessedAPITypes?.forEach((newType) => { const existingType = NSPrivacyAccessedAPITypes.find( (t) => t.NSPrivacyAccessedAPIType === newType.NSPrivacyAccessedAPIType ); if (!existingType) { NSPrivacyAccessedAPITypes.push(newType); } else { existingType.NSPrivacyAccessedAPITypeReasons = [ ...new Set( existingType?.NSPrivacyAccessedAPITypeReasons?.concat( ...newType.NSPrivacyAccessedAPITypeReasons ) ), ]; } }); // merge the collected data types – for each type ensure the key is in the array, and if it is add the purposes if it's not there privacyManifests.NSPrivacyCollectedDataTypes?.forEach((newType) => { const existingType = NSPrivacyCollectedDataTypes.find( (t) => t.NSPrivacyCollectedDataType === newType.NSPrivacyCollectedDataType ); if (!existingType) { NSPrivacyCollectedDataTypes.push(newType); } else { existingType.NSPrivacyCollectedDataTypePurposes = [ ...new Set( existingType?.NSPrivacyCollectedDataTypePurposes?.concat( ...newType.NSPrivacyCollectedDataTypePurposes ) ), ]; } }); // merge the tracking domains NSPrivacyTrackingDomains = [ ...new Set( NSPrivacyTrackingDomains.concat( privacyManifests.NSPrivacyTrackingDomains ?? [] ) ), ]; return { NSPrivacyAccessedAPITypes, NSPrivacyCollectedDataTypes, NSPrivacyTracking, NSPrivacyTrackingDomains, }; } const withExpoPrivacyManifestPolyfillPlugin: ConfigPlugin<{} | void> = ( config, _props = {} ) => { const privacyManifests = config.ios?.privacyManifests; if (!privacyManifests) { return config; } return withXcodeProject( config, (projectConfig: ExportedConfigWithProps<XcodeProject>) => { return setPrivacyInfo(projectConfig, privacyManifests); } ); }; export default withExpoPrivacyManifestPolyfillPlugin;