@capgo/capacitor-updater 8.51.18 → 8.51.20

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.
@@ -146,7 +146,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
146
146
  static final int APPLICATION_EXIT_REASON_USER_REQUESTED = 10;
147
147
  static final int APPLICATION_EXIT_REASON_DEPENDENCY_DIED = 12;
148
148
 
149
- private final String pluginVersion = "8.51.18";
149
+ private final String pluginVersion = "8.51.20";
150
150
  private static final String DELAY_CONDITION_PREFERENCES = "";
151
151
 
152
152
  private SharedPreferences.Editor editor;
@@ -14,6 +14,7 @@ import androidx.work.WorkerParameters;
14
14
  import java.io.*;
15
15
  import java.io.FileInputStream;
16
16
  import java.net.HttpURLConnection;
17
+ import java.net.MalformedURLException;
17
18
  import java.net.URL;
18
19
  import java.nio.channels.FileChannel;
19
20
  import java.security.MessageDigest;
@@ -30,6 +31,8 @@ import java.util.concurrent.Future;
30
31
  import java.util.concurrent.TimeUnit;
31
32
  import java.util.concurrent.atomic.AtomicBoolean;
32
33
  import java.util.concurrent.atomic.AtomicLong;
34
+ import java.util.function.BooleanSupplier;
35
+ import java.util.function.LongConsumer;
33
36
  import okhttp3.Call;
34
37
  import okhttp3.Callback;
35
38
  import okhttp3.Dispatcher;
@@ -47,6 +50,41 @@ import org.json.JSONObject;
47
50
 
