@capawesome/capacitor-live-update 7.4.0 → 7.5.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 (28) hide show
  1. package/CapawesomeCapacitorLiveUpdate.podspec +1 -1
  2. package/Package.swift +2 -2
  3. package/README.md +96 -1
  4. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdate.java +171 -58
  5. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdateHttpClient.java +12 -1
  6. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdatePlugin.java +32 -1
  7. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/api/GetChannelsResponseItem.java +28 -0
  8. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/options/FetchChannelsOptions.java +38 -0
  9. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/results/ChannelResult.java +28 -0
  10. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/results/FetchChannelsResult.java +28 -0
  11. package/dist/docs.json +150 -2
  12. package/dist/esm/definitions.d.ts +83 -0
  13. package/dist/esm/definitions.js.map +1 -1
  14. package/dist/esm/web.d.ts +2 -1
  15. package/dist/esm/web.js +3 -0
  16. package/dist/esm/web.js.map +1 -1
  17. package/dist/plugin.cjs.js +3 -0
  18. package/dist/plugin.cjs.js.map +1 -1
  19. package/dist/plugin.js +3 -0
  20. package/dist/plugin.js.map +1 -1
  21. package/ios/Plugin/Classes/Options/FetchChannelsOptions.swift +26 -0
  22. package/ios/Plugin/Classes/Results/ChannelResult.swift +19 -0
  23. package/ios/Plugin/Classes/Results/FetchChannelsResult.swift +16 -0
  24. package/ios/Plugin/LiveUpdate.swift +80 -17
  25. package/ios/Plugin/LiveUpdateHttpClient.swift +11 -0
  26. package/ios/Plugin/LiveUpdatePlugin.swift +21 -1
  27. package/ios/Plugin/Protocols/Api/GetChannelsResponseItem.swift +4 -0
  28. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.js","sources":["esm/index.js","esm/web.js"],"sourcesContent":["import { registerPlugin } from '@capacitor/core';\nconst LiveUpdate = registerPlugin('LiveUpdate', {\n web: () => import('./web').then(m => new m.LiveUpdateWeb()),\n});\nexport * from './definitions';\nexport { LiveUpdate };\n//# sourceMappingURL=index.js.map","import { WebPlugin } from '@capacitor/core';\nexport class LiveUpdateWeb extends WebPlugin {\n async clearBlockedBundles() {\n this.throwUnimplementedError();\n }\n async deleteBundle(_options) {\n this.throwUnimplementedError();\n }\n async downloadBundle(_options) {\n this.throwUnimplementedError();\n }\n async fetchLatestBundle() {\n this.throwUnimplementedError();\n }\n async getBlockedBundles() {\n this.throwUnimplementedError();\n }\n async getBundle() {\n this.throwUnimplementedError();\n }\n async getBundles() {\n this.throwUnimplementedError();\n }\n async getDownloadedBundles() {\n this.throwUnimplementedError();\n }\n async getChannel() {\n this.throwUnimplementedError();\n }\n async getConfig() {\n this.throwUnimplementedError();\n }\n async getCurrentBundle() {\n this.throwUnimplementedError();\n }\n async getCustomId() {\n this.throwUnimplementedError();\n }\n async getDeviceId() {\n this.throwUnimplementedError();\n }\n async getNextBundle() {\n this.throwUnimplementedError();\n }\n async getVersionCode() {\n this.throwUnimplementedError();\n }\n async getVersionName() {\n this.throwUnimplementedError();\n }\n async isSyncing() {\n this.throwUnimplementedError();\n }\n async ready() {\n this.throwUnimplementedError();\n }\n async reload() {\n this.throwUnimplementedError();\n }\n async reset() {\n this.throwUnimplementedError();\n }\n async resetConfig() {\n this.throwUnimplementedError();\n }\n async setBundle(_options) {\n this.throwUnimplementedError();\n }\n async setChannel(_options) {\n this.throwUnimplementedError();\n }\n async setConfig(_options) {\n this.throwUnimplementedError();\n }\n async setCustomId(_options) {\n this.throwUnimplementedError();\n }\n async setNextBundle(_options) {\n this.throwUnimplementedError();\n }\n async sync() {\n this.throwUnimplementedError();\n }\n throwUnimplementedError() {\n throw this.unimplemented('Not implemented on web.');\n }\n}\n//# sourceMappingURL=web.js.map"],"names":["registerPlugin","WebPlugin"],"mappings":"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"}
