@capgo/capacitor-updater 8.0.0 → 8.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/CapgoCapacitorUpdater.podspec +2 -2
  2. package/Package.swift +35 -0
  3. package/README.md +667 -206
  4. package/android/build.gradle +16 -11
  5. package/android/proguard-rules.pro +28 -0
  6. package/android/src/main/AndroidManifest.xml +0 -1
  7. package/android/src/main/java/ee/forgr/capacitor_updater/BundleInfo.java +134 -194
  8. package/android/src/main/java/ee/forgr/capacitor_updater/BundleStatus.java +23 -23
  9. package/android/src/main/java/ee/forgr/capacitor_updater/Callback.java +13 -0
  10. package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdater.java +967 -1027
  11. package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdaterPlugin.java +1283 -1180
  12. package/android/src/main/java/ee/forgr/capacitor_updater/CryptoCipherV2.java +276 -0
  13. package/android/src/main/java/ee/forgr/capacitor_updater/DataManager.java +28 -0
  14. package/android/src/main/java/ee/forgr/capacitor_updater/DelayCondition.java +45 -48
  15. package/android/src/main/java/ee/forgr/capacitor_updater/DelayUntilNext.java +4 -4
  16. package/android/src/main/java/ee/forgr/capacitor_updater/DownloadService.java +440 -113
  17. package/android/src/main/java/ee/forgr/capacitor_updater/DownloadWorkerManager.java +101 -0
  18. package/android/src/main/java/ee/forgr/capacitor_updater/InternalUtils.java +32 -0
  19. package/dist/docs.json +1316 -473
  20. package/dist/esm/definitions.d.ts +518 -248
  21. package/dist/esm/definitions.js.map +1 -1
  22. package/dist/esm/index.d.ts +2 -2
  23. package/dist/esm/index.js +4 -4
  24. package/dist/esm/index.js.map +1 -1
  25. package/dist/esm/web.d.ts +25 -41
  26. package/dist/esm/web.js +67 -35
  27. package/dist/esm/web.js.map +1 -1
  28. package/dist/plugin.cjs.js +67 -35
  29. package/dist/plugin.cjs.js.map +1 -1
  30. package/dist/plugin.js +67 -35
  31. package/dist/plugin.js.map +1 -1
  32. package/ios/Plugin/CapacitorUpdater.swift +736 -361
  33. package/ios/Plugin/CapacitorUpdaterPlugin.swift +436 -136
  34. package/ios/Plugin/CryptoCipherV2.swift +310 -0
  35. package/ios/Plugin/InternalUtils.swift +258 -0
  36. package/package.json +33 -29
  37. package/android/src/main/java/ee/forgr/capacitor_updater/CryptoCipher.java +0 -153
  38. package/ios/Plugin/CapacitorUpdaterPlugin.h +0 -10
  39. package/ios/Plugin/CapacitorUpdaterPlugin.m +0 -27
  40. package/ios/Plugin/CryptoCipher.swift +0 -240
@@ -13,60 +13,173 @@ import Version
13
13
  * here: https://capacitorjs.com/docs/plugins/ios
14
14
  */
15
15
  @objc(CapacitorUpdaterPlugin)
