@capgo/capacitor-updater 4.41.0 → 4.43.5
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/CapgoCapacitorUpdater.podspec +7 -5
- package/Package.swift +40 -0
- package/README.md +1913 -303
- package/android/build.gradle +41 -8
- package/android/proguard-rules.pro +45 -0
- package/android/src/main/AndroidManifest.xml +1 -3
- package/android/src/main/java/ee/forgr/capacitor_updater/AppLifecycleObserver.java +88 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/BundleInfo.java +223 -195
- package/android/src/main/java/ee/forgr/capacitor_updater/BundleStatus.java +23 -23
- package/android/src/main/java/ee/forgr/capacitor_updater/Callback.java +13 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdaterPlugin.java +2720 -1242
- package/android/src/main/java/ee/forgr/capacitor_updater/CapgoUpdater.java +1854 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/CryptoCipher.java +359 -121
- package/android/src/main/java/ee/forgr/capacitor_updater/DataManager.java +28 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/DelayCondition.java +44 -49
- package/android/src/main/java/ee/forgr/capacitor_updater/DelayUntilNext.java +4 -4
- package/android/src/main/java/ee/forgr/capacitor_updater/DelayUpdateUtils.java +296 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/DeviceIdHelper.java +215 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/DownloadService.java +858 -117
- package/android/src/main/java/ee/forgr/capacitor_updater/DownloadWorkerManager.java +156 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/InternalUtils.java +45 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/Logger.java +360 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/ShakeDetector.java +72 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/ShakeMenu.java +603 -0
- package/dist/docs.json +3022 -765
- package/dist/esm/definitions.d.ts +1717 -198
- package/dist/esm/definitions.js +103 -1
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/history.d.ts +1 -0
- package/dist/esm/history.js +283 -0
- package/dist/esm/history.js.map +1 -0
- package/dist/esm/index.d.ts +3 -2
- package/dist/esm/index.js +5 -4
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/web.d.ts +43 -42
- package/dist/esm/web.js +122 -37
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +512 -37
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +512 -37
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/CapacitorUpdaterPlugin/AES.swift +87 -0
- package/ios/Sources/CapacitorUpdaterPlugin/BigInt.swift +55 -0
- package/ios/Sources/CapacitorUpdaterPlugin/BundleInfo.swift +177 -0
- package/ios/{Plugin → Sources/CapacitorUpdaterPlugin}/BundleStatus.swift +12 -12
- package/ios/Sources/CapacitorUpdaterPlugin/CapacitorUpdaterPlugin.swift +2020 -0
- package/ios/Sources/CapacitorUpdaterPlugin/CapgoUpdater.swift +1959 -0
- package/ios/Sources/CapacitorUpdaterPlugin/CryptoCipher.swift +313 -0
- package/ios/Sources/CapacitorUpdaterPlugin/DelayUpdateUtils.swift +257 -0
- package/ios/Sources/CapacitorUpdaterPlugin/DeviceIdHelper.swift +120 -0
- package/ios/Sources/CapacitorUpdaterPlugin/InternalUtils.swift +392 -0
- package/ios/Sources/CapacitorUpdaterPlugin/Logger.swift +310 -0
- package/ios/Sources/CapacitorUpdaterPlugin/RSA.swift +274 -0
- package/ios/Sources/CapacitorUpdaterPlugin/ShakeMenu.swift +441 -0
- package/ios/{Plugin → Sources/CapacitorUpdaterPlugin}/UserDefaultsExtension.swift +1 -2
- package/package.json +49 -41
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdater.java +0 -1131
- package/ios/Plugin/BundleInfo.swift +0 -113
- package/ios/Plugin/CapacitorUpdater.swift +0 -850
- package/ios/Plugin/CapacitorUpdaterPlugin.h +0 -10
- package/ios/Plugin/CapacitorUpdaterPlugin.m +0 -27
- package/ios/Plugin/CapacitorUpdaterPlugin.swift +0 -678
- package/ios/Plugin/CryptoCipher.swift +0 -240
- /package/{LICENCE → LICENSE} +0 -0
- /package/ios/{Plugin → Sources/CapacitorUpdaterPlugin}/DelayCondition.swift +0 -0
- /package/ios/{Plugin → Sources/CapacitorUpdaterPlugin}/DelayUntilNext.swift +0 -0
- /package/ios/{Plugin → Sources/CapacitorUpdaterPlugin}/Info.plist +0 -0
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
3
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import UIKit
|
|
8
|
+
import Capacitor
|
|
9
|
+
|
|
10
|
+
extension UIApplication {
|
|
11
|
+
// swiftlint:disable:next line_length
|
|
12
|
+
public class func topViewController(_ base: UIViewController? = UIApplication.shared.windows.first?.rootViewController) -> UIViewController? {
|
|
13
|
+
if let nav = base as? UINavigationController {
|
|
14
|
+
return topViewController(nav.visibleViewController)
|
|
15
|
+
}
|
|
16
|
+
if let tab = base as? UITabBarController, let selected = tab.selectedViewController {
|
|
17
|
+
return topViewController(selected)
|
|
18
|
+
}
|
|
19
|
+
if let presented = base?.presentedViewController {
|
|
20
|
+
return topViewController(presented)
|
|
21
|
+
}
|
|
22
|
+
return base
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
extension UIWindow {
|
|
27
|
+
override open func motionEnded(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {
|
|
28
|
+
if motion == .motionShake {
|
|
29
|
+
// Find the CapacitorUpdaterPlugin instance
|
|
30
|
+
guard let bridge = (rootViewController as? CAPBridgeProtocol),
|
|
31
|
+
let plugin = bridge.plugin(withName: "CapacitorUpdaterPlugin") as? CapacitorUpdaterPlugin else {
|
|
32
|
+
return
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Check if shake menu is enabled
|
|
36
|
+
if !plugin.shakeMenuEnabled {
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Check if channel selector mode is enabled
|
|
41
|
+
if plugin.shakeChannelSelectorEnabled {
|
|
42
|
+
showChannelSelector(plugin: plugin, bridge: bridge)
|
|
43
|
+
} else {
|
|
44
|
+
showDefaultMenu(plugin: plugin, bridge: bridge)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private func showDefaultMenu(plugin: CapacitorUpdaterPlugin, bridge: CAPBridgeProtocol) {
|
|
50
|
+
// Prevent multiple alerts from showing
|
|
51
|
+
if let topVC = UIApplication.topViewController(),
|
|
52
|
+
topVC.isKind(of: UIAlertController.self) {
|
|
53
|
+
plugin.logger.info("UIAlertController is already presented")
|
|
54
|
+
return
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
let appName = Bundle.main.infoDictionary?["CFBundleDisplayName"] as? String ?? "App"
|
|
58
|
+
let title = "Preview \(appName) Menu"
|
|
59
|
+
let message = "What would you like to do?"
|
|
60
|
+
let okButtonTitle = "Go Home"
|
|
61
|
+
let reloadButtonTitle = "Reload app"
|
|
62
|
+
let cancelButtonTitle = "Close menu"
|
|
63
|
+
|
|
64
|
+
let updater = plugin.implementation
|
|
65
|
+
|
|
66
|
+
func resetBuiltin() {
|
|
67
|
+
updater.reset()
|
|
68
|
+
bridge.setServerBasePath("")
|
|
69
|
+
DispatchQueue.main.async {
|
|
70
|
+
if let viewController = (self.rootViewController as? CAPBridgeViewController) {
|
|
71
|
+
viewController.loadView()
|
|
72
|
+
viewController.viewDidLoad()
|
|
73
|
+
}
|
|
74
|
+
_ = updater.delete(id: updater.getCurrentBundleId())
|
|
75
|
+
plugin.logger.info("Reset to builtin version")
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
let bundleId = updater.getCurrentBundleId()
|
|
80
|
+
if let viewController = (self.rootViewController as? CAPBridgeViewController) {
|
|
81
|
+
plugin.logger.info("getServerBasePath: \(viewController.getServerBasePath())")
|
|
82
|
+
}
|
|
83
|
+
plugin.logger.info("bundleId: \(bundleId)")
|
|
84
|
+
|
|
85
|
+
let alertShake = UIAlertController(title: title, message: message, preferredStyle: .alert)
|
|
86
|
+
|
|
87
|
+
alertShake.addAction(UIAlertAction(title: okButtonTitle, style: .default) { _ in
|
|
88
|
+
guard let next = updater.getNextBundle() else {
|
|
89
|
+
resetBuiltin()
|
|
90
|
+
return
|
|
91
|
+
}
|
|
92
|
+
if !next.isBuiltin() {
|
|
93
|
+
plugin.logger.info("Resetting to: \(next.toString())")
|
|
94
|
+
_ = updater.set(bundle: next)
|
|
95
|
+
let destHot = updater.getBundleDirectory(id: next.getId())
|
|
96
|
+
plugin.logger.info("Reloading \(next.toString())")
|
|
97
|
+
bridge.setServerBasePath(destHot.path)
|
|
98
|
+
} else {
|
|
99
|
+
resetBuiltin()
|
|
100
|
+
}
|
|
101
|
+
plugin.logger.info("Reload app done")
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
alertShake.addAction(UIAlertAction(title: cancelButtonTitle, style: .default))
|
|
105
|
+
|
|
106
|
+
alertShake.addAction(UIAlertAction(title: reloadButtonTitle, style: .default) { _ in
|
|
107
|
+
DispatchQueue.main.async {
|
|
108
|
+
bridge.webView?.reload()
|
|
109
|
+
}
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
DispatchQueue.main.async {
|
|
113
|
+
if let topVC = UIApplication.topViewController() {
|
|
114
|
+
topVC.present(alertShake, animated: true)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
private func showChannelSelector(plugin: CapacitorUpdaterPlugin, bridge: CAPBridgeProtocol) {
|
|
120
|
+
// Prevent multiple alerts from showing
|
|
121
|
+
if let topVC = UIApplication.topViewController(),
|
|
122
|
+
topVC.isKind(of: UIAlertController.self) {
|
|
123
|
+
plugin.logger.info("UIAlertController is already presented")
|
|
124
|
+
return
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
let updater = plugin.implementation
|
|
128
|
+
|
|
129
|
+
// Show loading indicator
|
|
130
|
+
let loadingAlert = UIAlertController(title: "Loading Channels...", message: nil, preferredStyle: .alert)
|
|
131
|
+
var didCancel = false
|
|
132
|
+
let loadingIndicator = UIActivityIndicatorView(style: .medium)
|
|
133
|
+
loadingIndicator.translatesAutoresizingMaskIntoConstraints = false
|
|
134
|
+
loadingIndicator.startAnimating()
|
|
135
|
+
loadingAlert.view.addSubview(loadingIndicator)
|
|
136
|
+
|
|
137
|
+
NSLayoutConstraint.activate([
|
|
138
|
+
loadingIndicator.centerXAnchor.constraint(equalTo: loadingAlert.view.centerXAnchor),
|
|
139
|
+
loadingIndicator.bottomAnchor.constraint(equalTo: loadingAlert.view.bottomAnchor, constant: -20)
|
|
140
|
+
])
|
|
141
|
+
|
|
142
|
+
// Add cancel button to loading alert
|
|
143
|
+
loadingAlert.addAction(UIAlertAction(title: "Cancel", style: .cancel) { _ in
|
|
144
|
+
didCancel = true
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
DispatchQueue.main.async {
|
|
148
|
+
if let topVC = UIApplication.topViewController() {
|
|
149
|
+
topVC.present(loadingAlert, animated: true) {
|
|
150
|
+
// Fetch channels in background
|
|
151
|
+
DispatchQueue.global(qos: .userInitiated).async {
|
|
152
|
+
let result = updater.listChannels()
|
|
153
|
+
|
|
154
|
+
DispatchQueue.main.async {
|
|
155
|
+
loadingAlert.dismiss(animated: true) {
|
|
156
|
+
guard !didCancel else { return }
|
|
157
|
+
if !result.error.isEmpty {
|
|
158
|
+
self.showError(message: "Failed to load channels: \(result.error)", plugin: plugin)
|
|
159
|
+
} else if result.channels.isEmpty {
|
|
160
|
+
self.showError(message: "No channels available for self-assignment", plugin: plugin)
|
|
161
|
+
} else {
|
|
162
|
+
self.presentChannelPicker(channels: result.channels, plugin: plugin, bridge: bridge)
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
private func presentChannelPicker(channels: [[String: Any]], plugin: CapacitorUpdaterPlugin, bridge: CAPBridgeProtocol) {
|
|
173
|
+
let alert = UIAlertController(title: "Select Channel", message: "Choose a channel to switch to", preferredStyle: .actionSheet)
|
|
174
|
+
|
|
175
|
+
// Get channel names
|
|
176
|
+
let channelNames = channels.compactMap { $0["name"] as? String }
|
|
177
|
+
|
|
178
|
+
// Show first 5 channels as actions
|
|
179
|
+
let channelsToShow = Array(channelNames.prefix(5))
|
|
180
|
+
|
|
181
|
+
for channelName in channelsToShow {
|
|
182
|
+
alert.addAction(UIAlertAction(title: channelName, style: .default) { [weak self] _ in
|
|
183
|
+
self?.selectChannel(name: channelName, plugin: plugin, bridge: bridge)
|
|
184
|
+
})
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// If there are more channels, add a "More..." option
|
|
188
|
+
if channelNames.count > 5 {
|
|
189
|
+
alert.addAction(UIAlertAction(title: "More channels...", style: .default) { [weak self] _ in
|
|
190
|
+
self?.showSearchableChannelPicker(channels: channels, plugin: plugin, bridge: bridge)
|
|
191
|
+
})
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
|
|
195
|
+
|
|
196
|
+
// For iPad support
|
|
197
|
+
if let popoverController = alert.popoverPresentationController {
|
|
198
|
+
popoverController.sourceView = self
|
|
199
|
+
popoverController.sourceRect = CGRect(x: self.bounds.midX, y: self.bounds.midY, width: 0, height: 0)
|
|
200
|
+
popoverController.permittedArrowDirections = []
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
DispatchQueue.main.async {
|
|
204
|
+
if let topVC = UIApplication.topViewController() {
|
|
205
|
+
topVC.present(alert, animated: true)
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
private func showSearchableChannelPicker(channels: [[String: Any]], plugin: CapacitorUpdaterPlugin, bridge: CAPBridgeProtocol) {
|
|
211
|
+
let alert = UIAlertController(title: "Search Channels", message: "Enter channel name to search", preferredStyle: .alert)
|
|
212
|
+
|
|
213
|
+
alert.addTextField { textField in
|
|
214
|
+
textField.placeholder = "Channel name..."
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
let channelNames = channels.compactMap { $0["name"] as? String }
|
|
218
|
+
|
|
219
|
+
alert.addAction(UIAlertAction(title: "Search", style: .default) { [weak self, weak alert] _ in
|
|
220
|
+
guard let searchText = alert?.textFields?.first?.text?.lowercased(), !searchText.isEmpty else {
|
|
221
|
+
// If empty, show first 5
|
|
222
|
+
self?.presentChannelPicker(channels: channels, plugin: plugin, bridge: bridge)
|
|
223
|
+
return
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Filter channels
|
|
227
|
+
let filtered = channelNames.filter { $0.lowercased().contains(searchText) }
|
|
228
|
+
|
|
229
|
+
if filtered.isEmpty {
|
|
230
|
+
self?.showError(message: "No channels found matching '\(searchText)'", plugin: plugin)
|
|
231
|
+
} else if filtered.count == 1 {
|
|
232
|
+
// Directly select if only one match
|
|
233
|
+
self?.selectChannel(name: filtered[0], plugin: plugin, bridge: bridge)
|
|
234
|
+
} else {
|
|
235
|
+
// Show filtered results
|
|
236
|
+
let filteredChannels = channels.filter { channel in
|
|
237
|
+
if let name = channel["name"] as? String {
|
|
238
|
+
return name.lowercased().contains(searchText)
|
|
239
|
+
}
|
|
240
|
+
return false
|
|
241
|
+
}
|
|
242
|
+
self?.presentChannelPicker(channels: filteredChannels, plugin: plugin, bridge: bridge)
|
|
243
|
+
}
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
|
|
247
|
+
|
|
248
|
+
DispatchQueue.main.async {
|
|
249
|
+
if let topVC = UIApplication.topViewController() {
|
|
250
|
+
topVC.present(alert, animated: true)
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
private func selectChannel(name: String, plugin: CapacitorUpdaterPlugin, bridge: CAPBridgeProtocol) {
|
|
256
|
+
let updater = plugin.implementation
|
|
257
|
+
|
|
258
|
+
// Show progress indicator
|
|
259
|
+
let progressAlert = UIAlertController(title: "Switching to \(name)", message: "Setting channel...", preferredStyle: .alert)
|
|
260
|
+
let indicator = UIActivityIndicatorView(style: .medium)
|
|
261
|
+
indicator.translatesAutoresizingMaskIntoConstraints = false
|
|
262
|
+
indicator.startAnimating()
|
|
263
|
+
progressAlert.view.addSubview(indicator)
|
|
264
|
+
|
|
265
|
+
NSLayoutConstraint.activate([
|
|
266
|
+
indicator.centerXAnchor.constraint(equalTo: progressAlert.view.centerXAnchor),
|
|
267
|
+
indicator.bottomAnchor.constraint(equalTo: progressAlert.view.bottomAnchor, constant: -20)
|
|
268
|
+
])
|
|
269
|
+
|
|
270
|
+
DispatchQueue.main.async {
|
|
271
|
+
if let topVC = UIApplication.topViewController() {
|
|
272
|
+
topVC.present(progressAlert, animated: true) {
|
|
273
|
+
DispatchQueue.global(qos: .userInitiated).async {
|
|
274
|
+
// Set the channel - respect plugin's allowSetDefaultChannel config
|
|
275
|
+
let setResult = updater.setChannel(
|
|
276
|
+
channel: name,
|
|
277
|
+
defaultChannelKey: "CapacitorUpdater.defaultChannel",
|
|
278
|
+
allowSetDefaultChannel: plugin.allowSetDefaultChannel
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
if !setResult.error.isEmpty {
|
|
282
|
+
DispatchQueue.main.async {
|
|
283
|
+
progressAlert.dismiss(animated: true) {
|
|
284
|
+
self.showError(message: "Failed to set channel: \(setResult.error)", plugin: plugin)
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Update progress message
|
|
291
|
+
DispatchQueue.main.async {
|
|
292
|
+
progressAlert.message = "Checking for updates..."
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Check for updates with the new channel
|
|
296
|
+
let pluginUpdateUrl = plugin.getUpdateUrl()
|
|
297
|
+
let updateUrlStr = pluginUpdateUrl.isEmpty ? CapacitorUpdaterPlugin.updateUrlDefault : pluginUpdateUrl
|
|
298
|
+
guard let updateUrl = URL(string: updateUrlStr) else {
|
|
299
|
+
DispatchQueue.main.async {
|
|
300
|
+
progressAlert.dismiss(animated: true) {
|
|
301
|
+
self.showError(
|
|
302
|
+
message: "Channel set to \(name). Invalid update URL, could not check for updates.",
|
|
303
|
+
plugin: plugin
|
|
304
|
+
)
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
return
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
let latest = updater.getLatest(url: updateUrl, channel: name)
|
|
311
|
+
|
|
312
|
+
// Handle update errors first (before "no new version" check)
|
|
313
|
+
if let error = latest.error, !error.isEmpty && error != "no_new_version_available" {
|
|
314
|
+
DispatchQueue.main.async {
|
|
315
|
+
progressAlert.dismiss(animated: true) {
|
|
316
|
+
self.showError(message: "Channel set to \(name). Update check failed: \(error)", plugin: plugin)
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
return
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Check if there's an actual update available
|
|
323
|
+
if latest.error == "no_new_version_available" || latest.url.isEmpty {
|
|
324
|
+
DispatchQueue.main.async {
|
|
325
|
+
progressAlert.dismiss(animated: true) {
|
|
326
|
+
self.showSuccess(message: "Channel set to \(name). Already on latest version.", plugin: plugin)
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
return
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Update message
|
|
333
|
+
DispatchQueue.main.async {
|
|
334
|
+
progressAlert.message = "Downloading update \(latest.version)..."
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Download the update
|
|
338
|
+
do {
|
|
339
|
+
let bundle: BundleInfo
|
|
340
|
+
if let manifest = latest.manifest, !manifest.isEmpty {
|
|
341
|
+
bundle = try updater.downloadManifest(
|
|
342
|
+
manifest: manifest,
|
|
343
|
+
version: latest.version,
|
|
344
|
+
sessionKey: latest.sessionKey ?? ""
|
|
345
|
+
)
|
|
346
|
+
} else {
|
|
347
|
+
// Safe unwrap URL
|
|
348
|
+
guard let downloadUrl = URL(string: latest.url) else {
|
|
349
|
+
DispatchQueue.main.async {
|
|
350
|
+
progressAlert.dismiss(animated: true) {
|
|
351
|
+
self.showError(message: "Failed to download update: invalid update URL.", plugin: plugin)
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
return
|
|
355
|
+
}
|
|
356
|
+
bundle = try updater.download(
|
|
357
|
+
url: downloadUrl,
|
|
358
|
+
version: latest.version,
|
|
359
|
+
sessionKey: latest.sessionKey ?? ""
|
|
360
|
+
)
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Set as next bundle
|
|
364
|
+
_ = updater.setNextBundle(next: bundle.getId())
|
|
365
|
+
|
|
366
|
+
DispatchQueue.main.async {
|
|
367
|
+
progressAlert.dismiss(animated: true) {
|
|
368
|
+
self.showSuccessWithReload(
|
|
369
|
+
message: "Update downloaded! Reload to apply version \(latest.version)?",
|
|
370
|
+
plugin: plugin,
|
|
371
|
+
bridge: bridge,
|
|
372
|
+
onReload: { [weak plugin] in
|
|
373
|
+
_ = updater.set(bundle: bundle)
|
|
374
|
+
_ = plugin?._reload()
|
|
375
|
+
}
|
|
376
|
+
)
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
} catch {
|
|
380
|
+
DispatchQueue.main.async {
|
|
381
|
+
progressAlert.dismiss(animated: true) {
|
|
382
|
+
self.showError(message: "Failed to download update: \(error.localizedDescription)", plugin: plugin)
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
private func showError(message: String, plugin: CapacitorUpdaterPlugin) {
|
|
393
|
+
plugin.logger.error(message)
|
|
394
|
+
let alert = UIAlertController(title: "Error", message: message, preferredStyle: .alert)
|
|
395
|
+
alert.addAction(UIAlertAction(title: "OK", style: .default))
|
|
396
|
+
|
|
397
|
+
DispatchQueue.main.async {
|
|
398
|
+
if let topVC = UIApplication.topViewController() {
|
|
399
|
+
topVC.present(alert, animated: true)
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
private func showSuccess(message: String, plugin: CapacitorUpdaterPlugin) {
|
|
405
|
+
plugin.logger.info(message)
|
|
406
|
+
let alert = UIAlertController(title: "Success", message: message, preferredStyle: .alert)
|
|
407
|
+
alert.addAction(UIAlertAction(title: "OK", style: .default))
|
|
408
|
+
|
|
409
|
+
DispatchQueue.main.async {
|
|
410
|
+
if let topVC = UIApplication.topViewController() {
|
|
411
|
+
topVC.present(alert, animated: true)
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
private func showSuccessWithReload(
|
|
417
|
+
message: String,
|
|
418
|
+
plugin: CapacitorUpdaterPlugin,
|
|
419
|
+
bridge: CAPBridgeProtocol,
|
|
420
|
+
onReload: (() -> Void)? = nil
|
|
421
|
+
) {
|
|
422
|
+
plugin.logger.info(message)
|
|
423
|
+
let alert = UIAlertController(title: "Update Ready", message: message, preferredStyle: .alert)
|
|
424
|
+
alert.addAction(UIAlertAction(title: "Later", style: .cancel))
|
|
425
|
+
alert.addAction(UIAlertAction(title: "Reload Now", style: .default) { _ in
|
|
426
|
+
if let onReload = onReload {
|
|
427
|
+
onReload()
|
|
428
|
+
} else {
|
|
429
|
+
DispatchQueue.main.async {
|
|
430
|
+
bridge.webView?.reload()
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
})
|
|
434
|
+
|
|
435
|
+
DispatchQueue.main.async {
|
|
436
|
+
if let topVC = UIApplication.topViewController() {
|
|
437
|
+
topVC.present(alert, animated: true)
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
@@ -15,6 +15,7 @@ enum ObjectSavableError: String, LocalizedError {
|
|
|
15
15
|
case unableToEncode = "Unable to encode object into data"
|
|
16
16
|
case noValue = "No data object found for the given key"
|
|
17
17
|
case unableToDecode = "Unable to decode object into given type"
|
|
18
|
+
case checksum = "Checksum failed"
|
|
18
19
|
|
|
19
20
|
var errorDescription: String? {
|
|
20
21
|
rawValue
|
|
@@ -33,9 +34,7 @@ extension UserDefaults: ObjectSavable {
|
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
func getObj<Object>(forKey: String, castTo type: Object.Type) throws -> Object where Object: Decodable {
|
|
36
|
-
// print("forKey", forKey)
|
|
37
37
|
guard let data: Data = data(forKey: forKey) else { throw ObjectSavableError.noValue }
|
|
38
|
-
// print("data", data)
|
|
39
38
|
let decoder: JSONDecoder = JSONDecoder()
|
|
40
39
|
do {
|
|
41
40
|
let object: Object = try decoder.decode(type, from: data)
|
package/package.json
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@capgo/capacitor-updater",
|
|
3
|
-
"version": "4.
|
|
4
|
-
"packageManager": "pnpm@8.2.0",
|
|
3
|
+
"version": "4.43.5",
|
|
5
4
|
"license": "MPL-2.0",
|
|
6
5
|
"description": "Live update for capacitor apps",
|
|
7
6
|
"main": "dist/plugin.cjs.js",
|
|
@@ -10,12 +9,14 @@
|
|
|
10
9
|
"unpkg": "dist/plugin.js",
|
|
11
10
|
"files": [
|
|
12
11
|
"android/src/main/",
|
|
12
|
+
"android/proguard-rules.pro",
|
|
13
13
|
"android/build.gradle",
|
|
14
14
|
"dist/",
|
|
15
|
-
"ios/
|
|
16
|
-
"CapgoCapacitorUpdater.podspec"
|
|
15
|
+
"ios/Sources/",
|
|
16
|
+
"CapgoCapacitorUpdater.podspec",
|
|
17
|
+
"Package.swift"
|
|
17
18
|
],
|
|
18
|
-
"author": "Martin Donadieu",
|
|
19
|
+
"author": "Martin Donadieu <martin@capgo.app>",
|
|
19
20
|
"repository": {
|
|
20
21
|
"type": "git",
|
|
21
22
|
"url": "git+https://github.com/Cap-go/capacitor-updater.git"
|
|
@@ -23,58 +24,65 @@
|
|
|
23
24
|
"bugs": {
|
|
24
25
|
"url": "https://github.com/Cap-go/capacitor-updater/issues"
|
|
25
26
|
},
|
|
27
|
+
"homepage": "https://capgo.app/docs/plugins/updater/",
|
|
26
28
|
"keywords": [
|
|
27
29
|
"capacitor",
|
|
28
|
-
"
|
|
29
|
-
"OTA",
|
|
30
|
-
"manual update",
|
|
30
|
+
"live updates",
|
|
31
31
|
"live update",
|
|
32
|
+
"updates",
|
|
32
33
|
"auto update",
|
|
34
|
+
"manual update",
|
|
35
|
+
"capgo",
|
|
36
|
+
"plugin",
|
|
37
|
+
"OTA",
|
|
33
38
|
"ionic",
|
|
34
39
|
"appflow alternative",
|
|
35
|
-
"
|
|
40
|
+
"microsoft alternative",
|
|
36
41
|
"native"
|
|
37
42
|
],
|
|
38
43
|
"scripts": {
|
|
39
|
-
"verify": "
|
|
40
|
-
"verify:ios": "
|
|
41
|
-
"verify:android": "cd android && ./gradlew clean
|
|
42
|
-
"verify:web": "
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"
|
|
44
|
+
"verify": "bun run verify:ios && bun run verify:android && bun run verify:web",
|
|
45
|
+
"verify:ios": "xcodebuild -scheme CapgoCapacitorUpdater -destination generic/platform=iOS",
|
|
46
|
+
"verify:android": "cd android && ./gradlew clean assemble test && cd ..",
|
|
47
|
+
"verify:web": "bun run build",
|
|
48
|
+
"test": "bun run test:ios && bun run test:android",
|
|
49
|
+
"test:ios": "./scripts/test-ios.sh",
|
|
50
|
+
"test:android": "cd android && ./gradlew test && cd ..",
|
|
51
|
+
"lint": "bun run eslint && bun run prettier -- --check && bun run swiftlint -- lint",
|
|
52
|
+
"fmt": "bun run eslint -- --fix && bun run prettier -- --write && bun run swiftlint -- --fix --format",
|
|
53
|
+
"eslint": "eslint . --ext .ts",
|
|
54
|
+
"prettier": "prettier-pretty-check \"**/*.{css,html,ts,js,java}\" --plugin=prettier-plugin-java",
|
|
47
55
|
"swiftlint": "node-swiftlint",
|
|
48
|
-
"docgen": "
|
|
49
|
-
"
|
|
50
|
-
"build": "npm run clean && npm run docgen && tsc && rollup -c rollup.config.mjs",
|
|
56
|
+
"docgen": "node scripts/generate-docs.js",
|
|
57
|
+
"build": "bun run clean && bun run docgen && tsc && rollup -c rollup.config.mjs",
|
|
51
58
|
"clean": "rimraf ./dist",
|
|
52
59
|
"watch": "tsc --watch",
|
|
53
|
-
"prepublishOnly": "
|
|
60
|
+
"prepublishOnly": "bun run build",
|
|
61
|
+
"check:wiring": "node scripts/check-capacitor-plugin-wiring.mjs"
|
|
54
62
|
},
|
|
55
63
|
"devDependencies": {
|
|
56
|
-
"@capacitor/android": "^4.
|
|
57
|
-
"@capacitor/cli": "^4.
|
|
58
|
-
"@capacitor/core": "^4.
|
|
59
|
-
"@capacitor/docgen": "^0.
|
|
60
|
-
"@capacitor/ios": "^4.
|
|
61
|
-
"@ionic/eslint-config": "^0.
|
|
62
|
-
"@ionic/prettier-config": "^
|
|
63
|
-
"@ionic/swiftlint-config": "^
|
|
64
|
-
"@types/node": "^
|
|
65
|
-
"
|
|
66
|
-
"
|
|
67
|
-
"
|
|
68
|
-
"
|
|
69
|
-
"prettier": "^2.
|
|
70
|
-
"
|
|
71
|
-
"
|
|
72
|
-
"
|
|
73
|
-
"
|
|
74
|
-
"
|
|
64
|
+
"@capacitor/android": "^4.8.2",
|
|
65
|
+
"@capacitor/cli": "^4.8.2",
|
|
66
|
+
"@capacitor/core": "^4.8.2",
|
|
67
|
+
"@capacitor/docgen": "^0.3.0",
|
|
68
|
+
"@capacitor/ios": "^4.8.2",
|
|
69
|
+
"@ionic/eslint-config": "^0.4.0",
|
|
70
|
+
"@ionic/prettier-config": "^4.0.0",
|
|
71
|
+
"@ionic/swiftlint-config": "^2.0.0",
|
|
72
|
+
"@types/node": "^24.3.0",
|
|
73
|
+
"eslint": "^8.57.0",
|
|
74
|
+
"eslint-plugin-import": "^2.32.0",
|
|
75
|
+
"husky": "^9.1.7",
|
|
76
|
+
"prettier": "^3.6.2",
|
|
77
|
+
"prettier-plugin-java": "^2.7.4",
|
|
78
|
+
"rimraf": "^6.0.1",
|
|
79
|
+
"rollup": "^4.50.0",
|
|
80
|
+
"swiftlint": "^2.0.0",
|
|
81
|
+
"typescript": "^5.9.2",
|
|
82
|
+
"prettier-pretty-check": "^0.2.0"
|
|
75
83
|
},
|
|
76
84
|
"peerDependencies": {
|
|
77
|
-
"@capacitor/core": "^4.0.0
|
|
85
|
+
"@capacitor/core": "^4.0.0"
|
|
78
86
|
},
|
|
79
87
|
"prettier": "@ionic/prettier-config",
|
|
80
88
|
"swiftlint": "@ionic/swiftlint-config",
|