@capawesome/capacitor-live-update 8.1.0-dev.c088829.1767373807 → 8.1.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.
- package/README.md +10 -14
- package/android/build.gradle +1 -1
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdate.java +56 -74
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdatePlugin.java +4 -23
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdatePreferences.java +17 -11
- package/dist/docs.json +2 -2
- package/dist/esm/definitions.d.ts +2 -2
- package/dist/esm/definitions.js.map +1 -1
- package/ios/Plugin/LiveUpdate.swift +32 -37
- package/ios/Plugin/LiveUpdatePlugin.swift +5 -29
- package/ios/Plugin/LiveUpdatePreferences.swift +1 -3
- package/package.json +1 -1
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/CustomException.java +0 -20
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/CustomExceptions.java +0 -33
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/interfaces/LiveUpdateEventEmitter.java +0 -13
- package/ios/Plugin/Protocols/LiveUpdateEventEmitter.swift +0 -8
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ Capacitor plugin that allows you to update your app remotely in real-time withou
|
|
|
13
13
|
We are proud to offer one of the most complete and feature-rich Capacitor plugins for Over-the-Air (OTA) updates. Here are some of the key features:
|
|
14
14
|
|
|
15
15
|
- 🔋 Supports **Android and iOS**
|
|
16
|
-
- ⚡️ **Capacitor 7** support
|
|
16
|
+
- ⚡️ **Capacitor 6/7/8** support
|
|
17
17
|
- 📦 **Bundle Management**: Download, set, and delete bundles.
|
|
18
18
|
- ☁️ **Cloud Support**: Use the [Capawesome Cloud](https://cloud.capawesome.io/) to manage your app updates.
|
|
19
19
|
- 📺 **Channel Support**: Set a channel for the app to manage different versions.
|
|
@@ -38,12 +38,8 @@ Missing a feature? Just [open an issue](https://github.com/capawesome-team/capac
|
|
|
38
38
|
|
|
39
39
|
## Guides
|
|
40
40
|
|
|
41
|
-
- [
|
|
42
|
-
- [
|
|
43
|
-
- [How Live Updates for Capacitor work](https://capawesome.io/blog/how-live-updates-for-capacitor-work/)
|
|
44
|
-
- [How to gradually roll out Capacitor Live Updates](https://capawesome.io/blog/how-to-gradually-roll-out-capacitor-live-updates/#update-a-bundle)
|
|
45
|
-
- [How to restrict Capacitor Live Updates to Native Versions](https://capawesome.io/blog/how-to-restrict-capacitor-live-updates-to-native-versions/)
|
|
46
|
-
- [Live Updates for Nuxt Capacitor Apps with Capawesome Cloud](https://capawesome.io/blog/live-updates-for-nuxt-capacitor/)
|
|
41
|
+
- [Getting Started with Capawesome Cloud Live Updates](https://capawesome.io/cloud/live-updates/setup/)
|
|
42
|
+
- [Migrating from Ionic Appflow to Capawesome Cloud](https://capawesome.io/blog/migrating-from-ionic-appflow-to-capawesome-cloud/)
|
|
47
43
|
- [Migrating from App Center to Capawesome Cloud](https://capawesome.io/blog/migrating-from-app-center-to-capawesome-cloud/)
|
|
48
44
|
|
|
49
45
|
## Installation
|
|
@@ -886,13 +882,13 @@ Remove all listeners for this plugin.
|
|
|
886
882
|
|
|
887
883
|
#### DownloadBundleOptions
|
|
888
884
|
|
|
889
|
-
| Prop | Type | Description
|
|
890
|
-
| ------------------ | -------------------------------- |
|
|
891
|
-
| **`artifactType`** | <code>'manifest' \| 'zip'</code> | The artifact type of the bundle.
|
|
892
|
-
| **`bundleId`** | <code>string</code> | The unique identifier of the bundle. **Attention**: The value `public` is reserved and cannot be used as a bundle identifier.
|
|
893
|
-
| **`checksum`** | <code>string</code> | The checksum of the self-hosted bundle as a SHA-256 hash in
|
|
894
|
-
| **`signature`** | <code>string</code> | The signature of the self-hosted bundle as a signed SHA-256 hash in base64 format to verify the integrity of the bundle. **Attention**: Only supported for the `zip` artifact type.
|
|
895
|
-
| **`url`** | <code>string</code> | The URL of the bundle to download. For the `zip` artifact type, the URL must point to a ZIP file. For the `manifest` artifact type, the URL serves as the base URL to download the individual files. For example, if the URL is `https://example.com/download`, the plugin will download the file with the href `index.html` from `https://example.com/download?href=index.html`. To **verify the integrity** of the file, the server should return a `X-Checksum` header with the SHA-256 hash in
|
|
885
|
+
| Prop | Type | Description | Default | Since |
|
|
886
|
+
| ------------------ | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------ | ----- |
|
|
887
|
+
| **`artifactType`** | <code>'manifest' \| 'zip'</code> | The artifact type of the bundle. | <code>'zip'</code> | 6.6.0 |
|
|
888
|
+
| **`bundleId`** | <code>string</code> | The unique identifier of the bundle. **Attention**: The value `public` is reserved and cannot be used as a bundle identifier. | | 5.0.0 |
|
|
889
|
+
| **`checksum`** | <code>string</code> | The checksum of the self-hosted bundle as a SHA-256 hash in hex format to verify the integrity of the bundle. **Attention**: Only supported for the `zip` artifact type. | | 7.1.0 |
|
|
890
|
+
| **`signature`** | <code>string</code> | The signature of the self-hosted bundle as a signed SHA-256 hash in base64 format to verify the integrity of the bundle. **Attention**: Only supported for the `zip` artifact type. | | 7.1.0 |
|
|
891
|
+
| **`url`** | <code>string</code> | The URL of the bundle to download. For the `zip` artifact type, the URL must point to a ZIP file. For the `manifest` artifact type, the URL serves as the base URL to download the individual files. For example, if the URL is `https://example.com/download`, the plugin will download the file with the href `index.html` from `https://example.com/download?href=index.html`. To **verify the integrity** of the file, the server should return a `X-Checksum` header with the SHA-256 hash in hex format. To **verify the signature** of the file, the server should return a `X-Signature` header with the signed SHA-256 hash in base64 format. | | 5.0.0 |
|
|
896
892
|
|
|
897
893
|
|
|
898
894
|
#### FetchLatestBundleResult
|
package/android/build.gradle
CHANGED
|
@@ -32,7 +32,7 @@ android {
|
|
|
32
32
|
buildTypes {
|
|
33
33
|
release {
|
|
34
34
|
minifyEnabled false
|
|
35
|
-
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
|
35
|
+
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
lintOptions {
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
package io.capawesome.capacitorjs.plugins.liveupdate;
|
|
2
2
|
|
|
3
3
|
import android.app.Activity;
|
|
4
|
-
import android.content.Context;
|
|
5
4
|
import android.content.SharedPreferences;
|
|
6
5
|
import android.content.pm.PackageInfo;
|
|
7
6
|
import android.content.pm.PackageManager;
|
|
@@ -14,7 +13,6 @@ import androidx.annotation.Nullable;
|
|
|
14
13
|
import com.getcapacitor.Bridge;
|
|
15
14
|
import com.getcapacitor.Logger;
|
|
16
15
|
import com.getcapacitor.plugin.WebView;
|
|
17
|
-
import io.capawesome.capacitorjs.plugins.liveupdate.classes.CustomExceptions;
|
|
18
16
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.Manifest;
|
|
19
17
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.ManifestItem;
|
|
20
18
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.api.GetLatestBundleResponse;
|
|
@@ -46,7 +44,6 @@ import io.capawesome.capacitorjs.plugins.liveupdate.classes.results.SyncResult;
|
|
|
46
44
|
import io.capawesome.capacitorjs.plugins.liveupdate.enums.ArtifactType;
|
|
47
45
|
import io.capawesome.capacitorjs.plugins.liveupdate.interfaces.DownloadProgressCallback;
|
|
48
46
|
import io.capawesome.capacitorjs.plugins.liveupdate.interfaces.EmptyCallback;
|
|
49
|
-
import io.capawesome.capacitorjs.plugins.liveupdate.interfaces.LiveUpdateEventEmitter;
|
|
50
47
|
import io.capawesome.capacitorjs.plugins.liveupdate.interfaces.NonEmptyCallback;
|
|
51
48
|
import io.capawesome.capacitorjs.plugins.liveupdate.interfaces.Result;
|
|
52
49
|
import java.io.File;
|
|
@@ -80,27 +77,18 @@ import org.json.JSONObject;
|
|
|
80
77
|
|
|
81
78
|
public class LiveUpdate {
|
|
82
79
|
|
|
83
|
-
public static final String TAG = "LiveUpdate";
|
|
84
|
-
public static final String VERSION = "8.1.1";
|
|
85
|
-
|
|
86
80
|
private final long autoUpdateIntervalMs = 15 * 60 * 1000; // 15 minutes
|
|
87
81
|
|
|
88
|
-
@NonNull
|
|
89
|
-
private final Bridge bridge;
|
|
90
|
-
|
|
91
82
|
@NonNull
|
|
92
83
|
private final LiveUpdateConfig config;
|
|
93
84
|
|
|
94
|
-
@NonNull
|
|
95
|
-
private final Context context;
|
|
96
|
-
|
|
97
85
|
private final String defaultWebAssetDir = Bridge.DEFAULT_WEB_ASSET_DIR;
|
|
98
86
|
|
|
99
87
|
@NonNull
|
|
100
|
-
private final
|
|
88
|
+
private final LiveUpdateHttpClient httpClient;
|
|
101
89
|
|
|
102
90
|
@NonNull
|
|
103
|
-
private final
|
|
91
|
+
private final LiveUpdatePlugin plugin;
|
|
104
92
|
|
|
105
93
|
@NonNull
|
|
106
94
|
private final LiveUpdatePreferences preferences;
|
|
@@ -117,19 +105,12 @@ public class LiveUpdate {
|
|
|
117
105
|
private boolean rollbackPerformed = false;
|
|
118
106
|
private boolean syncInProgress = false;
|
|
119
107
|
|
|
120
|
-
public LiveUpdate(
|
|
121
|
-
@NonNull Context context,
|
|
122
|
-
@NonNull Bridge bridge,
|
|
123
|
-
@NonNull LiveUpdateConfig config,
|
|
124
|
-
@NonNull LiveUpdateEventEmitter eventEmitter
|
|
125
|
-
) throws PackageManager.NameNotFoundException {
|
|
126
|
-
this.context = context;
|
|
127
|
-
this.bridge = bridge;
|
|
108
|
+
public LiveUpdate(@NonNull LiveUpdateConfig config, @NonNull LiveUpdatePlugin plugin) throws PackageManager.NameNotFoundException {
|
|
128
109
|
this.config = config;
|
|
129
|
-
this.eventEmitter = eventEmitter;
|
|
130
110
|
this.httpClient = new LiveUpdateHttpClient(config);
|
|
131
|
-
this.
|
|
132
|
-
this.
|
|
111
|
+
this.plugin = plugin;
|
|
112
|
+
this.preferences = new LiveUpdatePreferences(plugin.getContext());
|
|
113
|
+
this.webViewSettingsEditor = plugin.getContext().getSharedPreferences(WebView.WEBVIEW_PREFS_NAME, Activity.MODE_PRIVATE).edit();
|
|
133
114
|
|
|
134
115
|
// Check version and reset config if version changed
|
|
135
116
|
checkAndResetConfigIfVersionChanged();
|
|
@@ -147,7 +128,7 @@ public class LiveUpdate {
|
|
|
147
128
|
String bundleId = options.getBundleId();
|
|
148
129
|
|
|
149
130
|
if (!hasBundleById(bundleId)) {
|
|
150
|
-
Exception exception =
|
|
131
|
+
Exception exception = new Exception(LiveUpdatePlugin.ERROR_BUNDLE_NOT_FOUND);
|
|
151
132
|
callback.error(exception);
|
|
152
133
|
return;
|
|
153
134
|
}
|
|
@@ -303,9 +284,9 @@ public class LiveUpdate {
|
|
|
303
284
|
}
|
|
304
285
|
|
|
305
286
|
public void ready(@NonNull NonEmptyCallback callback) {
|
|
306
|
-
Logger.debug(
|
|
287
|
+
Logger.debug(LiveUpdatePlugin.TAG, "App is ready.");
|
|
307
288
|
if (config.getReadyTimeout() <= 0) {
|
|
308
|
-
Logger.warn(
|
|
289
|
+
Logger.warn(LiveUpdatePlugin.TAG, "Ready timeout is set to 0. Automatic rollback is disabled.");
|
|
309
290
|
}
|
|
310
291
|
// Stop the rollback timer
|
|
311
292
|
stopRollbackTimer();
|
|
@@ -371,7 +352,7 @@ public class LiveUpdate {
|
|
|
371
352
|
if (hasBundleById(bundleId)) {
|
|
372
353
|
setNextBundleById(bundleId);
|
|
373
354
|
} else {
|
|
374
|
-
Exception exception =
|
|
355
|
+
Exception exception = new Exception(LiveUpdatePlugin.ERROR_BUNDLE_NOT_FOUND);
|
|
375
356
|
callback.error(exception);
|
|
376
357
|
return;
|
|
377
358
|
}
|
|
@@ -381,7 +362,7 @@ public class LiveUpdate {
|
|
|
381
362
|
|
|
382
363
|
public void sync(@NonNull SyncOptions options, @NonNull NonEmptyCallback<Result> callback) {
|
|
383
364
|
if (syncInProgress) {
|
|
384
|
-
Exception exception =
|
|
365
|
+
Exception exception = new Exception(LiveUpdatePlugin.ERROR_SYNC_IN_PROGRESS);
|
|
385
366
|
callback.error(exception);
|
|
386
367
|
return;
|
|
387
368
|
}
|
|
@@ -397,7 +378,7 @@ public class LiveUpdate {
|
|
|
397
378
|
public void success(@Nullable GetLatestBundleResponse response) {
|
|
398
379
|
try {
|
|
399
380
|
if (response == null) {
|
|
400
|
-
Logger.debug(
|
|
381
|
+
Logger.debug(LiveUpdatePlugin.TAG, "No update available.");
|
|
401
382
|
syncInProgress = false;
|
|
402
383
|
SyncResult syncResult = new SyncResult(null);
|
|
403
384
|
callback.success(syncResult);
|
|
@@ -410,7 +391,7 @@ public class LiveUpdate {
|
|
|
410
391
|
String url = response.getUrl();
|
|
411
392
|
// Check if the bundle is blocked
|
|
412
393
|
if (isBlockedBundleId(latestBundleId)) {
|
|
413
|
-
Logger.warn(
|
|
394
|
+
Logger.warn(LiveUpdatePlugin.TAG, "Bundle is blocked and will not be downloaded.");
|
|
414
395
|
syncInProgress = false;
|
|
415
396
|
SyncResult syncResult = new SyncResult(null);
|
|
416
397
|
callback.success(syncResult);
|
|
@@ -478,7 +459,7 @@ public class LiveUpdate {
|
|
|
478
459
|
// Search folder with index.html file
|
|
479
460
|
File indexHtmlFile = searchIndexHtmlFile(sourceDirectory);
|
|
480
461
|
if (indexHtmlFile == null) {
|
|
481
|
-
throw
|
|
462
|
+
throw new Exception(LiveUpdatePlugin.ERROR_BUNDLE_INDEX_HTML_MISSING);
|
|
482
463
|
}
|
|
483
464
|
|
|
484
465
|
// Create the bundles directory if it does not exist
|
|
@@ -501,21 +482,21 @@ public class LiveUpdate {
|
|
|
501
482
|
}
|
|
502
483
|
|
|
503
484
|
private File buildBundlesDirectory() {
|
|
504
|
-
return new File(
|
|
485
|
+
return new File(plugin.getContext().getFilesDir(), bundlesDirectory);
|
|
505
486
|
}
|
|
506
487
|
|
|
507
488
|
private File buildBundleDirectoryFor(@NonNull String bundleId) {
|
|
508
|
-
return new File(
|
|
489
|
+
return new File(plugin.getContext().getFilesDir(), bundlesDirectory + "/" + bundleId);
|
|
509
490
|
}
|
|
510
491
|
|
|
511
492
|
private File buildTemporaryDirectory() {
|
|
512
493
|
String fileName = UUID.randomUUID().toString();
|
|
513
|
-
return new File(
|
|
494
|
+
return new File(plugin.getContext().getCacheDir(), fileName);
|
|
514
495
|
}
|
|
515
496
|
|
|
516
497
|
private File buildTemporaryZipFile() {
|
|
517
498
|
String fileName = UUID.randomUUID().toString() + ".zip";
|
|
518
|
-
return new File(
|
|
499
|
+
return new File(plugin.getContext().getCacheDir(), fileName);
|
|
519
500
|
}
|
|
520
501
|
|
|
521
502
|
private void copyCurrentBundleFile(@NonNull ManifestItem fileToCopy, @NonNull File destinationDirectory) throws IOException {
|
|
@@ -523,7 +504,7 @@ public class LiveUpdate {
|
|
|
523
504
|
String currentBundleId = getCurrentBundleId();
|
|
524
505
|
if (currentBundleId == null) {
|
|
525
506
|
// Create the source input stream
|
|
526
|
-
AssetManager assets =
|
|
507
|
+
AssetManager assets = plugin.getContext().getAssets();
|
|
527
508
|
InputStream inputStream = assets.open(defaultWebAssetDir + "/" + href);
|
|
528
509
|
// Create the destination file
|
|
529
510
|
File destination = new File(destinationDirectory, href);
|
|
@@ -551,7 +532,7 @@ public class LiveUpdate {
|
|
|
551
532
|
for (ManifestItem fileToCopy : filesToCopy) {
|
|
552
533
|
boolean success = tryCopyCurrentBundleFile(fileToCopy, destinationDirectory);
|
|
553
534
|
if (!success) {
|
|
554
|
-
Logger.warn(
|
|
535
|
+
Logger.warn(LiveUpdatePlugin.TAG, "Failed to copy file: " + fileToCopy.getHref());
|
|
555
536
|
// If the file could not be copied, add it to the list of missing items
|
|
556
537
|
missingItems.add(fileToCopy);
|
|
557
538
|
}
|
|
@@ -597,8 +578,8 @@ public class LiveUpdate {
|
|
|
597
578
|
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
|
|
598
579
|
return keyFactory.generatePublic(X509publicKey);
|
|
599
580
|
} catch (Exception exception) {
|
|
600
|
-
Logger.error(
|
|
601
|
-
throw
|
|
581
|
+
Logger.error(LiveUpdatePlugin.TAG, exception.getMessage(), exception);
|
|
582
|
+
throw new Exception(LiveUpdatePlugin.ERROR_PUBLIC_KEY_INVALID);
|
|
602
583
|
}
|
|
603
584
|
}
|
|
604
585
|
|
|
@@ -664,8 +645,8 @@ public class LiveUpdate {
|
|
|
664
645
|
completionCallback.success();
|
|
665
646
|
} else {
|
|
666
647
|
String errorMessage = response.body().string();
|
|
667
|
-
Exception exception =
|
|
668
|
-
Logger.error(
|
|
648
|
+
Exception exception = new Exception(LiveUpdatePlugin.ERROR_DOWNLOAD_FAILED);
|
|
649
|
+
Logger.error(LiveUpdatePlugin.TAG, errorMessage, exception);
|
|
669
650
|
completionCallback.error(exception);
|
|
670
651
|
}
|
|
671
652
|
} catch (Exception e) {
|
|
@@ -768,7 +749,7 @@ public class LiveUpdate {
|
|
|
768
749
|
public void error(@NonNull Exception e) {
|
|
769
750
|
// Capture first error and cancel all remaining downloads
|
|
770
751
|
if (firstError.compareAndSet(null, e)) {
|
|
771
|
-
Logger.error(
|
|
752
|
+
Logger.error(LiveUpdatePlugin.TAG, "Failed to download file: " + item.getHref(), e);
|
|
772
753
|
// Cancel all in-flight downloads (fail-fast)
|
|
773
754
|
synchronized (activeCalls) {
|
|
774
755
|
for (Call activeCall : activeCalls) {
|
|
@@ -943,10 +924,10 @@ public class LiveUpdate {
|
|
|
943
924
|
.addQueryParameter("deviceId", getDeviceId())
|
|
944
925
|
.addQueryParameter("osVersion", String.valueOf(Build.VERSION.SDK_INT))
|
|
945
926
|
.addQueryParameter("platform", "0")
|
|
946
|
-
.addQueryParameter("pluginVersion",
|
|
927
|
+
.addQueryParameter("pluginVersion", LiveUpdatePlugin.VERSION)
|
|
947
928
|
.build()
|
|
948
929
|
.toString();
|
|
949
|
-
Logger.debug(
|
|
930
|
+
Logger.debug(LiveUpdatePlugin.TAG, "Fetching latest bundle: " + url);
|
|
950
931
|
|
|
951
932
|
httpClient.enqueue(
|
|
952
933
|
url,
|
|
@@ -955,7 +936,7 @@ public class LiveUpdate {
|
|
|
955
936
|
public void success(@NonNull Response response) {
|
|
956
937
|
try {
|
|
957
938
|
String responseBodyString = response.body().string();
|
|
958
|
-
Logger.debug(
|
|
939
|
+
Logger.debug(LiveUpdatePlugin.TAG, "Latest bundle response: " + responseBodyString);
|
|
959
940
|
if (response.isSuccessful()) {
|
|
960
941
|
JSONObject responseJson = new JSONObject(responseBodyString);
|
|
961
942
|
GetLatestBundleResponse result = new GetLatestBundleResponse(responseJson);
|
|
@@ -1005,8 +986,8 @@ public class LiveUpdate {
|
|
|
1005
986
|
source.close();
|
|
1006
987
|
return digest.digest();
|
|
1007
988
|
} catch (IOException exception) {
|
|
1008
|
-
Logger.error(
|
|
1009
|
-
throw
|
|
989
|
+
Logger.error(LiveUpdatePlugin.TAG, exception.getMessage(), exception);
|
|
990
|
+
throw new Exception(LiveUpdatePlugin.ERROR_CHECKSUM_CALCULATION_FAILED);
|
|
1010
991
|
}
|
|
1011
992
|
}
|
|
1012
993
|
|
|
@@ -1056,7 +1037,7 @@ public class LiveUpdate {
|
|
|
1056
1037
|
* @return The absolute path to the current bundle directory (`public` for the built-in bundle).
|
|
1057
1038
|
*/
|
|
1058
1039
|
private String getCurrentCapacitorServerPath() {
|
|
1059
|
-
return
|
|
1040
|
+
return plugin.getBridge().getServerBasePath();
|
|
1060
1041
|
}
|
|
1061
1042
|
|
|
1062
1043
|
@NonNull
|
|
@@ -1086,7 +1067,8 @@ public class LiveUpdate {
|
|
|
1086
1067
|
*/
|
|
1087
1068
|
@NonNull
|
|
1088
1069
|
private String getNextCapacitorServerPath() {
|
|
1089
|
-
String path =
|
|
1070
|
+
String path = plugin
|
|
1071
|
+
.getContext()
|
|
1090
1072
|
.getSharedPreferences(WebView.WEBVIEW_PREFS_NAME, Activity.MODE_PRIVATE)
|
|
1091
1073
|
.getString(WebView.CAP_SERVER_PATH, defaultWebAssetDir);
|
|
1092
1074
|
// Empty path means default path
|
|
@@ -1131,7 +1113,7 @@ public class LiveUpdate {
|
|
|
1131
1113
|
private Manifest loadCurrentManifest() throws Exception {
|
|
1132
1114
|
String currentBundleId = getCurrentBundleId();
|
|
1133
1115
|
if (currentBundleId == null) {
|
|
1134
|
-
AssetManager assets =
|
|
1116
|
+
AssetManager assets = plugin.getContext().getAssets();
|
|
1135
1117
|
boolean manifestFileExists = Arrays.asList(assets.list(defaultWebAssetDir)).contains(manifestFileName);
|
|
1136
1118
|
if (manifestFileExists) {
|
|
1137
1119
|
InputStream inputStream = assets.open(defaultWebAssetDir + "/" + manifestFileName);
|
|
@@ -1163,14 +1145,14 @@ public class LiveUpdate {
|
|
|
1163
1145
|
}
|
|
1164
1146
|
|
|
1165
1147
|
private void notifyDownloadBundleProgressListeners(@NonNull final DownloadBundleProgressEvent event) {
|
|
1166
|
-
|
|
1148
|
+
plugin.notifyDownloadBundleProgressListeners(event);
|
|
1167
1149
|
}
|
|
1168
1150
|
|
|
1169
1151
|
private void performAutoUpdate() {
|
|
1170
1152
|
// Check if enough time has passed since the last check
|
|
1171
1153
|
long now = System.currentTimeMillis();
|
|
1172
1154
|
if (lastAutoUpdateCheckTimestamp > 0 && (now - lastAutoUpdateCheckTimestamp) < autoUpdateIntervalMs) {
|
|
1173
|
-
Logger.debug(
|
|
1155
|
+
Logger.debug(LiveUpdatePlugin.TAG, "Auto-update skipped. Last check was less than 15 minutes ago.");
|
|
1174
1156
|
return;
|
|
1175
1157
|
}
|
|
1176
1158
|
|
|
@@ -1178,17 +1160,17 @@ public class LiveUpdate {
|
|
|
1178
1160
|
lastAutoUpdateCheckTimestamp = now;
|
|
1179
1161
|
|
|
1180
1162
|
// Run sync
|
|
1181
|
-
Logger.debug(
|
|
1163
|
+
Logger.debug(LiveUpdatePlugin.TAG, "Auto-update started.");
|
|
1182
1164
|
SyncOptions options = new SyncOptions((String) null);
|
|
1183
1165
|
NonEmptyCallback<Result> callback = new NonEmptyCallback<>() {
|
|
1184
1166
|
@Override
|
|
1185
1167
|
public void success(@NonNull Result result) {
|
|
1186
|
-
Logger.debug(
|
|
1168
|
+
Logger.debug(LiveUpdatePlugin.TAG, "Auto-update completed successfully.");
|
|
1187
1169
|
}
|
|
1188
1170
|
|
|
1189
1171
|
@Override
|
|
1190
1172
|
public void error(@NonNull Exception exception) {
|
|
1191
|
-
Logger.error(
|
|
1173
|
+
Logger.error(LiveUpdatePlugin.TAG, "Auto-update failed: " + exception.getMessage(), exception);
|
|
1192
1174
|
}
|
|
1193
1175
|
};
|
|
1194
1176
|
sync(options, callback);
|
|
@@ -1202,9 +1184,9 @@ public class LiveUpdate {
|
|
|
1202
1184
|
setPreviousBundleId(currentBundleId);
|
|
1203
1185
|
// Log the rollback result
|
|
1204
1186
|
if (currentBundleId == null) {
|
|
1205
|
-
Logger.debug(
|
|
1187
|
+
Logger.debug(LiveUpdatePlugin.TAG, "App is not ready. Default bundle is already in use.");
|
|
1206
1188
|
} else {
|
|
1207
|
-
Logger.debug(
|
|
1189
|
+
Logger.debug(LiveUpdatePlugin.TAG, "App is not ready. Rolling back to default bundle.");
|
|
1208
1190
|
// Rollback to the default bundle
|
|
1209
1191
|
setNextBundleById(null);
|
|
1210
1192
|
setCurrentBundleById(null);
|
|
@@ -1250,11 +1232,11 @@ public class LiveUpdate {
|
|
|
1250
1232
|
|
|
1251
1233
|
private void setCurrentCapacitorServerPath(@NonNull String path) {
|
|
1252
1234
|
if (path.equals(defaultWebAssetDir)) {
|
|
1253
|
-
this.
|
|
1235
|
+
this.plugin.getBridge().setServerAssetPath(path);
|
|
1254
1236
|
} else {
|
|
1255
|
-
this.
|
|
1237
|
+
this.plugin.getBridge().setServerBasePath(path);
|
|
1256
1238
|
}
|
|
1257
|
-
this.
|
|
1239
|
+
this.plugin.getBridge().reload();
|
|
1258
1240
|
// Notify listeners
|
|
1259
1241
|
notifyReloadedListeners();
|
|
1260
1242
|
}
|
|
@@ -1276,11 +1258,11 @@ public class LiveUpdate {
|
|
|
1276
1258
|
|
|
1277
1259
|
private void notifyNextBundleSetListeners(@Nullable String bundleId) {
|
|
1278
1260
|
NextBundleSetEvent event = new NextBundleSetEvent(bundleId);
|
|
1279
|
-
|
|
1261
|
+
plugin.notifyNextBundleSetListeners(event);
|
|
1280
1262
|
}
|
|
1281
1263
|
|
|
1282
1264
|
private void notifyReloadedListeners() {
|
|
1283
|
-
|
|
1265
|
+
plugin.notifyReloadedListeners();
|
|
1284
1266
|
}
|
|
1285
1267
|
|
|
1286
1268
|
private void addBlockedBundleId(@NonNull String bundleId) {
|
|
@@ -1310,7 +1292,7 @@ public class LiveUpdate {
|
|
|
1310
1292
|
String newBlockedIds = String.join(",", blockedList);
|
|
1311
1293
|
preferences.setBlockedBundleIds(newBlockedIds);
|
|
1312
1294
|
|
|
1313
|
-
Logger.debug(
|
|
1295
|
+
Logger.debug(LiveUpdatePlugin.TAG, "Bundle blocked: " + bundleId);
|
|
1314
1296
|
}
|
|
1315
1297
|
|
|
1316
1298
|
private boolean isBlockedBundleId(@NonNull String bundleId) {
|
|
@@ -1334,7 +1316,7 @@ public class LiveUpdate {
|
|
|
1334
1316
|
|
|
1335
1317
|
if (lastVersionCode == -1 || lastVersionCode != currentVersionCode) {
|
|
1336
1318
|
Logger.debug(
|
|
1337
|
-
|
|
1319
|
+
LiveUpdatePlugin.TAG,
|
|
1338
1320
|
"App version changed (last: " + lastVersionCode + ", current: " + currentVersionCode + "), resetting config."
|
|
1339
1321
|
);
|
|
1340
1322
|
resetConfig();
|
|
@@ -1384,12 +1366,12 @@ public class LiveUpdate {
|
|
|
1384
1366
|
if (publicKey != null) {
|
|
1385
1367
|
// Verify the signature
|
|
1386
1368
|
if (signature == null) {
|
|
1387
|
-
throw
|
|
1369
|
+
throw new Exception(LiveUpdatePlugin.ERROR_SIGNATURE_MISSING);
|
|
1388
1370
|
}
|
|
1389
1371
|
// Verify the signature
|
|
1390
1372
|
boolean verified = verifySignatureForFile(file, signature, publicKey);
|
|
1391
1373
|
if (!verified) {
|
|
1392
|
-
throw
|
|
1374
|
+
throw new Exception(LiveUpdatePlugin.ERROR_SIGNATURE_VERIFICATION_FAILED);
|
|
1393
1375
|
}
|
|
1394
1376
|
}
|
|
1395
1377
|
// Verify the checksum
|
|
@@ -1397,7 +1379,7 @@ public class LiveUpdate {
|
|
|
1397
1379
|
// Calculate the checksum
|
|
1398
1380
|
boolean verified = verifyChecksumForFile(file, checksum);
|
|
1399
1381
|
if (!verified) {
|
|
1400
|
-
throw
|
|
1382
|
+
throw new Exception(LiveUpdatePlugin.ERROR_CHECKSUM_MISMATCH);
|
|
1401
1383
|
}
|
|
1402
1384
|
}
|
|
1403
1385
|
}
|
|
@@ -1420,17 +1402,17 @@ public class LiveUpdate {
|
|
|
1420
1402
|
source.close();
|
|
1421
1403
|
return sig.verify(signatureBytes);
|
|
1422
1404
|
} catch (Exception exception) {
|
|
1423
|
-
Logger.error(
|
|
1424
|
-
throw
|
|
1405
|
+
Logger.error(LiveUpdatePlugin.TAG, exception.getMessage(), exception);
|
|
1406
|
+
throw new Exception(LiveUpdatePlugin.ERROR_SIGNATURE_VERIFICATION_FAILED);
|
|
1425
1407
|
}
|
|
1426
1408
|
}
|
|
1427
1409
|
|
|
1428
1410
|
private PackageInfo getPackageInfo() throws PackageManager.NameNotFoundException {
|
|
1429
|
-
String packageName = this.
|
|
1411
|
+
String packageName = this.plugin.getContext().getPackageName();
|
|
1430
1412
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
1431
|
-
return this.
|
|
1413
|
+
return this.plugin.getContext().getPackageManager().getPackageInfo(packageName, PackageManager.PackageInfoFlags.of(0));
|
|
1432
1414
|
} else {
|
|
1433
|
-
return this.
|
|
1415
|
+
return this.plugin.getContext().getPackageManager().getPackageInfo(packageName, 0);
|
|
1434
1416
|
}
|
|
1435
1417
|
}
|
|
1436
1418
|
}
|
package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdatePlugin.java
CHANGED
|
@@ -28,15 +28,15 @@ import io.capawesome.capacitorjs.plugins.liveupdate.classes.results.GetDownloade
|
|
|
28
28
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.results.GetNextBundleResult;
|
|
29
29
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.results.IsSyncingResult;
|
|
30
30
|
import io.capawesome.capacitorjs.plugins.liveupdate.interfaces.EmptyCallback;
|
|
31
|
-
import io.capawesome.capacitorjs.plugins.liveupdate.interfaces.LiveUpdateEventEmitter;
|
|
32
31
|
import io.capawesome.capacitorjs.plugins.liveupdate.interfaces.NonEmptyCallback;
|
|
33
32
|
import io.capawesome.capacitorjs.plugins.liveupdate.interfaces.Result;
|
|
34
33
|
|
|
35
34
|
@CapacitorPlugin(name = "LiveUpdate")
|
|
36
35
|
public class LiveUpdatePlugin extends Plugin {
|
|
37
36
|
|
|
38
|
-
public static final String TAG = LiveUpdate
|
|
39
|
-
public static final String VERSION =
|
|
37
|
+
public static final String TAG = "LiveUpdate";
|
|
38
|
+
public static final String VERSION = "8.1.0";
|
|
39
|
+
public static final String SHARED_PREFERENCES_NAME = "CapawesomeLiveUpdate"; // DO NOT CHANGE
|
|
40
40
|
public static final String ERROR_APP_ID_MISSING = "appId must be configured.";
|
|
41
41
|
public static final String ERROR_BUNDLE_EXISTS = "bundle already exists.";
|
|
42
42
|
public static final String ERROR_BUNDLE_ID_MISSING = "bundleId must be provided.";
|
|
@@ -68,8 +68,7 @@ public class LiveUpdatePlugin extends Plugin {
|
|
|
68
68
|
public void load() {
|
|
69
69
|
try {
|
|
70
70
|
config = getLiveUpdateConfig();
|
|
71
|
-
|
|
72
|
-
implementation = new LiveUpdate(getContext(), getBridge(), config, eventEmitter);
|
|
71
|
+
implementation = new LiveUpdate(config, this);
|
|
73
72
|
} catch (Exception exception) {
|
|
74
73
|
Logger.error(TAG, exception.getMessage(), exception);
|
|
75
74
|
}
|
|
@@ -685,22 +684,4 @@ public class LiveUpdatePlugin extends Plugin {
|
|
|
685
684
|
Logger.error(TAG, message, exception);
|
|
686
685
|
call.reject(message);
|
|
687
686
|
}
|
|
688
|
-
|
|
689
|
-
private class PluginLiveUpdateEventEmitter implements LiveUpdateEventEmitter {
|
|
690
|
-
|
|
691
|
-
@Override
|
|
692
|
-
public void onDownloadBundleProgress(@NonNull DownloadBundleProgressEvent event) {
|
|
693
|
-
notifyDownloadBundleProgressListeners(event);
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
@Override
|
|
697
|
-
public void onNextBundleSet(@NonNull NextBundleSetEvent event) {
|
|
698
|
-
notifyNextBundleSetListeners(event);
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
@Override
|
|
702
|
-
public void onReloaded() {
|
|
703
|
-
notifyReloadedListeners();
|
|
704
|
-
}
|
|
705
|
-
}
|
|
706
687
|
}
|
|
@@ -9,8 +9,6 @@ import java.util.UUID;
|
|
|
9
9
|
|
|
10
10
|
public class LiveUpdatePreferences {
|
|
11
11
|
|
|
12
|
-
private static final String SHARED_PREFERENCES_NAME = "CapawesomeLiveUpdate"; // DO NOT CHANGE
|
|
13
|
-
|
|
14
12
|
@NonNull
|
|
15
13
|
private final Context context;
|
|
16
14
|
|
|
@@ -27,45 +25,53 @@ public class LiveUpdatePreferences {
|
|
|
27
25
|
|
|
28
26
|
public LiveUpdatePreferences(@NonNull Context context) {
|
|
29
27
|
this.context = context;
|
|
30
|
-
this.settingsEditor = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Activity.MODE_PRIVATE).edit();
|
|
28
|
+
this.settingsEditor = context.getSharedPreferences(LiveUpdatePlugin.SHARED_PREFERENCES_NAME, Activity.MODE_PRIVATE).edit();
|
|
31
29
|
}
|
|
32
30
|
|
|
33
31
|
@Nullable
|
|
34
32
|
public String getAppId() {
|
|
35
|
-
return context.getSharedPreferences(SHARED_PREFERENCES_NAME, Activity.MODE_PRIVATE).getString(appIdKey, null);
|
|
33
|
+
return context.getSharedPreferences(LiveUpdatePlugin.SHARED_PREFERENCES_NAME, Activity.MODE_PRIVATE).getString(appIdKey, null);
|
|
36
34
|
}
|
|
37
35
|
|
|
38
36
|
@Nullable
|
|
39
37
|
public String getChannel() {
|
|
40
|
-
return context.getSharedPreferences(SHARED_PREFERENCES_NAME, Activity.MODE_PRIVATE).getString(channelKey, null);
|
|
38
|
+
return context.getSharedPreferences(LiveUpdatePlugin.SHARED_PREFERENCES_NAME, Activity.MODE_PRIVATE).getString(channelKey, null);
|
|
41
39
|
}
|
|
42
40
|
|
|
43
41
|
@Nullable
|
|
44
42
|
public String getCustomId() {
|
|
45
|
-
return context.getSharedPreferences(SHARED_PREFERENCES_NAME, Activity.MODE_PRIVATE).getString(customIdKey, null);
|
|
43
|
+
return context.getSharedPreferences(LiveUpdatePlugin.SHARED_PREFERENCES_NAME, Activity.MODE_PRIVATE).getString(customIdKey, null);
|
|
46
44
|
}
|
|
47
45
|
|
|
48
46
|
@Nullable
|
|
49
47
|
public String getDeviceIdForApp(@Nullable String appId) {
|
|
50
48
|
if (appId == null) {
|
|
51
|
-
return context
|
|
49
|
+
return context
|
|
50
|
+
.getSharedPreferences(LiveUpdatePlugin.SHARED_PREFERENCES_NAME, Activity.MODE_PRIVATE)
|
|
51
|
+
.getString(deviceIdKey, null);
|
|
52
52
|
} else {
|
|
53
|
-
return context
|
|
53
|
+
return context
|
|
54
|
+
.getSharedPreferences(LiveUpdatePlugin.SHARED_PREFERENCES_NAME, Activity.MODE_PRIVATE)
|
|
55
|
+
.getString(deviceIdKey + "_" + appId, null);
|
|
54
56
|
}
|
|
55
57
|
}
|
|
56
58
|
|
|
57
59
|
public int getLastVersionCode() {
|
|
58
|
-
return context.getSharedPreferences(SHARED_PREFERENCES_NAME, Activity.MODE_PRIVATE).getInt(lastVersionCodeKey, -1);
|
|
60
|
+
return context.getSharedPreferences(LiveUpdatePlugin.SHARED_PREFERENCES_NAME, Activity.MODE_PRIVATE).getInt(lastVersionCodeKey, -1);
|
|
59
61
|
}
|
|
60
62
|
|
|
61
63
|
@Nullable
|
|
62
64
|
public String getPreviousBundleId() {
|
|
63
|
-
return context
|
|
65
|
+
return context
|
|
66
|
+
.getSharedPreferences(LiveUpdatePlugin.SHARED_PREFERENCES_NAME, Activity.MODE_PRIVATE)
|
|
67
|
+
.getString(previousBundleIdKey, null);
|
|
64
68
|
}
|
|
65
69
|
|
|
66
70
|
@Nullable
|
|
67
71
|
public String getBlockedBundleIds() {
|
|
68
|
-
return context
|
|
72
|
+
return context
|
|
73
|
+
.getSharedPreferences(LiveUpdatePlugin.SHARED_PREFERENCES_NAME, Activity.MODE_PRIVATE)
|
|
74
|
+
.getString(blockedBundleIdsKey, null);
|
|
69
75
|
}
|
|
70
76
|
|
|
71
77
|
public void setAppId(@Nullable String appId) {
|
package/dist/docs.json
CHANGED
|
@@ -663,7 +663,7 @@
|
|
|
663
663
|
"name": "since"
|
|
664
664
|
}
|
|
665
665
|
],
|
|
666
|
-
"docs": "The checksum of the self-hosted bundle as a SHA-256 hash\nin
|
|
666
|
+
"docs": "The checksum of the self-hosted bundle as a SHA-256 hash\nin hex format to verify the integrity of the bundle.\n\n**Attention**: Only supported for the `zip` artifact type.",
|
|
667
667
|
"complexTypes": [],
|
|
668
668
|
"type": "string | undefined"
|
|
669
669
|
},
|
|
@@ -691,7 +691,7 @@
|
|
|
691
691
|
"name": "example"
|
|
692
692
|
}
|
|
693
693
|
],
|
|
694
|
-
"docs": "The URL of the bundle to download.\n\nFor the `zip` artifact type, the URL must point to a ZIP file.\nFor the `manifest` artifact type, the URL serves as the base URL\nto download the individual files. For example, if the URL is\n`https://example.com/download`, the plugin will download the file\nwith the href `index.html` from `https://example.com/download?href=index.html`.\n\nTo **verify the integrity** of the file, the server should return\na `X-Checksum` header with the SHA-256 hash in
|
|
694
|
+
"docs": "The URL of the bundle to download.\n\nFor the `zip` artifact type, the URL must point to a ZIP file.\nFor the `manifest` artifact type, the URL serves as the base URL\nto download the individual files. For example, if the URL is\n`https://example.com/download`, the plugin will download the file\nwith the href `index.html` from `https://example.com/download?href=index.html`.\n\nTo **verify the integrity** of the file, the server should return\na `X-Checksum` header with the SHA-256 hash in hex format.\n\nTo **verify the signature** of the file, the server should return\na `X-Signature` header with the signed SHA-256 hash in base64 format.",
|
|
695
695
|
"complexTypes": [],
|
|
696
696
|
"type": "string"
|
|
697
697
|
}
|
|
@@ -413,7 +413,7 @@ export interface DownloadBundleOptions {
|
|
|
413
413
|
bundleId: string;
|
|
414
414
|
/**
|
|
415
415
|
* The checksum of the self-hosted bundle as a SHA-256 hash
|
|
416
|
-
* in
|
|
416
|
+
* in hex format to verify the integrity of the bundle.
|
|
417
417
|
*
|
|
418
418
|
* **Attention**: Only supported for the `zip` artifact type.
|
|
419
419
|
*
|
|
@@ -439,7 +439,7 @@ export interface DownloadBundleOptions {
|
|
|
439
439
|
* with the href `index.html` from `https://example.com/download?href=index.html`.
|
|
440
440
|
*
|
|
441
441
|
* To **verify the integrity** of the file, the server should return
|
|
442
|
-
* a `X-Checksum` header with the SHA-256 hash in
|
|
442
|
+
* a `X-Checksum` header with the SHA-256 hash in hex format.
|
|
443
443
|
*
|
|
444
444
|
* To **verify the signature** of the file, the server should return
|
|
445
445
|
* a `X-Signature` header with the signed SHA-256 hash in base64 format.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"AAAA,wCAAwC","sourcesContent":["/// <reference types=\"@capacitor/cli\" />\n\nimport type { PluginListenerHandle } from '@capacitor/core';\n\ndeclare module '@capacitor/cli' {\n export interface PluginsConfig {\n LiveUpdate?: {\n /**\n * The app ID is used to identify the app when using [Capawesome Cloud](https://capawesome.io/cloud/).\n *\n * This is **NOT** the same as the app identifier (e.g. `com.example.app`).\n * This is a unique identifier generated by Capawesome Cloud (e.g. `6e351b4f-69a7-415e-a057-4567df7ffe94`).\n *\n * @since 5.0.0\n * @example '6e351b4f-69a7-415e-a057-4567df7ffe94'\n */\n appId?: string;\n /**\n * Whether or not to automatically block bundles that have been rolled back.\n *\n * When enabled, the plugin will automatically block bundles that caused a rollback\n * (up to 100 bundles). When the limit is reached, the oldest blocked bundle is unblocked.\n * Blocked bundles will be skipped in future sync operations.\n *\n * **Attention**: This option has no effect if `readyTimeout` is set to `0`.\n *\n * Only available on Android and iOS.\n *\n * @since 7.3.0\n * @default false\n */\n autoBlockRolledBackBundles?: boolean;\n /**\n * Whether or not to automatically delete unused bundles.\n *\n * When enabled, the plugin will automatically delete unused bundles after calling `ready()`.\n *\n * @since 5.0.0\n * @default false\n */\n autoDeleteBundles?: boolean;\n /**\n * The auto-update strategy for live updates.\n *\n * - `none`: Live updates will not be applied automatically.\n * - `background`: Live updates will be automatically downloaded\n * and applied in the background at app startup and when the app resumes\n * (if the last check was more than 15 minutes ago).\n *\n * Only available on Android and iOS.\n *\n * @since 7.3.0\n * @default 'none'\n * @example 'background'\n */\n autoUpdateStrategy?: 'none' | 'background';\n /**\n * The default channel of the app.\n *\n * @since 6.3.0\n * @example 'production'\n */\n defaultChannel?: string;\n /**\n * The timeout in milliseconds for HTTP requests.\n *\n * @since 6.4.0\n * @default 60000\n */\n httpTimeout?: number;\n /**\n * The public key to verify the integrity of the bundle.\n *\n * The public key must be a PEM-encoded RSA public key.\n *\n * @since 6.1.0\n * @example '-----BEGIN PUBLIC KEY-----MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDDodf1SD0OOn6hIlDuKBza0Ed0OqtwyVJwiyjmE9BJaZ7y8ZUfcF+SKmd0l2cDPM45XIg2tAFux5n29uoKyHwSt+6tCi5CJA5Z1/1eZruRRqABLonV77KS3HUtvOgqRLDnKSV89dYZkM++NwmzOPgIF422mvc+VukcVOBfc8/AHQIDAQAB-----END PUBLIC KEY-----'\n */\n publicKey?: string;\n /**\n * The timeout in milliseconds to wait for the app to be ready\n * before resetting to the default bundle.\n *\n * It is strongly **recommended** to configure this option (e.g. `10000` ms)\n * so that the plugin can roll back to the default bundle in case of problems.\n * If configured, the plugin will wait for the app to call the `ready()`\n * method before resetting to the default bundle.\n *\n * Set to `0` to disable the timeout.\n *\n * @since 5.0.0\n * @default 0\n * @example 10000\n */\n readyTimeout?: number;\n /**\n * The API domain of the [Capawesome Cloud](https://cloud.capawesome.io) server **without** scheme or path.\n *\n * @since 7.0.0\n * @default 'api.cloud.capawesome.io'\n * @example 'api.cloud.capawesome.eu'\n */\n serverDomain?: string;\n };\n }\n}\n\nexport interface LiveUpdatePlugin {\n /**\n * Clear all blocked bundles from the blocked list.\n *\n * This removes all bundle identifiers that were automatically blocked\n * due to rollbacks when `autoBlockRolledBackBundles` is enabled.\n *\n * Only available on Android and iOS.\n *\n * @since 7.4.0\n */\n clearBlockedBundles(): Promise<void>;\n /**\n * Delete a bundle from the app.\n *\n * Only available on Android and iOS.\n *\n * @since 5.0.0\n */\n deleteBundle(options: DeleteBundleOptions): Promise<void>;\n /**\n * Download a bundle.\n *\n * Only available on Android and iOS.\n *\n * @since 5.0.0\n */\n downloadBundle(options: DownloadBundleOptions): Promise<void>;\n /**\n * Fetch the latest bundle using the [Capawesome Cloud](https://capawesome.io/cloud/).\n *\n * Only available on Android and iOS.\n *\n * @since 6.6.0\n */\n fetchLatestBundle(\n options?: FetchLatestBundleOptions,\n ): Promise<FetchLatestBundleResult>;\n /**\n * Get all blocked bundle identifiers.\n *\n * Returns the list of bundle identifiers that were automatically blocked\n * due to rollbacks when `autoBlockRolledBackBundles` is enabled.\n *\n * Only available on Android and iOS.\n *\n * @since 7.4.0\n */\n getBlockedBundles(): Promise<GetBlockedBundlesResult>;\n /**\n * Get all identifiers of bundles that have been downloaded.\n *\n * Only available on Android and iOS.\n *\n * @since 5.0.0\n * @deprecated Use `getDownloadedBundles()` instead.\n */\n getBundles(): Promise<GetBundlesResult>;\n /**\n * Get the channel that is used for the update.\n *\n * Only available on Android and iOS.\n *\n * @since 5.0.0\n */\n getChannel(): Promise<GetChannelResult>;\n /**\n * Get the runtime configuration.\n *\n * Returns the current plugin configuration including any runtime\n * overrides set via `setConfig()`.\n *\n * Only available on Android and iOS.\n *\n * @since 7.4.0\n */\n getConfig(): Promise<GetConfigResult>;\n /**\n * Get all identifiers of bundles that have been downloaded.\n *\n * Only available on Android and iOS.\n *\n * @since 7.4.0\n */\n getDownloadedBundles(): Promise<GetDownloadedBundlesResult>;\n /**\n * Get the bundle identifier of the current bundle.\n * The current bundle is the bundle that is currently used by the app.\n *\n * Only available on Android and iOS.\n *\n * @since 6.7.0\n */\n getCurrentBundle(): Promise<GetCurrentBundleResult>;\n /**\n * Get the custom identifier of the device.\n *\n * Only available on Android and iOS.\n *\n * @since 5.0.0\n */\n getCustomId(): Promise<GetCustomIdResult>;\n /**\n * Get the unique device identifier.\n *\n * Only available on Android and iOS.\n *\n * @since 5.0.0\n */\n getDeviceId(): Promise<GetDeviceIdResult>;\n /**\n * Check whether a sync operation is currently in progress.\n *\n * Only available on Android and iOS.\n *\n * @since 7.4.0\n */\n isSyncing(): Promise<IsSyncingResult>;\n /**\n * Get the bundle identifier of the next bundle.\n * The next bundle is the bundle that will be used after calling `reload()`\n * or restarting the app.\n *\n * Only available on Android and iOS.\n *\n * @since 6.7.0\n */\n getNextBundle(): Promise<GetNextBundleResult>;\n /**\n * Get the version code of the app.\n *\n * On **Android**, this is the `versionCode` from the `android/app/build.gradle` file.\n * On **iOS**, this is the `CFBundleVersion` from the `Info.plist` file.\n *\n * Only available on Android and iOS.\n *\n * @since 5.0.0\n */\n getVersionCode(): Promise<GetVersionCodeResult>;\n /**\n * Get the version name of the app.\n *\n * On **Android**, this is the `versionName` from the `android/app/build.gradle` file.\n * On **iOS**, this is the `CFBundleShortVersionString` from the `Info.plist` file.\n *\n * Only available on Android and iOS.\n *\n * @since 5.0.0\n */\n getVersionName(): Promise<GetVersionNameResult>;\n /**\n * Notify the plugin that the app is ready to use and no rollback is needed.\n *\n * **Attention**: This method should be called as soon as the app is ready to use\n * to prevent the app from being reset to the default bundle.\n *\n * Only available on Android and iOS.\n *\n * @since 5.0.0\n */\n ready(): Promise<ReadyResult>;\n /**\n * Reload the app to apply the new bundle.\n *\n * Only available on Android and iOS.\n *\n * @since 5.0.0\n */\n reload(): Promise<void>;\n /**\n * Reset the app to the default bundle.\n *\n * Call `reload()` or restart the app to apply the changes.\n *\n * Only available on Android and iOS.\n *\n * @since 5.0.0\n */\n reset(): Promise<void>;\n /**\n * Reset the runtime configuration to the values from the Capacitor config file.\n *\n * This clears any runtime configuration set via `setConfig()`.\n * The changes take effect immediately.\n *\n * Only available on Android and iOS.\n *\n * @since 7.4.0\n */\n resetConfig(): Promise<void>;\n /**\n * Set the channel to use for the update.\n *\n * Only available on Android and iOS.\n *\n * @since 5.0.0\n */\n setChannel(options: SetChannelOptions): Promise<void>;\n /**\n * Set the runtime configuration.\n *\n * This allows updating plugin configuration options at runtime.\n * The changes are persisted across app restarts and take effect immediately.\n *\n * **Important:** Runtime configuration is automatically reset to default values\n * whenever the native app is updated to a new version. This ensures that\n * configuration from previous versions doesn't persist after an app update.\n *\n * Only available on Android and iOS.\n *\n * @since 7.4.0\n */\n setConfig(options: SetConfigOptions): Promise<void>;\n /**\n * Set the custom identifier of the device.\n *\n * Only available on Android and iOS.\n *\n * @since 5.0.0\n */\n setCustomId(options: SetCustomIdOptions): Promise<void>;\n /**\n * Set the next bundle to use for the app.\n *\n * Call `reload()` or restart the app to apply the changes.\n *\n * Only available on Android and iOS.\n *\n * @since 6.7.0\n */\n setNextBundle(options: SetNextBundleOptions): Promise<void>;\n /**\n * Automatically download and set the latest bundle for the app using the [Capawesome Cloud](https://capawesome.io/cloud/).\n *\n * Call `reload()` or restart the app to apply the changes.\n *\n * Only available on Android and iOS.\n *\n * @since 5.0.0\n */\n sync(options?: SyncOptions): Promise<SyncResult>;\n /**\n * Listen for the download progress of a bundle.\n *\n * Only available on Android and iOS.\n *\n * @since 7.0.0\n */\n addListener(\n eventName: 'downloadBundleProgress',\n listenerFunc: DownloadBundleProgressListener,\n ): Promise<PluginListenerHandle>;\n /**\n * Listen for when a bundle is set as the next bundle.\n *\n * This event is triggered whenever a bundle is set to be used on the next app restart,\n * either through automatic updates or manual calls to `setNextBundle()`.\n *\n * Only available on Android and iOS.\n *\n * @since 7.3.0\n */\n addListener(\n eventName: 'nextBundleSet',\n listenerFunc: NextBundleSetListener,\n ): Promise<PluginListenerHandle>;\n /**\n * Listen for when the app is reloaded.\n *\n * This event is triggered after the `reload()` method is called\n * and the app has been reloaded.\n *\n * **Note:** To verify whether an update was successfully applied after a reload,\n * use the `ready()` method instead. The `ready()` method provides detailed information\n * about the current bundle, previous bundle, and whether a rollback occurred.\n *\n * Only available on Android and iOS.\n *\n * @since 7.4.0\n */\n addListener(\n eventName: 'reloaded',\n listenerFunc: ReloadedListener,\n ): Promise<PluginListenerHandle>;\n /**\n * Remove all listeners for this plugin.\n *\n * @since 7.2.0\n */\n removeAllListeners(): Promise<void>;\n}\n\n/**\n * @since 5.0.0\n */\nexport interface DeleteBundleOptions {\n /**\n * The unique identifier of the bundle to delete.\n *\n * @since 5.0.0\n * @example '1.0.0'\n */\n bundleId: string;\n}\n\nexport interface DownloadBundleOptions {\n /**\n * The artifact type of the bundle.\n *\n * @since 6.6.0\n * @default 'zip'\n * @example 'manifest'\n */\n artifactType?: 'manifest' | 'zip';\n /**\n * The unique identifier of the bundle.\n *\n * **Attention**: The value `public` is reserved and cannot be used as a bundle identifier.\n *\n * @since 5.0.0\n * @example '1.0.0'\n */\n bundleId: string;\n /**\n * The checksum of the self-hosted bundle as a SHA-256 hash\n * in base64 format to verify the integrity of the bundle.\n *\n * **Attention**: Only supported for the `zip` artifact type.\n *\n * @since 7.1.0\n */\n checksum?: string;\n /**\n * The signature of the self-hosted bundle as a signed SHA-256 hash\n * in base64 format to verify the integrity of the bundle.\n *\n * **Attention**: Only supported for the `zip` artifact type.\n *\n * @since 7.1.0\n */\n signature?: string;\n /**\n * The URL of the bundle to download.\n *\n * For the `zip` artifact type, the URL must point to a ZIP file.\n * For the `manifest` artifact type, the URL serves as the base URL\n * to download the individual files. For example, if the URL is\n * `https://example.com/download`, the plugin will download the file\n * with the href `index.html` from `https://example.com/download?href=index.html`.\n *\n * To **verify the integrity** of the file, the server should return\n * a `X-Checksum` header with the SHA-256 hash in base64 format.\n *\n * To **verify the signature** of the file, the server should return\n * a `X-Signature` header with the signed SHA-256 hash in base64 format.\n *\n * @since 5.0.0\n * @example 'https://example.com/bundle.zip'\n */\n url: string;\n}\n\n/**\n * @since 6.7.0\n */\nexport interface FetchLatestBundleOptions {\n /**\n * The name of the channel where the latest bundle is fetched from.\n *\n * @since 6.7.0\n */\n channel?: string;\n}\n\n/**\n * @since 6.6.0\n */\nexport interface FetchLatestBundleResult {\n /**\n * The artifact type of the bundle.\n *\n * @since 6.7.0\n */\n artifactType?: 'manifest' | 'zip';\n /**\n * The unique identifier of the latest bundle.\n *\n * If `null`, no bundle is available.\n *\n * @since 6.6.0\n */\n bundleId: string | null;\n /**\n * The checksum of the latest bundle if the bundle is self-hosted.\n *\n * If the bundle is hosted on Capawesome Cloud, the checksum will be\n * returned as response header when downloading the bundle.\n *\n * @since 7.1.0\n */\n checksum?: string;\n /**\n * Custom properties that are associated with the latest bundle.\n *\n * @since 7.0.0\n * @example { \"key\": \"value\" }\n */\n customProperties?: { [key: string]: string };\n /**\n * The URL of the latest bundle to download.\n * Pass this URL to the `downloadBundle(...)` method to download the bundle.\n *\n * @since 6.7.0\n */\n downloadUrl?: string;\n /**\n * The signature of the latest bundle if the bundle is self-hosted.\n *\n * If the bundle is hosted on Capawesome Cloud, the signature will be\n * returned as response header when downloading the bundle.\n *\n * @since 7.1.0\n */\n signature?: string;\n}\n\n/**\n * @since 5.0.0\n */\nexport interface GetBundleResult {\n /**\n * The unique identifier of the active bundle.\n *\n * If `null`, the default bundle is being used.\n *\n * @since 5.0.0\n * @example '1.0.0'\n */\n bundleId: string | null;\n}\n\n/**\n * @since 7.4.0\n */\nexport interface GetBlockedBundlesResult {\n /**\n * An array of unique identifiers of all blocked bundles.\n *\n * @since 7.4.0\n */\n bundleIds: string[];\n}\n\n/**\n * @since 5.0.0\n */\nexport interface GetBundlesResult {\n /**\n * An array of unique identifiers of all available bundles.\n *\n * @since 5.0.0\n */\n bundleIds: string[];\n}\n\n/**\n * @since 7.4.0\n */\nexport interface GetDownloadedBundlesResult {\n /**\n * An array of unique identifiers of all downloaded bundles.\n *\n * @since 7.4.0\n */\n bundleIds: string[];\n}\n\n/**\n * @since 5.0.0\n */\nexport interface GetChannelResult {\n /**\n * The channel name.\n *\n * If `null`, the app is using the default channel.\n *\n * @since 5.0.0\n * @example 'production'\n */\n channel: string | null;\n}\n\n/**\n * @since 7.4.0\n */\nexport interface GetConfigResult {\n /**\n * The app ID used to identify the app.\n *\n * If `null`, no app ID is configured.\n *\n * @since 7.4.0\n * @example '6e351b4f-69a7-415e-a057-4567df7ffe94'\n */\n appId: string | null;\n /**\n * The auto-update strategy for live updates.\n *\n * @since 7.4.0\n * @example 'background'\n */\n autoUpdateStrategy: 'none' | 'background';\n}\n\n/**\n * @since 6.7.0\n */\nexport interface GetCurrentBundleResult {\n /**\n * The unique identifier of the current bundle.\n *\n * If `null`, the default bundle is being used.\n *\n * @since 6.7.0\n */\n bundleId: string | null;\n}\n\n/**\n * @since 5.0.0\n */\nexport interface GetDeviceIdResult {\n /**\n * The unique identifier of the device.\n *\n * On iOS, [`identifierForVendor`](https://developer.apple.com/documentation/uikit/uidevice/1620059-identifierforvendor) is used.\n * The value of this property is the same for apps that come from the same vendor running on the same device.\n *\n * @since 5.0.0\n *\n * @example '50d2a548-80b7-4dad-adc7-97c0e79d8a89'\n */\n deviceId: string;\n}\n\n/**\n * @since 7.4.0\n */\nexport interface IsSyncingResult {\n /**\n * Whether a sync operation is currently in progress.\n *\n * @since 7.4.0\n */\n syncing: boolean;\n}\n\n/**\n * @since 6.7.0\n */\nexport interface GetNextBundleResult {\n /**\n * The unique identifier of the next bundle.\n *\n * If `null`, the default bundle is being used.\n *\n * @since 6.7.0\n */\n bundleId: string | null;\n}\n\n/**\n * @since 5.0.0\n */\nexport interface GetVersionCodeResult {\n /**\n * The version code of the app.\n *\n * On **Android**, this is the `versionCode` from the `android/app/build.gradle` file.\n * On **iOS**, this is the `CFBundleVersion` from the `Info.plist` file.\n *\n * @since 5.0.0\n * @example \"1\"\n */\n versionCode: string;\n}\n\n/**\n * @since 5.0.0\n */\nexport interface GetVersionNameResult {\n /**\n * The version name of the app.\n *\n * On **Android**, this is the `versionName` from the `android/app/build.gradle` file.\n * On **iOS**, this is the `CFBundleShortVersionString` from the `Info.plist` file.\n *\n * @since 5.0.0\n * @example \"1.0.0\"\n */\n versionName: string;\n}\n\n/**\n * @since 5.0.0\n */\nexport interface GetCustomIdResult {\n /**\n * The custom identifier of the device.\n *\n * If `null`, no custom identifier is set.\n *\n * @since 5.0.0\n * @example '50d2a548-80b7-4dad-adc7-97c0e79d8a89'\n */\n customId: string | null;\n}\n\n/**\n * @since 7.0.0\n */\nexport interface ReadyResult {\n /**\n * The identifier of the previous bundle used.\n *\n * If `null`, the default bundle was used.\n *\n * @since 7.0.0\n */\n previousBundleId: string | null;\n /**\n * The identifier of the current bundle used.\n *\n * If `null`, the default bundle is being used.\n *\n * @since 7.0.0\n */\n currentBundleId: string | null;\n /**\n * Whether or not the app was reset to the default bundle.\n *\n * @since 7.0.0\n */\n rollback: boolean;\n}\n\n/**\n * @since 5.0.0\n */\nexport interface SetBundleOptions {\n /**\n * The unique identifier of the bundle to use.\n *\n * @since 5.0.0\n * @example '1.0.0'\n */\n bundleId: string;\n}\n\n/**\n * @since 5.0.0\n */\nexport interface SetChannelOptions {\n /**\n * The channel name.\n *\n * Set `null` to remove the channel.\n *\n * @since 5.0.0\n */\n channel: string | null;\n}\n\n/**\n * @since 7.4.0\n */\nexport interface SetConfigOptions {\n /**\n * The app ID used to identify the app.\n *\n * Set `null` to reset to the value from the Capacitor config file.\n *\n * @since 7.4.0\n * @example '6e351b4f-69a7-415e-a057-4567df7ffe94'\n */\n appId?: string | null;\n}\n\n/**\n * @since 5.0.0\n */\nexport interface SetCustomIdOptions {\n /**\n * The custom identifier of the device.\n *\n * Set `null` to remove the custom identifier.\n *\n * @since 5.0.0\n */\n customId: string | null;\n}\n\n/**\n * @since 6.7.0\n */\nexport interface SetNextBundleOptions {\n /**\n * The unique identifier of the bundle to use.\n *\n * Set `null` to use the default bundle (same as calling `reset()`).\n *\n * @since 6.7.0\n * @example '1.0.0'\n */\n bundleId: string | null;\n}\n\n/**\n * @since 6.7.0\n */\nexport interface SyncOptions {\n /**\n * The name of the channel where the latest bundle is fetched from.\n *\n * @since 6.7.0\n */\n channel?: string;\n}\n\n/**\n * @since 5.0.0\n */\nexport interface SyncResult {\n /**\n * The identifier of the next bundle to use.\n *\n * If `null`, the app is up-to-date and no new bundle is available.\n *\n * @since 5.0.0\n */\n nextBundleId: string | null;\n}\n\n/**\n * Listener for the download progress of a bundle.\n *\n * @since 7.0.0\n */\nexport type DownloadBundleProgressListener = (\n event: DownloadBundleProgressEvent,\n) => void;\n\n/**\n * Event that is triggered when the download progress of a bundle changes.\n *\n * @since 7.0.0\n */\nexport interface DownloadBundleProgressEvent {\n /**\n * The unique identifier of the bundle that is being downloaded.\n *\n * @since 7.0.0\n */\n bundleId: string;\n /**\n * The number of bytes that have been downloaded.\n *\n * @since 7.0.0\n */\n downloadedBytes: number;\n /**\n * The progress of the download in percent as a value between `0` and `1`.\n *\n * @since 7.0.0\n * @example 0.5\n */\n progress: number;\n /**\n * The total number of bytes to download.\n *\n * @since 7.0.0\n */\n totalBytes: number;\n}\n\n/**\n * Listener for when a bundle is set as the next bundle.\n *\n * @since 7.3.0\n */\nexport type NextBundleSetListener = (event: NextBundleSetEvent) => void;\n\n/**\n * Event that is triggered when a bundle is set as the next bundle.\n *\n * @since 7.3.0\n */\nexport interface NextBundleSetEvent {\n /**\n * The unique identifier of the bundle that is set as the next bundle.\n *\n * If `null`, the default bundle will be used.\n *\n * @since 7.3.0\n * @example '1.0.0'\n */\n bundleId: string | null;\n}\n\n/**\n * Listener for when the app is reloaded.\n *\n * @since 7.4.0\n */\nexport type ReloadedListener = () => void;\n"]}
|
|
1
|
+
{"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"AAAA,wCAAwC","sourcesContent":["/// <reference types=\"@capacitor/cli\" />\n\nimport type { PluginListenerHandle } from '@capacitor/core';\n\ndeclare module '@capacitor/cli' {\n export interface PluginsConfig {\n LiveUpdate?: {\n /**\n * The app ID is used to identify the app when using [Capawesome Cloud](https://capawesome.io/cloud/).\n *\n * This is **NOT** the same as the app identifier (e.g. `com.example.app`).\n * This is a unique identifier generated by Capawesome Cloud (e.g. `6e351b4f-69a7-415e-a057-4567df7ffe94`).\n *\n * @since 5.0.0\n * @example '6e351b4f-69a7-415e-a057-4567df7ffe94'\n */\n appId?: string;\n /**\n * Whether or not to automatically block bundles that have been rolled back.\n *\n * When enabled, the plugin will automatically block bundles that caused a rollback\n * (up to 100 bundles). When the limit is reached, the oldest blocked bundle is unblocked.\n * Blocked bundles will be skipped in future sync operations.\n *\n * **Attention**: This option has no effect if `readyTimeout` is set to `0`.\n *\n * Only available on Android and iOS.\n *\n * @since 7.3.0\n * @default false\n */\n autoBlockRolledBackBundles?: boolean;\n /**\n * Whether or not to automatically delete unused bundles.\n *\n * When enabled, the plugin will automatically delete unused bundles after calling `ready()`.\n *\n * @since 5.0.0\n * @default false\n */\n autoDeleteBundles?: boolean;\n /**\n * The auto-update strategy for live updates.\n *\n * - `none`: Live updates will not be applied automatically.\n * - `background`: Live updates will be automatically downloaded\n * and applied in the background at app startup and when the app resumes\n * (if the last check was more than 15 minutes ago).\n *\n * Only available on Android and iOS.\n *\n * @since 7.3.0\n * @default 'none'\n * @example 'background'\n */\n autoUpdateStrategy?: 'none' | 'background';\n /**\n * The default channel of the app.\n *\n * @since 6.3.0\n * @example 'production'\n */\n defaultChannel?: string;\n /**\n * The timeout in milliseconds for HTTP requests.\n *\n * @since 6.4.0\n * @default 60000\n */\n httpTimeout?: number;\n /**\n * The public key to verify the integrity of the bundle.\n *\n * The public key must be a PEM-encoded RSA public key.\n *\n * @since 6.1.0\n * @example '-----BEGIN PUBLIC KEY-----MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDDodf1SD0OOn6hIlDuKBza0Ed0OqtwyVJwiyjmE9BJaZ7y8ZUfcF+SKmd0l2cDPM45XIg2tAFux5n29uoKyHwSt+6tCi5CJA5Z1/1eZruRRqABLonV77KS3HUtvOgqRLDnKSV89dYZkM++NwmzOPgIF422mvc+VukcVOBfc8/AHQIDAQAB-----END PUBLIC KEY-----'\n */\n publicKey?: string;\n /**\n * The timeout in milliseconds to wait for the app to be ready\n * before resetting to the default bundle.\n *\n * It is strongly **recommended** to configure this option (e.g. `10000` ms)\n * so that the plugin can roll back to the default bundle in case of problems.\n * If configured, the plugin will wait for the app to call the `ready()`\n * method before resetting to the default bundle.\n *\n * Set to `0` to disable the timeout.\n *\n * @since 5.0.0\n * @default 0\n * @example 10000\n */\n readyTimeout?: number;\n /**\n * The API domain of the [Capawesome Cloud](https://cloud.capawesome.io) server **without** scheme or path.\n *\n * @since 7.0.0\n * @default 'api.cloud.capawesome.io'\n * @example 'api.cloud.capawesome.eu'\n */\n serverDomain?: string;\n };\n }\n}\n\nexport interface LiveUpdatePlugin {\n /**\n * Clear all blocked bundles from the blocked list.\n *\n * This removes all bundle identifiers that were automatically blocked\n * due to rollbacks when `autoBlockRolledBackBundles` is enabled.\n *\n * Only available on Android and iOS.\n *\n * @since 7.4.0\n */\n clearBlockedBundles(): Promise<void>;\n /**\n * Delete a bundle from the app.\n *\n * Only available on Android and iOS.\n *\n * @since 5.0.0\n */\n deleteBundle(options: DeleteBundleOptions): Promise<void>;\n /**\n * Download a bundle.\n *\n * Only available on Android and iOS.\n *\n * @since 5.0.0\n */\n downloadBundle(options: DownloadBundleOptions): Promise<void>;\n /**\n * Fetch the latest bundle using the [Capawesome Cloud](https://capawesome.io/cloud/).\n *\n * Only available on Android and iOS.\n *\n * @since 6.6.0\n */\n fetchLatestBundle(\n options?: FetchLatestBundleOptions,\n ): Promise<FetchLatestBundleResult>;\n /**\n * Get all blocked bundle identifiers.\n *\n * Returns the list of bundle identifiers that were automatically blocked\n * due to rollbacks when `autoBlockRolledBackBundles` is enabled.\n *\n * Only available on Android and iOS.\n *\n * @since 7.4.0\n */\n getBlockedBundles(): Promise<GetBlockedBundlesResult>;\n /**\n * Get all identifiers of bundles that have been downloaded.\n *\n * Only available on Android and iOS.\n *\n * @since 5.0.0\n * @deprecated Use `getDownloadedBundles()` instead.\n */\n getBundles(): Promise<GetBundlesResult>;\n /**\n * Get the channel that is used for the update.\n *\n * Only available on Android and iOS.\n *\n * @since 5.0.0\n */\n getChannel(): Promise<GetChannelResult>;\n /**\n * Get the runtime configuration.\n *\n * Returns the current plugin configuration including any runtime\n * overrides set via `setConfig()`.\n *\n * Only available on Android and iOS.\n *\n * @since 7.4.0\n */\n getConfig(): Promise<GetConfigResult>;\n /**\n * Get all identifiers of bundles that have been downloaded.\n *\n * Only available on Android and iOS.\n *\n * @since 7.4.0\n */\n getDownloadedBundles(): Promise<GetDownloadedBundlesResult>;\n /**\n * Get the bundle identifier of the current bundle.\n * The current bundle is the bundle that is currently used by the app.\n *\n * Only available on Android and iOS.\n *\n * @since 6.7.0\n */\n getCurrentBundle(): Promise<GetCurrentBundleResult>;\n /**\n * Get the custom identifier of the device.\n *\n * Only available on Android and iOS.\n *\n * @since 5.0.0\n */\n getCustomId(): Promise<GetCustomIdResult>;\n /**\n * Get the unique device identifier.\n *\n * Only available on Android and iOS.\n *\n * @since 5.0.0\n */\n getDeviceId(): Promise<GetDeviceIdResult>;\n /**\n * Check whether a sync operation is currently in progress.\n *\n * Only available on Android and iOS.\n *\n * @since 7.4.0\n */\n isSyncing(): Promise<IsSyncingResult>;\n /**\n * Get the bundle identifier of the next bundle.\n * The next bundle is the bundle that will be used after calling `reload()`\n * or restarting the app.\n *\n * Only available on Android and iOS.\n *\n * @since 6.7.0\n */\n getNextBundle(): Promise<GetNextBundleResult>;\n /**\n * Get the version code of the app.\n *\n * On **Android**, this is the `versionCode` from the `android/app/build.gradle` file.\n * On **iOS**, this is the `CFBundleVersion` from the `Info.plist` file.\n *\n * Only available on Android and iOS.\n *\n * @since 5.0.0\n */\n getVersionCode(): Promise<GetVersionCodeResult>;\n /**\n * Get the version name of the app.\n *\n * On **Android**, this is the `versionName` from the `android/app/build.gradle` file.\n * On **iOS**, this is the `CFBundleShortVersionString` from the `Info.plist` file.\n *\n * Only available on Android and iOS.\n *\n * @since 5.0.0\n */\n getVersionName(): Promise<GetVersionNameResult>;\n /**\n * Notify the plugin that the app is ready to use and no rollback is needed.\n *\n * **Attention**: This method should be called as soon as the app is ready to use\n * to prevent the app from being reset to the default bundle.\n *\n * Only available on Android and iOS.\n *\n * @since 5.0.0\n */\n ready(): Promise<ReadyResult>;\n /**\n * Reload the app to apply the new bundle.\n *\n * Only available on Android and iOS.\n *\n * @since 5.0.0\n */\n reload(): Promise<void>;\n /**\n * Reset the app to the default bundle.\n *\n * Call `reload()` or restart the app to apply the changes.\n *\n * Only available on Android and iOS.\n *\n * @since 5.0.0\n */\n reset(): Promise<void>;\n /**\n * Reset the runtime configuration to the values from the Capacitor config file.\n *\n * This clears any runtime configuration set via `setConfig()`.\n * The changes take effect immediately.\n *\n * Only available on Android and iOS.\n *\n * @since 7.4.0\n */\n resetConfig(): Promise<void>;\n /**\n * Set the channel to use for the update.\n *\n * Only available on Android and iOS.\n *\n * @since 5.0.0\n */\n setChannel(options: SetChannelOptions): Promise<void>;\n /**\n * Set the runtime configuration.\n *\n * This allows updating plugin configuration options at runtime.\n * The changes are persisted across app restarts and take effect immediately.\n *\n * **Important:** Runtime configuration is automatically reset to default values\n * whenever the native app is updated to a new version. This ensures that\n * configuration from previous versions doesn't persist after an app update.\n *\n * Only available on Android and iOS.\n *\n * @since 7.4.0\n */\n setConfig(options: SetConfigOptions): Promise<void>;\n /**\n * Set the custom identifier of the device.\n *\n * Only available on Android and iOS.\n *\n * @since 5.0.0\n */\n setCustomId(options: SetCustomIdOptions): Promise<void>;\n /**\n * Set the next bundle to use for the app.\n *\n * Call `reload()` or restart the app to apply the changes.\n *\n * Only available on Android and iOS.\n *\n * @since 6.7.0\n */\n setNextBundle(options: SetNextBundleOptions): Promise<void>;\n /**\n * Automatically download and set the latest bundle for the app using the [Capawesome Cloud](https://capawesome.io/cloud/).\n *\n * Call `reload()` or restart the app to apply the changes.\n *\n * Only available on Android and iOS.\n *\n * @since 5.0.0\n */\n sync(options?: SyncOptions): Promise<SyncResult>;\n /**\n * Listen for the download progress of a bundle.\n *\n * Only available on Android and iOS.\n *\n * @since 7.0.0\n */\n addListener(\n eventName: 'downloadBundleProgress',\n listenerFunc: DownloadBundleProgressListener,\n ): Promise<PluginListenerHandle>;\n /**\n * Listen for when a bundle is set as the next bundle.\n *\n * This event is triggered whenever a bundle is set to be used on the next app restart,\n * either through automatic updates or manual calls to `setNextBundle()`.\n *\n * Only available on Android and iOS.\n *\n * @since 7.3.0\n */\n addListener(\n eventName: 'nextBundleSet',\n listenerFunc: NextBundleSetListener,\n ): Promise<PluginListenerHandle>;\n /**\n * Listen for when the app is reloaded.\n *\n * This event is triggered after the `reload()` method is called\n * and the app has been reloaded.\n *\n * **Note:** To verify whether an update was successfully applied after a reload,\n * use the `ready()` method instead. The `ready()` method provides detailed information\n * about the current bundle, previous bundle, and whether a rollback occurred.\n *\n * Only available on Android and iOS.\n *\n * @since 7.4.0\n */\n addListener(\n eventName: 'reloaded',\n listenerFunc: ReloadedListener,\n ): Promise<PluginListenerHandle>;\n /**\n * Remove all listeners for this plugin.\n *\n * @since 7.2.0\n */\n removeAllListeners(): Promise<void>;\n}\n\n/**\n * @since 5.0.0\n */\nexport interface DeleteBundleOptions {\n /**\n * The unique identifier of the bundle to delete.\n *\n * @since 5.0.0\n * @example '1.0.0'\n */\n bundleId: string;\n}\n\nexport interface DownloadBundleOptions {\n /**\n * The artifact type of the bundle.\n *\n * @since 6.6.0\n * @default 'zip'\n * @example 'manifest'\n */\n artifactType?: 'manifest' | 'zip';\n /**\n * The unique identifier of the bundle.\n *\n * **Attention**: The value `public` is reserved and cannot be used as a bundle identifier.\n *\n * @since 5.0.0\n * @example '1.0.0'\n */\n bundleId: string;\n /**\n * The checksum of the self-hosted bundle as a SHA-256 hash\n * in hex format to verify the integrity of the bundle.\n *\n * **Attention**: Only supported for the `zip` artifact type.\n *\n * @since 7.1.0\n */\n checksum?: string;\n /**\n * The signature of the self-hosted bundle as a signed SHA-256 hash\n * in base64 format to verify the integrity of the bundle.\n *\n * **Attention**: Only supported for the `zip` artifact type.\n *\n * @since 7.1.0\n */\n signature?: string;\n /**\n * The URL of the bundle to download.\n *\n * For the `zip` artifact type, the URL must point to a ZIP file.\n * For the `manifest` artifact type, the URL serves as the base URL\n * to download the individual files. For example, if the URL is\n * `https://example.com/download`, the plugin will download the file\n * with the href `index.html` from `https://example.com/download?href=index.html`.\n *\n * To **verify the integrity** of the file, the server should return\n * a `X-Checksum` header with the SHA-256 hash in hex format.\n *\n * To **verify the signature** of the file, the server should return\n * a `X-Signature` header with the signed SHA-256 hash in base64 format.\n *\n * @since 5.0.0\n * @example 'https://example.com/bundle.zip'\n */\n url: string;\n}\n\n/**\n * @since 6.7.0\n */\nexport interface FetchLatestBundleOptions {\n /**\n * The name of the channel where the latest bundle is fetched from.\n *\n * @since 6.7.0\n */\n channel?: string;\n}\n\n/**\n * @since 6.6.0\n */\nexport interface FetchLatestBundleResult {\n /**\n * The artifact type of the bundle.\n *\n * @since 6.7.0\n */\n artifactType?: 'manifest' | 'zip';\n /**\n * The unique identifier of the latest bundle.\n *\n * If `null`, no bundle is available.\n *\n * @since 6.6.0\n */\n bundleId: string | null;\n /**\n * The checksum of the latest bundle if the bundle is self-hosted.\n *\n * If the bundle is hosted on Capawesome Cloud, the checksum will be\n * returned as response header when downloading the bundle.\n *\n * @since 7.1.0\n */\n checksum?: string;\n /**\n * Custom properties that are associated with the latest bundle.\n *\n * @since 7.0.0\n * @example { \"key\": \"value\" }\n */\n customProperties?: { [key: string]: string };\n /**\n * The URL of the latest bundle to download.\n * Pass this URL to the `downloadBundle(...)` method to download the bundle.\n *\n * @since 6.7.0\n */\n downloadUrl?: string;\n /**\n * The signature of the latest bundle if the bundle is self-hosted.\n *\n * If the bundle is hosted on Capawesome Cloud, the signature will be\n * returned as response header when downloading the bundle.\n *\n * @since 7.1.0\n */\n signature?: string;\n}\n\n/**\n * @since 5.0.0\n */\nexport interface GetBundleResult {\n /**\n * The unique identifier of the active bundle.\n *\n * If `null`, the default bundle is being used.\n *\n * @since 5.0.0\n * @example '1.0.0'\n */\n bundleId: string | null;\n}\n\n/**\n * @since 7.4.0\n */\nexport interface GetBlockedBundlesResult {\n /**\n * An array of unique identifiers of all blocked bundles.\n *\n * @since 7.4.0\n */\n bundleIds: string[];\n}\n\n/**\n * @since 5.0.0\n */\nexport interface GetBundlesResult {\n /**\n * An array of unique identifiers of all available bundles.\n *\n * @since 5.0.0\n */\n bundleIds: string[];\n}\n\n/**\n * @since 7.4.0\n */\nexport interface GetDownloadedBundlesResult {\n /**\n * An array of unique identifiers of all downloaded bundles.\n *\n * @since 7.4.0\n */\n bundleIds: string[];\n}\n\n/**\n * @since 5.0.0\n */\nexport interface GetChannelResult {\n /**\n * The channel name.\n *\n * If `null`, the app is using the default channel.\n *\n * @since 5.0.0\n * @example 'production'\n */\n channel: string | null;\n}\n\n/**\n * @since 7.4.0\n */\nexport interface GetConfigResult {\n /**\n * The app ID used to identify the app.\n *\n * If `null`, no app ID is configured.\n *\n * @since 7.4.0\n * @example '6e351b4f-69a7-415e-a057-4567df7ffe94'\n */\n appId: string | null;\n /**\n * The auto-update strategy for live updates.\n *\n * @since 7.4.0\n * @example 'background'\n */\n autoUpdateStrategy: 'none' | 'background';\n}\n\n/**\n * @since 6.7.0\n */\nexport interface GetCurrentBundleResult {\n /**\n * The unique identifier of the current bundle.\n *\n * If `null`, the default bundle is being used.\n *\n * @since 6.7.0\n */\n bundleId: string | null;\n}\n\n/**\n * @since 5.0.0\n */\nexport interface GetDeviceIdResult {\n /**\n * The unique identifier of the device.\n *\n * On iOS, [`identifierForVendor`](https://developer.apple.com/documentation/uikit/uidevice/1620059-identifierforvendor) is used.\n * The value of this property is the same for apps that come from the same vendor running on the same device.\n *\n * @since 5.0.0\n *\n * @example '50d2a548-80b7-4dad-adc7-97c0e79d8a89'\n */\n deviceId: string;\n}\n\n/**\n * @since 7.4.0\n */\nexport interface IsSyncingResult {\n /**\n * Whether a sync operation is currently in progress.\n *\n * @since 7.4.0\n */\n syncing: boolean;\n}\n\n/**\n * @since 6.7.0\n */\nexport interface GetNextBundleResult {\n /**\n * The unique identifier of the next bundle.\n *\n * If `null`, the default bundle is being used.\n *\n * @since 6.7.0\n */\n bundleId: string | null;\n}\n\n/**\n * @since 5.0.0\n */\nexport interface GetVersionCodeResult {\n /**\n * The version code of the app.\n *\n * On **Android**, this is the `versionCode` from the `android/app/build.gradle` file.\n * On **iOS**, this is the `CFBundleVersion` from the `Info.plist` file.\n *\n * @since 5.0.0\n * @example \"1\"\n */\n versionCode: string;\n}\n\n/**\n * @since 5.0.0\n */\nexport interface GetVersionNameResult {\n /**\n * The version name of the app.\n *\n * On **Android**, this is the `versionName` from the `android/app/build.gradle` file.\n * On **iOS**, this is the `CFBundleShortVersionString` from the `Info.plist` file.\n *\n * @since 5.0.0\n * @example \"1.0.0\"\n */\n versionName: string;\n}\n\n/**\n * @since 5.0.0\n */\nexport interface GetCustomIdResult {\n /**\n * The custom identifier of the device.\n *\n * If `null`, no custom identifier is set.\n *\n * @since 5.0.0\n * @example '50d2a548-80b7-4dad-adc7-97c0e79d8a89'\n */\n customId: string | null;\n}\n\n/**\n * @since 7.0.0\n */\nexport interface ReadyResult {\n /**\n * The identifier of the previous bundle used.\n *\n * If `null`, the default bundle was used.\n *\n * @since 7.0.0\n */\n previousBundleId: string | null;\n /**\n * The identifier of the current bundle used.\n *\n * If `null`, the default bundle is being used.\n *\n * @since 7.0.0\n */\n currentBundleId: string | null;\n /**\n * Whether or not the app was reset to the default bundle.\n *\n * @since 7.0.0\n */\n rollback: boolean;\n}\n\n/**\n * @since 5.0.0\n */\nexport interface SetBundleOptions {\n /**\n * The unique identifier of the bundle to use.\n *\n * @since 5.0.0\n * @example '1.0.0'\n */\n bundleId: string;\n}\n\n/**\n * @since 5.0.0\n */\nexport interface SetChannelOptions {\n /**\n * The channel name.\n *\n * Set `null` to remove the channel.\n *\n * @since 5.0.0\n */\n channel: string | null;\n}\n\n/**\n * @since 7.4.0\n */\nexport interface SetConfigOptions {\n /**\n * The app ID used to identify the app.\n *\n * Set `null` to reset to the value from the Capacitor config file.\n *\n * @since 7.4.0\n * @example '6e351b4f-69a7-415e-a057-4567df7ffe94'\n */\n appId?: string | null;\n}\n\n/**\n * @since 5.0.0\n */\nexport interface SetCustomIdOptions {\n /**\n * The custom identifier of the device.\n *\n * Set `null` to remove the custom identifier.\n *\n * @since 5.0.0\n */\n customId: string | null;\n}\n\n/**\n * @since 6.7.0\n */\nexport interface SetNextBundleOptions {\n /**\n * The unique identifier of the bundle to use.\n *\n * Set `null` to use the default bundle (same as calling `reset()`).\n *\n * @since 6.7.0\n * @example '1.0.0'\n */\n bundleId: string | null;\n}\n\n/**\n * @since 6.7.0\n */\nexport interface SyncOptions {\n /**\n * The name of the channel where the latest bundle is fetched from.\n *\n * @since 6.7.0\n */\n channel?: string;\n}\n\n/**\n * @since 5.0.0\n */\nexport interface SyncResult {\n /**\n * The identifier of the next bundle to use.\n *\n * If `null`, the app is up-to-date and no new bundle is available.\n *\n * @since 5.0.0\n */\n nextBundleId: string | null;\n}\n\n/**\n * Listener for the download progress of a bundle.\n *\n * @since 7.0.0\n */\nexport type DownloadBundleProgressListener = (\n event: DownloadBundleProgressEvent,\n) => void;\n\n/**\n * Event that is triggered when the download progress of a bundle changes.\n *\n * @since 7.0.0\n */\nexport interface DownloadBundleProgressEvent {\n /**\n * The unique identifier of the bundle that is being downloaded.\n *\n * @since 7.0.0\n */\n bundleId: string;\n /**\n * The number of bytes that have been downloaded.\n *\n * @since 7.0.0\n */\n downloadedBytes: number;\n /**\n * The progress of the download in percent as a value between `0` and `1`.\n *\n * @since 7.0.0\n * @example 0.5\n */\n progress: number;\n /**\n * The total number of bytes to download.\n *\n * @since 7.0.0\n */\n totalBytes: number;\n}\n\n/**\n * Listener for when a bundle is set as the next bundle.\n *\n * @since 7.3.0\n */\nexport type NextBundleSetListener = (event: NextBundleSetEvent) => void;\n\n/**\n * Event that is triggered when a bundle is set as the next bundle.\n *\n * @since 7.3.0\n */\nexport interface NextBundleSetEvent {\n /**\n * The unique identifier of the bundle that is set as the next bundle.\n *\n * If `null`, the default bundle will be used.\n *\n * @since 7.3.0\n * @example '1.0.0'\n */\n bundleId: string | null;\n}\n\n/**\n * Listener for when the app is reloaded.\n *\n * @since 7.4.0\n */\nexport type ReloadedListener = () => void;\n"]}
|
|
@@ -7,20 +7,16 @@ import CommonCrypto
|
|
|
7
7
|
|
|
8
8
|
// swiftlint:disable type_body_length
|
|
9
9
|
@objc public class LiveUpdate: NSObject {
|
|
10
|
-
public static let tag = "LiveUpdate"
|
|
11
|
-
public static let version = "8.1.1"
|
|
12
|
-
|
|
13
10
|
private let autoUpdateIntervalMs: Int64 = 15 * 60 * 1000 // 15 minutes
|
|
14
|
-
private let bridge: CAPBridgeProtocol
|
|
15
11
|
private let bundlesDirectory = "NoCloud/ionic_built_snapshots" // DO NOT CHANGE! (See https://dub.sh/BLluidt)
|
|
16
12
|
private let cachesDirectoryUrl = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
|
|
17
13
|
private let config: LiveUpdateConfig
|
|
18
14
|
private let defaultWebAssetDir = "public" // DO NOT CHANGE! (See https://dub.sh/Buvz4yj)
|
|
19
15
|
private let defaultServerPathKey = "serverBasePath" // DO NOT CHANGE! (See https://dub.sh/ceDl0zT)
|
|
20
|
-
private let eventEmitter: LiveUpdateEventEmitter
|
|
21
16
|
private let httpClient: LiveUpdateHttpClient
|
|
22
17
|
private let libraryDirectoryUrl = FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask).first!
|
|
23
18
|
private let manifestFileName = "capawesome-live-update-manifest.json" // DO NOT CHANGE!
|
|
19
|
+
private let plugin: LiveUpdatePlugin
|
|
24
20
|
private let preferences: LiveUpdatePreferences
|
|
25
21
|
|
|
26
22
|
private var rollbackDispatchWorkItem: DispatchWorkItem?
|
|
@@ -28,11 +24,10 @@ import CommonCrypto
|
|
|
28
24
|
private var lastAutoUpdateCheckTimestamp: Int64 = 0
|
|
29
25
|
private var syncInProgress = false
|
|
30
26
|
|
|
31
|
-
init(
|
|
32
|
-
self.bridge = bridge
|
|
27
|
+
init(config: LiveUpdateConfig, plugin: LiveUpdatePlugin) {
|
|
33
28
|
self.config = config
|
|
34
|
-
self.eventEmitter = eventEmitter
|
|
35
29
|
self.httpClient = LiveUpdateHttpClient(config: config)
|
|
30
|
+
self.plugin = plugin
|
|
36
31
|
self.preferences = LiveUpdatePreferences()
|
|
37
32
|
super.init()
|
|
38
33
|
|
|
@@ -179,9 +174,9 @@ import CommonCrypto
|
|
|
179
174
|
}
|
|
180
175
|
|
|
181
176
|
@objc public func ready(completion: @escaping (Result?, Error?) -> Void) {
|
|
182
|
-
CAPLog.print("[",
|
|
177
|
+
CAPLog.print("[", LiveUpdatePlugin.tag, "] ", "App is ready.")
|
|
183
178
|
if config.readyTimeout <= 0 {
|
|
184
|
-
CAPLog.print("[",
|
|
179
|
+
CAPLog.print("[", LiveUpdatePlugin.tag, "] ", "Ready timeout is set to 0. Automatic rollback is disabled.")
|
|
185
180
|
}
|
|
186
181
|
// Stop the rollback timer
|
|
187
182
|
stopRollbackTimer()
|
|
@@ -269,7 +264,7 @@ import CommonCrypto
|
|
|
269
264
|
// Fetch the latest bundle
|
|
270
265
|
let fetchLatestBundleOptions = FetchLatestBundleOptions(channel: channel)
|
|
271
266
|
guard let response = try await fetchLatestBundle(fetchLatestBundleOptions) else {
|
|
272
|
-
CAPLog.print("[",
|
|
267
|
+
CAPLog.print("[", LiveUpdatePlugin.tag, "] ", "No update available.")
|
|
273
268
|
return SyncResult(nextBundleId: nil)
|
|
274
269
|
}
|
|
275
270
|
let artifactType = response.artifactType
|
|
@@ -279,7 +274,7 @@ import CommonCrypto
|
|
|
279
274
|
let downloadUrl = response.url
|
|
280
275
|
// Check if the bundle is blocked
|
|
281
276
|
if isBlockedBundleId(latestBundleId) {
|
|
282
|
-
CAPLog.print("[",
|
|
277
|
+
CAPLog.print("[", LiveUpdatePlugin.tag, "] ", "Bundle is blocked and will not be downloaded.")
|
|
283
278
|
return SyncResult(nextBundleId: nil)
|
|
284
279
|
}
|
|
285
280
|
// Check if bundle already exists
|
|
@@ -377,7 +372,7 @@ import CommonCrypto
|
|
|
377
372
|
for fileToCopy in filesToCopy {
|
|
378
373
|
let success = tryCopyCurrentBundleFile(fileToCopy: fileToCopy, toDirectory: toDirectory)
|
|
379
374
|
if !success {
|
|
380
|
-
CAPLog.print("[",
|
|
375
|
+
CAPLog.print("[", LiveUpdatePlugin.tag, "] ", "Failed to copy file: \(fileToCopy.href)")
|
|
381
376
|
// If the file could not be copied, add it to the list of missing items
|
|
382
377
|
missingItems.append(fileToCopy)
|
|
383
378
|
}
|
|
@@ -392,7 +387,7 @@ import CommonCrypto
|
|
|
392
387
|
do {
|
|
393
388
|
try FileManager.default.createDirectory(at: bundlesDirectoryUrl, withIntermediateDirectories: true, attributes: nil)
|
|
394
389
|
} catch {
|
|
395
|
-
CAPLog.print("[",
|
|
390
|
+
CAPLog.print("[", LiveUpdatePlugin.tag, "] ", "Failed to create bundles directory.")
|
|
396
391
|
}
|
|
397
392
|
}
|
|
398
393
|
}
|
|
@@ -424,7 +419,7 @@ import CommonCrypto
|
|
|
424
419
|
do {
|
|
425
420
|
try deleteBundleById(bundleId)
|
|
426
421
|
} catch {
|
|
427
|
-
CAPLog.print("[",
|
|
422
|
+
CAPLog.print("[", LiveUpdatePlugin.tag, "] ", "Failed to delete bundle with id: \(bundleId)")
|
|
428
423
|
}
|
|
429
424
|
}
|
|
430
425
|
}
|
|
@@ -437,7 +432,7 @@ import CommonCrypto
|
|
|
437
432
|
let urlComponents = URLComponents(string: url)!
|
|
438
433
|
let result = try await httpClient.download(url: urlComponents.asURL(), destination: destination, callback: callback)
|
|
439
434
|
if let error = result.error {
|
|
440
|
-
CAPLog.print("[",
|
|
435
|
+
CAPLog.print("[", LiveUpdatePlugin.tag, "] ", "Failed to download file: \(error)")
|
|
441
436
|
if let urlError = error.underlyingError as? URLError {
|
|
442
437
|
if urlError.code == .timedOut {
|
|
443
438
|
throw urlError
|
|
@@ -559,14 +554,14 @@ import CommonCrypto
|
|
|
559
554
|
parameters["deviceId"] = getDeviceId()
|
|
560
555
|
parameters["osVersion"] = await UIDevice.current.systemVersion
|
|
561
556
|
parameters["platform"] = "1"
|
|
562
|
-
parameters["pluginVersion"] =
|
|
557
|
+
parameters["pluginVersion"] = LiveUpdatePlugin.version
|
|
563
558
|
var urlComponents = URLComponents(string: "https://\(config.serverDomain)/v1/apps/\(getAppId() ?? "")/bundles/latest")!
|
|
564
559
|
urlComponents.queryItems = parameters.map { URLQueryItem(name: $0.key, value: $0.value) }
|
|
565
560
|
let url = try urlComponents.asURL()
|
|
566
|
-
CAPLog.print("[",
|
|
561
|
+
CAPLog.print("[", LiveUpdatePlugin.tag, "] Fetching latest bundle: ", url)
|
|
567
562
|
let response = try await self.httpClient.request(url: url, type: GetLatestBundleResponse.self)
|
|
568
563
|
if let data = response.data {
|
|
569
|
-
CAPLog.print("[",
|
|
564
|
+
CAPLog.print("[", LiveUpdatePlugin.tag, "] Latest bundle response: ", String(decoding: data, as: UTF8.self))
|
|
570
565
|
}
|
|
571
566
|
if let error = response.error {
|
|
572
567
|
if let urlError = error.underlyingError as? URLError {
|
|
@@ -643,7 +638,7 @@ import CommonCrypto
|
|
|
643
638
|
|
|
644
639
|
/// - Returns: The path to the current bundle directory or `nil` if no view controller was found.
|
|
645
640
|
private func getCurrentCapacitorServerPath() -> String? {
|
|
646
|
-
guard let viewController = self.bridge
|
|
641
|
+
guard let viewController = self.plugin.bridge?.viewController as? CAPBridgeViewController else {
|
|
647
642
|
return nil
|
|
648
643
|
}
|
|
649
644
|
return viewController.getServerBasePath()
|
|
@@ -725,14 +720,14 @@ import CommonCrypto
|
|
|
725
720
|
}
|
|
726
721
|
|
|
727
722
|
private func notifyDownloadBundleProgressListeners(_ event: DownloadBundleProgressEvent) {
|
|
728
|
-
|
|
723
|
+
plugin.notifyDownloadBundleProgressListeners(event)
|
|
729
724
|
}
|
|
730
725
|
|
|
731
726
|
private func performAutoUpdate() {
|
|
732
727
|
// Check if enough time has passed since the last check
|
|
733
728
|
let now = Int64(Date().timeIntervalSince1970 * 1000)
|
|
734
729
|
if lastAutoUpdateCheckTimestamp > 0 && (now - lastAutoUpdateCheckTimestamp) < autoUpdateIntervalMs {
|
|
735
|
-
CAPLog.print("[",
|
|
730
|
+
CAPLog.print("[", LiveUpdatePlugin.tag, "] ", "Auto-update skipped. Last check was less than 15 minutes ago.")
|
|
736
731
|
return
|
|
737
732
|
}
|
|
738
733
|
|
|
@@ -742,12 +737,12 @@ import CommonCrypto
|
|
|
742
737
|
// Run sync in background task
|
|
743
738
|
Task {
|
|
744
739
|
do {
|
|
745
|
-
CAPLog.print("[",
|
|
740
|
+
CAPLog.print("[", LiveUpdatePlugin.tag, "] ", "Auto-update started.")
|
|
746
741
|
let options = SyncOptions(channel: nil)
|
|
747
742
|
_ = try await sync(options)
|
|
748
|
-
CAPLog.print("[",
|
|
743
|
+
CAPLog.print("[", LiveUpdatePlugin.tag, "] ", "Auto-update completed successfully.")
|
|
749
744
|
} catch {
|
|
750
|
-
CAPLog.print("[",
|
|
745
|
+
CAPLog.print("[", LiveUpdatePlugin.tag, "] ", "Auto-update failed: ", error.localizedDescription)
|
|
751
746
|
}
|
|
752
747
|
}
|
|
753
748
|
}
|
|
@@ -760,11 +755,11 @@ import CommonCrypto
|
|
|
760
755
|
setPreviousBundleId(bundleId: currentBundleId)
|
|
761
756
|
// Perform the rollback
|
|
762
757
|
if let _ = currentBundleId {
|
|
763
|
-
CAPLog.print("[",
|
|
758
|
+
CAPLog.print("[", LiveUpdatePlugin.tag, "] ", "App is not ready. Rolling back to default bundle.")
|
|
764
759
|
setNextBundleById(nil)
|
|
765
760
|
setCurrentBundleById(nil)
|
|
766
761
|
} else {
|
|
767
|
-
CAPLog.print("[",
|
|
762
|
+
CAPLog.print("[", LiveUpdatePlugin.tag, "] ", "App is not ready. Default bundle is already in use.")
|
|
768
763
|
}
|
|
769
764
|
}
|
|
770
765
|
|
|
@@ -790,7 +785,7 @@ import CommonCrypto
|
|
|
790
785
|
}
|
|
791
786
|
}
|
|
792
787
|
} catch {
|
|
793
|
-
CAPLog.print("[",
|
|
788
|
+
CAPLog.print("[", LiveUpdatePlugin.tag, "] ", "Failed to search index.html file: \(error.localizedDescription)")
|
|
794
789
|
}
|
|
795
790
|
return nil
|
|
796
791
|
}
|
|
@@ -802,7 +797,7 @@ import CommonCrypto
|
|
|
802
797
|
}
|
|
803
798
|
|
|
804
799
|
private func setCurrentCapacitorServerPath(path: String) {
|
|
805
|
-
guard let viewController = self.bridge
|
|
800
|
+
guard let viewController = self.plugin.bridge?.viewController as? CAPBridgeViewController else {
|
|
806
801
|
return
|
|
807
802
|
}
|
|
808
803
|
viewController.setServerBasePath(path: path)
|
|
@@ -821,11 +816,11 @@ import CommonCrypto
|
|
|
821
816
|
|
|
822
817
|
private func notifyNextBundleSetListeners(_ bundleId: String?) {
|
|
823
818
|
let event = NextBundleSetEvent(bundleId: bundleId)
|
|
824
|
-
|
|
819
|
+
plugin.notifyNextBundleSetListeners(event)
|
|
825
820
|
}
|
|
826
821
|
|
|
827
822
|
private func notifyReloadedListeners() {
|
|
828
|
-
|
|
823
|
+
plugin.notifyReloadedListeners()
|
|
829
824
|
}
|
|
830
825
|
|
|
831
826
|
private func addBlockedBundleId(_ bundleId: String) {
|
|
@@ -853,7 +848,7 @@ import CommonCrypto
|
|
|
853
848
|
let newBlockedIds = blockedList.joined(separator: ",")
|
|
854
849
|
preferences.setBlockedBundleIds(newBlockedIds)
|
|
855
850
|
|
|
856
|
-
CAPLog.print("[",
|
|
851
|
+
CAPLog.print("[", LiveUpdatePlugin.tag, "] ", "Bundle blocked: ", bundleId)
|
|
857
852
|
}
|
|
858
853
|
|
|
859
854
|
private func isBlockedBundleId(_ bundleId: String) -> Bool {
|
|
@@ -883,7 +878,7 @@ import CommonCrypto
|
|
|
883
878
|
|
|
884
879
|
if lastVersionCode == nil || lastVersionName == nil || lastVersionCode != currentVersionCode || lastVersionName != currentVersionName {
|
|
885
880
|
CAPLog.print(
|
|
886
|
-
"[",
|
|
881
|
+
"[", LiveUpdatePlugin.tag, "] ",
|
|
887
882
|
"App version changed (last: \(lastVersionName ?? "nil")/\(lastVersionCode ?? "nil"), current: \(currentVersionName)/\(currentVersionCode)), resetting config."
|
|
888
883
|
)
|
|
889
884
|
resetConfig()
|
|
@@ -969,7 +964,7 @@ import CommonCrypto
|
|
|
969
964
|
.replacingOccurrences(of: "-----END PUBLIC KEY-----", with: "")
|
|
970
965
|
.replacingOccurrences(of: "\n", with: "")
|
|
971
966
|
guard let publicKeyData = Data(base64Encoded: publicKeyAsBase64) else {
|
|
972
|
-
CAPLog.print("[",
|
|
967
|
+
CAPLog.print("[", LiveUpdatePlugin.tag, "] ", "Failed to decode public key.")
|
|
973
968
|
return false
|
|
974
969
|
}
|
|
975
970
|
let publicKeyAttributes: [CFString: Any] = [
|
|
@@ -981,12 +976,12 @@ import CommonCrypto
|
|
|
981
976
|
var secKeyCreateWithDataError: Unmanaged<CFError>?
|
|
982
977
|
guard let publicKey = SecKeyCreateWithData(publicKeyData as CFData, publicKeyAttributes as CFDictionary, &secKeyCreateWithDataError) else {
|
|
983
978
|
if let error = secKeyCreateWithDataError?.takeRetainedValue() {
|
|
984
|
-
CAPLog.print("[",
|
|
979
|
+
CAPLog.print("[", LiveUpdatePlugin.tag, "] ", "Failed to create public key with error: \(error)")
|
|
985
980
|
}
|
|
986
981
|
return false
|
|
987
982
|
}
|
|
988
983
|
guard let signatureData = Data(base64Encoded: signature) else {
|
|
989
|
-
CAPLog.print("[",
|
|
984
|
+
CAPLog.print("[", LiveUpdatePlugin.tag, "] ", "Failed to decode signature.")
|
|
990
985
|
return false
|
|
991
986
|
}
|
|
992
987
|
|
|
@@ -1016,7 +1011,7 @@ import CommonCrypto
|
|
|
1016
1011
|
let signatureAlgorithm = SecKeyAlgorithm.rsaSignatureDigestPKCS1v15SHA256
|
|
1017
1012
|
let verificationResult = SecKeyVerifySignature(publicKey, signatureAlgorithm, digest as CFData, signatureData as CFData, &secKeyVerifySignatureError)
|
|
1018
1013
|
if let error = secKeyVerifySignatureError?.takeRetainedValue() {
|
|
1019
|
-
CAPLog.print("[",
|
|
1014
|
+
CAPLog.print("[", LiveUpdatePlugin.tag, "] ", "Failed to verify signature with error: \(error)")
|
|
1020
1015
|
}
|
|
1021
1016
|
return verificationResult
|
|
1022
1017
|
}
|
|
@@ -7,8 +7,9 @@ import Capacitor
|
|
|
7
7
|
*/
|
|
8
8
|
@objc(LiveUpdatePlugin)
|
|
9
9
|
public class LiveUpdatePlugin: CAPPlugin, CAPBridgedPlugin {
|
|
10
|
-
public static let tag = LiveUpdate
|
|
11
|
-
public static let version =
|
|
10
|
+
public static let tag = "LiveUpdate"
|
|
11
|
+
public static let version = "8.1.0"
|
|
12
|
+
public static let userDefaultsPrefix = "CapawesomeLiveUpdate" // DO NOT CHANGE
|
|
12
13
|
|
|
13
14
|
public let identifier = "LiveUpdatePlugin"
|
|
14
15
|
public let jsName = "LiveUpdate"
|
|
@@ -48,13 +49,8 @@ public class LiveUpdatePlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
48
49
|
private var implementation: LiveUpdate?
|
|
49
50
|
|
|
50
51
|
override public func load() {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
let config = liveUpdateConfig()
|
|
55
|
-
self.config = config
|
|
56
|
-
let eventEmitter = PluginLiveUpdateEventEmitter(plugin: self)
|
|
57
|
-
self.implementation = LiveUpdate(bridge: bridge, config: config, eventEmitter: eventEmitter)
|
|
52
|
+
self.config = liveUpdateConfig()
|
|
53
|
+
self.implementation = LiveUpdate(config: config!, plugin: self)
|
|
58
54
|
|
|
59
55
|
// Notify implementation about load
|
|
60
56
|
implementation?.handleLoad()
|
|
@@ -426,24 +422,4 @@ public class LiveUpdatePlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
426
422
|
private func resolveCall(_ call: CAPPluginCall, _ result: JSObject) {
|
|
427
423
|
call.resolve(result)
|
|
428
424
|
}
|
|
429
|
-
|
|
430
|
-
private class PluginLiveUpdateEventEmitter: NSObject, LiveUpdateEventEmitter {
|
|
431
|
-
weak var plugin: LiveUpdatePlugin?
|
|
432
|
-
|
|
433
|
-
init(plugin: LiveUpdatePlugin) {
|
|
434
|
-
self.plugin = plugin
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
func onDownloadBundleProgress(_ event: DownloadBundleProgressEvent) {
|
|
438
|
-
plugin?.notifyDownloadBundleProgressListeners(event)
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
func onNextBundleSet(_ event: NextBundleSetEvent) {
|
|
442
|
-
plugin?.notifyNextBundleSetListeners(event)
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
func onReloaded() {
|
|
446
|
-
plugin?.notifyReloadedListeners()
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
425
|
}
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import Foundation
|
|
2
2
|
|
|
3
3
|
public class LiveUpdatePreferences: NSObject {
|
|
4
|
-
private static let userDefaultsPrefix = "CapawesomeLiveUpdate" // DO NOT CHANGE
|
|
5
|
-
|
|
6
4
|
private let appIdKey = "appId" // DO NOT CHANGE
|
|
7
5
|
private let blockedBundleIdsKey = "blockedBundleIds" // DO NOT CHANGE
|
|
8
6
|
private let channelKey = "channel" // DO NOT CHANGE
|
|
@@ -99,6 +97,6 @@ public class LiveUpdatePreferences: NSObject {
|
|
|
99
97
|
}
|
|
100
98
|
|
|
101
99
|
private func applyPrefix(to key: String) -> String {
|
|
102
|
-
return
|
|
100
|
+
return LiveUpdatePlugin.userDefaultsPrefix + "." + key
|
|
103
101
|
}
|
|
104
102
|
}
|
package/package.json
CHANGED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
package io.capawesome.capacitorjs.plugins.liveupdate.classes;
|
|
2
|
-
|
|
3
|
-
import androidx.annotation.NonNull;
|
|
4
|
-
import androidx.annotation.Nullable;
|
|
5
|
-
|
|
6
|
-
public class CustomException extends Exception {
|
|
7
|
-
|
|
8
|
-
@Nullable
|
|
9
|
-
private final String code;
|
|
10
|
-
|
|
11
|
-
public CustomException(@Nullable String code, @NonNull String message) {
|
|
12
|
-
super(message);
|
|
13
|
-
this.code = code;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
@Nullable
|
|
17
|
-
public String getCode() {
|
|
18
|
-
return code;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
package io.capawesome.capacitorjs.plugins.liveupdate.classes;
|
|
2
|
-
|
|
3
|
-
public class CustomExceptions {
|
|
4
|
-
|
|
5
|
-
public static final CustomException APP_ID_MISSING = new CustomException("APP_ID_MISSING", "appId must be configured.");
|
|
6
|
-
public static final CustomException BUNDLE_EXISTS = new CustomException("BUNDLE_EXISTS", "bundle already exists.");
|
|
7
|
-
public static final CustomException BUNDLE_ID_MISSING = new CustomException(null, "bundleId must be provided.");
|
|
8
|
-
public static final CustomException BUNDLE_INDEX_HTML_MISSING = new CustomException(
|
|
9
|
-
"BUNDLE_INDEX_HTML_MISSING",
|
|
10
|
-
"The bundle does not contain an index.html file."
|
|
11
|
-
);
|
|
12
|
-
public static final CustomException BUNDLE_NOT_FOUND = new CustomException("BUNDLE_NOT_FOUND", "bundle not found.");
|
|
13
|
-
public static final CustomException CHECKSUM_CALCULATION_FAILED = new CustomException(
|
|
14
|
-
"CHECKSUM_CALCULATION_FAILED",
|
|
15
|
-
"Failed to calculate checksum."
|
|
16
|
-
);
|
|
17
|
-
public static final CustomException CHECKSUM_MISMATCH = new CustomException("CHECKSUM_MISMATCH", "Checksum mismatch.");
|
|
18
|
-
public static final CustomException CUSTOM_ID_MISSING = new CustomException(null, "customId must be provided.");
|
|
19
|
-
public static final CustomException DOWNLOAD_FAILED = new CustomException("DOWNLOAD_FAILED", "Bundle could not be downloaded.");
|
|
20
|
-
public static final CustomException HTTP_TIMEOUT = new CustomException("HTTP_TIMEOUT", "Request timed out.");
|
|
21
|
-
public static final CustomException URL_MISSING = new CustomException(null, "url must be provided.");
|
|
22
|
-
public static final CustomException SIGNATURE_VERIFICATION_FAILED = new CustomException(
|
|
23
|
-
"SIGNATURE_VERIFICATION_FAILED",
|
|
24
|
-
"Signature verification failed."
|
|
25
|
-
);
|
|
26
|
-
public static final CustomException PUBLIC_KEY_INVALID = new CustomException("PUBLIC_KEY_INVALID", "Invalid public key.");
|
|
27
|
-
public static final CustomException SIGNATURE_MISSING = new CustomException(
|
|
28
|
-
"SIGNATURE_MISSING",
|
|
29
|
-
"Bundle does not contain a signature."
|
|
30
|
-
);
|
|
31
|
-
public static final CustomException SYNC_IN_PROGRESS = new CustomException("SYNC_IN_PROGRESS", "Sync is already in progress.");
|
|
32
|
-
public static final CustomException UNKNOWN_ERROR = new CustomException("UNKNOWN_ERROR", "An unknown error has occurred.");
|
|
33
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
package io.capawesome.capacitorjs.plugins.liveupdate.interfaces;
|
|
2
|
-
|
|
3
|
-
import androidx.annotation.NonNull;
|
|
4
|
-
import io.capawesome.capacitorjs.plugins.liveupdate.classes.events.DownloadBundleProgressEvent;
|
|
5
|
-
import io.capawesome.capacitorjs.plugins.liveupdate.classes.events.NextBundleSetEvent;
|
|
6
|
-
|
|
7
|
-
public interface LiveUpdateEventEmitter {
|
|
8
|
-
void onDownloadBundleProgress(@NonNull DownloadBundleProgressEvent event);
|
|
9
|
-
|
|
10
|
-
void onNextBundleSet(@NonNull NextBundleSetEvent event);
|
|
11
|
-
|
|
12
|
-
void onReloaded();
|
|
13
|
-
}
|