@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.
- package/CapawesomeCapacitorLiveUpdate.podspec +1 -1
- package/Package.swift +2 -2
- package/README.md +96 -1
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdate.java +171 -58
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdateHttpClient.java +12 -1
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdatePlugin.java +32 -1
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/api/GetChannelsResponseItem.java +28 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/options/FetchChannelsOptions.java +38 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/results/ChannelResult.java +28 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/results/FetchChannelsResult.java +28 -0
- package/dist/docs.json +150 -2
- package/dist/esm/definitions.d.ts +83 -0
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.d.ts +2 -1
- package/dist/esm/web.js +3 -0
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +3 -0
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +3 -0
- package/dist/plugin.js.map +1 -1
- package/ios/Plugin/Classes/Options/FetchChannelsOptions.swift +26 -0
- package/ios/Plugin/Classes/Results/ChannelResult.swift +19 -0
- package/ios/Plugin/Classes/Results/FetchChannelsResult.swift +16 -0
- package/ios/Plugin/LiveUpdate.swift +80 -17
- package/ios/Plugin/LiveUpdateHttpClient.swift +11 -0
- package/ios/Plugin/LiveUpdatePlugin.swift +21 -1
- package/ios/Plugin/Protocols/Api/GetChannelsResponseItem.swift +4 -0
- package/package.json +1 -1
package/dist/plugin.js.map
CHANGED
|
@@ -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":";;;AACK,UAAC,UAAU,GAAGA,mBAAc,CAAC,YAAY,EAAE;IAChD,IAAI,GAAG,EAAE,MAAM,mDAAe,CAAC,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,aAAa,EAAE,CAAC;IAC/D,CAAC;;ICFM,MAAM,aAAa,SAASC,cAAS,CAAC;IAC7C,IAAI,MAAM,mBAAmB,GAAG;IAChC,QAAQ,IAAI,CAAC,uBAAuB,EAAE;IACtC;IACA,IAAI,MAAM,YAAY,CAAC,QAAQ,EAAE;IACjC,QAAQ,IAAI,CAAC,uBAAuB,EAAE;IACtC;IACA,IAAI,MAAM,cAAc,CAAC,QAAQ,EAAE;IACnC,QAAQ,IAAI,CAAC,uBAAuB,EAAE;IACtC;IACA,IAAI,MAAM,aAAa,GAAG;IAC1B,QAAQ,IAAI,CAAC,uBAAuB,EAAE;IACtC;IACA,IAAI,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,IAAI,CAAC,uBAAuB,EAAE;IACtC;IACA,IAAI,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,IAAI,CAAC,uBAAuB,EAAE;IACtC;IACA,IAAI,MAAM,SAAS,GAAG;IACtB,QAAQ,IAAI,CAAC,uBAAuB,EAAE;IACtC;IACA,IAAI,MAAM,UAAU,GAAG;IACvB,QAAQ,IAAI,CAAC,uBAAuB,EAAE;IACtC;IACA,IAAI,MAAM,oBAAoB,GAAG;IACjC,QAAQ,IAAI,CAAC,uBAAuB,EAAE;IACtC;IACA,IAAI,MAAM,UAAU,GAAG;IACvB,QAAQ,IAAI,CAAC,uBAAuB,EAAE;IACtC;IACA,IAAI,MAAM,SAAS,GAAG;IACtB,QAAQ,IAAI,CAAC,uBAAuB,EAAE;IACtC;IACA,IAAI,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,IAAI,CAAC,uBAAuB,EAAE;IACtC;IACA,IAAI,MAAM,WAAW,GAAG;IACxB,QAAQ,IAAI,CAAC,uBAAuB,EAAE;IACtC;IACA,IAAI,MAAM,WAAW,GAAG;IACxB,QAAQ,IAAI,CAAC,uBAAuB,EAAE;IACtC;IACA,IAAI,MAAM,aAAa,GAAG;IAC1B,QAAQ,IAAI,CAAC,uBAAuB,EAAE;IACtC;IACA,IAAI,MAAM,cAAc,GAAG;IAC3B,QAAQ,IAAI,CAAC,uBAAuB,EAAE;IACtC;IACA,IAAI,MAAM,cAAc,GAAG;IAC3B,QAAQ,IAAI,CAAC,uBAAuB,EAAE;IACtC;IACA,IAAI,MAAM,SAAS,GAAG;IACtB,QAAQ,IAAI,CAAC,uBAAuB,EAAE;IACtC;IACA,IAAI,MAAM,KAAK,GAAG;IAClB,QAAQ,IAAI,CAAC,uBAAuB,EAAE;IACtC;IACA,IAAI,MAAM,MAAM,GAAG;IACnB,QAAQ,IAAI,CAAC,uBAAuB,EAAE;IACtC;IACA,IAAI,MAAM,KAAK,GAAG;IAClB,QAAQ,IAAI,CAAC,uBAAuB,EAAE;IACtC;IACA,IAAI,MAAM,WAAW,GAAG;IACxB,QAAQ,IAAI,CAAC,uBAAuB,EAAE;IACtC;IACA,IAAI,MAAM,SAAS,CAAC,QAAQ,EAAE;IAC9B,QAAQ,IAAI,CAAC,uBAAuB,EAAE;IACtC;IACA,IAAI,MAAM,UAAU,CAAC,QAAQ,EAAE;IAC/B,QAAQ,IAAI,CAAC,uBAAuB,EAAE;IACtC;IACA,IAAI,MAAM,SAAS,CAAC,QAAQ,EAAE;IAC9B,QAAQ,IAAI,CAAC,uBAAuB,EAAE;IACtC;IACA,IAAI,MAAM,WAAW,CAAC,QAAQ,EAAE;IAChC,QAAQ,IAAI,CAAC,uBAAuB,EAAE;IACtC;IACA,IAAI,MAAM,aAAa,CAAC,QAAQ,EAAE;IAClC,QAAQ,IAAI,CAAC,uBAAuB,EAAE;IACtC;IACA,IAAI,MAAM,IAAI,GAAG;IACjB,QAAQ,IAAI,CAAC,uBAAuB,EAAE;IACtC;IACA,IAAI,uBAAuB,GAAG;IAC9B,QAAQ,MAAM,IAAI,CAAC,aAAa,CAAC,yBAAyB,CAAC;IAC3D;IACA;;;;;;;;;;;;;;;"}
|
|
@@ -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
|
-
|
|
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.
|
|
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
|
|
400
|
-
let temporaryDirectory =
|
|
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
|
|
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
|
-
|
|
540
|
-
let
|
|
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:
|
|
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:
|
|
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
|
|
927
|
-
|
|
928
|
-
|
|
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.
|
|
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 {
|