@capgo/capacitor-updater 5.50.1 → 5.51.15
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 +1 -1
- package/Package.swift +3 -2
- package/README.md +53 -48
- package/android/build.gradle +1 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/AppLifecycleObserver.java +29 -2
- package/android/src/main/java/ee/forgr/capacitor_updater/BundleInfo.java +7 -3
- package/android/src/main/java/ee/forgr/capacitor_updater/BundleStatus.java +1 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdaterPlugin.java +476 -151
- package/android/src/main/java/ee/forgr/capacitor_updater/CapgoUpdater.java +1354 -412
- package/android/src/main/java/ee/forgr/capacitor_updater/CryptoCipher.java +102 -31
- package/android/src/main/java/ee/forgr/capacitor_updater/DataManager.java +23 -7
- package/android/src/main/java/ee/forgr/capacitor_updater/DelayCondition.java +2 -2
- package/android/src/main/java/ee/forgr/capacitor_updater/DownloadService.java +540 -224
- package/android/src/main/java/ee/forgr/capacitor_updater/DownloadWorkerManager.java +103 -3
- package/android/src/main/java/ee/forgr/capacitor_updater/InternalUtils.java +1 -1
- package/android/src/main/java/ee/forgr/capacitor_updater/ShakeMenu.java +131 -145
- package/dist/docs.json +32 -8
- package/dist/esm/definitions.d.ts +41 -17
- package/dist/esm/definitions.js.map +1 -1
- package/ios/Sources/CapacitorUpdaterPlugin/AES.swift +124 -0
- package/ios/Sources/CapacitorUpdaterPlugin/BundleInfo.swift +9 -1
- package/ios/Sources/CapacitorUpdaterPlugin/BundleStatus.swift +3 -0
- package/ios/Sources/CapacitorUpdaterPlugin/CapacitorUpdaterPlugin.swift +787 -92
- package/ios/Sources/CapacitorUpdaterPlugin/CapgoUpdater.swift +1014 -268
- package/ios/Sources/CapacitorUpdaterPlugin/CryptoCipher.swift +49 -31
- package/ios/Sources/CapacitorUpdaterPlugin/ShakeMenu.swift +44 -20
- package/ios/Sources/CapacitorUpdaterPlugin/WebViewStatsReporter.swift +28 -0
- package/package.json +12 -7
|
@@ -141,8 +141,24 @@ public struct CryptoCipher {
|
|
|
141
141
|
}
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
+
/// 256 KiB: one size for checksum, copy, and decode.
|
|
145
|
+
/// 64 workers * 256 KiB = 16 MiB for one buffer; AES/Brotli hold two (~32 MiB).
|
|
146
|
+
static let ioBufferBytesValue = 256 * 1024
|
|
147
|
+
|
|
148
|
+
static func ioBufferBytes() -> Int {
|
|
149
|
+
return ioBufferBytesValue
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
static func checksumBufferBytes() -> Int {
|
|
153
|
+
return ioBufferBytesValue
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
static func copyBufferBytes() -> Int {
|
|
157
|
+
return ioBufferBytesValue
|
|
158
|
+
}
|
|
159
|
+
|
|
144
160
|
public static func calcChecksum(filePath: URL) -> String {
|
|
145
|
-
let bufferSize =
|
|
161
|
+
let bufferSize = checksumBufferBytes()
|
|
146
162
|
var sha256 = SHA256()
|
|
147
163
|
|
|
148
164
|
do {
|
|
@@ -175,8 +191,7 @@ public struct CryptoCipher {
|
|
|
175
191
|
}
|
|
176
192
|
}) {}
|
|
177
193
|
|
|
178
|
-
|
|
179
|
-
return digest.compactMap { String(format: "%02x", $0) }.joined()
|
|
194
|
+
return hexString(from: sha256)
|
|
180
195
|
} catch {
|
|
181
196
|
logger.error("Cannot calculate checksum")
|
|
182
197
|
logger.debug("Path: \(filePath.path), Error: \(error)")
|
|
@@ -184,6 +199,32 @@ public struct CryptoCipher {
|
|
|
184
199
|
}
|
|
185
200
|
}
|
|
186
201
|
|
|
202
|
+
final class RunningChecksum {
|
|
203
|
+
private var sha256 = SHA256()
|
|
204
|
+
|
|
205
|
+
func update(_ data: Data) {
|
|
206
|
+
guard !data.isEmpty else {
|
|
207
|
+
return
|
|
208
|
+
}
|
|
209
|
+
sha256.update(data: data)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
func hex() -> String {
|
|
213
|
+
CryptoCipher.hexString(from: sha256)
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
static func hexString(from sha256: SHA256) -> String {
|
|
218
|
+
var copy = sha256
|
|
219
|
+
return copy.finalize().compactMap { String(format: "%02x", $0) }.joined()
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
static func shortPathKey(_ fileName: String) -> String {
|
|
223
|
+
var sha256 = SHA256()
|
|
224
|
+
sha256.update(data: Data(fileName.utf8))
|
|
225
|
+
return String(hexString(from: sha256).prefix(16))
|
|
226
|
+
}
|
|
227
|
+
|
|
187
228
|
public static func decryptFile(filePath: URL, publicKey: String, sessionKey: String, version: String) throws {
|
|
188
229
|
if publicKey.isEmpty || sessionKey.isEmpty || sessionKey.components(separatedBy: ":").count != 2 {
|
|
189
230
|
logger.info("Encryption not set, no public key or session, ignored")
|
|
@@ -236,42 +277,19 @@ public struct CryptoCipher {
|
|
|
236
277
|
|
|
237
278
|
let aesPrivateKey = AES128Key(iv: ivData, aes128Key: sessionKeyDataDecrypted, logger: logger)
|
|
238
279
|
|
|
239
|
-
let
|
|
240
|
-
|
|
241
|
-
encryptedData = try Data(contentsOf: filePath)
|
|
242
|
-
} catch {
|
|
243
|
-
logger.error("Failed to read encrypted data")
|
|
244
|
-
logger.debug("Error: \(error)")
|
|
245
|
-
throw NSError(domain: "Failed to read encrypted data", code: 3, userInfo: nil)
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
if encryptedData.isEmpty {
|
|
280
|
+
let encryptedSize = (try FileManager.default.attributesOfItem(atPath: filePath.path)[.size] as? NSNumber)?.uint64Value ?? 0
|
|
281
|
+
if encryptedSize == 0 {
|
|
249
282
|
logger.error("Encrypted file data is empty")
|
|
250
283
|
throw NSError(domain: "Empty encrypted data", code: 6, userInfo: nil)
|
|
251
284
|
}
|
|
252
285
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
if decryptedData.isEmpty {
|
|
286
|
+
try aesPrivateKey.decrypt(from: filePath, to: filePath)
|
|
287
|
+
let decryptedSize = (try FileManager.default.attributesOfItem(atPath: filePath.path)[.size] as? NSNumber)?.uint64Value ?? 0
|
|
288
|
+
if decryptedSize == 0 {
|
|
259
289
|
logger.error("Decrypted data is empty")
|
|
260
290
|
throw NSError(domain: "Empty decrypted data", code: 7, userInfo: nil)
|
|
261
291
|
}
|
|
262
292
|
|
|
263
|
-
do {
|
|
264
|
-
try decryptedData.write(to: filePath, options: .atomic)
|
|
265
|
-
if !FileManager.default.fileExists(atPath: filePath.path) {
|
|
266
|
-
logger.error("File was not created after write")
|
|
267
|
-
throw NSError(domain: "File write failed", code: 8, userInfo: nil)
|
|
268
|
-
}
|
|
269
|
-
} catch {
|
|
270
|
-
logger.error("Error writing decrypted file")
|
|
271
|
-
logger.debug("Error: \(error)")
|
|
272
|
-
throw error
|
|
273
|
-
}
|
|
274
|
-
|
|
275
293
|
} catch {
|
|
276
294
|
logger.error("File decryption failed")
|
|
277
295
|
throw CustomError.cannotDecode
|
|
@@ -92,18 +92,26 @@ final class ThreeFingerPinchGestureRecognizer: UIGestureRecognizer {
|
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
extension UIApplication {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
if let nav =
|
|
95
|
+
public class func topViewController(_ base: UIViewController? = nil) -> UIViewController? {
|
|
96
|
+
let resolvedBase = base ?? keyWindowRootViewController
|
|
97
|
+
if let nav = resolvedBase as? UINavigationController {
|
|
98
98
|
return topViewController(nav.visibleViewController)
|
|
99
99
|
}
|
|
100
|
-
if let tab =
|
|
100
|
+
if let tab = resolvedBase as? UITabBarController, let selected = tab.selectedViewController {
|
|
101
101
|
return topViewController(selected)
|
|
102
102
|
}
|
|
103
|
-
if let presented =
|
|
103
|
+
if let presented = resolvedBase?.presentedViewController {
|
|
104
104
|
return topViewController(presented)
|
|
105
105
|
}
|
|
106
|
-
return
|
|
106
|
+
return resolvedBase
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private static var keyWindowRootViewController: UIViewController? {
|
|
110
|
+
let scenes = shared.connectedScenes.compactMap { $0 as? UIWindowScene }
|
|
111
|
+
let foregroundScenes = scenes.filter { $0.activationState == .foregroundActive }
|
|
112
|
+
let candidates = foregroundScenes.isEmpty ? scenes : foregroundScenes
|
|
113
|
+
let window = candidates.flatMap(\.windows).first(where: \.isKeyWindow) ?? candidates.first?.windows.first
|
|
114
|
+
return window?.rootViewController
|
|
107
115
|
}
|
|
108
116
|
}
|
|
109
117
|
|
|
@@ -184,6 +192,10 @@ extension CapacitorUpdaterPlugin: UIGestureRecognizerDelegate {
|
|
|
184
192
|
}
|
|
185
193
|
|
|
186
194
|
extension UIWindow {
|
|
195
|
+
private var menuHostViewController: UIViewController? {
|
|
196
|
+
UIApplication.topViewController(rootViewController)
|
|
197
|
+
}
|
|
198
|
+
|
|
187
199
|
override open func motionEnded(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {
|
|
188
200
|
if motion == .motionShake {
|
|
189
201
|
guard let bridgeViewController = rootViewController as? CAPBridgeViewController,
|
|
@@ -230,7 +242,7 @@ extension UIWindow {
|
|
|
230
242
|
@discardableResult
|
|
231
243
|
private func showDefaultMenu(plugin: CapacitorUpdaterPlugin, bridge: CAPBridgeProtocol) -> Bool {
|
|
232
244
|
// Prevent multiple alerts from showing
|
|
233
|
-
guard let topVC =
|
|
245
|
+
guard let topVC = self.menuHostViewController else {
|
|
234
246
|
return false
|
|
235
247
|
}
|
|
236
248
|
if topVC.isKind(of: UIAlertController.self) {
|
|
@@ -365,7 +377,7 @@ extension UIWindow {
|
|
|
365
377
|
})
|
|
366
378
|
|
|
367
379
|
DispatchQueue.main.async {
|
|
368
|
-
if let topVC =
|
|
380
|
+
if let topVC = self.menuHostViewController {
|
|
369
381
|
topVC.present(alertShake, animated: true)
|
|
370
382
|
}
|
|
371
383
|
}
|
|
@@ -388,7 +400,7 @@ extension UIWindow {
|
|
|
388
400
|
}
|
|
389
401
|
|
|
390
402
|
private func showPreviewSelector(plugin: CapacitorUpdaterPlugin) {
|
|
391
|
-
guard let topVC =
|
|
403
|
+
guard let topVC = self.menuHostViewController else {
|
|
392
404
|
return
|
|
393
405
|
}
|
|
394
406
|
if topVC.isKind(of: UIAlertController.self) {
|
|
@@ -456,7 +468,7 @@ extension UIWindow {
|
|
|
456
468
|
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
|
|
457
469
|
|
|
458
470
|
DispatchQueue.main.async {
|
|
459
|
-
if let topVC =
|
|
471
|
+
if let topVC = self.menuHostViewController {
|
|
460
472
|
topVC.present(alert, animated: true)
|
|
461
473
|
}
|
|
462
474
|
}
|
|
@@ -479,7 +491,7 @@ extension UIWindow {
|
|
|
479
491
|
}
|
|
480
492
|
|
|
481
493
|
DispatchQueue.main.async {
|
|
482
|
-
if let topVC =
|
|
494
|
+
if let topVC = self.menuHostViewController {
|
|
483
495
|
topVC.present(alert, animated: true)
|
|
484
496
|
}
|
|
485
497
|
}
|
|
@@ -498,7 +510,7 @@ extension UIWindow {
|
|
|
498
510
|
@discardableResult
|
|
499
511
|
private func showChannelSelector(plugin: CapacitorUpdaterPlugin, bridge: CAPBridgeProtocol) -> Bool {
|
|
500
512
|
// Prevent multiple alerts from showing
|
|
501
|
-
guard let topVC =
|
|
513
|
+
guard let topVC = self.menuHostViewController else {
|
|
502
514
|
return false
|
|
503
515
|
}
|
|
504
516
|
if topVC.isKind(of: UIAlertController.self) {
|
|
@@ -582,7 +594,7 @@ extension UIWindow {
|
|
|
582
594
|
}
|
|
583
595
|
|
|
584
596
|
DispatchQueue.main.async {
|
|
585
|
-
if let topVC =
|
|
597
|
+
if let topVC = self.menuHostViewController {
|
|
586
598
|
topVC.present(alert, animated: true)
|
|
587
599
|
}
|
|
588
600
|
}
|
|
@@ -627,7 +639,7 @@ extension UIWindow {
|
|
|
627
639
|
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
|
|
628
640
|
|
|
629
641
|
DispatchQueue.main.async {
|
|
630
|
-
if let topVC =
|
|
642
|
+
if let topVC = self.menuHostViewController {
|
|
631
643
|
topVC.present(alert, animated: true)
|
|
632
644
|
}
|
|
633
645
|
}
|
|
@@ -649,7 +661,7 @@ extension UIWindow {
|
|
|
649
661
|
])
|
|
650
662
|
|
|
651
663
|
DispatchQueue.main.async {
|
|
652
|
-
if let topVC =
|
|
664
|
+
if let topVC = self.menuHostViewController {
|
|
653
665
|
topVC.present(progressAlert, animated: true) {
|
|
654
666
|
DispatchQueue.global(qos: .userInitiated).async {
|
|
655
667
|
// Set the channel - respect plugin's allowSetDefaultChannel config
|
|
@@ -668,6 +680,14 @@ extension UIWindow {
|
|
|
668
680
|
}
|
|
669
681
|
return
|
|
670
682
|
}
|
|
683
|
+
guard plugin.persistDefaultChannelStateFromDefaults() else {
|
|
684
|
+
DispatchQueue.main.async {
|
|
685
|
+
progressAlert.dismiss(animated: true) {
|
|
686
|
+
self.showError(message: "Channel set to \(name), but local persistence failed.", plugin: plugin)
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
return
|
|
690
|
+
}
|
|
671
691
|
|
|
672
692
|
// Update progress message
|
|
673
693
|
DispatchQueue.main.async {
|
|
@@ -718,8 +738,12 @@ extension UIWindow {
|
|
|
718
738
|
return
|
|
719
739
|
}
|
|
720
740
|
|
|
721
|
-
// Check if there's an actual update available
|
|
722
|
-
|
|
741
|
+
// Check if there's an actual update available. A manifest-only
|
|
742
|
+
// response legitimately has no URL (the files come from the
|
|
743
|
+
// manifest, not a zip), so only report "already on latest" when
|
|
744
|
+
// the URL is empty AND there is no manifest to download from.
|
|
745
|
+
let hasManifest = !(latest.manifest?.isEmpty ?? true)
|
|
746
|
+
if latestKind == "up_to_date" || (latest.url.isEmpty && !hasManifest) {
|
|
723
747
|
DispatchQueue.main.async {
|
|
724
748
|
progressAlert.dismiss(animated: true) {
|
|
725
749
|
self.showSuccess(message: "Channel set to \(name). Already on latest version.", plugin: plugin)
|
|
@@ -794,7 +818,7 @@ extension UIWindow {
|
|
|
794
818
|
alert.addAction(UIAlertAction(title: "OK", style: .default))
|
|
795
819
|
|
|
796
820
|
DispatchQueue.main.async {
|
|
797
|
-
if let topVC =
|
|
821
|
+
if let topVC = self.menuHostViewController {
|
|
798
822
|
topVC.present(alert, animated: true)
|
|
799
823
|
}
|
|
800
824
|
}
|
|
@@ -806,7 +830,7 @@ extension UIWindow {
|
|
|
806
830
|
alert.addAction(UIAlertAction(title: "OK", style: .default))
|
|
807
831
|
|
|
808
832
|
DispatchQueue.main.async {
|
|
809
|
-
if let topVC =
|
|
833
|
+
if let topVC = self.menuHostViewController {
|
|
810
834
|
topVC.present(alert, animated: true)
|
|
811
835
|
}
|
|
812
836
|
}
|
|
@@ -832,7 +856,7 @@ extension UIWindow {
|
|
|
832
856
|
})
|
|
833
857
|
|
|
834
858
|
DispatchQueue.main.async {
|
|
835
|
-
if let topVC =
|
|
859
|
+
if let topVC = self.menuHostViewController {
|
|
836
860
|
topVC.present(alert, animated: true)
|
|
837
861
|
}
|
|
838
862
|
}
|
|
@@ -86,6 +86,10 @@ final class WebViewStatsReporter {
|
|
|
86
86
|
}
|
|
87
87
|
writeSession(true);
|
|
88
88
|
setInterval(function(){writeSession(true);},15000);
|
|
89
|
+
function pageDuration(){
|
|
90
|
+
var started=Number(window.__capgoWebViewSessionStartedAt||Date.now());
|
|
91
|
+
return String(Math.max(0,Date.now()-started));
|
|
92
|
+
}
|
|
89
93
|
function markClean(){writeSession(false);}
|
|
90
94
|
window.addEventListener('pagehide',markClean,true);
|
|
91
95
|
window.addEventListener('beforeunload',markClean,true);
|
|
@@ -120,6 +124,22 @@ final class WebViewStatsReporter {
|
|
|
120
124
|
source:s(event&&event.blockedURI)
|
|
121
125
|
});
|
|
122
126
|
},true);
|
|
127
|
+
document.addEventListener('DOMContentLoaded',function(){
|
|
128
|
+
send({
|
|
129
|
+
type:'webview_dom_content_loaded',
|
|
130
|
+
message:'WebView DOM content loaded',
|
|
131
|
+
duration_ms:pageDuration(),
|
|
132
|
+
page_started_at:String(window.__capgoWebViewSessionStartedAt)
|
|
133
|
+
});
|
|
134
|
+
},true);
|
|
135
|
+
window.addEventListener('load',function(){
|
|
136
|
+
send({
|
|
137
|
+
type:'webview_page_loaded',
|
|
138
|
+
message:'WebView page loaded',
|
|
139
|
+
duration_ms:pageDuration(),
|
|
140
|
+
page_started_at:String(window.__capgoWebViewSessionStartedAt)
|
|
141
|
+
});
|
|
142
|
+
},true);
|
|
123
143
|
document.addEventListener('deviceready',scheduleFlush,false);
|
|
124
144
|
setTimeout(scheduleFlush,0);
|
|
125
145
|
})();
|
|
@@ -164,6 +184,8 @@ final class WebViewStatsReporter {
|
|
|
164
184
|
"href": call.getString("href"),
|
|
165
185
|
"user_agent": call.getString("user_agent"),
|
|
166
186
|
"session_id": call.getString("session_id"),
|
|
187
|
+
"duration_ms": call.getString("duration_ms"),
|
|
188
|
+
"page_started_at": call.getString("page_started_at"),
|
|
167
189
|
"previous_session_id": call.getString("previous_session_id"),
|
|
168
190
|
"previous_href": call.getString("previous_href"),
|
|
169
191
|
"previous_started_at": call.getString("previous_started_at"),
|
|
@@ -187,6 +209,10 @@ final class WebViewStatsReporter {
|
|
|
187
209
|
return "webview_render_process_gone"
|
|
188
210
|
case "web_content_process_terminated":
|
|
189
211
|
return "webview_content_process_terminated"
|
|
212
|
+
case "webview_dom_content_loaded":
|
|
213
|
+
return "webview_dom_content_loaded"
|
|
214
|
+
case "webview_page_loaded":
|
|
215
|
+
return "webview_page_loaded"
|
|
190
216
|
case "javascript_error":
|
|
191
217
|
return "webview_javascript_error"
|
|
192
218
|
default:
|
|
@@ -206,6 +232,8 @@ final class WebViewStatsReporter {
|
|
|
206
232
|
put(&metadata, key: "href", value: sanitizeUrl(payloadValue(values, "href")), maxLength: 512)
|
|
207
233
|
put(&metadata, key: "user_agent", value: payloadValue(values, "user_agent"), maxLength: 256)
|
|
208
234
|
put(&metadata, key: "session_id", value: payloadValue(values, "session_id"), maxLength: 128)
|
|
235
|
+
put(&metadata, key: "duration_ms", value: payloadValue(values, "duration_ms"), maxLength: 32)
|
|
236
|
+
put(&metadata, key: "page_started_at", value: payloadValue(values, "page_started_at"), maxLength: 64)
|
|
209
237
|
put(&metadata, key: "previous_session_id", value: payloadValue(values, "previous_session_id"), maxLength: 128)
|
|
210
238
|
put(&metadata, key: "previous_href", value: sanitizeUrl(payloadValue(values, "previous_href")), maxLength: 512)
|
|
211
239
|
put(&metadata, key: "previous_started_at", value: payloadValue(values, "previous_started_at"), maxLength: 64)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@capgo/capacitor-updater",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.51.15",
|
|
4
4
|
"license": "MPL-2.0",
|
|
5
5
|
"description": "Live update for capacitor apps",
|
|
6
6
|
"main": "dist/plugin.cjs.js",
|
|
@@ -72,15 +72,20 @@
|
|
|
72
72
|
"watch": "tsc --watch",
|
|
73
73
|
"prepublishOnly": "bun run build",
|
|
74
74
|
"check:wiring": "node scripts/check-capacitor-plugin-wiring.mjs",
|
|
75
|
+
"changelog:ai": "node scripts/generate-ai-changelog.mjs",
|
|
75
76
|
"example:install": "cd example-app && bun install --frozen-lockfile",
|
|
76
|
-
"example:build": "bun run build && cd example-app && bun install --frozen-lockfile && bun run build"
|
|
77
|
+
"example:build": "bun run build && cd example-app && bun install --frozen-lockfile && bun run build",
|
|
78
|
+
"native:contract:crypto": "bun run native:contract:crypto:ios && bun run native:contract:crypto:android",
|
|
79
|
+
"native:contract:crypto:ios": "./scripts/test-ios.sh -only-testing:CapacitorUpdaterPluginTests/RsaContractTests",
|
|
80
|
+
"native:contract:crypto:android": "cd android && ./gradlew testDebugUnitTest --tests ee.forgr.capacitor_updater.RsaContractTest && cd ..",
|
|
81
|
+
"generate:rsa-contract": "bun scripts/generate-rsa-contract-fixtures.mjs"
|
|
77
82
|
},
|
|
78
83
|
"devDependencies": {
|
|
79
|
-
"@capacitor/android": "^5.
|
|
80
|
-
"@capacitor/cli": "^5.
|
|
81
|
-
"@capacitor/core": "^5.
|
|
82
|
-
"@capacitor/docgen": "^0.
|
|
83
|
-
"@capacitor/ios": "^5.
|
|
84
|
+
"@capacitor/android": "^5.0.0",
|
|
85
|
+
"@capacitor/cli": "^5.0.0",
|
|
86
|
+
"@capacitor/core": "^5.0.0",
|
|
87
|
+
"@capacitor/docgen": "^0.3.0",
|
|
88
|
+
"@capacitor/ios": "^5.0.0",
|
|
84
89
|
"@ionic/eslint-config": "^0.4.0",
|
|
85
90
|
"@ionic/prettier-config": "^4.0.0",
|
|
86
91
|
"@ionic/swiftlint-config": "^2.0.0",
|