@capawesome/capacitor-live-update 8.1.0 → 8.2.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.
Files changed (28) hide show
  1. package/README.md +122 -15
  2. package/android/build.gradle +1 -1
  3. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdate.java +80 -0
  4. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdateHttpClient.java +12 -1
  5. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdatePlugin.java +31 -1
  6. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/api/GetChannelsResponseItem.java +28 -0
  7. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/options/FetchChannelsOptions.java +38 -0
  8. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/results/ChannelResult.java +28 -0
  9. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/results/FetchChannelsResult.java +28 -0
  10. package/dist/docs.json +152 -4
  11. package/dist/esm/definitions.d.ts +85 -2
  12. package/dist/esm/definitions.js.map +1 -1
  13. package/dist/esm/web.d.ts +2 -1
  14. package/dist/esm/web.js +3 -0
  15. package/dist/esm/web.js.map +1 -1
  16. package/dist/plugin.cjs.js +3 -0
  17. package/dist/plugin.cjs.js.map +1 -1
  18. package/dist/plugin.js +3 -0
  19. package/dist/plugin.js.map +1 -1
  20. package/ios/Plugin/Classes/Options/FetchChannelsOptions.swift +26 -0
  21. package/ios/Plugin/Classes/Results/ChannelResult.swift +19 -0
  22. package/ios/Plugin/Classes/Results/FetchChannelsResult.swift +16 -0
  23. package/ios/Plugin/Enums/CustomError.swift +3 -0
  24. package/ios/Plugin/LiveUpdate.swift +43 -0
  25. package/ios/Plugin/LiveUpdateHttpClient.swift +11 -0
  26. package/ios/Plugin/LiveUpdatePlugin.swift +21 -1
  27. package/ios/Plugin/Protocols/Api/GetChannelsResponseItem.swift +4 -0
  28. package/package.json +1 -3
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.
@@ -21,6 +21,10 @@ We are proud to offer one of the most complete and feature-rich Capacitor plugin
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
+ - ⚙️ **Runtime Configuration**: Update plugin configuration at runtime without rebuilding the app.
25
+ - 📡 **Update Lifecycle Events**: Track download progress, react to bundle changes, and detect reloads with auto-blocking of rolled back bundles.
26
+ - 🏠 **Self-Hosted Bundles**: Download bundles from any URL, no Capawesome Cloud dependency required.
27
+ - 🏷️ **Custom Properties**: Associate custom key-value metadata with bundles via Capawesome Cloud.
24
28
  - 🔒 **Security**: Verify the authenticity and integrity of the bundle using a public key.
25
29
  - ⚔️ **Battle-Tested**: Used in more than 1,000 projects to update apps on more than 20,000,000 devices.
26
30
  - 🌐 **Open Source**: Licensed under the MIT License.
