@capgo/capacitor-updater 7.3.3 → 7.5.1
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 +101 -16
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdaterPlugin.java +140 -6
- package/android/src/main/java/ee/forgr/capacitor_updater/CapgoUpdater.java +135 -1
- package/android/src/main/java/ee/forgr/capacitor_updater/ShakeDetector.java +72 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/ShakeMenu.java +169 -0
- package/dist/docs.json +260 -30
- package/dist/esm/definitions.d.ts +78 -0
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.d.ts +4 -1
- package/dist/esm/web.js +14 -2
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +14 -2
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +14 -2
- package/dist/plugin.js.map +1 -1
- package/ios/Plugin/CapacitorUpdaterPlugin.swift +76 -8
- package/ios/Plugin/CapgoUpdater.swift +96 -39
- package/ios/Plugin/InternalUtils.swift +45 -0
- package/ios/Plugin/Logger.swift +1 -1
- package/ios/Plugin/ShakeMenu.swift +112 -0
- package/package.json +1 -1
|
@@ -955,19 +955,17 @@ import UIKit
|
|
|
955
955
|
request.validate().responseDecodable(of: SetChannelDec.self) { response in
|
|
956
956
|
switch response.result {
|
|
957
957
|
case .success:
|
|
958
|
-
if let
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
setChannel.message = message
|
|
958
|
+
if let responseValue = response.value {
|
|
959
|
+
if let error = responseValue.error {
|
|
960
|
+
setChannel.error = error
|
|
961
|
+
} else {
|
|
962
|
+
setChannel.status = responseValue.status ?? ""
|
|
963
|
+
setChannel.message = responseValue.message ?? ""
|
|
964
|
+
}
|
|
966
965
|
}
|
|
967
966
|
case let .failure(error):
|
|
968
|
-
self.logger.error("Error unset Channel \(
|
|
969
|
-
setChannel.
|
|
970
|
-
setChannel.error = "response_error"
|
|
967
|
+
self.logger.error("Error unset Channel \(error)")
|
|
968
|
+
setChannel.error = "Request failed: \(error.localizedDescription)"
|
|
971
969
|
}
|
|
972
970
|
semaphore.signal()
|
|
973
971
|
}
|
|
@@ -992,19 +990,17 @@ import UIKit
|
|
|
992
990
|
request.validate().responseDecodable(of: SetChannelDec.self) { response in
|
|
993
991
|
switch response.result {
|
|
994
992
|
case .success:
|
|
995
|
-
if let
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
setChannel.message = message
|
|
993
|
+
if let responseValue = response.value {
|
|
994
|
+
if let error = responseValue.error {
|
|
995
|
+
setChannel.error = error
|
|
996
|
+
} else {
|
|
997
|
+
setChannel.status = responseValue.status ?? ""
|
|
998
|
+
setChannel.message = responseValue.message ?? ""
|
|
999
|
+
}
|
|
1003
1000
|
}
|
|
1004
1001
|
case let .failure(error):
|
|
1005
|
-
self.logger.error("Error set Channel \(
|
|
1006
|
-
setChannel.
|
|
1007
|
-
setChannel.error = "response_error"
|
|
1002
|
+
self.logger.error("Error set Channel \(error)")
|
|
1003
|
+
setChannel.error = "Request failed: \(error.localizedDescription)"
|
|
1008
1004
|
}
|
|
1009
1005
|
semaphore.signal()
|
|
1010
1006
|
}
|
|
@@ -1030,20 +1026,15 @@ import UIKit
|
|
|
1030
1026
|
}
|
|
1031
1027
|
switch response.result {
|
|
1032
1028
|
case .success:
|
|
1033
|
-
if let
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
if let channel = response.value?.channel {
|
|
1043
|
-
getChannel.channel = channel
|
|
1044
|
-
}
|
|
1045
|
-
if let allowSet = response.value?.allowSet {
|
|
1046
|
-
getChannel.allowSet = allowSet
|
|
1029
|
+
if let responseValue = response.value {
|
|
1030
|
+
if let error = responseValue.error {
|
|
1031
|
+
getChannel.error = error
|
|
1032
|
+
} else {
|
|
1033
|
+
getChannel.status = responseValue.status ?? ""
|
|
1034
|
+
getChannel.message = responseValue.message ?? ""
|
|
1035
|
+
getChannel.channel = responseValue.channel ?? ""
|
|
1036
|
+
getChannel.allowSet = responseValue.allowSet ?? true
|
|
1037
|
+
}
|
|
1047
1038
|
}
|
|
1048
1039
|
case let .failure(error):
|
|
1049
1040
|
if let data = response.data, let bodyString = String(data: data, encoding: .utf8) {
|
|
@@ -1054,15 +1045,81 @@ import UIKit
|
|
|
1054
1045
|
}
|
|
1055
1046
|
}
|
|
1056
1047
|
|
|
1057
|
-
self.logger.error("Error get Channel \(
|
|
1058
|
-
getChannel.
|
|
1059
|
-
getChannel.error = "response_error"
|
|
1048
|
+
self.logger.error("Error get Channel \(error)")
|
|
1049
|
+
getChannel.error = "Request failed: \(error.localizedDescription)"
|
|
1060
1050
|
}
|
|
1061
1051
|
}
|
|
1062
1052
|
semaphore.wait()
|
|
1063
1053
|
return getChannel
|
|
1064
1054
|
}
|
|
1065
1055
|
|
|
1056
|
+
func listChannels() -> ListChannels {
|
|
1057
|
+
let listChannels: ListChannels = ListChannels()
|
|
1058
|
+
if (self.channelUrl).isEmpty {
|
|
1059
|
+
logger.error("Channel URL is not set")
|
|
1060
|
+
listChannels.error = "Channel URL is not set"
|
|
1061
|
+
return listChannels
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
let semaphore: DispatchSemaphore = DispatchSemaphore(value: 0)
|
|
1065
|
+
|
|
1066
|
+
// Auto-detect values
|
|
1067
|
+
let appId = self.appId
|
|
1068
|
+
let platform = "ios"
|
|
1069
|
+
let isEmulator = self.isEmulator()
|
|
1070
|
+
let isProd = self.isProd()
|
|
1071
|
+
|
|
1072
|
+
// Create query parameters
|
|
1073
|
+
var urlComponents = URLComponents(string: self.channelUrl)
|
|
1074
|
+
urlComponents?.queryItems = [
|
|
1075
|
+
URLQueryItem(name: "app_id", value: appId),
|
|
1076
|
+
URLQueryItem(name: "platform", value: platform),
|
|
1077
|
+
URLQueryItem(name: "is_emulator", value: String(isEmulator)),
|
|
1078
|
+
URLQueryItem(name: "is_prod", value: String(isProd))
|
|
1079
|
+
]
|
|
1080
|
+
|
|
1081
|
+
guard let url = urlComponents?.url else {
|
|
1082
|
+
logger.error("Invalid channel URL")
|
|
1083
|
+
listChannels.error = "Invalid channel URL"
|
|
1084
|
+
return listChannels
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
let request = AF.request(url, method: .get, requestModifier: { $0.timeoutInterval = self.timeout })
|
|
1088
|
+
|
|
1089
|
+
request.validate().responseDecodable(of: ListChannelsDec.self) { response in
|
|
1090
|
+
defer {
|
|
1091
|
+
semaphore.signal()
|
|
1092
|
+
}
|
|
1093
|
+
switch response.result {
|
|
1094
|
+
case .success:
|
|
1095
|
+
if let responseValue = response.value {
|
|
1096
|
+
// Check for server-side errors
|
|
1097
|
+
if let error = responseValue.error {
|
|
1098
|
+
listChannels.error = error
|
|
1099
|
+
return
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
// Backend returns direct array, so channels should be populated by our custom decoder
|
|
1103
|
+
if let channels = responseValue.channels {
|
|
1104
|
+
listChannels.channels = channels.map { channel in
|
|
1105
|
+
var channelDict: [String: Any] = [:]
|
|
1106
|
+
channelDict["id"] = channel.id ?? ""
|
|
1107
|
+
channelDict["name"] = channel.name ?? ""
|
|
1108
|
+
channelDict["public"] = channel.public ?? false
|
|
1109
|
+
channelDict["allow_self_set"] = channel.allow_self_set ?? false
|
|
1110
|
+
return channelDict
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
case let .failure(error):
|
|
1115
|
+
self.logger.error("Error list channels \(error)")
|
|
1116
|
+
listChannels.error = "Request failed: \(error.localizedDescription)"
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
semaphore.wait()
|
|
1120
|
+
return listChannels
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1066
1123
|
private let operationQueue = OperationQueue()
|
|
1067
1124
|
|
|
1068
1125
|
func sendStats(action: String, versionName: String? = nil, oldVersionName: String? = "") {
|
|
@@ -67,6 +67,51 @@ extension GetChannel {
|
|
|
67
67
|
return dict
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
|
+
struct ChannelInfo: Codable {
|
|
71
|
+
let id: String?
|
|
72
|
+
let name: String?
|
|
73
|
+
let `public`: Bool?
|
|
74
|
+
let allow_self_set: Bool?
|
|
75
|
+
}
|
|
76
|
+
struct ListChannelsDec: Decodable {
|
|
77
|
+
let channels: [ChannelInfo]?
|
|
78
|
+
let error: String?
|
|
79
|
+
|
|
80
|
+
init(from decoder: Decoder) throws {
|
|
81
|
+
let container = try decoder.singleValueContainer()
|
|
82
|
+
|
|
83
|
+
if let channelsArray = try? container.decode([ChannelInfo].self) {
|
|
84
|
+
// Backend returns direct array
|
|
85
|
+
self.channels = channelsArray
|
|
86
|
+
self.error = nil
|
|
87
|
+
} else {
|
|
88
|
+
// Handle error response
|
|
89
|
+
let errorContainer = try decoder.container(keyedBy: CodingKeys.self)
|
|
90
|
+
self.channels = nil
|
|
91
|
+
self.error = try? errorContainer.decode(String.self, forKey: .error)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
private enum CodingKeys: String, CodingKey {
|
|
96
|
+
case error
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
public class ListChannels: NSObject {
|
|
100
|
+
var channels: [[String: Any]] = []
|
|
101
|
+
var error: String = ""
|
|
102
|
+
}
|
|
103
|
+
extension ListChannels {
|
|
104
|
+
func toDict() -> [String: Any] {
|
|
105
|
+
var dict: [String: Any] = [String: Any]()
|
|
106
|
+
let otherSelf: Mirror = Mirror(reflecting: self)
|
|
107
|
+
for child: Mirror.Child in otherSelf.children {
|
|
108
|
+
if let key: String = child.label {
|
|
109
|
+
dict[key] = child.value
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return dict
|
|
113
|
+
}
|
|
114
|
+
}
|
|
70
115
|
struct InfoObject: Codable {
|
|
71
116
|
let platform: String?
|
|
72
117
|
let device_id: String?
|
package/ios/Plugin/Logger.swift
CHANGED
|
@@ -134,7 +134,7 @@ public class Logger {
|
|
|
134
134
|
withTag tag: String,
|
|
135
135
|
config: InstanceConfiguration? = nil,
|
|
136
136
|
options: Options? = nil
|
|
137
|
-
|
|
137
|
+
) {
|
|
138
138
|
self.tag = tag
|
|
139
139
|
if let config = config {
|
|
140
140
|
// The logger plugin's name is LoggerBridge, we want to look at the config
|
|
@@ -0,0 +1,112 @@
|
|
|
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
|
+
public class func topViewController(_ base: UIViewController? = UIApplication.shared.windows.first?.rootViewController) -> UIViewController? {
|
|
12
|
+
if let nav = base as? UINavigationController {
|
|
13
|
+
return topViewController(nav.visibleViewController)
|
|
14
|
+
}
|
|
15
|
+
if let tab = base as? UITabBarController, let selected = tab.selectedViewController {
|
|
16
|
+
return topViewController(selected)
|
|
17
|
+
}
|
|
18
|
+
if let presented = base?.presentedViewController {
|
|
19
|
+
return topViewController(presented)
|
|
20
|
+
}
|
|
21
|
+
return base
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
extension UIWindow {
|
|
26
|
+
override open func motionEnded(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {
|
|
27
|
+
if motion == .motionShake {
|
|
28
|
+
// Find the CapacitorUpdaterPlugin instance
|
|
29
|
+
guard let bridge = (rootViewController as? CAPBridgeProtocol),
|
|
30
|
+
let plugin = bridge.plugin(withName: "CapacitorUpdaterPlugin") as? CapacitorUpdaterPlugin else {
|
|
31
|
+
return
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Check if shake menu is enabled
|
|
35
|
+
if !plugin.shakeMenuEnabled {
|
|
36
|
+
return
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
showShakeMenu(plugin: plugin, bridge: bridge)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private func showShakeMenu(plugin: CapacitorUpdaterPlugin, bridge: CAPBridgeProtocol) {
|
|
44
|
+
// Prevent multiple alerts from showing
|
|
45
|
+
if let topVC = UIApplication.topViewController(),
|
|
46
|
+
topVC.isKind(of: UIAlertController.self) {
|
|
47
|
+
plugin.logger.info("UIAlertController is already presented")
|
|
48
|
+
return
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
let appName = Bundle.main.infoDictionary?["CFBundleDisplayName"] as? String ?? "App"
|
|
52
|
+
let title = "Preview \(appName) Menu"
|
|
53
|
+
let message = "What would you like to do?"
|
|
54
|
+
let okButtonTitle = "Go Home"
|
|
55
|
+
let reloadButtonTitle = "Reload app"
|
|
56
|
+
let cancelButtonTitle = "Close menu"
|
|
57
|
+
|
|
58
|
+
let updater = plugin.implementation
|
|
59
|
+
|
|
60
|
+
func resetBuiltin() {
|
|
61
|
+
updater.reset()
|
|
62
|
+
bridge.setServerBasePath("")
|
|
63
|
+
DispatchQueue.main.async {
|
|
64
|
+
if let vc = (self.rootViewController as? CAPBridgeViewController) {
|
|
65
|
+
vc.loadView()
|
|
66
|
+
vc.viewDidLoad()
|
|
67
|
+
}
|
|
68
|
+
_ = updater.delete(id: updater.getCurrentBundleId())
|
|
69
|
+
plugin.logger.info("Reset to builtin version")
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
let bundleId = updater.getCurrentBundleId()
|
|
74
|
+
if let vc = (self.rootViewController as? CAPBridgeViewController) {
|
|
75
|
+
plugin.logger.info("getServerBasePath: \(vc.getServerBasePath())")
|
|
76
|
+
}
|
|
77
|
+
plugin.logger.info("bundleId: \(bundleId)")
|
|
78
|
+
|
|
79
|
+
let alertShake = UIAlertController(title: title, message: message, preferredStyle: .alert)
|
|
80
|
+
|
|
81
|
+
alertShake.addAction(UIAlertAction(title: okButtonTitle, style: .default) { _ in
|
|
82
|
+
guard let next = updater.getNextBundle() else {
|
|
83
|
+
resetBuiltin()
|
|
84
|
+
return
|
|
85
|
+
}
|
|
86
|
+
if !next.isBuiltin() {
|
|
87
|
+
plugin.logger.info("Resetting to: \(next.toString())")
|
|
88
|
+
_ = updater.set(bundle: next)
|
|
89
|
+
let destHot = updater.getBundleDirectory(id: next.getId())
|
|
90
|
+
plugin.logger.info("Reloading \(next.toString())")
|
|
91
|
+
bridge.setServerBasePath(destHot.path)
|
|
92
|
+
} else {
|
|
93
|
+
resetBuiltin()
|
|
94
|
+
}
|
|
95
|
+
plugin.logger.info("Reload app done")
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
alertShake.addAction(UIAlertAction(title: cancelButtonTitle, style: .default))
|
|
99
|
+
|
|
100
|
+
alertShake.addAction(UIAlertAction(title: reloadButtonTitle, style: .default) { _ in
|
|
101
|
+
DispatchQueue.main.async {
|
|
102
|
+
bridge.webView?.reload()
|
|
103
|
+
}
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
DispatchQueue.main.async {
|
|
107
|
+
if let topVC = UIApplication.topViewController() {
|
|
108
|
+
topVC.present(alertShake, animated: true)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|