@capawesome/capacitor-live-update 7.4.0-dev.f18299f8.1767510052 → 7.5.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 (29) hide show
  1. package/CapawesomeCapacitorLiveUpdate.podspec +1 -1
  2. package/Package.swift +2 -2
  3. package/README.md +98 -6
  4. package/android/build.gradle +0 -2
  5. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdate.java +91 -3
  6. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdateHttpClient.java +12 -10
  7. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdatePlugin.java +31 -1
  8. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/api/GetChannelsResponseItem.java +28 -0
  9. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/options/FetchChannelsOptions.java +38 -0
  10. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/results/ChannelResult.java +28 -0
  11. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/results/FetchChannelsResult.java +28 -0
  12. package/dist/docs.json +150 -2
  13. package/dist/esm/definitions.d.ts +83 -0
  14. package/dist/esm/definitions.js.map +1 -1
  15. package/dist/esm/web.d.ts +2 -1
  16. package/dist/esm/web.js +3 -0
  17. package/dist/esm/web.js.map +1 -1
  18. package/dist/plugin.cjs.js +3 -0
  19. package/dist/plugin.cjs.js.map +1 -1
  20. package/dist/plugin.js +3 -0
  21. package/dist/plugin.js.map +1 -1
  22. package/ios/Plugin/Classes/Options/FetchChannelsOptions.swift +26 -0
  23. package/ios/Plugin/Classes/Results/ChannelResult.swift +19 -0
  24. package/ios/Plugin/Classes/Results/FetchChannelsResult.swift +16 -0
  25. package/ios/Plugin/LiveUpdate.swift +45 -8
  26. package/ios/Plugin/LiveUpdateHttpClient.swift +11 -0
  27. package/ios/Plugin/LiveUpdatePlugin.swift +21 -1
  28. package/ios/Plugin/Protocols/Api/GetChannelsResponseItem.swift +4 -0
  29. package/package.json +1 -1
@@ -14,7 +14,7 @@ Pod::Spec.new do |s|
14
14
  s.ios.deployment_target = '14.0'
15
15
  s.dependency 'Capacitor'
16
16
  s.dependency 'Alamofire', '~> 5.9'
17
- s.dependency 'SSZipArchive', '~> 2.2.3'
17
+ s.dependency 'ZIPFoundation', '~> 0.9.0'
18
18
  s.swift_version = '5.1'
19
19
  s.static_framework = true
20
20
  end