1
+ {"version":3,"file":"plugin.js","sources":["esm/index.js","esm/web.js"],"sourcesContent":["import { registerPlugin } from '@capacitor/core';\nconst LiveUpdate = registerPlugin('LiveUpdate', {\n web: () => import('./web').then(m => new m.LiveUpdateWeb()),\n});\nexport * from './definitions';\nexport { LiveUpdate };\n//# sourceMappingURL=index.js.map","import { WebPlugin } from '@capacitor/core';\nexport class LiveUpdateWeb extends WebPlugin {\n async clearBlockedBundles() {\n this.throwUnimplementedError();\n }\n async deleteBundle(_options) {\n this.throwUnimplementedError();\n }\n async downloadBundle(_options) {\n this.throwUnimplementedError();\n }\n async fetchChannels() {\n this.throwUnimplementedError();\n }\n async fetchLatestBundle() {\n this.throwUnimplementedError();\n }\n async getBlockedBundles() {\n this.throwUnimplementedError();\n }\n async getBundle() {\n this.throwUnimplementedError();\n }\n async getBundles() {\n this.throwUnimplementedError();\n }\n async getDownloadedBundles() {\n this.throwUnimplementedError();\n }\n async getChannel() {\n this.throwUnimplementedError();\n }\n async getConfig() {\n this.throwUnimplementedError();\n }\n async getCurrentBundle() {\n this.throwUnimplementedError();\n }\n async getCustomId() {\n this.throwUnimplementedError();\n }\n async getDeviceId() {\n this.throwUnimplementedError();\n }\n async getNextBundle() {\n this.throwUnimplementedError();\n }\n async getVersionCode() {\n this.throwUnimplementedError();\n }\n async getVersionName() {\n this.throwUnimplementedError();\n }\n async isSyncing() {\n this.throwUnimplementedError();\n }\n async ready() {\n this.throwUnimplementedError();\n }\n async reload() {\n this.throwUnimplementedError();\n }\n async reset() {\n this.throwUnimplementedError();\n }\n async resetConfig() {\n this.throwUnimplementedError();\n }\n async setBundle(_options) {\n this.throwUnimplementedError();\n }\n async setChannel(_options) {\n this.throwUnimplementedError();\n }\n async setConfig(_options) {\n this.throwUnimplementedError();\n }\n async setCustomId(_options) {\n this.throwUnimplementedError();\n }\n async setNextBundle(_options) {\n this.throwUnimplementedError();\n }\n async sync() {\n this.throwUnimplementedError();\n }\n throwUnimplementedError() {\n throw this.unimplemented('Not implemented on web.');\n }\n}\n//# sourceMappingURL=web.js.map"],"names":["registerPlugin","WebPlugin"],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ import Foundation
2
+ import Capacitor
3
+
4
+ @objc public class FetchChannelsOptions: NSObject {
5
+ private var limit: Int?
6
+ private var offset: Int?
7
+ private var query: String?
8
+
9
+ init(_ call: CAPPluginCall) {
10
+ self.limit = call.getInt("limit") as Int?
11
+ self.offset = call.getInt("offset") as Int?
12
+ self.query = call.getString("query")
13
+ }
14
+
15
+ func getLimit() -> Int? {
16
+ return limit
17
+ }
18
+
19
+ func getOffset() -> Int? {
20
+ return offset
21
+ }
22
+
23
+ func getQuery() -> String? {
24
+ return query
25
+ }
26
+ }
@@ -0,0 +1,19 @@
1
+ import Foundation
2
+ import Capacitor
3
+
4
+ @objc public class ChannelResult: NSObject, Result {
5
+ private let id: String
6
+ private let name: String
7
+
8
+ init(id: String, name: String) {
9
+ self.id = id
10
+ self.name = name
11
+ }
12
+
13
+ public func toJSObject() -> AnyObject {
14
+ var result = JSObject()
15
+ result["id"] = id
16
+ result["name"] = name
17
+ return result as AnyObject
18
+ }
19
+ }
@@ -0,0 +1,16 @@
1
+ import Foundation
2
+ import Capacitor
3
+
4
+ @objc public class FetchChannelsResult: NSObject, Result {
5
+ private let channels: [ChannelResult]
6
+
7
+ init(channels: [ChannelResult]) {
8
+ self.channels = channels
9
+ }
10
+
11
+ public func toJSObject() -> AnyObject {
12
+ var result = JSObject()
13
+ result["channels"] = channels.map { $0.toJSObject() }
14
+ return result as AnyObject
15
+ }
16
+ }
@@ -1,10 +1,6 @@
1
1
  import Foundation
2
2
  import CryptoKit
3
- #if canImport(ZipArchive)
4
- import ZipArchive
5
- #else
6
- import SSZipArchive
7
- #endif
3
+ import ZIPFoundation
8
4
  import Capacitor
9
5
  import Alamofire
10
6
  import CommonCrypto
@@ -17,6 +13,7 @@ import CommonCrypto
17
13
  private let config: LiveUpdateConfig
18
14
  private let defaultWebAssetDir = "public" // DO NOT CHANGE! (See https://dub.sh/Buvz4yj)
19
15
  private let defaultServerPathKey = "serverBasePath" // DO NOT CHANGE! (See https://dub.sh/ceDl0zT)
16
+ private let downloadsDirectory = "capawesome_capacitor_live_update_downloads"
20
17
  private let httpClient: LiveUpdateHttpClient
21
18
  private let libraryDirectoryUrl = FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask).first!
