1082815dcSEvan Baconimport { Android, ExpoConfig, IOS } from '@expo/config-types'; 2*f0d67e12SMateus Craveiroimport * as Fingerprint from '@expo/fingerprint'; 3082815dcSEvan Baconimport { getRuntimeVersionForSDKVersion } from '@expo/sdk-runtime-versions'; 4082815dcSEvan Baconimport fs from 'fs'; 5082815dcSEvan Baconimport { boolish } from 'getenv'; 6082815dcSEvan Baconimport path from 'path'; 7082815dcSEvan Baconimport resolveFrom from 'resolve-from'; 8082815dcSEvan Baconimport semver from 'semver'; 9082815dcSEvan Bacon 10082815dcSEvan Baconimport { AndroidConfig, IOSConfig } from '..'; 11082815dcSEvan Bacon 12082815dcSEvan Baconexport type ExpoConfigUpdates = Pick< 13082815dcSEvan Bacon ExpoConfig, 14082815dcSEvan Bacon 'sdkVersion' | 'owner' | 'runtimeVersion' | 'updates' | 'slug' 15082815dcSEvan Bacon>; 16082815dcSEvan Bacon 17082815dcSEvan Baconexport function getExpoUpdatesPackageVersion(projectRoot: string): string | null { 18082815dcSEvan Bacon const expoUpdatesPackageJsonPath = resolveFrom.silent(projectRoot, 'expo-updates/package.json'); 19082815dcSEvan Bacon if (!expoUpdatesPackageJsonPath || !fs.existsSync(expoUpdatesPackageJsonPath)) { 20082815dcSEvan Bacon return null; 21082815dcSEvan Bacon } 22082815dcSEvan Bacon const packageJson = JSON.parse(fs.readFileSync(expoUpdatesPackageJsonPath, 'utf8')); 23082815dcSEvan Bacon return packageJson.version; 24082815dcSEvan Bacon} 25082815dcSEvan Bacon 2682ade864SWill Schurmanexport function getUpdateUrl(config: Pick<ExpoConfigUpdates, 'updates'>): string | null { 2782ade864SWill Schurman return config.updates?.url ?? null; 28082815dcSEvan Bacon} 29082815dcSEvan Bacon 30053187fcSWojciech Kozyraexport function getAppVersion(config: Pick<ExpoConfig, 'version'>): string { 31053187fcSWojciech Kozyra return config.version ?? '1.0.0'; 32053187fcSWojciech Kozyra} 33053187fcSWojciech Kozyra 34082815dcSEvan Baconexport function getNativeVersion( 35082815dcSEvan Bacon config: Pick<ExpoConfig, 'version'> & { 36082815dcSEvan Bacon android?: Pick<Android, 'versionCode'>; 37082815dcSEvan Bacon ios?: Pick<IOS, 'buildNumber'>; 38082815dcSEvan Bacon }, 39082815dcSEvan Bacon platform: 'android' | 'ios' 40082815dcSEvan Bacon): string { 41082815dcSEvan Bacon const version = IOSConfig.Version.getVersion(config); 42082815dcSEvan Bacon switch (platform) { 43082815dcSEvan Bacon case 'ios': { 44082815dcSEvan Bacon const buildNumber = IOSConfig.Version.getBuildNumber(config); 45082815dcSEvan Bacon return `${version}(${buildNumber})`; 46082815dcSEvan Bacon } 47082815dcSEvan Bacon case 'android': { 48082815dcSEvan Bacon const versionCode = AndroidConfig.Version.getVersionCode(config); 49082815dcSEvan Bacon return `${version}(${versionCode})`; 50082815dcSEvan Bacon } 51082815dcSEvan Bacon default: { 52082815dcSEvan Bacon throw new Error( 53082815dcSEvan Bacon `"${platform}" is not a supported platform. Choose either "ios" or "android".` 54082815dcSEvan Bacon ); 55082815dcSEvan Bacon } 56082815dcSEvan Bacon } 57082815dcSEvan Bacon} 58082815dcSEvan Bacon 59*f0d67e12SMateus Craveiroexport async function getRuntimeVersionNullableAsync( 60*f0d67e12SMateus Craveiro ...[projectRoot, config, platform]: Parameters<typeof getRuntimeVersionAsync> 61*f0d67e12SMateus Craveiro): Promise<string | null> { 62082815dcSEvan Bacon try { 63*f0d67e12SMateus Craveiro return await getRuntimeVersionAsync(projectRoot, config, platform); 64082815dcSEvan Bacon } catch (e) { 65082815dcSEvan Bacon if (boolish('EXPO_DEBUG', false)) { 66082815dcSEvan Bacon console.log(e); 67082815dcSEvan Bacon } 68082815dcSEvan Bacon return null; 69082815dcSEvan Bacon } 70082815dcSEvan Bacon} 71082815dcSEvan Bacon 72*f0d67e12SMateus Craveiroexport async function getRuntimeVersionAsync( 73*f0d67e12SMateus Craveiro projectRoot: string, 74082815dcSEvan Bacon config: Pick<ExpoConfig, 'version' | 'runtimeVersion' | 'sdkVersion'> & { 75082815dcSEvan Bacon android?: Pick<Android, 'versionCode' | 'runtimeVersion'>; 76082815dcSEvan Bacon ios?: Pick<IOS, 'buildNumber' | 'runtimeVersion'>; 77082815dcSEvan Bacon }, 78082815dcSEvan Bacon platform: 'android' | 'ios' 79*f0d67e12SMateus Craveiro): Promise<string | null> { 80082815dcSEvan Bacon const runtimeVersion = config[platform]?.runtimeVersion ?? config.runtimeVersion; 81082815dcSEvan Bacon if (!runtimeVersion) { 82082815dcSEvan Bacon return null; 83082815dcSEvan Bacon } 84082815dcSEvan Bacon 85082815dcSEvan Bacon if (typeof runtimeVersion === 'string') { 86082815dcSEvan Bacon return runtimeVersion; 87053187fcSWojciech Kozyra } else if (runtimeVersion.policy === 'appVersion') { 88053187fcSWojciech Kozyra return getAppVersion(config); 89082815dcSEvan Bacon } else if (runtimeVersion.policy === 'nativeVersion') { 90082815dcSEvan Bacon return getNativeVersion(config, platform); 91082815dcSEvan Bacon } else if (runtimeVersion.policy === 'sdkVersion') { 92082815dcSEvan Bacon if (!config.sdkVersion) { 93082815dcSEvan Bacon throw new Error("An SDK version must be defined when using the 'sdkVersion' runtime policy."); 94082815dcSEvan Bacon } 95082815dcSEvan Bacon return getRuntimeVersionForSDKVersion(config.sdkVersion); 96*f0d67e12SMateus Craveiro } else if (runtimeVersion.policy === 'fingerprintExperimental') { 97*f0d67e12SMateus Craveiro console.warn( 98*f0d67e12SMateus Craveiro "Use of the experimental 'fingerprintExperimental' runtime policy may result in unexpected system behavior." 99*f0d67e12SMateus Craveiro ); 100*f0d67e12SMateus Craveiro return await Fingerprint.createProjectHashAsync(projectRoot); 101082815dcSEvan Bacon } 102082815dcSEvan Bacon 103082815dcSEvan Bacon throw new Error( 104082815dcSEvan Bacon `"${ 105082815dcSEvan Bacon typeof runtimeVersion === 'object' ? JSON.stringify(runtimeVersion) : runtimeVersion 106*f0d67e12SMateus Craveiro }" is not a valid runtime version. getRuntimeVersionAsync only supports a string, "sdkVersion", "appVersion", "nativeVersion" or "fingerprintExperimental" policy.` 107082815dcSEvan Bacon ); 108082815dcSEvan Bacon} 109082815dcSEvan Bacon 110082815dcSEvan Baconexport function getSDKVersion(config: Pick<ExpoConfigUpdates, 'sdkVersion'>): string | null { 111082815dcSEvan Bacon return typeof config.sdkVersion === 'string' ? config.sdkVersion : null; 112082815dcSEvan Bacon} 113082815dcSEvan Bacon 11482ade864SWill Schurmanexport function getUpdatesEnabled(config: Pick<ExpoConfigUpdates, 'updates'>): boolean { 1152fae8288SWill Schurman // allow override of enabled property 1162fae8288SWill Schurman if (config.updates?.enabled !== undefined) { 1172fae8288SWill Schurman return config.updates.enabled; 1182fae8288SWill Schurman } 1192fae8288SWill Schurman 12082ade864SWill Schurman return getUpdateUrl(config) !== null; 121082815dcSEvan Bacon} 122082815dcSEvan Bacon 123082815dcSEvan Baconexport function getUpdatesTimeout(config: Pick<ExpoConfigUpdates, 'updates'>): number { 124082815dcSEvan Bacon return config.updates?.fallbackToCacheTimeout ?? 0; 125082815dcSEvan Bacon} 126082815dcSEvan Bacon 127082815dcSEvan Baconexport function getUpdatesCheckOnLaunch( 128082815dcSEvan Bacon config: Pick<ExpoConfigUpdates, 'updates'>, 129082815dcSEvan Bacon expoUpdatesPackageVersion?: string | null 130b8b0e64dSDouglas Lowder): 'NEVER' | 'ERROR_RECOVERY_ONLY' | 'ALWAYS' | 'WIFI_ONLY' { 131082815dcSEvan Bacon if (config.updates?.checkAutomatically === 'ON_ERROR_RECOVERY') { 132082815dcSEvan Bacon // native 'ERROR_RECOVERY_ONLY' option was only introduced in 0.11.x 133082815dcSEvan Bacon if (expoUpdatesPackageVersion && semver.gte(expoUpdatesPackageVersion, '0.11.0')) { 134082815dcSEvan Bacon return 'ERROR_RECOVERY_ONLY'; 135082815dcSEvan Bacon } 136082815dcSEvan Bacon return 'NEVER'; 137082815dcSEvan Bacon } else if (config.updates?.checkAutomatically === 'ON_LOAD') { 138082815dcSEvan Bacon return 'ALWAYS'; 139b8b0e64dSDouglas Lowder } else if (config.updates?.checkAutomatically === 'WIFI_ONLY') { 140b8b0e64dSDouglas Lowder return 'WIFI_ONLY'; 141b8b0e64dSDouglas Lowder } else if (config.updates?.checkAutomatically === 'NEVER') { 142b8b0e64dSDouglas Lowder return 'NEVER'; 143082815dcSEvan Bacon } 144082815dcSEvan Bacon return 'ALWAYS'; 145082815dcSEvan Bacon} 146082815dcSEvan Bacon 147082815dcSEvan Baconexport function getUpdatesCodeSigningCertificate( 148082815dcSEvan Bacon projectRoot: string, 149082815dcSEvan Bacon config: Pick<ExpoConfigUpdates, 'updates'> 150082815dcSEvan Bacon): string | undefined { 151082815dcSEvan Bacon const codeSigningCertificatePath = config.updates?.codeSigningCertificate; 152082815dcSEvan Bacon if (!codeSigningCertificatePath) { 153082815dcSEvan Bacon return undefined; 154082815dcSEvan Bacon } 155082815dcSEvan Bacon 156082815dcSEvan Bacon const finalPath = path.join(projectRoot, codeSigningCertificatePath); 157082815dcSEvan Bacon if (!fs.existsSync(finalPath)) { 158082815dcSEvan Bacon throw new Error(`File not found at \`updates.codeSigningCertificate\` path: ${finalPath}`); 159082815dcSEvan Bacon } 160082815dcSEvan Bacon 161082815dcSEvan Bacon return fs.readFileSync(finalPath, 'utf8'); 162082815dcSEvan Bacon} 163082815dcSEvan Bacon 164082815dcSEvan Baconexport function getUpdatesCodeSigningMetadata( 165082815dcSEvan Bacon config: Pick<ExpoConfigUpdates, 'updates'> 166082815dcSEvan Bacon): NonNullable<ExpoConfigUpdates['updates']>['codeSigningMetadata'] { 167082815dcSEvan Bacon return config.updates?.codeSigningMetadata; 168082815dcSEvan Bacon} 169082815dcSEvan Bacon 170082815dcSEvan Baconexport function getUpdatesCodeSigningMetadataStringified( 171082815dcSEvan Bacon config: Pick<ExpoConfigUpdates, 'updates'> 172082815dcSEvan Bacon): string | undefined { 173082815dcSEvan Bacon const metadata = getUpdatesCodeSigningMetadata(config); 174082815dcSEvan Bacon if (!metadata) { 175082815dcSEvan Bacon return undefined; 176082815dcSEvan Bacon } 177082815dcSEvan Bacon 178082815dcSEvan Bacon return JSON.stringify(metadata); 179082815dcSEvan Bacon} 18009bd1012SUmberto Ghio 18109bd1012SUmberto Ghioexport function getUpdatesRequestHeaders( 18209bd1012SUmberto Ghio config: Pick<ExpoConfigUpdates, 'updates'> 18309bd1012SUmberto Ghio): NonNullable<ExpoConfigUpdates['updates']>['requestHeaders'] { 18409bd1012SUmberto Ghio return config.updates?.requestHeaders; 18509bd1012SUmberto Ghio} 18609bd1012SUmberto Ghio 18709bd1012SUmberto Ghioexport function getUpdatesRequestHeadersStringified( 18809bd1012SUmberto Ghio config: Pick<ExpoConfigUpdates, 'updates'> 18909bd1012SUmberto Ghio): string | undefined { 19009bd1012SUmberto Ghio const metadata = getUpdatesRequestHeaders(config); 19109bd1012SUmberto Ghio if (!metadata) { 19209bd1012SUmberto Ghio return undefined; 19309bd1012SUmberto Ghio } 19409bd1012SUmberto Ghio 19509bd1012SUmberto Ghio return JSON.stringify(metadata); 19609bd1012SUmberto Ghio} 197