1 // Copyright © 2019 650 Industries. All rights reserved. 2 3 import Foundation 4 import ABI49_0_0EXManifests 5 6 internal final class LegacyUpdate: Update { 7 /** 8 * Method for initializing updates with manifests in the classic format (i.e. come from Expo's 9 * classic updates service or a self-hosted service following the classic updates format, such as 10 * one making use of `expo-cli export`). 11 * 12 * Asset URLs are relative in this format, and we assume that if no base URL is explicitly provided, 13 * the base URL is Expo's classic asset CDN. 14 */ 15 static func update( 16 withLegacyManifest: LegacyManifest, 17 config: UpdatesConfig, 18 database: UpdatesDatabase? 19 ) -> Update { 20 let manifest = withLegacyManifest 21 22 var updateId: UUID 23 var commitTime: Date 24 if manifest.isUsingDeveloperTool() { 25 // XDL does not set a releaseId or commitTime for development manifests. 26 // we do not need these so we just stub them out 27 updateId = UUID() 28 commitTime = Date() 29 } else { 30 updateId = UUID(uuidString: manifest.releaseID()).require("updateId should be a valid UUID") 31 let commitTimeString = manifest.commitTime() 32 commitTime = ABI49_0_0RCTConvert.nsDate(commitTimeString) 33 } 34 35 var isDevelopmentMode: Bool = false 36 var status: UpdateStatus 37 if manifest.isDevelopmentMode() { 38 isDevelopmentMode = true 39 status = UpdateStatus.StatusDevelopment 40 } else { 41 status = UpdateStatus.StatusPending 42 } 43 44 let bundleUrlString = manifest.bundleUrl() 45 let assets = manifest.bundledAssets() ?? [] 46 47 var runtimeVersion: String 48 let manifestRuntimeVersion = manifest.runtimeVersion() 49 if let manifestRuntimeVersion = manifestRuntimeVersion as? String { 50 runtimeVersion = manifestRuntimeVersion 51 } else { 52 runtimeVersion = manifest.expoGoSDKVersion().require("Manifest JSON must have either a valid runtimeVersion property or a valid sdkVersion property") 53 } 54 55 let bundleUrl = URL(string: bundleUrlString).require("Manifest JSON must have a valid URL as the bundleUrl property") 56 57 var processedAssets: [UpdateAsset] = [] 58 59 let bundleKey = manifest.bundleKey() 60 let jsBundleAsset = UpdateAsset(key: bundleKey, type: EmbeddedAppLoader.ABI49_0_0EXUpdatesEmbeddedBundleFileType) 61 jsBundleAsset.url = bundleUrl 62 jsBundleAsset.isLaunchAsset = true 63 jsBundleAsset.mainBundleFilename = EmbeddedAppLoader.ABI49_0_0EXUpdatesEmbeddedBundleFilename 64 processedAssets.append(jsBundleAsset) 65 66 let bundledAssetBaseUrl = LegacyUpdate.bundledAssetBaseUrl(withManifest: manifest, config: config) 67 68 assets.forEach { bundledAsset in 69 let bundledAssetString: String = assertType( 70 value: bundledAsset, 71 description: "Manifest JSON bundledAssets property must be an array of strings, found unexpected value" 72 ) 73 let extensionStartIndex = bundledAssetString.lastIndex(of: ".") 74 let prefixLength = "asset_".count 75 76 var filename: String 77 var hash: String 78 var type: String 79 80 if let extensionStartIndex = extensionStartIndex { 81 filename = String(bundledAssetString[..<extensionStartIndex]) 82 let hashStartIndex = bundledAssetString.index(bundledAssetString.startIndex, offsetBy: prefixLength) 83 hash = String(bundledAssetString[hashStartIndex..<extensionStartIndex]) 84 let typeStartIndex = bundledAssetString.index(extensionStartIndex, offsetBy: 1) 85 type = String(bundledAssetString[typeStartIndex...]) 86 } else { 87 filename = bundledAssetString 88 let bundledAssetStringIndex = bundledAssetString.index(bundledAssetString.startIndex, offsetBy: prefixLength) 89 hash = String(bundledAssetString[bundledAssetStringIndex...]) 90 type = "" 91 } 92 93 let url = bundledAssetBaseUrl.appendingPathComponent(hash) 94 let key = hash 95 let asset = UpdateAsset(key: key, type: type) 96 asset.url = url 97 asset.mainBundleFilename = filename 98 99 processedAssets.append(asset) 100 } 101 102 return Update.init( 103 manifest: manifest, 104 config: config, 105 database: database, 106 updateId: updateId, 107 scopeKey: config.scopeKey.require("Must supply scopeKey in configuration"), 108 commitTime: commitTime, 109 runtimeVersion: runtimeVersion, 110 keep: true, 111 status: status, 112 isDevelopmentMode: isDevelopmentMode, 113 assetsFromManifest: processedAssets 114 ) 115 } 116 117 private static let ABI49_0_0EXUpdatesExpoAssetBaseUrl = "https://classic-assets.eascdn.net/~assets/" 118 private static let ABI49_0_0EXUpdatesExpoIoDomain = "expo.io" 119 private static let ABI49_0_0EXUpdatesExpHostDomain = "exp.host" 120 private static let ABI49_0_0EXUpdatesExpoTestDomain = "expo.test" 121 122 static func bundledAssetBaseUrl(withManifest: LegacyManifest, config: UpdatesConfig) -> URL { 123 let manifestUrl = config.updateUrl 124 let host = manifestUrl?.host 125 126 guard let host = host else { 127 // The URL is valid and constant, so it'll never throw 128 // swiftlint:disable:next force_unwrapping 129 return URL(string: ABI49_0_0EXUpdatesExpoAssetBaseUrl)! 130 } 131 132 if host.contains(ABI49_0_0EXUpdatesExpoIoDomain) || 133 host.contains(ABI49_0_0EXUpdatesExpHostDomain) || 134 host.contains(ABI49_0_0EXUpdatesExpoTestDomain) { 135 // The URL is valid and constant, so it'll never throw 136 // swiftlint:disable:next force_unwrapping 137 return URL(string: ABI49_0_0EXUpdatesExpoAssetBaseUrl)! 138 } 139 140 let assetsPathOrUrl = withManifest.assetUrlOverride() ?? "assets" 141 // assetUrlOverride may be an absolute or relative URL 142 // if relative, we should resolve with respect to the manifest URL 143 return URL(string: assetsPathOrUrl, relativeTo: manifestUrl).require( 144 "Invalid assetUrlOverride" 145 ).absoluteURL.standardized 146 } 147 } 148