48
51
  public class DownloadService extends Worker {
49
52
 
53
+ static final class DownloadRetryException extends RuntimeException {
54
+
55
+ DownloadRetryException(String message) {
56
+ super(message);
57
+ }
58
+
59
+ DownloadRetryException(String message, Throwable cause) {
60
+ super(message, cause);
61
+ }
62
+ }
63
+
64
+ static final class ZipWritePlan {
65
+
66
+ final int statusCode;
67
+ final long writeOffset;
68
+
69
+ ZipWritePlan(int statusCode, long writeOffset) {
70
+ this.statusCode = statusCode;
71
+ this.writeOffset = writeOffset;
72
+ }
73
+ }
74
+
75
+ static final class ContentRangeInfo {
76
+
77
+ final long start;
78
+ final long end;
79
+ final long total;
80
+
81
+ ContentRangeInfo(long start, long end, long total) {
82
+ this.start = start;
83
+ this.end = end;
84
+ this.total = total;
85
+ }
86
+ }
87
+
50
88
  private static Logger logger;
51
89
 
52
90
  public static void setLogger(Logger loggerInstance) {
@@ -389,6 +427,11 @@ public class DownloadService extends Worker {
389
427
  handleSingleFileDownload(url, id, documentsDir, dest, version, sessionKey, checksum);
390
428
  return createSuccessResult(dest, version, sessionKey, checksum, false);
391
429
  }
430
+ } catch (DownloadRetryException e) {
431
+ if (logger != null) {
432
+ logger.debug("Download will retry: " + e.getMessage());
433
+ }
434
+ return Result.retry();
392
435
  } catch (Exception e) {
393
436
  return createFailureResult(e.getMessage());
394
437
  }
@@ -652,7 +695,6 @@ public class DownloadService extends Worker {
652
695
 
653
696
  HttpURLConnection httpConn = null;
654
697
  InputStream inputStream = null;
655
- FileOutputStream outputStream = null;
656
698
  BufferedReader reader = null;
657
699
  BufferedWriter writer = null;
658
700
 
@@ -689,6 +731,10 @@ public class DownloadService extends Worker {
689
731
  clearDownloadData(documentsDir, id);
690
732
  }
691
733
 
734
+ if (isStopped()) {
735
+ throw new DownloadRetryException("download_stopped");
736
+ }
737
+
692
738
  if (downloadedBytes > 0) {
693
739
  httpConn.setRequestProperty("Range", "bytes=" + downloadedBytes + "-");
694
740
  }
@@ -696,7 +742,11 @@ public class DownloadService extends Worker {
696
742
  int responseCode = httpConn.getResponseCode();
697
743
 
698
744
  if (responseCode == HttpURLConnection.HTTP_OK || responseCode == HttpURLConnection.HTTP_PARTIAL) {
699
- long contentLength = httpConn.getContentLength() + downloadedBytes;
745
+ ZipWritePlan writePlan = planZipResumeWrite(responseCode, downloadedBytes, httpConn.getHeaderField("Content-Range"));
746
+ long responseBodyLength = httpConn.getContentLength();
747
+ long contentLength = shouldAppendHttpBody(writePlan.statusCode, writePlan.writeOffset)
748
+ ? (responseBodyLength >= 0 ? responseBodyLength + writePlan.writeOffset : -1)
749
+ : responseBodyLength;
700
750
 
701
751
  // Check if we have enough space for the actual file
702
752
  if (contentLength > 0 && availableSpace < contentLength * 2) {
@@ -705,44 +755,36 @@ public class DownloadService extends Worker {
705
755
 
706
756
  try {
707
757
  inputStream = httpConn.getInputStream();
708
- outputStream = new FileOutputStream(tempFile, downloadedBytes > 0);
709
758
 
710
- if (downloadedBytes == 0) {
759
+ if (writePlan.writeOffset == 0 && downloadedBytes == 0) {
711
760
  writer = new BufferedWriter(new FileWriter(infoFile));
712
761
  writer.write(String.valueOf(version));
713
762
  writer.close();
714
763
  writer = null;
715
764
  }
716
765
 
717
- byte[] buffer = new byte[CryptoCipher.ioBufferBytes()];
718
- int lastNotifiedPercent = 0;
719
- int bytesRead;
720
-
721
- while ((bytesRead = inputStream.read(buffer)) != -1) {
722
- outputStream.write(buffer, 0, bytesRead);
723
- downloadedBytes += bytesRead;
724
-
725
- // Flush every 1MB to ensure progress is saved
726
- if (downloadedBytes % (1024 * 1024) == 0) {
727
- outputStream.flush();
766
+ final int[] lastNotifiedPercent = { 0 };
767
+ final long totalContentLength = contentLength;
768
+ String contentRangeHeader = httpConn.getHeaderField("Content-Range");
769
+ writeHttpBody(tempFile, inputStream, writePlan.statusCode, writePlan.writeOffset, this::isStopped, (written) -> {
770
+ int percent = calcTotalPercent(written, totalContentLength);
771
+ if (percent >= lastNotifiedPercent[0] + 10) {
772
+ lastNotifiedPercent[0] = (percent / 10) * 10;
773
+ setProgress(lastNotifiedPercent[0]);
728
774
  }
729
-
730
- // Computing percentage
731
- int percent = calcTotalPercent(downloadedBytes, contentLength);
732
- if (percent >= lastNotifiedPercent + 10) {
733
- lastNotifiedPercent = (percent / 10) * 10;
734
- setProgress(lastNotifiedPercent);
735
- }
736
- }
737
-
738
- // Final flush
739
- outputStream.flush();
740
- outputStream.close();
741
- outputStream = null;
775
+ });
742
776
 
743
777
  inputStream.close();
744
778
  inputStream = null;
745
779
 
780
+ validateZipDownloadComplete(
781
+ tempFile,
782
+ writePlan.statusCode,
783
+ contentRangeHeader,
784
+ writePlan.writeOffset,
785
+ responseBodyLength
786
+ );
787
+
746
788
  // Rename the temp file with the final name (dest)
747
789
  if (!tempFile.renameTo(new File(documentsDir, dest))) {
748
790
  throw new RuntimeException("Failed to rename temp file to final destination");
@@ -756,13 +798,12 @@ public class DownloadService extends Worker {
756
798
  // Try to free some memory
757
799
  System.gc();
758
800
  throw new RuntimeException("low_mem_fail");
759
- } finally {
760
- // Ensure all resources are closed
761
- if (outputStream != null) {
762
- try {
763
- outputStream.close();
764
- } catch (Exception ignored) {}
801
+ } catch (IOException e) {
802
+ if ("download_stopped".equals(e.getMessage())) {
803
+ throw new DownloadRetryException("download_stopped", e);
765
804
  }
805
+ throw new DownloadRetryException(e.getMessage(), e);
806
+ } finally {
766
807
  if (inputStream != null) {
767
808
  try {
768
809
  inputStream.close();
@@ -774,10 +815,14 @@ public class DownloadService extends Worker {
774
815
  } catch (Exception ignored) {}
775
816
  }
776
817
  }
818
+ } else if (isRetryableHttpStatus(responseCode)) {
819
+ throw new DownloadRetryException("HTTP error: " + responseCode);
777
820
  } else {
778
821
  infoFile.delete();
779
822
  throw new RuntimeException("HTTP error: " + responseCode);
780
823
  }
824
+ } catch (DownloadRetryException e) {
825
+ throw e;
781
826
  } catch (OutOfMemoryError e) {
782
827
  logger.error("Critical memory error: " + e.getMessage());
783
828
  System.gc(); // Suggest garbage collection
@@ -785,6 +830,12 @@ public class DownloadService extends Worker {
785
830
  } catch (SecurityException e) {
786
831
  logger.error("Security error during download: " + e.getMessage());
787
832
  throw new RuntimeException("security_error: " + e.getMessage());
833
+ } catch (MalformedURLException e) {
834
+ logger.error("Invalid download URL: " + e.getMessage());
835
+ throw new RuntimeException("invalid_url: " + e.getMessage());
836
+ } catch (IOException e) {
837
+ logger.error("Download error: " + e.getMessage());
838
+ throw new DownloadRetryException(e.getMessage(), e);
788
839
  } catch (Exception e) {
789
840
  logger.error("Download error: " + e.getMessage());
790
841
  throw new RuntimeException(e.getMessage());
@@ -798,6 +849,93 @@ public class DownloadService extends Worker {
798
849
  }
799
850
  }
800
851
 
852
+ static boolean isRetryableHttpStatus(int responseCode) {
853
+ return responseCode >= 500 || responseCode == 408 || responseCode == 429;
854
+ }
855
+
856
+ static long parseContentRangeStart(String contentRange) {
857
+ ContentRangeInfo range = parseContentRange(contentRange);
858
+ return range == null ? -1 : range.start;
859
+ }
860
+
861
+ static ContentRangeInfo parseContentRange(String contentRange) {
862
+ if (contentRange == null || contentRange.isEmpty()) {
863
+ return null;
864
+ }
865
+ String trimmed = contentRange.trim();
866
+ if (!trimmed.startsWith("bytes ")) {
867
+ return null;
868
+ }
869
+ int slash = trimmed.indexOf('/');
870
+ if (slash < 0) {
871
+ return null;
872
+ }
873
+ int dash = trimmed.indexOf('-', 6);
874
+ if (dash < 0 || dash >= slash) {
875
+ return null;
876
+ }
877
+ try {
878
+ long start = Long.parseLong(trimmed.substring(6, dash).trim());
879
+ long end = Long.parseLong(trimmed.substring(dash + 1, slash).trim());
880
+ String totalPart = trimmed.substring(slash + 1).trim();
881
+ long total = "*".equals(totalPart) ? -1 : Long.parseLong(totalPart);
882
+ if (end < start) {
883
+ return null;
884
+ }
885
+ return new ContentRangeInfo(start, end, total);
886
+ } catch (NumberFormatException e) {
887
+ return null;
888
+ }
889
+ }
890
+
891
+ static void validateZipDownloadComplete(
892
+ File tempFile,
893
+ int responseCode,
894
+ String contentRangeHeader,
895
+ long writeOffset,
896
+ long responseBodyLength
897
+ ) {
898
+ long fileLen = tempFile.length();
899
+ if (responseCode == HttpURLConnection.HTTP_PARTIAL) {
900
+ ContentRangeInfo range = parseContentRange(contentRangeHeader);
901
+ if (range == null || range.total < 0) {
902
+ throw new DownloadRetryException("unknown_content_range_total");
903
+ }
904
+ if (range.start != writeOffset) {
905
+ throw new DownloadRetryException("content_range_mismatch");
906
+ }
907
+ long advertisedSpan = range.end - range.start + 1;
908
+ long receivedSpan = fileLen - writeOffset;
909
+ if (receivedSpan != advertisedSpan) {
910
+ throw new DownloadRetryException("incomplete_content_range");
911
+ }
912
+ if (fileLen != range.total) {
913
+ throw new DownloadRetryException("incomplete_download");
914
+ }
915
+ return;
916
+ }
917
+ if (responseBodyLength >= 0 && fileLen != responseBodyLength) {
918
+ throw new DownloadRetryException("incomplete_download");
919
+ }
920
+ }
921
+
922
+ static ZipWritePlan planZipResumeWrite(int responseCode, long downloadedBytes, String contentRange) {
923
+ if (responseCode == HttpURLConnection.HTTP_PARTIAL) {
924
+ long rangeStart = parseContentRangeStart(contentRange);
925
+ if (rangeStart == downloadedBytes) {
926
+ return new ZipWritePlan(HttpURLConnection.HTTP_PARTIAL, downloadedBytes);
927
+ }
928
+ if (rangeStart == 0) {
929
+ return new ZipWritePlan(HttpURLConnection.HTTP_OK, 0);
930
+ }
931
+ throw new DownloadRetryException("invalid_content_range");
932
+ }
933
+ if (responseCode == HttpURLConnection.HTTP_OK && downloadedBytes > 0) {
934
+ return new ZipWritePlan(HttpURLConnection.HTTP_OK, 0);
935
+ }
936
+ return new ZipWritePlan(responseCode, downloadedBytes);
937
+ }
938
+
801
939
  private void clearDownloadData(String docDir, String id) {
802
940
  File tempFile = new File(docDir, "temp_" + id + ".tmp");
803
941
  File infoFile = new File(docDir, "update_" + id + ".dat");
@@ -995,17 +1133,44 @@ public class DownloadService extends Worker {
995
1133
  }
996
1134
 
997
1135
  static void writeHttpBody(File dest, InputStream body, int statusCode, long existingBytes) throws IOException {
1136
+ writeHttpBody(dest, body, statusCode, existingBytes, null, null);
1137
+ }
1138
+
1139
+ static void writeHttpBody(
1140
+ File dest,
1141
+ InputStream body,
1142
+ int statusCode,
1143
+ long existingBytes,
1144
+ BooleanSupplier shouldStop,
1145
+ LongConsumer onProgress
1146
+ ) throws IOException {
1147
+ if (shouldStop != null && shouldStop.getAsBoolean()) {
1148
+ throw new IOException("download_stopped");
1149
+ }
998
1150
  boolean append = shouldAppendHttpBody(statusCode, existingBytes);
999
1151
  byte[] buffer = new byte[CryptoCipher.ioBufferBytes()];
1000
1152
  try (FileOutputStream fos = new FileOutputStream(dest, append)) {
1001
1153
  int n;
1002
1154
  long written = append ? existingBytes : 0;
1003
- while ((n = body.read(buffer)) != -1) {
1155
+ while (true) {
1156
+ if (shouldStop != null && shouldStop.getAsBoolean()) {
1157
+ throw new IOException("download_stopped");
1158
+ }
1159
+ n = body.read(buffer);
1160
+ if (shouldStop != null && shouldStop.getAsBoolean()) {
1161
+ throw new IOException("download_stopped");
1162
+ }
1163
+ if (n == -1) {
1164
+ break;
1165
+ }
1004
1166
  fos.write(buffer, 0, n);
1005
1167
  written += n;
1006
1168
  if (written % (1024 * 1024) == 0) {
1007
1169
  fos.flush();
1008
1170
  }
1171
+ if (onProgress != null) {
1172
+ onProgress.accept(written);
1173
+ }
1009
1174
  }
1010
1175
  fos.flush();
1011
1176
  }
@@ -96,7 +96,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
96
96
  deinit {
97
97
  implementation.shutdown()
98
98
  }
99
- private let pluginVersion: String = "8.51.18"
99
+ private let pluginVersion: String = "8.51.20"
100
100
  private let launchStartedAtMs = Int64(Date().timeIntervalSince1970 * 1000)
101
101
  static let updateUrlDefault = "https://plugin.capgo.app/updates"
102
102
  static let statsUrlDefault = "https://plugin.capgo.app/stats"
@@ -3555,7 +3555,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
3555
3555
  }
3556
3556
 
3557
3557
  private func makeSplashscreenCall(callbackId: String, options: [String: Any], methodName: String) -> CAPPluginCall {
3558
- CAPPluginCall(callbackId: callbackId, options: options, success: { [weak self] (_, _) in
3558
+ CAPPluginCall(callbackId: callbackId, methodName: methodName, options: options, success: { [weak self] (_, _) in
3559
3559
  guard let self = self else { return }
3560
3560
  self.logger.info(self.splashscreenCompletedMessage(methodName: methodName))
3561
3561
  }, error: { [weak self] (_) in
@@ -3846,7 +3846,8 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
3846
3846
  return
3847
3847
  }
3848
3848
 
3849
- let scheduleTimeout = {
3849
+ let scheduleTimeout = { [weak self] in
3850
+ guard let self = self else { return }
3850
3851
  self.autoSplashscreenTimeoutWorkItem?.cancel()
3851
3852
 
3852
3853
  let workItem = DispatchWorkItem { [weak self] in
@@ -172,7 +172,15 @@ import UIKit
172
172
  return nsError.domain == NSURLErrorDomain && nsError.code == NSURLErrorTimedOut
173
173
  }
174
174
 
175
- private lazy var alamofireSession: Session = {
175
+ // lazy var is not thread-safe; concurrent manifest downloads can race first access when statsUrl is empty.
176
+ private var _alamofireSession: Session?
177
+ private let alamofireSessionLock = NSLock()
178
+ private var alamofireSession: Session {
179
+ alamofireSessionLock.lock()
180
+ defer { alamofireSessionLock.unlock() }
181
+ if let session = _alamofireSession {
182
+ return session
183
+ }
176
184
  let configuration = URLSessionConfiguration.ephemeral
177
185
  configuration.httpAdditionalHeaders = ["User-Agent": self.userAgent]
178
186
  configuration.httpCookieStorage = nil
@@ -180,8 +188,10 @@ import UIKit
180
188
  configuration.requestCachePolicy = .reloadIgnoringLocalCacheData
181
189
  configuration.urlCache = nil
182
190
  configuration.httpMaximumConnectionsPerHost = Self.manifestMaxConcurrentFiles
183
- return Session(configuration: configuration)
184
- }()
191
+ let session = Session(configuration: configuration)
192
+ _alamofireSession = session
193
+ return session
194
+ }
185
195
  private let networkResponseQueue = DispatchQueue(label: "ee.forgr.capacitor-updater.network-response", qos: .utility)
186
196
 
187
197
  public var notifyDownloadRaw: (String, Int, Bool, BundleInfo?) -> Void = { _, _, _, _ in }
@@ -161,49 +161,43 @@ public struct CryptoCipher {
161
161
  let bufferSize = checksumBufferBytes()
162
162
  var sha256 = SHA256()
163
163
 
164
+ let fileHandle: FileHandle
164
165
  do {
165
- let fileHandle: FileHandle
166
+ fileHandle = try FileHandle(forReadingFrom: filePath)
167
+ } catch {
168
+ logger.error("Cannot open file for checksum calculation")
169
+ logger.debug("Path: \(filePath.path), Error: \(error)")
170
+ return ""
171
+ }
172
+
173
+ defer {
166
174
  do {
167
- fileHandle = try FileHandle(forReadingFrom: filePath)
175
+ try fileHandle.close()
168
176
  } catch {
169
- logger.error("Cannot open file for checksum calculation")
170
- logger.debug("Path: \(filePath.path), Error: \(error)")
171
- return ""
177
+ logger.error("Error closing file during checksum")
178
+ logger.debug("Error: \(error)")
172
179
  }
180
+ }
173
181
 
174
- defer {
175
- do {
176
- try fileHandle.close()
177
- } catch {
178
- logger.error("Error closing file during checksum")
179
- logger.debug("Error: \(error)")
180
- }
182
+ while autoreleasepool(invoking: {
183
+ let fileData: Data
184
+ do {
185
+ fileData = try fileHandle.read(upToCount: bufferSize) ?? Data()
186
+ } catch {
187
+ logger.error("Error reading file during checksum")
188
+ logger.debug("Error: \(error)")
189
+ return false
181
190
  }
182
191
 
183
- while autoreleasepool(invoking: {
184
- let fileData: Data
185
- do {
186
- fileData = try fileHandle.read(upToCount: bufferSize) ?? Data()
187
- } catch {
188
- logger.error("Error reading file during checksum")
189
- logger.debug("Error: \(error)")
190
- return false
191
- }
192
-
193
- if fileData.count > 0 {
194
- sha256.update(data: fileData)
195
- return true // Continue
196
- } else {
197
- return false // End of file
198
- }
199
- }) {}
192
+ if fileData.count > 0 {
193
+ sha256.update(data: fileData)
194
+ return true // Continue
195
+ } else {
196
+ return false // End of file
197
+ }
198
+ }) {}
200
199
 
201
- return hexString(from: sha256)
202
- } catch {
203
- logger.error("Cannot calculate checksum")
204
- logger.debug("Path: \(filePath.path), Error: \(error)")
205
- return ""
206
- }
200
+ return hexString(from: sha256)
207
201
  }
208
202
 
209
203
  final class RunningChecksum {
@@ -222,8 +216,7 @@ public struct CryptoCipher {
222
216
  }
223
217
 
224
218
  static func hexString(from sha256: SHA256) -> String {
225
- var copy = sha256
226
- return copy.finalize().compactMap { String(format: "%02x", $0) }.joined()
219
+ return sha256.finalize().compactMap { String(format: "%02x", $0) }.joined()
227
220
  }
228
221
 
229
222
  static func shortPathKey(_ fileName: String) -> String {
@@ -151,59 +151,34 @@ public class DelayUpdateUtils {
151
151
  }
152
152
 
153
153
  public func setMultiDelay(delayConditions: String) -> Bool {
154
- do {
155
- UserDefaults.standard.set(delayConditions, forKey: DelayUpdateUtils.DELAY_CONDITION_PREFERENCES)
156
- UserDefaults.standard.synchronize()
157
- logger.info("Delay update saved")
158
- return true
159
- } catch {
160
- logger.error("Failed to delay update, [Error calling 'setMultiDelay()']: \(error)")
161
- return false
162
- }
154
+ UserDefaults.standard.set(delayConditions, forKey: DelayUpdateUtils.DELAY_CONDITION_PREFERENCES)
155
+ UserDefaults.standard.synchronize()
156
+ logger.info("Delay update saved")
157
+ return true
163
158
  }
164
159
 
165
160
  public func setBackgroundTimestamp(_ backgroundTimestamp: Int64) {
166
- do {
167
- UserDefaults.standard.set(backgroundTimestamp, forKey: DelayUpdateUtils.BACKGROUND_TIMESTAMP_KEY)
168
- UserDefaults.standard.synchronize()
169
- logger.info("Background timestamp saved")
170
- } catch {
171
- logger.error("Failed to save background timestamp, [Error calling 'setBackgroundTimestamp()']: \(error)")
172
- }
161
+ UserDefaults.standard.set(backgroundTimestamp, forKey: DelayUpdateUtils.BACKGROUND_TIMESTAMP_KEY)
162
+ UserDefaults.standard.synchronize()
163
+ logger.info("Background timestamp saved")
173
164
  }
174
165
 
175
166
  public func unsetBackgroundTimestamp() {
176
- do {
177
- UserDefaults.standard.removeObject(forKey: DelayUpdateUtils.BACKGROUND_TIMESTAMP_KEY)
178
- UserDefaults.standard.synchronize()
179
- logger.info("Background timestamp removed")
180
- } catch {
181
- // swiftlint:disable:next line_length
182
- logger.error("Failed to remove background timestamp, [Error calling 'unsetBackgroundTimestamp()']: \(error)")
183
- }
167
+ UserDefaults.standard.removeObject(forKey: DelayUpdateUtils.BACKGROUND_TIMESTAMP_KEY)
168
+ UserDefaults.standard.synchronize()
169
+ logger.info("Background timestamp removed")
184
170
  }
185
171
 
186
172
  private func getBackgroundTimestamp() -> Int64 {
187
- do {
188
- let key = DelayUpdateUtils.BACKGROUND_TIMESTAMP_KEY
189
- let timestamp = UserDefaults.standard.object(forKey: key) as? Int64 ?? 0
190
- return timestamp
191
- } catch {
192
- logger.error("Failed to get background timestamp, [Error calling 'getBackgroundTimestamp()']: \(error)")
193
- return 0
194
- }
173
+ let key = DelayUpdateUtils.BACKGROUND_TIMESTAMP_KEY
174
+ return UserDefaults.standard.object(forKey: key) as? Int64 ?? 0
195
175
  }
196
176
 
197
177
  public func cancelDelay(source: String) -> Bool {
198
- do {
199
- UserDefaults.standard.removeObject(forKey: DelayUpdateUtils.DELAY_CONDITION_PREFERENCES)
200
- UserDefaults.standard.synchronize()
201
- logger.info("All delays canceled from \(source)")
202
- return true
203
- } catch {
204
- logger.error("Failed to cancel update delay: \(error)")
205
- return false
206
- }
178
+ UserDefaults.standard.removeObject(forKey: DelayUpdateUtils.DELAY_CONDITION_PREFERENCES)
179
+ UserDefaults.standard.synchronize()
180
+ logger.info("All delays canceled from \(source)")
181
+ return true
207
182
  }
208
183
 
209
184
  // MARK: - Helper methods
@@ -792,9 +792,9 @@ extension UIWindow {
792
792
  message: "Update downloaded! Reload to apply version \(latest.version)?",
793
793
  plugin: plugin,
794
794
  bridge: bridge,
795
- onReload: { [weak plugin] in
795
+ onReload: {
796
796
  _ = updater.set(bundle: bundle)
797
- _ = plugin?._reload()
797
+ _ = plugin._reload()
798
798
  }
799
799
  )
800
800
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/capacitor-updater",
3
- "version": "8.51.18",
3
+ "version": "8.51.20",
4
4
  "license": "MPL-2.0",
5
5
  "description": "Live update for capacitor apps",
6
6
  "main": "dist/plugin.cjs.js",