22
19
  private let manifestFileName = "capawesome-live-update-manifest.json" // DO NOT CHANGE!
@@ -38,6 +35,12 @@ import CommonCrypto
38
35
  // Check version and reset config if version changed
39
36
  checkAndResetConfigIfVersionChanged()
40
37
 
38
+ // Set the device ID on the HTTP client (after any potential config reset)
39
+ self.httpClient.setDeviceId(getDeviceId())
40
+
41
+ // Delete the leftovers of downloads that were interrupted by a process kill
42
+ tryDeleteDownloadsDirectory()
43
+
41
44
  // Start the rollback timer to rollback to the default bundle
42
45
  // if the app is not ready after a certain time
43
46
  startRollbackTimer()
@@ -84,6 +87,36 @@ import CommonCrypto
84
87
  }
85
88
  }
86
89
 
90
+ @objc public func fetchChannels(_ options: FetchChannelsOptions) async throws -> FetchChannelsResult {
91
+ var parameters = [String: String]()
92
+ if let limit = options.getLimit() {
93
+ parameters["limit"] = String(limit)
94
+ }
95
+ if let offset = options.getOffset() {
96
+ parameters["offset"] = String(offset)
97
+ }
98
+ if let query = options.getQuery() {
99
+ parameters["query"] = query
100
+ }
101
+ var urlComponents = URLComponents(string: "https://\(config.serverDomain)/v1/apps/\(getAppId() ?? "")/channels")!
102
+ if !parameters.isEmpty {
103
+ urlComponents.queryItems = parameters.map { URLQueryItem(name: $0.key, value: $0.value) }
104
+ }
105
+ let url = try urlComponents.asURL()
106
+ let response = try await self.httpClient.request(url: url, type: [GetChannelsResponseItem].self)
107
+ if let error = response.error {
108
+ if let urlError = error.underlyingError as? URLError {
109
+ if urlError.code == .timedOut {
110
+ throw urlError
111
+ }
112
+ }
113
+ throw error
114
+ }
115
+ let items = response.value ?? []
116
+ let channels = items.map { ChannelResult(id: $0.id, name: $0.name) }
117
+ return FetchChannelsResult(channels: channels)
118
+ }
119
+
87
120
  @objc public func fetchLatestBundle(_ options: FetchLatestBundleOptions) async throws -> FetchLatestBundleResult {
88
121
  let response: GetLatestBundleResponse? = try await self.fetchLatestBundle(options)
89
122
  return FetchLatestBundleResult(artifactType: response?.artifactType, bundleId: response?.bundleId, checksum: response?.checksum, customProperties: response?.customProperties, downloadUrl: response?.url, signature: response?.signature)
@@ -323,7 +356,7 @@ import CommonCrypto
323
356
 
324
357
  private func addBundleOfTypeZip(bundleId: String, zipFile: URL) async throws {
325
358
  // Unzip the bundle
326
- let unzippedDirectory = self.unzipFile(zipFile: zipFile)
359
+ let unzippedDirectory = try self.unzipDownloadedBundle(zipFile: zipFile)
327
360
  // Add the bundle
328
361
  try self.addBundle(bundleId: bundleId, directory: unzippedDirectory)
329
362
  }
@@ -347,6 +380,10 @@ import CommonCrypto
347
380
  return url
348
381
  }
