@capgo/capacitor-updater 4.42.0 → 4.43.5

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.
Files changed (67) hide show
  1. package/CapgoCapacitorUpdater.podspec +7 -5
  2. package/Package.swift +40 -0
  3. package/README.md +1913 -303
  4. package/android/build.gradle +41 -8
  5. package/android/proguard-rules.pro +45 -0
  6. package/android/src/main/AndroidManifest.xml +1 -3
  7. package/android/src/main/java/ee/forgr/capacitor_updater/AppLifecycleObserver.java +88 -0
  8. package/android/src/main/java/ee/forgr/capacitor_updater/BundleInfo.java +223 -195
  9. package/android/src/main/java/ee/forgr/capacitor_updater/BundleStatus.java +23 -23
  10. package/android/src/main/java/ee/forgr/capacitor_updater/Callback.java +13 -0
  11. package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdaterPlugin.java +2720 -1242
  12. package/android/src/main/java/ee/forgr/capacitor_updater/CapgoUpdater.java +1854 -0
  13. package/android/src/main/java/ee/forgr/capacitor_updater/CryptoCipher.java +359 -121
  14. package/android/src/main/java/ee/forgr/capacitor_updater/DataManager.java +28 -0
  15. package/android/src/main/java/ee/forgr/capacitor_updater/DelayCondition.java +44 -49
  16. package/android/src/main/java/ee/forgr/capacitor_updater/DelayUntilNext.java +4 -4
  17. package/android/src/main/java/ee/forgr/capacitor_updater/DelayUpdateUtils.java +296 -0
  18. package/android/src/main/java/ee/forgr/capacitor_updater/DeviceIdHelper.java +215 -0
  19. package/android/src/main/java/ee/forgr/capacitor_updater/DownloadService.java +858 -117
  20. package/android/src/main/java/ee/forgr/capacitor_updater/DownloadWorkerManager.java +156 -0
  21. package/android/src/main/java/ee/forgr/capacitor_updater/InternalUtils.java +45 -0
  22. package/android/src/main/java/ee/forgr/capacitor_updater/Logger.java +360 -0
  23. package/android/src/main/java/ee/forgr/capacitor_updater/ShakeDetector.java +72 -0
  24. package/android/src/main/java/ee/forgr/capacitor_updater/ShakeMenu.java +603 -0
  25. package/dist/docs.json +3022 -765
  26. package/dist/esm/definitions.d.ts +1717 -198
  27. package/dist/esm/definitions.js +103 -1
  28. package/dist/esm/definitions.js.map +1 -1
  29. package/dist/esm/history.d.ts +1 -0
  30. package/dist/esm/history.js +283 -0
  31. package/dist/esm/history.js.map +1 -0
  32. package/dist/esm/index.d.ts +3 -2
  33. package/dist/esm/index.js +5 -4
  34. package/dist/esm/index.js.map +1 -1
  35. package/dist/esm/web.d.ts +43 -42
  36. package/dist/esm/web.js +122 -37
  37. package/dist/esm/web.js.map +1 -1
  38. package/dist/plugin.cjs.js +512 -37
  39. package/dist/plugin.cjs.js.map +1 -1
  40. package/dist/plugin.js +512 -37
  41. package/dist/plugin.js.map +1 -1
  42. package/ios/Sources/CapacitorUpdaterPlugin/AES.swift +87 -0
  43. package/ios/Sources/CapacitorUpdaterPlugin/BigInt.swift +55 -0
  44. package/ios/Sources/CapacitorUpdaterPlugin/BundleInfo.swift +177 -0
  45. package/ios/{Plugin → Sources/CapacitorUpdaterPlugin}/BundleStatus.swift +12 -12
  46. package/ios/Sources/CapacitorUpdaterPlugin/CapacitorUpdaterPlugin.swift +2020 -0
  47. package/ios/Sources/CapacitorUpdaterPlugin/CapgoUpdater.swift +1959 -0
  48. package/ios/Sources/CapacitorUpdaterPlugin/CryptoCipher.swift +313 -0
  49. package/ios/Sources/CapacitorUpdaterPlugin/DelayUpdateUtils.swift +257 -0
  50. package/ios/Sources/CapacitorUpdaterPlugin/DeviceIdHelper.swift +120 -0
  51. package/ios/Sources/CapacitorUpdaterPlugin/InternalUtils.swift +392 -0
  52. package/ios/Sources/CapacitorUpdaterPlugin/Logger.swift +310 -0
  53. package/ios/Sources/CapacitorUpdaterPlugin/RSA.swift +274 -0
  54. package/ios/Sources/CapacitorUpdaterPlugin/ShakeMenu.swift +441 -0
  55. package/ios/{Plugin → Sources/CapacitorUpdaterPlugin}/UserDefaultsExtension.swift +1 -2
  56. package/package.json +49 -41
  57. package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdater.java +0 -1131
  58. package/ios/Plugin/BundleInfo.swift +0 -113
  59. package/ios/Plugin/CapacitorUpdater.swift +0 -850
  60. package/ios/Plugin/CapacitorUpdaterPlugin.h +0 -10
  61. package/ios/Plugin/CapacitorUpdaterPlugin.m +0 -27
  62. package/ios/Plugin/CapacitorUpdaterPlugin.swift +0 -678
  63. package/ios/Plugin/CryptoCipher.swift +0 -240
  64. /package/{LICENCE → LICENSE} +0 -0
  65. /package/ios/{Plugin → Sources/CapacitorUpdaterPlugin}/DelayCondition.swift +0 -0
  66. /package/ios/{Plugin → Sources/CapacitorUpdaterPlugin}/DelayUntilNext.swift +0 -0
  67. /package/ios/{Plugin → Sources/CapacitorUpdaterPlugin}/Info.plist +0 -0
