@capgo/capacitor-updater 8.46.3 → 8.47.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/README.md +178 -19
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdaterPlugin.java +466 -30
- package/android/src/main/java/ee/forgr/capacitor_updater/CapgoUpdater.java +239 -22
- package/android/src/main/java/ee/forgr/capacitor_updater/ShakeMenu.java +95 -3
- package/dist/docs.json +473 -0
- package/dist/esm/definitions.d.ts +241 -0
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.d.ts +4 -1
- package/dist/esm/web.js +30 -0
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +30 -0
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +30 -0
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/CapacitorUpdaterPlugin/CapacitorUpdaterPlugin.swift +354 -15
- package/ios/Sources/CapacitorUpdaterPlugin/CapgoUpdater.swift +194 -8
- package/ios/Sources/CapacitorUpdaterPlugin/InternalUtils.swift +2 -0
- package/ios/Sources/CapacitorUpdaterPlugin/ShakeMenu.swift +47 -3
- package/package.json +1 -1
|
@@ -37,6 +37,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
37
37
|
CAPPluginMethod(name: "setStatsUrl", returnType: CAPPluginReturnPromise),
|
|
38
38
|
CAPPluginMethod(name: "setChannelUrl", returnType: CAPPluginReturnPromise),
|
|
39
39
|
CAPPluginMethod(name: "set", returnType: CAPPluginReturnPromise),
|
|
40
|
+
CAPPluginMethod(name: "startPreviewSession", returnType: CAPPluginReturnPromise),
|
|
40
41
|
CAPPluginMethod(name: "list", returnType: CAPPluginReturnPromise),
|
|
41
42
|
CAPPluginMethod(name: "delete", returnType: CAPPluginReturnPromise),
|
|
42
43
|
CAPPluginMethod(name: "setBundleError", returnType: CAPPluginReturnPromise),
|
|
@@ -47,6 +48,8 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
47
48
|
CAPPluginMethod(name: "setMultiDelay", returnType: CAPPluginReturnPromise),
|
|
48
49
|
CAPPluginMethod(name: "cancelDelay", returnType: CAPPluginReturnPromise),
|
|
49
50
|
CAPPluginMethod(name: "getLatest", returnType: CAPPluginReturnPromise),
|
|
51
|
+
CAPPluginMethod(name: "getMissingBundleFiles", returnType: CAPPluginReturnPromise),
|
|
52
|
+
CAPPluginMethod(name: "getBundleDownloadSize", returnType: CAPPluginReturnPromise),
|
|
50
53
|
CAPPluginMethod(name: "triggerUpdateCheck", returnType: CAPPluginReturnPromise),
|
|
51
54
|
CAPPluginMethod(name: "setChannel", returnType: CAPPluginReturnPromise),
|
|
52
55
|
CAPPluginMethod(name: "unsetChannel", returnType: CAPPluginReturnPromise),
|
|
@@ -76,7 +79,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
76
79
|
CAPPluginMethod(name: "completeFlexibleUpdate", returnType: CAPPluginReturnPromise)
|
|
77
80
|
]
|
|
78
81
|
public var implementation = CapgoUpdater()
|
|
79
|
-
private let pluginVersion: String = "8.
|
|
82
|
+
private let pluginVersion: String = "8.47.1"
|
|
80
83
|
static let updateUrlDefault = "https://plugin.capgo.app/updates"
|
|
81
84
|
static let statsUrlDefault = "https://plugin.capgo.app/stats"
|
|
82
85
|
static let channelUrlDefault = "https://plugin.capgo.app/channel_self"
|
|
@@ -87,6 +90,12 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
87
90
|
private let channelUrlDefaultsKey = "CapacitorUpdater.channelUrl"
|
|
88
91
|
private let defaultChannelDefaultsKey = "CapacitorUpdater.defaultChannel"
|
|
89
92
|
private let lastFailedBundleDefaultsKey = "CapacitorUpdater.lastFailedBundle"
|
|
93
|
+
private let previewSessionDefaultsKey = "CapacitorUpdater.previewSession"
|
|
94
|
+
private let previewPreviousShakeMenuDefaultsKey = "CapacitorUpdater.previewPreviousShakeMenu"
|
|
95
|
+
private let previewPreviousShakeChannelSelectorDefaultsKey = "CapacitorUpdater.previewPreviousShakeChannelSelector"
|
|
96
|
+
private let previewPreviousNextBundleDefaultsKey = "CapacitorUpdater.previewPreviousNextBundle"
|
|
97
|
+
private let previewPreviousAppIdDefaultsKey = "CapacitorUpdater.previewPreviousAppId"
|
|
98
|
+
private let previewAppIdDefaultsKey = "CapacitorUpdater.previewAppId"
|
|
90
99
|
// Note: DELAY_CONDITION_PREFERENCES is now defined in DelayUpdateUtils.DELAY_CONDITION_PREFERENCES
|
|
91
100
|
private var updateUrl = ""
|
|
92
101
|
private var backgroundTaskID: UIBackgroundTaskIdentifier = UIBackgroundTaskIdentifier.invalid
|
|
@@ -131,11 +140,14 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
131
140
|
private var persistCustomId = false
|
|
132
141
|
private var persistModifyUrl = false
|
|
133
142
|
private var allowManualBundleError = false
|
|
143
|
+
private var allowPreview = false
|
|
134
144
|
private var keepUrlPathFlagLastValue: Bool?
|
|
135
145
|
private var appHealthTracker: AppHealthTracker?
|
|
136
146
|
private var webViewStatsReporter: WebViewStatsReporter?
|
|
137
147
|
public var shakeMenuEnabled = false
|
|
138
148
|
public var shakeChannelSelectorEnabled = false
|
|
149
|
+
public var previewSessionEnabled = false
|
|
150
|
+
private var previewSessionAlertPending = false
|
|
139
151
|
let semaphoreReady = DispatchSemaphore(value: 0)
|
|
140
152
|
|
|
141
153
|
private var delayUpdateUtils: DelayUpdateUtils!
|
|
@@ -171,6 +183,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
171
183
|
}
|
|
172
184
|
persistModifyUrl = getConfig().getBoolean("persistModifyUrl", false)
|
|
173
185
|
allowManualBundleError = getConfig().getBoolean("allowManualBundleError", false)
|
|
186
|
+
allowPreview = getConfig().getBoolean("allowPreview", false)
|
|
174
187
|
logger.info("init for device \(self.implementation.deviceID)")
|
|
175
188
|
guard let versionName = getConfig().getString("version", Bundle.main.versionName) else {
|
|
176
189
|
logger.error("Cannot get version name")
|
|
@@ -232,6 +245,14 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
232
245
|
resetWhenUpdate = getConfig().getBoolean("resetWhenUpdate", true)
|
|
233
246
|
shakeMenuEnabled = getConfig().getBoolean("shakeMenu", false)
|
|
234
247
|
shakeChannelSelectorEnabled = getConfig().getBoolean("allowShakeChannelSelector", false)
|
|
248
|
+
let storedPreviewSessionEnabled = UserDefaults.standard.bool(forKey: previewSessionDefaultsKey)
|
|
249
|
+
let shouldClearPreviewSessionBecauseDisabled = !allowPreview && storedPreviewSessionEnabled
|
|
250
|
+
previewSessionEnabled = allowPreview && storedPreviewSessionEnabled
|
|
251
|
+
implementation.previewSession = previewSessionEnabled
|
|
252
|
+
if previewSessionEnabled {
|
|
253
|
+
shakeMenuEnabled = true
|
|
254
|
+
shakeChannelSelectorEnabled = false
|
|
255
|
+
}
|
|
235
256
|
periodCheckDelay = Self.normalizedPeriodCheckDelaySeconds(getConfig().getInt("periodCheckDelay", 0))
|
|
236
257
|
|
|
237
258
|
implementation.setPublicKey(getConfig().getString("publicKey") ?? "")
|
|
@@ -269,6 +290,15 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
269
290
|
// crash the app on purpose it should not happen
|
|
270
291
|
fatalError("appId is missing in capacitor.config.json or plugin config, and cannot be retrieved from the native app, please add it globally or in the plugin config")
|
|
271
292
|
}
|
|
293
|
+
if shouldClearPreviewSessionBecauseDisabled {
|
|
294
|
+
clearPreviewSessionBecauseDisabled()
|
|
295
|
+
}
|
|
296
|
+
if previewSessionEnabled,
|
|
297
|
+
let previewAppId = UserDefaults.standard.string(forKey: previewAppIdDefaultsKey),
|
|
298
|
+
!previewAppId.isEmpty {
|
|
299
|
+
implementation.appId = previewAppId
|
|
300
|
+
logger.info("Using preview appId \(previewAppId)")
|
|
301
|
+
}
|
|
272
302
|
logger.info("appId \(implementation.appId)")
|
|
273
303
|
implementation.statsUrl = getConfig().getString("statsUrl", CapacitorUpdaterPlugin.statsUrlDefault)!
|
|
274
304
|
implementation.channelUrl = getConfig().getString("channelUrl", CapacitorUpdaterPlugin.channelUrlDefault)!
|
|
@@ -298,6 +328,10 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
298
328
|
|
|
299
329
|
// Check if app was recently installed/updated BEFORE cleanup updates the stored native build version.
|
|
300
330
|
self.wasRecentlyInstalledOrUpdated = self.checkIfRecentlyInstalledOrUpdated()
|
|
331
|
+
let nativeBuildVersionChanged = self.hasNativeBuildVersionChanged()
|
|
332
|
+
if nativeBuildVersionChanged {
|
|
333
|
+
self.clearPreviewSessionForNativeBuildChange()
|
|
334
|
+
}
|
|
301
335
|
|
|
302
336
|
if resetWhenUpdate {
|
|
303
337
|
let didResetCurrentBundle = self.resetCurrentBundleForNativeBuildChangeIfNeeded()
|
|
@@ -718,6 +752,23 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
718
752
|
call.resolve(["version": self.pluginVersion])
|
|
719
753
|
}
|
|
720
754
|
|
|
755
|
+
private func manifestEntries(from manifestArray: [Any]?) -> [ManifestEntry]? {
|
|
756
|
+
guard let manifestArray = manifestArray else {
|
|
757
|
+
return nil
|
|
758
|
+
}
|
|
759
|
+
var manifestEntries: [ManifestEntry] = []
|
|
760
|
+
for item in manifestArray {
|
|
761
|
+
if let manifestDict = item as? [String: Any] {
|
|
762
|
+
manifestEntries.append(ManifestEntry(
|
|
763
|
+
file_name: manifestDict["file_name"] as? String,
|
|
764
|
+
file_hash: manifestDict["file_hash"] as? String,
|
|
765
|
+
download_url: manifestDict["download_url"] as? String
|
|
766
|
+
))
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
return manifestEntries
|
|
770
|
+
}
|
|
771
|
+
|
|
721
772
|
@objc func download(_ call: CAPPluginCall) {
|
|
722
773
|
guard let urlString = call.getString("url") else {
|
|
723
774
|
logger.error("Download called without url")
|
|
@@ -739,19 +790,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
739
790
|
DispatchQueue.global(qos: .background).async {
|
|
740
791
|
do {
|
|
741
792
|
let next: BundleInfo
|
|
742
|
-
if let
|
|
743
|
-
// Convert JSArray to [ManifestEntry]
|
|
744
|
-
var manifestEntries: [ManifestEntry] = []
|
|
745
|
-
for item in manifestArray {
|
|
746
|
-
if let manifestDict = item as? [String: Any] {
|
|
747
|
-
let entry = ManifestEntry(
|
|
748
|
-
file_name: manifestDict["file_name"] as? String,
|
|
749
|
-
file_hash: manifestDict["file_hash"] as? String,
|
|
750
|
-
download_url: manifestDict["download_url"] as? String
|
|
751
|
-
)
|
|
752
|
-
manifestEntries.append(entry)
|
|
753
|
-
}
|
|
754
|
-
}
|
|
793
|
+
if let manifestEntries = self.manifestEntries(from: manifestArray) {
|
|
755
794
|
next = try self.implementation.downloadManifest(manifest: manifestEntries, version: version, sessionKey: sessionKey)
|
|
756
795
|
} else {
|
|
757
796
|
next = try self.implementation.download(url: url!, version: version, sessionKey: sessionKey)
|
|
@@ -908,6 +947,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
908
947
|
}
|
|
909
948
|
self.notifyBundleSet(next)
|
|
910
949
|
_ = self.implementation.setNextBundle(next: Optional<String>.none)
|
|
950
|
+
self.showPreviewSessionNoticeIfNeeded()
|
|
911
951
|
call.resolve()
|
|
912
952
|
return
|
|
913
953
|
}
|
|
@@ -919,6 +959,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
919
959
|
}
|
|
920
960
|
|
|
921
961
|
if self._reload() {
|
|
962
|
+
self.showPreviewSessionNoticeIfNeeded()
|
|
922
963
|
call.resolve()
|
|
923
964
|
} else {
|
|
924
965
|
logger.error("Reload failed")
|
|
@@ -956,10 +997,250 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
956
997
|
call.reject("Reload failed after setting bundle \(id)")
|
|
957
998
|
} else {
|
|
958
999
|
self.notifyBundleSet(self.implementation.getBundleInfo(id: id))
|
|
1000
|
+
self.showPreviewSessionNoticeIfNeeded()
|
|
959
1001
|
call.resolve()
|
|
960
1002
|
}
|
|
961
1003
|
}
|
|
962
1004
|
|
|
1005
|
+
@objc func startPreviewSession(_ call: CAPPluginCall) {
|
|
1006
|
+
guard self.allowPreview else {
|
|
1007
|
+
logger.error("startPreviewSession called without allowPreview")
|
|
1008
|
+
call.reject("startPreviewSession not allowed. Set allowPreview to true in your config to enable it.")
|
|
1009
|
+
return
|
|
1010
|
+
}
|
|
1011
|
+
let previewAppId = self.normalizedPreviewAppId(call.getString("appId"))
|
|
1012
|
+
|
|
1013
|
+
if !self.previewSessionEnabled {
|
|
1014
|
+
let current = self.implementation.getCurrentBundle()
|
|
1015
|
+
guard self.implementation.setPreviewFallbackBundle(fallback: current.getId()) else {
|
|
1016
|
+
logger.error("Could not save current bundle as preview fallback")
|
|
1017
|
+
call.reject("Could not save current bundle as preview fallback")
|
|
1018
|
+
return
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
if let previousNext = self.implementation.getNextBundle(),
|
|
1022
|
+
!previousNext.isDeleted(),
|
|
1023
|
+
!previousNext.isErrorStatus() {
|
|
1024
|
+
UserDefaults.standard.set(previousNext.getId(), forKey: self.previewPreviousNextBundleDefaultsKey)
|
|
1025
|
+
} else {
|
|
1026
|
+
UserDefaults.standard.removeObject(forKey: self.previewPreviousNextBundleDefaultsKey)
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
UserDefaults.standard.set(self.implementation.appId, forKey: self.previewPreviousAppIdDefaultsKey)
|
|
1030
|
+
UserDefaults.standard.set(self.shakeMenuEnabled, forKey: self.previewPreviousShakeMenuDefaultsKey)
|
|
1031
|
+
UserDefaults.standard.set(self.shakeChannelSelectorEnabled, forKey: self.previewPreviousShakeChannelSelectorDefaultsKey)
|
|
1032
|
+
logger.info("Preview session started with fallback bundle: \(current.toString())")
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
if let previewAppId = previewAppId, !previewAppId.isEmpty {
|
|
1036
|
+
self.implementation.appId = previewAppId
|
|
1037
|
+
UserDefaults.standard.set(previewAppId, forKey: self.previewAppIdDefaultsKey)
|
|
1038
|
+
logger.info("Preview session using appId: \(previewAppId)")
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
self.previewSessionEnabled = true
|
|
1042
|
+
self.previewSessionAlertPending = true
|
|
1043
|
+
self.implementation.previewSession = true
|
|
1044
|
+
self.shakeMenuEnabled = true
|
|
1045
|
+
self.shakeChannelSelectorEnabled = false
|
|
1046
|
+
UserDefaults.standard.set(true, forKey: self.previewSessionDefaultsKey)
|
|
1047
|
+
UserDefaults.standard.synchronize()
|
|
1048
|
+
call.resolve()
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
func leavePreviewSessionFromShakeMenu() -> Bool {
|
|
1052
|
+
let previewBundle = self.implementation.getCurrentBundle()
|
|
1053
|
+
let configDefaultChannel = self.getConfig().getString("defaultChannel", "")!
|
|
1054
|
+
|
|
1055
|
+
let didReset = self.resetToPreviewFallbackBundle()
|
|
1056
|
+
guard didReset else {
|
|
1057
|
+
return false
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
_ = self.implementation.unsetChannel(defaultChannelKey: self.defaultChannelDefaultsKey, configDefaultChannel: configDefaultChannel)
|
|
1061
|
+
let previewFallbackBundle = self.implementation.getPreviewFallbackBundle()
|
|
1062
|
+
self.endPreviewSession()
|
|
1063
|
+
let restoredNextBundle = self.implementation.getNextBundle()
|
|
1064
|
+
if !previewBundle.isBuiltin() &&
|
|
1065
|
+
previewFallbackBundle?.getId() != previewBundle.getId() &&
|
|
1066
|
+
restoredNextBundle?.getId() != previewBundle.getId() {
|
|
1067
|
+
_ = self.implementation.delete(id: previewBundle.getId(), removeInfo: false)
|
|
1068
|
+
}
|
|
1069
|
+
return true
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
func reloadPreviewSessionFromShakeMenu() -> Bool {
|
|
1073
|
+
self._reload()
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
func hasActivePreviewSession() -> Bool {
|
|
1077
|
+
self.previewSessionEnabled
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
private func resetToPreviewFallbackBundle() -> Bool {
|
|
1081
|
+
guard self.canPerformResetTransition() else { return false }
|
|
1082
|
+
guard let fallback = self.implementation.getPreviewFallbackBundle(), !fallback.isErrorStatus() else {
|
|
1083
|
+
logger.error("No preview fallback bundle available")
|
|
1084
|
+
return false
|
|
1085
|
+
}
|
|
1086
|
+
guard self.implementation.canSet(bundle: fallback) else {
|
|
1087
|
+
logger.error("Preview fallback bundle is not installable")
|
|
1088
|
+
return false
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
let previousState = self.implementation.captureResetState()
|
|
1092
|
+
let previousBundleName = self.implementation.getCurrentBundle().getVersionName()
|
|
1093
|
+
logger.info("Resetting to preview fallback bundle: \(fallback.toString())")
|
|
1094
|
+
if self.implementation.stagePreviewFallbackReload(bundle: fallback) && self._reload() {
|
|
1095
|
+
self.implementation.finalizeResetTransition(previousBundleName: previousBundleName, isInternal: false)
|
|
1096
|
+
self.notifyBundleSet(fallback)
|
|
1097
|
+
return true
|
|
1098
|
+
}
|
|
1099
|
+
self.implementation.restoreResetState(previousState)
|
|
1100
|
+
self.restoreLiveBundleStateAfterFailedReload()
|
|
1101
|
+
return false
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
private func endPreviewSession() {
|
|
1105
|
+
let previousShakeMenuEnabled = UserDefaults.standard.object(forKey: self.previewPreviousShakeMenuDefaultsKey) as? Bool
|
|
1106
|
+
?? getConfig().getBoolean("shakeMenu", false)
|
|
1107
|
+
let previousShakeChannelSelectorEnabled = UserDefaults.standard.object(forKey: self.previewPreviousShakeChannelSelectorDefaultsKey) as? Bool
|
|
1108
|
+
?? getConfig().getBoolean("allowShakeChannelSelector", false)
|
|
1109
|
+
self.restorePreviewPreviousNextBundle()
|
|
1110
|
+
self.restorePreviewPreviousAppId()
|
|
1111
|
+
|
|
1112
|
+
self.previewSessionEnabled = false
|
|
1113
|
+
self.previewSessionAlertPending = false
|
|
1114
|
+
self.implementation.previewSession = false
|
|
1115
|
+
self.shakeMenuEnabled = previousShakeMenuEnabled
|
|
1116
|
+
self.shakeChannelSelectorEnabled = previousShakeChannelSelectorEnabled
|
|
1117
|
+
_ = self.implementation.setPreviewFallbackBundle(fallback: nil)
|
|
1118
|
+
self.clearPreviewSessionPreferences()
|
|
1119
|
+
logger.info("Preview session ended")
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
private func clearPreviewSessionBecauseDisabled() {
|
|
1123
|
+
logger.info("Preview session disabled by config; restoring preview fallback")
|
|
1124
|
+
let fallback = self.implementation.getPreviewFallbackBundle()
|
|
1125
|
+
let bundleToRestore: BundleInfo
|
|
1126
|
+
if let fallback, !fallback.isErrorStatus() {
|
|
1127
|
+
bundleToRestore = fallback
|
|
1128
|
+
} else {
|
|
1129
|
+
bundleToRestore = self.implementation.getBundleInfo(id: BundleInfo.ID_BUILTIN)
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
if self.implementation.canSet(bundle: bundleToRestore) {
|
|
1133
|
+
_ = self.implementation.stagePreviewFallbackReload(bundle: bundleToRestore)
|
|
1134
|
+
} else {
|
|
1135
|
+
logger.warn("Could not restore preview fallback while disabling preview")
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
self.restorePreviewPreviousNextBundle()
|
|
1139
|
+
self.restorePreviewPreviousAppId()
|
|
1140
|
+
self.previewSessionEnabled = false
|
|
1141
|
+
self.previewSessionAlertPending = false
|
|
1142
|
+
self.implementation.previewSession = false
|
|
1143
|
+
self.shakeMenuEnabled = getConfig().getBoolean("shakeMenu", false)
|
|
1144
|
+
self.shakeChannelSelectorEnabled = getConfig().getBoolean("allowShakeChannelSelector", false)
|
|
1145
|
+
self.clearPreviewSessionPreferences()
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
private func clearPreviewSessionPreferences() {
|
|
1149
|
+
_ = self.implementation.setPreviewFallbackBundle(fallback: nil)
|
|
1150
|
+
UserDefaults.standard.removeObject(forKey: self.previewSessionDefaultsKey)
|
|
1151
|
+
UserDefaults.standard.removeObject(forKey: self.previewPreviousShakeMenuDefaultsKey)
|
|
1152
|
+
UserDefaults.standard.removeObject(forKey: self.previewPreviousShakeChannelSelectorDefaultsKey)
|
|
1153
|
+
UserDefaults.standard.removeObject(forKey: self.previewPreviousNextBundleDefaultsKey)
|
|
1154
|
+
UserDefaults.standard.removeObject(forKey: self.previewPreviousAppIdDefaultsKey)
|
|
1155
|
+
UserDefaults.standard.removeObject(forKey: self.previewAppIdDefaultsKey)
|
|
1156
|
+
UserDefaults.standard.synchronize()
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
private func restorePreviewPreviousAppId() {
|
|
1160
|
+
guard let previousAppId = UserDefaults.standard.string(forKey: self.previewPreviousAppIdDefaultsKey),
|
|
1161
|
+
!previousAppId.isEmpty else {
|
|
1162
|
+
return
|
|
1163
|
+
}
|
|
1164
|
+
self.implementation.appId = previousAppId
|
|
1165
|
+
logger.info("Restored appId after preview: \(previousAppId)")
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
private func normalizedPreviewAppId(_ rawAppId: String?) -> String? {
|
|
1169
|
+
guard let rawAppId else {
|
|
1170
|
+
return nil
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
let appId = rawAppId.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
1174
|
+
guard !appId.isEmpty else {
|
|
1175
|
+
return nil
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
let lowercasedAppId = appId.lowercased()
|
|
1179
|
+
if lowercasedAppId == "undefined" || lowercasedAppId == "null" {
|
|
1180
|
+
return nil
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
return appId
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
private func clearPreviewSessionForNativeBuildChange() {
|
|
1187
|
+
guard self.previewSessionEnabled || self.implementation.getPreviewFallbackBundle() != nil else {
|
|
1188
|
+
return
|
|
1189
|
+
}
|
|
1190
|
+
logger.info("Native build changed; clearing preview session state")
|
|
1191
|
+
self.previewSessionEnabled = false
|
|
1192
|
+
self.previewSessionAlertPending = false
|
|
1193
|
+
self.implementation.previewSession = false
|
|
1194
|
+
self.shakeMenuEnabled = getConfig().getBoolean("shakeMenu", false)
|
|
1195
|
+
self.shakeChannelSelectorEnabled = getConfig().getBoolean("allowShakeChannelSelector", false)
|
|
1196
|
+
self.restorePreviewPreviousAppId()
|
|
1197
|
+
_ = self.implementation.setPreviewFallbackBundle(fallback: nil)
|
|
1198
|
+
_ = self.implementation.setNextBundle(next: Optional<String>.none)
|
|
1199
|
+
let configDefaultChannel = self.getConfig().getString("defaultChannel", "")!
|
|
1200
|
+
_ = self.implementation.unsetChannel(defaultChannelKey: self.defaultChannelDefaultsKey, configDefaultChannel: configDefaultChannel)
|
|
1201
|
+
self.clearPreviewSessionPreferences()
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
private func restorePreviewPreviousNextBundle() {
|
|
1205
|
+
guard let previousNextBundleId = UserDefaults.standard.string(forKey: self.previewPreviousNextBundleDefaultsKey),
|
|
1206
|
+
!previousNextBundleId.isEmpty else {
|
|
1207
|
+
_ = self.implementation.setNextBundle(next: Optional<String>.none)
|
|
1208
|
+
return
|
|
1209
|
+
}
|
|
1210
|
+
if !self.implementation.setNextBundle(next: previousNextBundleId) {
|
|
1211
|
+
logger.warn("Could not restore pre-preview next bundle: \(previousNextBundleId)")
|
|
1212
|
+
_ = self.implementation.setNextBundle(next: Optional<String>.none)
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
private func showPreviewSessionNoticeIfNeeded() {
|
|
1217
|
+
guard self.previewSessionEnabled && self.previewSessionAlertPending else {
|
|
1218
|
+
return
|
|
1219
|
+
}
|
|
1220
|
+
self.previewSessionAlertPending = false
|
|
1221
|
+
|
|
1222
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(600)) {
|
|
1223
|
+
guard self.previewSessionEnabled else {
|
|
1224
|
+
return
|
|
1225
|
+
}
|
|
1226
|
+
if let topVC = UIApplication.topViewController(),
|
|
1227
|
+
topVC.isKind(of: UIAlertController.self) {
|
|
1228
|
+
self.previewSessionAlertPending = true
|
|
1229
|
+
return
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
let alert = UIAlertController(
|
|
1233
|
+
title: "Preview started",
|
|
1234
|
+
message: "Shake your device anytime to reload or leave the test app.",
|
|
1235
|
+
preferredStyle: .alert
|
|
1236
|
+
)
|
|
1237
|
+
alert.addAction(UIAlertAction(title: "Got it", style: .default))
|
|
1238
|
+
if let topVC = UIApplication.topViewController() {
|
|
1239
|
+
topVC.present(alert, animated: true)
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
|
|
963
1244
|
@objc func delete(_ call: CAPPluginCall) {
|
|
964
1245
|
guard let id = call.getString("id") else {
|
|
965
1246
|
logger.error("Delete called without version")
|
|
@@ -1019,9 +1300,23 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
1019
1300
|
|
|
1020
1301
|
@objc func getLatest(_ call: CAPPluginCall) {
|
|
1021
1302
|
let channel = call.getString("channel")
|
|
1303
|
+
let includeBundleSize = call.getBool("includeBundleSize", false)
|
|
1304
|
+
let appId = self.normalizedPreviewAppId(call.getString("appId"))
|
|
1305
|
+
if appId != nil && !self.allowPreview {
|
|
1306
|
+
logger.error("getLatest preview override called without allowPreview")
|
|
1307
|
+
call.reject("getLatest preview override not allowed. Set allowPreview to true in your config to enable it.")
|
|
1308
|
+
return
|
|
1309
|
+
}
|
|
1022
1310
|
self.saveCallForAsyncHandling(call)
|
|
1023
1311
|
runGetLatestWork {
|
|
1024
|
-
let res = self.implementation.getLatest(
|
|
1312
|
+
let res = self.implementation.getLatest(
|
|
1313
|
+
url: URL(string: self.updateUrl)!,
|
|
1314
|
+
channel: channel,
|
|
1315
|
+
appIdOverride: appId
|
|
1316
|
+
)
|
|
1317
|
+
if includeBundleSize {
|
|
1318
|
+
self.attachBundleSize(to: res)
|
|
1319
|
+
}
|
|
1025
1320
|
if let error = res.error, !error.isEmpty {
|
|
1026
1321
|
let responseKind = self.updateResponseKind(kind: res.kind)
|
|
1027
1322
|
res.kind = responseKind
|
|
@@ -1055,6 +1350,50 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
1055
1350
|
}
|
|
1056
1351
|
}
|
|
1057
1352
|
|
|
1353
|
+
private func attachBundleSize(to res: AppVersion) {
|
|
1354
|
+
guard let manifest = res.manifest, !manifest.isEmpty, let updateUrl = URL(string: self.updateUrl) else {
|
|
1355
|
+
return
|
|
1356
|
+
}
|
|
1357
|
+
let missing = self.implementation.getMissingBundleFiles(manifest: manifest, sessionKey: res.sessionKey ?? "")
|
|
1358
|
+
res.missing = [
|
|
1359
|
+
"missing": missing.map { $0.toDict() },
|
|
1360
|
+
"total": manifest.count,
|
|
1361
|
+
"missingCount": missing.count,
|
|
1362
|
+
"reusableCount": manifest.count - missing.count
|
|
1363
|
+
]
|
|
1364
|
+
res.downloadSize = self.implementation.getBundleDownloadSize(updateUrl: updateUrl, version: res.version, manifest: missing)
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
@objc func getMissingBundleFiles(_ call: CAPPluginCall) {
|
|
1368
|
+
guard let manifest = manifestEntries(from: call.getArray("manifest")) else {
|
|
1369
|
+
call.reject("getMissingBundleFiles called without manifest")
|
|
1370
|
+
return
|
|
1371
|
+
}
|
|
1372
|
+
let sessionKey = call.getString("sessionKey", "")
|
|
1373
|
+
self.saveCallForAsyncHandling(call)
|
|
1374
|
+
DispatchQueue.global(qos: .utility).async {
|
|
1375
|
+
let res = self.implementation.missingBundleFilesResult(manifest: manifest, sessionKey: sessionKey)
|
|
1376
|
+
self.resolveCall(call, data: res)
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
@objc func getBundleDownloadSize(_ call: CAPPluginCall) {
|
|
1381
|
+
guard let manifest = manifestEntries(from: call.getArray("manifest")) else {
|
|
1382
|
+
call.reject("getBundleDownloadSize called without manifest")
|
|
1383
|
+
return
|
|
1384
|
+
}
|
|
1385
|
+
guard let updateUrl = URL(string: self.updateUrl) else {
|
|
1386
|
+
call.reject("getBundleDownloadSize called without valid updateUrl")
|
|
1387
|
+
return
|
|
1388
|
+
}
|
|
1389
|
+
let version = call.getString("version")
|
|
1390
|
+
self.saveCallForAsyncHandling(call)
|
|
1391
|
+
DispatchQueue.global(qos: .utility).async {
|
|
1392
|
+
let res = self.implementation.getBundleDownloadSize(updateUrl: updateUrl, version: version, manifest: manifest)
|
|
1393
|
+
self.resolveCall(call, data: res)
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1058
1397
|
public func triggerBackgroundUpdateCheck() -> String {
|
|
1059
1398
|
guard !self.updateUrl.isEmpty, URL(string: self.updateUrl) != nil else {
|
|
1060
1399
|
logger.error("Error no url or wrong format")
|