349
382
 
383
+ private func buildDownloadsDirectoryUrl() -> URL {
384
+ return cachesDirectoryUrl.appendingPathComponent(downloadsDirectory)
385
+ }
386
+
350
387
  private func copyCurrentBundleFile(fileToCopy: ManifestItem, toDirectory: URL) throws {
351
388
  let currentBundleId = getCurrentBundleId()
352
389
  let destination = toDirectory.appendingPathComponent(fileToCopy.href)
@@ -396,8 +433,8 @@ import CommonCrypto
396
433
  }
397
434
  }
398
435
 
399
- private func createTemporaryDirectory() throws -> URL {
400
- let temporaryDirectory = cachesDirectoryUrl.appendingPathComponent(UUID().uuidString)
436
+ private func createTemporaryDownloadDirectory() throws -> URL {
437
+ let temporaryDirectory = buildDownloadsDirectoryUrl().appendingPathComponent(UUID().uuidString)
401
438
  try FileManager.default.createDirectory(at: temporaryDirectory, withIntermediateDirectories: true, attributes: nil)
402
439
  return temporaryDirectory
403
440
  }
@@ -504,8 +541,9 @@ import CommonCrypto
504
541
  }
505
542
 
506
543
  private func downloadBundleOfTypeManifest(bundleId: String, url: String) async throws {
507
- // Create a temporary directory
508
- let temporaryDirectory = try createTemporaryDirectory()
544
+ // Create a temporary directory that is deleted once the download completed or failed
545
+ let temporaryDirectory = try createTemporaryDownloadDirectory()
546
+ defer { tryDeleteDirectory(temporaryDirectory) }
509
547
  // Download the latest manifest
510
548
  let latestManifestFile = try await downloadBundleFile(baseUrl: url, href: manifestFileName, directory: temporaryDirectory, callback: nil)
511
549
  let latestManifest = try loadManifest(file: latestManifestFile)
@@ -536,15 +574,17 @@ import CommonCrypto
536
574
  }
537
575
 