package/Package.swift CHANGED
@@ -12,7 +12,7 @@ let package = Package(
12
12
  dependencies: [
13
13
  .package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", from: "7.0.0"),
14
14
  .package(url: "https://github.com/Alamofire/Alamofire.git", .upToNextMajor(from: "5.9.0")),
15
- .package(url: "https://github.com/ZipArchive/ZipArchive.git", .upToNextMinor(from: "2.4.0"))
15
+ .package(url: "https://github.com/weichsel/ZIPFoundation.git", .upToNextMinor(from: "0.9.0"))
16
16
  ],
17
17
  targets: [
18
18
  .target(
@@ -21,7 +21,7 @@ let package = Package(
21
21
  .product(name: "Capacitor", package: "capacitor-swift-pm"),
22
22
  .product(name: "Cordova", package: "capacitor-swift-pm"),
23
23
  .product(name: "Alamofire", package: "Alamofire"),
24
- .product(name: "ZipArchive", package: "ZipArchive")
24
+ .product(name: "ZIPFoundation", package: "ZIPFoundation")
25
25
  ],
26
26
  path: "ios/Plugin"),
27
27
  .testTarget(
package/README.md CHANGED
@@ -54,20 +54,44 @@ npx cap sync
54
54
 
55
55
  ### Android
56
56
 
57
+ #### Channel
58
+
59
+ 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.
60
+ This allows you to tie the channel to the version code at build time.
61
+
62
+ Add the following to your app's `build.gradle` file:
63
+
64
+ ```groovy
65
+ android {
66
+ defaultConfig {
67
+ resValue "string", "capawesome_live_update_default_channel", "production-" + defaultConfig.versionCode
68
+ }
69
+ }
70
+ ```
71
+
57
72
  #### Variables
58
73
 
59
- If needed, you can define the following project variable in your app's `variables.gradle` file to change the default version of the dependency:
74
+ If needed, you can define the following project variable in your app’s `variables.gradle` file to change the default version of the dependency:
60
75
 
61
- - `$okhttp3Version` version of `com.squareup.okhttp3:okhttp` (default: `4.12.0`)
76
+ - `$okhttp3Version` version of `com.squareup.okhttp3:okhttp` (default: `22.3.1`)
62
77
  - `$zip4jVersion` version of `net.lingala.zip4j:zip4j` (default: `2.11.5`)
63
- - `$conscryptVersion` version of `org.conscrypt:conscrypt-android` (default: `2.5.3`)
64
78
 
65
79
  This can be useful if you encounter dependency conflicts with other plugins in your project.
66
80
 
67
- **Note**: The plugin uses Google Conscrypt as the security provider to ensure better SSL/TLS certificate compatibility on older Android versions (especially Android 7 and below).
68
-
69
81
  ### iOS
70
82
 
83
+ #### Channel
84
+
85
+ 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.
86
+ This allows you to tie the channel to the build version at build time.
87
+
88
+ Add the following to your `Info.plist` file:
89
+
90
+ ```xml
91
+ <key>CapawesomeLiveUpdateDefaultChannel</key>
92
+ <string>production-$(CURRENT_PROJECT_VERSION)</string>
93
+ ```
94
+
71
95
  #### Privacy manifest
72
96
 
73
97
  Add the `NSPrivacyAccessedAPICategoryUserDefaults` dictionary key to your [Privacy Manifest](https://capacitorjs.com/docs/ios/privacy-manifest) (usually `ios/App/PrivacyInfo.xcprivacy`):
@@ -106,7 +130,7 @@ We recommend to declare [`CA92.1`](https://developer.apple.com/documentation/bun
106
130
  | **`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 |
107
131
  | **`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 |
108
132
  | **`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 |
109
- | **`defaultChannel`** | <code>string</code> | The default channel of the app. | | 6.3.0 |
133
+ | **`defaultChannel`** | <code>string</code> | The default channel of the app. This can be overridden by `setChannel()` or the `channel` parameter of `sync()`. It takes priority over the native channel configuration (`CapawesomeLiveUpdateDefaultChannel` in `Info.plist` on iOS or `capawesome_live_update_default_channel` in `strings.xml` on Android). | | 6.3.0 |
110
134
  | **`httpTimeout`** | <code>number</code> | The timeout in milliseconds for HTTP requests. | <code>60000</code> | 6.4.0 |
111
135
  | **`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 |
112
136
  | **`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 |
@@ -175,6 +199,11 @@ const downloadBundle = async () => {
175
199
  await LiveUpdate.downloadBundle({ url: 'https://example.com/1.0.0.zip', bundleId: '1.0.0' });
176
200
  };
177
201
 
202
+ const fetchChannels = async () => {
203
+ const result = await LiveUpdate.fetchChannels();
204
+ return result.channels;
205
+ };
206
+
178
207
  const fetchLatestBundle = async () => {
179
208
  await LiveUpdate.fetchLatestBundle();
180
209
  };
@@ -279,6 +308,7 @@ const isNewBundleAvailable = async () => {
279
308
  * [`clearBlockedBundles()`](#clearblockedbundles)
280
309
  * [`deleteBundle(...)`](#deletebundle)
281
310
  * [`downloadBundle(...)`](#downloadbundle)
311
+ * [`fetchChannels(...)`](#fetchchannels)
282
312
  * [`fetchLatestBundle(...)`](#fetchlatestbundle)
283
313
  * [`getBlockedBundles()`](#getblockedbundles)
284
314
  * [`getBundles()`](#getbundles)
@@ -369,6 +399,35 @@ Only available on Android and iOS.
369
399
  --------------------
370
400
 
371
401
 
402
+ ### fetchChannels(...)
403
+
404
+ ```typescript
405
+ fetchChannels(options?: FetchChannelsOptions | undefined) => Promise<FetchChannelsResult>
406
+ ```
407
+
408
+ Fetch channels from [Capawesome Cloud](https://capawesome.io/cloud/).
409
+
410
+ This is primarily intended for development and QA purposes.
411
+ It allows you to retrieve a list of available channels so you can
412
+ dynamically switch between them using `setChannel(...)`.
413
+
414
+ **Attention**: Only works for apps with public channels enabled.
415
+ If channels are private, they can still be set using `setChannel(...)`
416
+ but won't be returned by this method.
417
+
418
+ Only available on Android and iOS.
419
+
420
+ | Param | Type |
421
+ | ------------- | --------------------------------------------------------------------- |
422
+ | **`options`** | <code><a href="#fetchchannelsoptions">FetchChannelsOptions</a></code> |
423
+
424
+ **Returns:** <code>Promise&lt;<a href="#fetchchannelsresult">FetchChannelsResult</a>&gt;</code>
425
+
426
+ **Since:** 7.5.0
427
+
428
+ --------------------
429
+
430
+
372
431
  ### fetchLatestBundle(...)
373
432
 
374
433
  ```typescript
@@ -435,6 +494,15 @@ getChannel() => Promise<GetChannelResult>
435
494
 
436
495
  Get the channel that is used for the update.
437
496
 
497
+ The channel is resolved in the following order (highest priority first):
498
+ 1. `setChannel()` (SharedPreferences on Android / UserDefaults on iOS)
499
+ 2. Capacitor config `defaultChannel`
500
+ 3. Native config (`CapawesomeLiveUpdateDefaultChannel` in `Info.plist` on iOS or
501
+ `capawesome_live_update_default_channel` in `strings.xml` on Android)
502
+
503
+ **Note**: The `channel` parameter of `sync()` takes the highest priority
504
+ but is not persisted and therefore not returned by this method.
505
+
438
506
  Only available on Android and iOS.
439
507
 
440
508
  **Returns:** <code>Promise&lt;<a href="#getchannelresult">GetChannelResult</a>&gt;</code>
@@ -897,6 +965,30 @@ Remove all listeners for this plugin.
897
965
  | **`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 |
898
966
 
899
967
 
968
+ #### FetchChannelsResult
969
+
970
+ | Prop | Type | Description | Since |
971
+ | -------------- | ---------------------- | --------------------- | ----- |
972
+ | **`channels`** | <code>Channel[]</code> | The list of channels. | 7.5.0 |
973
+
974
+
975
+ #### Channel
976
+
977
+ | Prop | Type | Description | Since |
978
+ | ---------- | ------------------- | ------------------------------------- | ----- |
979
+ | **`id`** | <code>string</code> | The unique identifier of the channel. | 7.5.0 |
980
+ | **`name`** | <code>string</code> | The name of the channel. | 7.5.0 |
981
+
982
+
983
+ #### FetchChannelsOptions
984
+
985
+ | Prop | Type | Description | Default | Since |
986
+ | ------------ | ------------------- | ----------------------------------------- | --------------- | ----- |
987
+ | **`limit`** | <code>number</code> | The maximum number of channels to return. | <code>50</code> | 7.5.0 |
988
+ | **`offset`** | <code>number</code> | The number of channels to skip. | <code>0</code> | 7.5.0 |
989
+ | **`query`** | <code>string</code> | The query to filter channels by name. | | 7.5.0 |
990
+
991
+
900
992
  #### FetchLatestBundleResult
901
993
 
902
994
  | Prop | Type | Description | Since |
@@ -5,7 +5,6 @@ ext {
5
5
  androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.6.1'
6
6
  okhttp3Version = project.hasProperty('okhttp3Version') ? rootProject.ext.okhttp3Version : '4.12.0'
7
7
  zip4jVersion = project.hasProperty('zip4jVersion') ? rootProject.ext.zip4jVersion : '2.11.5'
8
- conscryptVersion = project.hasProperty('conscryptVersion') ? rootProject.ext.conscryptVersion : '2.5.3'
9
8
  }
10
9
 
11
10
  buildscript {
@@ -57,7 +56,6 @@ dependencies {
57
56
  implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
58
57
  implementation "net.lingala.zip4j:zip4j:$zip4jVersion"
59
58
  implementation "com.squareup.okhttp3:okhttp:$okhttp3Version"
60
- implementation "org.conscrypt:conscrypt-android:$conscryptVersion"
61
59
  testImplementation "junit:junit:$junitVersion"
62
60
  androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
63
61
  androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
@@ -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;
@@ -65,6 +69,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
65
69
  import java.util.concurrent.atomic.AtomicLong;
66
70
  import java.util.concurrent.atomic.AtomicReference;
67
71
  import net.lingala.zip4j.ZipFile;
72
+ import net.lingala.zip4j.model.FileHeader;
68
73
  import okhttp3.Call;
69
74
  import okhttp3.HttpUrl;
70
75
  import okhttp3.Response;
@@ -115,6 +120,9 @@ public class LiveUpdate {
115
120
  // Check version and reset config if version changed
116
121
  checkAndResetConfigIfVersionChanged();
117
122
 
123
+ // Set the device ID on the HTTP client (after any potential config reset)
124
+ this.httpClient.setDeviceId(getDeviceId());
125
+
118
126
  // Start the rollback timer to rollback to the default bundle
119
127
  // if the app is not ready after a certain time
120
128
  startRollbackTimer();
@@ -159,6 +167,61 @@ public class LiveUpdate {
159
167
  }
160
168
  }
161
169
 
170
+ public void fetchChannels(@NonNull FetchChannelsOptions options, @NonNull NonEmptyCallback<FetchChannelsResult> callback) {
171
+ try {
172
+ HttpUrl.Builder urlBuilder = new HttpUrl.Builder()
173
+ .scheme("https")
174
+ .host(config.getServerDomain())
175
+ .addPathSegment("v1")
176
+ .addPathSegment("apps")
177
+ .addPathSegment(getAppId())
178
+ .addPathSegment("channels");
179
+ if (options.getLimit() != null) {
180
+ urlBuilder.addQueryParameter("limit", String.valueOf(options.getLimit()));
181
+ }
182
+ if (options.getOffset() != null) {
183
+ urlBuilder.addQueryParameter("offset", String.valueOf(options.getOffset()));
184
+ }
185
+ if (options.getQuery() != null) {
186
+ urlBuilder.addQueryParameter("query", options.getQuery());
187
+ }
188
+ String url = urlBuilder.build().toString();
189
+
190
+ httpClient.enqueue(
191
+ url,
192
+ new NonEmptyCallback<Response>() {
193
+ @Override
194
+ public void success(@NonNull Response response) {
195
+ try {
196
+ String responseBodyString = response.body().string();
197
+ if (response.isSuccessful()) {
198
+ JSONArray responseJsonArray = new JSONArray(responseBodyString);
199
+ ChannelResult[] channels = new ChannelResult[responseJsonArray.length()];
200
+ for (int i = 0; i < responseJsonArray.length(); i++) {
201
+ GetChannelsResponseItem item = new GetChannelsResponseItem(responseJsonArray.getJSONObject(i));
202
+ channels[i] = new ChannelResult(item.getId(), item.getName());
203
+ }
204
+ FetchChannelsResult result = new FetchChannelsResult(channels);
205
+ callback.success(result);
206
+ } else {
207
+ callback.error(new Exception(responseBodyString));
208
+ }
209
+ } catch (Exception e) {
210
+ callback.error(e);
211
+ }
212
+ }
213
+
214
+ @Override
215
+ public void error(@NonNull Exception exception) {
216
+ callback.error(exception);
217
+ }
218
+ }
219
+ );
220
+ } catch (Exception e) {
221
+ callback.error(e);
222
+ }
223
+ }
224
+
162
225
  public void fetchLatestBundle(@NonNull FetchLatestBundleOptions options, @NonNull NonEmptyCallback<FetchLatestBundleResult> callback) {
163
226
  fetchLatestBundleInternal(
164
227
  options,
@@ -602,8 +665,11 @@ public class LiveUpdate {
602
665
 
603
666
  private void deleteFileRecursively(@NonNull File file) {
604
667
  if (file.isDirectory()) {
605
- for (File child : file.listFiles()) {
606
- deleteFileRecursively(child);
668
+ File[] children = file.listFiles();
669
+ if (children != null) {
670
+ for (File child : children) {
671
+ deleteFileRecursively(child);
672
+ }
607
673
  }
608
674
  }
609
675
  file.delete();
@@ -1012,6 +1078,10 @@ public class LiveUpdate {
1012
1078
  @Nullable
1013
1079
  private String getChannel() {
1014
1080
  String channel = null;
1081
+ String nativeChannel = getNativeChannel();
1082
+ if (nativeChannel != null) {
1083
+ channel = nativeChannel;
1084
+ }
1015
1085
  if (config.getDefaultChannel() != null) {
1016
1086
  channel = config.getDefaultChannel();
1017
1087
  }
@@ -1021,6 +1091,18 @@ public class LiveUpdate {
1021
1091
  return channel;
1022
1092
  }
1023
1093
 
1094
+ @Nullable
1095
+ private String getNativeChannel() {
1096
+ int resId = plugin
1097
+ .getContext()
1098
+ .getResources()
1099
+ .getIdentifier("capawesome_live_update_default_channel", "string", plugin.getContext().getPackageName());
1100
+ if (resId == 0) {
1101
+ return null;
1102
+ }
1103
+ return plugin.getContext().getResources().getString(resId);
1104
+ }
1105
+
1024
1106
  /**
1025
1107
  * @return The current bundle ID or `null` if the default bundle is in use.
1026
1108
  */
@@ -1352,7 +1434,13 @@ public class LiveUpdate {
1352
1434
  private File unzipFile(@NonNull File zipFile) throws IOException {
1353
1435
  File destination = buildTemporaryDirectory();
1354
1436
  String destinationPath = destination.getPath();
1355
- new ZipFile(zipFile).extractAll(destinationPath);
1437
+ ZipFile zip = new ZipFile(zipFile);
1438
+ // Clear stored Unix permissions to prevent EACCES errors on newer Android versions
1439
+ // where restrictive directory permissions from the zip block file creation.
1440
+ for (FileHeader fileHeader : zip.getFileHeaders()) {
1441
+ fileHeader.setExternalFileAttributes(null);
1442
+ }
1443
+ zip.extractAll(destinationPath);
1356
1444
  return destination;
1357
1445
  }
1358
1446
 
@@ -8,7 +8,6 @@ import io.capawesome.capacitorjs.plugins.liveupdate.interfaces.EmptyCallback;
8
8
  import io.capawesome.capacitorjs.plugins.liveupdate.interfaces.NonEmptyCallback;
9
9
  import java.io.File;
10
10
  import java.io.IOException;
11
- import java.security.Security;
12
11
  import java.util.concurrent.TimeUnit;
13
12
  import okhttp3.Call;
14
13
  import okhttp3.Callback;
@@ -20,13 +19,15 @@ import okio.Buffer;
20
19
  import okio.BufferedSink;
21
20
  import okio.BufferedSource;
22
21
  import okio.Okio;
23
- import org.conscrypt.Conscrypt;
24
22
 
25
23
  public class LiveUpdateHttpClient {
26
24
 
27
25
  @NonNull
28
26
  private final LiveUpdateConfig config;
29
27
 
28
+ @Nullable
29
+ private String deviceId;
30
+
30
31
  @NonNull
31
32
  private final OkHttpClient okHttpClient;
32
33
 
@@ -52,13 +53,6 @@ public class LiveUpdateHttpClient {
52
53
  this.config = config;
53
54
  int httpTimeout = config.getHttpTimeout();
54
55
 
55
- // Install Conscrypt as the preferred security provider for better SSL/TLS support on older Android versions
56
- try {
57
- Security.insertProviderAt(Conscrypt.newProvider(), 1);
58
- } catch (Exception e) {
59
- Logger.warn("LiveUpdateHttpClient", "Failed to install Conscrypt security provider: " + e.getMessage());
60
- }
61
-
62
56
  // Increase max requests per host to allow multiple parallel downloads from the same host
63
57
  okhttp3.Dispatcher dispatcher = new okhttp3.Dispatcher();
64
58
  dispatcher.setMaxRequestsPerHost(30);
@@ -71,8 +65,16 @@ public class LiveUpdateHttpClient {
71
65
  .build();
72
66
  }
73
67
 
68
+ public void setDeviceId(@NonNull String deviceId) {
69
+ this.deviceId = deviceId;
70
+ }
71
+
74
72
  public Call enqueue(String url, NonEmptyCallback<Response> callback) {
75
- 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-Device-Id", deviceId);
76
+ }
77
+ Request request = builder.build();
76
78
 
77
79
  Call call = okHttpClient.newCall(request);
78
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 = "7.4.0";
40
+ public static final String VERSION = "7.5.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
+ }