@capgo/capacitor-updater 8.45.10 → 8.45.11
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 +74 -28
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdaterPlugin.java +147 -50
- package/android/src/main/java/ee/forgr/capacitor_updater/CapgoUpdater.java +40 -16
- package/android/src/main/java/ee/forgr/capacitor_updater/ShakeMenu.java +27 -3
- package/dist/docs.json +163 -4
- package/dist/esm/definitions.d.ts +82 -19
- package/dist/esm/definitions.js.map +1 -1
- package/ios/Sources/CapacitorUpdaterPlugin/BundleInfo.swift +2 -2
- package/ios/Sources/CapacitorUpdaterPlugin/BundleStatus.swift +78 -2
- package/ios/Sources/CapacitorUpdaterPlugin/CapacitorUpdaterPlugin.swift +289 -94
- package/ios/Sources/CapacitorUpdaterPlugin/CapgoUpdater.swift +672 -300
- package/ios/Sources/CapacitorUpdaterPlugin/InternalUtils.swift +31 -0
- package/ios/Sources/CapacitorUpdaterPlugin/ShakeMenu.swift +20 -3
- package/package.json +3 -2
|
@@ -64,6 +64,8 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
64
64
|
CAPPluginMethod(name: "isShakeMenuEnabled", returnType: CAPPluginReturnPromise),
|
|
65
65
|
CAPPluginMethod(name: "setShakeChannelSelector", returnType: CAPPluginReturnPromise),
|
|
66
66
|
CAPPluginMethod(name: "isShakeChannelSelectorEnabled", returnType: CAPPluginReturnPromise),
|
|
67
|
+
CAPPluginMethod(name: "getAppId", returnType: CAPPluginReturnPromise),
|
|
68
|
+
CAPPluginMethod(name: "setAppId", returnType: CAPPluginReturnPromise),
|
|
67
69
|
// App Store update methods
|
|
68
70
|
CAPPluginMethod(name: "getAppUpdateInfo", returnType: CAPPluginReturnPromise),
|
|
69
71
|
CAPPluginMethod(name: "openAppStore", returnType: CAPPluginReturnPromise),
|
|
@@ -72,7 +74,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
72
74
|
CAPPluginMethod(name: "completeFlexibleUpdate", returnType: CAPPluginReturnPromise)
|
|
73
75
|
]
|
|
74
76
|
public var implementation = CapgoUpdater()
|
|
75
|
-
private let pluginVersion: String = "8.45.
|
|
77
|
+
private let pluginVersion: String = "8.45.11"
|
|
76
78
|
static let updateUrlDefault = "https://plugin.capgo.app/updates"
|
|
77
79
|
static let statsUrlDefault = "https://plugin.capgo.app/stats"
|
|
78
80
|
static let channelUrlDefault = "https://plugin.capgo.app/channel_self"
|
|
@@ -233,7 +235,16 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
233
235
|
implementation.setPublicKey(getConfig().getString("publicKey") ?? "")
|
|
234
236
|
implementation.notifyDownloadRaw = notifyDownload
|
|
235
237
|
implementation.notifyListeners = { [weak self] eventName, data in
|
|
236
|
-
|
|
238
|
+
let emit = {
|
|
239
|
+
self?.notifyListeners(eventName, data: data)
|
|
240
|
+
}
|
|
241
|
+
if Thread.isMainThread {
|
|
242
|
+
emit()
|
|
243
|
+
} else {
|
|
244
|
+
DispatchQueue.main.async {
|
|
245
|
+
emit()
|
|
246
|
+
}
|
|
247
|
+
}
|
|
237
248
|
}
|
|
238
249
|
implementation.pluginVersion = self.pluginVersion
|
|
239
250
|
|
|
@@ -279,11 +290,12 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
279
290
|
}
|
|
280
291
|
self.implementation.autoReset()
|
|
281
292
|
|
|
282
|
-
// Check if app was recently installed/updated BEFORE
|
|
293
|
+
// Check if app was recently installed/updated BEFORE cleanup updates the stored native build version.
|
|
283
294
|
self.wasRecentlyInstalledOrUpdated = self.checkIfRecentlyInstalledOrUpdated()
|
|
284
295
|
|
|
285
296
|
if resetWhenUpdate {
|
|
286
|
-
self.
|
|
297
|
+
let didResetCurrentBundle = self.resetCurrentBundleForNativeBuildChangeIfNeeded()
|
|
298
|
+
self.cleanupObsoleteVersions(didResetCurrentBundle: didResetCurrentBundle)
|
|
287
299
|
}
|
|
288
300
|
|
|
289
301
|
// Load the server
|
|
@@ -399,7 +411,29 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
399
411
|
semaphoreReady.signal()
|
|
400
412
|
}
|
|
401
413
|
|
|
402
|
-
|
|
414
|
+
func storedNativeBuildVersion() -> String {
|
|
415
|
+
UserDefaults.standard.string(forKey: "LatestNativeBuildVersion") ?? UserDefaults.standard.string(forKey: "LatestVersionNative") ?? "0"
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
func hasNativeBuildVersionChanged() -> Bool {
|
|
419
|
+
let previous = self.storedNativeBuildVersion()
|
|
420
|
+
return previous != "0" && self.currentBuildVersion != previous
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
@discardableResult
|
|
424
|
+
func resetCurrentBundleForNativeBuildChangeIfNeeded() -> Bool {
|
|
425
|
+
let previous = self.storedNativeBuildVersion()
|
|
426
|
+
guard previous != "0" && self.currentBuildVersion != previous else {
|
|
427
|
+
return false
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Reset startup state synchronously so initialLoad() boots from the builtin bundle.
|
|
431
|
+
self.logger.info("Native build version changed from \(previous) to \(self.currentBuildVersion). Resetting startup bundle to builtin.")
|
|
432
|
+
self.implementation.reset(isInternal: true)
|
|
433
|
+
return true
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
private func cleanupObsoleteVersions(didResetCurrentBundle: Bool = false) {
|
|
403
437
|
cleanupThread = Thread {
|
|
404
438
|
self.cleanupLock.lock()
|
|
405
439
|
defer {
|
|
@@ -434,9 +468,12 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
434
468
|
// 1. Write "LatestVersionNative" - this fixes the part 1 of this bug
|
|
435
469
|
// 2. Compare both keys. If any is not equal to "currentBuildVersion", then revert to builtin version. This fixes the part 2 of this bug
|
|
436
470
|
|
|
437
|
-
let previous =
|
|
471
|
+
let previous = self.storedNativeBuildVersion()
|
|
438
472
|
if previous != "0" && self.currentBuildVersion != previous {
|
|
439
|
-
|
|
473
|
+
if !didResetCurrentBundle {
|
|
474
|
+
self.logger.info("Native build version changed from \(previous) to \(self.currentBuildVersion). Resetting current bundle to builtin.")
|
|
475
|
+
self.implementation.reset(isInternal: true)
|
|
476
|
+
}
|
|
440
477
|
let res = self.implementation.list()
|
|
441
478
|
for version in res {
|
|
442
479
|
// Check if thread was cancelled
|
|
@@ -496,11 +533,88 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
496
533
|
logger.info("Cleanup finished, proceeding with download")
|
|
497
534
|
}
|
|
498
535
|
|
|
536
|
+
private func resolveCall(_ call: CAPPluginCall, data: PluginCallResultData? = nil) {
|
|
537
|
+
let resolve = {
|
|
538
|
+
let savedCall = self.bridge?.savedCall(withID: call.callbackId)
|
|
539
|
+
let targetCall = savedCall ?? call
|
|
540
|
+
|
|
541
|
+
if let data {
|
|
542
|
+
targetCall.resolve(data)
|
|
543
|
+
} else {
|
|
544
|
+
targetCall.resolve()
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
if savedCall != nil {
|
|
548
|
+
self.bridge?.releaseCall(withID: call.callbackId)
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
if Thread.isMainThread {
|
|
553
|
+
resolve()
|
|
554
|
+
} else {
|
|
555
|
+
DispatchQueue.main.async {
|
|
556
|
+
resolve()
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
private func rejectCall(_ call: CAPPluginCall, message: String, code: String? = nil, error: Error? = nil, data: PluginCallResultData? = nil) {
|
|
562
|
+
let reject = {
|
|
563
|
+
let savedCall = self.bridge?.savedCall(withID: call.callbackId)
|
|
564
|
+
let targetCall = savedCall ?? call
|
|
565
|
+
|
|
566
|
+
targetCall.reject(message, code, error, data)
|
|
567
|
+
|
|
568
|
+
if savedCall != nil {
|
|
569
|
+
self.bridge?.releaseCall(withID: call.callbackId)
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
if Thread.isMainThread {
|
|
574
|
+
reject()
|
|
575
|
+
} else {
|
|
576
|
+
DispatchQueue.main.async {
|
|
577
|
+
reject()
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
private func saveCallForAsyncHandling(_ call: CAPPluginCall) {
|
|
583
|
+
bridge?.saveCall(call)
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
private func notifyListenersOnMain(_ eventName: String, data: JSObject) {
|
|
587
|
+
let notify = {
|
|
588
|
+
self.notifyListeners(eventName, data: data)
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
if Thread.isMainThread {
|
|
592
|
+
notify()
|
|
593
|
+
} else {
|
|
594
|
+
DispatchQueue.main.async {
|
|
595
|
+
notify()
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
private func bundlePayload(_ bundleInfo: BundleInfo) -> JSObject {
|
|
601
|
+
var payload: JSObject = [:]
|
|
602
|
+
for (key, value) in bundleInfo.toJSON() {
|
|
603
|
+
payload[key] = value
|
|
604
|
+
}
|
|
605
|
+
return payload
|
|
606
|
+
}
|
|
607
|
+
|
|
499
608
|
@objc func notifyDownload(id: String, percent: Int, ignoreMultipleOfTen: Bool = false, bundle: BundleInfo? = nil) {
|
|
500
609
|
let bundleInfo = bundle ?? self.implementation.getBundleInfo(id: id)
|
|
501
|
-
|
|
610
|
+
var downloadPayload: JSObject = [:]
|
|
611
|
+
downloadPayload["percent"] = percent
|
|
612
|
+
downloadPayload["bundle"] = bundlePayload(bundleInfo)
|
|
613
|
+
self.notifyListenersOnMain("download", data: downloadPayload)
|
|
502
614
|
if percent == 100 {
|
|
503
|
-
|
|
615
|
+
var downloadCompletePayload: JSObject = [:]
|
|
616
|
+
downloadCompletePayload["bundle"] = bundlePayload(bundleInfo)
|
|
617
|
+
self.notifyListenersOnMain("downloadComplete", data: downloadCompletePayload)
|
|
504
618
|
self.implementation.sendStats(action: "download_complete", versionName: bundleInfo.getVersionName())
|
|
505
619
|
} else if percent.isMultiple(of: 10) || ignoreMultipleOfTen {
|
|
506
620
|
self.implementation.sendStats(action: "download_\(percent)", versionName: bundleInfo.getVersionName())
|
|
@@ -597,6 +711,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
597
711
|
let manifestArray = call.getArray("manifest")
|
|
598
712
|
let url = URL(string: urlString)
|
|
599
713
|
logger.info("Downloading \(String(describing: url))")
|
|
714
|
+
self.saveCallForAsyncHandling(call)
|
|
600
715
|
DispatchQueue.global(qos: .background).async {
|
|
601
716
|
do {
|
|
602
717
|
let next: BundleInfo
|
|
@@ -644,13 +759,17 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
644
759
|
} else {
|
|
645
760
|
self.logger.info("Good checksum \(next.getChecksum()) \(checksum)")
|
|
646
761
|
}
|
|
647
|
-
|
|
648
|
-
|
|
762
|
+
var updateAvailablePayload: JSObject = [:]
|
|
763
|
+
updateAvailablePayload["bundle"] = self.bundlePayload(next)
|
|
764
|
+
self.notifyListenersOnMain("updateAvailable", data: updateAvailablePayload)
|
|
765
|
+
self.resolveCall(call, data: next.toJSON())
|
|
649
766
|
} catch {
|
|
650
767
|
self.logger.error("Failed to download from: \(String(describing: url)) \(error.localizedDescription)")
|
|
651
|
-
|
|
768
|
+
var downloadFailedPayload: JSObject = [:]
|
|
769
|
+
downloadFailedPayload["version"] = version
|
|
770
|
+
self.notifyListenersOnMain("downloadFailed", data: downloadFailedPayload)
|
|
652
771
|
self.implementation.sendStats(action: "download_fail")
|
|
653
|
-
|
|
772
|
+
self.rejectCall(call, message: "Failed to download from: \(url!) - \(error.localizedDescription)")
|
|
654
773
|
}
|
|
655
774
|
}
|
|
656
775
|
}
|
|
@@ -876,25 +995,47 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
876
995
|
|
|
877
996
|
@objc func getLatest(_ call: CAPPluginCall) {
|
|
878
997
|
let channel = call.getString("channel")
|
|
879
|
-
|
|
998
|
+
self.saveCallForAsyncHandling(call)
|
|
999
|
+
runGetLatestWork {
|
|
880
1000
|
let res = self.implementation.getLatest(url: URL(string: self.updateUrl)!, channel: channel)
|
|
881
|
-
if res.error
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
1001
|
+
if let error = res.error, !error.isEmpty {
|
|
1002
|
+
let responseKind = self.updateResponseKind(kind: res.kind)
|
|
1003
|
+
res.kind = responseKind
|
|
1004
|
+
if responseKind == "failed" {
|
|
1005
|
+
self.rejectCall(call, message: error)
|
|
1006
|
+
} else {
|
|
1007
|
+
if res.version.isEmpty {
|
|
1008
|
+
res.version = self.implementation.getCurrentBundle().getVersionName()
|
|
1009
|
+
}
|
|
1010
|
+
self.resolveCall(call, data: res.toDict())
|
|
1011
|
+
}
|
|
1012
|
+
} else if let kind = res.kind, !kind.isEmpty {
|
|
1013
|
+
let responseKind = self.updateResponseKind(kind: kind)
|
|
1014
|
+
res.kind = responseKind
|
|
1015
|
+
if responseKind != "failed" {
|
|
1016
|
+
if res.version.isEmpty {
|
|
1017
|
+
res.version = self.implementation.getCurrentBundle().getVersionName()
|
|
1018
|
+
}
|
|
1019
|
+
self.resolveCall(call, data: res.toDict())
|
|
1020
|
+
} else {
|
|
1021
|
+
self.rejectCall(call, message: res.message ?? "server did not provide a message")
|
|
1022
|
+
}
|
|
1023
|
+
} else if let message = res.message, !message.isEmpty {
|
|
1024
|
+
self.rejectCall(call, message: message)
|
|
885
1025
|
} else {
|
|
886
|
-
|
|
1026
|
+
self.resolveCall(call, data: res.toDict())
|
|
887
1027
|
}
|
|
888
1028
|
}
|
|
889
1029
|
}
|
|
890
1030
|
|
|
891
1031
|
@objc func unsetChannel(_ call: CAPPluginCall) {
|
|
892
1032
|
let triggerAutoUpdate = call.getBool("triggerAutoUpdate", false)
|
|
893
|
-
|
|
1033
|
+
self.saveCallForAsyncHandling(call)
|
|
1034
|
+
DispatchQueue.global(qos: .utility).async {
|
|
894
1035
|
let configDefaultChannel = self.getConfig().getString("defaultChannel", "")!
|
|
895
1036
|
let res = self.implementation.unsetChannel(defaultChannelKey: self.defaultChannelDefaultsKey, configDefaultChannel: configDefaultChannel)
|
|
896
1037
|
if res.error != "" {
|
|
897
|
-
|
|
1038
|
+
self.rejectCall(call, message: res.error, code: "UNSETCHANNEL_FAILED", data: [
|
|
898
1039
|
"message": res.error,
|
|
899
1040
|
"error": res.error.contains("Channel URL") ? "missing_config" : "request_failed"
|
|
900
1041
|
])
|
|
@@ -908,7 +1049,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
908
1049
|
self.logger.info("Download already in progress, skipping duplicate download request")
|
|
909
1050
|
}
|
|
910
1051
|
}
|
|
911
|
-
|
|
1052
|
+
self.resolveCall(call, data: res.toDict())
|
|
912
1053
|
}
|
|
913
1054
|
}
|
|
914
1055
|
}
|
|
@@ -923,17 +1064,18 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
923
1064
|
return
|
|
924
1065
|
}
|
|
925
1066
|
let triggerAutoUpdate = call.getBool("triggerAutoUpdate") ?? false
|
|
926
|
-
|
|
1067
|
+
self.saveCallForAsyncHandling(call)
|
|
1068
|
+
DispatchQueue.global(qos: .utility).async {
|
|
927
1069
|
let res = self.implementation.setChannel(channel: channel, defaultChannelKey: self.defaultChannelDefaultsKey, allowSetDefaultChannel: self.allowSetDefaultChannel)
|
|
928
1070
|
if res.error != "" {
|
|
929
1071
|
// Fire channelPrivate event if channel doesn't allow self-assignment
|
|
930
1072
|
if res.error.contains("cannot_update_via_private_channel") || res.error.contains("channel_self_set_not_allowed") {
|
|
931
|
-
self.
|
|
1073
|
+
self.notifyListenersOnMain("channelPrivate", data: [
|
|
932
1074
|
"channel": channel,
|
|
933
1075
|
"message": res.error
|
|
934
1076
|
])
|
|
935
1077
|
}
|
|
936
|
-
|
|
1078
|
+
self.rejectCall(call, message: res.error, code: "SETCHANNEL_FAILED", data: [
|
|
937
1079
|
"message": res.error,
|
|
938
1080
|
"error": res.error.contains("Channel URL") ? "missing_config" : (res.error.contains("cannot_update_via_private_channel") || res.error.contains("channel_self_set_not_allowed")) ? "channel_private" : "request_failed"
|
|
939
1081
|
])
|
|
@@ -947,35 +1089,39 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
947
1089
|
self.logger.info("Download already in progress, skipping duplicate download request")
|
|
948
1090
|
}
|
|
949
1091
|
}
|
|
950
|
-
|
|
1092
|
+
self.resolveCall(call, data: res.toDict())
|
|
951
1093
|
}
|
|
952
1094
|
}
|
|
953
1095
|
}
|
|
954
1096
|
|
|
955
1097
|
@objc func getChannel(_ call: CAPPluginCall) {
|
|
956
|
-
|
|
1098
|
+
self.saveCallForAsyncHandling(call)
|
|
1099
|
+
DispatchQueue.global(qos: .utility).async {
|
|
957
1100
|
let res = self.implementation.getChannel()
|
|
958
1101
|
if res.error != "" {
|
|
959
|
-
|
|
1102
|
+
self.rejectCall(call, message: res.error, code: "GETCHANNEL_FAILED", data: [
|
|
960
1103
|
"message": res.error,
|
|
961
1104
|
"error": res.error.contains("Channel URL") ? "missing_config" : "request_failed"
|
|
962
1105
|
])
|
|
963
1106
|
} else {
|
|
964
|
-
|
|
1107
|
+
self.resolveCall(call, data: res.toDict())
|
|
965
1108
|
}
|
|
966
1109
|
}
|
|
967
1110
|
}
|
|
968
1111
|
|
|
969
1112
|
@objc func listChannels(_ call: CAPPluginCall) {
|
|
970
|
-
|
|
1113
|
+
self.saveCallForAsyncHandling(call)
|
|
1114
|
+
DispatchQueue.global(qos: .utility).async {
|
|
971
1115
|
let res = self.implementation.listChannels()
|
|
972
1116
|
if res.error != "" {
|
|
973
|
-
|
|
1117
|
+
self.rejectCall(call, message: res.error, code: "LISTCHANNELS_FAILED", data: [
|
|
974
1118
|
"message": res.error,
|
|
975
1119
|
"error": res.error.contains("Channel URL") ? "missing_config" : "request_failed"
|
|
976
1120
|
])
|
|
977
1121
|
} else {
|
|
978
|
-
|
|
1122
|
+
var payload: JSObject = [:]
|
|
1123
|
+
payload["channels"] = res.channels
|
|
1124
|
+
self.resolveCall(call, data: payload)
|
|
979
1125
|
}
|
|
980
1126
|
}
|
|
981
1127
|
}
|
|
@@ -1102,6 +1248,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
1102
1248
|
let bundle = self.implementation.getCurrentBundle()
|
|
1103
1249
|
self.implementation.setSuccess(bundle: bundle, autoDeletePrevious: self.autoDeletePrevious)
|
|
1104
1250
|
logger.info("Current bundle loaded successfully. [notifyAppReady was called] \(bundle.toString())")
|
|
1251
|
+
|
|
1105
1252
|
call.resolve(["bundle": bundle.toJSON()])
|
|
1106
1253
|
}
|
|
1107
1254
|
|
|
@@ -1191,7 +1338,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
1191
1338
|
|
|
1192
1339
|
logger.info("Current bundle is: \(current.toString())")
|
|
1193
1340
|
|
|
1194
|
-
if BundleStatus.SUCCESS.
|
|
1341
|
+
if BundleStatus.SUCCESS.storedValue != current.getStatus() {
|
|
1195
1342
|
logger.error("notifyAppReady was not called, roll back current bundle: \(current.toString())")
|
|
1196
1343
|
logger.error("Did you forget to call 'notifyAppReady()' in your Capacitor App code?")
|
|
1197
1344
|
self.notifyListeners("updateFailed", data: [
|
|
@@ -1601,6 +1748,10 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
1601
1748
|
self.updateUrl = updateUrl
|
|
1602
1749
|
}
|
|
1603
1750
|
|
|
1751
|
+
func setCurrentBuildVersionForTesting(_ currentBuildVersion: String) {
|
|
1752
|
+
self.currentBuildVersion = currentBuildVersion
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1604
1755
|
func shouldUseDirectUpdateForTesting() -> Bool {
|
|
1605
1756
|
self.shouldUseDirectUpdate()
|
|
1606
1757
|
}
|
|
@@ -1618,6 +1769,52 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
1618
1769
|
self.notifyListeners("majorAvailable", data: payload)
|
|
1619
1770
|
}
|
|
1620
1771
|
|
|
1772
|
+
private func updateResponseKind(kind: String?) -> String {
|
|
1773
|
+
if let kind, ["up_to_date", "blocked", "failed"].contains(kind) {
|
|
1774
|
+
return kind
|
|
1775
|
+
}
|
|
1776
|
+
return "failed"
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
private func endBackgroundDownloadAfterLatestError(
|
|
1780
|
+
backendError: String,
|
|
1781
|
+
res: AppVersion,
|
|
1782
|
+
current: BundleInfo,
|
|
1783
|
+
plannedDirectUpdate: Bool
|
|
1784
|
+
) {
|
|
1785
|
+
let statusCode = res.statusCode
|
|
1786
|
+
let responseKind = self.updateResponseKind(kind: res.kind)
|
|
1787
|
+
let responseMessage = res.message?.isEmpty == false ? res.message : nil
|
|
1788
|
+
let message = responseMessage ?? (backendError.isEmpty ? "server did not provide a message" : backendError)
|
|
1789
|
+
let latestVersionName = res.version.isEmpty ? current.getVersionName() : res.version
|
|
1790
|
+
self.notifyListeners("updateCheckResult", data: [
|
|
1791
|
+
"kind": responseKind,
|
|
1792
|
+
"error": backendError,
|
|
1793
|
+
"message": message,
|
|
1794
|
+
"statusCode": statusCode,
|
|
1795
|
+
"version": latestVersionName,
|
|
1796
|
+
"bundle": current.toJSON()
|
|
1797
|
+
])
|
|
1798
|
+
|
|
1799
|
+
if responseKind == "up_to_date" {
|
|
1800
|
+
self.logger.info("No new version available")
|
|
1801
|
+
} else if responseKind == "blocked" {
|
|
1802
|
+
self.logger.info("Update check blocked with error: \(backendError)")
|
|
1803
|
+
} else {
|
|
1804
|
+
self.logger.error("getLatest failed with error: \(backendError)")
|
|
1805
|
+
}
|
|
1806
|
+
|
|
1807
|
+
let isFailure = responseKind == "failed"
|
|
1808
|
+
self.endBackGroundTaskWithNotif(
|
|
1809
|
+
msg: message,
|
|
1810
|
+
latestVersionName: latestVersionName,
|
|
1811
|
+
current: current,
|
|
1812
|
+
error: isFailure,
|
|
1813
|
+
plannedDirectUpdate: plannedDirectUpdate,
|
|
1814
|
+
sendStats: isFailure
|
|
1815
|
+
)
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1621
1818
|
func endBackGroundTaskWithNotif(
|
|
1622
1819
|
msg: String,
|
|
1623
1820
|
latestVersionName: String,
|
|
@@ -1672,6 +1869,26 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
1672
1869
|
}
|
|
1673
1870
|
|
|
1674
1871
|
func runBackgroundDownloadWork(_ work: @escaping () -> Void) {
|
|
1872
|
+
// Live update checks/downloads are user-visible work. Using `.background`
|
|
1873
|
+
// lets the scheduler starve them for minutes while the app is active.
|
|
1874
|
+
DispatchQueue.global(qos: .utility).async(execute: work)
|
|
1875
|
+
}
|
|
1876
|
+
|
|
1877
|
+
private func beginDownloadBackgroundTask() {
|
|
1878
|
+
let registerTask = {
|
|
1879
|
+
self.backgroundTaskID = UIApplication.shared.beginBackgroundTask(withName: "Finish Download Tasks") {
|
|
1880
|
+
self.endBackGroundTask()
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1883
|
+
|
|
1884
|
+
if Thread.isMainThread {
|
|
1885
|
+
registerTask()
|
|
1886
|
+
} else {
|
|
1887
|
+
DispatchQueue.main.sync(execute: registerTask)
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1890
|
+
|
|
1891
|
+
func runGetLatestWork(_ work: @escaping () -> Void) {
|
|
1675
1892
|
DispatchQueue.global(qos: .background).async(execute: work)
|
|
1676
1893
|
}
|
|
1677
1894
|
|
|
@@ -1697,26 +1914,20 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
1697
1914
|
self.runBackgroundDownloadWork {
|
|
1698
1915
|
// Wait for cleanup to complete before starting download
|
|
1699
1916
|
self.waitForCleanupIfNeeded()
|
|
1700
|
-
self.
|
|
1701
|
-
// End the task if time expires.
|
|
1702
|
-
self.endBackGroundTask()
|
|
1703
|
-
}
|
|
1917
|
+
self.beginDownloadBackgroundTask()
|
|
1704
1918
|
self.logger.info("Check for update via \(self.updateUrl)")
|
|
1705
1919
|
let res = self.implementation.getLatest(url: url, channel: nil)
|
|
1706
1920
|
let current = self.implementation.getCurrentBundle()
|
|
1707
1921
|
|
|
1708
1922
|
// Handle network errors and other failures first
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
latestVersionName: res.version,
|
|
1923
|
+
let backendError = res.error ?? ""
|
|
1924
|
+
let backendKind = res.kind ?? ""
|
|
1925
|
+
if !backendError.isEmpty || !backendKind.isEmpty {
|
|
1926
|
+
self.endBackgroundDownloadAfterLatestError(
|
|
1927
|
+
backendError: backendError,
|
|
1928
|
+
res: res,
|
|
1716
1929
|
current: current,
|
|
1717
|
-
|
|
1718
|
-
plannedDirectUpdate: plannedDirectUpdate,
|
|
1719
|
-
sendStats: !responseIsOk
|
|
1930
|
+
plannedDirectUpdate: plannedDirectUpdate
|
|
1720
1931
|
)
|
|
1721
1932
|
return
|
|
1722
1933
|
}
|
|
@@ -1987,7 +2198,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
1987
2198
|
timer.invalidate()
|
|
1988
2199
|
return
|
|
1989
2200
|
}
|
|
1990
|
-
DispatchQueue.global(qos: .
|
|
2201
|
+
DispatchQueue.global(qos: .utility).async {
|
|
1991
2202
|
let res = self.implementation.getLatest(url: url, channel: nil)
|
|
1992
2203
|
let current = self.implementation.getCurrentBundle()
|
|
1993
2204
|
|
|
@@ -2139,29 +2350,30 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
2139
2350
|
|
|
2140
2351
|
logger.info("Getting App Store update info for \(bundleId) in country \(country)")
|
|
2141
2352
|
|
|
2353
|
+
self.saveCallForAsyncHandling(call)
|
|
2142
2354
|
DispatchQueue.global(qos: .background).async {
|
|
2143
2355
|
let urlString = "https://itunes.apple.com/lookup?bundleId=\(bundleId)&country=\(country)"
|
|
2144
2356
|
guard let url = URL(string: urlString) else {
|
|
2145
|
-
|
|
2357
|
+
self.rejectCall(call, message: "Invalid URL for App Store lookup")
|
|
2146
2358
|
return
|
|
2147
2359
|
}
|
|
2148
2360
|
|
|
2149
2361
|
let task = URLSession.shared.dataTask(with: url) { data, _, error in
|
|
2150
2362
|
if let error = error {
|
|
2151
2363
|
self.logger.error("App Store lookup failed: \(error.localizedDescription)")
|
|
2152
|
-
|
|
2364
|
+
self.rejectCall(call, message: "App Store lookup failed: \(error.localizedDescription)")
|
|
2153
2365
|
return
|
|
2154
2366
|
}
|
|
2155
2367
|
|
|
2156
2368
|
guard let data = data else {
|
|
2157
|
-
|
|
2369
|
+
self.rejectCall(call, message: "No data received from App Store")
|
|
2158
2370
|
return
|
|
2159
2371
|
}
|
|
2160
2372
|
|
|
2161
2373
|
do {
|
|
2162
2374
|
guard let json = try JSONSerialization.jsonObject(with: data) as? [String: Any],
|
|
2163
2375
|
let resultCount = json["resultCount"] as? Int else {
|
|
2164
|
-
|
|
2376
|
+
self.rejectCall(call, message: "Invalid response from App Store")
|
|
2165
2377
|
return
|
|
2166
2378
|
}
|
|
2167
2379
|
|
|
@@ -2218,10 +2430,10 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
2218
2430
|
self.logger.info("App not found in App Store for bundleId: \(bundleId)")
|
|
2219
2431
|
}
|
|
2220
2432
|
|
|
2221
|
-
|
|
2433
|
+
self.resolveCall(call, data: result)
|
|
2222
2434
|
} catch {
|
|
2223
2435
|
self.logger.error("Failed to parse App Store response: \(error.localizedDescription)")
|
|
2224
|
-
|
|
2436
|
+
self.rejectCall(call, message: "Failed to parse App Store response: \(error.localizedDescription)")
|
|
2225
2437
|
}
|
|
2226
2438
|
}
|
|
2227
2439
|
task.resume()
|
|
@@ -2230,37 +2442,48 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
2230
2442
|
|
|
2231
2443
|
@objc func openAppStore(_ call: CAPPluginCall) {
|
|
2232
2444
|
let appId = call.getString("appId")
|
|
2445
|
+
let bundleId = implementation.appId
|
|
2446
|
+
self.saveCallForAsyncHandling(call)
|
|
2233
2447
|
|
|
2234
|
-
|
|
2235
|
-
// Open App Store with provided app ID
|
|
2236
|
-
let urlString = "https://apps.apple.com/app/id\(appId)"
|
|
2448
|
+
func openAppStorePage(urlString: String, invalidMessage: String = "Invalid App Store URL", failureMessage: String = "Failed to open App Store") {
|
|
2237
2449
|
guard let url = URL(string: urlString) else {
|
|
2238
|
-
|
|
2450
|
+
self.rejectCall(call, message: invalidMessage)
|
|
2239
2451
|
return
|
|
2240
2452
|
}
|
|
2241
2453
|
DispatchQueue.main.async {
|
|
2242
2454
|
UIApplication.shared.open(url) { success in
|
|
2243
2455
|
if success {
|
|
2244
|
-
|
|
2456
|
+
self.resolveCall(call)
|
|
2245
2457
|
} else {
|
|
2246
|
-
|
|
2458
|
+
self.rejectCall(call, message: failureMessage)
|
|
2247
2459
|
}
|
|
2248
2460
|
}
|
|
2249
2461
|
}
|
|
2462
|
+
}
|
|
2463
|
+
|
|
2464
|
+
func openFallbackAppStorePage() {
|
|
2465
|
+
guard let encodedBundleId = bundleId.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) else {
|
|
2466
|
+
self.rejectCall(call, message: "Failed to build App Store fallback URL")
|
|
2467
|
+
return
|
|
2468
|
+
}
|
|
2469
|
+
openAppStorePage(urlString: "https://apps.apple.com/app/\(encodedBundleId)")
|
|
2470
|
+
}
|
|
2471
|
+
|
|
2472
|
+
if let appId = appId {
|
|
2473
|
+
openAppStorePage(urlString: "https://apps.apple.com/app/id\(appId)")
|
|
2250
2474
|
} else {
|
|
2251
|
-
// Look up app ID using bundle identifier
|
|
2252
|
-
let bundleId = implementation.appId
|
|
2253
2475
|
let lookupUrl = "https://itunes.apple.com/lookup?bundleId=\(bundleId)"
|
|
2254
2476
|
|
|
2255
2477
|
DispatchQueue.global(qos: .background).async {
|
|
2256
2478
|
guard let url = URL(string: lookupUrl) else {
|
|
2257
|
-
|
|
2479
|
+
openFallbackAppStorePage()
|
|
2258
2480
|
return
|
|
2259
2481
|
}
|
|
2260
2482
|
|
|
2261
2483
|
let task = URLSession.shared.dataTask(with: url) { data, _, error in
|
|
2262
2484
|
if let error = error {
|
|
2263
|
-
|
|
2485
|
+
self.logger.error("App Store lookup failed: \(error.localizedDescription)")
|
|
2486
|
+
openFallbackAppStorePage()
|
|
2264
2487
|
return
|
|
2265
2488
|
}
|
|
2266
2489
|
|
|
@@ -2269,39 +2492,11 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
2269
2492
|
let results = json["results"] as? [[String: Any]],
|
|
2270
2493
|
let appInfo = results.first,
|
|
2271
2494
|
let trackId = appInfo["trackId"] as? Int else {
|
|
2272
|
-
|
|
2273
|
-
let fallbackUrlString = "https://apps.apple.com/app/\(bundleId)"
|
|
2274
|
-
guard let fallbackUrl = URL(string: fallbackUrlString) else {
|
|
2275
|
-
call.reject("Failed to find app in App Store and fallback URL is invalid")
|
|
2276
|
-
return
|
|
2277
|
-
}
|
|
2278
|
-
DispatchQueue.main.async {
|
|
2279
|
-
UIApplication.shared.open(fallbackUrl) { success in
|
|
2280
|
-
if success {
|
|
2281
|
-
call.resolve()
|
|
2282
|
-
} else {
|
|
2283
|
-
call.reject("Failed to open App Store")
|
|
2284
|
-
}
|
|
2285
|
-
}
|
|
2286
|
-
}
|
|
2495
|
+
openFallbackAppStorePage()
|
|
2287
2496
|
return
|
|
2288
2497
|
}
|
|
2289
2498
|
|
|
2290
|
-
|
|
2291
|
-
guard let url = URL(string: appStoreUrl) else {
|
|
2292
|
-
call.reject("Invalid App Store URL")
|
|
2293
|
-
return
|
|
2294
|
-
}
|
|
2295
|
-
|
|
2296
|
-
DispatchQueue.main.async {
|
|
2297
|
-
UIApplication.shared.open(url) { success in
|
|
2298
|
-
if success {
|
|
2299
|
-
call.resolve()
|
|
2300
|
-
} else {
|
|
2301
|
-
call.reject("Failed to open App Store")
|
|
2302
|
-
}
|
|
2303
|
-
}
|
|
2304
|
-
}
|
|
2499
|
+
openAppStorePage(urlString: "https://apps.apple.com/app/id\(trackId)")
|
|
2305
2500
|
}
|
|
2306
2501
|
task.resume()
|
|
2307
2502
|
}
|