@capawesome/capacitor-live-update 5.0.0

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 (66) hide show
  1. package/CapawesomeCapacitorLiveUpdate.podspec +20 -0
  2. package/LICENSE +21 -0
  3. package/README.md +509 -0
  4. package/android/build.gradle +62 -0
  5. package/android/src/main/AndroidManifest.xml +2 -0
  6. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdate.java +613 -0
  7. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdateConfig.java +55 -0
  8. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdatePlugin.java +437 -0
  9. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdatePreferences.java +86 -0
  10. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/api/GetLatestBundleResponse.java +23 -0
  11. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/options/DeleteBundleOptions.java +18 -0
  12. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/options/DownloadBundleOptions.java +27 -0
  13. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/options/SetBundleOptions.java +18 -0
  14. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/options/SetChannelOptions.java +18 -0
  15. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/options/SetCustomIdOptions.java +18 -0
  16. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/results/GetBundleResult.java +24 -0
  17. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/results/GetBundlesResult.java +27 -0
  18. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/results/GetChannelResult.java +21 -0
  19. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/results/GetCustomIdResult.java +22 -0
  20. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/results/GetDeviceIdResult.java +22 -0
  21. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/results/GetVersionCodeResult.java +21 -0
  22. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/results/GetVersionNameResult.java +21 -0
  23. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/results/SyncResult.java +22 -0
  24. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/interfaces/Callback.java +5 -0
  25. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/interfaces/EmptyCallback.java +5 -0
  26. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/interfaces/NonEmptyCallback.java +7 -0
  27. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/interfaces/Result.java +7 -0
  28. package/android/src/main/res/.gitkeep +0 -0
  29. package/dist/docs.json +785 -0
  30. package/dist/esm/definitions.d.ts +356 -0
  31. package/dist/esm/definitions.js +3 -0
  32. package/dist/esm/definitions.js.map +1 -0
  33. package/dist/esm/index.d.ts +4 -0
  34. package/dist/esm/index.js +7 -0
  35. package/dist/esm/index.js.map +1 -0
  36. package/dist/esm/web.d.ts +21 -0
  37. package/dist/esm/web.js +55 -0
  38. package/dist/esm/web.js.map +1 -0
  39. package/dist/plugin.cjs.js +71 -0
  40. package/dist/plugin.cjs.js.map +1 -0
  41. package/dist/plugin.js +74 -0
  42. package/dist/plugin.js.map +1 -0
  43. package/ios/Plugin/Classes/CustomError.swift +38 -0
  44. package/ios/Plugin/Classes/Options/DeleteBundleOptions.swift +14 -0
  45. package/ios/Plugin/Classes/Options/DownloadBundleOptions.swift +20 -0
  46. package/ios/Plugin/Classes/Options/SetBundleOptions.swift +14 -0
  47. package/ios/Plugin/Classes/Options/SetChannelOptions.swift +14 -0
  48. package/ios/Plugin/Classes/Options/SetCustomIdOptions.swift +14 -0
  49. package/ios/Plugin/Classes/Results/GetBundleResult.swift +16 -0
  50. package/ios/Plugin/Classes/Results/GetBundlesResult.swift +16 -0
  51. package/ios/Plugin/Classes/Results/GetChannelResult.swift +16 -0
  52. package/ios/Plugin/Classes/Results/GetCustomIdResult.swift +16 -0
  53. package/ios/Plugin/Classes/Results/GetDeviceIdResult.swift +16 -0
  54. package/ios/Plugin/Classes/Results/GetVersionCodeResult.swift +16 -0
  55. package/ios/Plugin/Classes/Results/GetVersionNameResult.swift +16 -0
  56. package/ios/Plugin/Classes/Results/SyncResult.swift +16 -0
  57. package/ios/Plugin/Info.plist +24 -0
  58. package/ios/Plugin/LiveUpdate.swift +512 -0
  59. package/ios/Plugin/LiveUpdateConfig.swift +7 -0
  60. package/ios/Plugin/LiveUpdatePlugin.h +10 -0
  61. package/ios/Plugin/LiveUpdatePlugin.m +23 -0
  62. package/ios/Plugin/LiveUpdatePlugin.swift +247 -0
  63. package/ios/Plugin/LiveUpdatePreferences.swift +60 -0
  64. package/ios/Plugin/Protocols/Api/GetLatestBundleResponse.swift +4 -0
  65. package/ios/Plugin/Protocols/Result.swift +6 -0
  66. package/package.json +92 -0
