@capgo/capacitor-updater 7.43.3 → 7.45.10
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/Package.swift +5 -2
- package/README.md +149 -39
- package/android/build.gradle +3 -3
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdaterPlugin.java +537 -172
- package/android/src/main/java/ee/forgr/capacitor_updater/CapgoUpdater.java +170 -35
- package/android/src/main/java/ee/forgr/capacitor_updater/DelayUpdateUtils.java +49 -13
- package/android/src/main/java/ee/forgr/capacitor_updater/DownloadService.java +38 -13
- package/android/src/main/java/ee/forgr/capacitor_updater/ShakeMenu.java +49 -9
- package/dist/docs.json +290 -10
- package/dist/esm/definitions.d.ts +134 -22
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.d.ts +1 -2
- package/dist/esm/web.js +0 -4
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +0 -4
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +0 -4
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/CapacitorUpdaterPlugin/CapacitorUpdaterPlugin.swift +557 -135
- package/ios/Sources/CapacitorUpdaterPlugin/CapgoUpdater.swift +213 -50
- package/ios/Sources/CapacitorUpdaterPlugin/DelayUpdateUtils.swift +37 -16
- package/ios/Sources/CapacitorUpdaterPlugin/InternalUtils.swift +2 -0
- package/ios/Sources/CapacitorUpdaterPlugin/ShakeMenu.swift +20 -3
- package/package.json +11 -8
|
@@ -54,10 +54,31 @@ import UIKit
|
|
|
54
54
|
private var statsFlushTimer: Timer?
|
|
55
55
|
private static let statsFlushInterval: TimeInterval = 1.0
|
|
56
56
|
|
|
57
|
+
private static func sanitizeHeaderValue(_ value: String) -> String {
|
|
58
|
+
if value.isEmpty {
|
|
59
|
+
return "unknown"
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
let filteredScalars = value.unicodeScalars.filter { scalar in
|
|
63
|
+
let cp = scalar.value
|
|
64
|
+
let isVisibleAscii = (0x20...0x7E).contains(cp)
|
|
65
|
+
let isIso88591 = (0xA0...0xFF).contains(cp)
|
|
66
|
+
return isVisibleAscii || isIso88591
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
let sanitized = String(String.UnicodeScalarView(filteredScalars)).trimmingCharacters(in: .whitespacesAndNewlines)
|
|
70
|
+
return sanitized.isEmpty ? "unknown" : sanitized
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
static func buildUserAgent(appId: String, pluginVersion: String, versionOs: String) -> String {
|
|
74
|
+
let safePluginVersion = sanitizeHeaderValue(pluginVersion)
|
|
75
|
+
let safeAppId = sanitizeHeaderValue(appId)
|
|
76
|
+
let safeVersionOs = sanitizeHeaderValue(versionOs)
|
|
77
|
+
return "CapacitorUpdater/\(safePluginVersion) (\(safeAppId)) ios/\(safeVersionOs)"
|
|
78
|
+
}
|
|
79
|
+
|
|
57
80
|
private var userAgent: String {
|
|
58
|
-
|
|
59
|
-
let safeAppId = appId.isEmpty ? "unknown" : appId
|
|
60
|
-
return "CapacitorUpdater/\(safePluginVersion) (\(safeAppId)) ios/\(versionOs)"
|
|
81
|
+
CapgoUpdater.buildUserAgent(appId: appId, pluginVersion: pluginVersion, versionOs: versionOs)
|
|
61
82
|
}
|
|
62
83
|
|
|
63
84
|
private lazy var alamofireSession: Session = {
|
|
@@ -71,6 +92,7 @@ import UIKit
|
|
|
71
92
|
notifyDownloadRaw(id, percent, ignoreMultipleOfTen, bundle)
|
|
72
93
|
}
|
|
73
94
|
public var notifyDownload: (String, Int) -> Void = { _, _ in }
|
|
95
|
+
public var notifyListeners: (String, [String: Any]) -> Void = { _, _ in }
|
|
74
96
|
|
|
75
97
|
public func setLogger(_ logger: Logger) {
|
|
76
98
|
self.logger = logger
|
|
@@ -456,6 +478,47 @@ import UIKit
|
|
|
456
478
|
public func getLatest(url: URL, channel: String?) -> AppVersion {
|
|
457
479
|
let semaphore: DispatchSemaphore = DispatchSemaphore(value: 0)
|
|
458
480
|
let latest: AppVersion = AppVersion()
|
|
481
|
+
func applyLatestResponse(_ value: AppVersionDec?) {
|
|
482
|
+
if let url = value?.url {
|
|
483
|
+
latest.url = url
|
|
484
|
+
}
|
|
485
|
+
if let checksum = value?.checksum {
|
|
486
|
+
latest.checksum = checksum
|
|
487
|
+
}
|
|
488
|
+
if let version = value?.version {
|
|
489
|
+
latest.version = version
|
|
490
|
+
}
|
|
491
|
+
if let major = value?.major {
|
|
492
|
+
latest.major = major
|
|
493
|
+
}
|
|
494
|
+
if let breaking = value?.breaking {
|
|
495
|
+
latest.breaking = breaking
|
|
496
|
+
}
|
|
497
|
+
if let error = value?.error {
|
|
498
|
+
latest.error = error
|
|
499
|
+
}
|
|
500
|
+
if let kind = value?.kind {
|
|
501
|
+
latest.kind = kind
|
|
502
|
+
}
|
|
503
|
+
if let message = value?.message {
|
|
504
|
+
latest.message = message
|
|
505
|
+
}
|
|
506
|
+
if let sessionKey = value?.session_key {
|
|
507
|
+
latest.sessionKey = sessionKey
|
|
508
|
+
}
|
|
509
|
+
if let data = value?.data {
|
|
510
|
+
latest.data = data
|
|
511
|
+
}
|
|
512
|
+
if let manifest = value?.manifest {
|
|
513
|
+
latest.manifest = manifest
|
|
514
|
+
}
|
|
515
|
+
if let link = value?.link {
|
|
516
|
+
latest.link = link
|
|
517
|
+
}
|
|
518
|
+
if let comment = value?.comment {
|
|
519
|
+
latest.comment = comment
|
|
520
|
+
}
|
|
521
|
+
}
|
|
459
522
|
var parameters: InfoObject = self.createInfoObject()
|
|
460
523
|
if let channel = channel {
|
|
461
524
|
parameters.defaultChannel = channel
|
|
@@ -467,48 +530,32 @@ import UIKit
|
|
|
467
530
|
switch response.result {
|
|
468
531
|
case .success:
|
|
469
532
|
latest.statusCode = response.response?.statusCode ?? 0
|
|
470
|
-
|
|
471
|
-
latest.url = url
|
|
472
|
-
}
|
|
473
|
-
if let checksum = response.value?.checksum {
|
|
474
|
-
latest.checksum = checksum
|
|
475
|
-
}
|
|
476
|
-
if let version = response.value?.version {
|
|
477
|
-
latest.version = version
|
|
478
|
-
}
|
|
479
|
-
if let major = response.value?.major {
|
|
480
|
-
latest.major = major
|
|
481
|
-
}
|
|
482
|
-
if let breaking = response.value?.breaking {
|
|
483
|
-
latest.breaking = breaking
|
|
484
|
-
}
|
|
485
|
-
if let error = response.value?.error {
|
|
486
|
-
latest.error = error
|
|
487
|
-
}
|
|
488
|
-
if let message = response.value?.message {
|
|
489
|
-
latest.message = message
|
|
490
|
-
}
|
|
491
|
-
if let sessionKey = response.value?.session_key {
|
|
492
|
-
latest.sessionKey = sessionKey
|
|
493
|
-
}
|
|
494
|
-
if let data = response.value?.data {
|
|
495
|
-
latest.data = data
|
|
496
|
-
}
|
|
497
|
-
if let manifest = response.value?.manifest {
|
|
498
|
-
latest.manifest = manifest
|
|
499
|
-
}
|
|
500
|
-
if let link = response.value?.link {
|
|
501
|
-
latest.link = link
|
|
502
|
-
}
|
|
503
|
-
if let comment = response.value?.comment {
|
|
504
|
-
latest.comment = comment
|
|
505
|
-
}
|
|
533
|
+
applyLatestResponse(response.value)
|
|
506
534
|
case let .failure(error):
|
|
507
535
|
self.logger.error("Error getting latest version")
|
|
508
536
|
self.logger.debug("Response: \(response.value.debugDescription), Error: \(error)")
|
|
509
|
-
latest.message = "Error getting Latest"
|
|
510
|
-
latest.error = "response_error"
|
|
511
537
|
latest.statusCode = response.response?.statusCode ?? 0
|
|
538
|
+
if let data = response.data,
|
|
539
|
+
let decoded = try? JSONDecoder().decode(AppVersionDec.self, from: data) {
|
|
540
|
+
applyLatestResponse(decoded)
|
|
541
|
+
let decodedError = decoded.error ?? ""
|
|
542
|
+
let decodedKind = decoded.kind ?? ""
|
|
543
|
+
if decodedError.isEmpty && decodedKind.isEmpty {
|
|
544
|
+
if latest.message == nil || latest.message?.isEmpty == true {
|
|
545
|
+
latest.message = "Error getting Latest"
|
|
546
|
+
}
|
|
547
|
+
if latest.error == nil || latest.error?.isEmpty == true {
|
|
548
|
+
latest.error = "response_error"
|
|
549
|
+
}
|
|
550
|
+
if latest.kind == nil || latest.kind?.isEmpty == true {
|
|
551
|
+
latest.kind = "failed"
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
} else {
|
|
555
|
+
latest.message = "Error getting Latest"
|
|
556
|
+
latest.error = "response_error"
|
|
557
|
+
latest.kind = "failed"
|
|
558
|
+
}
|
|
512
559
|
}
|
|
513
560
|
semaphore.signal()
|
|
514
561
|
}
|
|
@@ -522,6 +569,22 @@ import UIKit
|
|
|
522
569
|
logger.info("Current bundle set to: \((bundle ).isEmpty ? BundleInfo.ID_BUILTIN : bundle)")
|
|
523
570
|
}
|
|
524
571
|
|
|
572
|
+
static func shouldResetForForeignBundle(bundlePath: String?, isBuiltin: Bool, hasStoredBundleInfo: Bool) -> Bool {
|
|
573
|
+
guard let bundlePath, !bundlePath.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
|
|
574
|
+
return false
|
|
575
|
+
}
|
|
576
|
+
return !isBuiltin && !hasStoredBundleInfo
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
private func hasStoredBundleInfo(id: String) -> Bool {
|
|
580
|
+
guard !id.isEmpty,
|
|
581
|
+
id != BundleInfo.ID_BUILTIN,
|
|
582
|
+
id != BundleInfo.VERSION_UNKNOWN else {
|
|
583
|
+
return false
|
|
584
|
+
}
|
|
585
|
+
return UserDefaults.standard.object(forKey: "\(id)\(self.INFO_SUFFIX)") != nil
|
|
586
|
+
}
|
|
587
|
+
|
|
525
588
|
// Per-download temp file paths to prevent collisions when multiple downloads run concurrently
|
|
526
589
|
private func tempDataPath(for id: String) -> URL {
|
|
527
590
|
return FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("package_\(id).tmp")
|
|
@@ -578,10 +641,36 @@ import UIKit
|
|
|
578
641
|
for entry in manifest {
|
|
579
642
|
guard let fileName = entry.file_name,
|
|
580
643
|
let downloadUrl = entry.download_url else {
|
|
644
|
+
let error = NSError(
|
|
645
|
+
domain: "ManifestEntryError",
|
|
646
|
+
code: 1,
|
|
647
|
+
userInfo: [
|
|
648
|
+
NSLocalizedDescriptionKey: "Manifest entry is missing file_name or download_url"
|
|
649
|
+
]
|
|
650
|
+
)
|
|
651
|
+
errorLock.lock()
|
|
652
|
+
if downloadError == nil {
|
|
653
|
+
downloadError = error
|
|
654
|
+
}
|
|
655
|
+
errorLock.unlock()
|
|
656
|
+
hasError.value = true
|
|
657
|
+
logger.error("Manifest entry is missing file_name or download_url")
|
|
581
658
|
continue
|
|
582
659
|
}
|
|
583
660
|
guard let entryFileHash = entry.file_hash, !entryFileHash.isEmpty else {
|
|
584
661
|
logger.error("Missing file_hash for manifest entry: \(entry.file_name ?? "unknown")")
|
|
662
|
+
let error = NSError(
|
|
663
|
+
domain: "ManifestEntryError",
|
|
664
|
+
code: 2,
|
|
665
|
+
userInfo: [
|
|
666
|
+
NSLocalizedDescriptionKey: "Manifest entry is missing file_hash for \(entry.file_name ?? "unknown")"
|
|
667
|
+
]
|
|
668
|
+
)
|
|
669
|
+
errorLock.lock()
|
|
670
|
+
if downloadError == nil {
|
|
671
|
+
downloadError = error
|
|
672
|
+
}
|
|
673
|
+
errorLock.unlock()
|
|
585
674
|
hasError.value = true
|
|
586
675
|
continue
|
|
587
676
|
}
|
|
@@ -670,11 +759,16 @@ import UIKit
|
|
|
670
759
|
// Execute all operations concurrently and wait for completion
|
|
671
760
|
manifestDownloadQueue.addOperations(operations, waitUntilFinished: true)
|
|
672
761
|
|
|
673
|
-
if hasError.value
|
|
762
|
+
if hasError.value {
|
|
763
|
+
let resolvedError = downloadError ?? NSError(
|
|
764
|
+
domain: "ManifestDownloadError",
|
|
765
|
+
code: 1,
|
|
766
|
+
userInfo: [NSLocalizedDescriptionKey: "Manifest download failed due to invalid or missing entries"]
|
|
767
|
+
)
|
|
674
768
|
// Update bundle status to ERROR if download failed
|
|
675
769
|
let errorBundle = bundleInfo.setStatus(status: BundleStatus.ERROR.localizedString)
|
|
676
770
|
self.saveBundleInfo(id: id, bundle: errorBundle)
|
|
677
|
-
throw
|
|
771
|
+
throw resolvedError
|
|
678
772
|
}
|
|
679
773
|
|
|
680
774
|
// Update bundle status to PENDING after successful download
|
|
@@ -1411,6 +1505,52 @@ import UIKit
|
|
|
1411
1505
|
return libraryDir.appendingPathComponent(self.bundleDirectory).appendingPathComponent(id)
|
|
1412
1506
|
}
|
|
1413
1507
|
|
|
1508
|
+
struct ResetState {
|
|
1509
|
+
let currentBundlePath: String
|
|
1510
|
+
let fallbackBundleId: String
|
|
1511
|
+
let nextBundleId: String?
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1514
|
+
func captureResetState() -> ResetState {
|
|
1515
|
+
ResetState(
|
|
1516
|
+
currentBundlePath: UserDefaults.standard.string(forKey: self.CAP_SERVER_PATH) ?? self.DEFAULT_FOLDER,
|
|
1517
|
+
fallbackBundleId: UserDefaults.standard.string(forKey: self.FALLBACK_VERSION) ?? BundleInfo.ID_BUILTIN,
|
|
1518
|
+
nextBundleId: UserDefaults.standard.string(forKey: self.NEXT_VERSION)
|
|
1519
|
+
)
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
func restoreResetState(_ state: ResetState) {
|
|
1523
|
+
let currentBundlePath = state.currentBundlePath.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
|
|
1524
|
+
? self.DEFAULT_FOLDER
|
|
1525
|
+
: state.currentBundlePath
|
|
1526
|
+
let fallbackBundleId = state.fallbackBundleId.isEmpty ? BundleInfo.ID_BUILTIN : state.fallbackBundleId
|
|
1527
|
+
|
|
1528
|
+
self.setCurrentBundle(bundle: currentBundlePath)
|
|
1529
|
+
UserDefaults.standard.set(fallbackBundleId, forKey: self.FALLBACK_VERSION)
|
|
1530
|
+
if let nextBundleId = state.nextBundleId, !nextBundleId.isEmpty {
|
|
1531
|
+
UserDefaults.standard.set(nextBundleId, forKey: self.NEXT_VERSION)
|
|
1532
|
+
} else {
|
|
1533
|
+
UserDefaults.standard.removeObject(forKey: self.NEXT_VERSION)
|
|
1534
|
+
}
|
|
1535
|
+
UserDefaults.standard.synchronize()
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
func prepareResetStateForTransition() {
|
|
1539
|
+
self.setCurrentBundle(bundle: "")
|
|
1540
|
+
self.setFallbackBundle(fallback: Optional<BundleInfo>.none)
|
|
1541
|
+
_ = self.setNextBundle(next: Optional<String>.none)
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
func finalizeResetTransition(previousBundleName: String, isInternal: Bool) {
|
|
1545
|
+
if !isInternal {
|
|
1546
|
+
self.sendStats(action: "reset", versionName: self.getCurrentBundle().getVersionName(), oldVersionName: previousBundleName)
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
func canSet(bundle: BundleInfo) -> Bool {
|
|
1551
|
+
bundle.isBuiltin() || self.bundleExists(id: bundle.getId())
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1414
1554
|
public func set(bundle: BundleInfo) -> Bool {
|
|
1415
1555
|
return self.set(id: bundle.getId())
|
|
1416
1556
|
}
|
|
@@ -1448,11 +1588,36 @@ import UIKit
|
|
|
1448
1588
|
return false
|
|
1449
1589
|
}
|
|
1450
1590
|
|
|
1591
|
+
func stagePendingReload(bundle: BundleInfo) -> Bool {
|
|
1592
|
+
guard !bundle.isBuiltin(), bundleExists(id: bundle.getId()) else {
|
|
1593
|
+
return false
|
|
1594
|
+
}
|
|
1595
|
+
self.setCurrentBundle(bundle: self.getBundleDirectory(id: bundle.getId()).path)
|
|
1596
|
+
return true
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
func finalizePendingReload(bundle: BundleInfo, previousBundleName: String) {
|
|
1600
|
+
guard !bundle.isBuiltin() else {
|
|
1601
|
+
return
|
|
1602
|
+
}
|
|
1603
|
+
self.sendStats(action: "set", versionName: bundle.getVersionName(), oldVersionName: previousBundleName)
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1451
1606
|
public func autoReset() {
|
|
1452
1607
|
let currentBundle: BundleInfo = self.getCurrentBundle()
|
|
1453
1608
|
if !currentBundle.isBuiltin() && !self.bundleExists(id: currentBundle.getId()) {
|
|
1454
1609
|
logger.info("Folder at bundle path does not exist. Triggering reset.")
|
|
1455
1610
|
self.reset()
|
|
1611
|
+
return
|
|
1612
|
+
}
|
|
1613
|
+
let bundlePath = UserDefaults.standard.string(forKey: self.CAP_SERVER_PATH)
|
|
1614
|
+
if Self.shouldResetForForeignBundle(
|
|
1615
|
+
bundlePath: bundlePath,
|
|
1616
|
+
isBuiltin: currentBundle.isBuiltin(),
|
|
1617
|
+
hasStoredBundleInfo: self.hasStoredBundleInfo(id: currentBundle.getId())
|
|
1618
|
+
) {
|
|
1619
|
+
logger.info("Current bundle id is not one of the bundle ids stored by this plugin. Triggering reset.")
|
|
1620
|
+
self.reset()
|
|
1456
1621
|
}
|
|
1457
1622
|
}
|
|
1458
1623
|
|
|
@@ -1463,12 +1628,8 @@ import UIKit
|
|
|
1463
1628
|
public func reset(isInternal: Bool) {
|
|
1464
1629
|
logger.info("reset: \(isInternal)")
|
|
1465
1630
|
let currentBundleName = self.getCurrentBundle().getVersionName()
|
|
1466
|
-
self.
|
|
1467
|
-
self.
|
|
1468
|
-
_ = self.setNextBundle(next: Optional<String>.none)
|
|
1469
|
-
if !isInternal {
|
|
1470
|
-
self.sendStats(action: "reset", versionName: self.getCurrentBundle().getVersionName(), oldVersionName: currentBundleName)
|
|
1471
|
-
}
|
|
1631
|
+
self.prepareResetStateForTransition()
|
|
1632
|
+
self.finalizeResetTransition(previousBundleName: currentBundleName, isInternal: isInternal)
|
|
1472
1633
|
}
|
|
1473
1634
|
|
|
1474
1635
|
public func setSuccess(bundle: BundleInfo, autoDeletePrevious: Bool) {
|
|
@@ -1851,7 +2012,7 @@ import UIKit
|
|
|
1851
2012
|
}
|
|
1852
2013
|
let result: BundleInfo
|
|
1853
2014
|
if BundleInfo.ID_BUILTIN == trueId {
|
|
1854
|
-
result = BundleInfo(id: trueId, version:
|
|
2015
|
+
result = BundleInfo(id: trueId, version: self.versionBuild, status: BundleStatus.SUCCESS, checksum: "")
|
|
1855
2016
|
} else if BundleInfo.VERSION_UNKNOWN == trueId {
|
|
1856
2017
|
result = BundleInfo(id: trueId, version: "", status: BundleStatus.ERROR, checksum: "")
|
|
1857
2018
|
} else {
|
|
@@ -1954,6 +2115,8 @@ import UIKit
|
|
|
1954
2115
|
UserDefaults.standard.set(nextId, forKey: self.NEXT_VERSION)
|
|
1955
2116
|
UserDefaults.standard.synchronize()
|
|
1956
2117
|
self.setBundleStatus(id: nextId, status: BundleStatus.PENDING)
|
|
2118
|
+
self.sendStats(action: "set_next", versionName: newBundle.getVersionName(), oldVersionName: self.getCurrentBundle().getVersionName())
|
|
2119
|
+
self.notifyListeners("setNext", ["bundle": newBundle.toJSON()])
|
|
1957
2120
|
return true
|
|
1958
2121
|
}
|
|
1959
2122
|
}
|
|
@@ -97,25 +97,17 @@ public class DelayUpdateUtils {
|
|
|
97
97
|
|
|
98
98
|
case "date":
|
|
99
99
|
if let value = value, !value.isEmpty {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
dateFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
|
|
103
|
-
|
|
104
|
-
if let date = dateFormatter.date(from: value) {
|
|
105
|
-
if Date() > date {
|
|
106
|
-
// swiftlint:disable:next line_length
|
|
107
|
-
logger.info("Date delay (value: \(value)) condition removed due to expired date at index \(index)")
|
|
108
|
-
} else {
|
|
109
|
-
delayConditionListToKeep.append(condition)
|
|
110
|
-
logger.info("Date delay (value: \(value)) kept at index \(index)")
|
|
111
|
-
}
|
|
112
|
-
} else {
|
|
100
|
+
if let date = parseDateCondition(value) {
|
|
101
|
+
if Date() > date {
|
|
113
102
|
// swiftlint:disable:next line_length
|
|
114
|
-
logger.
|
|
103
|
+
logger.info("Date delay (value: \(value)) condition removed due to expired date at index \(index)")
|
|
104
|
+
} else {
|
|
105
|
+
delayConditionListToKeep.append(condition)
|
|
106
|
+
logger.info("Date delay (value: \(value)) kept at index \(index)")
|
|
115
107
|
}
|
|
116
|
-
}
|
|
108
|
+
} else {
|
|
117
109
|
// swiftlint:disable:next line_length
|
|
118
|
-
logger.error("Date delay (value: \(value)) condition removed due to parsing issue at index \(index)
|
|
110
|
+
logger.error("Date delay (value: \(value)) condition removed due to parsing issue at index \(index)")
|
|
119
111
|
}
|
|
120
112
|
} else {
|
|
121
113
|
// swiftlint:disable:next line_length
|
|
@@ -216,6 +208,35 @@ public class DelayUpdateUtils {
|
|
|
216
208
|
|
|
217
209
|
// MARK: - Helper methods
|
|
218
210
|
|
|
211
|
+
private func parseDateCondition(_ value: String) -> Date? {
|
|
212
|
+
let withFractionalSeconds = ISO8601DateFormatter()
|
|
213
|
+
withFractionalSeconds.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
|
|
214
|
+
if let date = withFractionalSeconds.date(from: value) {
|
|
215
|
+
return date
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
let withoutFractionalSeconds = ISO8601DateFormatter()
|
|
219
|
+
withoutFractionalSeconds.formatOptions = [.withInternetDateTime]
|
|
220
|
+
if let date = withoutFractionalSeconds.date(from: value) {
|
|
221
|
+
return date
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Legacy fallback for strings without timezone.
|
|
225
|
+
for format in ["yyyy-MM-dd'T'HH:mm:ss.SSS", "yyyy-MM-dd'T'HH:mm:ss"] {
|
|
226
|
+
let formatter = DateFormatter()
|
|
227
|
+
formatter.locale = Locale(identifier: "en_US_POSIX")
|
|
228
|
+
formatter.calendar = Calendar(identifier: .gregorian)
|
|
229
|
+
formatter.timeZone = .current
|
|
230
|
+
formatter.isLenient = false
|
|
231
|
+
formatter.dateFormat = format
|
|
232
|
+
if let date = formatter.date(from: value) {
|
|
233
|
+
return date
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return nil
|
|
238
|
+
}
|
|
239
|
+
|
|
219
240
|
private func toJson(object: Any) -> String {
|
|
220
241
|
guard let data = try? JSONSerialization.data(withJSONObject: object, options: []) else {
|
|
221
242
|
return ""
|
|
@@ -186,6 +186,7 @@ struct AppVersionDec: Decodable {
|
|
|
186
186
|
let url: String?
|
|
187
187
|
let message: String?
|
|
188
188
|
let error: String?
|
|
189
|
+
let kind: String?
|
|
189
190
|
let session_key: String?
|
|
190
191
|
let major: Bool?
|
|
191
192
|
let breaking: Bool?
|
|
@@ -203,6 +204,7 @@ public class AppVersion: NSObject {
|
|
|
203
204
|
var url: String = ""
|
|
204
205
|
var message: String?
|
|
205
206
|
var error: String?
|
|
207
|
+
var kind: String?
|
|
206
208
|
var sessionKey: String?
|
|
207
209
|
var major: Bool?
|
|
208
210
|
var breaking: Bool?
|
|
@@ -308,19 +308,36 @@ extension UIWindow {
|
|
|
308
308
|
}
|
|
309
309
|
|
|
310
310
|
let latest = updater.getLatest(url: updateUrl, channel: name)
|
|
311
|
+
let latestKind = latest.kind
|
|
312
|
+
|
|
313
|
+
let detail = [latest.message, latest.error, latestKind]
|
|
314
|
+
.compactMap { value in
|
|
315
|
+
guard let value, !value.isEmpty else { return nil }
|
|
316
|
+
return value
|
|
317
|
+
}
|
|
318
|
+
.first ?? "server did not provide a message"
|
|
311
319
|
|
|
312
320
|
// Handle update errors first (before "no new version" check)
|
|
313
|
-
if
|
|
321
|
+
if latestKind == "failed" || (latest.error?.isEmpty == false && latestKind != "up_to_date" && latestKind != "blocked") {
|
|
322
|
+
DispatchQueue.main.async {
|
|
323
|
+
progressAlert.dismiss(animated: true) {
|
|
324
|
+
self.showError(message: "Channel set to \(name). Update check failed: \(detail)", plugin: plugin)
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
return
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if latestKind == "blocked" {
|
|
314
331
|
DispatchQueue.main.async {
|
|
315
332
|
progressAlert.dismiss(animated: true) {
|
|
316
|
-
self.showError(message: "Channel set to \(name). Update check
|
|
333
|
+
self.showError(message: "Channel set to \(name). Update check blocked: \(detail)", plugin: plugin)
|
|
317
334
|
}
|
|
318
335
|
}
|
|
319
336
|
return
|
|
320
337
|
}
|
|
321
338
|
|
|
322
339
|
// Check if there's an actual update available
|
|
323
|
-
if
|
|
340
|
+
if latestKind == "up_to_date" || latest.url.isEmpty {
|
|
324
341
|
DispatchQueue.main.async {
|
|
325
342
|
progressAlert.dismiss(animated: true) {
|
|
326
343
|
self.showSuccess(message: "Channel set to \(name). Already on latest version.", plugin: plugin)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@capgo/capacitor-updater",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.45.10",
|
|
4
4
|
"license": "MPL-2.0",
|
|
5
5
|
"description": "Live update for capacitor apps",
|
|
6
6
|
"main": "dist/plugin.cjs.js",
|
|
@@ -41,23 +41,26 @@
|
|
|
41
41
|
"native"
|
|
42
42
|
],
|
|
43
43
|
"scripts": {
|
|
44
|
-
"verify": "
|
|
44
|
+
"verify": "bun run verify:ios && bun run verify:android && bun run verify:web",
|
|
45
45
|
"verify:ios": "xcodebuild -scheme CapgoCapacitorUpdater -destination generic/platform=iOS",
|
|
46
46
|
"verify:android": "cd android && ./gradlew clean build test && cd ..",
|
|
47
|
-
"verify:web": "
|
|
48
|
-
"test": "
|
|
47
|
+
"verify:web": "bun run build",
|
|
48
|
+
"test": "bun run test:ios && bun run test:android",
|
|
49
49
|
"test:ios": "./scripts/test-ios.sh",
|
|
50
50
|
"test:android": "cd android && ./gradlew test && cd ..",
|
|
51
|
-
"
|
|
52
|
-
"
|
|
51
|
+
"test:maestro": "./scripts/maestro/run-android-live-update.sh",
|
|
52
|
+
"test:maestro:android": "./scripts/test-maestro-android.sh",
|
|
53
|
+
"test:maestro:ios": "./scripts/test-maestro-ios.sh",
|
|
54
|
+
"lint": "bun run eslint && bun run prettier -- --check && bun run swiftlint -- lint",
|
|
55
|
+
"fmt": "bun run eslint -- --fix && bun run prettier -- --write && bun run swiftlint -- --fix --format",
|
|
53
56
|
"eslint": "eslint . --ext .ts",
|
|
54
57
|
"prettier": "prettier-pretty-check \"**/*.{css,html,ts,js,java}\" --plugin=prettier-plugin-java",
|
|
55
58
|
"swiftlint": "node-swiftlint",
|
|
56
59
|
"docgen": "node scripts/generate-docs.js",
|
|
57
|
-
"build": "
|
|
60
|
+
"build": "bun run clean && bun run docgen && tsc && rollup -c rollup.config.mjs",
|
|
58
61
|
"clean": "rimraf ./dist",
|
|
59
62
|
"watch": "tsc --watch",
|
|
60
|
-
"prepublishOnly": "
|
|
63
|
+
"prepublishOnly": "bun run build",
|
|
61
64
|
"check:wiring": "node scripts/check-capacitor-plugin-wiring.mjs"
|
|
62
65
|
},
|
|
63
66
|
"devDependencies": {
|