@capgo/capacitor-updater 6.1.2 → 6.1.4

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.
@@ -56,7 +56,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
56
56
  private static final String channelUrlDefault =
57
57
  "https://api.capgo.app/channel_self";
58
58
 
59
- private final String PLUGIN_VERSION = "6.1.2";
59
+ private final String PLUGIN_VERSION = "6.1.4";
60
60
  private static final String DELAY_CONDITION_PREFERENCES = "";
61
61
 
62
62
  private SharedPreferences.Editor editor;
@@ -3,17 +3,15 @@
3
3
  * License, v. 2.0. If a copy of the MPL was not distributed with this
4
4
  * file, You can obtain one at https://mozilla.org/MPL/2.0/.
5
5
  */
6
-
7
6
  package ee.forgr.capacitor_updater;
8
7
 
9
8
  import android.app.IntentService;
10
9
  import android.content.Intent;
11
- import java.io.DataInputStream;
12
- import java.io.File;
13
- import java.io.FileOutputStream;
14
- import java.io.InputStream;
10
+ import java.io.*;
11
+ import java.net.HttpURLConnection;
15
12
  import java.net.URL;
16
13
  import java.net.URLConnection;
14
+ import java.nio.channels.FileChannel;
17
15
  import java.util.Objects;
18
16
 