16
- public class CapacitorUpdaterPlugin: CAPPlugin {
17
- private var implementation = CapacitorUpdater()
18
- private let PLUGIN_VERSION: String = "8.0.0"
19
- static let updateUrlDefault = "https://api.capgo.app/updates"
20
- static let statsUrlDefault = "https://api.capgo.app/stats"
21
- static let channelUrlDefault = "https://api.capgo.app/channel_self"
16
+ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
17
+ public let identifier = "CapacitorUpdaterPlugin"
18
+ public let jsName = "CapacitorUpdater"
19
+ public let pluginMethods: [CAPPluginMethod] = [
20
+ CAPPluginMethod(name: "download", returnType: CAPPluginReturnPromise),
21
+ CAPPluginMethod(name: "setUpdateUrl", returnType: CAPPluginReturnPromise),
22
+ CAPPluginMethod(name: "setStatsUrl", returnType: CAPPluginReturnPromise),
23
+ CAPPluginMethod(name: "setChannelUrl", returnType: CAPPluginReturnPromise),
24
+ CAPPluginMethod(name: "set", returnType: CAPPluginReturnPromise),
25
+ CAPPluginMethod(name: "list", returnType: CAPPluginReturnPromise),
26
+ CAPPluginMethod(name: "delete", returnType: CAPPluginReturnPromise),
27
+ CAPPluginMethod(name: "reset", returnType: CAPPluginReturnPromise),
28
+ CAPPluginMethod(name: "current", returnType: CAPPluginReturnPromise),
29
+ CAPPluginMethod(name: "reload", returnType: CAPPluginReturnPromise),
30
+ CAPPluginMethod(name: "notifyAppReady", returnType: CAPPluginReturnPromise),
31
+ CAPPluginMethod(name: "setDelay", returnType: CAPPluginReturnPromise),
32
+ CAPPluginMethod(name: "setMultiDelay", returnType: CAPPluginReturnPromise),
33
+ CAPPluginMethod(name: "cancelDelay", returnType: CAPPluginReturnPromise),
34
+ CAPPluginMethod(name: "getLatest", returnType: CAPPluginReturnPromise),
35
+ CAPPluginMethod(name: "setChannel", returnType: CAPPluginReturnPromise),
36
+ CAPPluginMethod(name: "unsetChannel", returnType: CAPPluginReturnPromise),
37
+ CAPPluginMethod(name: "getChannel", returnType: CAPPluginReturnPromise),
38
+ CAPPluginMethod(name: "setCustomId", returnType: CAPPluginReturnPromise),
39
+ CAPPluginMethod(name: "getDeviceId", returnType: CAPPluginReturnPromise),
40
+ CAPPluginMethod(name: "getPluginVersion", returnType: CAPPluginReturnPromise),
41
+ CAPPluginMethod(name: "next", returnType: CAPPluginReturnPromise),
42
+ CAPPluginMethod(name: "isAutoUpdateEnabled", returnType: CAPPluginReturnPromise),
43
+ CAPPluginMethod(name: "getBuiltinVersion", returnType: CAPPluginReturnPromise),
44
+ CAPPluginMethod(name: "isAutoUpdateAvailable", returnType: CAPPluginReturnPromise),
45
+ CAPPluginMethod(name: "getNextBundle", returnType: CAPPluginReturnPromise)
46
+ ]
47
+ public var implementation = CapacitorUpdater()
48
+ private let PLUGIN_VERSION: String = "8.0.1"
49
+ static let updateUrlDefault = "https://plugin.capgo.app/updates"
50
+ static let statsUrlDefault = "https://plugin.capgo.app/stats"
51
+ static let channelUrlDefault = "https://plugin.capgo.app/channel_self"
22
52
  let DELAY_CONDITION_PREFERENCES = ""
23
53
  private var updateUrl = ""
24
54
  private var statsUrl = ""
25
- private var defaultPrivateKey = "-----BEGIN RSA PRIVATE KEY-----\nMIIEpQIBAAKCAQEA4pW9olT0FBXXivRCzd3xcImlWZrqkwcF2xTkX/FwXmj9eh9H\nkBLrsQmfsC+PJisRXIOGq6a0z3bsGq6jBpp3/Jr9jiaW5VuPGaKeMaZZBRvi/N5f\nIMG3hZXSOcy0IYg+E1Q7RkYO1xq5GLHseqG+PXvJsNe4R8R/Bmd/ngq0xh/cvcrH\nHpXwO0Aj9tfprlb+rHaVV79EkVRWYPidOLnK1n0EFHFJ1d/MyDIp10TEGm2xHpf/\nBrlb1an8wXEuzoC0DgYaczgTjovwR+ewSGhSHJliQdM0Qa3o1iN87DldWtydImMs\nPjJ3DUwpsjAMRe5X8Et4+udFW2ciYnQo9H0CkwIDAQABAoIBAQCtjlMV/4qBxAU4\nu0ZcWA9yywwraX0aJ3v1xrfzQYV322Wk4Ea5dbSxA5UcqCE29DA1M824t1Wxv/6z\npWbcTP9xLuresnJMtmgTE7umfiubvTONy2sENT20hgDkIwcq1CfwOEm61zjQzPhQ\nkSB5AmEsyR/BZEsUNc+ygR6AWOUFB7tj4yMc32LOTWSbE/znnF2BkmlmnQykomG1\n2oVqM3lUFP7+m8ux1O7scO6IMts+Z/eFXjWfxpbebUSvSIR83GXPQZ34S/c0ehOg\nyHdmCSOel1r3VvInMe+30j54Jr+Ml/7Ee6axiwyE2e/bd85MsK9sVdp0OtelXaqA\nOZZqWvN5AoGBAP2Hn3lSq+a8GsDH726mHJw60xM0LPbVJTYbXsmQkg1tl3NKJTMM\nQqz41+5uys+phEgLHI9gVJ0r+HaGHXnJ4zewlFjsudstb/0nfctUvTqnhEhfNo9I\ny4kufVKPRF3sMEeo7CDVJs4GNBLycEyIBy6Mbv0VcO7VaZqggRwu4no9AoGBAOTK\n6NWYs1BWlkua2wmxexGOzehNGedInp0wGr2l4FDayWjkZLqvB+nNXUQ63NdHlSs4\nWB2Z1kQXZxVaI2tPYexGUKXEo2uFob63uflbuE029ovDXIIPFTPtGNdNXwhHT5a+\nPhmy3sMc+s2BSNM5qaNmfxQxhdd6gRU6oikE+c0PAoGAMn3cKNFqIt27hkFLUgIL\nGKIuf1iYy9/PNWNmEUaVj88PpopRtkTu0nwMpROzmH/uNFriKTvKHjMvnItBO4wV\nkHW+VadvrFL0Rrqituf9d7z8/1zXBNo+juePVe3qc7oiM2NVA4Tv4YAixtM5wkQl\nCgQ15nlqsGYYTg9BJ1e/CxECgYEAjEYPzO2reuUrjr0p8F59ev1YJ0YmTJRMk0ks\nC/yIdGo/tGzbiU3JB0LfHPcN8Xu07GPGOpfYM7U5gXDbaG6qNgfCaHAQVdr/mQPi\nJQ1kCQtay8QCkscWk9iZM1//lP7LwDtxraXqSCwbZSYP9VlUNZeg8EuQqNU2EUL6\nqzWexmcCgYEA0prUGNBacraTYEknB1CsbP36UPWsqFWOvevlz+uEC5JPxPuW5ZHh\nSQN7xl6+PHyjPBM7ttwPKyhgLOVTb3K7ex/PXnudojMUK5fh7vYfChVTSlx2p6r0\nDi58PdD+node08cJH+ie0Yphp7m+D4+R9XD0v0nEvnu4BtAW6DrJasw=\n-----END RSA PRIVATE KEY-----\n"
26
55
  private var backgroundTaskID: UIBackgroundTaskIdentifier = UIBackgroundTaskIdentifier.invalid
27
56
  private var currentVersionNative: Version = "0.0.0"
28
57
  private var autoUpdate = false
29
58
  private var appReadyTimeout = 10000
30
59
  private var appReadyCheck: DispatchWorkItem?
31
60
  private var resetWhenUpdate = true
61
+ private var directUpdate = false
32
62
  private var autoDeleteFailed = false
33
63
  private var autoDeletePrevious = false
64
+ private var keepUrlPathAfterReload = false
34
65
  private var backgroundWork: DispatchWorkItem?
35
66
  private var taskRunning = false
67
+ private var periodCheckDelay = 0
68
+ let semaphoreReady = DispatchSemaphore(value: 0)
36
69
 
37
70
  override public func load() {
38
- print("\(self.implementation.TAG) init for device \(self.implementation.deviceID)")
71
+ #if targetEnvironment(simulator)
72
+ print("\(CapacitorUpdater.TAG) ::::: SIMULATOR :::::")
73
+ print("\(CapacitorUpdater.TAG) Application directory: \(NSHomeDirectory())")
74
+ #endif
75
+
76
+ self.semaphoreUp()
77
+ self.implementation.deviceID = (UserDefaults.standard.string(forKey: "appUUID") ?? UUID().uuidString).lowercased()
78
+ UserDefaults.standard.set( self.implementation.deviceID, forKey: "appUUID")
79
+ UserDefaults.standard.synchronize()
80
+ print("\(CapacitorUpdater.TAG) init for device \(self.implementation.deviceID)")
81
+ guard let versionName = getConfig().getString("version", Bundle.main.versionName) else {
82
+ print("\(CapacitorUpdater.TAG) Cannot get version name")
83
+ // crash the app
84
+ fatalError("Cannot get version name")
85
+ }
39
86
  do {
40
- currentVersionNative = try Version(getConfig().getString("version", Bundle.main.versionName ?? "0.0.0")!)
87
+ currentVersionNative = try Version(versionName)
41
88
  } catch {
42
- print("\(self.implementation.TAG) Cannot get version native \(currentVersionNative)")
89
+ print("\(CapacitorUpdater.TAG) Cannot parse versionName \(versionName)")
43
90
  }
44
- print("\(self.implementation.TAG) version native \(self.currentVersionNative.description)")
45
- implementation.versionName = getConfig().getString("version", Bundle.main.versionName)!
91
+ print("\(CapacitorUpdater.TAG) version native \(self.currentVersionNative.description)")
92
+ implementation.versionBuild = getConfig().getString("version", Bundle.main.versionName)!
46
93
  autoDeleteFailed = getConfig().getBoolean("autoDeleteFailed", true)
47
94
  autoDeletePrevious = getConfig().getBoolean("autoDeletePrevious", true)
95
+ keepUrlPathAfterReload = getConfig().getBoolean("keepUrlPathAfterReload", false)
96
+ directUpdate = getConfig().getBoolean("directUpdate", false)
48
97
  updateUrl = getConfig().getString("updateUrl", CapacitorUpdaterPlugin.updateUrlDefault)!
49
98
  autoUpdate = getConfig().getBoolean("autoUpdate", true)
50
99
  appReadyTimeout = getConfig().getInt("appReadyTimeout", 10000)
100
+ implementation.timeout = Double(getConfig().getInt("responseTimeout", 20))
51
101
  resetWhenUpdate = getConfig().getBoolean("resetWhenUpdate", true)
102
+ let periodCheckDelayValue = getConfig().getInt("periodCheckDelay", 0)
103
+ if periodCheckDelayValue >= 0 && periodCheckDelayValue > 600 {
104
+ periodCheckDelay = 600
105
+ } else {
106
+ periodCheckDelay = periodCheckDelayValue
107
+ }
52
108
 
53
- implementation.privateKey = getConfig().getString("privateKey", self.defaultPrivateKey)!
54
- implementation.notifyDownload = notifyDownload
109
+ implementation.publicKey = getConfig().getString("publicKey", "")!
110
+ implementation.notifyDownloadRaw = notifyDownload
55
111
  implementation.PLUGIN_VERSION = self.PLUGIN_VERSION
56
112
  let config = (self.bridge?.viewController as? CAPBridgeViewController)?.instanceDescriptor().legacyConfig
57
- if config?["appId"] != nil {
58
- implementation.appId = config?["appId"] as! String
113
+ implementation.appId = Bundle.main.infoDictionary?["CFBundleIdentifier"] as? String ?? ""
114
+ implementation.appId = config?["appId"] as? String ?? implementation.appId
115
+ implementation.appId = getConfig().getString("appId", implementation.appId)!
116
+ if implementation.appId == "" {
117
+ fatalError("appId is missing in capacitor.config.json or plugin config, and cannot be retrieved from the native app, please add it globally or in the plugin config")
59
118
  }
119
+ print("\(CapacitorUpdater.TAG) appId \(implementation.appId)")
60
120
  implementation.statsUrl = getConfig().getString("statsUrl", CapacitorUpdaterPlugin.statsUrlDefault)!
61
121
  implementation.channelUrl = getConfig().getString("channelUrl", CapacitorUpdaterPlugin.channelUrlDefault)!
122
+ implementation.defaultChannel = getConfig().getString("defaultChannel", "")!
123
+ self.implementation.autoReset()
124
+
62
125
  if resetWhenUpdate {
63
126
  self.cleanupObsoleteVersions()
64
127
  }
128
+
129
+ // Load the server
130
+ // This is very much swift specific, android does not do that
131
+ // In android we depend on the serverBasePath capacitor property
132
+ // In IOS we do not. Instead during the plugin initialization we try to call setServerBasePath
133
+ // The idea is to prevent having to store the bundle in 2 locations for hot reload and persisten storage
134
+ // According to martin it is not possible to use serverBasePath on ios in a way that allows us to store the bundle once
135
+
136
+ if !self.initialLoad() {
137
+ print("\(CapacitorUpdater.TAG) unable to force reload, the plugin might fallback to the builtin version")
138
+ }
139
+
65
140
  let nc = NotificationCenter.default
66
141
  nc.addObserver(self, selector: #selector(appMovedToBackground), name: UIApplication.didEnterBackgroundNotification, object: nil)
67
142
  nc.addObserver(self, selector: #selector(appMovedToForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
68
143
  nc.addObserver(self, selector: #selector(appKilled), name: UIApplication.willTerminateNotification, object: nil)
69
144
  self.appMovedToForeground()
145
+ self.checkForUpdateAfterDelay()
146
+ }
147
+
148
+ private func initialLoad() -> Bool {
149
+ guard let bridge = self.bridge else { return false }
150
+
151
+ let id = self.implementation.getCurrentBundleId()
152
+ var dest: URL
153
+ if BundleInfo.ID_BUILTIN == id {
154
+ dest = Bundle.main.resourceURL!.appendingPathComponent("public")
155
+ } else {
156
+ dest = self.implementation.getBundleDirectory(id: id)
157
+ }
158
+
159
+ if !FileManager.default.fileExists(atPath: dest.path) {
160
+ print("\(CapacitorUpdater.TAG) Initial load fail - file at path \(dest.path) doesn't exist. Defaulting to buildin!! \(id)")
161
+ dest = Bundle.main.resourceURL!.appendingPathComponent("public")
162
+ }
163
+
164
+ print("\(CapacitorUpdater.TAG) Initial load \(id)")
165
+ // We don't use the viewcontroller here as it does not work during the initial load state
166
+ bridge.setServerBasePath(dest.path)
167
+ return true
168
+ }
169
+
170
+ private func semaphoreWait(waitTime: Int) {
171
+ print("\(CapacitorUpdater.TAG) semaphoreWait \(waitTime)")
172
+ _ = semaphoreReady.wait(timeout: .now() + .milliseconds(waitTime))
173
+ }
174
+
175
+ private func semaphoreUp() {
176
+ DispatchQueue.global().async {
177
+ self.semaphoreWait(waitTime: 0)
178
+ }
179
+ }
180
+
181
+ private func semaphoreDown() {
182
+ semaphoreReady.signal()
70
183
  }
71
184
 
72
185
  private func cleanupObsoleteVersions() {
@@ -74,16 +187,16 @@ public class CapacitorUpdaterPlugin: CAPPlugin {
74
187
  do {
75
188
  LatestVersionNative = try Version(UserDefaults.standard.string(forKey: "LatestVersionNative") ?? "0.0.0")
76
189
  } catch {
77
- print("\(self.implementation.TAG) Cannot get version native \(currentVersionNative)")
190
+ print("\(CapacitorUpdater.TAG) Cannot get version native \(currentVersionNative)")
78
191
  }
79
192
  if LatestVersionNative != "0.0.0" && self.currentVersionNative.description != LatestVersionNative.description {
80
193
  _ = self._reset(toLastSuccessful: false)
81
194
  let res = implementation.list()
82
195
  res.forEach { version in
83
- print("\(self.implementation.TAG) Deleting obsolete bundle: \(version)")
196
+ print("\(CapacitorUpdater.TAG) Deleting obsolete bundle: \(version.getId())")
84
197
  let res = implementation.delete(id: version.getId())
85
198
  if !res {
86
- print("\(self.implementation.TAG) Delete failed, id \(version.getId()) doesn't exist")
199
+ print("\(CapacitorUpdater.TAG) Delete failed, id \(version.getId()) doesn't exist")
87
200
  }
88
201
  }
89
202
  }
@@ -91,17 +204,66 @@ public class CapacitorUpdaterPlugin: CAPPlugin {
91
204
  UserDefaults.standard.synchronize()
92
205
  }
93
206
 
94
- @objc func notifyDownload(id: String, percent: Int) {
207
+ @objc func notifyDownload(id: String, percent: Int, ignoreMultipleOfTen: Bool = false) {
95
208
  let bundle = self.implementation.getBundleInfo(id: id)
96
209
  self.notifyListeners("download", data: ["percent": percent, "bundle": bundle.toJSON()])
97
210
  if percent == 100 {
98
211
  self.notifyListeners("downloadComplete", data: ["bundle": bundle.toJSON()])
99
212
  self.implementation.sendStats(action: "download_complete", versionName: bundle.getVersionName())
100
- } else if percent.isMultiple(of: 10) {
213
+ } else if percent.isMultiple(of: 10) || ignoreMultipleOfTen {
101
214
  self.implementation.sendStats(action: "download_\(percent)", versionName: bundle.getVersionName())
102
215
  }
103
216
  }
104
217
 
218
+ @objc func setUpdateUrl(_ call: CAPPluginCall) {
219
+ if !getConfig().getBoolean("allowModifyUrl", false) {
220
+ print("\(CapacitorUpdater.TAG) setUpdateUrl called without allowModifyUrl")
221
+ call.reject("setUpdateUrl called without allowModifyUrl set allowModifyUrl in your config to true to allow it")
222
+ return
223
+ }
224
+ guard let url = call.getString("url") else {
225
+ print("\(CapacitorUpdater.TAG) setUpdateUrl called without url")
226
+ call.reject("setUpdateUrl called without url")
227
+ return
228
+ }
229
+ self.updateUrl = url
230
+ call.resolve()
231
+ }
232
+
233
+ @objc func setStatsUrl(_ call: CAPPluginCall) {
234
+ if !getConfig().getBoolean("allowModifyUrl", false) {
235
+ print("\(CapacitorUpdater.TAG) setStatsUrl called without allowModifyUrl")
236
+ call.reject("setStatsUrl called without allowModifyUrl set allowModifyUrl in your config to true to allow it")
237
+ return
238
+ }
239
+ guard let url = call.getString("url") else {
240
+ print("\(CapacitorUpdater.TAG) setStatsUrl called without url")
241
+ call.reject("setStatsUrl called without url")
242
+ return
243
+ }
244
+ self.statsUrl = url
245
+ call.resolve()
246
+ }
247
+
248
+ @objc func setChannelUrl(_ call: CAPPluginCall) {
249
+ if !getConfig().getBoolean("allowModifyUrl", false) {
250
+ print("\(CapacitorUpdater.TAG) setChannelUrl called without allowModifyUrl")
251
+ call.reject("setChannelUrl called without allowModifyUrl set allowModifyUrl in your config to true to allow it")
252
+ return
253
+ }
254
+ guard let url = call.getString("url") else {
255
+ print("\(CapacitorUpdater.TAG) setChannelUrl called without url")
256
+ call.reject("setChannelUrl called without url")
257
+ return
258
+ }
259
+ self.implementation.channelUrl = url
260
+ call.resolve()
261
+ }
262
+
263
+ @objc func getBuiltinVersion(_ call: CAPPluginCall) {
264
+ call.resolve(["version": implementation.versionBuild])
265
+ }
266
+
105
267
  @objc func getDeviceId(_ call: CAPPluginCall) {
106
268
  call.resolve(["deviceId": implementation.deviceID])
107
269
  }
@@ -112,51 +274,82 @@ public class CapacitorUpdaterPlugin: CAPPlugin {
112
274
 
113
275
  @objc func download(_ call: CAPPluginCall) {
114
276
  guard let urlString = call.getString("url") else {
115
- print("\(self.implementation.TAG) Download called without url")
277
+ print("\(CapacitorUpdater.TAG) Download called without url")
116
278
  call.reject("Download called without url")
117
279
  return
118
280
  }
119
281
  guard let version = call.getString("version") else {
120
- print("\(self.implementation.TAG) Download called without version")
282
+ print("\(CapacitorUpdater.TAG) Download called without version")
121
283
  call.reject("Download called without version")
122
284
  return
123
285
  }
286
+
124
287
  let sessionKey = call.getString("sessionKey", "")
125
- let checksum = call.getString("checksum", "")
288
+ var checksum = call.getString("checksum", "")
126
289
  let url = URL(string: urlString)
127
- print("\(self.implementation.TAG) Downloading \(String(describing: url))")
290
+ print("\(CapacitorUpdater.TAG) Downloading \(String(describing: url))")
128
291
  DispatchQueue.global(qos: .background).async {
129
292
  do {
130
293
  let next = try self.implementation.download(url: url!, version: version, sessionKey: sessionKey)
131
- if checksum != "" && next.getChecksum() != checksum {
132
- print("\(self.implementation.TAG) Error checksum", next.getChecksum(), checksum)
294
+ checksum = try self.implementation.decryptChecksum(checksum: checksum, version: version)
295
+ if (checksum != "" || self.implementation.publicKey != "") && next.getChecksum() != checksum {
296
+ print("\(CapacitorUpdater.TAG) Error checksum", next.getChecksum(), checksum)
133
297
  self.implementation.sendStats(action: "checksum_fail", versionName: next.getVersionName())
134
298
  let id = next.getId()
135
299
  let resDel = self.implementation.delete(id: id)
136
300
  if !resDel {
137
- print("\(self.implementation.TAG) Delete failed, id \(id) doesn't exist")
301
+ print("\(CapacitorUpdater.TAG) Delete failed, id \(id) doesn't exist")
138
302
  }
139
303
  throw ObjectSavableError.checksum
304
+ } else {
305
+ print("\(CapacitorUpdater.TAG) Good checksum", next.getChecksum(), checksum)
140
306
  }
141
307
  self.notifyListeners("updateAvailable", data: ["bundle": next.toJSON()])
142
308
  call.resolve(next.toJSON())
143
309
  } catch {
144
- print("\(self.implementation.TAG) Failed to download from: \(String(describing: url)) \(error.localizedDescription)")
310
+ print("\(CapacitorUpdater.TAG) Failed to download from: \(String(describing: url)) \(error.localizedDescription)")
145
311
  self.notifyListeners("downloadFailed", data: ["version": version])
146
- let current: BundleInfo = self.implementation.getCurrentBundle()
147
- self.implementation.sendStats(action: "download_fail", versionName: current.getVersionName())
312
+ self.implementation.sendStats(action: "download_fail")
148
313
  call.reject("Failed to download from: \(url!)", error.localizedDescription)
149
314
  }
150
315
  }
151
316
  }
152
317
 
153
- private func _reload() -> Bool {
318
+ public func _reload() -> Bool {
154
319
  guard let bridge = self.bridge else { return false }
320
+ self.semaphoreUp()
155
321
  let id = self.implementation.getCurrentBundleId()
156
- let destHot = self.implementation.getPathHot(id: id)
157
- print("\(self.implementation.TAG) Reloading \(id)")
322
+ let dest: URL
323
+ if BundleInfo.ID_BUILTIN == id {
324
+ dest = Bundle.main.resourceURL!.appendingPathComponent("public")
325
+ } else {
326
+ dest = self.implementation.getBundleDirectory(id: id)
327
+ }
328
+ print("\(CapacitorUpdater.TAG) Reloading \(id)")
158
329
  if let vc = bridge.viewController as? CAPBridgeViewController {
159
- vc.setServerBasePath(path: destHot.path)
330
+ guard let capBridge = vc.bridge else {
331
+ print("\(CapacitorUpdater.TAG) Cannot get capBridge")
332
+ return false
333
+ }
334
+ if keepUrlPathAfterReload {
335
+ DispatchQueue.main.async {
336
+ guard let url = vc.webView?.url else {
337
+ print("\(CapacitorUpdater.TAG) vc.webView?.url is null?")
338
+ return
339
+ }
340
+ capBridge.setServerBasePath(dest.path)
341
+ var urlComponents = URLComponents(url: capBridge.config.serverURL, resolvingAgainstBaseURL: false)!
342
+ urlComponents.path = url.path
343
+ if let finalUrl = urlComponents.url {
344
+ _ = vc.webView?.load(URLRequest(url: finalUrl))
345
+ vc.webView?.backForwardList.perform(Selector(("_removeAllItems")))
346
+ }
347
+ }
348
+ } else {
349
+ vc.setServerBasePath(path: dest.path)
350
+
351
+ }
352
+
160
353
  self.checkAppReady()
161
354
  self.notifyListeners("appReloaded", data: [:])
162
355
  return true
@@ -168,20 +361,20 @@ public class CapacitorUpdaterPlugin: CAPPlugin {
168
361
  if self._reload() {
169
362
  call.resolve()
170
363
  } else {
171
- print("\(self.implementation.TAG) Reload failed")
364
+ print("\(CapacitorUpdater.TAG) Reload failed")
172
365
  call.reject("Reload failed")
173
366
  }
174
367
  }
175
368
 
176
369
  @objc func next(_ call: CAPPluginCall) {
177
370
  guard let id = call.getString("id") else {
178
- print("\(self.implementation.TAG) Next called without id")
371
+ print("\(CapacitorUpdater.TAG) Next called without id")
179
372
  call.reject("Next called without id")
180
373
  return
181
374
  }
182
- print("\(self.implementation.TAG) Setting next active id \(id)")
375
+ print("\(CapacitorUpdater.TAG) Setting next active id \(id)")
183
376
  if !self.implementation.setNextBundle(next: id) {
184
- print("\(self.implementation.TAG) Set next version failed. id \(id) does not exist.")
377
+ print("\(CapacitorUpdater.TAG) Set next version failed. id \(id) does not exist.")
185
378
  call.reject("Set next version failed. id \(id) does not exist.")
186
379
  } else {
187
380
  call.resolve(self.implementation.getBundleInfo(id: id).toJSON())
@@ -190,14 +383,14 @@ public class CapacitorUpdaterPlugin: CAPPlugin {
190
383
 
191
384
  @objc func set(_ call: CAPPluginCall) {
192
385
  guard let id = call.getString("id") else {
193
- print("\(self.implementation.TAG) Set called without id")
386
+ print("\(CapacitorUpdater.TAG) Set called without id")
194
387
  call.reject("Set called without id")
195
388
  return
196
389
  }
197
390
  let res = implementation.set(id: id)
198
- print("\(self.implementation.TAG) Set active bundle: \(id)")
391
+ print("\(CapacitorUpdater.TAG) Set active bundle: \(id)")
199
392
  if !res {
200
- print("\(self.implementation.TAG) Bundle successfully set to: \(id) ")
393
+ print("\(CapacitorUpdater.TAG) Bundle successfully set to: \(id) ")
201
394
  call.reject("Update failed, id \(id) doesn't exist")
202
395
  } else {
203
396
  self.reload(call)
@@ -206,7 +399,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin {
206
399
 
207
400
  @objc func delete(_ call: CAPPluginCall) {
208
401
  guard let id = call.getString("id") else {
209
- print("\(self.implementation.TAG) Delete called without version")
402
+ print("\(CapacitorUpdater.TAG) Delete called without version")
210
403
  call.reject("Delete called without id")
211
404
  return
212
405
  }
@@ -214,13 +407,14 @@ public class CapacitorUpdaterPlugin: CAPPlugin {
214
407
  if res {
215
408
  call.resolve()
216
409
  } else {
217
- print("\(self.implementation.TAG) Delete failed, id \(id) doesn't exist")
218
- call.reject("Delete failed, id \(id) doesn't exist")
410
+ print("\(CapacitorUpdater.TAG) Delete failed, id \(id) doesn't exist or it cannot be deleted (perhaps it is the 'next' bundle)")
411
+ call.reject("Delete failed, id \(id) does not exist or it cannot be deleted (perhaps it is the 'next' bundle)")
219
412
  }
220
413
  }
221
414
 
222
415
  @objc func list(_ call: CAPPluginCall) {
223
- let res = implementation.list()
416
+ let raw = call.getBool("raw", false)
417
+ let res = implementation.list(raw: raw)
224
418
  var resArr: [[String: String]] = []
225
419
  for v in res {
226
420
  resArr.append(v.toJSON())
@@ -231,27 +425,51 @@ public class CapacitorUpdaterPlugin: CAPPlugin {
231
425
  }
232
426
 
233
427
  @objc func getLatest(_ call: CAPPluginCall) {
428
+ let channel = call.getString("channel")
234
429
  DispatchQueue.global(qos: .background).async {
235
- let res = self.implementation.getLatest(url: URL(string: self.updateUrl)!)
430
+ let res = self.implementation.getLatest(url: URL(string: self.updateUrl)!, channel: channel)
236
431
  if res.error != nil {
237
432
  call.reject( res.error!)
433
+ } else if res.message != nil {
434
+ call.reject( res.message!)
238
435
  } else {
239
436
  call.resolve(res.toDict())
240
437
  }
241
438
  }
242
439
  }
243
440
 
441
+ @objc func unsetChannel(_ call: CAPPluginCall) {
442
+ let triggerAutoUpdate = call.getBool("triggerAutoUpdate", false)
443
+ DispatchQueue.global(qos: .background).async {
444
+ let res = self.implementation.unsetChannel()
445
+ if res.error != "" {
446
+ call.reject(res.error)
447
+ } else {
448
+ if self._isAutoUpdateEnabled() && triggerAutoUpdate {
449
+ print("\(CapacitorUpdater.TAG) Calling autoupdater after channel change!")
450
+ self.backgroundDownload()
451
+ }
452
+ call.resolve(res.toDict())
453
+ }
454
+ }
455
+ }
456
+
244
457
  @objc func setChannel(_ call: CAPPluginCall) {
245
458
  guard let channel = call.getString("channel") else {
246
- print("\(self.implementation.TAG) setChannel called without channel")
459
+ print("\(CapacitorUpdater.TAG) setChannel called without channel")
247
460
  call.reject("setChannel called without channel")
248
461
  return
249
462
  }
463
+ let triggerAutoUpdate = call.getBool("triggerAutoUpdate") ?? false
250
464
  DispatchQueue.global(qos: .background).async {
251
465
  let res = self.implementation.setChannel(channel: channel)
252
466
  if res.error != "" {
253
467
  call.reject(res.error)
254
468
  } else {
469
+ if self._isAutoUpdateEnabled() && triggerAutoUpdate {
470
+ print("\(CapacitorUpdater.TAG) Calling autoupdater after channel change!")
471
+ self.backgroundDownload()
472
+ }
255
473
  call.resolve(res.toDict())
256
474
  }
257
475
  }
@@ -269,7 +487,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin {
269
487
  }
270
488
  @objc func setCustomId(_ call: CAPPluginCall) {
271
489
  guard let customId = call.getString("customId") else {
272
- print("\(self.implementation.TAG) setCustomId called without customId")
490
+ print("\(CapacitorUpdater.TAG) setCustomId called without customId")
273
491
  call.reject("setCustomId called without customId")
274
492
  return
275
493
  }
@@ -285,11 +503,11 @@ public class CapacitorUpdaterPlugin: CAPPlugin {
285
503
  // If developer wants to reset to the last successful bundle, and that bundle is not
286
504
  // the built-in bundle, set it as the bundle to use and reload.
287
505
  if toLastSuccessful && !fallback.isBuiltin() {
288
- print("\(self.implementation.TAG) Resetting to: \(fallback.toString())")
506
+ print("\(CapacitorUpdater.TAG) Resetting to: \(fallback.toString())")
289
507
  return self.implementation.set(bundle: fallback) && self._reload()
290
508
  }
291
509
 
292
- print("\(self.implementation.TAG) Resetting to builtin version")
510
+ print("\(CapacitorUpdater.TAG) Resetting to builtin version")
293
511
 
294
512
  // Otherwise, reset back to the built-in bundle and reload.
295
513
  self.implementation.reset()
@@ -304,8 +522,8 @@ public class CapacitorUpdaterPlugin: CAPPlugin {
304
522
  if self._reset(toLastSuccessful: toLastSuccessful) {
305
523
  call.resolve()
306
524
  } else {
307
- print("\(self.implementation.TAG) Reset failed")
308
- call.reject("\(self.implementation.TAG) Reset failed")
525
+ print("\(CapacitorUpdater.TAG) Reset failed")
526
+ call.reject("\(CapacitorUpdater.TAG) Reset failed")
309
527
  }
310
528
  }
311
529
 
@@ -318,15 +536,16 @@ public class CapacitorUpdaterPlugin: CAPPlugin {
318
536
  }
319
537
 
320
538
  @objc func notifyAppReady(_ call: CAPPluginCall) {
321
- let version = self.implementation.getCurrentBundle()
322
- self.implementation.setSuccess(bundle: version, autoDeletePrevious: self.autoDeletePrevious)
323
- print("\(self.implementation.TAG) Current bundle loaded successfully. ['notifyAppReady()' was called] \(version.toString())")
324
- call.resolve()
539
+ self.semaphoreDown()
540
+ let bundle = self.implementation.getCurrentBundle()
541
+ self.implementation.setSuccess(bundle: bundle, autoDeletePrevious: self.autoDeletePrevious)
542
+ print("\(CapacitorUpdater.TAG) Current bundle loaded successfully. ['notifyAppReady()' was called] \(bundle.toString())")
543
+ call.resolve(["bundle": bundle.toJSON()])
325
544
  }
326
545
 
327
546
  @objc func setMultiDelay(_ call: CAPPluginCall) {
328
547
  guard let delayConditionList = call.getValue("delayConditions") else {
329
- print("\(self.implementation.TAG) setMultiDelay called without delayCondition")
548
+ print("\(CapacitorUpdater.TAG) setMultiDelay called without delayCondition")
330
549
  call.reject("setMultiDelay called without delayCondition")
331
550
  return
332
551
  }
@@ -342,16 +561,16 @@ public class CapacitorUpdaterPlugin: CAPPlugin {
342
561
  if delayConditions != nil && "" != delayConditions {
343
562
  UserDefaults.standard.set(delayConditions, forKey: DELAY_CONDITION_PREFERENCES)
344
563
  UserDefaults.standard.synchronize()
345
- print("\(self.implementation.TAG) Delay update saved.")
564
+ print("\(CapacitorUpdater.TAG) Delay update saved.")
346
565
  return true
347
566
  } else {
348
- print("\(self.implementation.TAG) Failed to delay update, [Error calling '_setMultiDelay()']")
567
+ print("\(CapacitorUpdater.TAG) Failed to delay update, [Error calling '_setMultiDelay()']")
349
568
  return false
350
569
  }
351
570
  }
352
571
 
353
572
  private func _cancelDelay(source: String) {
354
- print("\(self.implementation.TAG) delay Canceled from \(source)")
573
+ print("\(CapacitorUpdater.TAG) delay Canceled from \(source)")
355
574
  UserDefaults.standard.removeObject(forKey: DELAY_CONDITION_PREFERENCES)
356
575
  UserDefaults.standard.synchronize()
357
576
  }
@@ -377,14 +596,12 @@ public class CapacitorUpdaterPlugin: CAPPlugin {
377
596
  if !killed {
378
597
  self._cancelDelay(source: "background check")
379
598
  }
380
- break
381
599
  case "kill":
382
600
  if killed {
383
601
  self._cancelDelay(source: "kill check")
384
602
  // instant install for kill action
385
603
  self.installNext()
386
604
  }
387
- break
388
605
  case "date":
389
606
  if value != nil && value != "" {
390
607
  let dateFormatter = ISO8601DateFormatter()
@@ -399,7 +616,6 @@ public class CapacitorUpdaterPlugin: CAPPlugin {
399
616
  } else {
400
617
  self._cancelDelay(source: "delayVal absent")
401
618
  }
402
- break
403
619
  case "nativeVersion":
404
620
  if value != nil && value != "" {
405
621
  do {
@@ -413,11 +629,10 @@ public class CapacitorUpdaterPlugin: CAPPlugin {
413
629
  } else {
414
630
  self._cancelDelay(source: "delayVal absent")
415
631
  }
416
- break
417
632
  case .none:
418
- print("\(self.implementation.TAG) _checkCancelDelay switch case none error")
633
+ print("\(CapacitorUpdater.TAG) _checkCancelDelay switch case none error")
419
634
  case .some:
420
- print("\(self.implementation.TAG) _checkCancelDelay switch case some error")
635
+ print("\(CapacitorUpdater.TAG) _checkCancelDelay switch case some error")
421
636
  }
422
637
  }
423
638
  }
@@ -427,7 +642,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin {
427
642
  private func _isAutoUpdateEnabled() -> Bool {
428
643
  let instanceDescriptor = (self.bridge?.viewController as? CAPBridgeViewController)?.instanceDescriptor()
429
644
  if instanceDescriptor?.serverURL != nil {
430
- print("⚠️ \(self.implementation.TAG) AutoUpdate is automatic disabled when serverUrl is set.")
645
+ print("⚠️ \(CapacitorUpdater.TAG) AutoUpdate is automatic disabled when serverUrl is set.")
431
646
  }
432
647
  return self.autoUpdate && self.updateUrl != "" && instanceDescriptor?.serverURL == nil
433
648
  }
@@ -438,12 +653,20 @@ public class CapacitorUpdaterPlugin: CAPPlugin {
438
653
  ])
439
654
  }
440
655
 
656
+ @objc func isAutoUpdateAvailable(_ call: CAPPluginCall) {
657
+ let instanceDescriptor = (self.bridge?.viewController as? CAPBridgeViewController)?.instanceDescriptor()
658
+ let isAvailable = instanceDescriptor?.serverURL == nil
659
+ call.resolve([
660
+ "available": isAvailable
661
+ ])
662
+ }
663
+
441
664
  func checkAppReady() {
442
665
  self.appReadyCheck?.cancel()
443
666
  self.appReadyCheck = DispatchWorkItem(block: {
444
667
  self.DeferredNotifyAppReadyCheck()
445
668
  })
446
- print("\(self.implementation.TAG) Wait for \(self.appReadyTimeout) ms, then check for notifyAppReady")
669
+ print("\(CapacitorUpdater.TAG) Wait for \(self.appReadyTimeout) ms, then check for notifyAppReady")
447
670
  DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(self.appReadyTimeout), execute: self.appReadyCheck!)
448
671
  }
449
672
 
@@ -451,15 +674,15 @@ public class CapacitorUpdaterPlugin: CAPPlugin {
451
674
  // Automatically roll back to fallback version if notifyAppReady has not been called yet
452
675
  let current: BundleInfo = self.implementation.getCurrentBundle()
453
676
  if current.isBuiltin() {
454
- print("\(self.implementation.TAG) Built-in bundle is active. Nothing to do.")
677
+ print("\(CapacitorUpdater.TAG) Built-in bundle is active. We skip the check for notifyAppReady.")
455
678
  return
456
679
  }
457
680
 
458
- print("\(self.implementation.TAG) Current bundle is: \(current.toString())")
681
+ print("\(CapacitorUpdater.TAG) Current bundle is: \(current.toString())")
459
682
 
460
683
  if BundleStatus.SUCCESS.localizedString != current.getStatus() {
461
- print("\(self.implementation.TAG) notifyAppReady was not called, roll back current bundle: \(current.toString())")
462
- print("\(self.implementation.TAG) Did you forget to call 'notifyAppReady()' in your Capacitor App code?")
684
+ print("\(CapacitorUpdater.TAG) notifyAppReady was not called, roll back current bundle: \(current.toString())")
685
+ print("\(CapacitorUpdater.TAG) Did you forget to call 'notifyAppReady()' in your Capacitor App code?")
463
686
  self.notifyListeners("updateFailed", data: [
464
687
  "bundle": current.toJSON()
465
688
  ])
@@ -467,16 +690,16 @@ public class CapacitorUpdaterPlugin: CAPPlugin {
467
690
  self.implementation.setError(bundle: current)
468
691
  _ = self._reset(toLastSuccessful: true)
469
692
  if self.autoDeleteFailed && !current.isBuiltin() {
470
- print("\(self.implementation.TAG) Deleting failing bundle: \(current.toString())")
693
+ print("\(CapacitorUpdater.TAG) Deleting failing bundle: \(current.toString())")
471
694
  let res = self.implementation.delete(id: current.getId(), removeInfo: false)
472
695
  if !res {
473
- print("\(self.implementation.TAG) Delete version deleted: \(current.toString())")
696
+ print("\(CapacitorUpdater.TAG) Delete version deleted: \(current.toString())")
474
697
  } else {
475
- print("\(self.implementation.TAG) Failed to delete failed bundle: \(current.toString())")
698
+ print("\(CapacitorUpdater.TAG) Failed to delete failed bundle: \(current.toString())")
476
699
  }
477
700
  }
478
701
  } else {
479
- print("\(self.implementation.TAG) notifyAppReady was called. This is fine: \(current.toString())")
702
+ print("\(CapacitorUpdater.TAG) notifyAppReady was called. This is fine: \(current.toString())")
480
703
  }
481
704
  }
482
705
 
@@ -490,94 +713,137 @@ public class CapacitorUpdaterPlugin: CAPPlugin {
490
713
  self.backgroundTaskID = UIBackgroundTaskIdentifier.invalid
491
714
  }
492
715
 
716
+ func sendReadyToJs(current: BundleInfo, msg: String) {
717
+ print("\(CapacitorUpdater.TAG) sendReadyToJs")
718
+ DispatchQueue.global().async {
719
+ self.semaphoreWait(waitTime: self.appReadyTimeout)
720
+ self.notifyListeners("appReady", data: ["bundle": current.toJSON(), "status": msg])
721
+ }
722
+ }
723
+
724
+ func endBackGroundTaskWithNotif(msg: String, latestVersionName: String, current: BundleInfo, error: Bool = true) {
725
+ if error {
726
+ self.implementation.sendStats(action: "download_fail", versionName: current.getVersionName())
727
+ self.notifyListeners("downloadFailed", data: ["version": latestVersionName])
728
+ }
729
+ self.notifyListeners("noNeedUpdate", data: ["bundle": current.toJSON()])
730
+ self.sendReadyToJs(current: current, msg: msg)
731
+ print("\(CapacitorUpdater.TAG) endBackGroundTaskWithNotif \(msg) current: \(current.getVersionName()) latestVersionName: \(latestVersionName)")
732
+ self.endBackGroundTask()
733
+ }
734
+
493
735
  func backgroundDownload() {
736
+ let messageUpdate = self.directUpdate ? "Update will occur now." : "Update will occur next time app moves to background."
737
+ guard let url = URL(string: self.updateUrl) else {
738
+ print("\(CapacitorUpdater.TAG) Error no url or wrong format")
739
+ return
740
+ }
494
741
  DispatchQueue.global(qos: .background).async {
495
742
  self.backgroundTaskID = UIApplication.shared.beginBackgroundTask(withName: "Finish Download Tasks") {
496
743
  // End the task if time expires.
497
744
  self.endBackGroundTask()
498
745
  }
499
- print("\(self.implementation.TAG) Check for update via \(self.updateUrl)")
500
- let url = URL(string: self.updateUrl)!
501
- let res = self.implementation.getLatest(url: url)
746
+ print("\(CapacitorUpdater.TAG) Check for update via \(self.updateUrl)")
747
+ let res = self.implementation.getLatest(url: url, channel: nil)
502
748
  let current = self.implementation.getCurrentBundle()
503
749
 
504
750
  if (res.message) != nil {
505
- print("\(self.implementation.TAG) message \(res.message ?? "")")
751
+ print("\(CapacitorUpdater.TAG) API message: \(res.message ?? "")")
506
752
  if res.major == true {
507
753
  self.notifyListeners("majorAvailable", data: ["version": res.version])
508
754
  }
509
- self.notifyListeners("noNeedUpdate", data: ["bundle": current.toJSON()])
510
- self.endBackGroundTask()
755
+ self.endBackGroundTaskWithNotif(msg: res.message ?? "", latestVersionName: res.version, current: current, error: true)
756
+ return
757
+ }
758
+ if res.version == "builtin" {
759
+ print("\(CapacitorUpdater.TAG) Latest version is builtin")
760
+ if self.directUpdate {
761
+ print("\(CapacitorUpdater.TAG) Direct update to builtin version")
762
+ _ = self._reset(toLastSuccessful: false)
763
+ self.endBackGroundTaskWithNotif(msg: "Updated to builtin version", latestVersionName: res.version, current: self.implementation.getCurrentBundle(), error: false)
764
+ } else {
765
+ print("\(CapacitorUpdater.TAG) Setting next bundle to builtin")
766
+ _ = self.implementation.setNextBundle(next: BundleInfo.ID_BUILTIN)
767
+ self.endBackGroundTaskWithNotif(msg: "Next update will be to builtin version", latestVersionName: res.version, current: current, error: false)
768
+ }
511
769
  return
512
770
  }
513
771
  let sessionKey = res.sessionKey ?? ""
514
772
  guard let downloadUrl = URL(string: res.url) else {
515
- print("\(self.implementation.TAG) Error no url or wrong format")
516
- self.notifyListeners("noNeedUpdate", data: ["bundle": current.toJSON()])
517
- self.endBackGroundTask()
773
+ print("\(CapacitorUpdater.TAG) Error no url or wrong format")
774
+ self.endBackGroundTaskWithNotif(msg: "Error no url or wrong format", latestVersionName: res.version, current: current)
518
775
  return
519
776
  }
520
777
  let latestVersionName = res.version
521
778
  if latestVersionName != "" && current.getVersionName() != latestVersionName {
522
- let latest = self.implementation.getBundleInfoByVersionName(version: latestVersionName)
523
- if latest != nil {
524
- if latest!.isErrorStatus() {
525
- print("\(self.implementation.TAG) Latest version already exists, and is in error state. Aborting update.")
526
- self.notifyListeners("noNeedUpdate", data: ["bundle": current.toJSON()])
527
- self.endBackGroundTask()
528
- return
779
+ do {
780
+ print("\(CapacitorUpdater.TAG) New bundle: \(latestVersionName) found. Current is: \(current.getVersionName()). \(messageUpdate)")
781
+ var nextImpl = self.implementation.getBundleInfoByVersionName(version: latestVersionName)
782
+ if nextImpl == nil || nextImpl?.isDeleted() == true {
783
+ if nextImpl?.isDeleted() == true {
784
+ print("\(CapacitorUpdater.TAG) Latest bundle already exists and will be deleted, download will overwrite it.")
785
+ let res = self.implementation.delete(id: nextImpl!.getId(), removeInfo: true)
786
+ if res {
787
+ print("\(CapacitorUpdater.TAG) Failed bundle deleted: \(nextImpl!.toString())")
788
+ } else {
789
+ print("\(CapacitorUpdater.TAG) Failed to delete failed bundle: \(nextImpl!.toString())")
790
+ }
791
+ }
792
+ if res.manifest != nil {
793
+ nextImpl = try self.implementation.downloadManifest(manifest: res.manifest!, version: latestVersionName, sessionKey: sessionKey)
794
+ } else {
795
+ nextImpl = try self.implementation.download(url: downloadUrl, version: latestVersionName, sessionKey: sessionKey)
796
+ }
529
797
  }
530
- if latest!.isDownloaded() {
531
- print("\(self.implementation.TAG) Latest version already exists and download is NOT required. Update will occur next time app moves to background.")
532
- self.notifyListeners("updateAvailable", data: ["bundle": current.toJSON()])
533
- _ = self.implementation.setNextBundle(next: latest!.getId())
534
- self.endBackGroundTask()
798
+ guard let next = nextImpl else {
799
+ print("\(CapacitorUpdater.TAG) Error downloading file")
800
+ self.endBackGroundTaskWithNotif(msg: "Error downloading file", latestVersionName: latestVersionName, current: current)
535
801
  return
536
802
  }
537
- if latest!.isDeleted() {
538
- print("\(self.implementation.TAG) Latest bundle already exists and will be deleted, download will overwrite it.")
539
- let res = self.implementation.delete(id: latest!.getId(), removeInfo: true)
540
- if !res {
541
- print("\(self.implementation.TAG) Delete version deleted: \(latest!.toString())")
542
- } else {
543
- print("\(self.implementation.TAG) Failed to delete failed bundle: \(latest!.toString())")
544
- }
803
+ if next.isErrorStatus() {
804
+ print("\(CapacitorUpdater.TAG) Latest bundle already exists and is in error state. Aborting update.")
805
+ self.endBackGroundTaskWithNotif(msg: "Latest version is in error state. Aborting update.", latestVersionName: latestVersionName, current: current)
806
+ return
545
807
  }
546
- }
547
-
548
- do {
549
- print("\(self.implementation.TAG) New bundle: \(latestVersionName) found. Current is: \(current.getVersionName()). Update will occur next time app moves to background.")
550
- let next = try self.implementation.download(url: downloadUrl, version: latestVersionName, sessionKey: sessionKey)
551
- if res.checksum != "" && next.getChecksum() != res.checksum {
552
- print("\(self.implementation.TAG) Error checksum", next.getChecksum(), res.checksum)
808
+ res.checksum = try self.implementation.decryptChecksum(checksum: res.checksum, version: latestVersionName)
809
+ if res.checksum != "" && next.getChecksum() != res.checksum && res.manifest == nil {
810
+ print("\(CapacitorUpdater.TAG) Error checksum", next.getChecksum(), res.checksum)
553
811
  self.implementation.sendStats(action: "checksum_fail", versionName: next.getVersionName())
554
812
  let id = next.getId()
555
813
  let resDel = self.implementation.delete(id: id)
556
814
  if !resDel {
557
- print("\(self.implementation.TAG) Delete failed, id \(id) doesn't exist")
815
+ print("\(CapacitorUpdater.TAG) Delete failed, id \(id) doesn't exist")
558
816
  }
559
- self.endBackGroundTask()
560
- throw ObjectSavableError.checksum
817
+ self.endBackGroundTaskWithNotif(msg: "Error checksum", latestVersionName: latestVersionName, current: current)
818
+ return
819
+ }
820
+ if self.directUpdate {
821
+ _ = self.implementation.set(bundle: next)
822
+ _ = self._reload()
823
+ self.directUpdate = false
824
+ self.endBackGroundTaskWithNotif(msg: "update installed", latestVersionName: latestVersionName, current: current, error: false)
825
+ } else {
826
+ self.notifyListeners("updateAvailable", data: ["bundle": next.toJSON()])
827
+ _ = self.implementation.setNextBundle(next: next.getId())
828
+ self.endBackGroundTaskWithNotif(msg: "update downloaded, will install next background", latestVersionName: latestVersionName, current: current, error: false)
561
829
  }
562
- self.notifyListeners("updateAvailable", data: ["bundle": next.toJSON()])
563
- _ = self.implementation.setNextBundle(next: next.getId())
830
+ return
564
831
  } catch {
565
- print("\(self.implementation.TAG) Error downloading file", error.localizedDescription)
832
+ print("\(CapacitorUpdater.TAG) Error downloading file", error.localizedDescription)
566
833
  let current: BundleInfo = self.implementation.getCurrentBundle()
567
- self.implementation.sendStats(action: "download_fail", versionName: current.getVersionName())
568
- self.notifyListeners("downloadFailed", data: ["version": latestVersionName])
569
- self.notifyListeners("noNeedUpdate", data: ["bundle": current.toJSON()])
834
+ self.endBackGroundTaskWithNotif(msg: "Error downloading file", latestVersionName: latestVersionName, current: current)
835
+ return
570
836
  }
571
837
  } else {
572
- print("\(self.implementation.TAG) No need to update, \(current.getId()) is the latest bundle.")
573
- self.notifyListeners("noNeedUpdate", data: ["bundle": current.toJSON()])
838
+ print("\(CapacitorUpdater.TAG) No need to update, \(current.getId()) is the latest bundle.")
839
+ self.endBackGroundTaskWithNotif(msg: "No need to update, \(current.getId()) is the latest bundle.", latestVersionName: latestVersionName, current: current, error: false)
840
+ return
574
841
  }
575
- self.endBackGroundTask()
576
842
  }
577
843
  }
578
844
 
579
845
  @objc func appKilled() {
580
- print("\(self.implementation.TAG) onActivityDestroyed: all activity destroyed")
846
+ print("\(CapacitorUpdater.TAG) onActivityDestroyed: all activity destroyed")
581
847
  self._checkCancelDelay(killed: true)
582
848
  }
583
849
 
@@ -589,19 +855,19 @@ public class CapacitorUpdaterPlugin: CAPPlugin {
589
855
  return DelayCondition(kind: kind, value: value)
590
856
  }
591
857
  if delayConditionList != nil && delayConditionList?.capacity != 0 {
592
- print("\(self.implementation.TAG) Update delayed to next backgrounding")
858
+ print("\(CapacitorUpdater.TAG) Update delayed until delay conditions met")
593
859
  return
594
860
  }
595
861
  let current: BundleInfo = self.implementation.getCurrentBundle()
596
862
  let next: BundleInfo? = self.implementation.getNextBundle()
597
863
 
598
864
  if next != nil && !next!.isErrorStatus() && next!.getVersionName() != current.getVersionName() {
599
- print("\(self.implementation.TAG) Next bundle is: \(next!.toString())")
865
+ print("\(CapacitorUpdater.TAG) Next bundle is: \(next!.toString())")
600
866
  if self.implementation.set(bundle: next!) && self._reload() {
601
- print("\(self.implementation.TAG) Updated to bundle: \(next!.toString())")
867
+ print("\(CapacitorUpdater.TAG) Updated to bundle: \(next!.toString())")
602
868
  _ = self.implementation.setNextBundle(next: Optional<String>.none)
603
869
  } else {
604
- print("\(self.implementation.TAG) Update to bundle: \(next!.toString()) Failed!")
870
+ print("\(CapacitorUpdater.TAG) Update to bundle: \(next!.toString()) Failed!")
605
871
  }
606
872
  }
607
873
  }
@@ -614,7 +880,9 @@ public class CapacitorUpdaterPlugin: CAPPlugin {
614
880
  }
615
881
 
616
882
  @objc private func fromJsonArr(json: String) -> [NSObject] {
617
- let jsonData = json.data(using: .utf8)!
883
+ guard let jsonData = json.data(using: .utf8) else {
884
+ return []
885
+ }
618
886
  let object = try? JSONSerialization.jsonObject(
619
887
  with: jsonData,
620
888
  options: .mutableContainers
@@ -627,20 +895,42 @@ public class CapacitorUpdaterPlugin: CAPPlugin {
627
895
  self.implementation.sendStats(action: "app_moved_to_foreground", versionName: current.getVersionName())
628
896
  if backgroundWork != nil && taskRunning {
629
897
  backgroundWork!.cancel()
630
- print("\(self.implementation.TAG) Background Timer Task canceled, Activity resumed before timer completes")
898
+ print("\(CapacitorUpdater.TAG) Background Timer Task canceled, Activity resumed before timer completes")
631
899
  }
632
900
  if self._isAutoUpdateEnabled() {
633
901
  self.backgroundDownload()
634
902
  } else {
635
- print("\(self.implementation.TAG) Auto update is disabled")
903
+ print("\(CapacitorUpdater.TAG) Auto update is disabled")
904
+ self.sendReadyToJs(current: current, msg: "disabled")
636
905
  }
637
906
  self.checkAppReady()
638
907
  }
639
908
 
909
+ @objc func checkForUpdateAfterDelay() {
910
+ if periodCheckDelay == 0 || !self._isAutoUpdateEnabled() {
911
+ return
912
+ }
913
+ guard let url = URL(string: self.updateUrl) else {
914
+ print("\(CapacitorUpdater.TAG) Error no url or wrong format")
915
+ return
916
+ }
917
+ let timer = Timer.scheduledTimer(withTimeInterval: TimeInterval(periodCheckDelay), repeats: true) { _ in
918
+ DispatchQueue.global(qos: .background).async {
919
+ let res = self.implementation.getLatest(url: url, channel: nil)
920
+ let current = self.implementation.getCurrentBundle()
921
+
922
+ if res.version != current.getVersionName() {
923
+ print("\(CapacitorUpdater.TAG) New version found: \(res.version)")
924
+ self.backgroundDownload()
925
+ }
926
+ }
927
+ }
928
+ RunLoop.current.add(timer, forMode: .default)
929
+ }
930
+
640
931
  @objc func appMovedToBackground() {
641
- let current: BundleInfo = self.implementation.getCurrentBundle()
642
- self.implementation.sendStats(action: "app_moved_to_background", versionName: current.getVersionName())
643
- print("\(self.implementation.TAG) Check for pending update")
932
+ self.implementation.sendStats(action: "app_moved_to_background")
933
+ print("\(CapacitorUpdater.TAG) Check for pending update")
644
934
  let delayUpdatePreferences = UserDefaults.standard.string(forKey: DELAY_CONDITION_PREFERENCES) ?? "[]"
645
935
 
646
936
  let delayConditionList: [DelayCondition] = fromJsonArr(json: delayUpdatePreferences).map { obj -> DelayCondition in
@@ -672,4 +962,14 @@ public class CapacitorUpdaterPlugin: CAPPlugin {
672
962
  }
673
963
 
674
964
  }
965
+
966
+ @objc func getNextBundle(_ call: CAPPluginCall) {
967
+ let bundle = self.implementation.getNextBundle()
968
+ if bundle == nil || bundle?.isUnknown() == true {
969
+ call.resolve()
970
+ return
971
+ }
972
+
973
+ call.resolve(bundle!.toJSON())
974
+ }
675
975
  }