1082815dcSEvan Baconimport path from 'path'; 2082815dcSEvan Baconimport resolveFrom from 'resolve-from'; 3082815dcSEvan Bacon 4*f0d67e12SMateus Craveiroimport { Resources } from '.'; 58a424bebSJames Ideimport { 68a424bebSJames Ide addMetaDataItemToMainApplication, 78a424bebSJames Ide AndroidManifest, 88a424bebSJames Ide findMetaDataItem, 98a424bebSJames Ide getMainApplicationMetaDataValue, 108a424bebSJames Ide getMainApplicationOrThrow, 118a424bebSJames Ide removeMetaDataItemFromMainApplication, 128a424bebSJames Ide} from './Manifest'; 138a424bebSJames Ideimport { buildResourceItem, ResourceXML } from './Resources'; 148a424bebSJames Ideimport { removeStringItem, setStringItem } from './Strings'; 15*f0d67e12SMateus Craveiroimport { ConfigPlugin, ExportedConfigWithProps } from '../Plugin.types'; 16166385e2SDouglas Lowderimport { createStringsXmlPlugin, withAndroidManifest } from '../plugins/android-plugins'; 17166385e2SDouglas Lowderimport { withPlugins } from '../plugins/withPlugins'; 18082815dcSEvan Baconimport { 19082815dcSEvan Bacon ExpoConfigUpdates, 20082815dcSEvan Bacon getExpoUpdatesPackageVersion, 21*f0d67e12SMateus Craveiro getRuntimeVersionNullableAsync, 22082815dcSEvan Bacon getSDKVersion, 23082815dcSEvan Bacon getUpdatesCheckOnLaunch, 24082815dcSEvan Bacon getUpdatesCodeSigningCertificate, 25082815dcSEvan Bacon getUpdatesCodeSigningMetadataStringified, 2609bd1012SUmberto Ghio getUpdatesRequestHeadersStringified, 27082815dcSEvan Bacon getUpdatesEnabled, 28082815dcSEvan Bacon getUpdatesTimeout, 29082815dcSEvan Bacon getUpdateUrl, 30082815dcSEvan Bacon} from '../utils/Updates'; 31082815dcSEvan Bacon 32082815dcSEvan Baconconst CREATE_MANIFEST_ANDROID_PATH = 'expo-updates/scripts/create-manifest-android.gradle'; 33082815dcSEvan Bacon 34082815dcSEvan Baconexport enum Config { 35082815dcSEvan Bacon ENABLED = 'expo.modules.updates.ENABLED', 36082815dcSEvan Bacon CHECK_ON_LAUNCH = 'expo.modules.updates.EXPO_UPDATES_CHECK_ON_LAUNCH', 37082815dcSEvan Bacon LAUNCH_WAIT_MS = 'expo.modules.updates.EXPO_UPDATES_LAUNCH_WAIT_MS', 38082815dcSEvan Bacon SDK_VERSION = 'expo.modules.updates.EXPO_SDK_VERSION', 39082815dcSEvan Bacon RUNTIME_VERSION = 'expo.modules.updates.EXPO_RUNTIME_VERSION', 40082815dcSEvan Bacon UPDATE_URL = 'expo.modules.updates.EXPO_UPDATE_URL', 41082815dcSEvan Bacon RELEASE_CHANNEL = 'expo.modules.updates.EXPO_RELEASE_CHANNEL', 42082815dcSEvan Bacon UPDATES_CONFIGURATION_REQUEST_HEADERS_KEY = 'expo.modules.updates.UPDATES_CONFIGURATION_REQUEST_HEADERS_KEY', 43082815dcSEvan Bacon CODE_SIGNING_CERTIFICATE = 'expo.modules.updates.CODE_SIGNING_CERTIFICATE', 44082815dcSEvan Bacon CODE_SIGNING_METADATA = 'expo.modules.updates.CODE_SIGNING_METADATA', 45082815dcSEvan Bacon} 46082815dcSEvan Bacon 47ae9cd511SWill Schurman// when making changes to this config plugin, ensure the same changes are also made in eas-cli and build-tools 48da5c7fcdSQuinlan Jung// Also ensure the docs are up-to-date: https://docs.expo.dev/bare/installing-updates/ 49ae9cd511SWill Schurman 5082ade864SWill Schurmanexport const withUpdates: ConfigPlugin = (config) => { 5182ade864SWill Schurman return withPlugins(config, [withUpdatesManifest, withRuntimeVersionResource]); 52166385e2SDouglas Lowder}; 53166385e2SDouglas Lowder 5482ade864SWill Schurmanconst withUpdatesManifest: ConfigPlugin = (config) => { 55*f0d67e12SMateus Craveiro return withAndroidManifest(config, async (config) => { 56082815dcSEvan Bacon const projectRoot = config.modRequest.projectRoot; 57082815dcSEvan Bacon const expoUpdatesPackageVersion = getExpoUpdatesPackageVersion(projectRoot); 58*f0d67e12SMateus Craveiro config.modResults = await setUpdatesConfigAsync( 59082815dcSEvan Bacon projectRoot, 60082815dcSEvan Bacon config, 61082815dcSEvan Bacon config.modResults, 62082815dcSEvan Bacon expoUpdatesPackageVersion 63082815dcSEvan Bacon ); 64082815dcSEvan Bacon return config; 65082815dcSEvan Bacon }); 66082815dcSEvan Bacon}; 67082815dcSEvan Bacon 68166385e2SDouglas Lowderconst withRuntimeVersionResource = createStringsXmlPlugin( 69*f0d67e12SMateus Craveiro applyRuntimeVersionFromConfigAsync, 70166385e2SDouglas Lowder 'withRuntimeVersionResource' 71166385e2SDouglas Lowder); 72166385e2SDouglas Lowder 73*f0d67e12SMateus Craveiroexport async function applyRuntimeVersionFromConfigAsync( 74*f0d67e12SMateus Craveiro config: ExportedConfigWithProps<Resources.ResourceXML>, 75166385e2SDouglas Lowder stringsJSON: ResourceXML 76*f0d67e12SMateus Craveiro): Promise<ResourceXML> { 77*f0d67e12SMateus Craveiro const projectRoot = config.modRequest.projectRoot; 78*f0d67e12SMateus Craveiro const runtimeVersion = await getRuntimeVersionNullableAsync(projectRoot, config, 'android'); 79166385e2SDouglas Lowder if (runtimeVersion) { 80166385e2SDouglas Lowder return setStringItem( 81166385e2SDouglas Lowder [buildResourceItem({ name: 'expo_runtime_version', value: runtimeVersion })], 82166385e2SDouglas Lowder stringsJSON 83166385e2SDouglas Lowder ); 84166385e2SDouglas Lowder } 85166385e2SDouglas Lowder return removeStringItem('expo_runtime_version', stringsJSON); 86166385e2SDouglas Lowder} 87166385e2SDouglas Lowder 88*f0d67e12SMateus Craveiroexport async function setUpdatesConfigAsync( 89082815dcSEvan Bacon projectRoot: string, 90082815dcSEvan Bacon config: ExpoConfigUpdates, 91082815dcSEvan Bacon androidManifest: AndroidManifest, 92082815dcSEvan Bacon expoUpdatesPackageVersion?: string | null 93*f0d67e12SMateus Craveiro): Promise<AndroidManifest> { 94082815dcSEvan Bacon const mainApplication = getMainApplicationOrThrow(androidManifest); 95082815dcSEvan Bacon 96082815dcSEvan Bacon addMetaDataItemToMainApplication( 97082815dcSEvan Bacon mainApplication, 98082815dcSEvan Bacon Config.ENABLED, 9982ade864SWill Schurman String(getUpdatesEnabled(config)) 100082815dcSEvan Bacon ); 101082815dcSEvan Bacon addMetaDataItemToMainApplication( 102082815dcSEvan Bacon mainApplication, 103082815dcSEvan Bacon Config.CHECK_ON_LAUNCH, 104082815dcSEvan Bacon getUpdatesCheckOnLaunch(config, expoUpdatesPackageVersion) 105082815dcSEvan Bacon ); 106082815dcSEvan Bacon addMetaDataItemToMainApplication( 107082815dcSEvan Bacon mainApplication, 108082815dcSEvan Bacon Config.LAUNCH_WAIT_MS, 109082815dcSEvan Bacon String(getUpdatesTimeout(config)) 110082815dcSEvan Bacon ); 111082815dcSEvan Bacon 11282ade864SWill Schurman const updateUrl = getUpdateUrl(config); 113082815dcSEvan Bacon if (updateUrl) { 114082815dcSEvan Bacon addMetaDataItemToMainApplication(mainApplication, Config.UPDATE_URL, updateUrl); 115082815dcSEvan Bacon } else { 116082815dcSEvan Bacon removeMetaDataItemFromMainApplication(mainApplication, Config.UPDATE_URL); 117082815dcSEvan Bacon } 118082815dcSEvan Bacon 119082815dcSEvan Bacon const codeSigningCertificate = getUpdatesCodeSigningCertificate(projectRoot, config); 120082815dcSEvan Bacon if (codeSigningCertificate) { 121082815dcSEvan Bacon addMetaDataItemToMainApplication( 122082815dcSEvan Bacon mainApplication, 123082815dcSEvan Bacon Config.CODE_SIGNING_CERTIFICATE, 124082815dcSEvan Bacon codeSigningCertificate 125082815dcSEvan Bacon ); 126082815dcSEvan Bacon } else { 127082815dcSEvan Bacon removeMetaDataItemFromMainApplication(mainApplication, Config.CODE_SIGNING_CERTIFICATE); 128082815dcSEvan Bacon } 129082815dcSEvan Bacon 130082815dcSEvan Bacon const codeSigningMetadata = getUpdatesCodeSigningMetadataStringified(config); 131082815dcSEvan Bacon if (codeSigningMetadata) { 132082815dcSEvan Bacon addMetaDataItemToMainApplication( 133082815dcSEvan Bacon mainApplication, 134082815dcSEvan Bacon Config.CODE_SIGNING_METADATA, 135082815dcSEvan Bacon codeSigningMetadata 136082815dcSEvan Bacon ); 137082815dcSEvan Bacon } else { 138082815dcSEvan Bacon removeMetaDataItemFromMainApplication(mainApplication, Config.CODE_SIGNING_METADATA); 139082815dcSEvan Bacon } 140082815dcSEvan Bacon 14109bd1012SUmberto Ghio const requestHeaders = getUpdatesRequestHeadersStringified(config); 14209bd1012SUmberto Ghio if (requestHeaders) { 14309bd1012SUmberto Ghio addMetaDataItemToMainApplication( 14409bd1012SUmberto Ghio mainApplication, 14509bd1012SUmberto Ghio Config.UPDATES_CONFIGURATION_REQUEST_HEADERS_KEY, 14609bd1012SUmberto Ghio requestHeaders 14709bd1012SUmberto Ghio ); 14809bd1012SUmberto Ghio } else { 149fc285a6fSBartosz Kaszubowski removeMetaDataItemFromMainApplication( 150fc285a6fSBartosz Kaszubowski mainApplication, 151fc285a6fSBartosz Kaszubowski Config.UPDATES_CONFIGURATION_REQUEST_HEADERS_KEY 152fc285a6fSBartosz Kaszubowski ); 15309bd1012SUmberto Ghio } 15409bd1012SUmberto Ghio 155*f0d67e12SMateus Craveiro return await setVersionsConfigAsync(projectRoot, config, androidManifest); 156082815dcSEvan Bacon} 157082815dcSEvan Bacon 158*f0d67e12SMateus Craveiroexport async function setVersionsConfigAsync( 159*f0d67e12SMateus Craveiro projectRoot: string, 160082815dcSEvan Bacon config: Pick<ExpoConfigUpdates, 'sdkVersion' | 'runtimeVersion'>, 161082815dcSEvan Bacon androidManifest: AndroidManifest 162*f0d67e12SMateus Craveiro): Promise<AndroidManifest> { 163082815dcSEvan Bacon const mainApplication = getMainApplicationOrThrow(androidManifest); 164082815dcSEvan Bacon 165*f0d67e12SMateus Craveiro const runtimeVersion = await getRuntimeVersionNullableAsync(projectRoot, config, 'android'); 166082815dcSEvan Bacon if (!runtimeVersion && findMetaDataItem(mainApplication, Config.RUNTIME_VERSION) > -1) { 167082815dcSEvan Bacon throw new Error( 168082815dcSEvan Bacon 'A runtime version is set in your AndroidManifest.xml, but is missing from your app.json/app.config.js. Please either set runtimeVersion in your app.json/app.config.js or remove expo.modules.updates.EXPO_RUNTIME_VERSION from your AndroidManifest.xml.' 169082815dcSEvan Bacon ); 170082815dcSEvan Bacon } 171082815dcSEvan Bacon const sdkVersion = getSDKVersion(config); 172082815dcSEvan Bacon if (runtimeVersion) { 173082815dcSEvan Bacon removeMetaDataItemFromMainApplication(mainApplication, Config.SDK_VERSION); 174166385e2SDouglas Lowder addMetaDataItemToMainApplication( 175166385e2SDouglas Lowder mainApplication, 176166385e2SDouglas Lowder Config.RUNTIME_VERSION, 177166385e2SDouglas Lowder '@string/expo_runtime_version' 178166385e2SDouglas Lowder ); 179082815dcSEvan Bacon } else if (sdkVersion) { 180082815dcSEvan Bacon /** 181082815dcSEvan Bacon * runtime version maybe null in projects using classic updates. In that 182082815dcSEvan Bacon * case we use SDK version 183082815dcSEvan Bacon */ 184082815dcSEvan Bacon removeMetaDataItemFromMainApplication(mainApplication, Config.RUNTIME_VERSION); 185082815dcSEvan Bacon addMetaDataItemToMainApplication(mainApplication, Config.SDK_VERSION, sdkVersion); 186082815dcSEvan Bacon } else { 187082815dcSEvan Bacon removeMetaDataItemFromMainApplication(mainApplication, Config.RUNTIME_VERSION); 188082815dcSEvan Bacon removeMetaDataItemFromMainApplication(mainApplication, Config.SDK_VERSION); 189082815dcSEvan Bacon } 190082815dcSEvan Bacon 191082815dcSEvan Bacon return androidManifest; 192082815dcSEvan Bacon} 193082815dcSEvan Baconexport function ensureBuildGradleContainsConfigurationScript( 194082815dcSEvan Bacon projectRoot: string, 195082815dcSEvan Bacon buildGradleContents: string 196082815dcSEvan Bacon): string { 197082815dcSEvan Bacon if (!isBuildGradleConfigured(projectRoot, buildGradleContents)) { 198082815dcSEvan Bacon let cleanedUpBuildGradleContents; 199082815dcSEvan Bacon 200082815dcSEvan Bacon const isBuildGradleMisconfigured = buildGradleContents 201082815dcSEvan Bacon .split('\n') 202082815dcSEvan Bacon .some((line) => line.includes(CREATE_MANIFEST_ANDROID_PATH)); 203082815dcSEvan Bacon if (isBuildGradleMisconfigured) { 204082815dcSEvan Bacon cleanedUpBuildGradleContents = buildGradleContents.replace( 205082815dcSEvan Bacon new RegExp(`(\n// Integration with Expo updates)?\n.*${CREATE_MANIFEST_ANDROID_PATH}.*\n`), 206082815dcSEvan Bacon '' 207082815dcSEvan Bacon ); 208082815dcSEvan Bacon } else { 209082815dcSEvan Bacon cleanedUpBuildGradleContents = buildGradleContents; 210082815dcSEvan Bacon } 211082815dcSEvan Bacon 212082815dcSEvan Bacon const gradleScriptApply = formatApplyLineForBuildGradle(projectRoot); 213082815dcSEvan Bacon return `${cleanedUpBuildGradleContents}\n// Integration with Expo updates\n${gradleScriptApply}\n`; 214082815dcSEvan Bacon } else { 215082815dcSEvan Bacon return buildGradleContents; 216082815dcSEvan Bacon } 217082815dcSEvan Bacon} 218082815dcSEvan Bacon 219082815dcSEvan Baconexport function formatApplyLineForBuildGradle(projectRoot: string): string { 220082815dcSEvan Bacon const updatesGradleScriptPath = resolveFrom.silent(projectRoot, CREATE_MANIFEST_ANDROID_PATH); 221082815dcSEvan Bacon 222082815dcSEvan Bacon if (!updatesGradleScriptPath) { 223082815dcSEvan Bacon throw new Error( 224082815dcSEvan Bacon "Could not find the build script for Android. This could happen in case of outdated 'node_modules'. Run 'npm install' to make sure that it's up-to-date." 225082815dcSEvan Bacon ); 226082815dcSEvan Bacon } 227082815dcSEvan Bacon 228082815dcSEvan Bacon const relativePath = path.relative( 229082815dcSEvan Bacon path.join(projectRoot, 'android', 'app'), 230082815dcSEvan Bacon updatesGradleScriptPath 231082815dcSEvan Bacon ); 232082815dcSEvan Bacon const posixPath = process.platform === 'win32' ? relativePath.replace(/\\/g, '/') : relativePath; 233082815dcSEvan Bacon 234082815dcSEvan Bacon return `apply from: "${posixPath}"`; 235082815dcSEvan Bacon} 236082815dcSEvan Bacon 237082815dcSEvan Baconexport function isBuildGradleConfigured(projectRoot: string, buildGradleContents: string): boolean { 238082815dcSEvan Bacon const androidBuildScript = formatApplyLineForBuildGradle(projectRoot); 239082815dcSEvan Bacon 240082815dcSEvan Bacon return ( 241082815dcSEvan Bacon buildGradleContents 242082815dcSEvan Bacon .replace(/\r\n/g, '\n') 243082815dcSEvan Bacon .split('\n') 244082815dcSEvan Bacon // Check for both single and double quotes 245082815dcSEvan Bacon .some((line) => line === androidBuildScript || line === androidBuildScript.replace(/"/g, "'")) 246082815dcSEvan Bacon ); 247082815dcSEvan Bacon} 248082815dcSEvan Bacon 249082815dcSEvan Baconexport function isMainApplicationMetaDataSet(androidManifest: AndroidManifest): boolean { 250082815dcSEvan Bacon const updateUrl = getMainApplicationMetaDataValue(androidManifest, Config.UPDATE_URL); 251082815dcSEvan Bacon const runtimeVersion = getMainApplicationMetaDataValue(androidManifest, Config.RUNTIME_VERSION); 252082815dcSEvan Bacon const sdkVersion = getMainApplicationMetaDataValue(androidManifest, Config.SDK_VERSION); 253082815dcSEvan Bacon 254082815dcSEvan Bacon return Boolean(updateUrl && (sdkVersion || runtimeVersion)); 255082815dcSEvan Bacon} 256082815dcSEvan Bacon 257*f0d67e12SMateus Craveiroexport async function isMainApplicationMetaDataSyncedAsync( 258082815dcSEvan Bacon projectRoot: string, 259082815dcSEvan Bacon config: ExpoConfigUpdates, 26082ade864SWill Schurman androidManifest: AndroidManifest 261*f0d67e12SMateus Craveiro): Promise<boolean> { 262082815dcSEvan Bacon return ( 26382ade864SWill Schurman getUpdateUrl(config) === getMainApplicationMetaDataValue(androidManifest, Config.UPDATE_URL) && 26482ade864SWill Schurman String(getUpdatesEnabled(config)) === 265082815dcSEvan Bacon getMainApplicationMetaDataValue(androidManifest, Config.ENABLED) && 266082815dcSEvan Bacon String(getUpdatesTimeout(config)) === 267082815dcSEvan Bacon getMainApplicationMetaDataValue(androidManifest, Config.LAUNCH_WAIT_MS) && 268082815dcSEvan Bacon getUpdatesCheckOnLaunch(config) === 269082815dcSEvan Bacon getMainApplicationMetaDataValue(androidManifest, Config.CHECK_ON_LAUNCH) && 270082815dcSEvan Bacon getUpdatesCodeSigningCertificate(projectRoot, config) === 271082815dcSEvan Bacon getMainApplicationMetaDataValue(androidManifest, Config.CODE_SIGNING_CERTIFICATE) && 272082815dcSEvan Bacon getUpdatesCodeSigningMetadataStringified(config) === 273082815dcSEvan Bacon getMainApplicationMetaDataValue(androidManifest, Config.CODE_SIGNING_METADATA) && 274*f0d67e12SMateus Craveiro (await areVersionsSyncedAsync(projectRoot, config, androidManifest)) 275082815dcSEvan Bacon ); 276082815dcSEvan Bacon} 277082815dcSEvan Bacon 278*f0d67e12SMateus Craveiroexport async function areVersionsSyncedAsync( 279*f0d67e12SMateus Craveiro projectRoot: string, 280082815dcSEvan Bacon config: Pick<ExpoConfigUpdates, 'runtimeVersion' | 'sdkVersion'>, 281082815dcSEvan Bacon androidManifest: AndroidManifest 282*f0d67e12SMateus Craveiro): Promise<boolean> { 283*f0d67e12SMateus Craveiro const expectedRuntimeVersion = await getRuntimeVersionNullableAsync( 284*f0d67e12SMateus Craveiro projectRoot, 285*f0d67e12SMateus Craveiro config, 286*f0d67e12SMateus Craveiro 'android' 287*f0d67e12SMateus Craveiro ); 288082815dcSEvan Bacon const expectedSdkVersion = getSDKVersion(config); 289082815dcSEvan Bacon 290082815dcSEvan Bacon const currentRuntimeVersion = getMainApplicationMetaDataValue( 291082815dcSEvan Bacon androidManifest, 292082815dcSEvan Bacon Config.RUNTIME_VERSION 293082815dcSEvan Bacon ); 294082815dcSEvan Bacon const currentSdkVersion = getMainApplicationMetaDataValue(androidManifest, Config.SDK_VERSION); 295082815dcSEvan Bacon 296082815dcSEvan Bacon if (expectedRuntimeVersion !== null) { 297082815dcSEvan Bacon return currentRuntimeVersion === expectedRuntimeVersion && currentSdkVersion === null; 298082815dcSEvan Bacon } else if (expectedSdkVersion !== null) { 299082815dcSEvan Bacon return currentSdkVersion === expectedSdkVersion && currentRuntimeVersion === null; 300082815dcSEvan Bacon } else { 301082815dcSEvan Bacon return true; 302082815dcSEvan Bacon } 303082815dcSEvan Bacon} 304