19
17
  public class DownloadService extends IntentService {
@@ -30,20 +28,22 @@ public class DownloadService extends IntentService {
30
28
  public static final String SIGNATURE = "signature";
31
29
  public static final String NOTIFICATION = "service receiver";
32
30
  public static final String PERCENTDOWNLOAD = "percent receiver";
31
+ private static final String UPDATE_FILE = "update.dat";
33
32
 
34
33
  public DownloadService() {
35
34
  super("Background DownloadService");
36
35
  }
37
36
 
38
- private int calcTotalPercent(
39
- final int percent,
40
- final int min,
41
- final int max
42
- ) {
43
- return (percent * (max - min)) / 100 + min;
37
+ private int calcTotalPercent(long downloadedBytes, long contentLength) {
38
+ if (contentLength <= 0) {
39
+ return 0;
40
+ }
41
+ int percent = (int) (((double) downloadedBytes / contentLength) * 100);
42
+ percent = Math.max(10, percent);
43
+ percent = Math.min(70, percent);
44
+ return percent;
44
45
  }
45
46
 
46
- // Will be called asynchronously by OS.
47
47
  @Override
48
48
  protected void onHandleIntent(Intent intent) {
49
49
  assert intent != null;
@@ -56,47 +56,95 @@ public class DownloadService extends IntentService {
56
56
  String checksum = intent.getStringExtra(CHECKSUM);
57
57
  String signature = intent.getStringExtra(SIGNATURE);
58
58
 
59
+ File target = new File(documentsDir, dest);
60
+ File infoFile = new File(documentsDir, UPDATE_FILE); // The file where the download progress (how much byte
61
+ // downloaded) is stored
62
+ File tempFile = new File(documentsDir, "temp" + ".tmp"); // Temp file, where the downloaded data is stored
59
63
  try {
60
- final URL u = new URL(url);
61
- final URLConnection connection = u.openConnection();
64
+ URL u = new URL(url);
65
+ HttpURLConnection httpConn = (HttpURLConnection) u.openConnection();
66
+
67
+ // Reading progress file (if exist)
68
+ long downloadedBytes = 0;
69
+
70
+ if (infoFile.exists() && tempFile.exists()) {
71
+ try (
72
+ BufferedReader reader = new BufferedReader(new FileReader(infoFile))
73
+ ) {
74
+ String updateVersion = reader.readLine();
75
+ if (!updateVersion.equals(version)) {
76
+ clearDownloadData(documentsDir);
77
+ downloadedBytes = 0;
78
+ } else {
79
+ downloadedBytes = tempFile.length();
80
+ }
81
+ }
82
+ } else {
83
+ clearDownloadData(documentsDir);
84
+ downloadedBytes = 0;
85
+ }
86
+
87
+ if (downloadedBytes > 0) {
88
+ httpConn.setRequestProperty("Range", "bytes=" + downloadedBytes + "-");
89
+ }
90
+
91
+ int responseCode = httpConn.getResponseCode();
62
92
 
63
- try (
64
- final InputStream is = u.openStream();
65
- final DataInputStream dis = new DataInputStream(is)
93
+ if (
94
+ responseCode == HttpURLConnection.HTTP_OK ||
95
+ responseCode == HttpURLConnection.HTTP_PARTIAL
66
96
  ) {
67
- assert dest != null;
68
- final File target = new File(documentsDir, dest);
69
- Objects.requireNonNull(target.getParentFile()).mkdirs();
70
- target.createNewFile();
71
- try (final FileOutputStream fos = new FileOutputStream(target)) {
72
- final long totalLength = connection.getContentLength();
73
- final int bufferSize = 1024;
74
- final byte[] buffer = new byte[bufferSize];
75
- int length;
76
-
77
- int bytesRead = bufferSize;
78
- int percent = 0;
79
- this.notifyDownload(id, 10);
80
- while ((length = dis.read(buffer)) > 0) {
81
- fos.write(buffer, 0, length);
82
- final int newPercent = (int) ((bytesRead * 100) / totalLength);
83
- if (totalLength > 1 && newPercent != percent) {
84
- percent = newPercent;
85
- this.notifyDownload(id, this.calcTotalPercent(percent, 10, 70));
86
- }
87
- bytesRead += length;
97
+ String contentType = httpConn.getContentType();
98
+ long contentLength = httpConn.getContentLength() + downloadedBytes;
99
+
100
+ InputStream inputStream = httpConn.getInputStream();
101
+ FileOutputStream outputStream = new FileOutputStream(
102
+ tempFile,
103
+ downloadedBytes > 0
104
+ );
105
+ if (downloadedBytes == 0) {
106
+ try (
107
+ BufferedWriter writer = new BufferedWriter(new FileWriter(infoFile))
108
+ ) {
109
+ writer.write(String.valueOf(version));
88
110
  }
89
- publishResults(
90
- dest,
91
- id,
92
- version,
93
- checksum,
94
- sessionKey,
95
- signature,
96
- ""
97
- );
98
111
  }
112
+ // Updating the info file
113
+ try (
114
+ BufferedWriter writer = new BufferedWriter(new FileWriter(infoFile))
115
+ ) {
116
+ writer.write(String.valueOf(version));
117
+ }
118
+
119
+ int bytesRead = -1;
120
+ byte[] buffer = new byte[4096];
121
+ int lastPercent = 0;
122
+ while ((bytesRead = inputStream.read(buffer)) != -1) {
123
+ outputStream.write(buffer, 0, bytesRead);
124
+ downloadedBytes += bytesRead;
125
+ // Saving progress (flushing every 100 Ko)
126
+ if (downloadedBytes % 102400 == 0) {
127
+ outputStream.flush();
128
+ }
129
+ // Computing percentage
130
+ int percent = calcTotalPercent(downloadedBytes, contentLength);
131
+ if (percent != lastPercent) {
132
+ notifyDownload(id, percent);
133
+ lastPercent = percent;
134
+ }
135
+ }
136
+
137
+ outputStream.close();
138
+ inputStream.close();
139
+
140
+ // Rename the temp file with the final name (dest)
141
+ tempFile.renameTo(new File(documentsDir, dest));
142
+ infoFile.delete();
143
+ publishResults(dest, id, version, checksum, sessionKey, signature, "");
144
+ } else {
145
+ infoFile.delete();
99
146
  }
147
+ httpConn.disconnect();
100
148
  } catch (OutOfMemoryError e) {
101
149
  e.printStackTrace();
102
150
  publishResults(
@@ -122,6 +170,19 @@ public class DownloadService extends IntentService {
122
170
  }
123
171
  }
124
172
 
173
+ private void clearDownloadData(String docDir) {
174
+ File tempFile = new File(docDir, "temp" + ".tmp");
175
+ File infoFile = new File(docDir, UPDATE_FILE);
176
+ try {
177
+ tempFile.delete();
178
+ infoFile.delete();
179
+ infoFile.createNewFile();
180
+ tempFile.createNewFile();
181
+ } catch (IOException e) {
182
+ e.printStackTrace();
183
+ }
184
+ }
185
+
125
186
  private void notifyDownload(String id, int percent) {
126
187
  Intent intent = new Intent(PERCENTDOWNLOAD);
127
188
  intent.setPackage(getPackageName());
@@ -144,14 +205,11 @@ public class DownloadService extends IntentService {
144
205
  if (dest != null && !dest.isEmpty()) {
145
206
  intent.putExtra(FILEDEST, dest);
146
207
  }
147
- if (error != null && !error.isEmpty()) {
148
- intent.putExtra(ERROR, error);
149
- }
208
+ intent.putExtra(ERROR, error);
150
209
  intent.putExtra(ID, id);
151
210
  intent.putExtra(VERSION, version);
152
211
  intent.putExtra(SESSIONKEY, sessionKey);
153
212
  intent.putExtra(CHECKSUM, checksum);
154
- intent.putExtra(ERROR, error);
155
213
  intent.putExtra(SIGNATURE, signature);
156
214
  sendBroadcast(intent);
157
215
  }
@@ -237,7 +237,7 @@ extension CustomError: LocalizedError {
237
237
  }
238
238
 
239
239
  @objc public class CapacitorUpdater: NSObject {
240
-
240
+
241
241
  private let versionCode: String = Bundle.main.versionCode ?? ""
242
242
  private let versionOs = UIDevice.current.systemVersion
243
243
  private let libraryDir: URL = FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask).first!
@@ -247,7 +247,7 @@ extension CustomError: LocalizedError {
247
247
  private let FALLBACK_VERSION: String = "pastVersion"
248
248
  private let NEXT_VERSION: String = "nextVersion"
249
249
  private var unzipPercent = 0
250
-
250
+
251
251
  public let TAG: String = "✨ Capacitor-updater:"
252
252
  public let CAP_SERVER_PATH: String = "serverBasePath"
253
253
  public var versionBuild: String = ""
@@ -261,7 +261,11 @@ extension CustomError: LocalizedError {
261
261
  public var deviceID = UIDevice.current.identifierForVendor?.uuidString ?? ""
262
262
  public var privateKey: String = ""
263
263
  public var signKey: PublicKey?
264
-
264
+
265
+ public var notifyDownloadRaw: (String, Int, Bool) -> Void = { _, _, _ in }
266
+ public func notifyDownload(id: String, percent: Int, ignoreMultipleOfTen: Bool = false) {
267
+ notifyDownloadRaw(id, percent, ignoreMultipleOfTen)
268
+ }
265
269
  public var notifyDownload: (String, Int) -> Void = { _, _ in }
266
270
 
267
271
  private func calcTotalPercent(percent: Int, min: Int, max: Int) -> Int {
@@ -356,34 +360,7 @@ extension CustomError: LocalizedError {
356
360
  }
357
361
  }
358
362
 
359
- private func calcChecksum(filePath: URL) -> String {
360
- let bufferSize = 1024 * 1024 * 5 // 5 MB
361
- var checksum = uLong(0)
362
-
363
- do {
364
- let fileHandle = try FileHandle(forReadingFrom: filePath)
365
- defer {
366
- fileHandle.closeFile()
367
- }
368
363
 
369
- while autoreleasepool(invoking: {
370
- let fileData = fileHandle.readData(ofLength: bufferSize)
371
- if fileData.count > 0 {
372
- checksum = fileData.withUnsafeBytes {
373
- crc32(checksum, $0.bindMemory(to: Bytef.self).baseAddress, uInt(fileData.count))
374
- }
375
- return true // Continue
376
- } else {
377
- return false // End of file
378
- }
379
- }) {}
380
-
381
- return String(format: "%08X", checksum).lowercased()
382
- } catch {
383
- print("\(self.TAG) Cannot calc checksum: \(filePath.path)", error)
384
- return ""
385
- }
386
- }
387
364
 
388
365
  private func verifyBundleSignature(version: String, filePath: URL, signature: String?) throws -> Bool {
389
366
  if (self.signKey == nil) {
@@ -447,6 +424,7 @@ extension CustomError: LocalizedError {
447
424
  }
448
425
 
449
426
  try decryptedData.write(to: filePath)
427
+
450
428
  } catch {
451
429
  print("\(self.TAG) Cannot decode: \(filePath.path)", error)
452
430
  self.sendStats(action: "decrypt_fail", versionName: version)
@@ -485,7 +463,7 @@ extension CustomError: LocalizedError {
485
463
  let newPercent = self.calcTotalPercent(percent: Int(Double(entryNumber) / Double(total) * 100), min: 75, max: 81)
486
464
  if newPercent != self.unzipPercent {
487
465
  self.unzipPercent = newPercent
488
- self.notifyDownload(id, newPercent)
466
+ self.notifyDownload(id: id, percent: newPercent)
489
467
  }
490
468
  }
491
469
 
@@ -495,7 +473,7 @@ extension CustomError: LocalizedError {
495
473
  let destUnZip: URL = libraryDir.appendingPathComponent(randomString(length: 10))
496
474
 
497
475
  self.unzipPercent = 0
498
- self.notifyDownload(id, 75)
476
+ self.notifyDownload(id: id, percent: 75)
499
477
 
500
478
  let semaphore = DispatchSemaphore(value: 0)
501
479
  var unzipError: NSError?
@@ -605,81 +583,107 @@ extension CustomError: LocalizedError {
605
583
  UserDefaults.standard.synchronize()
606
584
  print("\(self.TAG) Current bundle set to: \((bundle ).isEmpty ? BundleInfo.ID_BUILTIN : bundle)")
607
585
  }
586
+ private func calcChecksum(filePath: URL) -> String {
587
+ let bufferSize = 1024 * 1024 * 5 // 5 MB
588
+ var checksum = uLong(0)
608
589
 
609
- public func download(url: URL, version: String, sessionKey: String, signature: String?) throws -> BundleInfo {
610
- let semaphore: DispatchSemaphore = DispatchSemaphore(value: 0)
611
- let id: String = self.randomString(length: 10)
612
- var checksum: String = ""
590
+ do {
591
+ let fileHandle = try FileHandle(forReadingFrom: filePath)
592
+ defer {
593
+ fileHandle.closeFile()
594
+ }
613
595
 
614
- var mainError: NSError?
615
- let destination: DownloadRequest.Destination = { _, _ in
616
- let documentsURL: URL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
617
- let fileURL: URL = documentsURL.appendingPathComponent(self.randomString(length: 10))
596
+ while autoreleasepool(invoking: {
597
+ let fileData = fileHandle.readData(ofLength: bufferSize)
598
+ if fileData.count > 0 {
599
+ checksum = fileData.withUnsafeBytes {
600
+ crc32(checksum, $0.bindMemory(to: Bytef.self).baseAddress, uInt(fileData.count))
601
+ }
602
+ return true // Continue
603
+ } else {
604
+ return false // End of file
605
+ }
606
+ }) {}
618
607
 
619
- return (fileURL, [.removePreviousFile, .createIntermediateDirectories])
608
+ return String(format: "%08X", checksum).lowercased()
609
+ } catch {
610
+ print("\(self.TAG) Cannot get checksum: \(filePath.path)", error)
611
+ return ""
620
612
  }
621
- let request = AF.download(url, to: destination)
613
+ }
614
+
615
+ private var tempDataPath: URL {
616
+ return FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("package.tmp")
617
+ }
622
618
 
623
- request.downloadProgress { progress in
624
- let percent = self.calcTotalPercent(percent: Int(progress.fractionCompleted * 100), min: 10, max: 70)
625
- self.notifyDownload(id, percent)
619
+ private var updateInfo: URL {
620
+ return FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("update.dat")
621
+ }
622
+ private var tempData = Data()
623
+ public func download(url: URL, version: String, sessionKey: String, signature: String) throws -> BundleInfo {
624
+ let id: String = self.randomString(length: 10)
625
+ let semaphore = DispatchSemaphore(value: 0)
626
+ if(version != getLocalUpdateVersion()){
627
+ cleanDlData()
626
628
  }
627
- request.responseURL(queue: .global(qos: .background), completionHandler: { (response) in
628
- if let fileURL = response.fileURL {
629
- switch response.result {
630
- case .success:
631
- self.notifyDownload(id, 71)
632
-
633
- // The reason we do 2 blocks of try/catch is that in the first one we will try to cleanup afterwards. Cleanup wlll NOT happen in the second one
634
-
635
- do {
636
- let valid = try self.verifyBundleSignature(version: version, filePath: fileURL, signature: signature)
637
- if (!valid) {
638
- print("\(self.TAG) Invalid signature, cannot accept download")
639
- self.sendStats(action: "invalid_signature", versionName: version)
640
- throw CustomError.invalidSignature
641
- } else {
642
- print("\(self.TAG) Valid signature")
643
- }
644
-
645
- try self.decryptFile(filePath: fileURL, sessionKey: sessionKey, version: version)
646
- checksum = self.calcChecksum(filePath: fileURL)
647
- } catch {
648
- print("\(self.TAG) downloaded file verification error", error)
649
- mainError = error as NSError
650
-
651
- // Cleanup
652
- do {
653
- try self.deleteFolder(source: fileURL)
654
- } catch {
655
- print("\(self.TAG) Double error, cannot cleanup", error)
656
- }
657
- }
658
-
659
- do {
660
- if (mainError == nil) {
661
- try self.saveDownloaded(sourceZip: fileURL, id: id, base: self.libraryDir.appendingPathComponent(self.bundleDirectory), notify: true)
662
- try self.deleteFolder(source: fileURL)
663
- self.notifyDownload(id, 100)
664
- }
665
- } catch {
666
- print("\(self.TAG) download unzip error", error)
667
- mainError = error as NSError
668
- }
669
- case let .failure(error):
670
- print("\(self.TAG) download error", response.value ?? "", error)
671
- if let afError = error as? AFError,
672
- case .sessionTaskFailed(let urlError as URLError) = afError,
673
- urlError.code == .cannotWriteToFile {
674
- self.sendStats(action: "low_mem_fail", versionName: version)
675
- }
676
- mainError = error as NSError
677
- }
629
+ ensureResumableFilesExist()
630
+ saveDownloadInfo(version)
631
+ var checksum = ""
632
+ var targetSize = -1
633
+ 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){
638
+ self.notifyDownload(id: id, percent: 0, ignoreMultipleOfTen: true)
639
+ }
640
+ var mainError: NSError?
641
+ let monitor = ClosureEventMonitor()
642
+ monitor.requestDidCompleteTaskWithError = { (request, task, error) in
643
+ if error != nil {
644
+ print("\(self.TAG) Downloading failed - ClosureEventMonitor activated")
645
+ mainError = error as NSError?
678
646
  }
679
- semaphore.signal()
680
- })
647
+ }
648
+ let session = Session(eventMonitors: [monitor])
649
+
650
+ var request = session.streamRequest(url, headers: requestHeaders).validate().onHTTPResponse(perform: { response in
651
+ if let contentLength = response.headers.value(for: "Content-Length") {
652
+ targetSize = (Int(contentLength) ?? -1) + Int(totalReceivedBytes)
653
+ }
654
+ }).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 = Int((Double(totalReceivedBytes) / Double(targetSize)) * 100.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: true)
671
+ }
672
+ lastSentProgress = currentMilestone
673
+ }
674
+
675
+ }
676
+ else {
677
+ print("\(self.TAG) Download failed")
678
+ }
679
+
680
+
681
+ case .complete(_):
682
+ print("\(self.TAG) Download complete, total received bytes: \(totalReceivedBytes)")
683
+ semaphore.signal()
684
+ }
685
+ }
681
686
  self.saveBundleInfo(id: id, bundle: BundleInfo(id: id, version: version, status: BundleStatus.DOWNLOADING, downloaded: Date(), checksum: checksum))
682
- self.notifyDownload(id, 0)
683
687
  let reachabilityManager = NetworkReachabilityManager()
684
688
  reachabilityManager?.startListening { status in
685
689
  switch status {
@@ -694,13 +698,144 @@ extension CustomError: LocalizedError {
694
698
  }
695
699
  semaphore.wait()
696
700
  reachabilityManager?.stopListening()
697
- if let error = mainError {
701
+
702
+ if (mainError != nil) {
703
+ print("\(self.TAG) Failed to download: \(String(describing: mainError))")
704
+ self.saveBundleInfo(id: id, bundle: BundleInfo(id: id, version: version, status: BundleStatus.ERROR, downloaded: Date(), checksum: checksum))
705
+ throw mainError!
706
+ }
707
+
708
+ let finalPath = tempDataPath.deletingLastPathComponent().appendingPathComponent("\(id)")
709
+ do {
710
+ let valid = try self.verifyBundleSignature(version: version, filePath: tempDataPath, signature: signature)
711
+ if (!valid) {
712
+ print("\(self.TAG) Invalid signature, cannot accept download")
713
+ self.sendStats(action: "invalid_signature", versionName: version)
714
+ throw CustomError.invalidSignature
715
+ } else {
716
+ print("\(self.TAG) Valid signature")
717
+ }
718
+
719
+ try self.decryptFile(filePath: tempDataPath, sessionKey: sessionKey, version: version)
720
+ try FileManager.default.moveItem(at: tempDataPath, to: finalPath)
721
+ } catch {
722
+ print("\(self.TAG) Failed decrypt file or verify signature or move it: \(error)")
723
+ self.saveBundleInfo(id: id, bundle: BundleInfo(id: id, version: version, status: BundleStatus.ERROR, downloaded: Date(), checksum: checksum))
724
+ cleanDlData()
725
+ throw error
726
+ }
727
+
728
+ do {
729
+ checksum = self.calcChecksum(filePath: finalPath)
730
+ try self.saveDownloaded(sourceZip: finalPath, id: id, base: self.libraryDir.appendingPathComponent(self.bundleDirectory), notify: true)
731
+ } catch {
732
+ print("\(self.TAG) Failed to unzip file: \(error)")
733
+ self.saveBundleInfo(id: id, bundle: BundleInfo(id: id, version: version, status: BundleStatus.ERROR, downloaded: Date(), checksum: checksum))
734
+ cleanDlData()
735
+ // todo: cleanup zip attempts
698
736
  throw error
699
737
  }
700
- let info: BundleInfo = BundleInfo(id: id, version: version, status: BundleStatus.PENDING, downloaded: Date(), checksum: checksum)
738
+
739
+ let info = BundleInfo(id: id, version: version, status: BundleStatus.PENDING, downloaded: Date(), checksum: checksum)
701
740
  self.saveBundleInfo(id: id, bundle: info)
741
+ self.notifyDownload(id: id, percent: 100)
742
+ self.cleanDlData()
702
743
  return info
703
744
  }
745
+ private func ensureResumableFilesExist() {
746
+ let fileManager = FileManager.default
747
+ if !fileManager.fileExists(atPath: tempDataPath.path) {
748
+ if !fileManager.createFile(atPath: tempDataPath.path, contents: Data()) {
749
+ print("\(self.TAG) Cannot ensure that a file at \(tempDataPath.path) exists")
750
+ }
751
+ }
752
+
753
+ if !fileManager.fileExists(atPath: updateInfo.path) {
754
+ if !fileManager.createFile(atPath: updateInfo.path, contents: Data()) {
755
+ print("\(self.TAG) Cannot ensure that a file at \(updateInfo.path) exists")
756
+ }
757
+ }
758
+ }
759
+
760
+ private func cleanDlData(){
761
+ // Deleting package.tmp
762
+ let fileManager = FileManager.default
763
+ if fileManager.fileExists(atPath: tempDataPath.path) {
764
+ do {
765
+ try fileManager.removeItem(at: tempDataPath)
766
+ } catch {
767
+ print("\(self.TAG) Could not delete file at \(tempDataPath): \(error)")
768
+ }
769
+ } else {
770
+ print("\(self.TAG) \(tempDataPath.lastPathComponent) does not exist")
771
+ }
772
+
773
+ // Deleting update.dat
774
+ if fileManager.fileExists(atPath: updateInfo.path) {
775
+ do {
776
+ try fileManager.removeItem(at: updateInfo)
777
+ } catch {
778
+ print("\(self.TAG) Could not delete file at \(updateInfo): \(error)")
779
+ }
780
+ } else {
781
+ print("\(self.TAG) \(updateInfo.lastPathComponent) does not exist")
782
+ }
783
+ }
784
+
785
+ private func savePartialData(startingAt byteOffset: UInt64) {
786
+ let fileManager = FileManager.default
787
+ do {
788
+ // Check if package.tmp exist
789
+ if !fileManager.fileExists(atPath: tempDataPath.path) {
790
+ try self.tempData.write(to: tempDataPath, options: .atomicWrite)
791
+ } else {
792
+ // If yes, it start writing on it
793
+ let fileHandle = try FileHandle(forWritingTo: tempDataPath)
794
+ fileHandle.seek(toFileOffset: byteOffset) // Moving at the specified position to start writing
795
+ fileHandle.write(self.tempData)
796
+ fileHandle.closeFile()
797
+ }
798
+ } catch {
799
+ print("Failed to write data starting at byte \(byteOffset): \(error)")
800
+ }
801
+ self.tempData.removeAll() // Clearing tempData to avoid writing the same data multiple times
802
+ }
803
+
804
+
805
+ private func saveDownloadInfo(_ version: String) {
806
+ do {
807
+ try "\(version)".write(to: updateInfo, atomically: true, encoding: .utf8)
808
+ } catch {
809
+ print("\(self.TAG) Failed to save progress: \(error)")
810
+ }
811
+ }
812
+ private func getLocalUpdateVersion() -> String { //Return the version that was tried to be downloaded on last download attempt
813
+ if !FileManager.default.fileExists(atPath: updateInfo.path) {
814
+ return "nil"
815
+ }
816
+ guard let versionString = try? String(contentsOf: updateInfo),
817
+ let version = Optional(versionString) else {
818
+ return "nil"
819
+ }
820
+ return version
821
+ }
822
+ private func loadDownloadProgress() -> Int64 {
823
+
824
+ let fileManager = FileManager.default
825
+ do {
826
+ let attributes = try fileManager.attributesOfItem(atPath: tempDataPath.path)
827
+ if let fileSize = attributes[.size] as? NSNumber {
828
+ return fileSize.int64Value
829
+ }
830
+ } catch {
831
+ print("\(self.TAG) Could not retrieve already downloaded data size : \(error)")
832
+ }
833
+ return 0
834
+ }
835
+
836
+
837
+
838
+
704
839
 
705
840
  public func list() -> [BundleInfo] {
706
841
  let dest: URL = libraryDir.appendingPathComponent(bundleDirectory)
@@ -16,7 +16,7 @@ import SwiftyRSA
16
16
  @objc(CapacitorUpdaterPlugin)
17
17
  public class CapacitorUpdaterPlugin: CAPPlugin {
18
18
  public var implementation = CapacitorUpdater()
19
- private let PLUGIN_VERSION: String = "6.1.2"
19
+ private let PLUGIN_VERSION: String = "6.1.4"
20
20
  static let updateUrlDefault = "https://api.capgo.app/updates"
21
21
  static let statsUrlDefault = "https://api.capgo.app/stats"
22
22
  static let channelUrlDefault = "https://api.capgo.app/channel_self"
@@ -74,7 +74,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin {
74
74
  }
75
75
 
76
76
  implementation.privateKey = getConfig().getString("privateKey", self.defaultPrivateKey)!
77
- implementation.notifyDownload = notifyDownload
77
+ implementation.notifyDownloadRaw = notifyDownload
78
78
  implementation.PLUGIN_VERSION = self.PLUGIN_VERSION
79
79
  let config = (self.bridge?.viewController as? CAPBridgeViewController)?.instanceDescriptor().legacyConfig
80
80
  implementation.appId = Bundle.main.infoDictionary?["CFBundleIdentifier"] as? String ?? ""
@@ -173,13 +173,13 @@ public class CapacitorUpdaterPlugin: CAPPlugin {
173
173
  UserDefaults.standard.synchronize()
174
174
  }
175
175
 
176
- @objc func notifyDownload(id: String, percent: Int) {
176
+ @objc func notifyDownload(id: String, percent: Int, ignoreMultipleOfTen: Bool = false) {
177
177
  let bundle = self.implementation.getBundleInfo(id: id)
178
178
  self.notifyListeners("download", data: ["percent": percent, "bundle": bundle.toJSON()])
179
179
  if percent == 100 {
180
180
  self.notifyListeners("downloadComplete", data: ["bundle": bundle.toJSON()])
181
181
  self.implementation.sendStats(action: "download_complete", versionName: bundle.getVersionName())
182
- } else if percent.isMultiple(of: 10) {
182
+ } else if percent.isMultiple(of: 10) || ignoreMultipleOfTen {
183
183
  self.implementation.sendStats(action: "download_\(percent)", versionName: bundle.getVersionName())
184
184
  }
185
185
  }
@@ -721,6 +721,9 @@ public class CapacitorUpdaterPlugin: CAPPlugin {
721
721
  print("\(self.implementation.TAG) Failed to delete failed bundle: \(nextImpl!.toString())")
722
722
  }
723
723
  }
724
+ guard let signature = signature else {
725
+ throw CustomError.signatureNotProvided
726
+ }
724
727
  nextImpl = try self.implementation.download(url: downloadUrl, version: latestVersionName, sessionKey: sessionKey, signature: signature)
725
728
  }
726
729
  guard let next = nextImpl else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/capacitor-updater",
3
- "version": "6.1.2",
3
+ "version": "6.1.4",
4
4
  "license": "MPL-2.0",
5
5
  "description": "Live update for capacitor apps",
6
6
  "main": "dist/plugin.cjs.js",