@@ -1,850 +0,0 @@
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 SSZipArchive
9
- import Alamofire
10
- import zlib
11
-
12
- extension URL {
13
- var isDirectory: Bool {
14
- (try? resourceValues(forKeys: [.isDirectoryKey]))?.isDirectory == true
15
- }
16
- var exist: Bool {
17
- return FileManager().fileExists(atPath: self.path)
18
- }
19
- }
20
- extension Date {
21
- func adding(minutes: Int) -> Date {
22
- return Calendar.current.date(byAdding: .minute, value: minutes, to: self)!
23
- }
24
- }
25
- struct SetChannelDec: Decodable {
26
- let status: String?
27
- let error: String?
28
- let message: String?
29
- }
30
- public class SetChannel: NSObject {
31
- var status: String = ""
32
- var error: String = ""
33
- var message: String = ""
34
- }
35
- extension SetChannel {
36
- func toDict() -> [String: Any] {
37
- var dict: [String: Any] = [String: Any]()
38
- let otherSelf: Mirror = Mirror(reflecting: self)
39
- for child: Mirror.Child in otherSelf.children {
40
- if let key: String = child.label {
41
- dict[key] = child.value
42
- }
43
- }
44
- return dict
45
- }
46
- }
47
- struct GetChannelDec: Decodable {
48
- let channel: String?
49
- let status: String?
50
- let error: String?
51
- let message: String?
52
- let allowSet: Bool?
53
- }
54
- public class GetChannel: NSObject {
55
- var channel: String = ""
56
- var status: String = ""
57
- var error: String = ""
58
- var message: String = ""
59
- var allowSet: Bool = true
60
- }
61
- extension GetChannel {
62
- func toDict() -> [String: Any] {
63
- var dict: [String: Any] = [String: Any]()
64
- let otherSelf: Mirror = Mirror(reflecting: self)
65
- for child: Mirror.Child in otherSelf.children {
66
- if let key: String = child.label {
67
- dict[key] = child.value
68
- }
69
- }
70
- return dict
71
- }
72
- }
73
- struct InfoObject: Codable {
74
- let platform: String?
75
- let device_id: String?
76
- let app_id: String?
77
- let custom_id: String?
78
- let version_build: String?
79
- let version_code: String?
80
- let version_os: String?
81
- let version_name: String?
82
- let plugin_version: String?
83
- let is_emulator: Bool?
84
- let is_prod: Bool?
85
- var action: String?
86
- var channel: String?
87
- }
88
- struct AppVersionDec: Decodable {
89
- let version: String?
90
- let checksum: String?
91
- let url: String?
92
- let message: String?
93
- let error: String?
94
- let session_key: String?
95
- let major: Bool?
96
- }
97
- public class AppVersion: NSObject {
98
- var version: String = ""
99
- var checksum: String = ""
100
- var url: String = ""
101
- var message: String?
102
- var error: String?
103
- var sessionKey: String?
104
- var major: Bool?
105
- }
106
-
107
- extension AppVersion {
108
- func toDict() -> [String: Any] {
109
- var dict: [String: Any] = [String: Any]()
110
- let otherSelf: Mirror = Mirror(reflecting: self)
111
- for child: Mirror.Child in otherSelf.children {
112
- if let key: String = child.label {
113
- dict[key] = child.value
114
- }
115
- }
116
- return dict
117
- }
118
- }
119
-
120
- extension OperatingSystemVersion {
121
- func getFullVersion(separator: String = ".") -> String {
122
- return "\(majorVersion)\(separator)\(minorVersion)\(separator)\(patchVersion)"
123
- }
124
- }
125
- extension Bundle {
126
- var versionName: String? {
127
- return infoDictionary?["CFBundleShortVersionString"] as? String
128
- }
129
- var versionCode: String? {
130
- return infoDictionary?["CFBundleVersion"] as? String
131
- }
132
- }
133
-
134
- extension ISO8601DateFormatter {
135
- convenience init(_ formatOptions: Options) {
136
- self.init()
137
- self.formatOptions = formatOptions
138
- }
139
- }
140
- extension Formatter {
141
- static let iso8601withFractionalSeconds: ISO8601DateFormatter = ISO8601DateFormatter([.withInternetDateTime, .withFractionalSeconds])
142
- }
143
- extension Date {
144
- var iso8601withFractionalSeconds: String { return Formatter.iso8601withFractionalSeconds.string(from: self) }
145
- }
146
- extension String {
147
-
148
- var fileURL: URL {
149
- return URL(fileURLWithPath: self)
150
- }
151
-
152
- var lastPathComponent: String {
153
- get {
154
- return fileURL.lastPathComponent
155
- }
156
- }
157
- var iso8601withFractionalSeconds: Date? {
158
- return Formatter.iso8601withFractionalSeconds.date(from: self)
159
- }
160
- func trim(using characterSet: CharacterSet = .whitespacesAndNewlines) -> String {
161
- return trimmingCharacters(in: characterSet)
162
- }
163
- }
164
-
165
- enum CustomError: Error {
166
- // Throw when an unzip fail
167
- case cannotUnzip
168
- case cannotWrite
169
- case cannotDecode
170
- case cannotUnflat
171
- case cannotCreateDirectory
172
- case cannotDeleteDirectory
173
-
174
- // Throw in all other cases
175
- case unexpected(code: Int)
176
- }
177
-
178
- extension CustomError: LocalizedError {
179
- public var errorDescription: String? {
180
- switch self {
181
- case .cannotUnzip:
182
- return NSLocalizedString(
183
- "The file cannot be unzip",
184
- comment: "Invalid zip"
185
- )
186
- case .cannotCreateDirectory:
187
- return NSLocalizedString(
188
- "The folder cannot be created",
189
- comment: "Invalid folder"
190
- )
191
- case .cannotDeleteDirectory:
192
- return NSLocalizedString(
193
- "The folder cannot be deleted",
194
- comment: "Invalid folder"
195
- )
196
- case .cannotUnflat:
197
- return NSLocalizedString(
198
- "The file cannot be unflat",
199
- comment: "Invalid folder"
200
- )
201
- case .unexpected:
202
- return NSLocalizedString(
203
- "An unexpected error occurred.",
204
- comment: "Unexpected Error"
205
- )
206
- case .cannotDecode:
207
- return NSLocalizedString(
208
- "Decoding the zip failed with this key",
209
- comment: "Invalid private key"
210
- )
211
- case .cannotWrite:
212
- return NSLocalizedString(
213
- "Cannot write to the destination",
214
- comment: "Invalid destination"
215
- )
216
- }
217
- }
218
- }
219
-
220
- @objc public class CapacitorUpdater: NSObject {
221
-
222
- private let versionName: String = Bundle.main.versionName ?? ""
223
- private let versionCode: String = Bundle.main.versionCode ?? ""
224
- private let versionOs = UIDevice.current.systemVersion
225
- private let documentsDir: URL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
226
- private let libraryDir: URL = FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask).first!
227
- private let bundleDirectoryHot: String = "versions"
228
- private let DEFAULT_FOLDER: String = ""
229
- private let bundleDirectory: String = "NoCloud/ionic_built_snapshots"
230
- private let INFO_SUFFIX: String = "_info"
231
- private let FALLBACK_VERSION: String = "pastVersion"
232
- private let NEXT_VERSION: String = "nextVersion"
233
-
234
- public let TAG: String = "✨ Capacitor-updater:"
235
- public let CAP_SERVER_PATH: String = "serverBasePath"
236
- public var customId: String = ""
237
- public var PLUGIN_VERSION: String = ""
238
- public let timeout: Double = 20
239
- public var statsUrl: String = ""
240
- public var channelUrl: String = ""
241
- public var appId: String = ""
242
- public var deviceID = UIDevice.current.identifierForVendor?.uuidString ?? ""
243
- public var privateKey: String = ""
244
-
245
- public var notifyDownload: (String, Int) -> Void = { _, _ in }
246
-
247
- private func calcTotalPercent(percent: Int, min: Int, max: Int) -> Int {
248
- return (percent * (max - min)) / 100 + min
249
- }
250
-
251
- private func randomString(length: Int) -> String {
252
- let letters: String = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
253
- return String((0..<length).map { _ in letters.randomElement()! })
254
- }
255
-
256
- private func isProd() -> Bool {
257
- return !self.isAppStoreReceiptSandbox() && !self.hasEmbeddedMobileProvision()
258
- }
259
-
260
- // MARK: Private
261
- private func hasEmbeddedMobileProvision() -> Bool {
262
- guard Bundle.main.path(forResource: "embedded", ofType: "mobileprovision") == nil else {
263
- return true
264
- }
265
- return false
266
- }
267
-
268
- private func isAppStoreReceiptSandbox() -> Bool {
269
-
270
- if isEmulator() {
271
- return false
272
- } else {
273
- guard let url: URL = Bundle.main.appStoreReceiptURL else {
274
- return false
275
- }
276
- guard url.lastPathComponent == "sandboxReceipt" else {
277
- return false
278
- }
279
- return true
280
- }
281
- }
282
-
283
- private func isEmulator() -> Bool {
284
- #if targetEnvironment(simulator)
285
- return true
286
- #else
287
- return false
288
- #endif
289
- }
290
- // Persistent path /var/mobile/Containers/Data/Application/8C0C07BE-0FD3-4FD4-B7DF-90A88E12B8C3/Library/NoCloud/ionic_built_snapshots/FOLDER
291
- // Hot Reload path /var/mobile/Containers/Data/Application/8C0C07BE-0FD3-4FD4-B7DF-90A88E12B8C3/Documents/FOLDER
292
- // Normal /private/var/containers/Bundle/Application/8C0C07BE-0FD3-4FD4-B7DF-90A88E12B8C3/App.app/public
293
-
294
- private func prepareFolder(source: URL) throws {
295
- if !FileManager.default.fileExists(atPath: source.path) {
296
- do {
297
- try FileManager.default.createDirectory(atPath: source.path, withIntermediateDirectories: true, attributes: nil)
298
- } catch {
299
- print("\(self.TAG) Cannot createDirectory \(source.path)")
300
- throw CustomError.cannotCreateDirectory
301
- }
302
- }
303
- }
304
-
305
- private func deleteFolder(source: URL) throws {
306
- do {
307
- try FileManager.default.removeItem(atPath: source.path)
308
- } catch {
309
- print("\(self.TAG) File not removed. \(source.path)")
310
- throw CustomError.cannotDeleteDirectory
311
- }
312
- }
313
-
314
- private func unflatFolder(source: URL, dest: URL) throws -> Bool {
315
- let index: URL = source.appendingPathComponent("index.html")
316
- do {
317
- let files: [String] = try FileManager.default.contentsOfDirectory(atPath: source.path)
318
- if files.count == 1 && source.appendingPathComponent(files[0]).isDirectory && !FileManager.default.fileExists(atPath: index.path) {
319
- try FileManager.default.moveItem(at: source.appendingPathComponent(files[0]), to: dest)
320
- return true
321
- } else {
322
- try FileManager.default.moveItem(at: source, to: dest)
323
- return false
324
- }
325
- } catch {
326
- print("\(self.TAG) File not moved. source: \(source.path) dest: \(dest.path)")
327
- throw CustomError.cannotUnflat
328
- }
329
- }
330
-
331
- private func getChecksum(filePath: URL) -> String {
332
- do {
333
- let fileData: Data = try Data.init(contentsOf: filePath)
334
- let checksum: uLong = fileData.withUnsafeBytes { crc32(0, $0.bindMemory(to: Bytef.self).baseAddress, uInt(fileData.count)) }
335
- return String(format: "%08X", checksum).lowercased()
336
- } catch {
337
- print("\(self.TAG) Cannot get checksum: \(filePath.path)", error)
338
- return ""
339
- }
340
- }
341
-
342
- private func decryptFile(filePath: URL, sessionKey: String, version: String) throws {
343
- if self.privateKey.isEmpty || sessionKey.isEmpty {
344
- print("\(self.TAG) Cannot found privateKey or sessionKey")
345
- return
346
- }
347
- do {
348
- guard let rsaPrivateKey: RSAPrivateKey = .load(rsaPrivateKey: self.privateKey) else {
349
- print("cannot decode privateKey", self.privateKey)
350
- throw CustomError.cannotDecode
351
- }
352
-
353
- let sessionKeyArray: [String] = sessionKey.components(separatedBy: ":")
354
- guard let ivData: Data = Data(base64Encoded: sessionKeyArray[0]) else {
355
- print("cannot decode sessionKey", sessionKey)
356
- throw CustomError.cannotDecode
357
- }
358
- let sessionKeyDataEncrypted: Data = Data(base64Encoded: sessionKeyArray[1])!
359
- let sessionKeyDataDecrypted: Data = rsaPrivateKey.decrypt(data: sessionKeyDataEncrypted)!
360
- let aesPrivateKey: AES128Key = AES128Key(iv: ivData, aes128Key: sessionKeyDataDecrypted)
361
- let encryptedData: Data = try Data(contentsOf: filePath)
362
- let decryptedData: Data = aesPrivateKey.decrypt(data: encryptedData)!
363
-
364
- try decryptedData.write(to: filePath)
365
- } catch {
366
- print("\(self.TAG) Cannot decode: \(filePath.path)", error)
367
- self.sendStats(action: "decrypt_fail", versionName: version)
368
- throw CustomError.cannotDecode
369
- }
370
- }
371
-
372
- private func saveDownloaded(sourceZip: URL, id: String, base: URL) throws {
373
- try prepareFolder(source: base)
374
- let destHot: URL = base.appendingPathComponent(id)
375
- let destUnZip: URL = documentsDir.appendingPathComponent(randomString(length: 10))
376
- if !SSZipArchive.unzipFile(atPath: sourceZip.path, toDestination: destUnZip.path) {
377
- throw CustomError.cannotUnzip
378
- }
379
- if try unflatFolder(source: destUnZip, dest: destHot) {
380
- try deleteFolder(source: destUnZip)
381
- }
382
- }
383
-
384
- private func createInfoObject() -> InfoObject {
385
- return InfoObject(
386
- platform: "ios",
387
- device_id: self.deviceID,
388
- app_id: self.appId,
389
- custom_id: self.customId,
390
- version_build: self.versionName,
391
- version_code: self.versionCode,
392
- version_os: self.versionOs,
393
- version_name: self.getCurrentBundle().getVersionName(),
394
- plugin_version: self.PLUGIN_VERSION,
395
- is_emulator: self.isEmulator(),
396
- is_prod: self.isProd(),
397
- action: nil,
398
- channel: nil
399
- )
400
- }
401
-
402
- public func getLatest(url: URL) -> AppVersion {
403
- let semaphore: DispatchSemaphore = DispatchSemaphore(value: 0)
404
- let latest: AppVersion = AppVersion()
405
- let parameters: InfoObject = self.createInfoObject()
406
- print("\(self.TAG) Auto-update parameters: \(parameters)")
407
- let request = AF.request(url, method: .post, parameters: parameters, encoder: JSONParameterEncoder.default, requestModifier: { $0.timeoutInterval = self.timeout })
408
-
409
- request.validate().responseDecodable(of: AppVersionDec.self) { response in
410
- switch response.result {
411
- case .success:
412
- if let url = response.value?.url {
413
- latest.url = url
414
- }
415
- if let checksum = response.value?.checksum {
416
- latest.checksum = checksum
417
- }
418
- if let version = response.value?.version {
419
- latest.version = version
420
- }
421
- if let major = response.value?.major {
422
- latest.major = major
423
- }
424
- if let error = response.value?.error {
425
- latest.error = error
426
- }
427
- if let message = response.value?.message {
428
- latest.message = message
429
- }
430
- if let sessionKey = response.value?.session_key {
431
- latest.sessionKey = sessionKey
432
- }
433
- case let .failure(error):
434
- print("\(self.TAG) Error getting Latest", response.value ?? "", error )
435
- latest.message = "Error getting Latest \(String(describing: response.value))"
436
- latest.error = "response_error"
437
- }
438
- semaphore.signal()
439
- }
440
- semaphore.wait()
441
- return latest
442
- }
443
-
444
- private func setCurrentBundle(bundle: String) {
445
- UserDefaults.standard.set(bundle, forKey: self.CAP_SERVER_PATH)
446
- UserDefaults.standard.synchronize()
447
- print("\(self.TAG) Current bundle set to: \((bundle ).isEmpty ? BundleInfo.ID_BUILTIN : bundle)")
448
- }
449
-
450
- public func download(url: URL, version: String, sessionKey: String) throws -> BundleInfo {
451
- let semaphore: DispatchSemaphore = DispatchSemaphore(value: 0)
452
- let id: String = self.randomString(length: 10)
453
- var checksum: String = ""
454
-
455
- var mainError: NSError?
456
- let destination: DownloadRequest.Destination = { _, _ in
457
- let documentsURL: URL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
458
- let fileURL: URL = documentsURL.appendingPathComponent(self.randomString(length: 10))
459
-
460
- return (fileURL, [.removePreviousFile, .createIntermediateDirectories])
461
- }
462
- let request = AF.download(url, to: destination)
463
-
464
- request.downloadProgress { progress in
465
- let percent = self.calcTotalPercent(percent: Int(progress.fractionCompleted * 100), min: 10, max: 70)
466
- self.notifyDownload(id, percent)
467
- }
468
- request.responseURL { (response) in
469
- if let fileURL = response.fileURL {
470
- switch response.result {
471
- case .success:
472
- self.notifyDownload(id, 71)
473
- do {
474
- try self.decryptFile(filePath: fileURL, sessionKey: sessionKey, version: version)
475
- checksum = self.getChecksum(filePath: fileURL)
476
- try self.saveDownloaded(sourceZip: fileURL, id: id, base: self.documentsDir.appendingPathComponent(self.bundleDirectoryHot))
477
- self.notifyDownload(id, 85)
478
- try self.saveDownloaded(sourceZip: fileURL, id: id, base: self.libraryDir.appendingPathComponent(self.bundleDirectory))
479
- self.notifyDownload(id, 100)
480
- try self.deleteFolder(source: fileURL)
481
- } catch {
482
- print("\(self.TAG) download unzip error", error)
483
- mainError = error as NSError
484
- }
485
- case let .failure(error):
486
- print("\(self.TAG) download error", response.value ?? "", error)
487
- mainError = error as NSError
488
- }
489
- }
490
- semaphore.signal()
491
- }
492
- self.saveBundleInfo(id: id, bundle: BundleInfo(id: id, version: version, status: BundleStatus.DOWNLOADING, downloaded: Date(), checksum: checksum))
493
- self.notifyDownload(id, 0)
494
- semaphore.wait()
495
- if mainError != nil {
496
- throw mainError!
497
- }
498
- let info: BundleInfo = BundleInfo(id: id, version: version, status: BundleStatus.PENDING, downloaded: Date(), checksum: checksum)
499
- self.saveBundleInfo(id: id, bundle: info)
500
- return info
501
- }
502
-
503
- public func list() -> [BundleInfo] {
504
- let dest: URL = documentsDir.appendingPathComponent(bundleDirectoryHot)
505
- do {
506
- let files: [String] = try FileManager.default.contentsOfDirectory(atPath: dest.path)
507
- var res: [BundleInfo] = []
508
- print("\(self.TAG) list File : \(dest.path)")
509
- if dest.exist {
510
- for id: String in files {
511
- res.append(self.getBundleInfo(id: id))
512
- }
513
- }
514
- return res
515
- } catch {
516
- print("\(self.TAG) No version available \(dest.path)")
517
- return []
518
- }
519
- }
520
-
521
- public func delete(id: String, removeInfo: Bool) -> Bool {
522
- let deleted: BundleInfo = self.getBundleInfo(id: id)
523
- if deleted.isBuiltin() || self.getCurrentBundleId() == id {
524
- print("\(self.TAG) Cannot delete \(id)")
525
- return false
526
- }
527
- let destHot: URL = documentsDir.appendingPathComponent(bundleDirectoryHot).appendingPathComponent(id)
528
- let destPersist: URL = libraryDir.appendingPathComponent(bundleDirectory).appendingPathComponent(id)
529
- do {
530
- try FileManager.default.removeItem(atPath: destHot.path)
531
- } catch {
532
- print("\(self.TAG) Hot Folder \(destHot.path), not removed.")
533
- }
534
- do {
535
- try FileManager.default.removeItem(atPath: destPersist.path)
536
- } catch {
537
- print("\(self.TAG) Folder \(destPersist.path), not removed.")
538
- return false
539
- }
540
- if removeInfo {
541
- self.removeBundleInfo(id: id)
542
- } else {
543
- self.saveBundleInfo(id: id, bundle: deleted.setStatus(status: BundleStatus.DELETED.localizedString))
544
- }
545
- print("\(self.TAG) bundle delete \(deleted.getVersionName())")
546
- self.sendStats(action: "delete", versionName: deleted.getVersionName())
547
- return true
548
- }
549
-
550
- public func delete(id: String) -> Bool {
551
- return self.delete(id: id, removeInfo: true)
552
- }
553
-
554
- public func getBundleDirectory(id: String) -> URL {
555
- return libraryDir.appendingPathComponent(self.bundleDirectory).appendingPathComponent(id)
556
- }
557
-
558
- public func set(bundle: BundleInfo) -> Bool {
559
- return self.set(id: bundle.getId())
560
- }
561
-
562
- private func bundleExists(id: String) -> Bool {
563
- let destHot: URL = self.getPathHot(id: id)
564
- let destHotPersist: URL = self.getPathPersist(id: id)
565
- let indexHot: URL = destHot.appendingPathComponent("index.html")
566
- let indexPersist: URL = destHotPersist.appendingPathComponent("index.html")
567
- let url: URL = self.getBundleDirectory(id: id)
568
- let bundleIndo: BundleInfo = self.getBundleInfo(id: id)
569
- if url.isDirectory && destHotPersist.isDirectory && indexHot.exist && indexPersist.exist && !bundleIndo.isDeleted() {
570
- return true
571
- }
572
- return false
573
- }
574
-
575
- public func set(id: String) -> Bool {
576
- let newBundle: BundleInfo = self.getBundleInfo(id: id)
577
- if newBundle.isBuiltin() {
578
- self.reset()
579
- return true
580
- }
581
- if bundleExists(id: id) {
582
- self.setCurrentBundle(bundle: self.getBundleDirectory(id: id).path)
583
- self.setBundleStatus(id: id, status: BundleStatus.PENDING)
584
- self.sendStats(action: "set", versionName: newBundle.getVersionName())
585
- return true
586
- }
587
- self.setBundleStatus(id: id, status: BundleStatus.ERROR)
588
- self.sendStats(action: "set_fail", versionName: newBundle.getVersionName())
589
- return false
590
- }
591
-
592
- public func getPathHot(id: String) -> URL {
593
- return documentsDir.appendingPathComponent(self.bundleDirectoryHot).appendingPathComponent(id)
594
- }
595
-
596
- public func getPathPersist(id: String) -> URL {
597
- return libraryDir.appendingPathComponent(self.bundleDirectory).appendingPathComponent(id)
598
- }
599
-
600
- public func reset() {
601
- self.reset(isInternal: false)
602
- }
603
-
604
- public func reset(isInternal: Bool) {
605
- print("\(self.TAG) reset: \(isInternal)")
606
- self.setCurrentBundle(bundle: "")
607
- self.setFallbackBundle(fallback: Optional<BundleInfo>.none)
608
- _ = self.setNextBundle(next: Optional<String>.none)
609
- if !isInternal {
610
- self.sendStats(action: "reset", versionName: self.getCurrentBundle().getVersionName())
611
- }
612
- }
613
-
614
- public func setSuccess(bundle: BundleInfo, autoDeletePrevious: Bool) {
615
- self.setBundleStatus(id: bundle.getId(), status: BundleStatus.SUCCESS)
616
- let fallback: BundleInfo = self.getFallbackBundle()
617
- print("\(self.TAG) Fallback bundle is: \(fallback.toString())")
618
- print("\(self.TAG) Version successfully loaded: \(bundle.toString())")
619
- if autoDeletePrevious && !fallback.isBuiltin() {
620
- let res = self.delete(id: fallback.getId())
621
- if res {
622
- print("\(self.TAG) Deleted previous bundle: \(fallback.toString())")
623
- } else {
624
- print("\(self.TAG) Failed to delete previous bundle: \(fallback.toString())")
625
- }
626
- }
627
- self.setFallbackBundle(fallback: bundle)
628
- }
629
-
630
- public func setError(bundle: BundleInfo) {
631
- self.setBundleStatus(id: bundle.getId(), status: BundleStatus.ERROR)
632
- }
633
-
634
- func setChannel(channel: String) -> SetChannel {
635
- let setChannel: SetChannel = SetChannel()
636
- if (self.channelUrl ).isEmpty {
637
- print("\(self.TAG) Channel URL is not set")
638
- setChannel.message = "Channel URL is not set"
639
- setChannel.error = "missing_config"
640
- return setChannel
641
- }
642
- let semaphore: DispatchSemaphore = DispatchSemaphore(value: 0)
643
- var parameters: InfoObject = self.createInfoObject()
644
- parameters.channel = channel
645
-
646
- let request = AF.request(self.channelUrl, method: .post, parameters: parameters, encoder: JSONParameterEncoder.default, requestModifier: { $0.timeoutInterval = self.timeout })
647
-
648
- request.validate().responseDecodable(of: SetChannelDec.self) { response in
649
- switch response.result {
650
- case .success:
651
- if let status = response.value?.status {
652
- setChannel.status = status
653
- }
654
- if let error = response.value?.error {
655
- setChannel.error = error
656
- }
657
- if let message = response.value?.message {
658
- setChannel.message = message
659
- }
660
- case let .failure(error):
661
- print("\(self.TAG) Error set Channel", response.value ?? "", error)
662
- setChannel.message = "Error set Channel \(String(describing: response.value))"
663
- setChannel.error = "response_error"
664
- }
665
- semaphore.signal()
666
- }
667
- semaphore.wait()
668
- return setChannel
669
- }
670
-
671
- func getChannel() -> GetChannel {
672
- let getChannel: GetChannel = GetChannel()
673
- if (self.channelUrl ).isEmpty {
674
- print("\(self.TAG) Channel URL is not set")
675
- getChannel.message = "Channel URL is not set"
676
- getChannel.error = "missing_config"
677
- return getChannel
678
- }
679
- let semaphore: DispatchSemaphore = DispatchSemaphore(value: 0)
680
- let parameters: InfoObject = self.createInfoObject()
681
- let request = AF.request(self.channelUrl, method: .put, parameters: parameters, encoder: JSONParameterEncoder.default, requestModifier: { $0.timeoutInterval = self.timeout })
682
-
683
- request.validate().responseDecodable(of: GetChannelDec.self) { response in
684
- switch response.result {
685
- case .success:
686
- if let status = response.value?.status {
687
- getChannel.status = status
688
- }
689
- if let error = response.value?.error {
690
- getChannel.error = error
691
- }
692
- if let message = response.value?.message {
693
- getChannel.message = message
694
- }
695
- if let channel = response.value?.channel {
696
- getChannel.channel = channel
697
- }
698
- if let allowSet = response.value?.allowSet {
699
- getChannel.allowSet = allowSet
700
- }
701
- case let .failure(error):
702
- print("\(self.TAG) Error get Channel", response.value ?? "", error)
703
- getChannel.message = "Error get Channel \(String(describing: response.value)))"
704
- getChannel.error = "response_error"
705
- }
706
- semaphore.signal()
707
- }
708
- semaphore.wait()
709
- return getChannel
710
- }
711
-
712
- func sendStats(action: String, versionName: String) {
713
- if (self.statsUrl ).isEmpty {
714
- return
715
- }
716
- var parameters: InfoObject = self.createInfoObject()
717
- parameters.action = action
718
- DispatchQueue.global(qos: .background).async {
719
- let request = AF.request(self.statsUrl, method: .post, parameters: parameters, encoder: JSONParameterEncoder.default, requestModifier: { $0.timeoutInterval = self.timeout })
720
- request.responseData { response in
721
- switch response.result {
722
- case .success:
723
- print("\(self.TAG) Stats send for \(action), version \(versionName)")
724
- case let .failure(error):
725
- print("\(self.TAG) Error sending stats: ", response.value ?? "", error)
726
- }
727
- }
728
- }
729
- }
730
-
731
- public func getBundleInfo(id: String?) -> BundleInfo {
732
- var trueId = BundleInfo.VERSION_UNKNOWN
733
- if id != nil {
734
- trueId = id!
735
- }
736
- print("\(self.TAG) Getting info for bundle [\(trueId)]")
737
- let result: BundleInfo
738
- if BundleInfo.ID_BUILTIN == trueId {
739
- result = BundleInfo(id: trueId, version: "", status: BundleStatus.SUCCESS, checksum: "")
740
- } else if BundleInfo.VERSION_UNKNOWN == trueId {
741
- result = BundleInfo(id: trueId, version: "", status: BundleStatus.ERROR, checksum: "")
742
- } else {
743
- do {
744
- result = try UserDefaults.standard.getObj(forKey: "\(trueId)\(self.INFO_SUFFIX)", castTo: BundleInfo.self)
745
- } catch {
746
- print("\(self.TAG) Failed to parse info for bundle [\(trueId)]", error.localizedDescription)
747
- result = BundleInfo(id: trueId, version: "", status: BundleStatus.PENDING, checksum: "")
748
- }
749
- }
750
- print("\(self.TAG) Returning info bundle [\(result.toString())]")
751
- return result
752
- }
753
-
754
- public func getBundleInfoByVersionName(version: String) -> BundleInfo? {
755
- let installed: [BundleInfo] = self.list()
756
- for i in installed {
757
- if i.getVersionName() == version {
758
- return i
759
- }
760
- }
761
- return nil
762
- }
763
-
764
- private func removeBundleInfo(id: String) {
765
- self.saveBundleInfo(id: id, bundle: nil)
766
- }
767
-
768
- private func saveBundleInfo(id: String, bundle: BundleInfo?) {
769
- if bundle != nil && (bundle!.isBuiltin() || bundle!.isUnknown()) {
770
- print("\(self.TAG) Not saving info for bundle [\(id)]", bundle?.toString() ?? "")
771
- return
772
- }
773
- if bundle == nil {
774
- print("\(self.TAG) Removing info for bundle [\(id)]")
775
- UserDefaults.standard.removeObject(forKey: "\(id)\(self.INFO_SUFFIX)")
776
- } else {
777
- let update = bundle!.setId(id: id)
778
- print("\(self.TAG) Storing info for bundle [\(id)]", update.toString())
779
- do {
780
- try UserDefaults.standard.setObj(update, forKey: "\(id)\(self.INFO_SUFFIX)")
781
- } catch {
782
- print("\(self.TAG) Failed to save info for bundle [\(id)]", error.localizedDescription)
783
- }
784
- }
785
- UserDefaults.standard.synchronize()
786
- }
787
-
788
- public func setVersionName(id: String, version: String) {
789
- print("\(self.TAG) Setting version for folder [\(id)] to \(version)")
790
- let info = self.getBundleInfo(id: id)
791
- self.saveBundleInfo(id: id, bundle: info.setVersionName(version: version))
792
- }
793
-
794
- private func setBundleStatus(id: String, status: BundleStatus) {
795
- print("\(self.TAG) Setting status for bundle [\(id)] to \(status)")
796
- let info = self.getBundleInfo(id: id)
797
- self.saveBundleInfo(id: id, bundle: info.setStatus(status: status.localizedString))
798
- }
799
-
800
- public func getCurrentBundle() -> BundleInfo {
801
- return self.getBundleInfo(id: self.getCurrentBundleId())
802
- }
803
-
804
- public func getCurrentBundleId() -> String {
805
- guard let bundlePath: String = UserDefaults.standard.string(forKey: self.CAP_SERVER_PATH) else {
806
- return BundleInfo.ID_BUILTIN
807
- }
808
- if (bundlePath ).isEmpty {
809
- return BundleInfo.ID_BUILTIN
810
- }
811
- let bundleID: String = bundlePath.components(separatedBy: "/").last ?? bundlePath
812
- return bundleID
813
- }
814
-
815
- public func isUsingBuiltin() -> Bool {
816
- return (UserDefaults.standard.string(forKey: self.CAP_SERVER_PATH) ?? "") == self.DEFAULT_FOLDER
817
- }
818
-
819
- public func getFallbackBundle() -> BundleInfo {
820
- let id: String = UserDefaults.standard.string(forKey: self.FALLBACK_VERSION) ?? BundleInfo.ID_BUILTIN
821
- return self.getBundleInfo(id: id)
822
- }
823
-
824
- private func setFallbackBundle(fallback: BundleInfo?) {
825
- UserDefaults.standard.set(fallback == nil ? BundleInfo.ID_BUILTIN : fallback!.getId(), forKey: self.FALLBACK_VERSION)
826
- UserDefaults.standard.synchronize()
827
- }
828
-
829
- public func getNextBundle() -> BundleInfo? {
830
- let id: String? = UserDefaults.standard.string(forKey: self.NEXT_VERSION)
831
- return self.getBundleInfo(id: id)
832
- }
833
-
834
- public func setNextBundle(next: String?) -> Bool {
835
- guard let nextId: String = next else {
836
- UserDefaults.standard.removeObject(forKey: self.NEXT_VERSION)
837
- UserDefaults.standard.synchronize()
838
- return false
839
- }
840
- let newBundle: BundleInfo = self.getBundleInfo(id: nextId)
841
- let bundle: URL = self.getBundleDirectory(id: nextId)
842
- if !newBundle.isBuiltin() && !bundle.exist {
843
- return false
844
- }
845
- UserDefaults.standard.set(nextId, forKey: self.NEXT_VERSION)
846
- UserDefaults.standard.synchronize()
847
- self.setBundleStatus(id: nextId, status: BundleStatus.PENDING)
848
- return true
849
- }
850
- }