@capgo/capacitor-updater 8.0.1 → 8.1.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/CapgoCapacitorUpdater.podspec +7 -5
- package/Package.swift +9 -7
- package/README.md +984 -215
- package/android/build.gradle +24 -12
- package/android/proguard-rules.pro +22 -5
- package/android/src/main/java/ee/forgr/capacitor_updater/BundleInfo.java +110 -22
- package/android/src/main/java/ee/forgr/capacitor_updater/Callback.java +2 -2
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdaterPlugin.java +1310 -488
- package/android/src/main/java/ee/forgr/capacitor_updater/{CapacitorUpdater.java → CapgoUpdater.java} +640 -203
- package/android/src/main/java/ee/forgr/capacitor_updater/{CryptoCipherV2.java → CryptoCipher.java} +119 -33
- package/android/src/main/java/ee/forgr/capacitor_updater/DelayCondition.java +0 -3
- package/android/src/main/java/ee/forgr/capacitor_updater/DelayUpdateUtils.java +260 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/DeviceIdHelper.java +221 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/DownloadService.java +497 -133
- package/android/src/main/java/ee/forgr/capacitor_updater/DownloadWorkerManager.java +80 -25
- package/android/src/main/java/ee/forgr/capacitor_updater/Logger.java +338 -0
- 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 +873 -154
- package/dist/esm/definitions.d.ts +881 -114
- 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 +12 -1
- package/dist/esm/web.js +29 -2
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +311 -2
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +311 -2
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/CapacitorUpdaterPlugin/AES.swift +69 -0
- package/ios/Sources/CapacitorUpdaterPlugin/BigInt.swift +55 -0
- package/ios/{Plugin → Sources/CapacitorUpdaterPlugin}/BundleInfo.swift +37 -10
- package/ios/{Plugin → Sources/CapacitorUpdaterPlugin}/BundleStatus.swift +1 -1
- package/ios/Sources/CapacitorUpdaterPlugin/CapacitorUpdaterPlugin.swift +1605 -0
- package/ios/{Plugin/CapacitorUpdater.swift → Sources/CapacitorUpdaterPlugin/CapgoUpdater.swift} +523 -230
- package/ios/Sources/CapacitorUpdaterPlugin/CryptoCipher.swift +267 -0
- package/ios/Sources/CapacitorUpdaterPlugin/DelayUpdateUtils.swift +220 -0
- package/ios/Sources/CapacitorUpdaterPlugin/DeviceIdHelper.swift +120 -0
- package/ios/{Plugin → Sources/CapacitorUpdaterPlugin}/InternalUtils.swift +53 -0
- package/ios/Sources/CapacitorUpdaterPlugin/Logger.swift +310 -0
- package/ios/Sources/CapacitorUpdaterPlugin/RSA.swift +274 -0
- package/ios/Sources/CapacitorUpdaterPlugin/ShakeMenu.swift +112 -0
- package/ios/{Plugin → Sources/CapacitorUpdaterPlugin}/UserDefaultsExtension.swift +0 -2
- package/package.json +21 -19
- package/ios/Plugin/CapacitorUpdaterPlugin.swift +0 -975
- package/ios/Plugin/CryptoCipherV2.swift +0 -310
- /package/{LICENCE → LICENSE} +0 -0
- /package/ios/{Plugin → Sources/CapacitorUpdaterPlugin}/DelayCondition.swift +0 -0
- /package/ios/{Plugin → Sources/CapacitorUpdaterPlugin}/DelayUntilNext.swift +0 -0
- /package/ios/{Plugin → Sources/CapacitorUpdaterPlugin}/Info.plist +0 -0
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import Capacitor
|
|
2
|
+
import Foundation
|
|
3
|
+
import os.log
|
|
4
|
+
import WebKit
|
|
5
|
+
|
|
6
|
+
public class Logger {
|
|
7
|
+
public enum LogLevel: Int {
|
|
8
|
+
case silent = 0
|
|
9
|
+
case error
|
|
10
|
+
case warn
|
|
11
|
+
case info
|
|
12
|
+
case debug
|
|
13
|
+
|
|
14
|
+
public static subscript(_ str: String) -> LogLevel? {
|
|
15
|
+
var index = 0
|
|
16
|
+
|
|
17
|
+
while let item = LogLevel(rawValue: index) {
|
|
18
|
+
if str == "\(item)" {
|
|
19
|
+
return item
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
index += 1
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return nil
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
public func asOSLogType() -> OSLogType {
|
|
29
|
+
switch self {
|
|
30
|
+
case .debug:
|
|
31
|
+
return OSLogType.debug
|
|
32
|
+
case .info:
|
|
33
|
+
return OSLogType.info
|
|
34
|
+
case .warn:
|
|
35
|
+
return OSLogType.default
|
|
36
|
+
case .error:
|
|
37
|
+
return OSLogType.error
|
|
38
|
+
case .silent:
|
|
39
|
+
return OSLogType.info // Compiler wants this, it will never be used
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
public func asString() -> String {
|
|
43
|
+
switch self {
|
|
44
|
+
case .debug:
|
|
45
|
+
return "debug"
|
|
46
|
+
case .info:
|
|
47
|
+
return "info"
|
|
48
|
+
case .warn:
|
|
49
|
+
return "warn"
|
|
50
|
+
case .error:
|
|
51
|
+
return "error"
|
|
52
|
+
case .silent:
|
|
53
|
+
return "info"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
public struct Options {
|
|
59
|
+
public var level: LogLevel
|
|
60
|
+
public var labels: [String: String]
|
|
61
|
+
public var useSyslog: Bool
|
|
62
|
+
|
|
63
|
+
public init(level: LogLevel = LogLevel.info, labels: [String: String] = [:], useSyslog: Bool = false) {
|
|
64
|
+
self.level = level
|
|
65
|
+
self.labels = labels
|
|
66
|
+
self.useSyslog = useSyslog
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
private var _labels: [LogLevel: String] = [
|
|
71
|
+
LogLevel.silent: "",
|
|
72
|
+
LogLevel.error: "🔴",
|
|
73
|
+
LogLevel.warn: "🟠",
|
|
74
|
+
LogLevel.info: "🟢",
|
|
75
|
+
LogLevel.debug: "🔎"
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
public var labels: [String: String] {
|
|
79
|
+
get {
|
|
80
|
+
var result: [String: String] = [:]
|
|
81
|
+
|
|
82
|
+
for (level, label) in _labels {
|
|
83
|
+
result[String(describing: level)] = label
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return result
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
set {
|
|
90
|
+
for (level, label) in newValue {
|
|
91
|
+
if let logLevel = LogLevel[level] {
|
|
92
|
+
_labels[logLevel] = label
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
public var level = LogLevel.info
|
|
99
|
+
|
|
100
|
+
public var levelName: String {
|
|
101
|
+
get {
|
|
102
|
+
String(describing: level)
|
|
103
|
+
}
|
|
104
|
+
set {
|
|
105
|
+
if let newLevel = LogLevel[newValue] {
|
|
106
|
+
level = newLevel
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
private var _tag = ""
|
|
112
|
+
|
|
113
|
+
public var tag: String {
|
|
114
|
+
get {
|
|
115
|
+
_tag
|
|
116
|
+
}
|
|
117
|
+
set {
|
|
118
|
+
if !newValue.isEmpty {
|
|
119
|
+
_tag = newValue
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
private let kDefaultTimerLabel = "default"
|
|
125
|
+
private var timers: [String: Date] = [:]
|
|
126
|
+
public var useSyslog = false
|
|
127
|
+
public var webView: WKWebView?
|
|
128
|
+
|
|
129
|
+
public func setWebView(webView: WKWebView) {
|
|
130
|
+
self.webView = webView
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
public init(
|
|
134
|
+
withTag tag: String,
|
|
135
|
+
config: InstanceConfiguration? = nil,
|
|
136
|
+
options: Options? = nil
|
|
137
|
+
) {
|
|
138
|
+
self.tag = tag
|
|
139
|
+
if let config = config {
|
|
140
|
+
// The logger plugin's name is LoggerBridge, we want to look at the config
|
|
141
|
+
// named "Logger", so we can't use plugin.getConfigValue().
|
|
142
|
+
if let configLevel = getConfigValue("level", from: config) as? String,
|
|
143
|
+
let logLevel = LogLevel[configLevel] {
|
|
144
|
+
level = logLevel
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if let configLabels = getConfigValue("labels", from: config) as? [String: String] {
|
|
148
|
+
labels = configLabels
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if let configSyslog = getConfigValue("useSyslog", from: config) as? Bool {
|
|
152
|
+
useSyslog = configSyslog
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if let options = options {
|
|
157
|
+
self.level = options.level
|
|
158
|
+
self.labels = options.labels
|
|
159
|
+
self.useSyslog = options.useSyslog
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
private func getConfigValue(_ configKey: String, from config: InstanceConfiguration) -> Any? {
|
|
164
|
+
if let config = config.pluginConfigurations as? JSObject {
|
|
165
|
+
return config[keyPath: KeyPath(stringLiteral: "Logger.\(configKey)")]
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return nil
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
public func error(_ message: String) {
|
|
172
|
+
log(atLevel: LogLevel.error, message: message)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
public func warn(_ message: String) {
|
|
176
|
+
log(atLevel: LogLevel.warn, message: message)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
public func info(_ message: String) {
|
|
180
|
+
log(atLevel: LogLevel.info, message: message)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
public func log(_ message: String) {
|
|
184
|
+
log(atLevel: LogLevel.info, message: message)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
public func debug(_ message: String) {
|
|
188
|
+
log(atLevel: LogLevel.debug, message: message)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
public func dir(_ value: Any?) {
|
|
192
|
+
// Check the log level here to avoid conversion to string
|
|
193
|
+
if canLog(atLevel: LogLevel.info) {
|
|
194
|
+
log(atLevel: LogLevel.info, message: String(describing: value))
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
public func log(atLevel level: LogLevel, message: String) {
|
|
199
|
+
// This will never fail, but we have to keep swift happy
|
|
200
|
+
if let label = _labels[level] {
|
|
201
|
+
log(atLevel: level, label: label, tag: tag, message: message)
|
|
202
|
+
if let webView = self.webView {
|
|
203
|
+
DispatchQueue.main.async {
|
|
204
|
+
let combined = "\(label) \(self.tag) : \(message)"
|
|
205
|
+
let jsArg = self.toJSStringLiteral(combined)
|
|
206
|
+
webView.evaluateJavaScript("console.\(level.asString())(\(jsArg))", completionHandler: nil)
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
private func toJSStringLiteral(_ value: String) -> String {
|
|
213
|
+
// Prefer JSON encoding to produce a valid JS string literal
|
|
214
|
+
if let data = try? JSONEncoder().encode(value),
|
|
215
|
+
let encoded = String(data: data, encoding: .utf8) {
|
|
216
|
+
return encoded
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Fallback manual escaping (unlikely to be used)
|
|
220
|
+
var s = value
|
|
221
|
+
s = s.replacingOccurrences(of: "\\", with: "\\\\")
|
|
222
|
+
s = s.replacingOccurrences(of: "\"", with: "\\\"")
|
|
223
|
+
s = s.replacingOccurrences(of: "'", with: "\\'")
|
|
224
|
+
s = s.replacingOccurrences(of: "\n", with: "\\n")
|
|
225
|
+
s = s.replacingOccurrences(of: "\r", with: "\\r")
|
|
226
|
+
s = s.replacingOccurrences(of: "\u{2028}", with: "\\u2028")
|
|
227
|
+
s = s.replacingOccurrences(of: "\u{2029}", with: "\\u2029")
|
|
228
|
+
return "\"\(s)\""
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
public func log(atLevel level: LogLevel, label: String?, tag: String, message: String) {
|
|
232
|
+
// This will never fail, but we have to keep swift happy
|
|
233
|
+
if let label = label ?? _labels[level] {
|
|
234
|
+
print(atLevel: level, label: label, tag: tag, message: message)
|
|
235
|
+
// eval
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
private func canLog(atLevel level: LogLevel) -> Bool {
|
|
240
|
+
self.level.rawValue >= level.rawValue
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
private func print(atLevel level: LogLevel, label: String, tag: String, message: String) {
|
|
244
|
+
guard canLog(atLevel: level) else {
|
|
245
|
+
return
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
var msg = message
|
|
249
|
+
|
|
250
|
+
if !label.isEmpty {
|
|
251
|
+
// If the label is ASCII, put it after the tag, otherwise before.
|
|
252
|
+
if label[label.startIndex].isASCII {
|
|
253
|
+
msg = "[\(tag)] \(label): \(message)"
|
|
254
|
+
} else {
|
|
255
|
+
msg = "\(label) [\(tag)]: \(message)"
|
|
256
|
+
}
|
|
257
|
+
} else {
|
|
258
|
+
msg = "[\(tag)]: \(message)"
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if useSyslog {
|
|
262
|
+
os_log("%{public}@", type: level.asOSLogType(), msg)
|
|
263
|
+
} else {
|
|
264
|
+
Swift.print(msg)
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
public func time(_ label: String?) {
|
|
269
|
+
timers[label ?? ""] = Date()
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
public func timeLog(_ label: String?) {
|
|
273
|
+
if let timer = timers[label ?? ""] {
|
|
274
|
+
info(formatTimeInterval(timer.timeIntervalSinceNow))
|
|
275
|
+
} else {
|
|
276
|
+
warn("timer \(label ?? kDefaultTimerLabel) does not exist")
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
public func timeEnd(_ label: String?) {
|
|
281
|
+
timeLog(label)
|
|
282
|
+
timers.removeValue(forKey: label ?? kDefaultTimerLabel)
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
private func formatTimeInterval(_ interval: TimeInterval) -> String {
|
|
286
|
+
let int = Int(interval)
|
|
287
|
+
let millis = Int(((1 + interval.remainder(dividingBy: 1)) * 1000).rounded())
|
|
288
|
+
let seconds = int % 60
|
|
289
|
+
let minutes = (int / 60) % 60
|
|
290
|
+
let hours = (int / 3600)
|
|
291
|
+
|
|
292
|
+
if seconds < 1 {
|
|
293
|
+
return "\(millis)ms"
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if minutes < 1 {
|
|
297
|
+
return "\(seconds).\(String(format: "%0.3d", millis))s"
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if hours < 1 {
|
|
301
|
+
return "\(minutes):\(String(format: "%0.2d", seconds)).\(String(format: "%0.3d", millis)) (min:sec.ms)"
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return "\(hours):\(String(format: "%0.2d", minutes)):\(String(format: "%0.2d", seconds)) (hr:min:sec)"
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
public func trace() {
|
|
308
|
+
info(String(format: "%@", Thread.callStackSymbols))
|
|
309
|
+
}
|
|
310
|
+
}
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import BigInt
|
|
2
|
+
import Foundation
|
|
3
|
+
import CommonCrypto
|
|
4
|
+
import CryptoKit
|
|
5
|
+
|
|
6
|
+
///
|
|
7
|
+
/// Constants
|
|
8
|
+
///
|
|
9
|
+
private enum RSAConstants {
|
|
10
|
+
static let rsaKeySizeInBits: NSNumber = 2048
|
|
11
|
+
static let rsaAlgorithm: SecKeyAlgorithm = .rsaEncryptionOAEPSHA256
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// We do all this stuff because ios is shit and open source libraries allow to do decryption with public key
|
|
15
|
+
// So we have to do it manually, while in nodejs or Java it's ok and done at language level.
|
|
16
|
+
|
|
17
|
+
///
|
|
18
|
+
/// The RSA public key.
|
|
19
|
+
///
|
|
20
|
+
public struct RSAPublicKey {
|
|
21
|
+
private let manualKey: ManualRSAPublicKey
|
|
22
|
+
|
|
23
|
+
fileprivate init(manualKey: ManualRSAPublicKey) {
|
|
24
|
+
self.manualKey = manualKey
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
///
|
|
28
|
+
/// Takes the data and uses the public key to decrypt it.
|
|
29
|
+
/// Returns the decrypted data.
|
|
30
|
+
///
|
|
31
|
+
public func decrypt(data: Data) -> Data? {
|
|
32
|
+
return manualKey.decrypt(data)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
///
|
|
36
|
+
/// Allows you to load an RSA public key (i.e. one downloaded from the net).
|
|
37
|
+
///
|
|
38
|
+
public static func load(rsaPublicKey: String) -> RSAPublicKey? {
|
|
39
|
+
// Clean up the key string
|
|
40
|
+
var pubKey: String = rsaPublicKey
|
|
41
|
+
pubKey = pubKey.replacingOccurrences(of: "-----BEGIN RSA PUBLIC KEY-----", with: "")
|
|
42
|
+
pubKey = pubKey.replacingOccurrences(of: "-----END RSA PUBLIC KEY-----", with: "")
|
|
43
|
+
pubKey = pubKey.replacingOccurrences(of: "-----BEGIN PUBLIC KEY-----", with: "")
|
|
44
|
+
pubKey = pubKey.replacingOccurrences(of: "-----END PUBLIC KEY-----", with: "")
|
|
45
|
+
pubKey = pubKey.replacingOccurrences(of: "\\n+", with: "", options: .regularExpression)
|
|
46
|
+
pubKey = pubKey.replacingOccurrences(of: "\n", with: "")
|
|
47
|
+
pubKey = pubKey.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
48
|
+
|
|
49
|
+
do {
|
|
50
|
+
guard let rsaPublicKeyData: Data = Data(base64Encoded: String(pubKey)) else {
|
|
51
|
+
throw CustomError.cannotDecode
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Try parsing as PKCS#1
|
|
55
|
+
if let manualKey = ManualRSAPublicKey.fromPKCS1(rsaPublicKeyData) {
|
|
56
|
+
return RSAPublicKey(manualKey: manualKey)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Most common public exponent is 65537 (0x010001)
|
|
60
|
+
let commonExponent = Data([0x01, 0x00, 0x01]) // 65537 in big-endian
|
|
61
|
+
|
|
62
|
+
// Assume the entire key data is the modulus
|
|
63
|
+
let lastResortKey = ManualRSAPublicKey(modulus: rsaPublicKeyData, exponent: commonExponent)
|
|
64
|
+
return RSAPublicKey(manualKey: lastResortKey)
|
|
65
|
+
} catch {
|
|
66
|
+
return nil
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Manual RSA Public Key Implementation using the BigInt library
|
|
72
|
+
struct ManualRSAPublicKey {
|
|
73
|
+
let modulus: BigInt
|
|
74
|
+
let exponent: BigInt
|
|
75
|
+
|
|
76
|
+
init(modulus: Data, exponent: Data) {
|
|
77
|
+
// Create positive BigInts from Data
|
|
78
|
+
let modulusBytes = [UInt8](modulus)
|
|
79
|
+
var modulusValue = BigUInt(0)
|
|
80
|
+
for byte in modulusBytes {
|
|
81
|
+
modulusValue = (modulusValue << 8) | BigUInt(byte)
|
|
82
|
+
}
|
|
83
|
+
self.modulus = BigInt(modulusValue)
|
|
84
|
+
let exponentBytes = [UInt8](exponent)
|
|
85
|
+
var exponentValue = BigUInt(0)
|
|
86
|
+
for byte in exponentBytes {
|
|
87
|
+
exponentValue = (exponentValue << 8) | BigUInt(byte)
|
|
88
|
+
}
|
|
89
|
+
self.exponent = BigInt(exponentValue)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Parse PKCS#1 format public key
|
|
93
|
+
static func fromPKCS1(_ publicKeyData: Data) -> ManualRSAPublicKey? {
|
|
94
|
+
// Parse ASN.1 DER encoded RSA public key
|
|
95
|
+
// Format: RSAPublicKey ::= SEQUENCE { modulus INTEGER, publicExponent INTEGER }
|
|
96
|
+
|
|
97
|
+
guard publicKeyData.count > 0 else {
|
|
98
|
+
return nil
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
let bytes = [UInt8](publicKeyData)
|
|
102
|
+
|
|
103
|
+
// Check for sequence tag (0x30)
|
|
104
|
+
guard bytes[0] == 0x30 else {
|
|
105
|
+
// Try direct modulus/exponent approach as fallback
|
|
106
|
+
if publicKeyData.count >= 3 {
|
|
107
|
+
// Assume this is a raw RSA public key with modulus + exponent
|
|
108
|
+
// Most common: modulus is 256 bytes (2048 bits), exponent is 3 bytes (0x010001 = 65537)
|
|
109
|
+
let modulusSize = publicKeyData.count - 3
|
|
110
|
+
if modulusSize > 0 {
|
|
111
|
+
let modulusData = publicKeyData.prefix(modulusSize)
|
|
112
|
+
let exponentData = publicKeyData.suffix(3)
|
|
113
|
+
return ManualRSAPublicKey(modulus: modulusData, exponent: exponentData)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return nil
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
var index = 1
|
|
121
|
+
|
|
122
|
+
// Skip length
|
|
123
|
+
if bytes[index] & 0x80 != 0 {
|
|
124
|
+
let lenBytes = Int(bytes[index] & 0x7F)
|
|
125
|
+
if (index + 1 + lenBytes) >= bytes.count {
|
|
126
|
+
return nil
|
|
127
|
+
}
|
|
128
|
+
index += 1 + lenBytes
|
|
129
|
+
} else {
|
|
130
|
+
index += 1
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Check for INTEGER tag for modulus (0x02)
|
|
134
|
+
if index >= bytes.count {
|
|
135
|
+
return nil
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
guard bytes[index] == 0x02 else {
|
|
139
|
+
return nil
|
|
140
|
+
}
|
|
141
|
+
index += 1
|
|
142
|
+
|
|
143
|
+
// Get modulus length
|
|
144
|
+
if index >= bytes.count {
|
|
145
|
+
return nil
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
var modulusLength = 0
|
|
149
|
+
if bytes[index] & 0x80 != 0 {
|
|
150
|
+
let lenBytes = Int(bytes[index] & 0x7F)
|
|
151
|
+
if (index + 1 + lenBytes) >= bytes.count {
|
|
152
|
+
return nil
|
|
153
|
+
}
|
|
154
|
+
index += 1
|
|
155
|
+
for i in 0..<lenBytes {
|
|
156
|
+
modulusLength = (modulusLength << 8) | Int(bytes[index + i])
|
|
157
|
+
}
|
|
158
|
+
index += lenBytes
|
|
159
|
+
} else {
|
|
160
|
+
modulusLength = Int(bytes[index])
|
|
161
|
+
index += 1
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Skip any leading zero in modulus (for unsigned integer)
|
|
165
|
+
if index < bytes.count && bytes[index] == 0x00 {
|
|
166
|
+
index += 1
|
|
167
|
+
modulusLength -= 1
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Extract modulus
|
|
171
|
+
if (index + modulusLength) > bytes.count {
|
|
172
|
+
return nil
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
let modulusData = Data(bytes[index..<(index + modulusLength)])
|
|
176
|
+
index += modulusLength
|
|
177
|
+
|
|
178
|
+
// Check for INTEGER tag for exponent
|
|
179
|
+
if index >= bytes.count {
|
|
180
|
+
return nil
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
guard bytes[index] == 0x02 else {
|
|
184
|
+
return nil
|
|
185
|
+
}
|
|
186
|
+
index += 1
|
|
187
|
+
|
|
188
|
+
// Get exponent length
|
|
189
|
+
if index >= bytes.count {
|
|
190
|
+
return nil
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
var exponentLength = 0
|
|
194
|
+
if bytes[index] & 0x80 != 0 {
|
|
195
|
+
let lenBytes = Int(bytes[index] & 0x7F)
|
|
196
|
+
if (index + 1 + lenBytes) >= bytes.count {
|
|
197
|
+
return nil
|
|
198
|
+
}
|
|
199
|
+
index += 1
|
|
200
|
+
for i in 0..<lenBytes {
|
|
201
|
+
exponentLength = (exponentLength << 8) | Int(bytes[index + i])
|
|
202
|
+
}
|
|
203
|
+
index += lenBytes
|
|
204
|
+
} else {
|
|
205
|
+
exponentLength = Int(bytes[index])
|
|
206
|
+
index += 1
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Extract exponent
|
|
210
|
+
if (index + exponentLength) > bytes.count {
|
|
211
|
+
return nil
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
let exponentData = Data(bytes[index..<(index + exponentLength)])
|
|
215
|
+
return ManualRSAPublicKey(modulus: modulusData, exponent: exponentData)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Decrypt data using raw RSA operation (c^d mod n)
|
|
219
|
+
func decrypt(_ encryptedData: Data) -> Data? {
|
|
220
|
+
// Create positive BigInt from encrypted data
|
|
221
|
+
let encryptedBytes = [UInt8](encryptedData)
|
|
222
|
+
var encryptedValue = BigUInt(0)
|
|
223
|
+
for byte in encryptedBytes {
|
|
224
|
+
encryptedValue = (encryptedValue << 8) | BigUInt(byte)
|
|
225
|
+
}
|
|
226
|
+
let encrypted = BigInt(encryptedValue)
|
|
227
|
+
|
|
228
|
+
// In Node.js:
|
|
229
|
+
// privateEncrypt uses the private key (d) to encrypt
|
|
230
|
+
// publicDecrypt uses the public key (e) to decrypt
|
|
231
|
+
// The operation we want is: ciphertext^e mod n
|
|
232
|
+
|
|
233
|
+
// RSA operation: c^e mod n
|
|
234
|
+
let decrypted = encrypted.manualPower(exponent, modulus: modulus)
|
|
235
|
+
|
|
236
|
+
// Convert to bytes with proper padding
|
|
237
|
+
guard let bigUIntValue = decrypted.magnitude as? BigUInt else {
|
|
238
|
+
return nil
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Convert BigUInt to bytes with padding
|
|
242
|
+
var resultBytes = [UInt8]()
|
|
243
|
+
var tempValue = bigUIntValue
|
|
244
|
+
while tempValue > 0 {
|
|
245
|
+
let byte = UInt8(tempValue & 0xFF)
|
|
246
|
+
resultBytes.insert(byte, at: 0) // Prepend to get big-endian
|
|
247
|
+
tempValue >>= 8
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Ensure we have at least 256 bytes (2048 bits) with leading zeros
|
|
251
|
+
let paddedBytes = [UInt8](repeating: 0, count: max(0, 256 - resultBytes.count)) + resultBytes
|
|
252
|
+
|
|
253
|
+
// For PKCS1 padding from Node.js privateEncrypt, the format is:
|
|
254
|
+
// 0x00 || 0x01 || PS || 0x00 || actual data
|
|
255
|
+
// where PS is a string of 0xFF bytes
|
|
256
|
+
|
|
257
|
+
// Check for privateEncrypt padding format (0x00 || 0x01 || PS || 0x00)
|
|
258
|
+
var startIndex = 0
|
|
259
|
+
if paddedBytes.count > 2 && paddedBytes[0] == 0x00 && paddedBytes[1] == 0x01 {
|
|
260
|
+
for i in 2..<paddedBytes.count {
|
|
261
|
+
if paddedBytes[i] == 0x00 {
|
|
262
|
+
startIndex = i + 1
|
|
263
|
+
break
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
if startIndex < paddedBytes.count {
|
|
268
|
+
let result = Data(paddedBytes[startIndex...])
|
|
269
|
+
return result
|
|
270
|
+
} else {
|
|
271
|
+
return Data(paddedBytes)
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -34,9 +34,7 @@ extension UserDefaults: ObjectSavable {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
func getObj<Object>(forKey: String, castTo type: Object.Type) throws -> Object where Object: Decodable {
|
|
37
|
-
// print("forKey", forKey)
|
|
38
37
|
guard let data: Data = data(forKey: forKey) else { throw ObjectSavableError.noValue }
|
|
39
|
-
// print("data", data)
|
|
40
38
|
let decoder: JSONDecoder = JSONDecoder()
|
|
41
39
|
do {
|
|
42
40
|
let object: Object = try decoder.decode(type, from: data)
|