1 // Copyright 2019 650 Industries. All rights reserved. 2 3 // swiftlint:disable closure_body_length 4 // swiftlint:disable superfluous_else 5 6 import ABI49_0_0ExpoModulesCore 7 8 /** 9 * Exported module which provides to the JS runtime information about the currently running update 10 * and updates state, along with methods to check for and download new updates, reload with the 11 * newest downloaded update applied, and read/clear native log entries. 12 * 13 * Communicates with the updates hub (AppController in most apps, ABI49_0_0EXAppLoaderExpoUpdates in 14 * Expo Go and legacy standalone apps) via ABI49_0_0EXUpdatesService, an internal module which is overridden 15 * by ABI49_0_0EXUpdatesBinding, a scoped module, in Expo Go. 16 */ 17 public final class UpdatesModule: Module { 18 private let updatesService: ABI49_0_0EXUpdatesModuleInterface? 19 private let methodQueue = UpdatesUtils.methodQueue 20 21 public required init(appContext: AppContext) { 22 updatesService = appContext.legacyModule(implementing: ABI49_0_0EXUpdatesModuleInterface.self) 23 super.init(appContext: appContext) 24 } 25 26 // swiftlint:disable cyclomatic_complexity definitionnull27 public func definition() -> ModuleDefinition { 28 Name("ExpoUpdates") 29 30 Constants { 31 let releaseChannel = updatesService?.config?.releaseChannel 32 let channel = updatesService?.config?.requestHeaders["expo-channel-name"] ?? "" 33 let runtimeVersion = updatesService?.config?.runtimeVersion ?? "" 34 let checkAutomatically = updatesService?.config?.checkOnLaunch.asString ?? CheckAutomaticallyConfig.Always.asString 35 let isMissingRuntimeVersion = updatesService?.config?.isMissingRuntimeVersion() 36 37 guard let updatesService = updatesService, 38 updatesService.isStarted, 39 let launchedUpdate = updatesService.launchedUpdate else { 40 return [ 41 "isEnabled": false, 42 "isEmbeddedLaunch": false, 43 "isMissingRuntimeVersion": isMissingRuntimeVersion, 44 "releaseChannel": releaseChannel, 45 "runtimeVersion": runtimeVersion, 46 "checkAutomatically": checkAutomatically, 47 "channel": channel 48 ] 49 } 50 51 let commitTime = UInt64(floor(launchedUpdate.commitTime.timeIntervalSince1970 * 1000)) 52 return [ 53 "isEnabled": true, 54 "isEmbeddedLaunch": updatesService.isEmbeddedLaunch, 55 "isUsingEmbeddedAssets": updatesService.isUsingEmbeddedAssets, 56 "updateId": launchedUpdate.updateId.uuidString, 57 "manifest": launchedUpdate.manifest.rawManifestJSON(), 58 "localAssets": updatesService.assetFilesMap ?? [:], 59 "isEmergencyLaunch": updatesService.isEmergencyLaunch, 60 "isMissingRuntimeVersion": isMissingRuntimeVersion, 61 "releaseChannel": releaseChannel, 62 "runtimeVersion": runtimeVersion, 63 "checkAutomatically": checkAutomatically, 64 "channel": channel, 65 "commitTime": commitTime, 66 "nativeDebug": UpdatesUtils.isNativeDebuggingEnabled() 67 ] 68 } 69 70 AsyncFunction("reload") { (promise: Promise) in 71 guard let updatesService = updatesService, let config = updatesService.config, config.isEnabled else { 72 throw UpdatesDisabledException() 73 } 74 guard updatesService.canRelaunch else { 75 throw UpdatesNotInitializedException() 76 } 77 updatesService.requestRelaunch { success in 78 if success { 79 promise.resolve(nil) 80 } else { 81 promise.reject(UpdatesReloadException()) 82 } 83 } 84 } 85 86 AsyncFunction("checkForUpdateAsync") { (promise: Promise) in 87 let maybeIsCheckForUpdateEnabled: Bool? = updatesService?.canCheckForUpdateAndFetchUpdate ?? true 88 guard maybeIsCheckForUpdateEnabled ?? false else { 89 promise.reject("ERR_UPDATES_CHECK", "checkForUpdateAsync() is not enabled") 90 return 91 } 92 UpdatesUtils.checkForUpdate { result in 93 if result["message"] != nil { 94 guard let message = result["message"] as? String else { 95 promise.reject("ERR_UPDATES_CHECK", "") 96 return 97 } 98 promise.reject("ERR_UPDATES_CHECK", message) 99 return 100 } 101 if result["manifest"] != nil { 102 promise.resolve([ 103 "isAvailable": true, 104 "manifest": result["manifest"], 105 "isRollBackToEmbedded": false 106 ]) 107 return 108 } 109 if result["isRollBackToEmbedded"] != nil { 110 promise.resolve([ 111 "isAvailable": false, 112 "isRollBackToEmbedded": result["isRollBackToEmbedded"] 113 ]) 114 return 115 } 116 promise.resolve(["isAvailable": false, "isRollBackToEmbedded": false]) 117 } 118 } 119 120 AsyncFunction("getExtraParamsAsync") { (promise: Promise) in 121 guard let updatesService = updatesService, 122 let config = updatesService.config, 123 config.isEnabled else { 124 throw UpdatesDisabledException() 125 } 126 127 guard let scopeKey = config.scopeKey else { 128 throw Exception(name: "ERR_UPDATES_SCOPE_KEY", description: "Muse have scopeKey in config") 129 } 130 131 updatesService.database.databaseQueue.async { 132 do { 133 promise.resolve(try updatesService.database.extraParams(withScopeKey: scopeKey)) 134 } catch { 135 promise.reject(error) 136 } 137 } 138 } 139 140 AsyncFunction("setExtraParamAsync") { (key: String, value: String?, promise: Promise) in 141 guard let updatesService = updatesService, 142 let config = updatesService.config, 143 config.isEnabled else { 144 throw UpdatesDisabledException() 145 } 146 147 guard let scopeKey = config.scopeKey else { 148 throw Exception(name: "ERR_UPDATES_SCOPE_KEY", description: "Muse have scopeKey in config") 149 } 150 151 updatesService.database.databaseQueue.async { 152 do { 153 try updatesService.database.setExtraParam(key: key, value: value, withScopeKey: scopeKey) 154 promise.resolve(nil) 155 } catch { 156 promise.reject(error) 157 } 158 } 159 } 160 161 AsyncFunction("readLogEntriesAsync") { (maxAge: Int) -> [[String: Any]] in 162 // maxAge is in milliseconds, convert to seconds 163 do { 164 return try UpdatesLogReader().getLogEntries(newerThan: Date(timeIntervalSinceNow: TimeInterval(-1 * (maxAge / 1000)))) 165 } catch { 166 throw Exception(name: "ERR_UPDATES_READ_LOGS", description: error.localizedDescription) 167 } 168 } 169 170 AsyncFunction("clearLogEntriesAsync") { (promise: Promise) in 171 UpdatesLogReader().purgeLogEntries(olderThan: Date()) { error in 172 guard let error = error else { 173 promise.resolve(nil) 174 return 175 } 176 promise.reject("ERR_UPDATES_READ_LOGS", error.localizedDescription) 177 } 178 } 179 180 AsyncFunction("fetchUpdateAsync") { (promise: Promise) in 181 let maybeIsCheckForUpdateEnabled: Bool? = updatesService?.canCheckForUpdateAndFetchUpdate ?? true 182 guard maybeIsCheckForUpdateEnabled ?? false else { 183 promise.reject("ERR_UPDATES_FETCH", "fetchUpdateAsync() is not enabled") 184 return 185 } 186 UpdatesUtils.fetchUpdate { result in 187 if result["message"] != nil { 188 guard let message = result["message"] as? String else { 189 promise.reject("ERR_UPDATES_FETCH", "") 190 return 191 } 192 promise.reject("ERR_UPDATES_FETCH", message) 193 return 194 } else { 195 promise.resolve(result) 196 } 197 } 198 } 199 } 200 // swiftlint:enable cyclomatic_complexity 201 } 202 203 // swiftlint:enable closure_body_length 204 // swiftlint:enable superfluous_else 205