@capawesome/capacitor-live-update 8.4.2 → 8.4.3
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 +2 -1
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdate.java +80 -55
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdatePlugin.java +2 -1
- package/ios/Plugin/LiveUpdate.swift +37 -11
- package/ios/Plugin/LiveUpdatePlugin.swift +1 -1
- package/package.json +1 -1
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
|
|
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/)
|
|
@@ -104,6 +104,7 @@ public class LiveUpdate {
|
|
|
104
104
|
private final SharedPreferences.Editor webViewSettingsEditor;
|
|
105
105
|
|
|
106
106
|
private final String bundlesDirectory = "_capacitor_live_update_bundles"; // DO NOT CHANGE!
|
|
107
|
+
private final String downloadsDirectory = "capawesome_capacitor_live_update_downloads";
|
|
107
108
|
private final Handler rollbackHandler = new Handler(Looper.getMainLooper());
|
|
108
109
|
private final String manifestFileName = "capawesome-live-update-manifest.json"; // DO NOT CHANGE!
|
|
109
110
|
|
|
@@ -125,6 +126,9 @@ public class LiveUpdate {
|
|
|
125
126
|
// Set the device ID on the HTTP client (after any potential config reset)
|
|
126
127
|
this.httpClient.setDeviceId(getDeviceId());
|
|
127
128
|
|
|
129
|
+
// Delete the leftovers of downloads that were interrupted by a process kill
|
|
130
|
+
tryDeleteDownloadsDirectory();
|
|
131
|
+
|
|
128
132
|
// Start the rollback timer to rollback to the default bundle
|
|
129
133
|
// if the app is not ready after a certain time
|
|
130
134
|
startRollbackTimer();
|
|
@@ -536,7 +540,10 @@ public class LiveUpdate {
|
|
|
536
540
|
|
|
537
541
|
// Move the bundle directory to the bundles directory
|
|
538
542
|
File bundleDirectory = buildBundleDirectoryFor(bundleId);
|
|
539
|
-
indexHtmlFile.getParentFile().renameTo(bundleDirectory);
|
|
543
|
+
boolean moved = indexHtmlFile.getParentFile().renameTo(bundleDirectory);
|
|
544
|
+
if (!moved) {
|
|
545
|
+
throw new Exception(LiveUpdatePlugin.ERROR_BUNDLE_MOVE_FAILED);
|
|
546
|
+
}
|
|
540
547
|
}
|
|
541
548
|
|
|
542
549
|
private void addBundleOfTypeManifest(@NonNull String bundleId, @NonNull File directory) throws Exception {
|
|
@@ -545,7 +552,7 @@ public class LiveUpdate {
|
|
|
545
552
|
|
|
546
553
|
private void addBundleOfTypeZip(@NonNull String bundleId, @NonNull File zipFile) throws Exception {
|
|
547
554
|
// Unzip the file to the bundle directory
|
|
548
|
-
File unzippedDirectory =
|
|
555
|
+
File unzippedDirectory = unzipDownloadedBundle(zipFile);
|
|
549
556
|
// Add the bundle
|
|
550
557
|
addBundle(bundleId, unzippedDirectory);
|
|
551
558
|
}
|
|
@@ -558,14 +565,8 @@ public class LiveUpdate {
|
|
|
558
565
|
return new File(plugin.getContext().getFilesDir(), bundlesDirectory + "/" + bundleId);
|
|
559
566
|
}
|
|
560
567
|
|
|
561
|
-
private File
|
|
562
|
-
|
|
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);
|
|
568
|
+
private File buildDownloadsDirectory() {
|
|
569
|
+
return new File(plugin.getContext().getCacheDir(), downloadsDirectory);
|
|
569
570
|
}
|
|
570
571
|
|
|
571
572
|
private void copyCurrentBundleFile(@NonNull ManifestItem fileToCopy, @NonNull File destinationDirectory) throws IOException {
|
|
@@ -652,10 +653,10 @@ public class LiveUpdate {
|
|
|
652
653
|
}
|
|
653
654
|
}
|
|
654
655
|
|
|
655
|
-
private File
|
|
656
|
-
File
|
|
657
|
-
|
|
658
|
-
return
|
|
656
|
+
private File createTemporaryDownloadDirectory() {
|
|
657
|
+
File temporaryDirectory = new File(buildDownloadsDirectory(), UUID.randomUUID().toString());
|
|
658
|
+
temporaryDirectory.mkdirs();
|
|
659
|
+
return temporaryDirectory;
|
|
659
660
|
}
|
|
660
661
|
|
|
661
662
|
private void deleteBundleById(@NonNull String bundleId) {
|
|
@@ -681,6 +682,23 @@ public class LiveUpdate {
|
|
|
681
682
|
file.delete();
|
|
682
683
|
}
|
|
683
684
|
|
|
685
|
+
@NonNull
|
|
686
|
+
private EmptyCallback deleteTemporaryDirectoryOnCompletion(@NonNull File temporaryDirectory, @NonNull EmptyCallback callback) {
|
|
687
|
+
return new EmptyCallback() {
|
|
688
|
+
@Override
|
|
689
|
+
public void success() {
|
|
690
|
+
deleteFileRecursively(temporaryDirectory);
|
|
691
|
+
callback.success();
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
@Override
|
|
695
|
+
public void error(@NonNull Exception exception) {
|
|
696
|
+
deleteFileRecursively(temporaryDirectory);
|
|
697
|
+
callback.error(exception);
|
|
698
|
+
}
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
|
|
684
702
|
private void deleteUnusedBundles() {
|
|
685
703
|
String[] bundleIds = getDownloadedBundleIds();
|
|
686
704
|
for (String bundleId : bundleIds) {
|
|
@@ -851,10 +869,10 @@ public class LiveUpdate {
|
|
|
851
869
|
@NonNull String downloadUrl,
|
|
852
870
|
@NonNull EmptyCallback completionCallback
|
|
853
871
|
) {
|
|
872
|
+
// Create a temporary directory that is deleted once the download completed or failed
|
|
873
|
+
File temporaryDirectory = createTemporaryDownloadDirectory();
|
|
874
|
+
EmptyCallback callback = deleteTemporaryDirectoryOnCompletion(temporaryDirectory, completionCallback);
|
|
854
875
|
try {
|
|
855
|
-
// Create a temporary directory
|
|
856
|
-
File temporaryDirectory = createTemporaryDirectory();
|
|
857
|
-
|
|
858
876
|
// Download the latest manifest
|
|
859
877
|
downloadBundleFile(
|
|
860
878
|
downloadUrl,
|
|
@@ -904,31 +922,31 @@ public class LiveUpdate {
|
|
|
904
922
|
try {
|
|
905
923
|
// Add the bundle
|
|
906
924
|
addBundleOfTypeManifest(bundleId, temporaryDirectory);
|
|
907
|
-
|
|
925
|
+
callback.success();
|
|
908
926
|
} catch (Exception e) {
|
|
909
|
-
|
|
927
|
+
callback.error(e);
|
|
910
928
|
}
|
|
911
929
|
}
|
|
912
930
|
|
|
913
931
|
@Override
|
|
914
932
|
public void error(@NonNull Exception exception) {
|
|
915
|
-
|
|
933
|
+
callback.error(exception);
|
|
916
934
|
}
|
|
917
935
|
}
|
|
918
936
|
);
|
|
919
937
|
} catch (Exception e) {
|
|
920
|
-
|
|
938
|
+
callback.error(e);
|
|
921
939
|
}
|
|
922
940
|
}
|
|
923
941
|
|
|
924
942
|
@Override
|
|
925
943
|
public void error(@NonNull Exception exception) {
|
|
926
|
-
|
|
944
|
+
callback.error(exception);
|
|
927
945
|
}
|
|
928
946
|
}
|
|
929
947
|
);
|
|
930
948
|
} catch (Exception e) {
|
|
931
|
-
|
|
949
|
+
callback.error(e);
|
|
932
950
|
}
|
|
933
951
|
}
|
|
934
952
|
|
|
@@ -939,39 +957,42 @@ public class LiveUpdate {
|
|
|
939
957
|
@NonNull String downloadUrl,
|
|
940
958
|
@NonNull EmptyCallback completionCallback
|
|
941
959
|
) {
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
960
|
+
// Create a temporary directory that is deleted once the download completed or failed
|
|
961
|
+
File temporaryDirectory = createTemporaryDownloadDirectory();
|
|
962
|
+
EmptyCallback callback = deleteTemporaryDirectoryOnCompletion(temporaryDirectory, completionCallback);
|
|
963
|
+
File zipFile = new File(temporaryDirectory, "bundle.zip");
|
|
964
|
+
try {
|
|
965
|
+
// Download the bundle
|
|
966
|
+
downloadAndVerifyFile(
|
|
967
|
+
downloadUrl,
|
|
968
|
+
zipFile,
|
|
969
|
+
checksum,
|
|
970
|
+
signature,
|
|
971
|
+
(downloadedBytes, totalBytes) -> {
|
|
972
|
+
DownloadBundleProgressEvent event = new DownloadBundleProgressEvent(bundleId, downloadedBytes, totalBytes);
|
|
973
|
+
notifyDownloadBundleProgressListeners(event);
|
|
974
|
+
},
|
|
975
|
+
new EmptyCallback() {
|
|
976
|
+
@Override
|
|
977
|
+
public void success() {
|
|
978
|
+
try {
|
|
979
|
+
// Add the bundle
|
|
980
|
+
addBundleOfTypeZip(bundleId, zipFile);
|
|
981
|
+
callback.success();
|
|
982
|
+
} catch (Exception e) {
|
|
983
|
+
callback.error(e);
|
|
984
|
+
}
|
|
964
985
|
}
|
|
965
|
-
}
|
|
966
986
|
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
completionCallback.error(exception);
|
|
987
|
+
@Override
|
|
988
|
+
public void error(@NonNull Exception exception) {
|
|
989
|
+
callback.error(exception);
|
|
990
|
+
}
|
|
972
991
|
}
|
|
973
|
-
|
|
974
|
-
)
|
|
992
|
+
);
|
|
993
|
+
} catch (Exception e) {
|
|
994
|
+
callback.error(e);
|
|
995
|
+
}
|
|
975
996
|
}
|
|
976
997
|
|
|
977
998
|
private void fetchLatestBundleInternal(
|
|
@@ -1437,8 +1458,12 @@ public class LiveUpdate {
|
|
|
1437
1458
|
}
|
|
1438
1459
|
}
|
|
1439
1460
|
|
|
1440
|
-
private
|
|
1441
|
-
|
|
1461
|
+
private void tryDeleteDownloadsDirectory() {
|
|
1462
|
+
deleteFileRecursively(buildDownloadsDirectory());
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
private File unzipDownloadedBundle(@NonNull File zipFile) throws IOException {
|
|
1466
|
+
File destination = new File(zipFile.getParentFile(), "bundle");
|
|
1442
1467
|
String destinationPath = destination.getPath();
|
|
1443
1468
|
ZipFile zip = new ZipFile(zipFile);
|
|
1444
1469
|
// Clear stored Unix permissions to prevent EACCES errors on newer Android versions
|
package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdatePlugin.java
CHANGED
|
@@ -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.
|
|
40
|
+
public static final String VERSION = "8.4.3";
|
|
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()
|
|
@@ -355,7 +359,7 @@ import CommonCrypto
|
|
|
355
359
|
|
|
356
360
|
private func addBundleOfTypeZip(bundleId: String, zipFile: URL) async throws {
|
|
357
361
|
// Unzip the bundle
|
|
358
|
-
let unzippedDirectory = try self.
|
|
362
|
+
let unzippedDirectory = try self.unzipDownloadedBundle(zipFile: zipFile)
|
|
359
363
|
// Add the bundle
|
|
360
364
|
try self.addBundle(bundleId: bundleId, directory: unzippedDirectory)
|
|
361
365
|
}
|
|
@@ -379,6 +383,10 @@ import CommonCrypto
|
|
|
379
383
|
return url
|
|
380
384
|
}
|
|
381
385
|
|
|
386
|
+
private func buildDownloadsDirectoryUrl() -> URL {
|
|
387
|
+
return cachesDirectoryUrl.appendingPathComponent(downloadsDirectory)
|
|
388
|
+
}
|
|
389
|
+
|
|
382
390
|
private func copyCurrentBundleFile(fileToCopy: ManifestItem, toDirectory: URL) throws {
|
|
383
391
|
let currentBundleId = getCurrentBundleId()
|
|
384
392
|
let destination = toDirectory.appendingPathComponent(fileToCopy.href)
|
|
@@ -428,8 +436,8 @@ import CommonCrypto
|
|
|
428
436
|
}
|
|
429
437
|
}
|
|
430
438
|
|
|
431
|
-
private func
|
|
432
|
-
let temporaryDirectory =
|
|
439
|
+
private func createTemporaryDownloadDirectory() throws -> URL {
|
|
440
|
+
let temporaryDirectory = buildDownloadsDirectoryUrl().appendingPathComponent(UUID().uuidString)
|
|
433
441
|
try FileManager.default.createDirectory(at: temporaryDirectory, withIntermediateDirectories: true, attributes: nil)
|
|
434
442
|
return temporaryDirectory
|
|
435
443
|
}
|
|
@@ -537,8 +545,9 @@ import CommonCrypto
|
|
|
537
545
|
}
|
|
538
546
|
|
|
539
547
|
private func downloadBundleOfTypeManifest(bundleId: String, url: String) async throws {
|
|
540
|
-
// Create a temporary directory
|
|
541
|
-
let temporaryDirectory = try
|
|
548
|
+
// Create a temporary directory that is deleted once the download completed or failed
|
|
549
|
+
let temporaryDirectory = try createTemporaryDownloadDirectory()
|
|
550
|
+
defer { tryDeleteDirectory(temporaryDirectory) }
|
|
542
551
|
// Download the latest manifest
|
|
543
552
|
let latestManifestFile = try await downloadBundleFile(baseUrl: url, href: manifestFileName, directory: temporaryDirectory, callback: nil)
|
|
544
553
|
let latestManifest = try loadManifest(file: latestManifestFile)
|
|
@@ -569,15 +578,17 @@ import CommonCrypto
|
|
|
569
578
|
}
|
|
570
579
|
|
|
571
580
|
private func downloadBundleOfTypeZip(bundleId: String, checksum: String?, signature: String?, url: String) async throws {
|
|
572
|
-
|
|
573
|
-
let
|
|
581
|
+
// Create a temporary directory that is deleted once the download completed or failed
|
|
582
|
+
let temporaryDirectory = try createTemporaryDownloadDirectory()
|
|
583
|
+
defer { tryDeleteDirectory(temporaryDirectory) }
|
|
584
|
+
let zipFile = temporaryDirectory.appendingPathComponent("bundle.zip")
|
|
574
585
|
// Download the bundle
|
|
575
|
-
try await downloadAndVerifyFile(url: url, file:
|
|
586
|
+
try await downloadAndVerifyFile(url: url, file: zipFile, checksum: checksum, signature: signature, callback: { progress in
|
|
576
587
|
let event = DownloadBundleProgressEvent(bundleId: bundleId, downloadedBytes: progress.completedUnitCount, totalBytes: progress.totalUnitCount)
|
|
577
588
|
self.notifyDownloadBundleProgressListeners(event)
|
|
578
589
|
})
|
|
579
590
|
// Add the bundle
|
|
580
|
-
try await addBundleOfTypeZip(bundleId: bundleId, zipFile:
|
|
591
|
+
try await addBundleOfTypeZip(bundleId: bundleId, zipFile: zipFile)
|
|
581
592
|
}
|
|
582
593
|
|
|
583
594
|
private func fetchLatestBundle(_ options: FetchLatestBundleOptions) async throws -> GetLatestBundleResponse? {
|
|
@@ -963,8 +974,23 @@ import CommonCrypto
|
|
|
963
974
|
}
|
|
964
975
|
}
|
|
965
976
|
|
|
966
|
-
private func
|
|
967
|
-
|
|
977
|
+
private func tryDeleteDirectory(_ url: URL) {
|
|
978
|
+
guard FileManager.default.fileExists(atPath: url.path) else {
|
|
979
|
+
return
|
|
980
|
+
}
|
|
981
|
+
do {
|
|
982
|
+
try FileManager.default.removeItem(at: url)
|
|
983
|
+
} catch {
|
|
984
|
+
CAPLog.print("[", LiveUpdatePlugin.tag, "] ", "Failed to delete directory: \(url.path)")
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
private func tryDeleteDownloadsDirectory() {
|
|
989
|
+
tryDeleteDirectory(buildDownloadsDirectoryUrl())
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
private func unzipDownloadedBundle(zipFile: URL) throws -> URL {
|
|
993
|
+
let destinationDirectory = zipFile.deletingLastPathComponent().appendingPathComponent("bundle")
|
|
968
994
|
try FileManager.default.createDirectory(at: destinationDirectory, withIntermediateDirectories: true, attributes: nil)
|
|
969
995
|
try FileManager.default.unzipItem(at: zipFile, to: destinationDirectory)
|
|
970
996
|
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.
|
|
11
|
+
public static let version = "8.4.3"
|
|
12
12
|
public static let userDefaultsPrefix = "CapawesomeLiveUpdate" // DO NOT CHANGE
|
|
13
13
|
|
|
14
14
|
public let identifier = "LiveUpdatePlugin"
|
package/package.json
CHANGED