@capgo/capacitor-updater 7.20.0 → 7.22.0
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 +83 -14
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdaterPlugin.java +177 -31
- package/android/src/main/java/ee/forgr/capacitor_updater/CapgoUpdater.java +61 -47
- package/dist/docs.json +146 -2
- package/dist/esm/definitions.d.ts +50 -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 +1 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/web.d.ts +3 -1
- package/dist/esm/web.js +8 -0
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +290 -0
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +290 -0
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/CapacitorUpdaterPlugin/CapacitorUpdaterPlugin.swift +143 -15
- package/ios/Sources/CapacitorUpdaterPlugin/CapgoUpdater.swift +3 -0
- package/ios/Sources/CapacitorUpdaterPlugin/InternalUtils.swift +2 -0
- package/package.json +1 -1
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import Foundation
|
|
8
8
|
import Capacitor
|
|
9
9
|
import UIKit
|
|
10
|
+
import WebKit
|
|
10
11
|
import Version
|
|
11
12
|
|
|
12
13
|
/**
|
|
@@ -27,6 +28,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
27
28
|
CAPPluginMethod(name: "set", returnType: CAPPluginReturnPromise),
|
|
28
29
|
CAPPluginMethod(name: "list", returnType: CAPPluginReturnPromise),
|
|
29
30
|
CAPPluginMethod(name: "delete", returnType: CAPPluginReturnPromise),
|
|
31
|
+
CAPPluginMethod(name: "setBundleError", returnType: CAPPluginReturnPromise),
|
|
30
32
|
CAPPluginMethod(name: "reset", returnType: CAPPluginReturnPromise),
|
|
31
33
|
CAPPluginMethod(name: "current", returnType: CAPPluginReturnPromise),
|
|
32
34
|
CAPPluginMethod(name: "reload", returnType: CAPPluginReturnPromise),
|
|
@@ -47,18 +49,21 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
47
49
|
CAPPluginMethod(name: "getBuiltinVersion", returnType: CAPPluginReturnPromise),
|
|
48
50
|
CAPPluginMethod(name: "isAutoUpdateAvailable", returnType: CAPPluginReturnPromise),
|
|
49
51
|
CAPPluginMethod(name: "getNextBundle", returnType: CAPPluginReturnPromise),
|
|
52
|
+
CAPPluginMethod(name: "getFailedUpdate", returnType: CAPPluginReturnPromise),
|
|
50
53
|
CAPPluginMethod(name: "setShakeMenu", returnType: CAPPluginReturnPromise),
|
|
51
54
|
CAPPluginMethod(name: "isShakeMenuEnabled", returnType: CAPPluginReturnPromise)
|
|
52
55
|
]
|
|
53
56
|
public var implementation = CapgoUpdater()
|
|
54
|
-
private let PLUGIN_VERSION: String = "7.
|
|
57
|
+
private let PLUGIN_VERSION: String = "7.22.0"
|
|
55
58
|
static let updateUrlDefault = "https://plugin.capgo.app/updates"
|
|
56
59
|
static let statsUrlDefault = "https://plugin.capgo.app/stats"
|
|
57
60
|
static let channelUrlDefault = "https://plugin.capgo.app/channel_self"
|
|
61
|
+
private let keepUrlPathFlagKey = "__capgo_keep_url_path_after_reload"
|
|
58
62
|
private let customIdDefaultsKey = "CapacitorUpdater.customId"
|
|
59
63
|
private let updateUrlDefaultsKey = "CapacitorUpdater.updateUrl"
|
|
60
64
|
private let statsUrlDefaultsKey = "CapacitorUpdater.statsUrl"
|
|
61
65
|
private let channelUrlDefaultsKey = "CapacitorUpdater.channelUrl"
|
|
66
|
+
private let lastFailedBundleDefaultsKey = "CapacitorUpdater.lastFailedBundle"
|
|
62
67
|
// Note: DELAY_CONDITION_PREFERENCES is now defined in DelayUpdateUtils.DELAY_CONDITION_PREFERENCES
|
|
63
68
|
private var updateUrl = ""
|
|
64
69
|
private var backgroundTaskID: UIBackgroundTaskIdentifier = UIBackgroundTaskIdentifier.invalid
|
|
@@ -86,6 +91,8 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
86
91
|
private var periodCheckDelay = 0
|
|
87
92
|
private var persistCustomId = false
|
|
88
93
|
private var persistModifyUrl = false
|
|
94
|
+
private var allowManualBundleError = false
|
|
95
|
+
private var keepUrlPathFlagLastValue: Bool?
|
|
89
96
|
public var shakeMenuEnabled = false
|
|
90
97
|
let semaphoreReady = DispatchSemaphore(value: 0)
|
|
91
98
|
|
|
@@ -118,6 +125,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
118
125
|
}
|
|
119
126
|
}
|
|
120
127
|
persistModifyUrl = getConfig().getBoolean("persistModifyUrl", false)
|
|
128
|
+
allowManualBundleError = getConfig().getBoolean("allowManualBundleError", false)
|
|
121
129
|
logger.info("init for device \(self.implementation.deviceID)")
|
|
122
130
|
guard let versionName = getConfig().getString("version", Bundle.main.versionName) else {
|
|
123
131
|
logger.error("Cannot get version name")
|
|
@@ -135,6 +143,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
135
143
|
autoDeleteFailed = getConfig().getBoolean("autoDeleteFailed", true)
|
|
136
144
|
autoDeletePrevious = getConfig().getBoolean("autoDeletePrevious", true)
|
|
137
145
|
keepUrlPathAfterReload = getConfig().getBoolean("keepUrlPathAfterReload", false)
|
|
146
|
+
syncKeepUrlPathFlag(enabled: keepUrlPathAfterReload)
|
|
138
147
|
|
|
139
148
|
// Handle directUpdate configuration - support string values and backward compatibility
|
|
140
149
|
if let directUpdateString = getConfig().getString("directUpdate") {
|
|
@@ -232,8 +241,58 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
232
241
|
self.checkForUpdateAfterDelay()
|
|
233
242
|
}
|
|
234
243
|
|
|
244
|
+
private func syncKeepUrlPathFlag(enabled: Bool) {
|
|
245
|
+
let script: String
|
|
246
|
+
if enabled {
|
|
247
|
+
script = "(function(){ try { localStorage.setItem('\(keepUrlPathFlagKey)', '1'); } catch (err) {} window.__capgoKeepUrlPathAfterReload = true; var evt; try { evt = new CustomEvent('CapacitorUpdaterKeepUrlPathAfterReload', { detail: { enabled: true } }); } catch (e) { evt = document.createEvent('CustomEvent'); evt.initCustomEvent('CapacitorUpdaterKeepUrlPathAfterReload', false, false, { enabled: true }); } window.dispatchEvent(evt); })();"
|
|
248
|
+
} else {
|
|
249
|
+
script = "(function(){ try { localStorage.removeItem('\(keepUrlPathFlagKey)'); } catch (err) {} delete window.__capgoKeepUrlPathAfterReload; var evt; try { evt = new CustomEvent('CapacitorUpdaterKeepUrlPathAfterReload', { detail: { enabled: false } }); } catch (e) { evt = document.createEvent('CustomEvent'); evt.initCustomEvent('CapacitorUpdaterKeepUrlPathAfterReload', false, false, { enabled: false }); } window.dispatchEvent(evt); })();"
|
|
250
|
+
}
|
|
251
|
+
DispatchQueue.main.async { [weak self] in
|
|
252
|
+
guard let self = self, let webView = self.bridge?.webView else {
|
|
253
|
+
return
|
|
254
|
+
}
|
|
255
|
+
if self.keepUrlPathFlagLastValue != enabled {
|
|
256
|
+
let userScript = WKUserScript(source: script, injectionTime: .atDocumentStart, forMainFrameOnly: true)
|
|
257
|
+
webView.configuration.userContentController.addUserScript(userScript)
|
|
258
|
+
self.keepUrlPathFlagLastValue = enabled
|
|
259
|
+
}
|
|
260
|
+
webView.evaluateJavaScript(script, completionHandler: nil)
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
private func persistLastFailedBundle(_ bundle: BundleInfo?) {
|
|
265
|
+
if let bundle = bundle {
|
|
266
|
+
do {
|
|
267
|
+
try UserDefaults.standard.setObj(bundle, forKey: lastFailedBundleDefaultsKey)
|
|
268
|
+
} catch {
|
|
269
|
+
logger.error("Failed to persist failed bundle info \(error.localizedDescription)")
|
|
270
|
+
}
|
|
271
|
+
} else {
|
|
272
|
+
UserDefaults.standard.removeObject(forKey: lastFailedBundleDefaultsKey)
|
|
273
|
+
}
|
|
274
|
+
UserDefaults.standard.synchronize()
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
private func readLastFailedBundle() -> BundleInfo? {
|
|
278
|
+
do {
|
|
279
|
+
let bundle: BundleInfo = try UserDefaults.standard.getObj(forKey: lastFailedBundleDefaultsKey, castTo: BundleInfo.self)
|
|
280
|
+
return bundle
|
|
281
|
+
} catch ObjectSavableError.noValue {
|
|
282
|
+
return nil
|
|
283
|
+
} catch {
|
|
284
|
+
logger.error("Failed to read failed bundle info \(error.localizedDescription)")
|
|
285
|
+
UserDefaults.standard.removeObject(forKey: lastFailedBundleDefaultsKey)
|
|
286
|
+
UserDefaults.standard.synchronize()
|
|
287
|
+
return nil
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
235
291
|
private func initialLoad() -> Bool {
|
|
236
292
|
guard let bridge = self.bridge else { return false }
|
|
293
|
+
if keepUrlPathAfterReload {
|
|
294
|
+
syncKeepUrlPathFlag(enabled: true)
|
|
295
|
+
}
|
|
237
296
|
|
|
238
297
|
let id = self.implementation.getCurrentBundleId()
|
|
239
298
|
var dest: URL
|
|
@@ -460,34 +519,50 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
460
519
|
dest = self.implementation.getBundleDirectory(id: id)
|
|
461
520
|
}
|
|
462
521
|
logger.info("Reloading \(id)")
|
|
463
|
-
|
|
522
|
+
|
|
523
|
+
let performReload: () -> Bool = {
|
|
524
|
+
guard let vc = bridge.viewController as? CAPBridgeViewController else {
|
|
525
|
+
self.logger.error("Cannot get viewController")
|
|
526
|
+
return false
|
|
527
|
+
}
|
|
464
528
|
guard let capBridge = vc.bridge else {
|
|
465
|
-
logger.error("Cannot get capBridge")
|
|
529
|
+
self.logger.error("Cannot get capBridge")
|
|
466
530
|
return false
|
|
467
531
|
}
|
|
468
|
-
if keepUrlPathAfterReload {
|
|
469
|
-
|
|
470
|
-
guard let url = vc.webView?.url else {
|
|
471
|
-
self.logger.error("vc.webView?.url is null?")
|
|
472
|
-
return
|
|
473
|
-
}
|
|
532
|
+
if self.keepUrlPathAfterReload {
|
|
533
|
+
if let currentURL = vc.webView?.url {
|
|
474
534
|
capBridge.setServerBasePath(dest.path)
|
|
475
535
|
var urlComponents = URLComponents(url: capBridge.config.serverURL, resolvingAgainstBaseURL: false)!
|
|
476
|
-
urlComponents.path =
|
|
536
|
+
urlComponents.path = currentURL.path
|
|
537
|
+
urlComponents.query = currentURL.query
|
|
538
|
+
urlComponents.fragment = currentURL.fragment
|
|
477
539
|
if let finalUrl = urlComponents.url {
|
|
478
540
|
_ = vc.webView?.load(URLRequest(url: finalUrl))
|
|
541
|
+
} else {
|
|
542
|
+
self.logger.error("Unable to build final URL when keeping path after reload; falling back to base path")
|
|
543
|
+
vc.setServerBasePath(path: dest.path)
|
|
479
544
|
}
|
|
545
|
+
} else {
|
|
546
|
+
self.logger.error("vc.webView?.url is null? Falling back to base path reload.")
|
|
547
|
+
vc.setServerBasePath(path: dest.path)
|
|
480
548
|
}
|
|
481
549
|
} else {
|
|
482
550
|
vc.setServerBasePath(path: dest.path)
|
|
483
|
-
|
|
484
551
|
}
|
|
485
|
-
|
|
486
552
|
self.checkAppReady()
|
|
487
553
|
self.notifyListeners("appReloaded", data: [:])
|
|
488
554
|
return true
|
|
489
555
|
}
|
|
490
|
-
|
|
556
|
+
|
|
557
|
+
if Thread.isMainThread {
|
|
558
|
+
return performReload()
|
|
559
|
+
} else {
|
|
560
|
+
var result = false
|
|
561
|
+
DispatchQueue.main.sync {
|
|
562
|
+
result = performReload()
|
|
563
|
+
}
|
|
564
|
+
return result
|
|
565
|
+
}
|
|
491
566
|
}
|
|
492
567
|
|
|
493
568
|
@objc func reload(_ call: CAPPluginCall) {
|
|
@@ -545,6 +620,36 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
545
620
|
}
|
|
546
621
|
}
|
|
547
622
|
|
|
623
|
+
@objc func setBundleError(_ call: CAPPluginCall) {
|
|
624
|
+
if !allowManualBundleError {
|
|
625
|
+
logger.error("setBundleError called without allowManualBundleError")
|
|
626
|
+
call.reject("setBundleError not allowed. Set allowManualBundleError to true in your config to enable it.")
|
|
627
|
+
return
|
|
628
|
+
}
|
|
629
|
+
guard let id = call.getString("id") else {
|
|
630
|
+
logger.error("setBundleError called without id")
|
|
631
|
+
call.reject("setBundleError called without id")
|
|
632
|
+
return
|
|
633
|
+
}
|
|
634
|
+
let bundle = implementation.getBundleInfo(id: id)
|
|
635
|
+
if bundle.isUnknown() {
|
|
636
|
+
logger.error("setBundleError called with unknown bundle \(id)")
|
|
637
|
+
call.reject("Bundle \(id) does not exist")
|
|
638
|
+
return
|
|
639
|
+
}
|
|
640
|
+
if bundle.isBuiltin() {
|
|
641
|
+
logger.error("setBundleError called on builtin bundle")
|
|
642
|
+
call.reject("Cannot set builtin bundle to error state")
|
|
643
|
+
return
|
|
644
|
+
}
|
|
645
|
+
if self._isAutoUpdateEnabled() {
|
|
646
|
+
logger.warn("setBundleError used while autoUpdate is enabled; this method is intended for manual mode")
|
|
647
|
+
}
|
|
648
|
+
implementation.setError(bundle: bundle)
|
|
649
|
+
let updated = implementation.getBundleInfo(id: id)
|
|
650
|
+
call.resolve(["bundle": updated.toJSON()])
|
|
651
|
+
}
|
|
652
|
+
|
|
548
653
|
@objc func list(_ call: CAPPluginCall) {
|
|
549
654
|
let raw = call.getBool("raw", false)
|
|
550
655
|
let res = implementation.list(raw: raw)
|
|
@@ -804,6 +909,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
804
909
|
self.notifyListeners("updateFailed", data: [
|
|
805
910
|
"bundle": current.toJSON()
|
|
806
911
|
])
|
|
912
|
+
self.persistLastFailedBundle(current)
|
|
807
913
|
self.implementation.sendStats(action: "update_fail", versionName: current.getVersionName())
|
|
808
914
|
self.implementation.setError(bundle: current)
|
|
809
915
|
_ = self._reset(toLastSuccessful: true)
|
|
@@ -1085,6 +1191,15 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
1085
1191
|
}
|
|
1086
1192
|
}
|
|
1087
1193
|
|
|
1194
|
+
private func notifyBreakingEvents(version: String) {
|
|
1195
|
+
guard !version.isEmpty else {
|
|
1196
|
+
return
|
|
1197
|
+
}
|
|
1198
|
+
let payload: [String: Any] = ["version": version]
|
|
1199
|
+
self.notifyListeners("breakingAvailable", data: payload)
|
|
1200
|
+
self.notifyListeners("majorAvailable", data: payload)
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1088
1203
|
func endBackGroundTaskWithNotif(
|
|
1089
1204
|
msg: String,
|
|
1090
1205
|
latestVersionName: String,
|
|
@@ -1144,8 +1259,8 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
1144
1259
|
|
|
1145
1260
|
if let message = res.message, !message.isEmpty {
|
|
1146
1261
|
self.logger.info("API message: \(message)")
|
|
1147
|
-
if res.major == true {
|
|
1148
|
-
self.
|
|
1262
|
+
if res.breaking == true || res.major == true {
|
|
1263
|
+
self.notifyBreakingEvents(version: res.version)
|
|
1149
1264
|
}
|
|
1150
1265
|
self.endBackGroundTaskWithNotif(
|
|
1151
1266
|
msg: message,
|
|
@@ -1410,6 +1525,19 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
1410
1525
|
call.resolve(bundle!.toJSON())
|
|
1411
1526
|
}
|
|
1412
1527
|
|
|
1528
|
+
@objc func getFailedUpdate(_ call: CAPPluginCall) {
|
|
1529
|
+
let bundle = self.readLastFailedBundle()
|
|
1530
|
+
if bundle == nil || bundle?.isUnknown() == true {
|
|
1531
|
+
call.resolve()
|
|
1532
|
+
return
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
self.persistLastFailedBundle(nil)
|
|
1536
|
+
call.resolve([
|
|
1537
|
+
"bundle": bundle!.toJSON()
|
|
1538
|
+
])
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1413
1541
|
@objc func setShakeMenu(_ call: CAPPluginCall) {
|
|
1414
1542
|
guard let enabled = call.getBool("enabled") else {
|
|
1415
1543
|
logger.error("setShakeMenu called without enabled parameter")
|
|
@@ -157,6 +157,7 @@ struct AppVersionDec: Decodable {
|
|
|
157
157
|
let error: String?
|
|
158
158
|
let session_key: String?
|
|
159
159
|
let major: Bool?
|
|
160
|
+
let breaking: Bool?
|
|
160
161
|
let data: [String: String]?
|
|
161
162
|
let manifest: [ManifestEntry]?
|
|
162
163
|
}
|
|
@@ -169,6 +170,7 @@ public class AppVersion: NSObject {
|
|
|
169
170
|
var error: String?
|
|
170
171
|
var sessionKey: String?
|
|
171
172
|
var major: Bool?
|
|
173
|
+
var breaking: Bool?
|
|
172
174
|
var data: [String: String]?
|
|
173
175
|
var manifest: [ManifestEntry]?
|
|
174
176
|
}
|