@@ -0,0 +1,512 @@
1
+ import Foundation
2
+ import SSZipArchive
3
+ import Capacitor
4
+ import Alamofire
5
+
6
+ // swiftlint:disable type_body_length
7
+ @objc public class LiveUpdate: NSObject {
8
+ private let cachesDirectoryUrl = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
9
+ private let config: LiveUpdateConfig
10
+ private let defaultWebAssetDir = "public" // DO NOT CHANGE (See https://dub.sh/Buvz4yj)
11
+ private let defaultServerPathKey = "serverBasePath" // DO NOT CHANGE (See https://dub.sh/ceDl0zT)
12
+ private let libraryDirectoryUrl = FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask).first!
13
+ private let plugin: LiveUpdatePlugin
14
+ private let pluginDirectory = "NoCloud/ionic_built_snapshots" // DO NOT CHANGE (See https://dub.sh/BLluidt)
15
+ private let preferences: LiveUpdatePreferences
16
+
17
+ private var rollbackDispatchWorkItem: DispatchWorkItem?
18
+
19
+ init(config: LiveUpdateConfig, plugin: LiveUpdatePlugin) {
20
+ self.config = config
21
+ self.plugin = plugin
22
+ self.preferences = LiveUpdatePreferences()
23
+ super.init()
24
+
25
+ if config.enabled {
26
+ if wasUpdated() && config.resetOnUpdate {
27
+ reset()
28
+ } else {
29
+ startRollbackTimer()
30
+ }
31
+ saveCurrentBundleVersion()
32
+ }
33
+ }
34
+
35
+ @objc public func deleteBundle(_ options: DeleteBundleOptions, completion: @escaping (Error?) -> Void) {
36
+ let bundleId = options.getBundleId()
37
+
38
+ if !hasBundle(bundleId: bundleId) {
39
+ let error = CustomError.bundleNotFound
40
+ completion(error)
41
+ return
42
+ }
43
+
44
+ do {
45
+ try deleteBundle(bundleId: bundleId)
46
+ completion(nil)
47
+ } catch {
48
+ completion(error)
49
+ }
50
+ }
51
+
52
+ @objc public func downloadBundle(_ options: DownloadBundleOptions, completion: @escaping (Error?) -> Void) {
53
+ let url = options.getUrl()
54
+ let bundleId = options.getBundleId()
55
+
56
+ // Check if the bundle already exists
57
+ if hasBundle(bundleId: bundleId) {
58
+ let error = CustomError.bundleAlreadyExists
59
+ completion(error)
60
+ return
61
+ }
62
+
63
+ // Download the bundle
64
+ downloadBundle(bundleId: bundleId, url: url, completion: { error in
65
+ if let error = error {
66
+ completion(error)
67
+ return
68
+ }
69
+ completion(nil)
70
+ })
71
+ }
72
+
73
+ @objc public func getBundle(completion: @escaping (Result?, Error?) -> Void) {
74
+ var bundleId = getCurrentBundleId()
75
+ if bundleId == defaultWebAssetDir {
76
+ bundleId = nil
77
+ }
78
+ let result = GetBundleResult(bundleId: bundleId)
79
+ completion(result, nil)
80
+ }
81
+
82
+ @objc public func getBundles(completion: @escaping (Result?, Error?) -> Void) {
83
+ let bundleIds = getBundleIds()
84
+ let result = GetBundlesResult(bundleIds: bundleIds)
85
+ completion(result, nil)
86
+ }
87
+
88
+ @objc public func getChannel(completion: @escaping (Result?, Error?) -> Void) {
89
+ let channel = preferences.getChannel()
90
+ let result = GetChannelResult(channel: channel)
91
+ completion(result, nil)
92
+ }
93
+
94
+ @objc public func getCustomId(completion: @escaping (Result?, Error?) -> Void) {
95
+ let customId = preferences.getCustomId()
96
+
97
+ let result = GetCustomIdResult(customId: customId)
98
+ completion(result, nil)
99
+ }
100
+
101
+ @objc public func getDeviceId(completion: @escaping (Result?, Error?) -> Void) {
102
+ let deviceId = getDeviceId()
103
+ let result = GetDeviceIdResult(deviceId: deviceId)
104
+ completion(result, nil)
105
+ }
106
+
107
+ @objc public func getVersionCode(completion: @escaping (Result?, Error?) -> Void) {
108
+ let versionCode = getVersionCode()
109
+ let result = GetVersionCodeResult(versionCode: versionCode)
110
+ completion(result, nil)
111
+ }
112
+
113
+ @objc public func getVersionName(completion: @escaping (Result?, Error?) -> Void) {
114
+ let versionName = getVersionName()
115
+ let result = GetVersionNameResult(versionName: versionName)
116
+ completion(result, nil)
117
+ }
118
+
119
+ @objc public func ready() {
120
+ CAPLog.print("[", LiveUpdatePlugin.tag, "] ", "App is ready.")
121
+ stopRollbackTimer()
122
+ if config.autoDeleteBundles {
123
+ deleteUnusedBundles()
124
+ }
125
+ }
126
+
127
+ @objc public func reload() {
128
+ var path: String?
129
+ if let currentPath = getCurrentCapacitorServerPath() {
130
+ path = currentPath
131
+ }
132
+ if let nextPath = getNextCapacitorServerPath() {
133
+ path = nextPath
134
+ }
135
+ if let path = path {
136
+ setCurrentCapacitorServerPath(path: path)
137
+ startRollbackTimer()
138
+ }
139
+ }
140
+
141
+ @objc public func reset() {
142
+ self.setNextCapacitorServerPathToDefaultWebAssetDir()
143
+ }
144
+
145
+ @objc public func setBundle(_ options: SetBundleOptions, completion: @escaping (Error?) -> Void) {
146
+ let bundleId = options.getBundleId()
147
+
148
+ // Check if the bundle already exists
149
+ if !hasBundle(bundleId: bundleId) {
150
+ let error = CustomError.bundleNotFound
151
+ completion(error)
152
+ return
153
+ }
154
+
155
+ setNextBundle(bundleId: bundleId)
156
+ }
157
+
158
+ @objc public func setChannel(_ options: SetChannelOptions, completion: @escaping (Error?) -> Void) {
159
+ let channel = options.getChannel()
160
+
161
+ preferences.setChannel(channel)
162
+ completion(nil)
163
+ }
164
+
165
+ @objc public func setCustomId(_ options: SetCustomIdOptions, completion: @escaping (Error?) -> Void) {
166
+ let customId = options.getCustomId()
167
+
168
+ preferences.setCustomId(customId)
169
+ completion(nil)
170
+ }
171
+
172
+ @objc public func sync(completion: @escaping (SyncResult?, Error?) -> Void) {
173
+ // Fetch the latest bundle
174
+ fetchLatestBundle(completion: { response, error in
175
+ if let _ = error {
176
+ // No update available
177
+ CAPLog.print("[", LiveUpdatePlugin.tag, "] ", "No update available.")
178
+ let result = SyncResult(nextBundleId: nil)
179
+ completion(result, nil)
180
+ return
181
+ }
182
+ if let response = response {
183
+ let latestBundleId = response.bundleId
184
+ // Check if the bundle already exists
185
+ if self.hasBundle(bundleId: latestBundleId) {
186
+ var nextBundleId: String?
187
+ let currentBundleId = self.getCurrentBundleId()
188
+ if latestBundleId != currentBundleId {
189
+ // Set the next bundle
190
+ self.setNextBundle(bundleId: latestBundleId)
191
+ nextBundleId = latestBundleId
192
+ }
193
+ let result = SyncResult(nextBundleId: nextBundleId)
194
+ completion(result, nil)
195
+ } else {
196
+ // Download and unzip the bundle
197
+ self.downloadBundle(bundleId: latestBundleId, url: response.url, completion: { error in
198
+ if let error = error {
199
+ completion(nil, error)
200
+ return
201
+ }
202
+ // Set the next bundle
203
+ self.setNextBundle(bundleId: latestBundleId)
204
+ let result = SyncResult(nextBundleId: latestBundleId)
205
+ completion(result, nil)
206
+ })
207
+ }
208
+ }
209
+ })
210
+ }
211
+
212
+ private func addBundle(bundleId: String, zipFile: URL, completion: @escaping (Error?) -> Void) {
213
+ // Unzip the bundle
214
+ self.unzipFile(zipFile: zipFile, completion: { url in
215
+ // Search folder with index.html file
216
+ guard let indexHtmlFile = self.searchIndexHtmlFile(url: url) else {
217
+ completion(CustomError.bundleIndexHtmlMissing)
218
+ return
219
+ }
220
+ // Move the unzipped files to the bundles directory
221
+ let bundleDirectoryUrl = self.libraryDirectoryUrl.appendingPathComponent(self.pluginDirectory).appendingPathComponent(bundleId)
222
+ do {
223
+ try FileManager.default.moveItem(atPath: indexHtmlFile.deletingLastPathComponent().path, toPath: bundleDirectoryUrl.path)
224
+ completion(nil)
225
+ } catch {
226
+ completion(error)
227
+ return
228
+ }
229
+ })
230
+ }
231
+
232
+ private func buildCapacitorServerPathFor(bundleId: String) -> String {
233
+ let path: String
234
+ if bundleId == "public" {
235
+ path = Bundle.main.bundleURL.appendingPathComponent(bundleId).path
236
+ } else {
237
+ path = buildBundlePathFor(bundleId: bundleId)
238
+ }
239
+ return path
240
+ }
241
+
242
+ private func buildBundlePathFor(bundleId: String) -> String {
243
+ let url = libraryDirectoryUrl.appendingPathComponent(pluginDirectory).appendingPathComponent(bundleId)
244
+ return url.path
245
+ }
246
+
247
+ private func deleteBundle(bundleId: String) throws {
248
+ let bundleDirectoryUrl = libraryDirectoryUrl.appendingPathComponent(pluginDirectory).appendingPathComponent(bundleId)
249
+ try FileManager.default.removeItem(atPath: bundleDirectoryUrl.path)
250
+ }
251
+
252
+ private func deleteUnusedBundles() {
253
+ let bundleIds = getBundleIds()
254
+ let currentBundleId = getCurrentBundleId()
255
+ let nextBundleId = getNextBundleId()
256
+
257
+ for bundleId in bundleIds {
258
+ if bundleId != currentBundleId && bundleId != nextBundleId {
259
+ do {
260
+ try deleteBundle(bundleId: bundleId)
261
+ } catch {
262
+ CAPLog.print("[", LiveUpdatePlugin.tag, "] ", "Failed to delete bundle with id: \(bundleId)")
263
+ }
264
+ }
265
+ }
266
+ }
267
+
268
+ private func downloadBundle(bundleId: String, url: String, completion: @escaping (Error?) -> Void) {
269
+ // Download the bundle
270
+ downloadFile(url: url, completion: { url, error in
271
+ if let error = error {
272
+ completion(error)
273
+ return
274
+ }
275
+ if let url = url {
276
+ // Add the bundle
277
+ self.addBundle(bundleId: bundleId, zipFile: url, completion: { error in
278
+ // Delete the temporary file
279
+ do {
280
+ try FileManager.default.removeItem(atPath: url.path)
281
+ } catch {
282
+ CAPLog.print("[", LiveUpdatePlugin.tag, "] ", "Failed to delete temporary file.")
283
+ }
284
+ if let error = error {
285
+ completion(error)
286
+ return
287
+ }
288
+ completion(nil)
289
+ })
290
+ }
291
+ })
292
+ }
293
+
294
+ private func downloadFile(url: String, completion: @escaping (URL?, Error?) -> Void) {
295
+ let destination: DownloadRequest.Destination = { _, _ in
296
+ let timestamp = String(Int(Date().timeIntervalSince1970))
297
+ let temporaryZipFileUrl = self.cachesDirectoryUrl.appendingPathComponent(timestamp + ".zip")
298
+ return (temporaryZipFileUrl, [.createIntermediateDirectories])
299
+ }
300
+
301
+ AF.download(url, to: destination).validate().response { response in
302
+ if let error = response.error {
303
+ CAPLog.print("[", LiveUpdatePlugin.tag, "] ", error)
304
+ completion(nil, CustomError.downloadFailed)
305
+ return
306
+ }
307
+ completion(response.fileURL, nil)
308
+ }
309
+ }
310
+
311
+ private func fetchLatestBundle(completion: @escaping (GetLatestBundleResponse?, Error?) -> Void) {
312
+ var parameters = [String: String]()
313
+ parameters["appVersionCode"] = getVersionCode()
314
+ parameters["appVersionName"] = getVersionName()
315
+ parameters["bundleId"] = getCurrentBundleId()
316
+ parameters["channelName"] = preferences.getChannel()
317
+ parameters["customId"] = preferences.getCustomId()
318
+ parameters["deviceId"] = getDeviceId()
319
+ parameters["osVersion"] = UIDevice.current.systemVersion
320
+ parameters["platform"] = "1"
321
+ parameters["pluginVersion"] = LiveUpdatePlugin.version
322
+ let url = URL(string: "https://api.cloud.capawesome.io/v1/apps/\(config.appId ?? "")/bundles/latest")!
323
+ AF.request(url, method: .get, parameters: parameters).validate().responseDecodable(of: GetLatestBundleResponse.self) { response in
324
+ if let error = response.error {
325
+ CAPLog.print("[", LiveUpdatePlugin.tag, "] ", error)
326
+ completion(nil, error)
327
+ return
328
+ }
329
+ completion(response.value, nil)
330
+ }
331
+ }
332
+
333
+ private func getVersionCode() -> String {
334
+ guard let appVersionCode = Bundle.main.infoDictionary?["CFBundleVersion"] as? String else {
335
+ return ""
336
+ }
337
+ return appVersionCode
338
+ }
339
+
340
+ private func getVersionName() -> String {
341
+ guard let appVersionName = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String else {
342
+ return ""
343
+ }
344
+ return appVersionName
345
+ }
346
+
347
+ private func getBundleIds() -> [String] {
348
+ let url = libraryDirectoryUrl.appendingPathComponent(pluginDirectory)
349
+ do {
350
+ let pathExists = FileManager.default.fileExists(atPath: url.path)
351
+ var files: [String] = []
352
+ if pathExists {
353
+ files = try FileManager.default.contentsOfDirectory(atPath: url.path)
354
+ }
355
+ return files
356
+ } catch {
357
+ return []
358
+ }
359
+ }
360
+
361
+ private func getCurrentBundleId() -> String? {
362
+ guard let path = getCurrentCapacitorServerPath() else {
363
+ return nil
364
+ }
365
+ return URL(fileURLWithPath: path).lastPathComponent
366
+ }
367
+
368
+ private func getCurrentCapacitorServerPath() -> String? {
369
+ guard let viewController = self.plugin.bridge?.viewController as? CAPBridgeViewController else {
370
+ return nil
371
+ }
372
+ return viewController.getServerBasePath()
373
+ }
374
+
375
+ private func getDeviceId() -> String {
376
+ if let deviceId = preferences.getDeviceIdForApp(appId: config.appId) {
377
+ return deviceId
378
+ }
379
+ let deviceId = UUID().uuidString
380
+ preferences.setDeviceIdForApp(appId: config.appId, deviceId)
381
+ return deviceId
382
+ }
383
+
384
+ private func getNextBundleId() -> String? {
385
+ guard let path = getNextCapacitorServerPath() else {
386
+ return nil
387
+ }
388
+ return URL(fileURLWithPath: path).lastPathComponent
389
+ }
390
+
391
+ private func getNextCapacitorServerPath() -> String? {
392
+ if let path = UserDefaults.standard.string(forKey: self.defaultServerPathKey) {
393
+ return path.isEmpty ? nil : path
394
+ }
395
+ return nil
396
+ }
397
+
398
+ private func hasBundle(bundleId: String) -> Bool {
399
+ let url = libraryDirectoryUrl.appendingPathComponent(pluginDirectory).appendingPathComponent(bundleId)
400
+ return FileManager.default.fileExists(atPath: url.path)
401
+ }
402
+
403
+ private func rollback() {
404
+ if getCurrentBundleId() == defaultWebAssetDir {
405
+ CAPLog.print("[", LiveUpdatePlugin.tag, "] ", "App is not ready. Default bundle is already in use.")
406
+ return
407
+ }
408
+ CAPLog.print("[", LiveUpdatePlugin.tag, "] ", "App is not ready. Rolling back to default bundle.")
409
+ setNextCapacitorServerPathToDefaultWebAssetDir()
410
+ setCurrentCapacitorServerPathToDefaultWebAssetDir()
411
+ }
412
+
413
+ private func saveCurrentBundleVersion() {
414
+ guard let currentBundleVersion = Bundle.main.infoDictionary?["CFBundleVersion"] as? String else {
415
+ return
416
+ }
417
+ preferences.setLastBundleVersion(currentBundleVersion)
418
+ }
419
+
420
+ private func searchIndexHtmlFile(url: URL) -> URL? {
421
+ let enumerator = FileManager.default.enumerator(atPath: url.path)
422
+ while let element = enumerator?.nextObject() as? String {
423
+ if element.hasSuffix("index.html") {
424
+ return url.appendingPathComponent(element)
425
+ }
426
+ }
427
+ return nil
428
+ }
429
+
430
+ private func setCurrentCapacitorServerPath(path: String) {
431
+ guard let viewController = self.plugin.bridge?.viewController as? CAPBridgeViewController else {
432
+ return
433
+ }
434
+ viewController.setServerBasePath(path: path)
435
+ }
436
+
437
+ private func setCurrentCapacitorServerPathToDefaultWebAssetDir() {
438
+ let path = buildCapacitorServerPathFor(bundleId: defaultWebAssetDir)
439
+ setCurrentCapacitorServerPath(path: path)
440
+ }
441
+
442
+ private func setNextBundle(bundleId: String) {
443
+ let path = buildCapacitorServerPathFor(bundleId: bundleId)
444
+ setNextCapacitorServerPath(path: path)
445
+ }
446
+
447
+ private func setNextCapacitorServerPath(path: String) {
448
+ if path.hasSuffix("/public") {
449
+ // Reset the custom server base path
450
+ UserDefaults.standard.set("", forKey: self.defaultServerPathKey)
451
+ } else {
452
+ // Attention: Only the lastPathComponent is used (see https://dub.sh/BLluidt)
453
+ UserDefaults.standard.set(path, forKey: self.defaultServerPathKey)
454
+ }
455
+ UserDefaults.standard.synchronize()
456
+ }
457
+
458
+ private func setNextCapacitorServerPathToDefaultWebAssetDir() {
459
+ let path = buildCapacitorServerPathFor(bundleId: defaultWebAssetDir)
460
+ setNextCapacitorServerPath(path: path)
461
+ }
462
+
463
+ private func startRollbackTimer() {
464
+ stopRollbackTimer()
465
+ rollbackDispatchWorkItem = DispatchWorkItem { [weak self] in
466
+ self?.rollback()
467
+ }
468
+ DispatchQueue.main.asyncAfter(deadline: .now() + DispatchTimeInterval.milliseconds(config.readyTimeout), execute: rollbackDispatchWorkItem!)
469
+ }
470
+
471
+ private func stopRollbackTimer() {
472
+ rollbackDispatchWorkItem?.cancel()
473
+ }
474
+
475
+ private func unzipFile(zipFile: URL, completion: @escaping (URL) -> Void) {
476
+ let destinationDirectory = zipFile.deletingPathExtension()
477
+ DispatchQueue.background(background: {
478
+ SSZipArchive.unzipFile(atPath: zipFile.path, toDestination: destinationDirectory.path)
479
+ }, completion: {
480
+ completion(destinationDirectory)
481
+ })
482
+ }
483
+
484
+ private func wasUpdated() -> Bool {
485
+ guard let lastBundleVersionString = preferences.getLastBundleVersion() else {
486
+ return false
487
+ }
488
+ guard let lastBundleVersion = Int(lastBundleVersionString) else {
489
+ return false
490
+ }
491
+ guard let currentBundleVersionString = Bundle.main.infoDictionary?["CFBundleVersion"] as? String else {
492
+ return false
493
+ }
494
+ guard let currentBundleVersion = Int(currentBundleVersionString) else {
495
+ return false
496
+ }
497
+ return currentBundleVersion > lastBundleVersion
498
+ }
499
+ }
500
+
501
+ extension DispatchQueue {
502
+ static func background(background: (() -> Void)? = nil, completion: (() -> Void)? = nil) {
503
+ DispatchQueue.global(qos: .background).async {
504
+ background?()
505
+ if let completion = completion {
506
+ DispatchQueue.main.async {
507
+ completion()
508
+ }
509
+ }
510
+ }
511
+ }
512
+ }
@@ -0,0 +1,7 @@
1
+ public struct LiveUpdateConfig {
2
+ var appId: String?
3
+ var autoDeleteBundles = false
4
+ var enabled = true
5
+ var readyTimeout = 10000
6
+ var resetOnUpdate = true
7
+ }
@@ -0,0 +1,10 @@
1
+ #import <UIKit/UIKit.h>
2
+
3
+ //! Project version number for Plugin.
4
+ FOUNDATION_EXPORT double PluginVersionNumber;
5
+
6
+ //! Project version string for Plugin.
7
+ FOUNDATION_EXPORT const unsigned char PluginVersionString[];
8
+
9
+ // In this header, you should import all the public headers of your framework using statements like #import <Plugin/PublicHeader.h>
10
+
@@ -0,0 +1,23 @@
1
+ #import <Foundation/Foundation.h>
2
+ #import <Capacitor/Capacitor.h>
3
+
4
+ // Define the plugin using the CAP_PLUGIN Macro, and
5
+ // each method the plugin supports using the CAP_PLUGIN_METHOD macro.
6
+ CAP_PLUGIN(LiveUpdatePlugin, "LiveUpdate",
7
+ CAP_PLUGIN_METHOD(deleteBundle, CAPPluginReturnPromise);
8
+ CAP_PLUGIN_METHOD(downloadBundle, CAPPluginReturnPromise);
9
+ CAP_PLUGIN_METHOD(getBundle, CAPPluginReturnPromise);
10
+ CAP_PLUGIN_METHOD(getBundles, CAPPluginReturnPromise);
11
+ CAP_PLUGIN_METHOD(getChannel, CAPPluginReturnPromise);
12
+ CAP_PLUGIN_METHOD(getDeviceId, CAPPluginReturnPromise);
13
+ CAP_PLUGIN_METHOD(getCustomId, CAPPluginReturnPromise);
14
+ CAP_PLUGIN_METHOD(getVersionCode, CAPPluginReturnPromise);
15
+ CAP_PLUGIN_METHOD(getVersionName, CAPPluginReturnPromise);
16
+ CAP_PLUGIN_METHOD(ready, CAPPluginReturnPromise);
17
+ CAP_PLUGIN_METHOD(reload, CAPPluginReturnPromise);
18
+ CAP_PLUGIN_METHOD(reset, CAPPluginReturnPromise);
19
+ CAP_PLUGIN_METHOD(setBundle, CAPPluginReturnPromise);
20
+ CAP_PLUGIN_METHOD(setChannel, CAPPluginReturnPromise);
21
+ CAP_PLUGIN_METHOD(setCustomId, CAPPluginReturnPromise);
22
+ CAP_PLUGIN_METHOD(sync, CAPPluginReturnPromise);
23
+ )