1import { ExpoConfig } from '@expo/config-types'; 2import { JSONObject } from '@expo/json-file'; 3import fs from 'fs'; 4import path from 'path'; 5import slash from 'slash'; 6import { XCBuildConfiguration } from 'xcode'; 7 8import { findFirstNativeTarget, getXCBuildConfigurationFromPbxproj } from './Target'; 9import { 10 getBuildConfigurationsForListId, 11 getPbxproj, 12 getProductName, 13 getProjectName, 14} from './utils/Xcodeproj'; 15import { trimQuotes } from './utils/string'; 16import { createEntitlementsPlugin } from '../plugins/ios-plugins'; 17 18export const withAssociatedDomains = createEntitlementsPlugin( 19 setAssociatedDomains, 20 'withAssociatedDomains' 21); 22 23export function setAssociatedDomains( 24 config: ExpoConfig, 25 { 'com.apple.developer.associated-domains': _, ...entitlementsPlist }: JSONObject 26): JSONObject { 27 if (config.ios?.associatedDomains) { 28 return { 29 ...entitlementsPlist, 30 'com.apple.developer.associated-domains': config.ios.associatedDomains, 31 }; 32 } 33 34 return entitlementsPlist; 35} 36 37export function getEntitlementsPath( 38 projectRoot: string, 39 { 40 targetName, 41 buildConfiguration = 'Release', 42 }: { targetName?: string; buildConfiguration?: string } = {} 43): string | null { 44 const project = getPbxproj(projectRoot); 45 const xcBuildConfiguration = getXCBuildConfigurationFromPbxproj(project, { 46 targetName, 47 buildConfiguration, 48 }); 49 if (!xcBuildConfiguration) { 50 return null; 51 } 52 const entitlementsPath = getEntitlementsPathFromBuildConfiguration( 53 projectRoot, 54 xcBuildConfiguration 55 ); 56 return entitlementsPath && fs.existsSync(entitlementsPath) ? entitlementsPath : null; 57} 58 59function getEntitlementsPathFromBuildConfiguration( 60 projectRoot: string, 61 xcBuildConfiguration: XCBuildConfiguration 62): string | null { 63 const entitlementsPathRaw = xcBuildConfiguration?.buildSettings?.CODE_SIGN_ENTITLEMENTS as 64 | string 65 | undefined; 66 if (entitlementsPathRaw) { 67 return path.normalize(path.join(projectRoot, 'ios', trimQuotes(entitlementsPathRaw))); 68 } else { 69 return null; 70 } 71} 72 73export function ensureApplicationTargetEntitlementsFileConfigured(projectRoot: string): void { 74 const project = getPbxproj(projectRoot); 75 const projectName = getProjectName(projectRoot); 76 const productName = getProductName(project); 77 78 const [, applicationTarget] = findFirstNativeTarget(project); 79 const buildConfigurations = getBuildConfigurationsForListId( 80 project, 81 applicationTarget.buildConfigurationList 82 ); 83 let hasChangesToWrite = false; 84 for (const [, xcBuildConfiguration] of buildConfigurations) { 85 const oldEntitlementPath = getEntitlementsPathFromBuildConfiguration( 86 projectRoot, 87 xcBuildConfiguration 88 ); 89 if (oldEntitlementPath && fs.existsSync(oldEntitlementPath)) { 90 return; 91 } 92 hasChangesToWrite = true; 93 // Use posix formatted path, even on Windows 94 const entitlementsRelativePath = slash(path.join(projectName, `${productName}.entitlements`)); 95 const entitlementsPath = path.normalize( 96 path.join(projectRoot, 'ios', entitlementsRelativePath) 97 ); 98 fs.mkdirSync(path.dirname(entitlementsPath), { recursive: true }); 99 if (!fs.existsSync(entitlementsPath)) { 100 fs.writeFileSync(entitlementsPath, ENTITLEMENTS_TEMPLATE); 101 } 102 xcBuildConfiguration.buildSettings.CODE_SIGN_ENTITLEMENTS = entitlementsRelativePath; 103 } 104 if (hasChangesToWrite) { 105 fs.writeFileSync(project.filepath, project.writeSync()); 106 } 107} 108 109const ENTITLEMENTS_TEMPLATE = ` 110<?xml version="1.0" encoding="UTF-8"?> 111<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> 112<plist version="1.0"> 113<dict> 114</dict> 115</plist> 116`; 117