@capawesome/capacitor-live-update 8.3.0 → 8.4.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 +184 -69
- package/android/build.gradle +1 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdate.java +6 -4
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdateHttpClient.java +3 -1
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdatePlugin.java +8 -9
- package/ios/Plugin/LiveUpdate.swift +2 -1
- package/ios/Plugin/LiveUpdateHttpClient.swift +6 -1
- package/package.json +14 -7
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Capacitor Live Update Plugin
|
|
2
2
|
|
|
3
3
|
Capacitor plugin that allows you to update your app remotely in real-time without requiring users to download a new version from the app store, known as Over-the-Air (OTA) updates.
|
|
4
4
|
|
|
@@ -10,7 +10,7 @@ Capacitor plugin that allows you to update your app remotely in real-time withou
|
|
|
10
10
|
|
|
11
11
|
## Features
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
The Capacitor Live Update plugin is one of the most feature-rich Over-the-Air (OTA) update solutions for Capacitor apps. Here are some of the key features:
|
|
14
14
|
|
|
15
15
|
- 🔋 Supports **Android and iOS**
|
|
16
16
|
- ⚡️ **Capacitor 6/7/8** support
|
|
@@ -28,20 +28,27 @@ We are proud to offer one of the most complete and feature-rich Capacitor plugin
|
|
|
28
28
|
- 🔒 **Security**: Verify the authenticity and integrity of the bundle using a public key.
|
|
29
29
|
- ⚔️ **Battle-Tested**: Used in more than 1,000 projects to update apps on more than 20,000,000 devices.
|
|
30
30
|
- 🌐 **Open Source**: Licensed under the MIT License.
|
|
31
|
+
- 🤝 **Compatibility**: Works alongside the [App Update](https://capawesome.io/docs/sdks/capacitor/app-update/) and [Firebase Remote Config](https://capawesome.io/docs/sdks/capacitor/firebase/remote-config/) plugins.
|
|
31
32
|
|
|
32
33
|
Missing a feature? Just [open an issue](https://github.com/capawesome-team/capacitor-plugins/issues) and we'll take a look!
|
|
33
34
|
|
|
34
|
-
##
|
|
35
|
+
## Use Cases
|
|
35
36
|
|
|
36
|
-
|
|
37
|
+
The Live Update plugin is typically used to ship changes to users without going through the app stores, for example:
|
|
38
|
+
|
|
39
|
+
- **Instant bug fixes**: Deliver critical fixes to your users in real-time without waiting for app store reviews.
|
|
40
|
+
- **Fast feature delivery**: Ship new web-based features as soon as they are ready by publishing a new bundle.
|
|
41
|
+
- **Staged rollouts**: Gradually roll out new bundles to a subset of devices to gather feedback before a full release.
|
|
42
|
+
- **Testing and QA channels**: Distribute preview versions to testers via dedicated channels before promoting them to production.
|
|
43
|
+
- **Safe releases**: Automatically roll back to the default bundle if a faulty update prevents the app from becoming ready.
|
|
37
44
|
|
|
38
45
|
## Compatibility
|
|
39
46
|
|
|
40
47
|
| Plugin Version | Capacitor Version | Status |
|
|
41
48
|
| -------------- | ----------------- | -------------- |
|
|
42
49
|
| 8.x.x | >=8.x.x | Active support |
|
|
43
|
-
| 7.x.x | 7.x.x |
|
|
44
|
-
| 6.x.x | 6.x.x |
|
|
50
|
+
| 7.x.x | 7.x.x | Maintenance |
|
|
51
|
+
| 6.x.x | 6.x.x | Maintenance |
|
|
45
52
|
| 5.x.x | 5.x.x | Deprecated |
|
|
46
53
|
|
|
47
54
|
## Guides
|
|
@@ -49,6 +56,7 @@ Stay up to date with the latest news and updates about the Capawesome, Capacitor
|
|
|
49
56
|
- [Getting Started with Capawesome Cloud Live Updates](https://capawesome.io/docs/cloud/live-updates/setup/)
|
|
50
57
|
- [Migrating from Ionic Appflow to Capawesome Cloud](https://capawesome.io/blog/migrating-from-ionic-appflow-to-capawesome-cloud/)
|
|
51
58
|
- [Migrating from App Center to Capawesome Cloud](https://capawesome.io/blog/migrating-from-app-center-to-capawesome-cloud/)
|
|
59
|
+
- [Live Updates for Nuxt Capacitor Apps](https://capawesome.io/blog/live-updates-for-nuxt-capacitor/)
|
|
52
60
|
|
|
53
61
|
## Installation
|
|
54
62
|
|
|
@@ -76,7 +84,7 @@ npx cap sync
|
|
|
76
84
|
|
|
77
85
|
#### Channel
|
|
78
86
|
|
|
79
|
-
If you are using [Versioned Channels](https://capawesome.io/docs/cloud/live-updates/
|
|
87
|
+
If you are using [Versioned Channels](https://capawesome.io/docs/cloud/live-updates/channels/#versioned-channels), you can set a default channel directly in your native project by adding a string resource.
|
|
80
88
|
This allows you to tie the channel to the version code at build time.
|
|
81
89
|
|
|
82
90
|
Add the following to your app's `build.gradle` file:
|
|
@@ -93,7 +101,7 @@ android {
|
|
|
93
101
|
|
|
94
102
|
If needed, you can define the following project variable in your app’s `variables.gradle` file to change the default version of the dependency:
|
|
95
103
|
|
|
96
|
-
- `$okhttp3Version` version of `com.squareup.okhttp3:okhttp` (default: `5.3.2`)
|
|
104
|
+
- `$okhttp3Version` version of `com.squareup.okhttp3:okhttp` and `com.squareup.okhttp3:okhttp-brotli` (default: `5.3.2`)
|
|
97
105
|
- `$zip4jVersion` version of `net.lingala.zip4j:zip4j` (default: `2.11.5`)
|
|
98
106
|
|
|
99
107
|
This can be useful if you encounter dependency conflicts with other plugins in your project.
|
|
@@ -102,7 +110,7 @@ This can be useful if you encounter dependency conflicts with other plugins in y
|
|
|
102
110
|
|
|
103
111
|
#### Channel
|
|
104
112
|
|
|
105
|
-
If you are using [Versioned Channels](https://capawesome.io/docs/cloud/live-updates/
|
|
113
|
+
If you are using [Versioned Channels](https://capawesome.io/docs/cloud/live-updates/channels/#versioned-channels), you can set a default channel directly in your native project by adding a key to your `Info.plist` file.
|
|
106
114
|
This allows you to tie the channel to the build version at build time.
|
|
107
115
|
|
|
108
116
|
Add the following to your `Info.plist` file:
|
|
@@ -216,56 +224,182 @@ A working example can be found here: [capawesome-team/capacitor-live-update-demo
|
|
|
216
224
|
|
|
217
225
|
## Usage
|
|
218
226
|
|
|
227
|
+
All methods are only available on Android and iOS. The following examples show how to sync with Capawesome Cloud, mark the app as ready, check for and download new bundles, switch between and manage bundles, work with channels, inspect the current and next bundle, identify devices, read the app version, and reset to the default bundle.
|
|
228
|
+
|
|
229
|
+
### Sync with Capawesome Cloud
|
|
230
|
+
|
|
231
|
+
Automatically download and set the latest bundle for the app using [Capawesome Cloud](https://cloud.capawesome.io/). Call `reload()` or restart the app to apply the changes. The `channel` option overrides the channel for this single sync call without persisting it:
|
|
232
|
+
|
|
219
233
|
```typescript
|
|
220
234
|
import { LiveUpdate } from '@capawesome/capacitor-live-update';
|
|
221
235
|
|
|
222
|
-
const
|
|
223
|
-
await LiveUpdate.
|
|
236
|
+
const sync = async () => {
|
|
237
|
+
const result = await LiveUpdate.sync({
|
|
238
|
+
channel: 'production-5',
|
|
239
|
+
});
|
|
240
|
+
return result.nextBundleId;
|
|
241
|
+
};
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### Notify the plugin that the app is ready
|
|
245
|
+
|
|
246
|
+
Call `ready()` as soon as the app is ready to use so that the plugin knows no rollback is needed. If the `readyTimeout` configuration option is set and this method is not called in time, the plugin resets the app to the default bundle:
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
import { LiveUpdate } from '@capawesome/capacitor-live-update';
|
|
250
|
+
|
|
251
|
+
const ready = async () => {
|
|
252
|
+
const result = await LiveUpdate.ready();
|
|
253
|
+
if (result.currentBundleId) {
|
|
254
|
+
console.log(`The app is now using the bundle with the identifier ${result.currentBundleId}.`);
|
|
255
|
+
}
|
|
256
|
+
if (result.previousBundleId) {
|
|
257
|
+
console.log(`The app was using the bundle with the identifier ${result.previousBundleId}.`);
|
|
258
|
+
}
|
|
259
|
+
if (result.rollback) {
|
|
260
|
+
console.log('The app was reset to the default bundle.');
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### Check if a new bundle is available
|
|
266
|
+
|
|
267
|
+
Fetch the latest bundle from Capawesome Cloud and compare it with the bundle that is currently in use:
|
|
268
|
+
|
|
269
|
+
```typescript
|
|
270
|
+
import { LiveUpdate } from '@capawesome/capacitor-live-update';
|
|
271
|
+
|
|
272
|
+
const fetchLatestBundle = async () => {
|
|
273
|
+
await LiveUpdate.fetchLatestBundle();
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
const isNewBundleAvailable = async () => {
|
|
277
|
+
const { bundleId: latestBundleId } = await LiveUpdate.fetchLatestBundle({
|
|
278
|
+
channel: 'production-5',
|
|
279
|
+
});
|
|
280
|
+
if (latestBundleId) {
|
|
281
|
+
const { bundleId: currentBundleId } = await LiveUpdate.getCurrentBundle();
|
|
282
|
+
return latestBundleId !== currentBundleId;
|
|
283
|
+
} else {
|
|
284
|
+
return false;
|
|
285
|
+
}
|
|
224
286
|
};
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### Download a bundle from your own server
|
|
290
|
+
|
|
291
|
+
Download a self-hosted bundle from any URL, without any Capawesome Cloud dependency:
|
|
292
|
+
|
|
293
|
+
```typescript
|
|
294
|
+
import { LiveUpdate } from '@capawesome/capacitor-live-update';
|
|
225
295
|
|
|
226
296
|
const downloadBundle = async () => {
|
|
227
297
|
await LiveUpdate.downloadBundle({ url: 'https://example.com/1.0.0.zip', bundleId: '1.0.0' });
|
|
228
298
|
};
|
|
299
|
+
```
|
|
229
300
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
301
|
+
### Switch to a downloaded bundle
|
|
302
|
+
|
|
303
|
+
Set the next bundle to use for the app. Call `reload()` or restart the app to apply the changes:
|
|
304
|
+
|
|
305
|
+
```typescript
|
|
306
|
+
import { LiveUpdate } from '@capawesome/capacitor-live-update';
|
|
307
|
+
|
|
308
|
+
const setNextBundle = async () => {
|
|
309
|
+
await LiveUpdate.setNextBundle({ bundleId: '7f0b9bf2-dff6-4be2-bcac-b068cc5ea756' });
|
|
233
310
|
};
|
|
234
311
|
|
|
235
|
-
const
|
|
236
|
-
await LiveUpdate.
|
|
312
|
+
const reload = async () => {
|
|
313
|
+
await LiveUpdate.reload();
|
|
237
314
|
};
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
### Manage downloaded bundles
|
|
318
|
+
|
|
319
|
+
Get the identifiers of all bundles that have been downloaded and delete bundles that are no longer needed:
|
|
320
|
+
|
|
321
|
+
```typescript
|
|
322
|
+
import { LiveUpdate } from '@capawesome/capacitor-live-update';
|
|
238
323
|
|
|
239
324
|
const getBundles = async () => {
|
|
240
325
|
const result = await LiveUpdate.getBundles();
|
|
241
326
|
return result.bundleIds;
|
|
242
327
|
};
|
|
243
328
|
|
|
329
|
+
const deleteBundle = async () => {
|
|
330
|
+
await LiveUpdate.deleteBundle({ bundleId: 'my-bundle' });
|
|
331
|
+
};
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
### Work with channels
|
|
335
|
+
|
|
336
|
+
Set the channel to use for updates, read the currently resolved channel, and fetch the available channels from Capawesome Cloud. The channel set via `setChannel(...)` is persisted across app restarts. Note that `fetchChannels(...)` only works for apps with public channels enabled:
|
|
337
|
+
|
|
338
|
+
```typescript
|
|
339
|
+
import { LiveUpdate } from '@capawesome/capacitor-live-update';
|
|
340
|
+
|
|
341
|
+
const setChannel = async () => {
|
|
342
|
+
await LiveUpdate.setChannel({ channel: 'production-5' });
|
|
343
|
+
};
|
|
344
|
+
|
|
244
345
|
const getChannel = async () => {
|
|
245
346
|
const result = await LiveUpdate.getChannel();
|
|
246
347
|
return result.channel;
|
|
247
348
|
};
|
|
248
349
|
|
|
350
|
+
const fetchChannels = async () => {
|
|
351
|
+
const result = await LiveUpdate.fetchChannels();
|
|
352
|
+
return result.channels;
|
|
353
|
+
};
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
### Inspect the current and next bundle
|
|
357
|
+
|
|
358
|
+
Get the identifier of the bundle that is currently used by the app and of the bundle that will be used after the next reload or app restart:
|
|
359
|
+
|
|
360
|
+
```typescript
|
|
361
|
+
import { LiveUpdate } from '@capawesome/capacitor-live-update';
|
|
362
|
+
|
|
249
363
|
const getCurrentBundle = async () => {
|
|
250
364
|
const result = await LiveUpdate.getCurrentBundle();
|
|
251
365
|
return result.bundleId;
|
|
252
366
|
};
|
|
253
367
|
|
|
254
|
-
const
|
|
255
|
-
const result = await LiveUpdate.
|
|
256
|
-
return result.
|
|
368
|
+
const getNextBundle = async () => {
|
|
369
|
+
const result = await LiveUpdate.getNextBundle();
|
|
370
|
+
return result.bundleId;
|
|
257
371
|
};
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
### Identify devices
|
|
375
|
+
|
|
376
|
+
Read the unique device identifier and manage a custom identifier for the device:
|
|
377
|
+
|
|
378
|
+
```typescript
|
|
379
|
+
import { LiveUpdate } from '@capawesome/capacitor-live-update';
|
|
258
380
|
|
|
259
381
|
const getDeviceId = async () => {
|
|
260
382
|
const result = await LiveUpdate.getDeviceId();
|
|
261
383
|
return result.deviceId;
|
|
262
384
|
};
|
|
263
385
|
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
return result.bundleId;
|
|
386
|
+
const setCustomId = async () => {
|
|
387
|
+
await LiveUpdate.setCustomId({ customId: 'my-custom-id' });
|
|
267
388
|
};
|
|
268
389
|
|
|
390
|
+
const getCustomId = async () => {
|
|
391
|
+
const result = await LiveUpdate.getCustomId();
|
|
392
|
+
return result.customId;
|
|
393
|
+
};
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
### Read the app version
|
|
397
|
+
|
|
398
|
+
Get the native version code and version name of the app, for example to restrict live updates to compatible native versions:
|
|
399
|
+
|
|
400
|
+
```typescript
|
|
401
|
+
import { LiveUpdate } from '@capawesome/capacitor-live-update';
|
|
402
|
+
|
|
269
403
|
const getVersionCode = async () => {
|
|
270
404
|
const result = await LiveUpdate.getVersionCode();
|
|
271
405
|
return result.versionCode;
|
|
@@ -275,58 +409,18 @@ const getVersionName = async () => {
|
|
|
275
409
|
const result = await LiveUpdate.getVersionName();
|
|
276
410
|
return result.versionName;
|
|
277
411
|
};
|
|
412
|
+
```
|
|
278
413
|
|
|
279
|
-
|
|
280
|
-
const result = await LiveUpdate.ready();
|
|
281
|
-
if (result.currentBundleId) {
|
|
282
|
-
console.log(`The app is now using the bundle with the identifier ${result.currentBundleId}.`);
|
|
283
|
-
}
|
|
284
|
-
if (result.previousBundleId) {
|
|
285
|
-
console.log(`The app was using the bundle with the identifier ${result.previousBundleId}.`);
|
|
286
|
-
}
|
|
287
|
-
if (result.rollback) {
|
|
288
|
-
console.log('The app was reset to the default bundle.');
|
|
289
|
-
}
|
|
290
|
-
};
|
|
414
|
+
### Reset to the default bundle
|
|
291
415
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
416
|
+
Reset the app to the bundle that shipped with the native app binary. Call `reload()` or restart the app to apply the changes:
|
|
417
|
+
|
|
418
|
+
```typescript
|
|
419
|
+
import { LiveUpdate } from '@capawesome/capacitor-live-update';
|
|
295
420
|
|
|
296
421
|
const reset = async () => {
|
|
297
422
|
await LiveUpdate.reset();
|
|
298
423
|
};
|
|
299
|
-
|
|
300
|
-
const setChannel = async () => {
|
|
301
|
-
await LiveUpdate.setChannel({ channel: 'production-5' });
|
|
302
|
-
};
|
|
303
|
-
|
|
304
|
-
const setCustomId = async () => {
|
|
305
|
-
await LiveUpdate.setCustomId({ customId: 'my-custom-id' });
|
|
306
|
-
};
|
|
307
|
-
|
|
308
|
-
const setNextBundle = async () => {
|
|
309
|
-
await LiveUpdate.setNextBundle({ bundleId: '7f0b9bf2-dff6-4be2-bcac-b068cc5ea756' });
|
|
310
|
-
};
|
|
311
|
-
|
|
312
|
-
const sync = async () => {
|
|
313
|
-
const result = await LiveUpdate.sync({
|
|
314
|
-
channel: 'production-5',
|
|
315
|
-
});
|
|
316
|
-
return result.nextBundleId;
|
|
317
|
-
};
|
|
318
|
-
|
|
319
|
-
const isNewBundleAvailable = async () => {
|
|
320
|
-
const { bundleId: latestBundleId } = await LiveUpdate.fetchLatestBundle({
|
|
321
|
-
channel: 'production-5',
|
|
322
|
-
});
|
|
323
|
-
if (latestBundleId) {
|
|
324
|
-
const { bundleId: currentBundleId } = await LiveUpdate.getCurrentBundle();
|
|
325
|
-
return latestBundleId !== currentBundleId;
|
|
326
|
-
} else {
|
|
327
|
-
return false;
|
|
328
|
-
}
|
|
329
|
-
};
|
|
330
424
|
```
|
|
331
425
|
|
|
332
426
|
## API
|
|
@@ -1268,7 +1362,7 @@ On Capawesome Cloud, the bundle ID corresponds to the ID of the app build artifa
|
|
|
1268
1362
|
There are four ways to set a channel, listed from lowest to highest priority:
|
|
1269
1363
|
|
|
1270
1364
|
1. **Capacitor config**: Set the `defaultChannel` property in the [plugin configuration](#configuration). This is the simplest way to set a static default channel.
|
|
1271
|
-
2. **Native config**: Set `CapawesomeLiveUpdateDefaultChannel` in `Info.plist` (iOS) or `capawesome_live_update_default_channel` in `strings.xml` (Android). This is useful for [Versioned Channels](https://capawesome.io/docs/cloud/live-updates/
|
|
1365
|
+
2. **Native config**: Set `CapawesomeLiveUpdateDefaultChannel` in `Info.plist` (iOS) or `capawesome_live_update_default_channel` in `strings.xml` (Android). This is useful for [Versioned Channels](https://capawesome.io/docs/cloud/live-updates/channels/#versioned-channels) where the channel is tied to the build version.
|
|
1272
1366
|
3. **[`setChannel(...)`](#setchannel)**: Set the channel at runtime. The value is persisted across app restarts.
|
|
1273
1367
|
4. **[`sync(...)`](#sync)**: Pass a `channel` option to override the channel for a single sync call. This does **not** persist the channel.
|
|
1274
1368
|
|
|
@@ -1288,6 +1382,27 @@ You then have three options to get back to the default bundle:
|
|
|
1288
1382
|
|
|
1289
1383
|
However, this is only a problem during development. It is not a problem in production, as Capacitor automatically resets to the default bundle after a native update.
|
|
1290
1384
|
|
|
1385
|
+
### Do I have to use Capawesome Cloud?
|
|
1386
|
+
|
|
1387
|
+
No, the plugin also supports self-hosted bundles. You can download a bundle from any URL using the [`downloadBundle(...)`](#downloadbundle) method and apply it with [`setNextBundle(...)`](#setnextbundle), without any Capawesome Cloud dependency. Capawesome Cloud is only required for the convenience features such as [`sync(...)`](#sync), channels, and rollouts.
|
|
1388
|
+
|
|
1389
|
+
### What changes can I ship with a live update?
|
|
1390
|
+
|
|
1391
|
+
Live updates are only supported for [binary-compatible changes](https://capawesome.io/docs/cloud/live-updates/faq/#what-are-binary-compatible-changes) such as HTML, CSS, and JavaScript. If you change native code, for example by adding a new plugin, you need to resubmit your app to the app stores. You should therefore [restrict live updates to compatible native versions](https://capawesome.io/blog/how-to-restrict-capacitor-live-updates-to-native-versions/) of your app, as described in the [Limitations](#limitations) section.
|
|
1392
|
+
|
|
1393
|
+
### How does the rollback mechanism work?
|
|
1394
|
+
|
|
1395
|
+
If the `readyTimeout` configuration option is set, the plugin waits for the app to call the [`ready()`](#ready) method after a new bundle has been applied. If the method is not called within the configured timeout, the plugin assumes the bundle is faulty and resets the app to the default bundle. It is strongly recommended to configure `readyTimeout` (e.g. `10000` ms). Additionally, with the `autoBlockRolledBackBundles` option enabled, bundles that caused a rollback are automatically blocked and skipped in future sync operations.
|
|
1396
|
+
|
|
1397
|
+
## Related Plugins
|
|
1398
|
+
|
|
1399
|
+
- [App Update](https://capawesome.io/docs/sdks/capacitor/app-update/): Assist users with native app updates, which are required for changes that are not binary-compatible.
|
|
1400
|
+
- [Firebase Remote Config](https://capawesome.io/docs/sdks/capacitor/firebase/remote-config/): Change the behavior and appearance of your app remotely without publishing an update.
|
|
1401
|
+
|
|
1402
|
+
## Newsletter
|
|
1403
|
+
|
|
1404
|
+
Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/).
|
|
1405
|
+
|
|
1291
1406
|
## Changelog
|
|
1292
1407
|
|
|
1293
1408
|
See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/live-update/CHANGELOG.md).
|
package/android/build.gradle
CHANGED
|
@@ -56,6 +56,7 @@ dependencies {
|
|
|
56
56
|
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
|
|
57
57
|
implementation "net.lingala.zip4j:zip4j:$zip4jVersion"
|
|
58
58
|
implementation "com.squareup.okhttp3:okhttp:$okhttp3Version"
|
|
59
|
+
implementation "com.squareup.okhttp3:okhttp-brotli:$okhttp3Version"
|
|
59
60
|
testImplementation "junit:junit:$junitVersion"
|
|
60
61
|
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
|
|
61
62
|
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
|
|
@@ -7,9 +7,11 @@ import android.content.pm.PackageManager;
|
|
|
7
7
|
import android.content.res.AssetManager;
|
|
8
8
|
import android.os.Build;
|
|
9
9
|
import android.os.Handler;
|
|
10
|
+
import android.os.Looper;
|
|
10
11
|
import android.util.Base64;
|
|
11
12
|
import androidx.annotation.NonNull;
|
|
12
13
|
import androidx.annotation.Nullable;
|
|
14
|
+
import androidx.core.content.pm.PackageInfoCompat;
|
|
13
15
|
import com.getcapacitor.Bridge;
|
|
14
16
|
import com.getcapacitor.Logger;
|
|
15
17
|
import com.getcapacitor.plugin.WebView;
|
|
@@ -102,7 +104,7 @@ public class LiveUpdate {
|
|
|
102
104
|
private final SharedPreferences.Editor webViewSettingsEditor;
|
|
103
105
|
|
|
104
106
|
private final String bundlesDirectory = "_capacitor_live_update_bundles"; // DO NOT CHANGE!
|
|
105
|
-
private final Handler rollbackHandler = new Handler();
|
|
107
|
+
private final Handler rollbackHandler = new Handler(Looper.getMainLooper());
|
|
106
108
|
private final String manifestFileName = "capawesome-live-update-manifest.json"; // DO NOT CHANGE!
|
|
107
109
|
|
|
108
110
|
private boolean initialPageLoaded = false;
|
|
@@ -1050,7 +1052,7 @@ public class LiveUpdate {
|
|
|
1050
1052
|
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
|
1051
1053
|
BufferedSource source = Okio.buffer(Okio.source(file));
|
|
1052
1054
|
Buffer buffer = new Buffer();
|
|
1053
|
-
for (long bytesRead; (bytesRead = source.read(buffer, 2048)) != -1;) {
|
|
1055
|
+
for (long bytesRead; (bytesRead = source.read(buffer, 2048)) != -1; ) {
|
|
1054
1056
|
digest.update(buffer.readByteArray());
|
|
1055
1057
|
}
|
|
1056
1058
|
source.close();
|
|
@@ -1173,7 +1175,7 @@ public class LiveUpdate {
|
|
|
1173
1175
|
}
|
|
1174
1176
|
|
|
1175
1177
|
private int getVersionCodeAsInt() throws PackageManager.NameNotFoundException {
|
|
1176
|
-
return getPackageInfo()
|
|
1178
|
+
return (int) PackageInfoCompat.getLongVersionCode(getPackageInfo());
|
|
1177
1179
|
}
|
|
1178
1180
|
|
|
1179
1181
|
private String getVersionCodeAsString() throws PackageManager.NameNotFoundException {
|
|
@@ -1488,7 +1490,7 @@ public class LiveUpdate {
|
|
|
1488
1490
|
sig.initVerify(key);
|
|
1489
1491
|
BufferedSource source = Okio.buffer(Okio.source(file));
|
|
1490
1492
|
Buffer buffer = new Buffer();
|
|
1491
|
-
for (long bytesRead; (bytesRead = source.read(buffer, 2048)) != -1;) {
|
|
1493
|
+
for (long bytesRead; (bytesRead = source.read(buffer, 2048)) != -1; ) {
|
|
1492
1494
|
sig.update(buffer.readByteArray());
|
|
1493
1495
|
}
|
|
1494
1496
|
source.close();
|
package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdateHttpClient.java
CHANGED
|
@@ -15,6 +15,7 @@ import okhttp3.OkHttpClient;
|
|
|
15
15
|
import okhttp3.Request;
|
|
16
16
|
import okhttp3.Response;
|
|
17
17
|
import okhttp3.ResponseBody;
|
|
18
|
+
import okhttp3.brotli.BrotliInterceptor;
|
|
18
19
|
import okio.Buffer;
|
|
19
20
|
import okio.BufferedSink;
|
|
20
21
|
import okio.BufferedSource;
|
|
@@ -58,6 +59,7 @@ public class LiveUpdateHttpClient {
|
|
|
58
59
|
dispatcher.setMaxRequestsPerHost(30);
|
|
59
60
|
|
|
60
61
|
this.okHttpClient = new OkHttpClient.Builder()
|
|
62
|
+
.addInterceptor(BrotliInterceptor.INSTANCE)
|
|
61
63
|
.dispatcher(dispatcher)
|
|
62
64
|
.connectTimeout(httpTimeout, TimeUnit.MILLISECONDS)
|
|
63
65
|
.readTimeout(httpTimeout, TimeUnit.MILLISECONDS)
|
|
@@ -101,7 +103,7 @@ public class LiveUpdateHttpClient {
|
|
|
101
103
|
Buffer sinkBuffer = sink.getBuffer();
|
|
102
104
|
long totalBytesRead = 0;
|
|
103
105
|
int bufferSize = 8 * 1024;
|
|
104
|
-
for (long bytesRead; (bytesRead = source.read(sinkBuffer, bufferSize)) != -1;) {
|
|
106
|
+
for (long bytesRead; (bytesRead = source.read(sinkBuffer, bufferSize)) != -1; ) {
|
|
105
107
|
sink.emit();
|
|
106
108
|
totalBytesRead += bytesRead;
|
|
107
109
|
if (callback != null) {
|
package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdatePlugin.java
CHANGED
|
@@ -89,17 +89,16 @@ public class LiveUpdatePlugin extends Plugin {
|
|
|
89
89
|
// or constructor, it MUST be done here in onResume().
|
|
90
90
|
if (!webViewListenerRegistered) {
|
|
91
91
|
webViewListenerRegistered = true;
|
|
92
|
-
getBridge()
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
implementation.handleOnPageLoaded();
|
|
99
|
-
}
|
|
92
|
+
getBridge().addWebViewListener(
|
|
93
|
+
new WebViewListener() {
|
|
94
|
+
@Override
|
|
95
|
+
public void onPageLoaded(WebView webView) {
|
|
96
|
+
if (implementation != null) {
|
|
97
|
+
implementation.handleOnPageLoaded();
|
|
100
98
|
}
|
|
101
99
|
}
|
|
102
|
-
|
|
100
|
+
}
|
|
101
|
+
);
|
|
103
102
|
}
|
|
104
103
|
} catch (Exception exception) {
|
|
105
104
|
Logger.error(TAG, exception.getMessage(), exception);
|
|
@@ -463,7 +463,8 @@ import CommonCrypto
|
|
|
463
463
|
|
|
464
464
|
private func downloadAndVerifyFile(url: String, file: URL, checksum: String?, signature: String?, callback: ((Progress) -> Void)?) async throws {
|
|
465
465
|
let destination: DownloadRequest.Destination = { _, _ in
|
|
466
|
-
|
|
466
|
+
// `removePreviousFile` ensures that a leftover file from a failed attempt does not fail the download
|
|
467
|
+
return (file, [.createIntermediateDirectories, .removePreviousFile])
|
|
467
468
|
}
|
|
468
469
|
let urlComponents = URLComponents(string: url)!
|
|
469
470
|
let result = try await httpClient.download(url: urlComponents.asURL(), destination: destination, callback: callback)
|
|
@@ -27,13 +27,16 @@ public class LiveUpdateHttpClient: NSObject {
|
|
|
27
27
|
|
|
28
28
|
public func download(url: URL, destination: @escaping DownloadRequest.Destination, callback: ((Progress) -> Void)?) async throws -> AFDownloadResponse<Data> {
|
|
29
29
|
var request = URLRequest(url: url)
|
|
30
|
+
// Ignore the URL cache so that a cached response is never replayed for a deterministic request URL
|
|
31
|
+
request.cachePolicy = .reloadIgnoringLocalCacheData
|
|
30
32
|
request.httpMethod = HTTPMethod.get.rawValue
|
|
31
33
|
request.timeoutInterval = Double(config.httpTimeout) / 1000.0
|
|
32
34
|
if let deviceId = deviceId, !deviceId.isEmpty {
|
|
33
35
|
request.setValue(deviceId, forHTTPHeaderField: "X-Capawesome-Device-Id")
|
|
34
36
|
}
|
|
35
37
|
return try await withCheckedThrowingContinuation { continuation in
|
|
36
|
-
|
|
38
|
+
// `validate()` treats non-2xx responses as errors so that an error body is never saved as a bundle file
|
|
39
|
+
AF.download(request, to: destination).validate().downloadProgress { progress in
|
|
37
40
|
if let callback = callback {
|
|
38
41
|
callback(progress)
|
|
39
42
|
}
|
|
@@ -45,6 +48,8 @@ public class LiveUpdateHttpClient: NSObject {
|
|
|
45
48
|
|
|
46
49
|
public func request<T: Decodable>(url: URL, type: T.Type) async throws -> AFDataResponse<T> {
|
|
47
50
|
var request = URLRequest(url: url)
|
|
51
|
+
// Ignore the URL cache so that a cached response is never replayed for a deterministic request URL
|
|
52
|
+
request.cachePolicy = .reloadIgnoringLocalCacheData
|
|
48
53
|
request.httpMethod = HTTPMethod.get.rawValue
|
|
49
54
|
request.timeoutInterval = Double(config.httpTimeout) / 1000.0
|
|
50
55
|
if let deviceId = deviceId, !deviceId.isEmpty {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@capawesome/capacitor-live-update",
|
|
3
|
-
"version": "8.
|
|
4
|
-
"description": "Capacitor plugin to update your app remotely in real-time.",
|
|
3
|
+
"version": "8.4.0",
|
|
4
|
+
"description": "Capacitor plugin to update your app remotely in real-time on Android and iOS.",
|
|
5
5
|
"main": "dist/plugin.cjs.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|
|
7
7
|
"types": "dist/esm/index.d.ts",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"bugs": {
|
|
24
24
|
"url": "https://github.com/capawesome-team/capacitor-plugins/issues"
|
|
25
25
|
},
|
|
26
|
-
"homepage": "https://capawesome.io/docs/
|
|
26
|
+
"homepage": "https://capawesome.io/docs/sdks/capacitor/live-update/",
|
|
27
27
|
"funding": [
|
|
28
28
|
{
|
|
29
29
|
"type": "github",
|
|
@@ -44,7 +44,14 @@
|
|
|
44
44
|
"live update",
|
|
45
45
|
"remote update",
|
|
46
46
|
"ota",
|
|
47
|
-
"capgo"
|
|
47
|
+
"capgo",
|
|
48
|
+
"capacitor-plugin",
|
|
49
|
+
"live updates",
|
|
50
|
+
"ota update",
|
|
51
|
+
"over-the-air updates",
|
|
52
|
+
"code push",
|
|
53
|
+
"hot update",
|
|
54
|
+
"bundle management"
|
|
48
55
|
],
|
|
49
56
|
"scripts": {
|
|
50
57
|
"verify": "npm run verify:ios && npm run verify:android && npm run verify:web",
|
|
@@ -68,15 +75,15 @@
|
|
|
68
75
|
},
|
|
69
76
|
"devDependencies": {
|
|
70
77
|
"@capacitor/android": "8.0.0",
|
|
71
|
-
"@capacitor/cli": "8.
|
|
78
|
+
"@capacitor/cli": "8.4.2",
|
|
72
79
|
"@capacitor/core": "8.0.0",
|
|
73
80
|
"@capacitor/docgen": "0.3.1",
|
|
74
81
|
"@capacitor/ios": "8.0.0",
|
|
75
82
|
"@ionic/eslint-config": "0.4.0",
|
|
76
83
|
"eslint": "8.57.0",
|
|
77
|
-
"prettier-plugin-java": "2.
|
|
84
|
+
"prettier-plugin-java": "2.9.7",
|
|
78
85
|
"rimraf": "6.1.2",
|
|
79
|
-
"rollup": "4.
|
|
86
|
+
"rollup": "4.62.3",
|
|
80
87
|
"swiftlint": "2.0.0",
|
|
81
88
|
"typescript": "5.9.3"
|
|
82
89
|
},
|