538
576
  private func downloadBundleOfTypeZip(bundleId: String, checksum: String?, signature: String?, url: String) async throws {
539
- let timestamp = String(Int(Date().timeIntervalSince1970))
540
- let temporaryZipFileUrl = self.cachesDirectoryUrl.appendingPathComponent(timestamp + ".zip")
577
+ // Create a temporary directory that is deleted once the download completed or failed
578
+ let temporaryDirectory = try createTemporaryDownloadDirectory()
579
+ defer { tryDeleteDirectory(temporaryDirectory) }
580
+ let zipFile = temporaryDirectory.appendingPathComponent("bundle.zip")
541
581
  // Download the bundle
542
- try await downloadAndVerifyFile(url: url, file: temporaryZipFileUrl, checksum: checksum, signature: signature, callback: { progress in
582
+ try await downloadAndVerifyFile(url: url, file: zipFile, checksum: checksum, signature: signature, callback: { progress in
543
583
  let event = DownloadBundleProgressEvent(bundleId: bundleId, downloadedBytes: progress.completedUnitCount, totalBytes: progress.totalUnitCount)
544
584
  self.notifyDownloadBundleProgressListeners(event)
545
585
  })
546
586
  // Add the bundle
547
- try await addBundleOfTypeZip(bundleId: bundleId, zipFile: temporaryZipFileUrl)
587
+ try await addBundleOfTypeZip(bundleId: bundleId, zipFile: zipFile)
548
588
  }
549
589
 
550
590
  private func fetchLatestBundle(_ options: FetchLatestBundleOptions) async throws -> GetLatestBundleResponse? {
@@ -605,6 +645,9 @@ import CommonCrypto
605
645
 
606
646
  private func getChannel() -> String? {
607
647
  var channel: String?
648
+ if let nativeChannel = getNativeChannel() {
649
+ channel = nativeChannel
650
+ }
608
651
  if let _ = config.defaultChannel {
609
652
  channel = config.defaultChannel
610
653
  }
@@ -614,6 +657,10 @@ import CommonCrypto
614
657
  return channel
615
658
  }
616
659
 
660
+ private func getNativeChannel() -> String? {
661
+ return Bundle.main.object(forInfoDictionaryKey: "CapawesomeLiveUpdateDefaultChannel") as? String
662
+ }
663
+
617
664
  /// - Returns: The sha256 checksum of the file at the given URL.
618
665
  private func getChecksumForFile(url: URL) throws -> String {
619
666
  let handle = try FileHandle(forReadingFrom: url)
@@ -923,9 +970,25 @@ import CommonCrypto
923
970
  }
924
971
  }
925
972
 
926
- private func unzipFile(zipFile: URL) -> URL {
927
- let destinationDirectory = zipFile.deletingPathExtension()
928
- SSZipArchive.unzipFile(atPath: zipFile.path, toDestination: destinationDirectory.path)
973
+ private func tryDeleteDirectory(_ url: URL) {
974
+ guard FileManager.default.fileExists(atPath: url.path) else {
975
+ return
976
+ }
977
+ do {
978
+ try FileManager.default.removeItem(at: url)
979
+ } catch {
980
+ CAPLog.print("[", LiveUpdatePlugin.tag, "] ", "Failed to delete directory: \(url.path)")
981
+ }
982
+ }
983
+
984
+ private func tryDeleteDownloadsDirectory() {
985
+ tryDeleteDirectory(buildDownloadsDirectoryUrl())
986
+ }
987
+
988
+ private func unzipDownloadedBundle(zipFile: URL) throws -> URL {
989
+ let destinationDirectory = zipFile.deletingLastPathComponent().appendingPathComponent("bundle")
990
+ try FileManager.default.createDirectory(at: destinationDirectory, withIntermediateDirectories: true, attributes: nil)
991
+ try FileManager.default.unzipItem(at: zipFile, to: destinationDirectory)
929
992
  return destinationDirectory
930
993
  }
931
994
 
@@ -5,6 +5,7 @@ import Alamofire
5
5
  public class LiveUpdateHttpClient: NSObject {
6
6
 
7
7
  private let config: LiveUpdateConfig
8
+ private var deviceId: String?
8
9
 
9
10
  public static func getChecksumFromResponse(response: HTTPURLResponse) -> String? {
10
11
  guard let headers = response.allHeaderFields as? [String: String] else { return nil }
@@ -20,10 +21,17 @@ public class LiveUpdateHttpClient: NSObject {
20
21
  self.config = config
21
22
  }
22
23
 
24
+ public func setDeviceId(_ deviceId: String) {
25
+ self.deviceId = deviceId
26
+ }
27
+
23
28
  public func download(url: URL, destination: @escaping DownloadRequest.Destination, callback: ((Progress) -> Void)?) async throws -> AFDownloadResponse<Data> {
24
29
  var request = URLRequest(url: url)
25
30
  request.httpMethod = HTTPMethod.get.rawValue
26
31
  request.timeoutInterval = Double(config.httpTimeout) / 1000.0
32
+ if let deviceId = deviceId, !deviceId.isEmpty {
33
+ request.setValue(deviceId, forHTTPHeaderField: "X-Device-Id")
34
+ }
27
35
  return try await withCheckedThrowingContinuation { continuation in
28
36
  AF.download(request, to: destination).downloadProgress { progress in
29
37
  if let callback = callback {
@@ -39,6 +47,9 @@ public class LiveUpdateHttpClient: NSObject {
39
47
  var request = URLRequest(url: url)
40
48
  request.httpMethod = HTTPMethod.get.rawValue
41
49
  request.timeoutInterval = Double(config.httpTimeout) / 1000.0
50
+ if let deviceId = deviceId, !deviceId.isEmpty {
51
+ request.setValue(deviceId, forHTTPHeaderField: "X-Device-Id")
52
+ }
42
53
  return try await withCheckedThrowingContinuation { continuation in
43
54
  AF.request(request).validate().responseDecodable(of: type) { response in
44
55
  continuation.resume(returning: response)
@@ -8,7 +8,7 @@ import Capacitor
8
8
  @objc(LiveUpdatePlugin)
9
9
  public class LiveUpdatePlugin: CAPPlugin, CAPBridgedPlugin {
10
10
  public static let tag = "LiveUpdate"
11
- public static let version = "7.4.0"
11
+ public static let version = "7.5.1"
12
12
  public static let userDefaultsPrefix = "CapawesomeLiveUpdate" // DO NOT CHANGE
13
13
 
14
14
  public let identifier = "LiveUpdatePlugin"
@@ -17,6 +17,7 @@ public class LiveUpdatePlugin: CAPPlugin, CAPBridgedPlugin {
17
17
  CAPPluginMethod(name: "clearBlockedBundles", returnType: CAPPluginReturnPromise),
18
18
  CAPPluginMethod(name: "deleteBundle", returnType: CAPPluginReturnPromise),
19
19
  CAPPluginMethod(name: "downloadBundle", returnType: CAPPluginReturnPromise),
20
+ CAPPluginMethod(name: "fetchChannels", returnType: CAPPluginReturnPromise),
20
21
  CAPPluginMethod(name: "fetchLatestBundle", returnType: CAPPluginReturnPromise),
21
22
  CAPPluginMethod(name: "getBlockedBundles", returnType: CAPPluginReturnPromise),
22
23
  CAPPluginMethod(name: "getBundles", returnType: CAPPluginReturnPromise),
@@ -111,6 +112,25 @@ public class LiveUpdatePlugin: CAPPlugin, CAPBridgedPlugin {
111
112
  }
112
113
  }
113
114
 
115
+ @objc func fetchChannels(_ call: CAPPluginCall) {
116
+ Task {
117
+ do {
118
+ guard let appId = config?.appId, !appId.isEmpty else {
119
+ call.reject(CustomError.appIdMissing.localizedDescription)
120
+ return
121
+ }
122
+
123
+ let options = FetchChannelsOptions(call)
124
+ let result = try await implementation?.fetchChannels(options)
125
+ if let result = result?.toJSObject() as? JSObject {
126
+ self.resolveCall(call, result)
127
+ }
128
+ } catch {
129
+ rejectCall(call, error)
130
+ }
131
+ }
132
+ }
133
+
114
134
  @objc func fetchLatestBundle(_ call: CAPPluginCall) {
115
135
  Task {
116
136
  do {
@@ -0,0 +1,4 @@
1
+ public struct GetChannelsResponseItem: Codable {
2
+ var id: String
3
+ var name: String
4
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capawesome/capacitor-live-update",
3
- "version": "7.4.0",
3
+ "version": "7.5.1",
4
4
  "description": "Capacitor plugin to update your app remotely in real-time.",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",