@capawesome/capacitor-live-update 7.5.0 → 7.5.1

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.
@@ -102,6 +102,7 @@ public class LiveUpdate {
102
102
  private final SharedPreferences.Editor webViewSettingsEditor;
103
103
 
104
104
  private final String bundlesDirectory = "_capacitor_live_update_bundles"; // DO NOT CHANGE!
105
+ private final String downloadsDirectory = "capawesome_capacitor_live_update_downloads";
105
106
  private final Handler rollbackHandler = new Handler();
106
107
  private final String manifestFileName = "capawesome-live-update-manifest.json"; // DO NOT CHANGE!
107
108
 
@@ -123,6 +124,9 @@ public class LiveUpdate {
123
124
  // Set the device ID on the HTTP client (after any potential config reset)
124
125
  this.httpClient.setDeviceId(getDeviceId());
125
126
 
127
+ // Delete the leftovers of downloads that were interrupted by a process kill
128
+ tryDeleteDownloadsDirectory();
129
+
126
130
  // Start the rollback timer to rollback to the default bundle
127
131
  // if the app is not ready after a certain time
128
132
  startRollbackTimer();
@@ -530,7 +534,10 @@ public class LiveUpdate {
530
534
 
531
535
  // Move the bundle directory to the bundles directory
532
536
  File bundleDirectory = buildBundleDirectoryFor(bundleId);
533
- indexHtmlFile.getParentFile().renameTo(bundleDirectory);
537
+ boolean moved = indexHtmlFile.getParentFile().renameTo(bundleDirectory);
538
+ if (!moved) {
539
+ throw new Exception(LiveUpdatePlugin.ERROR_BUNDLE_MOVE_FAILED);
540
+ }
534
541
  }
535
542
 
536
543
  private void addBundleOfTypeManifest(@NonNull String bundleId, @NonNull File directory) throws Exception {
@@ -539,7 +546,7 @@ public class LiveUpdate {
539
546
 
540
547
  private void addBundleOfTypeZip(@NonNull String bundleId, @NonNull File zipFile) throws Exception {
541
548
  // Unzip the file to the bundle directory
542
- File unzippedDirectory = unzipFile(zipFile);
549
+ File unzippedDirectory = unzipDownloadedBundle(zipFile);
543
550
  // Add the bundle
544
551
  addBundle(bundleId, unzippedDirectory);
545
552
  }
@@ -552,14 +559,8 @@ public class LiveUpdate {
552
559
  return new File(plugin.getContext().getFilesDir(), bundlesDirectory + "/" + bundleId);
553
560
  }
554
561
 
555
- private File buildTemporaryDirectory() {
556
- String fileName = UUID.randomUUID().toString();
557
- return new File(plugin.getContext().getCacheDir(), fileName);
558
- }
559
-
560
- private File buildTemporaryZipFile() {
561
- String fileName = UUID.randomUUID().toString() + ".zip";
562
- return new File(plugin.getContext().getCacheDir(), fileName);
562
+ private File buildDownloadsDirectory() {
563
+ return new File(plugin.getContext().getCacheDir(), downloadsDirectory);
563
564
  }
564
565
 
565
566
  private void copyCurrentBundleFile(@NonNull ManifestItem fileToCopy, @NonNull File destinationDirectory) throws IOException {
@@ -646,10 +647,10 @@ public class LiveUpdate {
646
647
  }
647
648
  }
648
649
 
649
- private File createTemporaryDirectory() {
650
- File file = buildTemporaryDirectory();
651
- file.mkdir();
652
- return file;
650
+ private File createTemporaryDownloadDirectory() {
651
+ File temporaryDirectory = new File(buildDownloadsDirectory(), UUID.randomUUID().toString());
652
+ temporaryDirectory.mkdirs();
653
+ return temporaryDirectory;
653
654
  }
654
655
 
655
656
  private void deleteBundleById(@NonNull String bundleId) {
@@ -675,6 +676,23 @@ public class LiveUpdate {
675
676
  file.delete();
676
677
  }
677
678
 
679
+ @NonNull
680
+ private EmptyCallback deleteTemporaryDirectoryOnCompletion(@NonNull File temporaryDirectory, @NonNull EmptyCallback callback) {
681
+ return new EmptyCallback() {
682
+ @Override
683
+ public void success() {
684
+ deleteFileRecursively(temporaryDirectory);
685
+ callback.success();
686
+ }
687
+
688
+ @Override
689
+ public void error(@NonNull Exception exception) {
690
+ deleteFileRecursively(temporaryDirectory);
691
+ callback.error(exception);
692
+ }
693
+ };
694
+ }
695
+
678
696
  private void deleteUnusedBundles() {
679
697
  String[] bundleIds = getDownloadedBundleIds();
680
698
  for (String bundleId : bundleIds) {
@@ -845,10 +863,10 @@ public class LiveUpdate {
845
863
  @NonNull String downloadUrl,
846
864
  @NonNull EmptyCallback completionCallback
847
865
  ) {
866
+ // Create a temporary directory that is deleted once the download completed or failed
867
+ File temporaryDirectory = createTemporaryDownloadDirectory();
868
+ EmptyCallback callback = deleteTemporaryDirectoryOnCompletion(temporaryDirectory, completionCallback);
848
869
  try {
849
- // Create a temporary directory
850
- File temporaryDirectory = createTemporaryDirectory();
851
-
852
870
  // Download the latest manifest
853
871
  downloadBundleFile(
854
872
  downloadUrl,
@@ -898,31 +916,31 @@ public class LiveUpdate {
898
916
  try {
899
917
  // Add the bundle
900
918
  addBundleOfTypeManifest(bundleId, temporaryDirectory);
901
- completionCallback.success();
919
+ callback.success();
902
920
  } catch (Exception e) {
903
- completionCallback.error(e);
921
+ callback.error(e);
904
922
  }
905
923
  }
906
924
 
907
925
  @Override
908
926
  public void error(@NonNull Exception exception) {
909
- completionCallback.error(exception);
927
+ callback.error(exception);
910
928
  }
911
929
  }
912
930
  );
913
931
  } catch (Exception e) {
914
- completionCallback.error(e);
932
+ callback.error(e);
915
933
  }
916
934
  }
917
935
 
918
936
  @Override
919
937
  public void error(@NonNull Exception exception) {
920
- completionCallback.error(exception);
938
+ callback.error(exception);
921
939
  }
922
940
  }
923
941
  );
924
942
  } catch (Exception e) {
925
- completionCallback.error(e);
943
+ callback.error(e);
926
944
  }
927
945
  }
928
946
 
@@ -933,39 +951,42 @@ public class LiveUpdate {
933
951
  @NonNull String downloadUrl,
934
952
  @NonNull EmptyCallback completionCallback
935
953
  ) {
936
- File file = buildTemporaryZipFile();
937
- // Download the bundle
938
- downloadAndVerifyFile(
939
- downloadUrl,
940
- file,
941
- checksum,
942
- signature,
943
- (downloadedBytes, totalBytes) -> {
944
- DownloadBundleProgressEvent event = new DownloadBundleProgressEvent(bundleId, downloadedBytes, totalBytes);
945
- notifyDownloadBundleProgressListeners(event);
946
- },
947
- new EmptyCallback() {
948
- @Override
949
- public void success() {
950
- try {
951
- // Add the bundle
952
- addBundleOfTypeZip(bundleId, file);
953
- // Delete the temporary file
954
- file.delete();
955
- completionCallback.success();
956
- } catch (Exception e) {
957
- completionCallback.error(e);
954
+ // Create a temporary directory that is deleted once the download completed or failed
955
+ File temporaryDirectory = createTemporaryDownloadDirectory();
956
+ EmptyCallback callback = deleteTemporaryDirectoryOnCompletion(temporaryDirectory, completionCallback);
957
+ File zipFile = new File(temporaryDirectory, "bundle.zip");
958
+ try {
959
+ // Download the bundle
960
+ downloadAndVerifyFile(
961
+ downloadUrl,
962
+ zipFile,
963
+ checksum,
964
+ signature,
965
+ (downloadedBytes, totalBytes) -> {
966
+ DownloadBundleProgressEvent event = new DownloadBundleProgressEvent(bundleId, downloadedBytes, totalBytes);
967
+ notifyDownloadBundleProgressListeners(event);
968
+ },
969
+ new EmptyCallback() {
970
+ @Override
971
+ public void success() {
972
+ try {
973
+ // Add the bundle
974
+ addBundleOfTypeZip(bundleId, zipFile);
975
+ callback.success();
976
+ } catch (Exception e) {
977
+ callback.error(e);
978
+ }
958
979
  }
959
- }
960
980
 
961
- @Override
962
- public void error(@NonNull Exception exception) {
963
- // Delete the temporary file on error
964
- file.delete();
965
- completionCallback.error(exception);
981
+ @Override
982
+ public void error(@NonNull Exception exception) {
983
+ callback.error(exception);
984
+ }
966
985
  }
967
- }
968
- );
986
+ );
987
+ } catch (Exception e) {
988
+ callback.error(e);
989
+ }
969
990
  }
970
991
 
971
992
  private void fetchLatestBundleInternal(
@@ -1431,8 +1452,12 @@ public class LiveUpdate {
1431
1452
  }
1432
1453
  }
1433
1454
 
1434
- private File unzipFile(@NonNull File zipFile) throws IOException {
1435
- File destination = buildTemporaryDirectory();
1455
+ private void tryDeleteDownloadsDirectory() {
1456
+ deleteFileRecursively(buildDownloadsDirectory());
1457
+ }
1458
+
1459
+ private File unzipDownloadedBundle(@NonNull File zipFile) throws IOException {
1460
+ File destination = new File(zipFile.getParentFile(), "bundle");
1436
1461
  String destinationPath = destination.getPath();
1437
1462
  ZipFile zip = new ZipFile(zipFile);
1438
1463
  // Clear stored Unix permissions to prevent EACCES errors on newer Android versions
@@ -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 = "7.5.0";
40
+ public static final String VERSION = "7.5.1";
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.";
@@ -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()
@@ -352,7 +356,7 @@ import CommonCrypto
352
356
 
353
357
  private func addBundleOfTypeZip(bundleId: String, zipFile: URL) async throws {
354
358
  // Unzip the bundle
355
- let unzippedDirectory = try self.unzipFile(zipFile: zipFile)
359
+ let unzippedDirectory = try self.unzipDownloadedBundle(zipFile: zipFile)
356
360
  // Add the bundle
357
361
  try self.addBundle(bundleId: bundleId, directory: unzippedDirectory)
358
362
  }
@@ -376,6 +380,10 @@ import CommonCrypto
376
380
  return url
377
381
  }
378
382
 
383
+ private func buildDownloadsDirectoryUrl() -> URL {
384
+ return cachesDirectoryUrl.appendingPathComponent(downloadsDirectory)
385
+ }
386
+
379
387
  private func copyCurrentBundleFile(fileToCopy: ManifestItem, toDirectory: URL) throws {
380
388
  let currentBundleId = getCurrentBundleId()
381
389
  let destination = toDirectory.appendingPathComponent(fileToCopy.href)
@@ -425,8 +433,8 @@ import CommonCrypto
425
433
  }
426
434
  }
427
435
 
428
- private func createTemporaryDirectory() throws -> URL {
429
- let temporaryDirectory = cachesDirectoryUrl.appendingPathComponent(UUID().uuidString)
436
+ private func createTemporaryDownloadDirectory() throws -> URL {
437
+ let temporaryDirectory = buildDownloadsDirectoryUrl().appendingPathComponent(UUID().uuidString)
430
438
  try FileManager.default.createDirectory(at: temporaryDirectory, withIntermediateDirectories: true, attributes: nil)
431
439
  return temporaryDirectory
432
440
  }
@@ -533,8 +541,9 @@ import CommonCrypto
533
541
  }
534
542
 
535
543
  private func downloadBundleOfTypeManifest(bundleId: String, url: String) async throws {
536
- // Create a temporary directory
537
- let temporaryDirectory = try createTemporaryDirectory()
544
+ // Create a temporary directory that is deleted once the download completed or failed
545
+ let temporaryDirectory = try createTemporaryDownloadDirectory()
546
+ defer { tryDeleteDirectory(temporaryDirectory) }
538
547
  // Download the latest manifest
539
548
  let latestManifestFile = try await downloadBundleFile(baseUrl: url, href: manifestFileName, directory: temporaryDirectory, callback: nil)
540
549
  let latestManifest = try loadManifest(file: latestManifestFile)
@@ -565,15 +574,17 @@ import CommonCrypto
565
574
  }
566
575
 
567
576
  private func downloadBundleOfTypeZip(bundleId: String, checksum: String?, signature: String?, url: String) async throws {
568
- let timestamp = String(Int(Date().timeIntervalSince1970))
569
- let temporaryZipFileUrl = self.cachesDirectoryUrl.appendingPathComponent(timestamp + ".zip")
577
+ // Create a temporary directory that is deleted once the download completed or failed
578
+ let temporaryDirectory = try createTemporaryDownloadDirectory()
579
+ defer { tryDeleteDirectory(temporaryDirectory) }
580
+ let zipFile = temporaryDirectory.appendingPathComponent("bundle.zip")
570
581
  // Download the bundle
571
- try await downloadAndVerifyFile(url: url, file: temporaryZipFileUrl, checksum: checksum, signature: signature, callback: { progress in
582
+ try await downloadAndVerifyFile(url: url, file: zipFile, checksum: checksum, signature: signature, callback: { progress in
572
583
  let event = DownloadBundleProgressEvent(bundleId: bundleId, downloadedBytes: progress.completedUnitCount, totalBytes: progress.totalUnitCount)
573
584
  self.notifyDownloadBundleProgressListeners(event)
574
585
  })
575
586
  // Add the bundle
576
- try await addBundleOfTypeZip(bundleId: bundleId, zipFile: temporaryZipFileUrl)
587
+ try await addBundleOfTypeZip(bundleId: bundleId, zipFile: zipFile)
577
588
  }
578
589
 
579
590
  private func fetchLatestBundle(_ options: FetchLatestBundleOptions) async throws -> GetLatestBundleResponse? {
@@ -959,8 +970,23 @@ import CommonCrypto
959
970
  }
960
971
  }
961
972
 
962
- private func unzipFile(zipFile: URL) throws -> URL {
963
- let destinationDirectory = zipFile.deletingPathExtension()
973
+ private func tryDeleteDirectory(_ url: URL) {
974
+ guard FileManager.default.fileExists(atPath: url.path) else {
975
+ return
976
+ }
977
+ do {
978
+ try FileManager.default.removeItem(at: url)
979
+ } catch {
980
+ CAPLog.print("[", LiveUpdatePlugin.tag, "] ", "Failed to delete directory: \(url.path)")
981
+ }
982
+ }
983
+
984
+ private func tryDeleteDownloadsDirectory() {
985
+ tryDeleteDirectory(buildDownloadsDirectoryUrl())
986
+ }
987
+
988
+ private func unzipDownloadedBundle(zipFile: URL) throws -> URL {
989
+ let destinationDirectory = zipFile.deletingLastPathComponent().appendingPathComponent("bundle")
964
990
  try FileManager.default.createDirectory(at: destinationDirectory, withIntermediateDirectories: true, attributes: nil)
965
991
  try FileManager.default.unzipItem(at: zipFile, to: destinationDirectory)
966
992
  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 = "7.5.0"
11
+ public static let version = "7.5.1"
12
12
  public static let userDefaultsPrefix = "CapawesomeLiveUpdate" // DO NOT CHANGE
13
13
 
14
14
  public let identifier = "LiveUpdatePlugin"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capawesome/capacitor-live-update",
3
- "version": "7.5.0",
3
+ "version": "7.5.1",
4
4
  "description": "Capacitor plugin to update your app remotely in real-time.",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",