@@ -38,12 +42,8 @@ Missing a feature? Just [open an issue](https://github.com/capawesome-team/capac
38
42
 
39
43
  ## Guides
40
44
 
41
- - [Announcing the Capacitor Live Update Plugin](https://capawesome.io/blog/announcing-the-capacitor-live-update-plugin/)
42
- - [Exploring the Capacitor Live Update API](https://capawesome.io/blog/exploring-the-capacitor-live-update-api/)
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/)
45
+ - [Getting Started with Capawesome Cloud Live Updates](https://capawesome.io/cloud/live-updates/setup/)
46
+ - [Migrating from Ionic Appflow to Capawesome Cloud](https://capawesome.io/blog/migrating-from-ionic-appflow-to-capawesome-cloud/)
47
47
  - [Migrating from App Center to Capawesome Cloud](https://capawesome.io/blog/migrating-from-app-center-to-capawesome-cloud/)
48
48
 
49
49
  ## Installation
@@ -55,6 +55,21 @@ npx cap sync
55
55
 
56
56
  ### Android
57
57
 
58
+ #### Channel
59
+
60
+ If you are using [Versioned Channels](https://capawesome.io/cloud/live-updates/guides/best-practices/#versioned-channels), you can set a default channel directly in your native project by adding a string resource.
61
+ This allows you to tie the channel to the version code at build time.
62
+
63
+ Add the following to your app's `build.gradle` file:
64
+
65
+ ```groovy
66
+ android {
67
+ defaultConfig {
68
+ resValue "string", "capawesome_live_update_default_channel", "production-" + defaultConfig.versionCode
69
+ }
70
+ }
71
+ ```
72
+
58
73
  #### Variables
59
74
 
60
75
  If needed, you can define the following project variable in your app’s `variables.gradle` file to change the default version of the dependency:
@@ -66,6 +81,18 @@ This can be useful if you encounter dependency conflicts with other plugins in y
66
81
 
67
82
  ### iOS
68
83
 
84
+ #### Channel
85
+
86
+ If you are using [Versioned Channels](https://capawesome.io/cloud/live-updates/guides/best-practices/#versioned-channels), you can set a default channel directly in your native project by adding a key to your `Info.plist` file.
87
+ This allows you to tie the channel to the build version at build time.
88
+
89
+ Add the following to your `Info.plist` file:
90
+
91
+ ```xml
92
+ <key>CapawesomeLiveUpdateDefaultChannel</key>
93
+ <string>production-$(CURRENT_PROJECT_VERSION)</string>
94
+ ```
95
+
69
96
  #### Privacy manifest
70
97
 
71
98
  Add the `NSPrivacyAccessedAPICategoryUserDefaults` dictionary key to your [Privacy Manifest](https://capacitorjs.com/docs/ios/privacy-manifest) (usually `ios/App/PrivacyInfo.xcprivacy`):
@@ -104,7 +131,7 @@ We recommend to declare [`CA92.1`](https://developer.apple.com/documentation/bun
104
131
  | **`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 |
105
132
  | **`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 |
106
133
  | **`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 |
107
- | **`defaultChannel`** | <code>string</code> | The default channel of the app. | | 6.3.0 |
134
+ | **`defaultChannel`** | <code>string</code> | The default channel of the app. This can be overridden by `setChannel()`, the `channel` parameter of `sync()`, or the native channel configuration (`CapawesomeLiveUpdateDefaultChannel` in `Info.plist` on iOS or `capawesome_live_update_default_channel` in `strings.xml` on Android). | | 6.3.0 |
108
135
  | **`httpTimeout`** | <code>number</code> | The timeout in milliseconds for HTTP requests. | <code>60000</code> | 6.4.0 |
109
136
  | **`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 |
110
137
  | **`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 |
@@ -173,6 +200,11 @@ const downloadBundle = async () => {
173
200
  await LiveUpdate.downloadBundle({ url: 'https://example.com/1.0.0.zip', bundleId: '1.0.0' });
174
201
  };
175
202
 
203
+ const fetchChannels = async () => {
204
+ const result = await LiveUpdate.fetchChannels();
205
+ return result.channels;
206
+ };
207
+
176
208
  const fetchLatestBundle = async () => {
177
209
  await LiveUpdate.fetchLatestBundle();
178
210
  };
@@ -277,6 +309,7 @@ const isNewBundleAvailable = async () => {
277
309
  * [`clearBlockedBundles()`](#clearblockedbundles)
278
310
  * [`deleteBundle(...)`](#deletebundle)
279
311
  * [`downloadBundle(...)`](#downloadbundle)
312
+ * [`fetchChannels(...)`](#fetchchannels)
280
313
  * [`fetchLatestBundle(...)`](#fetchlatestbundle)
281
314
  * [`getBlockedBundles()`](#getblockedbundles)
282
315
  * [`getBundles()`](#getbundles)
@@ -367,6 +400,35 @@ Only available on Android and iOS.
367
400
  --------------------
368
401
 
369
402
 
403
+ ### fetchChannels(...)
404
+
405
+ ```typescript
406
+ fetchChannels(options?: FetchChannelsOptions | undefined) => Promise<FetchChannelsResult>
407
+ ```
408
+
409
+ Fetch channels from [Capawesome Cloud](https://capawesome.io/cloud/).
410
+
411
+ This is primarily intended for development and QA purposes.
412
+ It allows you to retrieve a list of available channels so you can
413
+ dynamically switch between them using `setChannel(...)`.
414
+
415
+ **Attention**: Only works for apps with public channels enabled.
416
+ If channels are private, they can still be set using `setChannel(...)`
417
+ but won't be returned by this method.
418
+
419
+ Only available on Android and iOS.
420
+
421
+ | Param | Type |
422
+ | ------------- | --------------------------------------------------------------------- |
423
+ | **`options`** | <code><a href="#fetchchannelsoptions">FetchChannelsOptions</a></code> |
424
+
425
+ **Returns:** <code>Promise&lt;<a href="#fetchchannelsresult">FetchChannelsResult</a>&gt;</code>
426
+
427
+ **Since:** 8.2.0
428
+
429
+ --------------------
430
+
431
+
370
432
  ### fetchLatestBundle(...)
371
433
 
372
434
  ```typescript
@@ -433,6 +495,15 @@ getChannel() => Promise<GetChannelResult>
433
495
 
434
496
  Get the channel that is used for the update.
435
497
 
498
+ The channel is resolved in the following order (highest priority first):
499
+ 1. `setChannel()` (SharedPreferences on Android / UserDefaults on iOS)
500
+ 2. Native config (`CapawesomeLiveUpdateDefaultChannel` in `Info.plist` on iOS or
501
+ `capawesome_live_update_default_channel` in `strings.xml` on Android)
502
+ 3. Capacitor config `defaultChannel`
503
+
504
+ **Note**: The `channel` parameter of `sync()` takes the highest priority
505
+ but is not persisted and therefore not returned by this method.
506
+
436
507
  Only available on Android and iOS.
437
508
 
438
509
  **Returns:** <code>Promise&lt;<a href="#getchannelresult">GetChannelResult</a>&gt;</code>
@@ -886,13 +957,37 @@ Remove all listeners for this plugin.
886
957
 
887
958
  #### DownloadBundleOptions
888
959
 
889
- | Prop | Type | Description | Default | Since |
890
- | ------------------ | -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | ----- |
891
- | **`artifactType`** | <code>'manifest' \| 'zip'</code> | The artifact type of the bundle. | <code>'zip'</code> | 6.6.0 |
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. | | 5.0.0 |
893
- | **`checksum`** | <code>string</code> | The checksum of the self-hosted bundle as a SHA-256 hash in base64 format to verify the integrity of the bundle. **Attention**: Only supported for the `zip` artifact type. | | 7.1.0 |
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. | | 7.1.0 |
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 base64 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 |
960
+ | Prop | Type | Description | Default | Since |
961
+ | ------------------ | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------ | ----- |
962
+ | **`artifactType`** | <code>'manifest' \| 'zip'</code> | The artifact type of the bundle. | <code>'zip'</code> | 6.6.0 |
963
+ | **`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 |
964
+ | **`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 |
965
+ | **`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 |
966
+ | **`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 |
967
+
968
+
969
+ #### FetchChannelsResult
970
+
971
+ | Prop | Type | Description | Since |
972
+ | -------------- | ---------------------- | --------------------- | ----- |
973
+ | **`channels`** | <code>Channel[]</code> | The list of channels. | 8.2.0 |
974
+
975
+
976
+ #### Channel
977
+
978
+ | Prop | Type | Description | Since |
979
+ | ---------- | ------------------- | ------------------------------------- | ----- |
980
+ | **`id`** | <code>string</code> | The unique identifier of the channel. | 8.2.0 |
981
+ | **`name`** | <code>string</code> | The name of the channel. | 8.2.0 |
982
+
983
+
984
+ #### FetchChannelsOptions
985
+
986
+ | Prop | Type | Description | Default | Since |
987
+ | ------------ | ------------------- | ----------------------------------------- | --------------- | ----- |
988
+ | **`limit`** | <code>number</code> | The maximum number of channels to return. | <code>50</code> | 8.2.0 |
989
+ | **`offset`** | <code>number</code> | The number of channels to skip. | <code>0</code> | 8.2.0 |
990
+ | **`query`** | <code>string</code> | The query to filter channels by name. | | 8.2.0 |
896
991
 
897
992
 
898
993
  #### FetchLatestBundleResult
@@ -1133,6 +1228,18 @@ For this reason, you must be careful to [restrict live updates to compatible nat
1133
1228
 
1134
1229
  ## FAQ
1135
1230
 
1231
+ ### How do I set a channel?
1232
+
1233
+ There are four ways to set a channel, listed from lowest to highest priority:
1234
+
1235
+ 1. **Capacitor config**: Set the `defaultChannel` property in the [plugin configuration](#configuration). This is the simplest way to set a static default channel.
1236
+ 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/cloud/live-updates/guides/best-practices/#versioned-channels) where the channel is tied to the build version.
1237
+ 3. **[`setChannel(...)`](#setchannel)**: Set the channel at runtime. The value is persisted across app restarts.
1238
+ 4. **[`sync(...)`](#sync)**: Pass a `channel` option to override the channel for a single sync call. This does **not** persist the channel.
1239
+
1240
+ Each method overrides the ones above it.
1241
+ You can check the currently resolved channel by calling [`getChannel()`](#getchannel).
1242
+
1136
1243
  ### Why can't I see my changes during development?
1137
1244
 
1138
1245
  As soon as you have installed a live update, the app will use the live update bundle and no longer the default bundle.
@@ -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 {
@@ -15,17 +15,21 @@ import com.getcapacitor.Logger;
15
15
  import com.getcapacitor.plugin.WebView;
16
16
  import io.capawesome.capacitorjs.plugins.liveupdate.classes.Manifest;
17
17
  import io.capawesome.capacitorjs.plugins.liveupdate.classes.ManifestItem;
18
+ import io.capawesome.capacitorjs.plugins.liveupdate.classes.api.GetChannelsResponseItem;
18
19
  import io.capawesome.capacitorjs.plugins.liveupdate.classes.api.GetLatestBundleResponse;
19
20
  import io.capawesome.capacitorjs.plugins.liveupdate.classes.events.DownloadBundleProgressEvent;
20
21
  import io.capawesome.capacitorjs.plugins.liveupdate.classes.events.NextBundleSetEvent;
21
22
  import io.capawesome.capacitorjs.plugins.liveupdate.classes.options.DeleteBundleOptions;
22
23
  import io.capawesome.capacitorjs.plugins.liveupdate.classes.options.DownloadBundleOptions;
24
+ import io.capawesome.capacitorjs.plugins.liveupdate.classes.options.FetchChannelsOptions;
23
25
  import io.capawesome.capacitorjs.plugins.liveupdate.classes.options.FetchLatestBundleOptions;
24
26
  import io.capawesome.capacitorjs.plugins.liveupdate.classes.options.SetChannelOptions;
25
27
  import io.capawesome.capacitorjs.plugins.liveupdate.classes.options.SetConfigOptions;
26
28
  import io.capawesome.capacitorjs.plugins.liveupdate.classes.options.SetCustomIdOptions;
27
29
  import io.capawesome.capacitorjs.plugins.liveupdate.classes.options.SetNextBundleOptions;
28
30
  import io.capawesome.capacitorjs.plugins.liveupdate.classes.options.SyncOptions;
31
+ import io.capawesome.capacitorjs.plugins.liveupdate.classes.results.ChannelResult;
32
+ import io.capawesome.capacitorjs.plugins.liveupdate.classes.results.FetchChannelsResult;
29
33
  import io.capawesome.capacitorjs.plugins.liveupdate.classes.results.FetchLatestBundleResult;
30
34
  import io.capawesome.capacitorjs.plugins.liveupdate.classes.results.GetBlockedBundlesResult;
31
35
  import io.capawesome.capacitorjs.plugins.liveupdate.classes.results.GetBundlesResult;
@@ -115,6 +119,9 @@ public class LiveUpdate {
115
119
  // Check version and reset config if version changed
116
120
  checkAndResetConfigIfVersionChanged();
117
121
 
122
+ // Set the device ID on the HTTP client (after any potential config reset)
123
+ this.httpClient.setDeviceId(getDeviceId());
124
+
118
125
  // Start the rollback timer to rollback to the default bundle
119
126
  // if the app is not ready after a certain time
120
127
  startRollbackTimer();
@@ -159,6 +166,63 @@ public class LiveUpdate {
159
166
  }
160
167
  }
161
168
 
169
+ public void fetchChannels(@NonNull FetchChannelsOptions options, @NonNull NonEmptyCallback<FetchChannelsResult> callback) {
170
+ try {
171
+ HttpUrl.Builder urlBuilder = new HttpUrl.Builder()
172
+ .scheme("https")
173
+ .host(config.getServerDomain())
174
+ .addPathSegment("v1")
175
+ .addPathSegment("apps")
176
+ .addPathSegment(getAppId())
177
+ .addPathSegment("channels");
178
+ if (options.getLimit() != null) {
179
+ urlBuilder.addQueryParameter("limit", String.valueOf(options.getLimit()));
180
+ }
181
+ if (options.getOffset() != null) {
182
+ urlBuilder.addQueryParameter("offset", String.valueOf(options.getOffset()));
183
+ }
184
+ if (options.getQuery() != null) {
185
+ urlBuilder.addQueryParameter("query", options.getQuery());
186
+ }
187
+ String url = urlBuilder.build().toString();
188
+
189
+ httpClient.enqueue(
190
+ url,
191
+ new NonEmptyCallback<Response>() {
192
+ @Override
193
+ public void success(@NonNull Response response) {
194
+ try {
195
+ String responseBodyString = response.body().string();
196
+ if (response.isSuccessful()) {
197
+ JSONArray responseJsonArray = new JSONArray(responseBodyString);
198
+ ChannelResult[] channels = new ChannelResult[responseJsonArray.length()];
199
+ for (int i = 0; i < responseJsonArray.length(); i++) {
200
+ GetChannelsResponseItem item = new GetChannelsResponseItem(responseJsonArray.getJSONObject(i));
201
+ channels[i] = new ChannelResult(item.getId(), item.getName());
202
+ }
203
+ FetchChannelsResult result = new FetchChannelsResult(channels);
204
+ callback.success(result);
205
+ } else if (response.code() == 401) {
206
+ callback.error(new Exception("Unauthorized. Channel Discovery may not be enabled for this app."));
207
+ } else {
208
+ callback.error(new Exception(responseBodyString));
209
+ }
210
+ } catch (Exception e) {
211
+ callback.error(e);
212
+ }
213
+ }
214
+
215
+ @Override
216
+ public void error(@NonNull Exception exception) {
217
+ callback.error(exception);
218
+ }
219
+ }
220
+ );
221
+ } catch (Exception e) {
222
+ callback.error(e);
223
+ }
224
+ }
225
+
162
226
  public void fetchLatestBundle(@NonNull FetchLatestBundleOptions options, @NonNull NonEmptyCallback<FetchLatestBundleResult> callback) {
163
227
  fetchLatestBundleInternal(
164
228
  options,
@@ -1015,12 +1079,28 @@ public class LiveUpdate {
1015
1079
  if (config.getDefaultChannel() != null) {
1016
1080
  channel = config.getDefaultChannel();
1017
1081
  }
1082
+ String nativeChannel = getNativeChannel();
1083
+ if (nativeChannel != null) {
1084
+ channel = nativeChannel;
1085
+ }
1018
1086
  if (preferences.getChannel() != null) {
1019
1087
  channel = preferences.getChannel();
1020
1088
  }
1021
1089
  return channel;
1022
1090
  }
1023
1091
 
1092
+ @Nullable
1093
+ private String getNativeChannel() {
1094
+ int resId = plugin
1095
+ .getContext()
1096
+ .getResources()
1097
+ .getIdentifier("capawesome_live_update_default_channel", "string", plugin.getContext().getPackageName());
1098
+ if (resId == 0) {
1099
+ return null;
1100
+ }
1101
+ return plugin.getContext().getResources().getString(resId);
1102
+ }
1103
+
1024
1104
  /**
1025
1105
  * @return The current bundle ID or `null` if the default bundle is in use.
1026
1106
  */
@@ -25,6 +25,9 @@ public class LiveUpdateHttpClient {
25
25
  @NonNull
26
26
  private final LiveUpdateConfig config;
27
27
 
28
+ @Nullable
29
+ private String deviceId;
30
+
28
31
  @NonNull
29
32
  private final OkHttpClient okHttpClient;
30
33
 
@@ -62,8 +65,16 @@ public class LiveUpdateHttpClient {
62
65
  .build();
63
66
  }
64
67
 
68
+ public void setDeviceId(@NonNull String deviceId) {
69
+ this.deviceId = deviceId;
70
+ }
71
+
65
72
  public Call enqueue(String url, NonEmptyCallback<Response> callback) {
66
- Request request = new Request.Builder().url(url).build();
73
+ Request.Builder builder = new Request.Builder().url(url);
74
+ if (deviceId != null) {
75
+ builder.addHeader("X-Capawesome-Device-Id", deviceId);
76
+ }
77
+ Request request = builder.build();
67
78
 
68
79
  Call call = okHttpClient.newCall(request);
69
80
  call.enqueue(
@@ -14,12 +14,14 @@ import io.capawesome.capacitorjs.plugins.liveupdate.classes.events.DownloadBundl
14
14
  import io.capawesome.capacitorjs.plugins.liveupdate.classes.events.NextBundleSetEvent;
15
15
  import io.capawesome.capacitorjs.plugins.liveupdate.classes.options.DeleteBundleOptions;
16
16
  import io.capawesome.capacitorjs.plugins.liveupdate.classes.options.DownloadBundleOptions;
17
+ import io.capawesome.capacitorjs.plugins.liveupdate.classes.options.FetchChannelsOptions;
17
18
  import io.capawesome.capacitorjs.plugins.liveupdate.classes.options.FetchLatestBundleOptions;
18
19
  import io.capawesome.capacitorjs.plugins.liveupdate.classes.options.SetChannelOptions;
19
20
  import io.capawesome.capacitorjs.plugins.liveupdate.classes.options.SetConfigOptions;
20
21
  import io.capawesome.capacitorjs.plugins.liveupdate.classes.options.SetCustomIdOptions;
21
22
  import io.capawesome.capacitorjs.plugins.liveupdate.classes.options.SetNextBundleOptions;
22
23
  import io.capawesome.capacitorjs.plugins.liveupdate.classes.options.SyncOptions;
24
+ import io.capawesome.capacitorjs.plugins.liveupdate.classes.results.FetchChannelsResult;
23
25
  import io.capawesome.capacitorjs.plugins.liveupdate.classes.results.FetchLatestBundleResult;
24
26
  import io.capawesome.capacitorjs.plugins.liveupdate.classes.results.GetBlockedBundlesResult;
25
27
  import io.capawesome.capacitorjs.plugins.liveupdate.classes.results.GetConfigResult;
@@ -35,7 +37,7 @@ import io.capawesome.capacitorjs.plugins.liveupdate.interfaces.Result;
35
37
  public class LiveUpdatePlugin extends Plugin {
36
38
 
37
39
  public static final String TAG = "LiveUpdate";
38
- public static final String VERSION = "8.1.0";
40
+ public static final String VERSION = "8.2.0";
39
41
  public static final String SHARED_PREFERENCES_NAME = "CapawesomeLiveUpdate"; // DO NOT CHANGE
40
42
  public static final String ERROR_APP_ID_MISSING = "appId must be configured.";
41
43
  public static final String ERROR_BUNDLE_EXISTS = "bundle already exists.";
@@ -178,6 +180,34 @@ public class LiveUpdatePlugin extends Plugin {
178
180
  }
179
181
  }
180
182
 
183
+ @PluginMethod
184
+ public void fetchChannels(PluginCall call) {
185
+ try {
186
+ String appId = config.getAppId();
187
+ if (appId == null || appId.isEmpty()) {
188
+ call.reject(ERROR_APP_ID_MISSING);
189
+ return;
190
+ }
191
+
192
+ FetchChannelsOptions options = new FetchChannelsOptions(call);
193
+ NonEmptyCallback<FetchChannelsResult> callback = new NonEmptyCallback<>() {
194
+ @Override
195
+ public void success(FetchChannelsResult result) {
196
+ resolveCall(call, result.toJSObject());
197
+ }
198
+
199
+ @Override
200
+ public void error(Exception exception) {
201
+ rejectCall(call, exception);
202
+ }
203
+ };
204
+
205
+ implementation.fetchChannels(options, callback);
206
+ } catch (Exception exception) {
207
+ rejectCall(call, exception);
208
+ }
209
+ }
210
+
181
211
  @PluginMethod
182
212
  public void fetchLatestBundle(PluginCall call) {
183
213
  try {
@@ -0,0 +1,28 @@
1
+ package io.capawesome.capacitorjs.plugins.liveupdate.classes.api;
2
+
3
+ import androidx.annotation.NonNull;
4
+ import org.json.JSONObject;
5
+
6
+ public class GetChannelsResponseItem {
7
+
8
+ @NonNull
9
+ private final String id;
10
+
11
+ @NonNull
12
+ private final String name;
13
+
14
+ public GetChannelsResponseItem(@NonNull JSONObject responseJson) {
15
+ this.id = responseJson.optString("id");
16
+ this.name = responseJson.optString("name");
17
+ }
18
+
19
+ @NonNull
20
+ public String getId() {
21
+ return id;
22
+ }
23
+
24
+ @NonNull
25
+ public String getName() {
26
+ return name;
27
+ }
28
+ }
@@ -0,0 +1,38 @@
1
+ package io.capawesome.capacitorjs.plugins.liveupdate.classes.options;
2
+
3
+ import androidx.annotation.NonNull;
4
+ import androidx.annotation.Nullable;
5
+ import com.getcapacitor.PluginCall;
6
+
7
+ public class FetchChannelsOptions {
8
+
9
+ @Nullable
10
+ private final Integer limit;
11
+
12
+ @Nullable
13
+ private final Integer offset;
14
+
15
+ @Nullable
16
+ private final String query;
17
+
18
+ public FetchChannelsOptions(@NonNull PluginCall call) {
19
+ this.limit = call.getInt("limit");
20
+ this.offset = call.getInt("offset");
21
+ this.query = call.getString("query");
22
+ }
23
+
24
+ @Nullable
25
+ public Integer getLimit() {
26
+ return limit;
27
+ }
28
+
29
+ @Nullable
30
+ public Integer getOffset() {
31
+ return offset;
32
+ }
33
+
34
+ @Nullable
35
+ public String getQuery() {
36
+ return query;
37
+ }
38
+ }
@@ -0,0 +1,28 @@
1
+ package io.capawesome.capacitorjs.plugins.liveupdate.classes.results;
2
+
3
+ import androidx.annotation.NonNull;
4
+ import com.getcapacitor.JSObject;
5
+ import io.capawesome.capacitorjs.plugins.liveupdate.interfaces.Result;
6
+
7
+ public class ChannelResult implements Result {
8
+
9
+ @NonNull
10
+ private final String id;
11
+
12
+ @NonNull
13
+ private final String name;
14
+
15
+ public ChannelResult(@NonNull String id, @NonNull String name) {
16
+ this.id = id;
17
+ this.name = name;
18
+ }
19
+
20
+ @NonNull
21
+ @Override
22
+ public JSObject toJSObject() {
23
+ JSObject result = new JSObject();
24
+ result.put("id", id);
25
+ result.put("name", name);
26
+ return result;
27
+ }
28
+ }
@@ -0,0 +1,28 @@
1
+ package io.capawesome.capacitorjs.plugins.liveupdate.classes.results;
2
+
3
+ import androidx.annotation.NonNull;
4
+ import com.getcapacitor.JSArray;
5
+ import com.getcapacitor.JSObject;
6
+ import io.capawesome.capacitorjs.plugins.liveupdate.interfaces.Result;
7
+
8
+ public class FetchChannelsResult implements Result {
9
+
10
+ @NonNull
11
+ private final ChannelResult[] channels;
12
+
13
+ public FetchChannelsResult(@NonNull ChannelResult[] channels) {
14
+ this.channels = channels;
15
+ }
16
+
17
+ @NonNull
18
+ @Override
19
+ public JSObject toJSObject() {
20
+ JSObject result = new JSObject();
21
+ JSArray channelsArray = new JSArray();
22
+ for (ChannelResult channel : channels) {
23
+ channelsArray.put(channel.toJSObject());
24
+ }
25
+ result.put("channels", channelsArray);
26
+ return result;
27
+ }
28
+ }