@capgo/capacitor-updater 8.0.0 → 8.0.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/CapgoCapacitorUpdater.podspec +2 -2
- package/Package.swift +35 -0
- package/README.md +667 -206
- package/android/build.gradle +16 -11
- package/android/proguard-rules.pro +28 -0
- package/android/src/main/AndroidManifest.xml +0 -1
- package/android/src/main/java/ee/forgr/capacitor_updater/BundleInfo.java +134 -194
- package/android/src/main/java/ee/forgr/capacitor_updater/BundleStatus.java +23 -23
- package/android/src/main/java/ee/forgr/capacitor_updater/Callback.java +13 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdater.java +967 -1027
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdaterPlugin.java +1283 -1180
- package/android/src/main/java/ee/forgr/capacitor_updater/CryptoCipherV2.java +276 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/DataManager.java +28 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/DelayCondition.java +45 -48
- package/android/src/main/java/ee/forgr/capacitor_updater/DelayUntilNext.java +4 -4
- package/android/src/main/java/ee/forgr/capacitor_updater/DownloadService.java +440 -113
- package/android/src/main/java/ee/forgr/capacitor_updater/DownloadWorkerManager.java +101 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/InternalUtils.java +32 -0
- package/dist/docs.json +1316 -473
- package/dist/esm/definitions.d.ts +518 -248
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/index.d.ts +2 -2
- package/dist/esm/index.js +4 -4
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/web.d.ts +25 -41
- package/dist/esm/web.js +67 -35
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +67 -35
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +67 -35
- package/dist/plugin.js.map +1 -1
- package/ios/Plugin/CapacitorUpdater.swift +736 -361
- package/ios/Plugin/CapacitorUpdaterPlugin.swift +436 -136
- package/ios/Plugin/CryptoCipherV2.swift +310 -0
- package/ios/Plugin/InternalUtils.swift +258 -0
- package/package.json +33 -29
- package/android/src/main/java/ee/forgr/capacitor_updater/CryptoCipher.java +0 -153
- package/ios/Plugin/CapacitorUpdaterPlugin.h +0 -10
- package/ios/Plugin/CapacitorUpdaterPlugin.m +0 -27
- package/ios/Plugin/CryptoCipher.swift +0 -240
|
@@ -0,0 +1,310 @@
|
|
|
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 Foundation
|
|
8
|
+
import CommonCrypto
|
|
9
|
+
import CryptoKit
|
|
10
|
+
|
|
11
|
+
///
|
|
12
|
+
/// Constants
|
|
13
|
+
///
|
|
14
|
+
private enum CryptoCipherConstants {
|
|
15
|
+
static let rsaKeySizeInBits: NSNumber = 2048
|
|
16
|
+
static let aesAlgorithm: CCAlgorithm = CCAlgorithm(kCCAlgorithmAES)
|
|
17
|
+
static let aesOptions: CCOptions = CCOptions(kCCOptionPKCS7Padding)
|
|
18
|
+
static let rsaAlgorithm: SecKeyAlgorithm = .rsaEncryptionOAEPSHA256
|
|
19
|
+
}
|
|
20
|
+
///
|
|
21
|
+
/// The AES key. Contains both the initialization vector and secret key.
|
|
22
|
+
///
|
|
23
|
+
public struct AES128Key {
|
|
24
|
+
/// Initialization vector
|
|
25
|
+
private let iv: Data
|
|
26
|
+
private let aes128Key: Data
|
|
27
|
+
#if DEBUG
|
|
28
|
+
public var __debug_iv: Data { iv }
|
|
29
|
+
public var __debug_aes128Key: Data { aes128Key }
|
|
30
|
+
#endif
|
|
31
|
+
init(iv: Data, aes128Key: Data) {
|
|
32
|
+
self.iv = iv
|
|
33
|
+
self.aes128Key = aes128Key
|
|
34
|
+
}
|
|
35
|
+
///
|
|
36
|
+
/// Takes the data and uses the private key to decrypt it. Will call `CCCrypt` in CommonCrypto
|
|
37
|
+
/// and provide it `ivData` for the initialization vector. Will use cipher block chaining (CBC) as
|
|
38
|
+
/// the mode of operation.
|
|
39
|
+
///
|
|
40
|
+
/// Returns the decrypted data.
|
|
41
|
+
///
|
|
42
|
+
public func decrypt(data: Data) -> Data? {
|
|
43
|
+
let encryptedData: UnsafePointer<UInt8> = (data as NSData).bytes.bindMemory(to: UInt8.self, capacity: data.count)
|
|
44
|
+
let encryptedDataLength: Int = data.count
|
|
45
|
+
|
|
46
|
+
if let result: NSMutableData = NSMutableData(length: encryptedDataLength) {
|
|
47
|
+
let keyData: UnsafePointer<UInt8> = (self.aes128Key as NSData).bytes.bindMemory(to: UInt8.self, capacity: self.aes128Key.count)
|
|
48
|
+
let keyLength: size_t = size_t(self.aes128Key.count)
|
|
49
|
+
let ivData: UnsafePointer<UInt8> = (iv as NSData).bytes.bindMemory(to: UInt8.self, capacity: self.iv.count)
|
|
50
|
+
|
|
51
|
+
let decryptedData: UnsafeMutablePointer<UInt8> = UnsafeMutablePointer<UInt8>(result.mutableBytes.assumingMemoryBound(to: UInt8.self))
|
|
52
|
+
let decryptedDataLength: size_t = size_t(result.length)
|
|
53
|
+
|
|
54
|
+
var decryptedLength: size_t = 0
|
|
55
|
+
|
|
56
|
+
let status: CCCryptorStatus = CCCrypt(CCOperation(kCCDecrypt), CryptoCipherConstants.aesAlgorithm, CryptoCipherConstants.aesOptions, keyData, keyLength, ivData, encryptedData, encryptedDataLength, decryptedData, decryptedDataLength, &decryptedLength)
|
|
57
|
+
|
|
58
|
+
if Int32(status) == Int32(kCCSuccess) {
|
|
59
|
+
result.length = Int(decryptedLength)
|
|
60
|
+
return result as Data
|
|
61
|
+
} else {
|
|
62
|
+
return nil
|
|
63
|
+
}
|
|
64
|
+
} else {
|
|
65
|
+
return nil
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
///
|
|
71
|
+
/// The RSA public key.
|
|
72
|
+
///
|
|
73
|
+
public struct RSAPublicKey {
|
|
74
|
+
private let publicKey: SecKey
|
|
75
|
+
|
|
76
|
+
#if DEBUG
|
|
77
|
+
public var __debug_publicKey: SecKey { self.publicKey }
|
|
78
|
+
#endif
|
|
79
|
+
|
|
80
|
+
fileprivate init(publicKey: SecKey) {
|
|
81
|
+
self.publicKey = publicKey
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
///
|
|
85
|
+
/// Takes the data and uses the public key to decrypt it.
|
|
86
|
+
/// Returns the decrypted data.
|
|
87
|
+
///
|
|
88
|
+
public func decrypt(data: Data) -> Data? {
|
|
89
|
+
do {
|
|
90
|
+
guard let decryptedData = RSAPublicKey.decryptWithRSAKey(data, rsaKeyRef: self.publicKey, padding: SecPadding()) else {
|
|
91
|
+
throw CustomError.cannotDecryptSessionKey
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return decryptedData
|
|
95
|
+
} catch {
|
|
96
|
+
print("Error decrypting data: \(error)")
|
|
97
|
+
return nil
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
///
|
|
102
|
+
/// Allows you to load an RSA public key (i.e. one downloaded from the net).
|
|
103
|
+
///
|
|
104
|
+
public static func load(rsaPublicKey: String) -> RSAPublicKey? {
|
|
105
|
+
var pubKey: String = rsaPublicKey
|
|
106
|
+
pubKey = pubKey.replacingOccurrences(of: "-----BEGIN RSA PUBLIC KEY-----", with: "")
|
|
107
|
+
pubKey = pubKey.replacingOccurrences(of: "-----END RSA PUBLIC KEY-----", with: "")
|
|
108
|
+
pubKey = pubKey.replacingOccurrences(of: "\\n+", with: "", options: .regularExpression)
|
|
109
|
+
pubKey = pubKey.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
110
|
+
do {
|
|
111
|
+
guard let rsaPublicKeyData: Data = Data(base64Encoded: String(pubKey)) else {
|
|
112
|
+
throw CustomError.cannotDecode
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
guard let publicKey: SecKey = .loadPublicFromData(rsaPublicKeyData) else {
|
|
116
|
+
throw CustomError.cannotDecode
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return RSAPublicKey(publicKey: publicKey)
|
|
120
|
+
} catch {
|
|
121
|
+
print("Error load RSA: \(error)")
|
|
122
|
+
return nil
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// code is copied from here: https://github.com/btnguyen2k/swiftutils/blob/88494f4c635b6c6d42ef0fb30a7d666acd38c4fa/SwiftUtils/RSAUtils.swift#L393
|
|
127
|
+
public static func decryptWithRSAKey(_ encryptedData: Data, rsaKeyRef: SecKey, padding: SecPadding) -> Data? {
|
|
128
|
+
let blockSize = SecKeyGetBlockSize(rsaKeyRef)
|
|
129
|
+
let dataSize = encryptedData.count / MemoryLayout<UInt8>.size
|
|
130
|
+
|
|
131
|
+
var encryptedDataAsArray = [UInt8](repeating: 0, count: dataSize)
|
|
132
|
+
(encryptedData as NSData).getBytes(&encryptedDataAsArray, length: dataSize)
|
|
133
|
+
|
|
134
|
+
var decryptedData = [UInt8](repeating: 0, count: 0)
|
|
135
|
+
var idx = 0
|
|
136
|
+
while idx < encryptedDataAsArray.count {
|
|
137
|
+
var idxEnd = idx + blockSize
|
|
138
|
+
if idxEnd > encryptedDataAsArray.count {
|
|
139
|
+
idxEnd = encryptedDataAsArray.count
|
|
140
|
+
}
|
|
141
|
+
var chunkData = [UInt8](repeating: 0, count: blockSize)
|
|
142
|
+
for i in idx..<idxEnd {
|
|
143
|
+
chunkData[i-idx] = encryptedDataAsArray[i]
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
var decryptedDataBuffer = [UInt8](repeating: 0, count: blockSize)
|
|
147
|
+
var decryptedDataLength = blockSize
|
|
148
|
+
|
|
149
|
+
let status = SecKeyDecrypt(rsaKeyRef, padding, chunkData, idxEnd-idx, &decryptedDataBuffer, &decryptedDataLength)
|
|
150
|
+
if status != noErr {
|
|
151
|
+
return nil
|
|
152
|
+
}
|
|
153
|
+
let finalData = removePadding(decryptedDataBuffer)
|
|
154
|
+
decryptedData += finalData
|
|
155
|
+
|
|
156
|
+
idx += blockSize
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return Data(decryptedData)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// code is copied from here: https://github.com/btnguyen2k/swiftutils/blob/88494f4c635b6c6d42ef0fb30a7d666acd38c4fa/SwiftUtils/RSAUtils.swift#L429
|
|
163
|
+
private static func removePadding(_ data: [UInt8]) -> [UInt8] {
|
|
164
|
+
var idxFirstZero = -1
|
|
165
|
+
var idxNextZero = data.count
|
|
166
|
+
for i in 0..<data.count {
|
|
167
|
+
if data[i] == 0 {
|
|
168
|
+
if idxFirstZero < 0 {
|
|
169
|
+
idxFirstZero = i
|
|
170
|
+
} else {
|
|
171
|
+
idxNextZero = i
|
|
172
|
+
break
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if idxNextZero-idxFirstZero-1 == 0 {
|
|
177
|
+
idxNextZero = idxFirstZero
|
|
178
|
+
idxFirstZero = -1
|
|
179
|
+
}
|
|
180
|
+
var newData = [UInt8](repeating: 0, count: idxNextZero-idxFirstZero-1)
|
|
181
|
+
for i in idxFirstZero+1..<idxNextZero {
|
|
182
|
+
newData[i-idxFirstZero-1] = data[i]
|
|
183
|
+
}
|
|
184
|
+
return newData
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
fileprivate extension SecKey {
|
|
189
|
+
func exportToData() -> Data? {
|
|
190
|
+
var error: Unmanaged<CFError>?
|
|
191
|
+
if let cfData: CFData = SecKeyCopyExternalRepresentation(self, &error) {
|
|
192
|
+
if error != nil {
|
|
193
|
+
return nil
|
|
194
|
+
} else {
|
|
195
|
+
return cfData as Data
|
|
196
|
+
}
|
|
197
|
+
} else {
|
|
198
|
+
return nil
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
static func loadPublicFromData(_ data: Data) -> SecKey? {
|
|
202
|
+
let keyDict: [NSObject: NSObject] = [
|
|
203
|
+
kSecAttrKeyType: kSecAttrKeyTypeRSA,
|
|
204
|
+
kSecAttrKeyClass: kSecAttrKeyClassPublic,
|
|
205
|
+
kSecAttrKeySizeInBits: CryptoCipherConstants.rsaKeySizeInBits
|
|
206
|
+
]
|
|
207
|
+
return SecKeyCreateWithData(data as NSData, keyDict as CFDictionary, nil)
|
|
208
|
+
}
|
|
209
|
+
static func loadPrivateFromData(_ data: Data) -> SecKey? {
|
|
210
|
+
let keyDict: [NSObject: NSObject] = [
|
|
211
|
+
kSecAttrKeyType: kSecAttrKeyTypeRSA,
|
|
212
|
+
kSecAttrKeyClass: kSecAttrKeyClassPrivate,
|
|
213
|
+
kSecAttrKeySizeInBits: CryptoCipherConstants.rsaKeySizeInBits
|
|
214
|
+
]
|
|
215
|
+
return SecKeyCreateWithData(data as CFData, keyDict as CFDictionary, nil)
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
public struct CryptoCipherV2 {
|
|
220
|
+
|
|
221
|
+
public static func decryptChecksum(checksum: String, publicKey: String, version: String) throws -> String {
|
|
222
|
+
if publicKey.isEmpty {
|
|
223
|
+
return checksum
|
|
224
|
+
}
|
|
225
|
+
do {
|
|
226
|
+
let checksumBytes: Data = Data(base64Encoded: checksum)!
|
|
227
|
+
guard let rsaPublicKey: RSAPublicKey = .load(rsaPublicKey: publicKey) else {
|
|
228
|
+
print("cannot decode publicKey", publicKey)
|
|
229
|
+
throw CustomError.cannotDecode
|
|
230
|
+
}
|
|
231
|
+
guard let decryptedChecksum = rsaPublicKey.decrypt(data: checksumBytes) else {
|
|
232
|
+
throw NSError(domain: "Failed to decrypt session key data", code: 2, userInfo: nil)
|
|
233
|
+
}
|
|
234
|
+
return decryptedChecksum.base64EncodedString()
|
|
235
|
+
} catch {
|
|
236
|
+
print("\(CapacitorUpdater.TAG) Cannot decrypt checksum: \(checksum)", error)
|
|
237
|
+
throw CustomError.cannotDecode
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
public static func calcChecksum(filePath: URL) -> String {
|
|
241
|
+
let bufferSize = 1024 * 1024 * 5 // 5 MB
|
|
242
|
+
var sha256 = SHA256()
|
|
243
|
+
|
|
244
|
+
do {
|
|
245
|
+
let fileHandle = try FileHandle(forReadingFrom: filePath)
|
|
246
|
+
defer {
|
|
247
|
+
fileHandle.closeFile()
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
while autoreleasepool(invoking: {
|
|
251
|
+
let fileData = fileHandle.readData(ofLength: bufferSize)
|
|
252
|
+
if fileData.count > 0 {
|
|
253
|
+
sha256.update(data: fileData)
|
|
254
|
+
return true // Continue
|
|
255
|
+
} else {
|
|
256
|
+
return false // End of file
|
|
257
|
+
}
|
|
258
|
+
}) {}
|
|
259
|
+
|
|
260
|
+
let digest = sha256.finalize()
|
|
261
|
+
return digest.compactMap { String(format: "%02x", $0) }.joined()
|
|
262
|
+
} catch {
|
|
263
|
+
print("\(CapacitorUpdater.TAG) Cannot get checksum: \(filePath.path)", error)
|
|
264
|
+
return ""
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
public static func decryptFile(filePath: URL, publicKey: String, sessionKey: String, version: String) throws {
|
|
269
|
+
if publicKey.isEmpty || sessionKey.isEmpty || sessionKey.components(separatedBy: ":").count != 2 {
|
|
270
|
+
print("\(CapacitorUpdater.TAG) Cannot find public key or sessionKey")
|
|
271
|
+
return
|
|
272
|
+
}
|
|
273
|
+
do {
|
|
274
|
+
guard let rsaPublicKey: RSAPublicKey = .load(rsaPublicKey: publicKey) else {
|
|
275
|
+
print("cannot decode publicKey", publicKey)
|
|
276
|
+
throw CustomError.cannotDecode
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
let sessionKeyArray: [String] = sessionKey.components(separatedBy: ":")
|
|
280
|
+
guard let ivData: Data = Data(base64Encoded: sessionKeyArray[0]) else {
|
|
281
|
+
print("cannot decode sessionKey", sessionKey)
|
|
282
|
+
throw CustomError.cannotDecode
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
guard let sessionKeyDataEncrypted = Data(base64Encoded: sessionKeyArray[1]) else {
|
|
286
|
+
throw NSError(domain: "Invalid session key data", code: 1, userInfo: nil)
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
guard let sessionKeyDataDecrypted = rsaPublicKey.decrypt(data: sessionKeyDataEncrypted) else {
|
|
290
|
+
throw NSError(domain: "Failed to decrypt session key data", code: 2, userInfo: nil)
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
let aesPrivateKey = AES128Key(iv: ivData, aes128Key: sessionKeyDataDecrypted)
|
|
294
|
+
|
|
295
|
+
guard let encryptedData = try? Data(contentsOf: filePath) else {
|
|
296
|
+
throw NSError(domain: "Failed to read encrypted data", code: 3, userInfo: nil)
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
guard let decryptedData = aesPrivateKey.decrypt(data: encryptedData) else {
|
|
300
|
+
throw NSError(domain: "Failed to decrypt data", code: 4, userInfo: nil)
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
try decryptedData.write(to: filePath)
|
|
304
|
+
|
|
305
|
+
} catch {
|
|
306
|
+
print("\(CapacitorUpdater.TAG) Cannot decode: \(filePath.path)", error)
|
|
307
|
+
throw CustomError.cannotDecode
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
@@ -0,0 +1,258 @@
|
|
|
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 Foundation
|
|
8
|
+
|
|
9
|
+
extension Collection {
|
|
10
|
+
subscript(safe index: Index) -> Element? {
|
|
11
|
+
return indices.contains(index) ? self[index] : nil
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
extension URL {
|
|
15
|
+
var isDirectory: Bool {
|
|
16
|
+
(try? resourceValues(forKeys: [.isDirectoryKey]))?.isDirectory == true
|
|
17
|
+
}
|
|
18
|
+
var exist: Bool {
|
|
19
|
+
return FileManager().fileExists(atPath: self.path)
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
struct SetChannelDec: Decodable {
|
|
23
|
+
let status: String?
|
|
24
|
+
let error: String?
|
|
25
|
+
let message: String?
|
|
26
|
+
}
|
|
27
|
+
public class SetChannel: NSObject {
|
|
28
|
+
var status: String = ""
|
|
29
|
+
var error: String = ""
|
|
30
|
+
var message: String = ""
|
|
31
|
+
}
|
|
32
|
+
extension SetChannel {
|
|
33
|
+
func toDict() -> [String: Any] {
|
|
34
|
+
var dict: [String: Any] = [String: Any]()
|
|
35
|
+
let otherSelf: Mirror = Mirror(reflecting: self)
|
|
36
|
+
for child: Mirror.Child in otherSelf.children {
|
|
37
|
+
if let key: String = child.label {
|
|
38
|
+
dict[key] = child.value
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return dict
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
struct GetChannelDec: Decodable {
|
|
45
|
+
let channel: String?
|
|
46
|
+
let status: String?
|
|
47
|
+
let error: String?
|
|
48
|
+
let message: String?
|
|
49
|
+
let allowSet: Bool?
|
|
50
|
+
}
|
|
51
|
+
public class GetChannel: NSObject {
|
|
52
|
+
var channel: String = ""
|
|
53
|
+
var status: String = ""
|
|
54
|
+
var error: String = ""
|
|
55
|
+
var message: String = ""
|
|
56
|
+
var allowSet: Bool = true
|
|
57
|
+
}
|
|
58
|
+
extension GetChannel {
|
|
59
|
+
func toDict() -> [String: Any] {
|
|
60
|
+
var dict: [String: Any] = [String: Any]()
|
|
61
|
+
let otherSelf: Mirror = Mirror(reflecting: self)
|
|
62
|
+
for child: Mirror.Child in otherSelf.children {
|
|
63
|
+
if let key: String = child.label {
|
|
64
|
+
dict[key] = child.value
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return dict
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
struct InfoObject: Codable {
|
|
71
|
+
let platform: String?
|
|
72
|
+
let device_id: String?
|
|
73
|
+
let app_id: String?
|
|
74
|
+
let custom_id: String?
|
|
75
|
+
let version_build: String?
|
|
76
|
+
let version_code: String?
|
|
77
|
+
let version_os: String?
|
|
78
|
+
var version_name: String?
|
|
79
|
+
var old_version_name: String?
|
|
80
|
+
let plugin_version: String?
|
|
81
|
+
let is_emulator: Bool?
|
|
82
|
+
let is_prod: Bool?
|
|
83
|
+
var action: String?
|
|
84
|
+
var channel: String?
|
|
85
|
+
var defaultChannel: String?
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
public struct ManifestEntry: Codable {
|
|
89
|
+
let file_name: String?
|
|
90
|
+
let file_hash: String?
|
|
91
|
+
let download_url: String?
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
extension ManifestEntry {
|
|
95
|
+
func toDict() -> [String: Any] {
|
|
96
|
+
var dict: [String: Any] = [String: Any]()
|
|
97
|
+
let mirror = Mirror(reflecting: self)
|
|
98
|
+
for child in mirror.children {
|
|
99
|
+
if let key = child.label {
|
|
100
|
+
dict[key] = child.value
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return dict
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
struct AppVersionDec: Decodable {
|
|
108
|
+
let version: String?
|
|
109
|
+
let checksum: String?
|
|
110
|
+
let url: String?
|
|
111
|
+
let message: String?
|
|
112
|
+
let error: String?
|
|
113
|
+
let session_key: String?
|
|
114
|
+
let major: Bool?
|
|
115
|
+
let data: [String: String]?
|
|
116
|
+
let manifest: [ManifestEntry]?
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
public class AppVersion: NSObject {
|
|
120
|
+
var version: String = ""
|
|
121
|
+
var checksum: String = ""
|
|
122
|
+
var url: String = ""
|
|
123
|
+
var message: String?
|
|
124
|
+
var error: String?
|
|
125
|
+
var sessionKey: String?
|
|
126
|
+
var major: Bool?
|
|
127
|
+
var data: [String: String]?
|
|
128
|
+
var manifest: [ManifestEntry]?
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
extension AppVersion {
|
|
132
|
+
func toDict() -> [String: Any] {
|
|
133
|
+
var dict: [String: Any] = [String: Any]()
|
|
134
|
+
let otherSelf: Mirror = Mirror(reflecting: self)
|
|
135
|
+
for child: Mirror.Child in otherSelf.children {
|
|
136
|
+
if let key: String = child.label {
|
|
137
|
+
if key == "manifest", let manifestEntries = child.value as? [ManifestEntry] {
|
|
138
|
+
dict[key] = manifestEntries.map { $0.toDict() }
|
|
139
|
+
} else {
|
|
140
|
+
dict[key] = child.value
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return dict
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
extension OperatingSystemVersion {
|
|
149
|
+
func getFullVersion(separator: String = ".") -> String {
|
|
150
|
+
return "\(majorVersion)\(separator)\(minorVersion)\(separator)\(patchVersion)"
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
extension Bundle {
|
|
154
|
+
var versionName: String? {
|
|
155
|
+
return infoDictionary?["CFBundleShortVersionString"] as? String
|
|
156
|
+
}
|
|
157
|
+
var versionCode: String? {
|
|
158
|
+
return infoDictionary?["CFBundleVersion"] as? String
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
extension ISO8601DateFormatter {
|
|
163
|
+
convenience init(_ formatOptions: Options) {
|
|
164
|
+
self.init()
|
|
165
|
+
self.formatOptions = formatOptions
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
extension Formatter {
|
|
169
|
+
static let iso8601withFractionalSeconds: ISO8601DateFormatter = ISO8601DateFormatter([.withInternetDateTime, .withFractionalSeconds])
|
|
170
|
+
}
|
|
171
|
+
extension Date {
|
|
172
|
+
var iso8601withFractionalSeconds: String { return Formatter.iso8601withFractionalSeconds.string(from: self) }
|
|
173
|
+
}
|
|
174
|
+
extension String {
|
|
175
|
+
|
|
176
|
+
var fileURL: URL {
|
|
177
|
+
return URL(fileURLWithPath: self)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
var lastPathComponent: String {
|
|
181
|
+
get {
|
|
182
|
+
return fileURL.lastPathComponent
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
var iso8601withFractionalSeconds: Date? {
|
|
186
|
+
return Formatter.iso8601withFractionalSeconds.date(from: self)
|
|
187
|
+
}
|
|
188
|
+
func trim(using characterSet: CharacterSet = .whitespacesAndNewlines) -> String {
|
|
189
|
+
return trimmingCharacters(in: characterSet)
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
enum CustomError: Error {
|
|
194
|
+
// Throw when an unzip fail
|
|
195
|
+
case cannotUnzip
|
|
196
|
+
case cannotWrite
|
|
197
|
+
case cannotDecode
|
|
198
|
+
case cannotUnflat
|
|
199
|
+
case cannotCreateDirectory
|
|
200
|
+
case cannotDeleteDirectory
|
|
201
|
+
case cannotDecryptSessionKey
|
|
202
|
+
case invalidBase64
|
|
203
|
+
|
|
204
|
+
// Throw in all other cases
|
|
205
|
+
case unexpected(code: Int)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
extension CustomError: LocalizedError {
|
|
209
|
+
public var errorDescription: String? {
|
|
210
|
+
switch self {
|
|
211
|
+
case .cannotUnzip:
|
|
212
|
+
return NSLocalizedString(
|
|
213
|
+
"The file cannot be unzip",
|
|
214
|
+
comment: "Invalid zip"
|
|
215
|
+
)
|
|
216
|
+
case .cannotCreateDirectory:
|
|
217
|
+
return NSLocalizedString(
|
|
218
|
+
"The folder cannot be created",
|
|
219
|
+
comment: "Invalid folder"
|
|
220
|
+
)
|
|
221
|
+
case .cannotDeleteDirectory:
|
|
222
|
+
return NSLocalizedString(
|
|
223
|
+
"The folder cannot be deleted",
|
|
224
|
+
comment: "Invalid folder"
|
|
225
|
+
)
|
|
226
|
+
case .cannotUnflat:
|
|
227
|
+
return NSLocalizedString(
|
|
228
|
+
"The file cannot be unflat",
|
|
229
|
+
comment: "Invalid folder"
|
|
230
|
+
)
|
|
231
|
+
case .unexpected:
|
|
232
|
+
return NSLocalizedString(
|
|
233
|
+
"An unexpected error occurred.",
|
|
234
|
+
comment: "Unexpected Error"
|
|
235
|
+
)
|
|
236
|
+
case .cannotDecode:
|
|
237
|
+
return NSLocalizedString(
|
|
238
|
+
"Decoding the zip failed with this key",
|
|
239
|
+
comment: "Invalid public key"
|
|
240
|
+
)
|
|
241
|
+
case .cannotWrite:
|
|
242
|
+
return NSLocalizedString(
|
|
243
|
+
"Cannot write to the destination",
|
|
244
|
+
comment: "Invalid destination"
|
|
245
|
+
)
|
|
246
|
+
case .cannotDecryptSessionKey:
|
|
247
|
+
return NSLocalizedString(
|
|
248
|
+
"Decrypting the session key failed",
|
|
249
|
+
comment: "Invalid session key"
|
|
250
|
+
)
|
|
251
|
+
case .invalidBase64:
|
|
252
|
+
return NSLocalizedString(
|
|
253
|
+
"Decrypting the base64 failed",
|
|
254
|
+
comment: "Invalid checksum key"
|
|
255
|
+
)
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@capgo/capacitor-updater",
|
|
3
|
-
"version": "8.0.
|
|
4
|
-
"packageManager": "pnpm@8.5.1",
|
|
3
|
+
"version": "8.0.1",
|
|
5
4
|
"license": "MPL-2.0",
|
|
6
5
|
"description": "Live update for capacitor apps",
|
|
7
6
|
"main": "dist/plugin.cjs.js",
|
|
@@ -10,10 +9,12 @@
|
|
|
10
9
|
"unpkg": "dist/plugin.js",
|
|
11
10
|
"files": [
|
|
12
11
|
"android/src/main/",
|
|
12
|
+
"android/proguard-rules.pro",
|
|
13
13
|
"android/build.gradle",
|
|
14
14
|
"dist/",
|
|
15
15
|
"ios/Plugin/",
|
|
16
|
-
"CapgoCapacitorUpdater.podspec"
|
|
16
|
+
"CapgoCapacitorUpdater.podspec",
|
|
17
|
+
"Package.swift"
|
|
17
18
|
],
|
|
18
19
|
"author": "Martin Donadieu",
|
|
19
20
|
"repository": {
|
|
@@ -25,25 +26,29 @@
|
|
|
25
26
|
},
|
|
26
27
|
"keywords": [
|
|
27
28
|
"capacitor",
|
|
28
|
-
"
|
|
29
|
-
"OTA",
|
|
30
|
-
"manual update",
|
|
29
|
+
"live updates",
|
|
31
30
|
"live update",
|
|
31
|
+
"updates",
|
|
32
32
|
"auto update",
|
|
33
|
+
"manual update",
|
|
34
|
+
"capgo",
|
|
35
|
+
"plugin",
|
|
36
|
+
"OTA",
|
|
33
37
|
"ionic",
|
|
34
38
|
"appflow alternative",
|
|
35
|
-
"
|
|
39
|
+
"capawesome alternative",
|
|
36
40
|
"native"
|
|
37
41
|
],
|
|
38
42
|
"scripts": {
|
|
39
43
|
"verify": "npm run verify:ios && npm run verify:android && npm run verify:web",
|
|
40
44
|
"verify:ios": "cd ios && pod install && xcodebuild -workspace Plugin.xcworkspace -scheme Plugin && cd ..",
|
|
45
|
+
"verify:iosSPM": "xcodebuild -workspace ios/Plugin.xcworkspace -scheme Plugin -destination generic/platform=iOS",
|
|
41
46
|
"verify:android": "cd android && ./gradlew clean build test && cd ..",
|
|
42
47
|
"verify:web": "npm run build",
|
|
43
48
|
"lint": "npm run eslint && npm run prettier -- --check && npm run swiftlint -- lint",
|
|
44
49
|
"fmt": "npm run eslint -- --fix && npm run prettier -- --write && npm run swiftlint -- --autocorrect --format",
|
|
45
|
-
"eslint": "eslint . --ext ts",
|
|
46
|
-
"prettier": "prettier
|
|
50
|
+
"eslint": "eslint . --ext .ts",
|
|
51
|
+
"prettier": "prettier \"**/*.{css,html,ts,js,java}\" --plugin=prettier-plugin-java",
|
|
47
52
|
"swiftlint": "node-swiftlint",
|
|
48
53
|
"docgen": "docgen --api CapacitorUpdaterPlugin --output-readme README.md --output-json dist/docs.json",
|
|
49
54
|
"docgen:api": "docgen --api CapacitorUpdaterPlugin --output-readme api.md --output-json dist/docs.json && awk '{sub(/###/,\"##\")}1' api.md > temp.txt && mv temp.txt api.md",
|
|
@@ -53,28 +58,27 @@
|
|
|
53
58
|
"prepublishOnly": "npm run build"
|
|
54
59
|
},
|
|
55
60
|
"devDependencies": {
|
|
56
|
-
"@capacitor/android": "^
|
|
57
|
-
"@capacitor/cli": "^
|
|
58
|
-
"@capacitor/core": "^
|
|
59
|
-
"@capacitor/docgen": "^0.
|
|
60
|
-
"@capacitor/ios": "^
|
|
61
|
-
"@ionic/eslint-config": "^0.
|
|
62
|
-
"@ionic/prettier-config": "^
|
|
63
|
-
"@ionic/swiftlint-config": "^
|
|
64
|
-
"@types/node": "^
|
|
65
|
-
"
|
|
66
|
-
"
|
|
67
|
-
"
|
|
68
|
-
"
|
|
69
|
-
"prettier": "^2.
|
|
70
|
-
"
|
|
71
|
-
"
|
|
72
|
-
"
|
|
73
|
-
"
|
|
74
|
-
"typescript": "^5.0.4"
|
|
61
|
+
"@capacitor/android": "^7.0.0",
|
|
62
|
+
"@capacitor/cli": "^7.0.0",
|
|
63
|
+
"@capacitor/core": "^7.0.0",
|
|
64
|
+
"@capacitor/docgen": "^0.3.0",
|
|
65
|
+
"@capacitor/ios": "^7.0.0",
|
|
66
|
+
"@ionic/eslint-config": "^0.4.0",
|
|
67
|
+
"@ionic/prettier-config": "^4.0.0",
|
|
68
|
+
"@ionic/swiftlint-config": "^2.0.0",
|
|
69
|
+
"@types/node": "^22.13.1",
|
|
70
|
+
"eslint": "^8.57.0",
|
|
71
|
+
"eslint-plugin-import": "^2.31.0",
|
|
72
|
+
"husky": "^9.1.7",
|
|
73
|
+
"prettier": "^3.4.2",
|
|
74
|
+
"prettier-plugin-java": "^2.6.7",
|
|
75
|
+
"rimraf": "^6.0.1",
|
|
76
|
+
"rollup": "^4.34.6",
|
|
77
|
+
"swiftlint": "^2.0.0",
|
|
78
|
+
"typescript": "^5.7.3"
|
|
75
79
|
},
|
|
76
80
|
"peerDependencies": {
|
|
77
|
-
"@capacitor/core": "
|
|
81
|
+
"@capacitor/core": ">=7.0.0"
|
|
78
82
|
},
|
|
79
83
|
"prettier": "@ionic/prettier-config",
|
|
80
84
|
"swiftlint": "@ionic/swiftlint-config",
|