@capgo/capacitor-updater 6.1.17 → 6.1.19

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.
@@ -9,11 +9,12 @@ import SSZipArchive
9
9
  import Alamofire
10
10
  import zlib
11
11
  import SwiftyRSA
12
+ import CryptoKit
12
13
 
13
14
  extension Collection {
14
- subscript(safe index: Index) -> Element? {
15
- return indices.contains(index) ? self[index] : nil
16
- }
15
+ subscript(safe index: Index) -> Element? {
16
+ return indices.contains(index) ? self[index] : nil
17
+ }
17
18
  }
18
19
  extension URL {
19
20
  var isDirectory: Bool {
@@ -97,7 +98,6 @@ struct AppVersionDec: Decodable {
97
98
  let session_key: String?
98
99
  let major: Bool?
99
100
  let data: [String: String]?
100
- let signature: String?
101
101
  }
102
102
  public class AppVersion: NSObject {
103
103
  var version: String = ""
@@ -108,7 +108,6 @@ public class AppVersion: NSObject {
108
108
  var sessionKey: String?
109
109
  var major: Bool?
110
110
  var data: [String: String]?
111
- var signature: String?
112
111
  }
113
112
 
114
113
  extension AppVersion {
@@ -177,8 +176,8 @@ enum CustomError: Error {
177
176
  case cannotUnflat
178
177
  case cannotCreateDirectory
179
178
  case cannotDeleteDirectory
180
- case signatureNotProvided
181
- case invalidSignature
179
+ case cannotDecryptSessionKey
180
+ case invalidBase64
182
181
 
183
182
  // Throw in all other cases
184
183
  case unexpected(code: Int)
@@ -187,16 +186,6 @@ enum CustomError: Error {
187
186
  extension CustomError: LocalizedError {
188
187
  public var errorDescription: String? {
189
188
  switch self {
190
- case .signatureNotProvided:
191
- return NSLocalizedString(
192
- "Signature was required but none was provided",
193
- comment: "Signature not provided"
194
- )
195
- case .invalidSignature:
196
- return NSLocalizedString(
197
- "Signature is not valid, cannot accept update",
198
- comment: "Invalid signature"
199
- )
200
189
  case .cannotUnzip:
201
190
  return NSLocalizedString(
202
191
  "The file cannot be unzip",
@@ -225,19 +214,29 @@ extension CustomError: LocalizedError {
225
214
  case .cannotDecode:
226
215
  return NSLocalizedString(
227
216
  "Decoding the zip failed with this key",
228
- comment: "Invalid private key"
217
+ comment: "Invalid public key"
229
218
  )
230
219
  case .cannotWrite:
231
220
  return NSLocalizedString(
232
221
  "Cannot write to the destination",
233
222
  comment: "Invalid destination"
234
223
  )
224
+ case .cannotDecryptSessionKey:
225
+ return NSLocalizedString(
226
+ "Decrypting the session key failed",
227
+ comment: "Invalid session key"
228
+ )
229
+ case .invalidBase64:
230
+ return NSLocalizedString(
231
+ "Decrypting the base64 failed",
232
+ comment: "Invalid checksum key"
233
+ )
235
234
  }
236
235
  }
237
236
  }
238
237
 
239
238
  @objc public class CapacitorUpdater: NSObject {
240
-
239
+
241
240
  private let versionCode: String = Bundle.main.versionCode ?? ""
242
241
  private let versionOs = UIDevice.current.systemVersion
243
242
  private let libraryDir: URL = FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask).first!
@@ -247,7 +246,7 @@ extension CustomError: LocalizedError {
247
246
  private let FALLBACK_VERSION: String = "pastVersion"
248
247
  private let NEXT_VERSION: String = "nextVersion"
249
248
  private var unzipPercent = 0
250
-
249
+
251
250
  public let TAG: String = "✨ Capacitor-updater:"
252
251
  public let CAP_SERVER_PATH: String = "serverBasePath"
253
252
  public var versionBuild: String = ""
@@ -260,8 +259,9 @@ extension CustomError: LocalizedError {
260
259
  public var appId: String = ""
261
260
  public var deviceID = ""
262
261
  public var privateKey: String = ""
263
- public var signKey: PublicKey?
264
-
262
+ public var publicKey: String = ""
263
+ public var hasOldPrivateKeyPropertyInConfig: Bool = false
264
+
265
265
  public var notifyDownloadRaw: (String, Int, Bool) -> Void = { _, _, _ in }
266
266
  public func notifyDownload(id: String, percent: Int, ignoreMultipleOfTen: Bool = false) {
267
267
  notifyDownloadRaw(id, percent, ignoreMultipleOfTen)
@@ -360,37 +360,56 @@ extension CustomError: LocalizedError {
360
360
  }
361
361
  }
362
362
 
363
-
364
-
365
- private func verifyBundleSignature(version: String, filePath: URL, signature: String?) throws -> Bool {
366
- if (self.signKey == nil) {
367
- print("\(self.TAG) Signing not configured")
368
- return true
369
- }
370
-
371
- if (self.signKey != nil && (signature == nil || signature?.isEmpty == true)) {
372
- print("\(self.TAG) Signature required but none provided")
373
- self.sendStats(action: "signature_not_provided", versionName: version)
374
- throw CustomError.signatureNotProvided
363
+ private func decryptFileV2(filePath: URL, sessionKey: String, version: String) throws {
364
+ if self.publicKey.isEmpty || sessionKey.isEmpty || sessionKey.components(separatedBy: ":").count != 2 {
365
+ print("\(self.TAG) Cannot find public key or sessionKey")
366
+ return
375
367
  }
376
-
377
368
  do {
378
- // let publicKey = try PublicKey(pemEncoded: self.signKey)
379
- let signatureObj = try Signature(base64Encoded: signature!) // I THINK I can unwrap safely here (?)
380
- let clear = try ClearMessage(data: Data(contentsOf: filePath))
381
-
382
- let isSuccessful = try clear.verify(with: self.signKey!, signature: signatureObj, digestType: .sha512)
383
- return isSuccessful
369
+ guard let rsaPublicKey: RSAPublicKey = .load(rsaPublicKey: self.publicKey) else {
370
+ print("cannot decode publicKey", self.publicKey)
371
+ throw CustomError.cannotDecode
372
+ }
373
+
374
+ let sessionKeyArray: [String] = sessionKey.components(separatedBy: ":")
375
+ guard let ivData: Data = Data(base64Encoded: sessionKeyArray[0]) else {
376
+ print("cannot decode sessionKey", sessionKey)
377
+ throw CustomError.cannotDecode
378
+ }
379
+
380
+ guard let sessionKeyDataEncrypted = Data(base64Encoded: sessionKeyArray[1]) else {
381
+ throw NSError(domain: "Invalid session key data", code: 1, userInfo: nil)
382
+ }
383
+
384
+ guard let sessionKeyDataDecrypted = rsaPublicKey.decrypt(data: sessionKeyDataEncrypted) else {
385
+ throw NSError(domain: "Failed to decrypt session key data", code: 2, userInfo: nil)
386
+ }
387
+
388
+ let aesPrivateKey = AES128Key(iv: ivData, aes128Key: sessionKeyDataDecrypted)
389
+
390
+ guard let encryptedData = try? Data(contentsOf: filePath) else {
391
+ throw NSError(domain: "Failed to read encrypted data", code: 3, userInfo: nil)
392
+ }
393
+
394
+ guard let decryptedData = aesPrivateKey.decrypt(data: encryptedData) else {
395
+ throw NSError(domain: "Failed to decrypt data", code: 4, userInfo: nil)
396
+ }
397
+
398
+ try decryptedData.write(to: filePath)
399
+
384
400
  } catch {
385
- print("\(self.TAG) Signature validation failed", error)
386
- self.sendStats(action: "signature_validation_failed", versionName: version)
387
- throw error
401
+ print("\(self.TAG) Cannot decode: \(filePath.path)", error)
402
+ self.sendStats(action: "decrypt_fail", versionName: version)
403
+ throw CustomError.cannotDecode
388
404
  }
389
405
  }
390
406
 
391
407
  private func decryptFile(filePath: URL, sessionKey: String, version: String) throws {
392
- if self.privateKey.isEmpty || sessionKey.isEmpty || sessionKey.components(separatedBy: ":").count != 2 {
393
- print("\(self.TAG) Cannot found privateKey or sessionKey")
408
+ if self.privateKey.isEmpty {
409
+ print("\(self.TAG) Cannot found privateKey")
410
+ return
411
+ } else if sessionKey.isEmpty || sessionKey.components(separatedBy: ":").count != 2 {
412
+ print("\(self.TAG) Cannot found sessionKey")
394
413
  return
395
414
  }
396
415
  do {
@@ -424,7 +443,7 @@ extension CustomError: LocalizedError {
424
443
  }
425
444
 
426
445
  try decryptedData.write(to: filePath)
427
-
446
+
428
447
  } catch {
429
448
  print("\(self.TAG) Cannot decode: \(filePath.path)", error)
430
449
  self.sendStats(action: "decrypt_fail", versionName: version)
@@ -564,9 +583,6 @@ extension CustomError: LocalizedError {
564
583
  if let data = response.value?.data {
565
584
  latest.data = data
566
585
  }
567
- if let signature = response.value?.signature {
568
- latest.signature = signature
569
- }
570
586
  case let .failure(error):
571
587
  print("\(self.TAG) Error getting Latest", response.value ?? "", error )
572
588
  latest.message = "Error getting Latest \(String(describing: response.value))"
@@ -611,7 +627,35 @@ extension CustomError: LocalizedError {
611
627
  return ""
612
628
  }
613
629
  }
614
-
630
+
631
+ private func calcChecksumV2(filePath: URL) -> String {
632
+ let bufferSize = 1024 * 1024 * 5 // 5 MB
633
+ var sha256 = SHA256()
634
+
635
+ do {
636
+ let fileHandle = try FileHandle(forReadingFrom: filePath)
637
+ defer {
638
+ fileHandle.closeFile()
639
+ }
640
+
641
+ while autoreleasepool(invoking: {
642
+ let fileData = fileHandle.readData(ofLength: bufferSize)
643
+ if fileData.count > 0 {
644
+ sha256.update(data: fileData)
645
+ return true // Continue
646
+ } else {
647
+ return false // End of file
648
+ }
649
+ }) {}
650
+
651
+ let digest = sha256.finalize()
652
+ return digest.compactMap { String(format: "%02x", $0) }.joined()
653
+ } catch {
654
+ print("\(self.TAG) Cannot get checksum: \(filePath.path)", error)
655
+ return ""
656
+ }
657
+ }
658
+
615
659
  private var tempDataPath: URL {
616
660
  return FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("package.tmp")
617
661
  }
@@ -620,26 +664,48 @@ extension CustomError: LocalizedError {
620
664
  return FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("update.dat")
621
665
  }
622
666
  private var tempData = Data()
623
- public func download(url: URL, version: String, sessionKey: String, signature: String) throws -> BundleInfo {
667
+
668
+ public func decryptChecksum(checksum: String, version: String) throws -> String {
669
+ if self.publicKey.isEmpty {
670
+ return checksum
671
+ }
672
+ do {
673
+ let checksumBytes: Data = Data(base64Encoded: checksum)!
674
+ guard let rsaPublicKey: RSAPublicKey = .load(rsaPublicKey: self.publicKey) else {
675
+ print("cannot decode publicKey", self.publicKey)
676
+ throw CustomError.cannotDecode
677
+ }
678
+ guard let decryptedChecksum = try? rsaPublicKey.decrypt(data: checksumBytes) else {
679
+ throw NSError(domain: "Failed to decrypt session key data", code: 2, userInfo: nil)
680
+ }
681
+ return decryptedChecksum.base64EncodedString()
682
+ } catch {
683
+ print("\(self.TAG) Cannot decrypt checksum: \(checksum)", error)
684
+ self.sendStats(action: "decrypt_fail", versionName: version)
685
+ throw CustomError.cannotDecode
686
+ }
687
+ }
688
+
689
+ public func download(url: URL, version: String, sessionKey: String) throws -> BundleInfo {
624
690
  let id: String = self.randomString(length: 10)
625
691
  let semaphore = DispatchSemaphore(value: 0)
626
- if(version != getLocalUpdateVersion()){
627
- cleanDlData()
692
+ if version != getLocalUpdateVersion() {
693
+ cleanDownloadData()
628
694
  }
629
695
  ensureResumableFilesExist()
630
696
  saveDownloadInfo(version)
631
697
  var checksum = ""
632
698
  var targetSize = -1
633
699
  var lastSentProgress = 0
634
- var totalReceivedBytes: Int64 = loadDownloadProgress() //Retrieving the amount of already downloaded data if exist, defined at 0 otherwise
635
- let requestHeaders: HTTPHeaders = ["Range": "bytes=\(totalReceivedBytes)-"]
636
- //Opening connection for streaming the bytes
637
- if(totalReceivedBytes == 0){
700
+ var totalReceivedBytes: Int64 = loadDownloadProgress() // Retrieving the amount of already downloaded data if exist, defined at 0 otherwise
701
+ let requestHeaders: HTTPHeaders = ["Range": "bytes=\(totalReceivedBytes)-"]
702
+ // Opening connection for streaming the bytes
703
+ if totalReceivedBytes == 0 {
638
704
  self.notifyDownload(id: id, percent: 0, ignoreMultipleOfTen: true)
639
705
  }
640
706
  var mainError: NSError?
641
707
  let monitor = ClosureEventMonitor()
642
- monitor.requestDidCompleteTaskWithError = { (request, task, error) in
708
+ monitor.requestDidCompleteTaskWithError = { (_, _, error) in
643
709
  if error != nil {
644
710
  print("\(self.TAG) Downloading failed - ClosureEventMonitor activated")
645
711
  mainError = error as NSError?
@@ -652,37 +718,34 @@ extension CustomError: LocalizedError {
652
718
  targetSize = (Int(contentLength) ?? -1) + Int(totalReceivedBytes)
653
719
  }
654
720
  }).responseStream { [weak self] streamResponse in
655
- guard let self = self else { return }
656
- switch streamResponse.event {
657
- case .stream(let result):
658
- if case .success(let data) = result {
659
- self.tempData.append(data)
660
-
661
- self.savePartialData(startingAt: UInt64(totalReceivedBytes)) // Saving the received data in the package.tmp file
662
- totalReceivedBytes += Int64(data.count)
663
-
664
- let percent = max(10, Int((Double(totalReceivedBytes) / Double(targetSize)) * 70.0))
665
-
666
- print("\(self.TAG) Downloading: \(percent)%")
667
- let currentMilestone = (percent / 10) * 10
668
- if currentMilestone > lastSentProgress && currentMilestone <= 70 {
669
- for milestone in stride(from: lastSentProgress + 10, through: currentMilestone, by: 10) {
670
- self.notifyDownload(id: id, percent: milestone, ignoreMultipleOfTen: false)
671
- }
672
- lastSentProgress = currentMilestone
673
- }
674
-
675
- }
676
- else {
677
- print("\(self.TAG) Download failed")
678
- }
679
-
680
-
681
- case .complete(_):
721
+ guard let self = self else { return }
722
+ switch streamResponse.event {
723
+ case .stream(let result):
724
+ if case .success(let data) = result {
725
+ self.tempData.append(data)
726
+
727
+ self.savePartialData(startingAt: UInt64(totalReceivedBytes)) // Saving the received data in the package.tmp file
728
+ totalReceivedBytes += Int64(data.count)
729
+
730
+ let percent = max(10, Int((Double(totalReceivedBytes) / Double(targetSize)) * 70.0))
731
+
732
+ let currentMilestone = (percent / 10) * 10
733
+ if currentMilestone > lastSentProgress && currentMilestone <= 70 {
734
+ for milestone in stride(from: lastSentProgress + 10, through: currentMilestone, by: 10) {
735
+ self.notifyDownload(id: id, percent: milestone, ignoreMultipleOfTen: false)
736
+ }
737
+ lastSentProgress = currentMilestone
738
+ }
739
+
740
+ } else {
741
+ print("\(self.TAG) Download failed")
742
+ }
743
+
744
+ case .complete:
682
745
  print("\(self.TAG) Download complete, total received bytes: \(totalReceivedBytes)")
683
- self.notifyDownload(id: id, percent: 70, ignoreMultipleOfTen: true)
746
+ self.notifyDownload(id: id, percent: 70, ignoreMultipleOfTen: true)
684
747
  semaphore.signal()
685
- }
748
+ }
686
749
  }
687
750
  self.saveBundleInfo(id: id, bundle: BundleInfo(id: id, version: version, status: BundleStatus.DOWNLOADING, downloaded: Date(), checksum: checksum))
688
751
  let reachabilityManager = NetworkReachabilityManager()
@@ -700,50 +763,50 @@ extension CustomError: LocalizedError {
700
763
  semaphore.wait()
701
764
  reachabilityManager?.stopListening()
702
765
 
703
- if (mainError != nil) {
766
+ if mainError != nil {
704
767
  print("\(self.TAG) Failed to download: \(String(describing: mainError))")
705
768
  self.saveBundleInfo(id: id, bundle: BundleInfo(id: id, version: version, status: BundleStatus.ERROR, downloaded: Date(), checksum: checksum))
706
769
  throw mainError!
707
770
  }
708
-
771
+
709
772
  let finalPath = tempDataPath.deletingLastPathComponent().appendingPathComponent("\(id)")
710
773
  do {
711
- let valid = try self.verifyBundleSignature(version: version, filePath: tempDataPath, signature: signature)
712
- if (!valid) {
713
- print("\(self.TAG) Invalid signature, cannot accept download")
714
- self.sendStats(action: "invalid_signature", versionName: version)
715
- throw CustomError.invalidSignature
774
+ var checksumDecrypted = checksum
775
+ if !self.hasOldPrivateKeyPropertyInConfig {
776
+ try self.decryptFileV2(filePath: tempDataPath, sessionKey: sessionKey, version: version)
716
777
  } else {
717
- print("\(self.TAG) Valid signature")
778
+ try self.decryptFile(filePath: tempDataPath, sessionKey: sessionKey, version: version)
718
779
  }
719
-
720
- try self.decryptFile(filePath: tempDataPath, sessionKey: sessionKey, version: version)
721
780
  try FileManager.default.moveItem(at: tempDataPath, to: finalPath)
722
781
  } catch {
723
- print("\(self.TAG) Failed decrypt file or verify signature or move it: \(error)")
782
+ print("\(self.TAG) Failed decrypt file : \(error)")
724
783
  self.saveBundleInfo(id: id, bundle: BundleInfo(id: id, version: version, status: BundleStatus.ERROR, downloaded: Date(), checksum: checksum))
725
- cleanDlData()
784
+ cleanDownloadData()
726
785
  throw error
727
786
  }
728
-
787
+
729
788
  do {
730
- checksum = self.calcChecksum(filePath: finalPath)
789
+ if !self.hasOldPrivateKeyPropertyInConfig && !sessionKey.isEmpty {
790
+ checksum = self.calcChecksumV2(filePath: finalPath)
791
+ } else {
792
+ checksum = self.calcChecksum(filePath: finalPath)
793
+ }
731
794
  print("\(self.TAG) Downloading: 80% (unzipping)")
732
795
  try self.saveDownloaded(sourceZip: finalPath, id: id, base: self.libraryDir.appendingPathComponent(self.bundleDirectory), notify: true)
733
-
796
+
734
797
  } catch {
735
798
  print("\(self.TAG) Failed to unzip file: \(error)")
736
799
  self.saveBundleInfo(id: id, bundle: BundleInfo(id: id, version: version, status: BundleStatus.ERROR, downloaded: Date(), checksum: checksum))
737
- cleanDlData()
800
+ cleanDownloadData()
738
801
  // todo: cleanup zip attempts
739
802
  throw error
740
803
  }
741
-
804
+
742
805
  self.notifyDownload(id: id, percent: 90)
743
806
  print("\(self.TAG) Downloading: 90% (wrapping up)")
744
807
  let info = BundleInfo(id: id, version: version, status: BundleStatus.PENDING, downloaded: Date(), checksum: checksum)
745
808
  self.saveBundleInfo(id: id, bundle: info)
746
- self.cleanDlData()
809
+ self.cleanDownloadData()
747
810
  self.notifyDownload(id: id, percent: 100)
748
811
  print("\(self.TAG) Downloading: 100% (complete)")
749
812
  return info
@@ -755,39 +818,34 @@ extension CustomError: LocalizedError {
755
818
  print("\(self.TAG) Cannot ensure that a file at \(tempDataPath.path) exists")
756
819
  }
757
820
  }
758
-
821
+
759
822
  if !fileManager.fileExists(atPath: updateInfo.path) {
760
823
  if !fileManager.createFile(atPath: updateInfo.path, contents: Data()) {
761
824
  print("\(self.TAG) Cannot ensure that a file at \(updateInfo.path) exists")
762
825
  }
763
826
  }
764
827
  }
765
-
766
- private func cleanDlData(){
828
+
829
+ private func cleanDownloadData() {
767
830
  // Deleting package.tmp
768
831
  let fileManager = FileManager.default
769
832
  if fileManager.fileExists(atPath: tempDataPath.path) {
770
- do {
771
- try fileManager.removeItem(at: tempDataPath)
772
- } catch {
773
- print("\(self.TAG) Could not delete file at \(tempDataPath): \(error)")
774
- }
775
- } else {
776
- print("\(self.TAG) \(tempDataPath.lastPathComponent) does not exist")
777
- }
778
-
779
- // Deleting update.dat
780
- if fileManager.fileExists(atPath: updateInfo.path) {
781
- do {
782
- try fileManager.removeItem(at: updateInfo)
783
- } catch {
784
- print("\(self.TAG) Could not delete file at \(updateInfo): \(error)")
785
- }
786
- } else {
787
- print("\(self.TAG) \(updateInfo.lastPathComponent) does not exist")
788
- }
789
- }
790
-
833
+ do {
834
+ try fileManager.removeItem(at: tempDataPath)
835
+ } catch {
836
+ print("\(self.TAG) Could not delete file at \(tempDataPath): \(error)")
837
+ }
838
+ }
839
+ // Deleting update.dat
840
+ if fileManager.fileExists(atPath: updateInfo.path) {
841
+ do {
842
+ try fileManager.removeItem(at: updateInfo)
843
+ } catch {
844
+ print("\(self.TAG) Could not delete file at \(updateInfo): \(error)")
845
+ }
846
+ }
847
+ }
848
+
791
849
  private func savePartialData(startingAt byteOffset: UInt64) {
792
850
  let fileManager = FileManager.default
793
851
  do {
@@ -807,7 +865,6 @@ extension CustomError: LocalizedError {
807
865
  self.tempData.removeAll() // Clearing tempData to avoid writing the same data multiple times
808
866
  }
809
867
 
810
-
811
868
  private func saveDownloadInfo(_ version: String) {
812
869
  do {
813
870
  try "\(version)".write(to: updateInfo, atomically: true, encoding: .utf8)
@@ -815,34 +872,30 @@ extension CustomError: LocalizedError {
815
872
  print("\(self.TAG) Failed to save progress: \(error)")
816
873
  }
817
874
  }
818
- private func getLocalUpdateVersion() -> String { //Return the version that was tried to be downloaded on last download attempt
875
+ private func getLocalUpdateVersion() -> String { // Return the version that was tried to be downloaded on last download attempt
819
876
  if !FileManager.default.fileExists(atPath: updateInfo.path) {
820
877
  return "nil"
821
878
  }
822
- guard let versionString = try? String(contentsOf: updateInfo),
823
- let version = Optional(versionString) else {
824
- return "nil"
825
- }
826
- return version
827
- }
879
+ guard let versionString = try? String(contentsOf: updateInfo),
880
+ let version = Optional(versionString) else {
881
+ return "nil"
882
+ }
883
+ return version
884
+ }
828
885
  private func loadDownloadProgress() -> Int64 {
829
-
886
+
830
887
  let fileManager = FileManager.default
831
- do {
832
- let attributes = try fileManager.attributesOfItem(atPath: tempDataPath.path)
833
- if let fileSize = attributes[.size] as? NSNumber {
834
- return fileSize.int64Value
835
- }
836
- } catch {
837
- print("\(self.TAG) Could not retrieve already downloaded data size : \(error)")
838
- }
839
- return 0
888
+ do {
889
+ let attributes = try fileManager.attributesOfItem(atPath: tempDataPath.path)
890
+ if let fileSize = attributes[.size] as? NSNumber {
891
+ return fileSize.int64Value
892
+ }
893
+ } catch {
894
+ print("\(self.TAG) Could not retrieve already downloaded data size : \(error)")
895
+ }
896
+ return 0
840
897
  }
841
898
 
842
-
843
-
844
-
845
-
846
899
  public func list() -> [BundleInfo] {
847
900
  let dest: URL = libraryDir.appendingPathComponent(bundleDirectory)
848
901
  do {
@@ -1100,34 +1153,30 @@ extension CustomError: LocalizedError {
1100
1153
  parameters.action = action
1101
1154
  parameters.version_name = versionName
1102
1155
  parameters.old_version_name = oldVersionName ?? ""
1103
-
1104
-
1156
+
1105
1157
  let operation = BlockOperation {
1106
1158
  let semaphore = DispatchSemaphore(value: 0)
1107
1159
  AF.request(
1108
- self.statsUrl,
1109
- method: .post,
1110
- parameters: parameters,
1111
- encoder: JSONParameterEncoder.default,
1112
- requestModifier: { $0.timeoutInterval = self.timeout }
1113
- ).responseData { response in
1114
- switch response.result {
1115
- case .success:
1116
- print("\(self.TAG) Stats sent for \(action), version \(versionName)")
1117
- case let .failure(error):
1118
- print("\(self.TAG) Error sending stats: ", response.value ?? "", error.localizedDescription)
1119
- }
1120
- semaphore.signal()
1121
- }
1122
- semaphore.wait()
1123
- }
1160
+ self.statsUrl,
1161
+ method: .post,
1162
+ parameters: parameters,
1163
+ encoder: JSONParameterEncoder.default,
1164
+ requestModifier: { $0.timeoutInterval = self.timeout }
1165
+ ).responseData { response in
1166
+ switch response.result {
1167
+ case .success:
1168
+ print("\(self.TAG) Stats sent for \(action), version \(versionName)")
1169
+ case let .failure(error):
1170
+ print("\(self.TAG) Error sending stats: ", response.value ?? "", error.localizedDescription)
1171
+ }
1172
+ semaphore.signal()
1173
+ }
1174
+ semaphore.wait()
1175
+ }
1124
1176
  operationQueue.addOperation(operation)
1125
-
1126
1177
 
1127
-
1128
1178
  }
1129
1179
 
1130
-
1131
1180
  public func getBundleInfo(id: String?) -> BundleInfo {
1132
1181
  var trueId = BundleInfo.VERSION_UNKNOWN
1133
1182
  if id != nil {