@capawesome/capacitor-live-update 6.0.8 → 6.2.0-dev.0a2a6a5.1722676620
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/README.md +34 -14
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdate.java +136 -19
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdateConfig.java +26 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdatePlugin.java +31 -9
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdatePreferences.java +17 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/api/GetLatestBundleResponse.java +29 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/options/DownloadBundleOptions.java +11 -1
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/results/GetBundleResult.java +2 -1
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/results/ReadyResult.java +32 -0
- package/dist/docs.json +117 -9
- package/dist/esm/definitions.d.ts +62 -8
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.d.ts +2 -2
- package/dist/esm/web.js.map +1 -1
- package/ios/Plugin/Classes/CustomError.swift +12 -0
- package/ios/Plugin/Classes/Options/DownloadBundleOptions.swift +7 -1
- package/ios/Plugin/Classes/Results/GetBundleResult.swift +1 -1
- package/ios/Plugin/Classes/Results/ReadyResult.swift +22 -0
- package/ios/Plugin/LiveUpdate.swift +145 -15
- package/ios/Plugin/LiveUpdateConfig.swift +2 -0
- package/ios/Plugin/LiveUpdatePlugin.swift +15 -4
- package/ios/Plugin/LiveUpdatePreferences.swift +14 -0
- package/ios/Plugin/Protocols/Api/GetLatestBundleResponse.swift +2 -0
- package/package.json +1 -1
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import Foundation
|
|
2
|
+
import CryptoKit
|
|
2
3
|
import SSZipArchive
|
|
3
4
|
import Capacitor
|
|
4
5
|
import Alamofire
|
|
6
|
+
import CommonCrypto
|
|
5
7
|
|
|
6
8
|
// swiftlint:disable type_body_length
|
|
7
9
|
@objc public class LiveUpdate: NSObject {
|
|
10
|
+
public static let defaultWebAssetDir = "public" // DO NOT CHANGE (See https://dub.sh/Buvz4yj)
|
|
11
|
+
|
|
8
12
|
private let cachesDirectoryUrl = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
|
|
9
13
|
private let config: LiveUpdateConfig
|
|
10
|
-
private let defaultWebAssetDir = "public" // DO NOT CHANGE (See https://dub.sh/Buvz4yj)
|
|
11
14
|
private let defaultServerPathKey = "serverBasePath" // DO NOT CHANGE (See https://dub.sh/ceDl0zT)
|
|
12
15
|
private let libraryDirectoryUrl = FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask).first!
|
|
13
16
|
private let plugin: LiveUpdatePlugin
|
|
@@ -15,6 +18,7 @@ import Alamofire
|
|
|
15
18
|
private let preferences: LiveUpdatePreferences
|
|
16
19
|
|
|
17
20
|
private var rollbackDispatchWorkItem: DispatchWorkItem?
|
|
21
|
+
private var rollbackPerformed = false
|
|
18
22
|
|
|
19
23
|
init(config: LiveUpdateConfig, plugin: LiveUpdatePlugin) {
|
|
20
24
|
self.config = config
|
|
@@ -50,8 +54,9 @@ import Alamofire
|
|
|
50
54
|
}
|
|
51
55
|
|
|
52
56
|
@objc public func downloadBundle(_ options: DownloadBundleOptions, completion: @escaping (Error?) -> Void) {
|
|
53
|
-
let url = options.getUrl()
|
|
54
57
|
let bundleId = options.getBundleId()
|
|
58
|
+
let checksum = options.getChecksum()
|
|
59
|
+
let url = options.getUrl()
|
|
55
60
|
|
|
56
61
|
// Check if the bundle already exists
|
|
57
62
|
if hasBundle(bundleId: bundleId) {
|
|
@@ -61,7 +66,7 @@ import Alamofire
|
|
|
61
66
|
}
|
|
62
67
|
|
|
63
68
|
// Download the bundle
|
|
64
|
-
downloadBundle(bundleId: bundleId, url: url, completion: { error in
|
|
69
|
+
downloadBundle(bundleId: bundleId, checksum: checksum, signature: nil, url: url, completion: { error in
|
|
65
70
|
if let error = error {
|
|
66
71
|
completion(error)
|
|
67
72
|
return
|
|
@@ -71,10 +76,7 @@ import Alamofire
|
|
|
71
76
|
}
|
|
72
77
|
|
|
73
78
|
@objc public func getBundle(completion: @escaping (Result?, Error?) -> Void) {
|
|
74
|
-
|
|
75
|
-
if bundleId == defaultWebAssetDir {
|
|
76
|
-
bundleId = nil
|
|
77
|
-
}
|
|
79
|
+
let bundleId = getCurrentBundleId()
|
|
78
80
|
let result = GetBundleResult(bundleId: bundleId)
|
|
79
81
|
completion(result, nil)
|
|
80
82
|
}
|
|
@@ -116,12 +118,18 @@ import Alamofire
|
|
|
116
118
|
completion(result, nil)
|
|
117
119
|
}
|
|
118
120
|
|
|
119
|
-
@objc public func ready() {
|
|
121
|
+
@objc public func ready(completion: @escaping (Result?, Error?) -> Void) {
|
|
120
122
|
CAPLog.print("[", LiveUpdatePlugin.tag, "] ", "App is ready.")
|
|
121
123
|
stopRollbackTimer()
|
|
122
124
|
if config.autoDeleteBundles {
|
|
123
125
|
deleteUnusedBundles()
|
|
124
126
|
}
|
|
127
|
+
let currentBundleId = getCurrentBundleId()
|
|
128
|
+
let previousBundleId = getPreviousBundleId()
|
|
129
|
+
let result = ReadyResult(currentBundleId: currentBundleId, previousBundleId: previousBundleId, rollback: rollbackPerformed)
|
|
130
|
+
completion(result, nil)
|
|
131
|
+
setPreviousBundleId(bundleId: currentBundleId)
|
|
132
|
+
rollbackPerformed = false
|
|
125
133
|
}
|
|
126
134
|
|
|
127
135
|
@objc public func reload() {
|
|
@@ -187,7 +195,7 @@ import Alamofire
|
|
|
187
195
|
completion(result, nil)
|
|
188
196
|
} else {
|
|
189
197
|
// Download and unzip the bundle
|
|
190
|
-
self.downloadBundle(bundleId: latestBundleId, url: response.url, completion: { error in
|
|
198
|
+
self.downloadBundle(bundleId: latestBundleId, checksum: response.checksum, signature: response.signature, url: response.url, completion: { error in
|
|
191
199
|
if let error = error {
|
|
192
200
|
completion(nil, error)
|
|
193
201
|
return
|
|
@@ -241,6 +249,20 @@ import Alamofire
|
|
|
241
249
|
return url.path
|
|
242
250
|
}
|
|
243
251
|
|
|
252
|
+
/// - Returns: The sha256 checksum of the file at the given URL.
|
|
253
|
+
private func getChecksumForFile(url: URL) throws -> String {
|
|
254
|
+
let handle = try FileHandle(forReadingFrom: url)
|
|
255
|
+
var hasher = SHA256()
|
|
256
|
+
while autoreleasepool(invoking: {
|
|
257
|
+
let nextChunk = handle.readData(ofLength: 2048)
|
|
258
|
+
guard !nextChunk.isEmpty else { return false }
|
|
259
|
+
hasher.update(data: nextChunk)
|
|
260
|
+
return true
|
|
261
|
+
}) { }
|
|
262
|
+
let digest = hasher.finalize()
|
|
263
|
+
return digest.map { String(format: "%02hhx", $0) }.joined()
|
|
264
|
+
}
|
|
265
|
+
|
|
244
266
|
private func createBundlesDirectory() {
|
|
245
267
|
let bundlesDirectoryUrl = libraryDirectoryUrl.appendingPathComponent(bundlesDirectory)
|
|
246
268
|
let exists = FileManager.default.fileExists(atPath: bundlesDirectoryUrl.path)
|
|
@@ -274,7 +296,7 @@ import Alamofire
|
|
|
274
296
|
}
|
|
275
297
|
}
|
|
276
298
|
|
|
277
|
-
private func downloadBundle(bundleId: String, url: String, completion: @escaping (Error?) -> Void) {
|
|
299
|
+
private func downloadBundle(bundleId: String, checksum: String?, signature: String?, url: String, completion: @escaping (Error?) -> Void) {
|
|
278
300
|
// Download the bundle
|
|
279
301
|
downloadFile(url: url, completion: { url, error in
|
|
280
302
|
if let error = error {
|
|
@@ -282,6 +304,39 @@ import Alamofire
|
|
|
282
304
|
return
|
|
283
305
|
}
|
|
284
306
|
if let url = url {
|
|
307
|
+
// Verify the signature
|
|
308
|
+
if let publicKey = self.config.publicKey {
|
|
309
|
+
guard let signature = signature else {
|
|
310
|
+
completion(CustomError.signatureMissing)
|
|
311
|
+
return
|
|
312
|
+
}
|
|
313
|
+
// Verify the signature
|
|
314
|
+
do {
|
|
315
|
+
let verified = try self.verifySignatureForFile(url: url, signature: signature, publicKey: publicKey)
|
|
316
|
+
if !verified {
|
|
317
|
+
completion(CustomError.signatureVerificationFailed)
|
|
318
|
+
return
|
|
319
|
+
}
|
|
320
|
+
} catch {
|
|
321
|
+
completion(CustomError.signatureVerificationFailed)
|
|
322
|
+
return
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
// Verify the checksum
|
|
326
|
+
else if let expectedChecksum = checksum {
|
|
327
|
+
// Calculate the checksum
|
|
328
|
+
do {
|
|
329
|
+
let receivedChecksum = try self.getChecksumForFile(url: url)
|
|
330
|
+
if receivedChecksum != expectedChecksum {
|
|
331
|
+
completion(CustomError.checksumMismatch)
|
|
332
|
+
return
|
|
333
|
+
}
|
|
334
|
+
} catch {
|
|
335
|
+
completion(CustomError.checksumCalculationFailed)
|
|
336
|
+
return
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
285
340
|
// Add the bundle
|
|
286
341
|
self.addBundle(bundleId: bundleId, zipFile: url, completion: { error in
|
|
287
342
|
// Delete the temporary file
|
|
@@ -328,7 +383,13 @@ import Alamofire
|
|
|
328
383
|
parameters["osVersion"] = UIDevice.current.systemVersion
|
|
329
384
|
parameters["platform"] = "1"
|
|
330
385
|
parameters["pluginVersion"] = LiveUpdatePlugin.version
|
|
331
|
-
|
|
386
|
+
var host = "api.cloud.capawesome.io"
|
|
387
|
+
if let location = config.location {
|
|
388
|
+
if location == "eu" {
|
|
389
|
+
host = "api.cloud.capawesome.eu"
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
let url = URL(string: "https://\(host)/v1/apps/\(config.appId ?? "")/bundles/latest")!
|
|
332
393
|
AF.request(url, method: .get, parameters: parameters).validate().responseDecodable(of: GetLatestBundleResponse.self) { response in
|
|
333
394
|
CAPLog.print("[", LiveUpdatePlugin.tag, "] Fetching latest bundle from ", response.request?.url?.absoluteString ?? "")
|
|
334
395
|
if let error = response.error {
|
|
@@ -400,20 +461,27 @@ import Alamofire
|
|
|
400
461
|
|
|
401
462
|
/// - Returns: The absolute path to the next bundle directory.
|
|
402
463
|
private func getNextCapacitorServerPath() -> String {
|
|
403
|
-
let defaultCapacitorServerPath = buildCapacitorServerPathFor(bundleId: defaultWebAssetDir)
|
|
464
|
+
let defaultCapacitorServerPath = buildCapacitorServerPathFor(bundleId: LiveUpdate.defaultWebAssetDir)
|
|
404
465
|
if let path = KeyValueStore.standard[self.defaultServerPathKey, as: String.self] {
|
|
405
466
|
return path.isEmpty ? defaultCapacitorServerPath : path
|
|
406
467
|
}
|
|
407
468
|
return defaultCapacitorServerPath
|
|
408
469
|
}
|
|
409
470
|
|
|
471
|
+
private func getPreviousBundleId() -> String? {
|
|
472
|
+
return preferences.getPreviousBundleId()
|
|
473
|
+
}
|
|
474
|
+
|
|
410
475
|
private func hasBundle(bundleId: String) -> Bool {
|
|
411
476
|
let path = buildBundlePathFor(bundleId: bundleId)
|
|
412
477
|
return FileManager.default.fileExists(atPath: path)
|
|
413
478
|
}
|
|
414
479
|
|
|
415
480
|
private func rollback() {
|
|
416
|
-
|
|
481
|
+
rollbackPerformed = true
|
|
482
|
+
let currentBundleId = getCurrentBundleId()
|
|
483
|
+
setPreviousBundleId(bundleId: currentBundleId)
|
|
484
|
+
if currentBundleId == LiveUpdate.defaultWebAssetDir {
|
|
417
485
|
CAPLog.print("[", LiveUpdatePlugin.tag, "] ", "App is not ready. Default bundle is already in use.")
|
|
418
486
|
return
|
|
419
487
|
}
|
|
@@ -447,7 +515,7 @@ import Alamofire
|
|
|
447
515
|
}
|
|
448
516
|
|
|
449
517
|
private func setCurrentCapacitorServerPathToDefaultWebAssetDir() {
|
|
450
|
-
let path = buildCapacitorServerPathFor(bundleId: defaultWebAssetDir)
|
|
518
|
+
let path = buildCapacitorServerPathFor(bundleId: LiveUpdate.defaultWebAssetDir)
|
|
451
519
|
setCurrentCapacitorServerPath(path: path)
|
|
452
520
|
}
|
|
453
521
|
|
|
@@ -467,10 +535,14 @@ import Alamofire
|
|
|
467
535
|
}
|
|
468
536
|
|
|
469
537
|
private func setNextCapacitorServerPathToDefaultWebAssetDir() {
|
|
470
|
-
let path = buildCapacitorServerPathFor(bundleId: defaultWebAssetDir)
|
|
538
|
+
let path = buildCapacitorServerPathFor(bundleId: LiveUpdate.defaultWebAssetDir)
|
|
471
539
|
setNextCapacitorServerPath(path: path)
|
|
472
540
|
}
|
|
473
541
|
|
|
542
|
+
private func setPreviousBundleId(bundleId: String?) {
|
|
543
|
+
preferences.setPreviousBundleId(bundleId)
|
|
544
|
+
}
|
|
545
|
+
|
|
474
546
|
private func startRollbackTimer() {
|
|
475
547
|
stopRollbackTimer()
|
|
476
548
|
rollbackDispatchWorkItem = DispatchWorkItem { [weak self] in
|
|
@@ -492,6 +564,64 @@ import Alamofire
|
|
|
492
564
|
})
|
|
493
565
|
}
|
|
494
566
|
|
|
567
|
+
private func verifySignatureForFile(url: URL, signature: String, publicKey: String) throws -> Bool {
|
|
568
|
+
let publicKeyAsBase64 = publicKey
|
|
569
|
+
.replacingOccurrences(of: "-----BEGIN PUBLIC KEY-----", with: "")
|
|
570
|
+
.replacingOccurrences(of: "-----END PUBLIC KEY-----", with: "")
|
|
571
|
+
.replacingOccurrences(of: "\n", with: "")
|
|
572
|
+
guard let publicKeyData = Data(base64Encoded: publicKeyAsBase64) else {
|
|
573
|
+
CAPLog.print("[", LiveUpdatePlugin.tag, "] ", "Failed to decode public key.")
|
|
574
|
+
return false
|
|
575
|
+
}
|
|
576
|
+
let publicKeyAttributes: [CFString: Any] = [
|
|
577
|
+
kSecAttrKeyType: kSecAttrKeyTypeRSA,
|
|
578
|
+
kSecAttrKeyClass: kSecAttrKeyClassPublic,
|
|
579
|
+
kSecAttrKeySizeInBits: 2048,
|
|
580
|
+
kSecReturnPersistentRef: true
|
|
581
|
+
]
|
|
582
|
+
var secKeyCreateWithDataError: Unmanaged<CFError>?
|
|
583
|
+
guard let publicKey = SecKeyCreateWithData(publicKeyData as CFData, publicKeyAttributes as CFDictionary, &secKeyCreateWithDataError) else {
|
|
584
|
+
if let error = secKeyCreateWithDataError?.takeRetainedValue() {
|
|
585
|
+
CAPLog.print("[", LiveUpdatePlugin.tag, "] ", "Failed to create public key with error: \(error)")
|
|
586
|
+
}
|
|
587
|
+
return false
|
|
588
|
+
}
|
|
589
|
+
guard let signatureData = Data(base64Encoded: signature) else {
|
|
590
|
+
CAPLog.print("[", LiveUpdatePlugin.tag, "] ", "Failed to decode signature.")
|
|
591
|
+
return false
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// Create SHA256 digest
|
|
595
|
+
var digestContext = CC_SHA256_CTX()
|
|
596
|
+
CC_SHA256_Init(&digestContext)
|
|
597
|
+
|
|
598
|
+
// Update the digest with the file's data
|
|
599
|
+
let handle = try FileHandle(forReadingFrom: url)
|
|
600
|
+
while autoreleasepool(invoking: {
|
|
601
|
+
let nextChunk = handle.readData(ofLength: 2048)
|
|
602
|
+
guard !nextChunk.isEmpty else { return false }
|
|
603
|
+
nextChunk.withUnsafeBytes {
|
|
604
|
+
_ = CC_SHA256_Update(&digestContext, $0.baseAddress, CC_LONG(nextChunk.count))
|
|
605
|
+
}
|
|
606
|
+
return true
|
|
607
|
+
}) { }
|
|
608
|
+
|
|
609
|
+
// Compute the digest
|
|
610
|
+
var digest = Data(count: Int(CC_SHA256_DIGEST_LENGTH))
|
|
611
|
+
digest.withUnsafeMutableBytes {
|
|
612
|
+
_ = CC_SHA256_Final($0.bindMemory(to: UInt8.self).baseAddress, &digestContext)
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
// Verify the signature
|
|
616
|
+
var secKeyVerifySignatureError: Unmanaged<CFError>?
|
|
617
|
+
let signatureAlgorithm = SecKeyAlgorithm.rsaSignatureDigestPKCS1v15SHA256
|
|
618
|
+
let verificationResult = SecKeyVerifySignature(publicKey, signatureAlgorithm, digest as CFData, signatureData as CFData, &secKeyVerifySignatureError)
|
|
619
|
+
if let error = secKeyVerifySignatureError?.takeRetainedValue() {
|
|
620
|
+
CAPLog.print("[", LiveUpdatePlugin.tag, "] ", "Failed to verify signature with error: \(error)")
|
|
621
|
+
}
|
|
622
|
+
return verificationResult
|
|
623
|
+
}
|
|
624
|
+
|
|
495
625
|
private func wasUpdated() -> Bool {
|
|
496
626
|
guard let lastBundleVersionString = preferences.getLastBundleVersion() else {
|
|
497
627
|
return false
|
|
@@ -8,7 +8,7 @@ import Capacitor
|
|
|
8
8
|
@objc(LiveUpdatePlugin)
|
|
9
9
|
public class LiveUpdatePlugin: CAPPlugin {
|
|
10
10
|
public static let tag = "LiveUpdate"
|
|
11
|
-
public static let version = "6.0.
|
|
11
|
+
public static let version = "6.2.0-dev.0a2a6a5.1722676620"
|
|
12
12
|
public static let userDefaultsPrefix = "CapawesomeLiveUpdate" // DO NOT CHANGE
|
|
13
13
|
|
|
14
14
|
private var config: LiveUpdateConfig?
|
|
@@ -42,12 +42,13 @@ public class LiveUpdatePlugin: CAPPlugin {
|
|
|
42
42
|
call.reject(CustomError.bundleIdMissing.localizedDescription)
|
|
43
43
|
return
|
|
44
44
|
}
|
|
45
|
+
let checksum = call.getString("checksum")
|
|
45
46
|
guard let url = call.getString("url") else {
|
|
46
47
|
call.reject(CustomError.urlMissing.localizedDescription)
|
|
47
48
|
return
|
|
48
49
|
}
|
|
49
50
|
|
|
50
|
-
let options = DownloadBundleOptions(bundleId: bundleId, url: url)
|
|
51
|
+
let options = DownloadBundleOptions(bundleId: bundleId, checksum: checksum, url: url)
|
|
51
52
|
|
|
52
53
|
implementation?.downloadBundle(options, completion: { error in
|
|
53
54
|
if let error = error {
|
|
@@ -151,8 +152,16 @@ public class LiveUpdatePlugin: CAPPlugin {
|
|
|
151
152
|
}
|
|
152
153
|
|
|
153
154
|
@objc func ready(_ call: CAPPluginCall) {
|
|
154
|
-
implementation?.ready(
|
|
155
|
-
|
|
155
|
+
implementation?.ready(completion: { result, error in
|
|
156
|
+
if let error = error {
|
|
157
|
+
CAPLog.print("[", LiveUpdatePlugin.tag, "] ", error)
|
|
158
|
+
call.reject(error.localizedDescription)
|
|
159
|
+
return
|
|
160
|
+
}
|
|
161
|
+
if let result = result?.toJSObject() as? JSObject {
|
|
162
|
+
call.resolve(result)
|
|
163
|
+
}
|
|
164
|
+
})
|
|
156
165
|
}
|
|
157
166
|
|
|
158
167
|
@objc func reload(_ call: CAPPluginCall) {
|
|
@@ -239,6 +248,8 @@ public class LiveUpdatePlugin: CAPPlugin {
|
|
|
239
248
|
config.appId = getConfig().getString("appId", config.appId)
|
|
240
249
|
config.autoDeleteBundles = getConfig().getBoolean("autoDeleteBundles", config.autoDeleteBundles)
|
|
241
250
|
config.enabled = getConfig().getBoolean("enabled", config.enabled)
|
|
251
|
+
config.location = getConfig().getString("location", config.location)
|
|
252
|
+
config.publicKey = getConfig().getString("publicKey", config.publicKey)
|
|
242
253
|
config.readyTimeout = getConfig().getInt("readyTimeout", config.readyTimeout)
|
|
243
254
|
config.resetOnUpdate = getConfig().getBoolean("resetOnUpdate", config.resetOnUpdate)
|
|
244
255
|
|
|
@@ -4,6 +4,7 @@ public class LiveUpdatePreferences: NSObject {
|
|
|
4
4
|
private let channelKey = "channel" // DO NOT CHANGE
|
|
5
5
|
private let customIdKey = "customId" // DO NOT CHANGE
|
|
6
6
|
private let lastBundleVersionKey = "lastBundleVersion" // DO NOT CHANGE
|
|
7
|
+
private let previousBundleIdKey = "previousBundleIdKey" // DO NOT CHANGE
|
|
7
8
|
|
|
8
9
|
public func getChannel() -> String? {
|
|
9
10
|
return UserDefaults.standard.string(forKey: applyPrefix(to: channelKey))
|
|
@@ -17,6 +18,10 @@ public class LiveUpdatePreferences: NSObject {
|
|
|
17
18
|
return UserDefaults.standard.string(forKey: applyPrefix(to: lastBundleVersionKey))
|
|
18
19
|
}
|
|
19
20
|
|
|
21
|
+
public func getPreviousBundleId() -> String? {
|
|
22
|
+
return UserDefaults.standard.string(forKey: applyPrefix(to: previousBundleIdKey))
|
|
23
|
+
}
|
|
24
|
+
|
|
20
25
|
public func setChannel(_ value: String?) {
|
|
21
26
|
if let value = value {
|
|
22
27
|
UserDefaults.standard.set(value, forKey: applyPrefix(to: channelKey))
|
|
@@ -36,6 +41,15 @@ public class LiveUpdatePreferences: NSObject {
|
|
|
36
41
|
UserDefaults.standard.synchronize()
|
|
37
42
|
}
|
|
38
43
|
|
|
44
|
+
public func setPreviousBundleId(_ value: String?) {
|
|
45
|
+
if let value = value {
|
|
46
|
+
UserDefaults.standard.set(value, forKey: applyPrefix(to: previousBundleIdKey))
|
|
47
|
+
} else {
|
|
48
|
+
UserDefaults.standard.removeObject(forKey: applyPrefix(to: previousBundleIdKey))
|
|
49
|
+
}
|
|
50
|
+
UserDefaults.standard.synchronize()
|
|
51
|
+
}
|
|
52
|
+
|
|
39
53
|
private func applyPrefix(to key: String) -> String {
|
|
40
54
|
return LiveUpdatePlugin.userDefaultsPrefix + "." + key
|
|
41
55
|
}
|
package/package.json
CHANGED