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 27 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 ]) 106 return 107 } 108 if result["isRollBackToEmbedded"] != nil { 109 promise.resolve([ 110 "isAvailable": false, 111 "isRollBackToEmbedded": result["isRollBackToEmbedded"] 112 ]) 113 return 114 } 115 promise.resolve(["isAvailable": false]) 116 } 117 } 118 119 AsyncFunction("getExtraParamsAsync") { (promise: Promise) in 120 guard let updatesService = updatesService, 121 let config = updatesService.config, 122 config.isEnabled else { 123 throw UpdatesDisabledException() 124 } 125 126 guard let scopeKey = config.scopeKey else { 127 throw Exception(name: "ERR_UPDATES_SCOPE_KEY", description: "Muse have scopeKey in config") 128 } 129 130 updatesService.database.databaseQueue.async { 131 do { 132 promise.resolve(try updatesService.database.extraParams(withScopeKey: scopeKey)) 133 } catch { 134 promise.reject(error) 135 } 136 } 137 } 138 139 AsyncFunction("setExtraParamAsync") { (key: String, value: String?, promise: Promise) in 140 guard let updatesService = updatesService, 141 let config = updatesService.config, 142 config.isEnabled else { 143 throw UpdatesDisabledException() 144 } 145 146 guard let scopeKey = config.scopeKey else { 147 throw Exception(name: "ERR_UPDATES_SCOPE_KEY", description: "Muse have scopeKey in config") 148 } 149 150 updatesService.database.databaseQueue.async { 151 do { 152 try updatesService.database.setExtraParam(key: key, value: value, withScopeKey: scopeKey) 153 promise.resolve(nil) 154 } catch { 155 promise.reject(error) 156 } 157 } 158 } 159 160 AsyncFunction("readLogEntriesAsync") { (maxAge: Int) -> [[String: Any]] in 161 // maxAge is in milliseconds, convert to seconds 162 do { 163 return try UpdatesLogReader().getLogEntries(newerThan: Date(timeIntervalSinceNow: TimeInterval(-1 * (maxAge / 1000)))) 164 } catch { 165 throw Exception(name: "ERR_UPDATES_READ_LOGS", description: error.localizedDescription) 166 } 167 } 168 169 AsyncFunction("clearLogEntriesAsync") { (promise: Promise) in 170 UpdatesLogReader().purgeLogEntries(olderThan: Date()) { error in 171 guard let error = error else { 172 promise.resolve(nil) 173 return 174 } 175 promise.reject("ERR_UPDATES_READ_LOGS", error.localizedDescription) 176 } 177 } 178 179 AsyncFunction("fetchUpdateAsync") { (promise: Promise) in 180 let maybeIsCheckForUpdateEnabled: Bool? = updatesService?.canCheckForUpdateAndFetchUpdate ?? true 181 guard maybeIsCheckForUpdateEnabled ?? false else { 182 promise.reject("ERR_UPDATES_FETCH", "fetchUpdateAsync() is not enabled") 183 return 184 } 185 UpdatesUtils.fetchUpdate { result in 186 if result["message"] != nil { 187 guard let message = result["message"] as? String else { 188 promise.reject("ERR_UPDATES_FETCH", "") 189 return 190 } 191 promise.reject("ERR_UPDATES_FETCH", message) 192 return 193 } else { 194 promise.resolve(result) 195 } 196 } 197 } 198 } 199 // swiftlint:enable cyclomatic_complexity 200 } 201 202 // swiftlint:enable closure_body_length 203 // swiftlint:enable superfluous_else 204