@capawesome/capacitor-live-update 8.4.2 → 8.4.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.
package/README.md CHANGED
@@ -55,9 +55,10 @@ The Live Update plugin is typically used to ship changes to users without going
55
55
 
56
56
  - [Getting Started with Capawesome Cloud Live Updates](https://capawesome.io/docs/cloud/live-updates/setup/)
57
57
  - [Capacitor Live Updates: The Complete OTA Guide](https://capawesome.io/blog/capacitor-live-updates-guide/)
58
- - [Best OTA Update Tools for Capacitor (2026)](https://capawesome.io/blog/best-ota-update-tools-for-capacitor/)
58
+ - [Best Live Update Tools for Capacitor (2026)](https://capawesome.io/blog/best-ota-update-tools-for-capacitor/)
59
59
  - [Capacitor Live Updates: Signing vs Encryption](https://capawesome.io/blog/capacitor-live-updates-end-to-end-encryption/)
60
60
  - [Capacitor Live Updates: Reliability & Uptime](https://capawesome.io/blog/capacitor-live-updates-reliability-and-uptime/)
61
+ - [Capawesome vs. Capgo: Which Is More Reliable?](https://capawesome.io/blog/capawesome-cloud-vs-capgo-reliability/)
61
62
  - [Migrating from Ionic Appflow to Capawesome Cloud](https://capawesome.io/blog/migrating-from-ionic-appflow-to-capawesome-cloud/)
62
63
  - [Migrating from App Center to Capawesome Cloud](https://capawesome.io/blog/migrating-from-app-center-to-capawesome-cloud/)
63
64
  - [Live Updates for Nuxt Capacitor Apps](https://capawesome.io/blog/live-updates-for-nuxt-capacitor/)
@@ -57,6 +57,7 @@ import java.io.FileInputStream;
57
57
  import java.io.FileOutputStream;
58
58
  import java.io.IOException;
59
59
  import java.io.InputStream;
60
+ import java.net.SocketTimeoutException;
60
61
  import java.security.KeyFactory;
61
62
  import java.security.MessageDigest;
62
63
  import java.security.PublicKey;
@@ -104,6 +105,7 @@ public class LiveUpdate {
104
105
  private final SharedPreferences.Editor webViewSettingsEditor;
105
106
 
106
107
  private final String bundlesDirectory = "_capacitor_live_update_bundles"; // DO NOT CHANGE!
108
+ private final String downloadsDirectory = "capawesome_capacitor_live_update_downloads";
107
109
  private final Handler rollbackHandler = new Handler(Looper.getMainLooper());
108
110
  private final String manifestFileName = "capawesome-live-update-manifest.json"; // DO NOT CHANGE!
109
111
 
@@ -125,6 +127,9 @@ public class LiveUpdate {
125
127
  // Set the device ID on the HTTP client (after any potential config reset)
126
128
  this.httpClient.setDeviceId(getDeviceId());
127
129
 
130
+ // Delete the leftovers of downloads that were interrupted by a process kill
131
+ tryDeleteDownloadsDirectory();
132
+
128
133
  // Start the rollback timer to rollback to the default bundle
129
134
  // if the app is not ready after a certain time
130
135
  startRollbackTimer();
@@ -176,7 +181,7 @@ public class LiveUpdate {
176
181
  .host(config.getServerDomain())
177
182
  .addPathSegment("v1")
178
183
  .addPathSegment("apps")
179
- .addPathSegment(getAppId())
184
+ .addPathSegment(requireAppId())
180
185
  .addPathSegment("channels");
181
186
  if (options.getLimit() != null) {
182
187
  urlBuilder.addQueryParameter("limit", String.valueOf(options.getLimit()));
@@ -536,7 +541,10 @@ public class LiveUpdate {
536
541
 
537
542
  // Move the bundle directory to the bundles directory
538
543
  File bundleDirectory = buildBundleDirectoryFor(bundleId);
539
- indexHtmlFile.getParentFile().renameTo(bundleDirectory);
544
+ boolean moved = indexHtmlFile.getParentFile().renameTo(bundleDirectory);
545
+ if (!moved) {
546
+ throw new Exception(LiveUpdatePlugin.ERROR_BUNDLE_MOVE_FAILED);
547
+ }
540
548
  }
541
549
 
542
550
  private void addBundleOfTypeManifest(@NonNull String bundleId, @NonNull File directory) throws Exception {
@@ -545,7 +553,7 @@ public class LiveUpdate {
545
553
 
546
554
  private void addBundleOfTypeZip(@NonNull String bundleId, @NonNull File zipFile) throws Exception {
547
555
  // Unzip the file to the bundle directory
548
- File unzippedDirectory = unzipFile(zipFile);
556
+ File unzippedDirectory = unzipDownloadedBundle(zipFile);
549
557
  // Add the bundle
550
558
  addBundle(bundleId, unzippedDirectory);
551
559
  }
@@ -558,14 +566,8 @@ public class LiveUpdate {
558
566
  return new File(plugin.getContext().getFilesDir(), bundlesDirectory + "/" + bundleId);
559
567
  }
560
568
 
561
- private File buildTemporaryDirectory() {
562
- String fileName = UUID.randomUUID().toString();
563
- return new File(plugin.getContext().getCacheDir(), fileName);
564
- }
565
-
566
- private File buildTemporaryZipFile() {
567
- String fileName = UUID.randomUUID().toString() + ".zip";
568
- return new File(plugin.getContext().getCacheDir(), fileName);
569
+ private File buildDownloadsDirectory() {
570
+ return new File(plugin.getContext().getCacheDir(), downloadsDirectory);
569
571
  }
570
572
 
571
573
  private void copyCurrentBundleFile(@NonNull ManifestItem fileToCopy, @NonNull File destinationDirectory) throws IOException {
@@ -652,10 +654,10 @@ public class LiveUpdate {
652
654
  }
653
655
  }
654
656
 
655
- private File createTemporaryDirectory() {
656
- File file = buildTemporaryDirectory();
657
- file.mkdir();
658
- return file;
657
+ private File createTemporaryDownloadDirectory() {
658
+ File temporaryDirectory = new File(buildDownloadsDirectory(), UUID.randomUUID().toString());
659
+ temporaryDirectory.mkdirs();
660
+ return temporaryDirectory;
659
661
  }
660
662
 
661
663
  private void deleteBundleById(@NonNull String bundleId) {
@@ -681,6 +683,23 @@ public class LiveUpdate {
681
683
  file.delete();
682
684
  }
683
685
 
686
+ @NonNull
687
+ private EmptyCallback deleteTemporaryDirectoryOnCompletion(@NonNull File temporaryDirectory, @NonNull EmptyCallback callback) {
688
+ return new EmptyCallback() {
689
+ @Override
690
+ public void success() {
691
+ deleteFileRecursively(temporaryDirectory);
692
+ callback.success();
693
+ }
694
+
695
+ @Override
696
+ public void error(@NonNull Exception exception) {
697
+ deleteFileRecursively(temporaryDirectory);
698
+ callback.error(exception);
699
+ }
700
+ };
701
+ }
702
+
684
703
  private void deleteUnusedBundles() {
685
704
  String[] bundleIds = getDownloadedBundleIds();
686
705
  for (String bundleId : bundleIds) {
@@ -721,6 +740,8 @@ public class LiveUpdate {
721
740
  Logger.error(LiveUpdatePlugin.TAG, errorMessage, exception);
722
741
  completionCallback.error(exception);
723
742
  }
743
+ } catch (IOException e) {
744
+ handleDownloadFailure(e, completionCallback);
724
745
  } catch (Exception e) {
725
746
  completionCallback.error(e);
726
747
  }
@@ -728,7 +749,7 @@ public class LiveUpdate {
728
749
 
729
750
  @Override
730
751
  public void error(@NonNull Exception exception) {
731
- completionCallback.error(exception);
752
+ handleDownloadFailure(exception, completionCallback);
732
753
  }
733
754
  }
734
755
  );
@@ -851,10 +872,10 @@ public class LiveUpdate {
851
872
  @NonNull String downloadUrl,
852
873
  @NonNull EmptyCallback completionCallback
853
874
  ) {
875
+ // Create a temporary directory that is deleted once the download completed or failed
876
+ File temporaryDirectory = createTemporaryDownloadDirectory();
877
+ EmptyCallback callback = deleteTemporaryDirectoryOnCompletion(temporaryDirectory, completionCallback);
854
878
  try {
855
- // Create a temporary directory
856
- File temporaryDirectory = createTemporaryDirectory();
857
-
858
879
  // Download the latest manifest
859
880
  downloadBundleFile(
860
881
  downloadUrl,
@@ -904,31 +925,31 @@ public class LiveUpdate {
904
925
  try {
905
926
  // Add the bundle
906
927
  addBundleOfTypeManifest(bundleId, temporaryDirectory);
907
- completionCallback.success();
928
+ callback.success();
908
929
  } catch (Exception e) {
909
- completionCallback.error(e);
930
+ callback.error(e);
910
931
  }
911
932
  }
912
933
 
913
934
  @Override
914
935
  public void error(@NonNull Exception exception) {
915
- completionCallback.error(exception);
936
+ callback.error(exception);
916
937
  }
917
938
  }
918
939
  );
919
940
  } catch (Exception e) {
920
- completionCallback.error(e);
941
+ callback.error(e);
921
942
  }
922
943
  }
923
944
 
924
945
  @Override
925
946
  public void error(@NonNull Exception exception) {
926
- completionCallback.error(exception);
947
+ callback.error(exception);
927
948
  }
928
949
  }
929
950
  );
930
951
  } catch (Exception e) {
931
- completionCallback.error(e);
952
+ callback.error(e);
932
953
  }
933
954
  }
934
955
 
@@ -939,39 +960,42 @@ public class LiveUpdate {
939
960
  @NonNull String downloadUrl,
940
961
  @NonNull EmptyCallback completionCallback
941
962
  ) {
942
- File file = buildTemporaryZipFile();
943
- // Download the bundle
944
- downloadAndVerifyFile(
945
- downloadUrl,
946
- file,
947
- checksum,
948
- signature,
949
- (downloadedBytes, totalBytes) -> {
950
- DownloadBundleProgressEvent event = new DownloadBundleProgressEvent(bundleId, downloadedBytes, totalBytes);
951
- notifyDownloadBundleProgressListeners(event);
952
- },
953
- new EmptyCallback() {
954
- @Override
955
- public void success() {
956
- try {
957
- // Add the bundle
958
- addBundleOfTypeZip(bundleId, file);
959
- // Delete the temporary file
960
- file.delete();
961
- completionCallback.success();
962
- } catch (Exception e) {
963
- completionCallback.error(e);
963
+ // Create a temporary directory that is deleted once the download completed or failed
964
+ File temporaryDirectory = createTemporaryDownloadDirectory();
965
+ EmptyCallback callback = deleteTemporaryDirectoryOnCompletion(temporaryDirectory, completionCallback);
966
+ File zipFile = new File(temporaryDirectory, "bundle.zip");
967
+ try {
968
+ // Download the bundle
969
+ downloadAndVerifyFile(
970
+ downloadUrl,
971
+ zipFile,
972
+ checksum,
973
+ signature,
974
+ (downloadedBytes, totalBytes) -> {
975
+ DownloadBundleProgressEvent event = new DownloadBundleProgressEvent(bundleId, downloadedBytes, totalBytes);
976
+ notifyDownloadBundleProgressListeners(event);
977
+ },
978
+ new EmptyCallback() {
979
+ @Override
980
+ public void success() {
981
+ try {
982
+ // Add the bundle
983
+ addBundleOfTypeZip(bundleId, zipFile);
984
+ callback.success();
985
+ } catch (Exception e) {
986
+ callback.error(e);
987
+ }
964
988
  }
965
- }
966
989
 
967
- @Override
968
- public void error(@NonNull Exception exception) {
969
- // Delete the temporary file on error
970
- file.delete();
971
- completionCallback.error(exception);
990
+ @Override
991
+ public void error(@NonNull Exception exception) {
992
+ callback.error(exception);
993
+ }
972
994
  }
973
- }
974
- );
995
+ );
996
+ } catch (Exception e) {
997
+ callback.error(e);
998
+ }
975
999
  }
976
1000
 
977
1001
  private void fetchLatestBundleInternal(
@@ -985,7 +1009,7 @@ public class LiveUpdate {
985
1009
  .host(config.getServerDomain())
986
1010
  .addPathSegment("v1")
987
1011
  .addPathSegment("apps")
988
- .addPathSegment(getAppId())
1012
+ .addPathSegment(requireAppId())
989
1013
  .addPathSegment("bundles")
990
1014
  .addPathSegment("latest")
991
1015
  .addQueryParameter("appVersionCode", getVersionCodeAsString())
@@ -1186,6 +1210,15 @@ public class LiveUpdate {
1186
1210
  return getPackageInfo().versionName;
1187
1211
  }
1188
1212
 
1213
+ private void handleDownloadFailure(@NonNull Exception exception, @NonNull EmptyCallback callback) {
1214
+ Logger.error(LiveUpdatePlugin.TAG, exception.getMessage(), exception);
1215
+ if (exception instanceof SocketTimeoutException) {
1216
+ callback.error(exception);
1217
+ } else {
1218
+ callback.error(new Exception(LiveUpdatePlugin.ERROR_DOWNLOAD_FAILED));
1219
+ }
1220
+ }
1221
+
1189
1222
  private boolean hasBundleById(@NonNull String bundleId) {
1190
1223
  File bundleDirectory = buildBundleDirectoryFor(bundleId);
1191
1224
  return bundleDirectory.exists();
@@ -1264,6 +1297,15 @@ public class LiveUpdate {
1264
1297
  sync(options, callback);
1265
1298
  }
1266
1299
 
1300
+ @NonNull
1301
+ private String requireAppId() throws Exception {
1302
+ String appId = getAppId();
1303
+ if (appId == null || appId.isEmpty()) {
1304
+ throw new Exception(LiveUpdatePlugin.ERROR_APP_ID_MISSING);
1305
+ }
1306
+ return appId;
1307
+ }
1308
+
1267
1309
  private void rollback() {
1268
1310
  // Set the rollback flag
1269
1311
  rollbackPerformed = true;
@@ -1437,8 +1479,12 @@ public class LiveUpdate {
1437
1479
  }
1438
1480
  }
1439
1481
 
1440
- private File unzipFile(@NonNull File zipFile) throws IOException {
1441
- File destination = buildTemporaryDirectory();
1482
+ private void tryDeleteDownloadsDirectory() {
1483
+ deleteFileRecursively(buildDownloadsDirectory());
1484
+ }
1485
+
1486
+ private File unzipDownloadedBundle(@NonNull File zipFile) throws IOException {
1487
+ File destination = new File(zipFile.getParentFile(), "bundle");
1442
1488
  String destinationPath = destination.getPath();
1443
1489
  ZipFile zip = new ZipFile(zipFile);
1444
1490
  // Clear stored Unix permissions to prevent EACCES errors on newer Android versions
@@ -88,7 +88,7 @@ public class LiveUpdateHttpClient {
88
88
 
89
89
  @Override
90
90
  public void onFailure(@NonNull Call call, @NonNull IOException e) {
91
- callback.error(new Exception(e));
91
+ callback.error(e);
92
92
  }
93
93
  }
94
94
  );
@@ -37,12 +37,13 @@ import io.capawesome.capacitorjs.plugins.liveupdate.interfaces.Result;
37
37
  public class LiveUpdatePlugin extends Plugin {
38
38
 
39
39
  public static final String TAG = "LiveUpdate";
40
- public static final String VERSION = "8.4.2";
40
+ public static final String VERSION = "8.4.4";
41
41
  public static final String SHARED_PREFERENCES_NAME = "CapawesomeLiveUpdate"; // DO NOT CHANGE
42
42
  public static final String ERROR_APP_ID_MISSING = "appId must be configured.";
43
43
  public static final String ERROR_BUNDLE_EXISTS = "bundle already exists.";
44
44
  public static final String ERROR_BUNDLE_ID_MISSING = "bundleId must be provided.";
45
45
  public static final String ERROR_BUNDLE_INDEX_HTML_MISSING = "The bundle does not contain an index.html file.";
46
+ public static final String ERROR_BUNDLE_MOVE_FAILED = "Bundle could not be moved.";
46
47
  public static final String ERROR_BUNDLE_NOT_FOUND = "bundle not found.";
47
48
  public static final String ERROR_CHECKSUM_CALCULATION_FAILED = "Failed to calculate checksum.";
48
49
  public static final String ERROR_CHECKSUM_MISMATCH = "Checksum mismatch.";
@@ -59,9 +60,6 @@ public class LiveUpdatePlugin extends Plugin {
59
60
  public static final String EVENT_NEXT_BUNDLE_SET = "nextBundleSet";
60
61
  public static final String EVENT_RELOADED = "reloaded";
61
62
 
62
- @Nullable
63
- private LiveUpdateConfig config;
64
-
65
63
  @Nullable
66
64
  private LiveUpdate implementation;
67
65
 
@@ -69,8 +67,7 @@ public class LiveUpdatePlugin extends Plugin {
69
67
 
70
68
  public void load() {
71
69
  try {
72
- config = getLiveUpdateConfig();
73
- implementation = new LiveUpdate(config, this);
70
+ implementation = new LiveUpdate(getLiveUpdateConfig(), this);
74
71
  } catch (Exception exception) {
75
72
  Logger.error(TAG, exception.getMessage(), exception);
76
73
  }
@@ -182,12 +179,6 @@ public class LiveUpdatePlugin extends Plugin {
182
179
  @PluginMethod
183
180
  public void fetchChannels(PluginCall call) {
184
181
  try {
185
- String appId = config.getAppId();
186
- if (appId == null || appId.isEmpty()) {
187
- call.reject(ERROR_APP_ID_MISSING);
188
- return;
189
- }
190
-
191
182
  FetchChannelsOptions options = new FetchChannelsOptions(call);
192
183
  NonEmptyCallback<FetchChannelsResult> callback = new NonEmptyCallback<>() {
193
184
  @Override
@@ -210,12 +201,6 @@ public class LiveUpdatePlugin extends Plugin {
210
201
  @PluginMethod
211
202
  public void fetchLatestBundle(PluginCall call) {
212
203
  try {
213
- String appId = config.getAppId();
214
- if (appId == null || appId.isEmpty()) {
215
- call.reject(ERROR_APP_ID_MISSING);
216
- return;
217
- }
218
-
219
204
  FetchLatestBundleOptions options = new FetchLatestBundleOptions(call);
220
205
  NonEmptyCallback<FetchLatestBundleResult> callback = new NonEmptyCallback<>() {
221
206
  @Override
@@ -632,12 +617,6 @@ public class LiveUpdatePlugin extends Plugin {
632
617
  @PluginMethod
633
618
  public void sync(PluginCall call) {
634
619
  try {
635
- String appId = config.getAppId();
636
- if (appId == null || appId.isEmpty()) {
637
- call.reject(ERROR_APP_ID_MISSING);
638
- return;
639
- }
640
-
641
620
  SyncOptions options = new SyncOptions(call);
642
621
  NonEmptyCallback<Result> callback = new NonEmptyCallback<>() {
643
622
  @Override
@@ -13,6 +13,7 @@ import CommonCrypto
13
13
  private let config: LiveUpdateConfig
14
14
  private let defaultWebAssetDir = "public" // DO NOT CHANGE! (See https://dub.sh/Buvz4yj)
15
15
  private let defaultServerPathKey = "serverBasePath" // DO NOT CHANGE! (See https://dub.sh/ceDl0zT)
16
+ private let downloadsDirectory = "capawesome_capacitor_live_update_downloads"
16
17
  private let httpClient: LiveUpdateHttpClient
17
18
  private let libraryDirectoryUrl = FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask).first!
18
19
  private let manifestFileName = "capawesome-live-update-manifest.json" // DO NOT CHANGE!
@@ -37,6 +38,9 @@ import CommonCrypto
37
38
  // Set the device ID on the HTTP client (after any potential config reset)
38
39
  self.httpClient.setDeviceId(getDeviceId())
39
40
 
41
+ // Delete the leftovers of downloads that were interrupted by a process kill
42
+ tryDeleteDownloadsDirectory()
43
+
40
44
  // Start the rollback timer to rollback to the default bundle
41
45
  // if the app is not ready after a certain time
42
46
  startRollbackTimer()
@@ -94,7 +98,8 @@ import CommonCrypto
94
98
  if let query = options.getQuery() {
95
99
  parameters["query"] = query
96
100
  }
97
- var urlComponents = URLComponents(string: "https://\(config.serverDomain)/v1/apps/\(getAppId() ?? "")/channels")!
101
+ let appId = try requireAppId()
102
+ var urlComponents = URLComponents(string: "https://\(config.serverDomain)/v1/apps/\(appId)/channels")!
98
103
  if !parameters.isEmpty {
99
104
  urlComponents.queryItems = parameters.map { URLQueryItem(name: $0.key, value: $0.value) }
100
105
  }
@@ -355,7 +360,7 @@ import CommonCrypto
355
360
 
356
361
  private func addBundleOfTypeZip(bundleId: String, zipFile: URL) async throws {
357
362
  // Unzip the bundle
358
- let unzippedDirectory = try self.unzipFile(zipFile: zipFile)
363
+ let unzippedDirectory = try self.unzipDownloadedBundle(zipFile: zipFile)
359
364
  // Add the bundle
360
365
  try self.addBundle(bundleId: bundleId, directory: unzippedDirectory)
361
366
  }
@@ -379,6 +384,10 @@ import CommonCrypto
379
384
  return url
380
385
  }
381
386
 
387
+ private func buildDownloadsDirectoryUrl() -> URL {
388
+ return cachesDirectoryUrl.appendingPathComponent(downloadsDirectory)
389
+ }
390
+
382
391
  private func copyCurrentBundleFile(fileToCopy: ManifestItem, toDirectory: URL) throws {
383
392
  let currentBundleId = getCurrentBundleId()
384
393
  let destination = toDirectory.appendingPathComponent(fileToCopy.href)
@@ -428,8 +437,8 @@ import CommonCrypto
428
437
  }
429
438
  }
430
439
 
431
- private func createTemporaryDirectory() throws -> URL {
432
- let temporaryDirectory = cachesDirectoryUrl.appendingPathComponent(UUID().uuidString)
440
+ private func createTemporaryDownloadDirectory() throws -> URL {
441
+ let temporaryDirectory = buildDownloadsDirectoryUrl().appendingPathComponent(UUID().uuidString)
433
442
  try FileManager.default.createDirectory(at: temporaryDirectory, withIntermediateDirectories: true, attributes: nil)
434
443
  return temporaryDirectory
435
444
  }
@@ -537,8 +546,9 @@ import CommonCrypto
537
546
  }
538
547
 
539
548
  private func downloadBundleOfTypeManifest(bundleId: String, url: String) async throws {
540
- // Create a temporary directory
541
- let temporaryDirectory = try createTemporaryDirectory()
549
+ // Create a temporary directory that is deleted once the download completed or failed
550
+ let temporaryDirectory = try createTemporaryDownloadDirectory()
551
+ defer { tryDeleteDirectory(temporaryDirectory) }
542
552
  // Download the latest manifest
543
553
  let latestManifestFile = try await downloadBundleFile(baseUrl: url, href: manifestFileName, directory: temporaryDirectory, callback: nil)
544
554
  let latestManifest = try loadManifest(file: latestManifestFile)
@@ -569,15 +579,17 @@ import CommonCrypto
569
579
  }
570
580
 
571
581
  private func downloadBundleOfTypeZip(bundleId: String, checksum: String?, signature: String?, url: String) async throws {
572
- let timestamp = String(Int(Date().timeIntervalSince1970))
573
- let temporaryZipFileUrl = self.cachesDirectoryUrl.appendingPathComponent(timestamp + ".zip")
582
+ // Create a temporary directory that is deleted once the download completed or failed
583
+ let temporaryDirectory = try createTemporaryDownloadDirectory()
584
+ defer { tryDeleteDirectory(temporaryDirectory) }
585
+ let zipFile = temporaryDirectory.appendingPathComponent("bundle.zip")
574
586
  // Download the bundle
575
- try await downloadAndVerifyFile(url: url, file: temporaryZipFileUrl, checksum: checksum, signature: signature, callback: { progress in
587
+ try await downloadAndVerifyFile(url: url, file: zipFile, checksum: checksum, signature: signature, callback: { progress in
576
588
  let event = DownloadBundleProgressEvent(bundleId: bundleId, downloadedBytes: progress.completedUnitCount, totalBytes: progress.totalUnitCount)
577
589
  self.notifyDownloadBundleProgressListeners(event)
578
590
  })
579
591
  // Add the bundle
580
- try await addBundleOfTypeZip(bundleId: bundleId, zipFile: temporaryZipFileUrl)
592
+ try await addBundleOfTypeZip(bundleId: bundleId, zipFile: zipFile)
581
593
  }
582
594
 
583
595
  private func fetchLatestBundle(_ options: FetchLatestBundleOptions) async throws -> GetLatestBundleResponse? {
@@ -592,7 +604,8 @@ import CommonCrypto
592
604
  parameters["osVersion"] = await UIDevice.current.systemVersion
593
605
  parameters["platform"] = "1"
594
606
  parameters["pluginVersion"] = LiveUpdatePlugin.version
595
- var urlComponents = URLComponents(string: "https://\(config.serverDomain)/v1/apps/\(getAppId() ?? "")/bundles/latest")!
607
+ let appId = try requireAppId()
608
+ var urlComponents = URLComponents(string: "https://\(config.serverDomain)/v1/apps/\(appId)/bundles/latest")!
596
609
  urlComponents.queryItems = parameters.map { URLQueryItem(name: $0.key, value: $0.value) }
597
610
  let url = try urlComponents.asURL()
598
611
  CAPLog.print("[", LiveUpdatePlugin.tag, "] Fetching latest bundle: ", url)
@@ -791,6 +804,13 @@ import CommonCrypto
791
804
  }
792
805
  }
793
806
 
807
+ private func requireAppId() throws -> String {
808
+ guard let appId = getAppId(), !appId.isEmpty else {
809
+ throw CustomError.appIdMissing
810
+ }
811
+ return appId
812
+ }
813
+
794
814
  private func rollback() {
795
815
  // Set the rollback flag
796
816
  rollbackPerformed = true
@@ -963,8 +983,23 @@ import CommonCrypto
963
983
  }
964
984
  }
965
985
 
966
- private func unzipFile(zipFile: URL) throws -> URL {
967
- let destinationDirectory = zipFile.deletingPathExtension()
986
+ private func tryDeleteDirectory(_ url: URL) {
987
+ guard FileManager.default.fileExists(atPath: url.path) else {
988
+ return
989
+ }
990
+ do {
991
+ try FileManager.default.removeItem(at: url)
992
+ } catch {
993
+ CAPLog.print("[", LiveUpdatePlugin.tag, "] ", "Failed to delete directory: \(url.path)")
994
+ }
995
+ }
996
+
997
+ private func tryDeleteDownloadsDirectory() {
998
+ tryDeleteDirectory(buildDownloadsDirectoryUrl())
999
+ }
1000
+
1001
+ private func unzipDownloadedBundle(zipFile: URL) throws -> URL {
1002
+ let destinationDirectory = zipFile.deletingLastPathComponent().appendingPathComponent("bundle")
968
1003
  try FileManager.default.createDirectory(at: destinationDirectory, withIntermediateDirectories: true, attributes: nil)
969
1004
  try FileManager.default.unzipItem(at: zipFile, to: destinationDirectory)
970
1005
  return destinationDirectory
@@ -8,7 +8,7 @@ import Capacitor
8
8
  @objc(LiveUpdatePlugin)
9
9
  public class LiveUpdatePlugin: CAPPlugin, CAPBridgedPlugin {
10
10
  public static let tag = "LiveUpdate"
11
- public static let version = "8.4.2"
11
+ public static let version = "8.4.4"
12
12
  public static let userDefaultsPrefix = "CapawesomeLiveUpdate" // DO NOT CHANGE
13
13
 
14
14
  public let identifier = "LiveUpdatePlugin"
@@ -46,12 +46,10 @@ public class LiveUpdatePlugin: CAPPlugin, CAPBridgedPlugin {
46
46
  private let eventNextBundleSet = "nextBundleSet"
47
47
  private let eventReloaded = "reloaded"
48
48
 
49
- private var config: LiveUpdateConfig?
50
49
  private var implementation: LiveUpdate?
51
50
 
52
51
  override public func load() {
53
- self.config = liveUpdateConfig()
54
- self.implementation = LiveUpdate(config: config!, plugin: self)
52
+ self.implementation = LiveUpdate(config: liveUpdateConfig(), plugin: self)
55
53
 
56
54
  // Notify implementation about load
57
55
  implementation?.handleLoad()
@@ -115,11 +113,6 @@ public class LiveUpdatePlugin: CAPPlugin, CAPBridgedPlugin {
115
113
  @objc func fetchChannels(_ call: CAPPluginCall) {
116
114
  Task {
117
115
  do {
118
- guard let appId = config?.appId, !appId.isEmpty else {
119
- call.reject(CustomError.appIdMissing.localizedDescription)
120
- return
121
- }
122
-
123
116
  let options = FetchChannelsOptions(call)
124
117
  let result = try await implementation?.fetchChannels(options)
125
118
  if let result = result?.toJSObject() as? JSObject {
@@ -134,11 +127,6 @@ public class LiveUpdatePlugin: CAPPlugin, CAPBridgedPlugin {
134
127
  @objc func fetchLatestBundle(_ call: CAPPluginCall) {
135
128
  Task {
136
129
  do {
137
- guard let appId = config?.appId, !appId.isEmpty else {
138
- call.reject(CustomError.appIdMissing.localizedDescription)
139
- return
140
- }
141
-
142
130
  let options = FetchLatestBundleOptions(call)
143
131
  let result = try await implementation?.fetchLatestBundle(options)
144
132
  if let result = result?.toJSObject() as? JSObject {
@@ -373,11 +361,6 @@ public class LiveUpdatePlugin: CAPPlugin, CAPBridgedPlugin {
373
361
  @objc func sync(_ call: CAPPluginCall) {
374
362
  Task {
375
363
  do {
376
- guard let appId = config?.appId, !appId.isEmpty else {
377
- call.reject(CustomError.appIdMissing.localizedDescription)
378
- return
379
- }
380
-
381
364
  let options = SyncOptions(call)
382
365
  let result = try await implementation?.sync(options)
383
366
  if let result = result?.toJSObject() as? JSObject {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capawesome/capacitor-live-update",
3
- "version": "8.4.2",
3
+ "version": "8.4.4",
4
4
  "description": "Capacitor plugin to update your app remotely in real-time on Android and iOS.",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",