@capawesome/capacitor-live-update 7.2.1 → 7.3.0
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 +78 -26
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdate.java +165 -35
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdateConfig.java +23 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdatePlugin.java +52 -13
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdatePreferences.java +17 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/events/NextBundleSetEvent.java +26 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/options/SyncOptions.java +4 -0
- package/dist/docs.json +109 -1
- package/dist/esm/definitions.d.ts +65 -2
- package/dist/esm/definitions.js.map +1 -1
- package/ios/Plugin/Classes/Events/NextBundleSetEvent.swift +16 -0
- package/ios/Plugin/Classes/Options/SyncOptions.swift +4 -0
- package/ios/Plugin/LiveUpdate.swift +101 -0
- package/ios/Plugin/LiveUpdateConfig.swift +2 -0
- package/ios/Plugin/LiveUpdateHttpClient.swift +5 -3
- package/ios/Plugin/LiveUpdatePlugin.swift +29 -11
- package/ios/Plugin/LiveUpdatePreferences.swift +14 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ Capacitor plugin that allows you to update your app remotely in real-time withou
|
|
|
4
4
|
|
|
5
5
|
<div class="capawesome-z29o10a">
|
|
6
6
|
<a href="https://cloud.capawesome.io/" target="_blank">
|
|
7
|
-
<img alt="Deliver Live Updates to your Capacitor app with Capawesome Cloud" src="https://cloud.capawesome.io/assets/banners/cloud-
|
|
7
|
+
<img alt="Deliver Live Updates to your Capacitor app with Capawesome Cloud" src="https://cloud.capawesome.io/assets/banners/cloud-build-and-deploy-capacitor-apps.png?t=1" />
|
|
8
8
|
</a>
|
|
9
9
|
</div>
|
|
10
10
|
|
|
@@ -17,16 +17,34 @@ We are proud to offer one of the most complete and feature-rich Capacitor plugin
|
|
|
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.
|
|
20
|
-
- 🔄 **Auto Update**: Automatically download and set the latest bundle for the app.
|
|
20
|
+
- 🔄 **Auto Update**: Automatically download and set the latest bundle for the app with configurable background update strategy.
|
|
21
21
|
- 🛟 **Rollback**: Reset the app to the default bundle if an incompatible bundle has been set.
|
|
22
22
|
- 🚀 **Rollout**: Gradually roll out new bundles to gather valuable feedback.
|
|
23
23
|
- 🔁 **Delta Updates**: Make your updates faster by only downloading changed files.
|
|
24
24
|
- 🔒 **Security**: Verify the authenticity and integrity of the bundle using a public key.
|
|
25
|
-
- ⚔️ **Battle-Tested**: Used in more than
|
|
25
|
+
- ⚔️ **Battle-Tested**: Used in more than 1,000 projects to update apps on more than 20,000,000 devices.
|
|
26
26
|
- 🌐 **Open Source**: Licensed under the MIT License.
|
|
27
27
|
|
|
28
28
|
Missing a feature? Just [open an issue](https://github.com/capawesome-team/capacitor-plugins/issues) and we'll take a look!
|
|
29
29
|
|
|
30
|
+
## Compatibility
|
|
31
|
+
|
|
32
|
+
| Plugin Version | Capacitor Version | Status |
|
|
33
|
+
| -------------- | ----------------- | -------------- |
|
|
34
|
+
| 7.x.x | >=7.x.x | Active support |
|
|
35
|
+
| 6.x.x | 6.x.x | Maintenance |
|
|
36
|
+
| 5.x.x | 5.x.x | Deprecated |
|
|
37
|
+
|
|
38
|
+
## Guides
|
|
39
|
+
|
|
40
|
+
- [Announcing the Capacitor Live Update Plugin](https://capawesome.io/blog/announcing-the-capacitor-live-update-plugin/)
|
|
41
|
+
- [Exploring the Capacitor Live Update API](https://capawesome.io/blog/exploring-the-capacitor-live-update-api/)
|
|
42
|
+
- [How Live Updates for Capacitor work](https://capawesome.io/blog/how-live-updates-for-capacitor-work/)
|
|
43
|
+
- [How to gradually roll out Capacitor Live Updates](https://capawesome.io/blog/how-to-gradually-roll-out-capacitor-live-updates/#update-a-bundle)
|
|
44
|
+
- [How to restrict Capacitor Live Updates to Native Versions](https://capawesome.io/blog/how-to-restrict-capacitor-live-updates-to-native-versions/)
|
|
45
|
+
- [Live Updates for Nuxt Capacitor Apps with Capawesome Cloud](https://capawesome.io/blog/live-updates-for-nuxt-capacitor/)
|
|
46
|
+
- [Migrating from App Center to Capawesome Cloud](https://capawesome.io/blog/migrating-from-app-center-to-capawesome-cloud/)
|
|
47
|
+
|
|
30
48
|
## Installation
|
|
31
49
|
|
|
32
50
|
```bash
|
|
@@ -79,15 +97,17 @@ We recommend to declare [`CA92.1`](https://developer.apple.com/documentation/bun
|
|
|
79
97
|
<docgen-config>
|
|
80
98
|
<!--Update the source file JSDoc comments and rerun docgen to update the docs below-->
|
|
81
99
|
|
|
82
|
-
| Prop
|
|
83
|
-
|
|
|
84
|
-
| **`appId`**
|
|
85
|
-
| **`
|
|
86
|
-
| **`
|
|
87
|
-
| **`
|
|
88
|
-
| **`
|
|
89
|
-
| **`
|
|
90
|
-
| **`
|
|
100
|
+
| Prop | Type | Description | Default | Since |
|
|
101
|
+
| -------------------------------- | ----------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- | ----- |
|
|
102
|
+
| **`appId`** | <code>string</code> | The app ID is used to identify the app when using [Capawesome Cloud](https://capawesome.io/cloud/). This is **NOT** the same as the app identifier (e.g. `com.example.app`). This is a unique identifier generated by Capawesome Cloud (e.g. `6e351b4f-69a7-415e-a057-4567df7ffe94`). | | 5.0.0 |
|
|
103
|
+
| **`autoBlockRolledBackBundles`** | <code>boolean</code> | Whether or not to automatically block bundles that have been rolled back. When enabled, the plugin will automatically block bundles that caused a rollback (up to 100 bundles). When the limit is reached, the oldest blocked bundle is unblocked. Blocked bundles will be skipped in future sync operations. **Attention**: This option has no effect if `readyTimeout` is set to `0`. Only available on Android and iOS. | <code>false</code> | 7.3.0 |
|
|
104
|
+
| **`autoDeleteBundles`** | <code>boolean</code> | Whether or not to automatically delete unused bundles. When enabled, the plugin will automatically delete unused bundles after calling `ready()`. | <code>false</code> | 5.0.0 |
|
|
105
|
+
| **`autoUpdateStrategy`** | <code>'none' \| 'background'</code> | The auto-update strategy for live updates. - `none`: Live updates will not be applied automatically. - `background`: Live updates will be automatically downloaded and applied in the background at app startup and when the app resumes (if the last check was more than 15 minutes ago). Only available on Android and iOS. | <code>'none'</code> | 7.3.0 |
|
|
106
|
+
| **`defaultChannel`** | <code>string</code> | The default channel of the app. | | 6.3.0 |
|
|
107
|
+
| **`httpTimeout`** | <code>number</code> | The timeout in milliseconds for HTTP requests. | <code>60000</code> | 6.4.0 |
|
|
108
|
+
| **`publicKey`** | <code>string</code> | The public key to verify the integrity of the bundle. The public key must be a PEM-encoded RSA public key. | | 6.1.0 |
|
|
109
|
+
| **`readyTimeout`** | <code>number</code> | The timeout in milliseconds to wait for the app to be ready before resetting to the default bundle. It is strongly **recommended** to configure this option (e.g. `10000` ms) so that the plugin can roll back to the default bundle in case of problems. If configured, the plugin will wait for the app to call the `ready()` method before resetting to the default bundle. Set to `0` to disable the timeout. | <code>0</code> | 5.0.0 |
|
|
110
|
+
| **`serverDomain`** | <code>string</code> | The API domain of the [Capawesome Cloud](https://cloud.capawesome.io) server **without** scheme or path. | <code>'api.cloud.capawesome.io'</code> | 7.0.0 |
|
|
91
111
|
|
|
92
112
|
### Examples
|
|
93
113
|
|
|
@@ -98,7 +118,9 @@ In `capacitor.config.json`:
|
|
|
98
118
|
"plugins": {
|
|
99
119
|
"LiveUpdate": {
|
|
100
120
|
"appId": '6e351b4f-69a7-415e-a057-4567df7ffe94',
|
|
121
|
+
"autoBlockRolledBackBundles": undefined,
|
|
101
122
|
"autoDeleteBundles": undefined,
|
|
123
|
+
"autoUpdateStrategy": 'background',
|
|
102
124
|
"defaultChannel": 'production',
|
|
103
125
|
"httpTimeout": undefined,
|
|
104
126
|
"publicKey": '-----BEGIN PUBLIC KEY-----MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDDodf1SD0OOn6hIlDuKBza0Ed0OqtwyVJwiyjmE9BJaZ7y8ZUfcF+SKmd0l2cDPM45XIg2tAFux5n29uoKyHwSt+6tCi5CJA5Z1/1eZruRRqABLonV77KS3HUtvOgqRLDnKSV89dYZkM++NwmzOPgIF422mvc+VukcVOBfc8/AHQIDAQAB-----END PUBLIC KEY-----',
|
|
@@ -120,7 +142,9 @@ const config: CapacitorConfig = {
|
|
|
120
142
|
plugins: {
|
|
121
143
|
LiveUpdate: {
|
|
122
144
|
appId: '6e351b4f-69a7-415e-a057-4567df7ffe94',
|
|
145
|
+
autoBlockRolledBackBundles: undefined,
|
|
123
146
|
autoDeleteBundles: undefined,
|
|
147
|
+
autoUpdateStrategy: 'background',
|
|
124
148
|
defaultChannel: 'production',
|
|
125
149
|
httpTimeout: undefined,
|
|
126
150
|
publicKey: '-----BEGIN PUBLIC KEY-----MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDDodf1SD0OOn6hIlDuKBza0Ed0OqtwyVJwiyjmE9BJaZ7y8ZUfcF+SKmd0l2cDPM45XIg2tAFux5n29uoKyHwSt+6tCi5CJA5Z1/1eZruRRqABLonV77KS3HUtvOgqRLDnKSV89dYZkM++NwmzOPgIF422mvc+VukcVOBfc8/AHQIDAQAB-----END PUBLIC KEY-----',
|
|
@@ -135,20 +159,6 @@ export default config;
|
|
|
135
159
|
|
|
136
160
|
</docgen-config>
|
|
137
161
|
|
|
138
|
-
## Starter templates
|
|
139
|
-
|
|
140
|
-
The following starter templates are available:
|
|
141
|
-
|
|
142
|
-
- [Ionstarter Angular Firebase](https://ionstarter.dev/)
|
|
143
|
-
- [Ionstarter Angular SQLite](https://ionstarter.dev/)
|
|
144
|
-
|
|
145
|
-
## Guides
|
|
146
|
-
|
|
147
|
-
- [Announcing the Capacitor Live Update Plugin](https://capawesome.io/blog/announcing-the-capacitor-live-update-plugin/)
|
|
148
|
-
- [How Live Updates for Capacitor work](https://capawesome.io/blog/how-live-updates-for-capacitor-work/)
|
|
149
|
-
- [How to restrict Capacitor Live Updates to Native Versions](https://capawesome.io/blog/how-to-restrict-capacitor-live-updates-to-native-versions/)
|
|
150
|
-
- [How to gradually roll out Capacitor Live Updates](https://capawesome.io/blog/how-to-gradually-roll-out-capacitor-live-updates/)
|
|
151
|
-
|
|
152
162
|
## Usage
|
|
153
163
|
|
|
154
164
|
```typescript
|
|
@@ -282,6 +292,7 @@ const isNewBundleAvailable = async () => {
|
|
|
282
292
|
* [`setNextBundle(...)`](#setnextbundle)
|
|
283
293
|
* [`sync(...)`](#sync)
|
|
284
294
|
* [`addListener('downloadBundleProgress', ...)`](#addlistenerdownloadbundleprogress-)
|
|
295
|
+
* [`addListener('nextBundleSet', ...)`](#addlistenernextbundleset-)
|
|
285
296
|
* [`removeAllListeners()`](#removealllisteners)
|
|
286
297
|
* [Interfaces](#interfaces)
|
|
287
298
|
* [Type Aliases](#type-aliases)
|
|
@@ -651,6 +662,31 @@ Only available on Android and iOS.
|
|
|
651
662
|
--------------------
|
|
652
663
|
|
|
653
664
|
|
|
665
|
+
### addListener('nextBundleSet', ...)
|
|
666
|
+
|
|
667
|
+
```typescript
|
|
668
|
+
addListener(eventName: 'nextBundleSet', listenerFunc: NextBundleSetListener) => Promise<PluginListenerHandle>
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
Listen for when a bundle is set as the next bundle.
|
|
672
|
+
|
|
673
|
+
This event is triggered whenever a bundle is set to be used on the next app restart,
|
|
674
|
+
either through automatic updates or manual calls to `setNextBundle()`.
|
|
675
|
+
|
|
676
|
+
Only available on Android and iOS.
|
|
677
|
+
|
|
678
|
+
| Param | Type |
|
|
679
|
+
| ------------------ | ----------------------------------------------------------------------- |
|
|
680
|
+
| **`eventName`** | <code>'nextBundleSet'</code> |
|
|
681
|
+
| **`listenerFunc`** | <code><a href="#nextbundlesetlistener">NextBundleSetListener</a></code> |
|
|
682
|
+
|
|
683
|
+
**Returns:** <code>Promise<<a href="#pluginlistenerhandle">PluginListenerHandle</a>></code>
|
|
684
|
+
|
|
685
|
+
**Since:** 7.3.0
|
|
686
|
+
|
|
687
|
+
--------------------
|
|
688
|
+
|
|
689
|
+
|
|
654
690
|
### removeAllListeners()
|
|
655
691
|
|
|
656
692
|
```typescript
|
|
@@ -823,6 +859,15 @@ Event that is triggered when the download progress of a bundle changes.
|
|
|
823
859
|
| **`totalBytes`** | <code>number</code> | The total number of bytes to download. | 7.0.0 |
|
|
824
860
|
|
|
825
861
|
|
|
862
|
+
#### NextBundleSetEvent
|
|
863
|
+
|
|
864
|
+
Event that is triggered when a bundle is set as the next bundle.
|
|
865
|
+
|
|
866
|
+
| Prop | Type | Description | Since |
|
|
867
|
+
| -------------- | --------------------------- | --------------------------------------------------------------------------------------------------------------- | ----- |
|
|
868
|
+
| **`bundleId`** | <code>string \| null</code> | The unique identifier of the bundle that is set as the next bundle. If `null`, the default bundle will be used. | 7.3.0 |
|
|
869
|
+
|
|
870
|
+
|
|
826
871
|
### Type Aliases
|
|
827
872
|
|
|
828
873
|
|
|
@@ -832,6 +877,13 @@ Listener for the download progress of a bundle.
|
|
|
832
877
|
|
|
833
878
|
<code>(event: <a href="#downloadbundleprogressevent">DownloadBundleProgressEvent</a>): void</code>
|
|
834
879
|
|
|
880
|
+
|
|
881
|
+
#### NextBundleSetListener
|
|
882
|
+
|
|
883
|
+
Listener for when a bundle is set as the next bundle.
|
|
884
|
+
|
|
885
|
+
<code>(event: <a href="#nextbundlesetevent">NextBundleSetEvent</a>): void</code>
|
|
886
|
+
|
|
835
887
|
</docgen-api>
|
|
836
888
|
|
|
837
889
|
## Testing
|
|
@@ -17,6 +17,7 @@ import io.capawesome.capacitorjs.plugins.liveupdate.classes.Manifest;
|
|
|
17
17
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.ManifestItem;
|
|
18
18
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.api.GetLatestBundleResponse;
|
|
19
19
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.events.DownloadBundleProgressEvent;
|
|
20
|
+
import io.capawesome.capacitorjs.plugins.liveupdate.classes.events.NextBundleSetEvent;
|
|
20
21
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.options.DeleteBundleOptions;
|
|
21
22
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.options.DownloadBundleOptions;
|
|
22
23
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.options.FetchLatestBundleOptions;
|
|
@@ -67,6 +68,8 @@ import org.json.JSONObject;
|
|
|
67
68
|
|
|
68
69
|
public class LiveUpdate {
|
|
69
70
|
|
|
71
|
+
private final long autoUpdateIntervalMs = 15 * 60 * 1000; // 15 minutes
|
|
72
|
+
|
|
70
73
|
@NonNull
|
|
71
74
|
private final LiveUpdateConfig config;
|
|
72
75
|
|
|
@@ -88,7 +91,10 @@ public class LiveUpdate {
|
|
|
88
91
|
private final Handler rollbackHandler = new Handler();
|
|
89
92
|
private final String manifestFileName = "capawesome-live-update-manifest.json"; // DO NOT CHANGE!
|
|
90
93
|
|
|
94
|
+
private boolean initialPageLoaded = false;
|
|
95
|
+
private long lastAutoUpdateCheckTimestamp = 0;
|
|
91
96
|
private boolean rollbackPerformed = false;
|
|
97
|
+
private boolean syncInProgress = false;
|
|
92
98
|
|
|
93
99
|
public LiveUpdate(@NonNull LiveUpdateConfig config, @NonNull LiveUpdatePlugin plugin) throws PackageManager.NameNotFoundException {
|
|
94
100
|
this.config = config;
|
|
@@ -205,6 +211,21 @@ public class LiveUpdate {
|
|
|
205
211
|
callback.success(result);
|
|
206
212
|
}
|
|
207
213
|
|
|
214
|
+
public void handleOnPageLoaded() {
|
|
215
|
+
// Wait for initial page load to perform auto update to make sure the WebViewLocalServer is initialized.
|
|
216
|
+
// Otherwise, a NullPointerException may occur for `com.getcapacitor.WebViewLocalServer.getBasePath()`.
|
|
217
|
+
if ("background".equals(config.getAutoUpdateStrategy()) && !initialPageLoaded) {
|
|
218
|
+
initialPageLoaded = true;
|
|
219
|
+
performAutoUpdate();
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
public void handleOnResume() {
|
|
224
|
+
if ("background".equals(config.getAutoUpdateStrategy()) && initialPageLoaded) {
|
|
225
|
+
performAutoUpdate();
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
208
229
|
public void ready(@NonNull NonEmptyCallback callback) {
|
|
209
230
|
Logger.debug(LiveUpdatePlugin.TAG, "App is ready.");
|
|
210
231
|
if (config.getReadyTimeout() <= 0) {
|
|
@@ -219,6 +240,10 @@ public class LiveUpdate {
|
|
|
219
240
|
// Get the current and previous bundle IDs
|
|
220
241
|
String currentBundleId = getCurrentBundleId();
|
|
221
242
|
String previousBundleId = getPreviousBundleId();
|
|
243
|
+
// Block the rolled back bundle if enabled
|
|
244
|
+
if (config.getAutoBlockRolledBackBundles() && rollbackPerformed && previousBundleId != null) {
|
|
245
|
+
addBlockedBundleId(previousBundleId);
|
|
246
|
+
}
|
|
222
247
|
// Return the result
|
|
223
248
|
ReadyResult result = new ReadyResult(currentBundleId, previousBundleId, rollbackPerformed);
|
|
224
249
|
callback.success(result);
|
|
@@ -270,44 +295,62 @@ public class LiveUpdate {
|
|
|
270
295
|
}
|
|
271
296
|
|
|
272
297
|
public void sync(@NonNull SyncOptions options, @NonNull NonEmptyCallback<Result> callback) throws Exception {
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
GetLatestBundleResponse response = fetchLatestBundle(fetchLatestBundleOptions);
|
|
277
|
-
if (response == null) {
|
|
278
|
-
Logger.debug(LiveUpdatePlugin.TAG, "No update available.");
|
|
279
|
-
SyncResult syncResult = new SyncResult(null);
|
|
280
|
-
callback.success(syncResult);
|
|
298
|
+
if (syncInProgress) {
|
|
299
|
+
Exception exception = new Exception(LiveUpdatePlugin.ERROR_SYNC_IN_PROGRESS);
|
|
300
|
+
callback.error(exception);
|
|
281
301
|
return;
|
|
282
302
|
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
nextBundleId = latestBundleId;
|
|
303
|
+
syncInProgress = true;
|
|
304
|
+
|
|
305
|
+
try {
|
|
306
|
+
String channel = options.getChannel();
|
|
307
|
+
// Fetch the latest bundle
|
|
308
|
+
FetchLatestBundleOptions fetchLatestBundleOptions = new FetchLatestBundleOptions(channel);
|
|
309
|
+
GetLatestBundleResponse response = fetchLatestBundle(fetchLatestBundleOptions);
|
|
310
|
+
if (response == null) {
|
|
311
|
+
Logger.debug(LiveUpdatePlugin.TAG, "No update available.");
|
|
312
|
+
SyncResult syncResult = new SyncResult(null);
|
|
313
|
+
callback.success(syncResult);
|
|
314
|
+
return;
|
|
296
315
|
}
|
|
297
|
-
|
|
316
|
+
ArtifactType artifactType = response.getArtifactType();
|
|
317
|
+
String latestBundleId = response.getBundleId();
|
|
318
|
+
String checksum = response.getChecksum();
|
|
319
|
+
String signature = response.getSignature();
|
|
320
|
+
String url = response.getUrl();
|
|
321
|
+
// Check if the bundle is blocked
|
|
322
|
+
if (isBlockedBundleId(latestBundleId)) {
|
|
323
|
+
Logger.warn(LiveUpdatePlugin.TAG, "Bundle is blocked and will not be downloaded.");
|
|
324
|
+
SyncResult syncResult = new SyncResult(null);
|
|
325
|
+
callback.success(syncResult);
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
// Check if the bundle already exists
|
|
329
|
+
if (hasBundleById(latestBundleId)) {
|
|
330
|
+
String nextBundleId = null;
|
|
331
|
+
String currentBundleId = getCurrentBundleId();
|
|
332
|
+
if (!latestBundleId.equals(currentBundleId)) {
|
|
333
|
+
// Set the next bundle
|
|
334
|
+
setNextBundleById(latestBundleId);
|
|
335
|
+
nextBundleId = latestBundleId;
|
|
336
|
+
}
|
|
337
|
+
SyncResult syncResult = new SyncResult(nextBundleId);
|
|
338
|
+
callback.success(syncResult);
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
// Download the bundle
|
|
342
|
+
if (artifactType == ArtifactType.MANIFEST) {
|
|
343
|
+
downloadBundleOfTypeManifest(latestBundleId, url);
|
|
344
|
+
} else {
|
|
345
|
+
downloadBundleOfTypeZip(latestBundleId, checksum, signature, url);
|
|
346
|
+
}
|
|
347
|
+
// Set the next bundle
|
|
348
|
+
setNextBundleById(latestBundleId);
|
|
349
|
+
SyncResult syncResult = new SyncResult(latestBundleId);
|
|
298
350
|
callback.success(syncResult);
|
|
299
|
-
|
|
351
|
+
} finally {
|
|
352
|
+
syncInProgress = false;
|
|
300
353
|
}
|
|
301
|
-
// Download the bundle
|
|
302
|
-
if (artifactType == ArtifactType.MANIFEST) {
|
|
303
|
-
downloadBundleOfTypeManifest(latestBundleId, url);
|
|
304
|
-
} else {
|
|
305
|
-
downloadBundleOfTypeZip(latestBundleId, checksum, signature, url);
|
|
306
|
-
}
|
|
307
|
-
// Set the next bundle
|
|
308
|
-
setNextBundleById(latestBundleId);
|
|
309
|
-
SyncResult syncResult = new SyncResult(latestBundleId);
|
|
310
|
-
callback.success(syncResult);
|
|
311
354
|
}
|
|
312
355
|
|
|
313
356
|
private void addBundle(@NonNull String bundleId, @NonNull File sourceDirectory) throws Exception {
|
|
@@ -766,10 +809,15 @@ public class LiveUpdate {
|
|
|
766
809
|
*/
|
|
767
810
|
@NonNull
|
|
768
811
|
private String getNextCapacitorServerPath() {
|
|
769
|
-
|
|
812
|
+
String path = plugin
|
|
770
813
|
.getContext()
|
|
771
814
|
.getSharedPreferences(WebView.WEBVIEW_PREFS_NAME, Activity.MODE_PRIVATE)
|
|
772
815
|
.getString(WebView.CAP_SERVER_PATH, defaultWebAssetDir);
|
|
816
|
+
// Empty path means default path
|
|
817
|
+
if (path.isEmpty()) {
|
|
818
|
+
path = defaultWebAssetDir;
|
|
819
|
+
}
|
|
820
|
+
return path;
|
|
773
821
|
}
|
|
774
822
|
|
|
775
823
|
/**
|
|
@@ -834,10 +882,44 @@ public class LiveUpdate {
|
|
|
834
882
|
return loadManifest(source);
|
|
835
883
|
}
|
|
836
884
|
|
|
837
|
-
|
|
885
|
+
private void notifyDownloadBundleProgressListeners(@NonNull final DownloadBundleProgressEvent event) {
|
|
838
886
|
plugin.notifyDownloadBundleProgressListeners(event);
|
|
839
887
|
}
|
|
840
888
|
|
|
889
|
+
private void performAutoUpdate() {
|
|
890
|
+
// Check if enough time has passed since the last check
|
|
891
|
+
long now = System.currentTimeMillis();
|
|
892
|
+
if (lastAutoUpdateCheckTimestamp > 0 && (now - lastAutoUpdateCheckTimestamp) < autoUpdateIntervalMs) {
|
|
893
|
+
Logger.debug(LiveUpdatePlugin.TAG, "Auto-update skipped. Last check was less than 15 minutes ago.");
|
|
894
|
+
return;
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
// Update the timestamp
|
|
898
|
+
lastAutoUpdateCheckTimestamp = now;
|
|
899
|
+
|
|
900
|
+
// Run sync in background thread
|
|
901
|
+
new Thread(() -> {
|
|
902
|
+
try {
|
|
903
|
+
Logger.debug(LiveUpdatePlugin.TAG, "Auto-update started.");
|
|
904
|
+
SyncOptions options = new SyncOptions((String) null);
|
|
905
|
+
NonEmptyCallback<Result> callback = new NonEmptyCallback<>() {
|
|
906
|
+
@Override
|
|
907
|
+
public void success(Result result) {
|
|
908
|
+
Logger.debug(LiveUpdatePlugin.TAG, "Auto-update completed successfully.");
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
@Override
|
|
912
|
+
public void error(Exception exception) {
|
|
913
|
+
Logger.error(LiveUpdatePlugin.TAG, "Auto-update failed: " + exception.getMessage(), exception);
|
|
914
|
+
}
|
|
915
|
+
};
|
|
916
|
+
sync(options, callback);
|
|
917
|
+
} catch (Exception exception) {
|
|
918
|
+
Logger.error(LiveUpdatePlugin.TAG, "Auto-update failed: " + exception.getMessage(), exception);
|
|
919
|
+
}
|
|
920
|
+
}).start();
|
|
921
|
+
}
|
|
922
|
+
|
|
841
923
|
private void rollback() {
|
|
842
924
|
// Set the rollback flag
|
|
843
925
|
rollbackPerformed = true;
|
|
@@ -911,6 +993,54 @@ public class LiveUpdate {
|
|
|
911
993
|
File bundleDirectory = buildBundleDirectoryFor(bundleId);
|
|
912
994
|
setNextCapacitorServerPath(bundleDirectory.getPath());
|
|
913
995
|
}
|
|
996
|
+
|
|
997
|
+
// Notify listeners
|
|
998
|
+
notifyNextBundleSetListeners(bundleId);
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
private void notifyNextBundleSetListeners(@Nullable String bundleId) {
|
|
1002
|
+
NextBundleSetEvent event = new NextBundleSetEvent(bundleId);
|
|
1003
|
+
plugin.notifyNextBundleSetListeners(event);
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
private void addBlockedBundleId(@NonNull String bundleId) {
|
|
1007
|
+
String blockedIds = preferences.getBlockedBundleIds();
|
|
1008
|
+
List<String> blockedList = new ArrayList<>();
|
|
1009
|
+
|
|
1010
|
+
// Parse existing blocked IDs
|
|
1011
|
+
if (blockedIds != null && !blockedIds.isEmpty()) {
|
|
1012
|
+
String[] ids = blockedIds.split(",");
|
|
1013
|
+
blockedList.addAll(Arrays.asList(ids));
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
// Skip if already blocked
|
|
1017
|
+
if (blockedList.contains(bundleId)) {
|
|
1018
|
+
return;
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
// Remove oldest if limit reached
|
|
1022
|
+
if (blockedList.size() >= 100) {
|
|
1023
|
+
blockedList.remove(0);
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
// Add new bundle
|
|
1027
|
+
blockedList.add(bundleId);
|
|
1028
|
+
|
|
1029
|
+
// Save back to preferences
|
|
1030
|
+
String newBlockedIds = String.join(",", blockedList);
|
|
1031
|
+
preferences.setBlockedBundleIds(newBlockedIds);
|
|
1032
|
+
|
|
1033
|
+
Logger.debug(LiveUpdatePlugin.TAG, "Bundle blocked: " + bundleId);
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
private boolean isBlockedBundleId(@NonNull String bundleId) {
|
|
1037
|
+
String blockedIds = preferences.getBlockedBundleIds();
|
|
1038
|
+
if (blockedIds == null || blockedIds.isEmpty()) {
|
|
1039
|
+
return false;
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
String[] ids = blockedIds.split(",");
|
|
1043
|
+
return Arrays.asList(ids).contains(bundleId);
|
|
914
1044
|
}
|
|
915
1045
|
|
|
916
1046
|
private void setNextCapacitorServerPath(@NonNull String path) {
|
package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdateConfig.java
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
package io.capawesome.capacitorjs.plugins.liveupdate;
|
|
2
2
|
|
|
3
|
+
import androidx.annotation.NonNull;
|
|
3
4
|
import androidx.annotation.Nullable;
|
|
4
5
|
import java.security.Key;
|
|
5
6
|
|
|
@@ -8,8 +9,13 @@ public class LiveUpdateConfig {
|
|
|
8
9
|
@Nullable
|
|
9
10
|
private String appId = null;
|
|
10
11
|
|
|
12
|
+
private boolean autoBlockRolledBackBundles = false;
|
|
13
|
+
|
|
11
14
|
private boolean autoDeleteBundles = false;
|
|
12
15
|
|
|
16
|
+
@NonNull
|
|
17
|
+
private String autoUpdateStrategy = "none";
|
|
18
|
+
|
|
13
19
|
@Nullable
|
|
14
20
|
private String defaultChannel = null;
|
|
15
21
|
|
|
@@ -26,10 +32,19 @@ public class LiveUpdateConfig {
|
|
|
26
32
|
return appId;
|
|
27
33
|
}
|
|
28
34
|
|
|
35
|
+
public boolean getAutoBlockRolledBackBundles() {
|
|
36
|
+
return autoBlockRolledBackBundles;
|
|
37
|
+
}
|
|
38
|
+
|
|
29
39
|
public boolean getAutoDeleteBundles() {
|
|
30
40
|
return autoDeleteBundles;
|
|
31
41
|
}
|
|
32
42
|
|
|
43
|
+
@NonNull
|
|
44
|
+
public String getAutoUpdateStrategy() {
|
|
45
|
+
return autoUpdateStrategy;
|
|
46
|
+
}
|
|
47
|
+
|
|
33
48
|
@Nullable
|
|
34
49
|
public String getDefaultChannel() {
|
|
35
50
|
return defaultChannel;
|
|
@@ -56,10 +71,18 @@ public class LiveUpdateConfig {
|
|
|
56
71
|
this.appId = appId;
|
|
57
72
|
}
|
|
58
73
|
|
|
74
|
+
public void setAutoBlockRolledBackBundles(boolean autoBlockRolledBackBundles) {
|
|
75
|
+
this.autoBlockRolledBackBundles = autoBlockRolledBackBundles;
|
|
76
|
+
}
|
|
77
|
+
|
|
59
78
|
public void setAutoDeleteBundles(boolean autoDeleteBundles) {
|
|
60
79
|
this.autoDeleteBundles = autoDeleteBundles;
|
|
61
80
|
}
|
|
62
81
|
|
|
82
|
+
public void setAutoUpdateStrategy(@NonNull String autoUpdateStrategy) {
|
|
83
|
+
this.autoUpdateStrategy = autoUpdateStrategy;
|
|
84
|
+
}
|
|
85
|
+
|
|
63
86
|
public void setDefaultChannel(@Nullable String defaultChannel) {
|
|
64
87
|
this.defaultChannel = defaultChannel;
|
|
65
88
|
}
|