@capgo/capacitor-updater 7.37.0 → 7.39.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 +300 -0
- package/android/build.gradle +3 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdaterPlugin.java +348 -22
- package/android/src/main/java/ee/forgr/capacitor_updater/CapgoUpdater.java +40 -1
- package/dist/docs.json +607 -1
- package/dist/esm/definitions.d.ts +426 -0
- package/dist/esm/definitions.js +103 -1
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.d.ts +6 -1
- package/dist/esm/web.js +24 -0
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +132 -0
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +132 -0
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/CapacitorUpdaterPlugin/CapacitorUpdaterPlugin.swift +213 -2
- package/ios/Sources/CapacitorUpdaterPlugin/CapgoUpdater.swift +39 -1
- package/package.json +1 -1
|
@@ -51,10 +51,16 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
51
51
|
CAPPluginMethod(name: "getNextBundle", returnType: CAPPluginReturnPromise),
|
|
52
52
|
CAPPluginMethod(name: "getFailedUpdate", returnType: CAPPluginReturnPromise),
|
|
53
53
|
CAPPluginMethod(name: "setShakeMenu", returnType: CAPPluginReturnPromise),
|
|
54
|
-
CAPPluginMethod(name: "isShakeMenuEnabled", returnType: CAPPluginReturnPromise)
|
|
54
|
+
CAPPluginMethod(name: "isShakeMenuEnabled", returnType: CAPPluginReturnPromise),
|
|
55
|
+
// App Store update methods
|
|
56
|
+
CAPPluginMethod(name: "getAppUpdateInfo", returnType: CAPPluginReturnPromise),
|
|
57
|
+
CAPPluginMethod(name: "openAppStore", returnType: CAPPluginReturnPromise),
|
|
58
|
+
CAPPluginMethod(name: "performImmediateUpdate", returnType: CAPPluginReturnPromise),
|
|
59
|
+
CAPPluginMethod(name: "startFlexibleUpdate", returnType: CAPPluginReturnPromise),
|
|
60
|
+
CAPPluginMethod(name: "completeFlexibleUpdate", returnType: CAPPluginReturnPromise)
|
|
55
61
|
]
|
|
56
62
|
public var implementation = CapgoUpdater()
|
|
57
|
-
private let pluginVersion: String = "7.
|
|
63
|
+
private let pluginVersion: String = "7.39.0"
|
|
58
64
|
static let updateUrlDefault = "https://plugin.capgo.app/updates"
|
|
59
65
|
static let statsUrlDefault = "https://plugin.capgo.app/stats"
|
|
60
66
|
static let channelUrlDefault = "https://plugin.capgo.app/channel_self"
|
|
@@ -399,6 +405,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
399
405
|
return id.isEmpty ? nil : id
|
|
400
406
|
})
|
|
401
407
|
self.implementation.cleanupDownloadDirectories(allowedIds: allowedIds, threadToCheck: Thread.current)
|
|
408
|
+
self.implementation.cleanupOrphanedTempFolders(threadToCheck: Thread.current)
|
|
402
409
|
|
|
403
410
|
// Check again before the expensive delta cache cleanup
|
|
404
411
|
if Thread.current.isCancelled {
|
|
@@ -1659,4 +1666,208 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
1659
1666
|
implementation.appId = appId
|
|
1660
1667
|
call.resolve()
|
|
1661
1668
|
}
|
|
1669
|
+
|
|
1670
|
+
// MARK: - App Store Update Methods
|
|
1671
|
+
|
|
1672
|
+
/// AppUpdateAvailability enum values matching TypeScript definitions
|
|
1673
|
+
private enum AppUpdateAvailability: Int {
|
|
1674
|
+
case unknown = 0
|
|
1675
|
+
case updateNotAvailable = 1
|
|
1676
|
+
case updateAvailable = 2
|
|
1677
|
+
case updateInProgress = 3
|
|
1678
|
+
}
|
|
1679
|
+
|
|
1680
|
+
@objc func getAppUpdateInfo(_ call: CAPPluginCall) {
|
|
1681
|
+
let country = call.getString("country", "US")
|
|
1682
|
+
let bundleId = implementation.appId
|
|
1683
|
+
|
|
1684
|
+
logger.info("Getting App Store update info for \(bundleId) in country \(country)")
|
|
1685
|
+
|
|
1686
|
+
DispatchQueue.global(qos: .background).async {
|
|
1687
|
+
let urlString = "https://itunes.apple.com/lookup?bundleId=\(bundleId)&country=\(country)"
|
|
1688
|
+
guard let url = URL(string: urlString) else {
|
|
1689
|
+
call.reject("Invalid URL for App Store lookup")
|
|
1690
|
+
return
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
let task = URLSession.shared.dataTask(with: url) { data, response, error in
|
|
1694
|
+
if let error = error {
|
|
1695
|
+
self.logger.error("App Store lookup failed: \(error.localizedDescription)")
|
|
1696
|
+
call.reject("App Store lookup failed: \(error.localizedDescription)")
|
|
1697
|
+
return
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
guard let data = data else {
|
|
1701
|
+
call.reject("No data received from App Store")
|
|
1702
|
+
return
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1705
|
+
do {
|
|
1706
|
+
guard let json = try JSONSerialization.jsonObject(with: data) as? [String: Any],
|
|
1707
|
+
let resultCount = json["resultCount"] as? Int else {
|
|
1708
|
+
call.reject("Invalid response from App Store")
|
|
1709
|
+
return
|
|
1710
|
+
}
|
|
1711
|
+
|
|
1712
|
+
let currentVersionName = Bundle.main.versionName ?? "0.0.0"
|
|
1713
|
+
let currentVersionCode = Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "0"
|
|
1714
|
+
|
|
1715
|
+
var result: [String: Any] = [
|
|
1716
|
+
"currentVersionName": currentVersionName,
|
|
1717
|
+
"currentVersionCode": currentVersionCode,
|
|
1718
|
+
"updateAvailability": AppUpdateAvailability.unknown.rawValue
|
|
1719
|
+
]
|
|
1720
|
+
|
|
1721
|
+
if resultCount > 0,
|
|
1722
|
+
let results = json["results"] as? [[String: Any]],
|
|
1723
|
+
let appInfo = results.first {
|
|
1724
|
+
|
|
1725
|
+
let availableVersion = appInfo["version"] as? String
|
|
1726
|
+
let releaseDate = appInfo["currentVersionReleaseDate"] as? String
|
|
1727
|
+
let minimumOsVersion = appInfo["minimumOsVersion"] as? String
|
|
1728
|
+
|
|
1729
|
+
result["availableVersionName"] = availableVersion
|
|
1730
|
+
result["availableVersionCode"] = availableVersion // iOS doesn't have separate version code
|
|
1731
|
+
result["availableVersionReleaseDate"] = releaseDate
|
|
1732
|
+
result["minimumOsVersion"] = minimumOsVersion
|
|
1733
|
+
|
|
1734
|
+
// Determine update availability by comparing versions
|
|
1735
|
+
if let availableVersion = availableVersion {
|
|
1736
|
+
do {
|
|
1737
|
+
let currentVer = try Version(currentVersionName)
|
|
1738
|
+
let availableVer = try Version(availableVersion)
|
|
1739
|
+
if availableVer > currentVer {
|
|
1740
|
+
result["updateAvailability"] = AppUpdateAvailability.updateAvailable.rawValue
|
|
1741
|
+
} else {
|
|
1742
|
+
result["updateAvailability"] = AppUpdateAvailability.updateNotAvailable.rawValue
|
|
1743
|
+
}
|
|
1744
|
+
} catch {
|
|
1745
|
+
// If version parsing fails, do string comparison
|
|
1746
|
+
if availableVersion != currentVersionName {
|
|
1747
|
+
result["updateAvailability"] = AppUpdateAvailability.updateAvailable.rawValue
|
|
1748
|
+
} else {
|
|
1749
|
+
result["updateAvailability"] = AppUpdateAvailability.updateNotAvailable.rawValue
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
} else {
|
|
1753
|
+
result["updateAvailability"] = AppUpdateAvailability.updateNotAvailable.rawValue
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
// iOS doesn't support in-app updates like Android
|
|
1757
|
+
result["immediateUpdateAllowed"] = false
|
|
1758
|
+
result["flexibleUpdateAllowed"] = false
|
|
1759
|
+
} else {
|
|
1760
|
+
// App not found in App Store (maybe not published yet)
|
|
1761
|
+
result["updateAvailability"] = AppUpdateAvailability.updateNotAvailable.rawValue
|
|
1762
|
+
self.logger.info("App not found in App Store for bundleId: \(bundleId)")
|
|
1763
|
+
}
|
|
1764
|
+
|
|
1765
|
+
call.resolve(result)
|
|
1766
|
+
} catch {
|
|
1767
|
+
self.logger.error("Failed to parse App Store response: \(error.localizedDescription)")
|
|
1768
|
+
call.reject("Failed to parse App Store response: \(error.localizedDescription)")
|
|
1769
|
+
}
|
|
1770
|
+
}
|
|
1771
|
+
task.resume()
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1775
|
+
@objc func openAppStore(_ call: CAPPluginCall) {
|
|
1776
|
+
let appId = call.getString("appId")
|
|
1777
|
+
|
|
1778
|
+
if let appId = appId {
|
|
1779
|
+
// Open App Store with provided app ID
|
|
1780
|
+
let urlString = "https://apps.apple.com/app/id\(appId)"
|
|
1781
|
+
guard let url = URL(string: urlString) else {
|
|
1782
|
+
call.reject("Invalid App Store URL")
|
|
1783
|
+
return
|
|
1784
|
+
}
|
|
1785
|
+
DispatchQueue.main.async {
|
|
1786
|
+
UIApplication.shared.open(url) { success in
|
|
1787
|
+
if success {
|
|
1788
|
+
call.resolve()
|
|
1789
|
+
} else {
|
|
1790
|
+
call.reject("Failed to open App Store")
|
|
1791
|
+
}
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
} else {
|
|
1795
|
+
// Look up app ID using bundle identifier
|
|
1796
|
+
let bundleId = implementation.appId
|
|
1797
|
+
let lookupUrl = "https://itunes.apple.com/lookup?bundleId=\(bundleId)"
|
|
1798
|
+
|
|
1799
|
+
DispatchQueue.global(qos: .background).async {
|
|
1800
|
+
guard let url = URL(string: lookupUrl) else {
|
|
1801
|
+
call.reject("Invalid lookup URL")
|
|
1802
|
+
return
|
|
1803
|
+
}
|
|
1804
|
+
|
|
1805
|
+
let task = URLSession.shared.dataTask(with: url) { data, _, error in
|
|
1806
|
+
if let error = error {
|
|
1807
|
+
call.reject("Failed to lookup app: \(error.localizedDescription)")
|
|
1808
|
+
return
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
guard let data = data,
|
|
1812
|
+
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
|
|
1813
|
+
let results = json["results"] as? [[String: Any]],
|
|
1814
|
+
let appInfo = results.first,
|
|
1815
|
+
let trackId = appInfo["trackId"] as? Int else {
|
|
1816
|
+
// If lookup fails, try opening the generic App Store app page using bundle ID
|
|
1817
|
+
let fallbackUrlString = "https://apps.apple.com/app/\(bundleId)"
|
|
1818
|
+
guard let fallbackUrl = URL(string: fallbackUrlString) else {
|
|
1819
|
+
call.reject("Failed to find app in App Store and fallback URL is invalid")
|
|
1820
|
+
return
|
|
1821
|
+
}
|
|
1822
|
+
DispatchQueue.main.async {
|
|
1823
|
+
UIApplication.shared.open(fallbackUrl) { success in
|
|
1824
|
+
if success {
|
|
1825
|
+
call.resolve()
|
|
1826
|
+
} else {
|
|
1827
|
+
call.reject("Failed to open App Store")
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
return
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
let appStoreUrl = "https://apps.apple.com/app/id\(trackId)"
|
|
1835
|
+
guard let url = URL(string: appStoreUrl) else {
|
|
1836
|
+
call.reject("Invalid App Store URL")
|
|
1837
|
+
return
|
|
1838
|
+
}
|
|
1839
|
+
|
|
1840
|
+
DispatchQueue.main.async {
|
|
1841
|
+
UIApplication.shared.open(url) { success in
|
|
1842
|
+
if success {
|
|
1843
|
+
call.resolve()
|
|
1844
|
+
} else {
|
|
1845
|
+
call.reject("Failed to open App Store")
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
}
|
|
1850
|
+
task.resume()
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1855
|
+
@objc func performImmediateUpdate(_ call: CAPPluginCall) {
|
|
1856
|
+
// iOS doesn't support in-app updates like Android's Play Store
|
|
1857
|
+
// Redirect users to the App Store instead
|
|
1858
|
+
logger.warn("performImmediateUpdate is not supported on iOS. Use openAppStore() instead.")
|
|
1859
|
+
call.reject("In-app updates are not supported on iOS. Use openAppStore() to direct users to the App Store.", "NOT_SUPPORTED")
|
|
1860
|
+
}
|
|
1861
|
+
|
|
1862
|
+
@objc func startFlexibleUpdate(_ call: CAPPluginCall) {
|
|
1863
|
+
// iOS doesn't support flexible in-app updates
|
|
1864
|
+
logger.warn("startFlexibleUpdate is not supported on iOS. Use openAppStore() instead.")
|
|
1865
|
+
call.reject("Flexible updates are not supported on iOS. Use openAppStore() to direct users to the App Store.", "NOT_SUPPORTED")
|
|
1866
|
+
}
|
|
1867
|
+
|
|
1868
|
+
@objc func completeFlexibleUpdate(_ call: CAPPluginCall) {
|
|
1869
|
+
// iOS doesn't support flexible in-app updates
|
|
1870
|
+
logger.warn("completeFlexibleUpdate is not supported on iOS.")
|
|
1871
|
+
call.reject("Flexible updates are not supported on iOS.", "NOT_SUPPORTED")
|
|
1872
|
+
}
|
|
1662
1873
|
}
|
|
@@ -26,6 +26,7 @@ import UIKit
|
|
|
26
26
|
private let FALLBACK_VERSION: String = "pastVersion"
|
|
27
27
|
private let NEXT_VERSION: String = "nextVersion"
|
|
28
28
|
private var unzipPercent = 0
|
|
29
|
+
private let TEMP_UNZIP_PREFIX: String = "capgo_unzip_"
|
|
29
30
|
|
|
30
31
|
// Add this line to declare cacheFolder
|
|
31
32
|
private let cacheFolder: URL = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!.appendingPathComponent("capgo_downloads")
|
|
@@ -274,7 +275,7 @@ import UIKit
|
|
|
274
275
|
private func saveDownloaded(sourceZip: URL, id: String, base: URL, notify: Bool) throws {
|
|
275
276
|
try prepareFolder(source: base)
|
|
276
277
|
let destPersist: URL = base.appendingPathComponent(id)
|
|
277
|
-
let destUnZip: URL = libraryDir.appendingPathComponent(randomString(length: 10))
|
|
278
|
+
let destUnZip: URL = libraryDir.appendingPathComponent(TEMP_UNZIP_PREFIX + randomString(length: 10))
|
|
278
279
|
|
|
279
280
|
self.unzipPercent = 0
|
|
280
281
|
self.notifyDownload(id: id, percent: 75)
|
|
@@ -1103,6 +1104,43 @@ import UIKit
|
|
|
1103
1104
|
}
|
|
1104
1105
|
}
|
|
1105
1106
|
|
|
1107
|
+
public func cleanupOrphanedTempFolders(threadToCheck: Thread?) {
|
|
1108
|
+
let fileManager = FileManager.default
|
|
1109
|
+
|
|
1110
|
+
do {
|
|
1111
|
+
let contents = try fileManager.contentsOfDirectory(at: libraryDir, includingPropertiesForKeys: [.isDirectoryKey], options: [.skipsHiddenFiles])
|
|
1112
|
+
|
|
1113
|
+
for url in contents {
|
|
1114
|
+
// Check if thread was cancelled
|
|
1115
|
+
if let thread = threadToCheck, thread.isCancelled {
|
|
1116
|
+
logger.warn("cleanupOrphanedTempFolders was cancelled")
|
|
1117
|
+
return
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
let resourceValues = try url.resourceValues(forKeys: [.isDirectoryKey])
|
|
1121
|
+
if resourceValues.isDirectory != true {
|
|
1122
|
+
continue
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
let folderName = url.lastPathComponent
|
|
1126
|
+
|
|
1127
|
+
// Only delete folders with the temp unzip prefix
|
|
1128
|
+
if !folderName.hasPrefix(TEMP_UNZIP_PREFIX) {
|
|
1129
|
+
continue
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
do {
|
|
1133
|
+
try fileManager.removeItem(at: url)
|
|
1134
|
+
logger.info("Deleted orphaned temp unzip folder: \(folderName)")
|
|
1135
|
+
} catch {
|
|
1136
|
+
logger.error("Failed to delete orphaned temp folder: \(folderName) \(error.localizedDescription)")
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
} catch {
|
|
1140
|
+
logger.error("Failed to enumerate library directory for temp folder cleanup: \(error.localizedDescription)")
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1106
1144
|
public func getBundleDirectory(id: String) -> URL {
|
|
1107
1145
|
return libraryDir.appendingPathComponent(self.bundleDirectory).appendingPathComponent(id)
|
|
1108
1146
|
}
|