@capawesome/capacitor-live-update 7.4.0 → 7.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CapawesomeCapacitorLiveUpdate.podspec +1 -1
- package/Package.swift +2 -2
- package/README.md +96 -1
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdate.java +171 -58
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdateHttpClient.java +12 -1
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdatePlugin.java +32 -1
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/api/GetChannelsResponseItem.java +28 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/options/FetchChannelsOptions.java +38 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/results/ChannelResult.java +28 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/results/FetchChannelsResult.java +28 -0
- package/dist/docs.json +150 -2
- package/dist/esm/definitions.d.ts +83 -0
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.d.ts +2 -1
- package/dist/esm/web.js +3 -0
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +3 -0
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +3 -0
- package/dist/plugin.js.map +1 -1
- package/ios/Plugin/Classes/Options/FetchChannelsOptions.swift +26 -0
- package/ios/Plugin/Classes/Results/ChannelResult.swift +19 -0
- package/ios/Plugin/Classes/Results/FetchChannelsResult.swift +16 -0
- package/ios/Plugin/LiveUpdate.swift +80 -17
- package/ios/Plugin/LiveUpdateHttpClient.swift +11 -0
- package/ios/Plugin/LiveUpdatePlugin.swift +21 -1
- package/ios/Plugin/Protocols/Api/GetChannelsResponseItem.swift +4 -0
- 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 '
|
|
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/
|
|
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: "
|
|
24
|
+
.product(name: "ZIPFoundation", package: "ZIPFoundation")
|
|
25
25
|
],
|
|
26
26
|
path: "ios/Plugin"),
|
|
27
27
|
.testTarget(
|
package/README.md
CHANGED
|
@@ -54,6 +54,21 @@ 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
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:
|
|
@@ -65,6 +80,18 @@ This can be useful if you encounter dependency conflicts with other plugins in y
|
|
|
65
80
|
|
|
66
81
|
### iOS
|
|
67
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
|
+
|
|
68
95
|
#### Privacy manifest
|
|
69
96
|
|
|
70
97
|
Add the `NSPrivacyAccessedAPICategoryUserDefaults` dictionary key to your [Privacy Manifest](https://capacitorjs.com/docs/ios/privacy-manifest) (usually `ios/App/PrivacyInfo.xcprivacy`):
|
|
@@ -103,7 +130,7 @@ We recommend to declare [`CA92.1`](https://developer.apple.com/documentation/bun
|
|
|
103
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 |
|
|
104
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 |
|
|
105
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 |
|
|
106
|
-
| **`defaultChannel`** | <code>string</code> | The default channel of the app.
|
|
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 |
|
|
107
134
|
| **`httpTimeout`** | <code>number</code> | The timeout in milliseconds for HTTP requests. | <code>60000</code> | 6.4.0 |
|
|
108
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 |
|
|
109
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 |
|
|
@@ -172,6 +199,11 @@ const downloadBundle = async () => {
|
|
|
172
199
|
await LiveUpdate.downloadBundle({ url: 'https://example.com/1.0.0.zip', bundleId: '1.0.0' });
|
|
173
200
|
};
|
|
174
201
|
|
|
202
|
+
const fetchChannels = async () => {
|
|
203
|
+
const result = await LiveUpdate.fetchChannels();
|
|
204
|
+
return result.channels;
|
|
205
|
+
};
|
|
206
|
+
|
|
175
207
|
const fetchLatestBundle = async () => {
|
|
176
208
|
await LiveUpdate.fetchLatestBundle();
|
|
177
209
|
};
|
|
@@ -276,6 +308,7 @@ const isNewBundleAvailable = async () => {
|
|
|
276
308
|
* [`clearBlockedBundles()`](#clearblockedbundles)
|
|
277
309
|
* [`deleteBundle(...)`](#deletebundle)
|
|
278
310
|
* [`downloadBundle(...)`](#downloadbundle)
|
|
311
|
+
* [`fetchChannels(...)`](#fetchchannels)
|
|
279
312
|
* [`fetchLatestBundle(...)`](#fetchlatestbundle)
|
|
280
313
|
* [`getBlockedBundles()`](#getblockedbundles)
|
|
281
314
|
* [`getBundles()`](#getbundles)
|
|
@@ -366,6 +399,35 @@ Only available on Android and iOS.
|
|
|
366
399
|
--------------------
|
|
367
400
|
|
|
368
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<<a href="#fetchchannelsresult">FetchChannelsResult</a>></code>
|
|
425
|
+
|
|
426
|
+
**Since:** 7.5.0
|
|
427
|
+
|
|
428
|
+
--------------------
|
|
429
|
+
|
|
430
|
+
|
|
369
431
|
### fetchLatestBundle(...)
|
|
370
432
|
|
|
371
433
|
```typescript
|
|
@@ -432,6 +494,15 @@ getChannel() => Promise<GetChannelResult>
|
|
|
432
494
|
|
|
433
495
|
Get the channel that is used for the update.
|
|
434
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
|
+
|
|
435
506
|
Only available on Android and iOS.
|
|
436
507
|
|
|
437
508
|
**Returns:** <code>Promise<<a href="#getchannelresult">GetChannelResult</a>></code>
|
|
@@ -894,6 +965,30 @@ Remove all listeners for this plugin.
|
|
|
894
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 |
|
|
895
966
|
|
|
896
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
|
+
|
|
897
992
|
#### FetchLatestBundleResult
|
|
898
993
|
|
|
899
994
|
| Prop | Type | Description | Since |
|
|
@@ -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;
|
|
@@ -97,6 +102,7 @@ public class LiveUpdate {
|
|
|
97
102
|
private final SharedPreferences.Editor webViewSettingsEditor;
|
|
98
103
|
|
|
99
104
|
private final String bundlesDirectory = "_capacitor_live_update_bundles"; // DO NOT CHANGE!
|
|
105
|
+
private final String downloadsDirectory = "capawesome_capacitor_live_update_downloads";
|
|
100
106
|
private final Handler rollbackHandler = new Handler();
|
|
101
107
|
private final String manifestFileName = "capawesome-live-update-manifest.json"; // DO NOT CHANGE!
|
|
102
108
|
|
|
@@ -115,6 +121,12 @@ public class LiveUpdate {
|
|
|
115
121
|
// Check version and reset config if version changed
|
|
116
122
|
checkAndResetConfigIfVersionChanged();
|
|
117
123
|
|
|
124
|
+
// Set the device ID on the HTTP client (after any potential config reset)
|
|
125
|
+
this.httpClient.setDeviceId(getDeviceId());
|
|
126
|
+
|
|
127
|
+
// Delete the leftovers of downloads that were interrupted by a process kill
|
|
128
|
+
tryDeleteDownloadsDirectory();
|
|
129
|
+
|
|
118
130
|
// Start the rollback timer to rollback to the default bundle
|
|
119
131
|
// if the app is not ready after a certain time
|
|
120
132
|
startRollbackTimer();
|
|
@@ -159,6 +171,61 @@ public class LiveUpdate {
|
|
|
159
171
|
}
|
|
160
172
|
}
|
|
161
173
|
|
|
174
|
+
public void fetchChannels(@NonNull FetchChannelsOptions options, @NonNull NonEmptyCallback<FetchChannelsResult> callback) {
|
|
175
|
+
try {
|
|
176
|
+
HttpUrl.Builder urlBuilder = new HttpUrl.Builder()
|
|
177
|
+
.scheme("https")
|
|
178
|
+
.host(config.getServerDomain())
|
|
179
|
+
.addPathSegment("v1")
|
|
180
|
+
.addPathSegment("apps")
|
|
181
|
+
.addPathSegment(getAppId())
|
|
182
|
+
.addPathSegment("channels");
|
|
183
|
+
if (options.getLimit() != null) {
|
|
184
|
+
urlBuilder.addQueryParameter("limit", String.valueOf(options.getLimit()));
|
|
185
|
+
}
|
|
186
|
+
if (options.getOffset() != null) {
|
|
187
|
+
urlBuilder.addQueryParameter("offset", String.valueOf(options.getOffset()));
|
|
188
|
+
}
|
|
189
|
+
if (options.getQuery() != null) {
|
|
190
|
+
urlBuilder.addQueryParameter("query", options.getQuery());
|
|
191
|
+
}
|
|
192
|
+
String url = urlBuilder.build().toString();
|
|
193
|
+
|
|
194
|
+
httpClient.enqueue(
|
|
195
|
+
url,
|
|
196
|
+
new NonEmptyCallback<Response>() {
|
|
197
|
+
@Override
|
|
198
|
+
public void success(@NonNull Response response) {
|
|
199
|
+
try {
|
|
200
|
+
String responseBodyString = response.body().string();
|
|
201
|
+
if (response.isSuccessful()) {
|
|
202
|
+
JSONArray responseJsonArray = new JSONArray(responseBodyString);
|
|
203
|
+
ChannelResult[] channels = new ChannelResult[responseJsonArray.length()];
|
|
204
|
+
for (int i = 0; i < responseJsonArray.length(); i++) {
|
|
205
|
+
GetChannelsResponseItem item = new GetChannelsResponseItem(responseJsonArray.getJSONObject(i));
|
|
206
|
+
channels[i] = new ChannelResult(item.getId(), item.getName());
|
|
207
|
+
}
|
|
208
|
+
FetchChannelsResult result = new FetchChannelsResult(channels);
|
|
209
|
+
callback.success(result);
|
|
210
|
+
} else {
|
|
211
|
+
callback.error(new Exception(responseBodyString));
|
|
212
|
+
}
|
|
213
|
+
} catch (Exception e) {
|
|
214
|
+
callback.error(e);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
@Override
|
|
219
|
+
public void error(@NonNull Exception exception) {
|
|
220
|
+
callback.error(exception);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
);
|
|
224
|
+
} catch (Exception e) {
|
|
225
|
+
callback.error(e);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
162
229
|
public void fetchLatestBundle(@NonNull FetchLatestBundleOptions options, @NonNull NonEmptyCallback<FetchLatestBundleResult> callback) {
|
|
163
230
|
fetchLatestBundleInternal(
|
|
164
231
|
options,
|
|
@@ -467,7 +534,10 @@ public class LiveUpdate {
|
|
|
467
534
|
|
|
468
535
|
// Move the bundle directory to the bundles directory
|
|
469
536
|
File bundleDirectory = buildBundleDirectoryFor(bundleId);
|
|
470
|
-
indexHtmlFile.getParentFile().renameTo(bundleDirectory);
|
|
537
|
+
boolean moved = indexHtmlFile.getParentFile().renameTo(bundleDirectory);
|
|
538
|
+
if (!moved) {
|
|
539
|
+
throw new Exception(LiveUpdatePlugin.ERROR_BUNDLE_MOVE_FAILED);
|
|
540
|
+
}
|
|
471
541
|
}
|
|
472
542
|
|
|
473
543
|
private void addBundleOfTypeManifest(@NonNull String bundleId, @NonNull File directory) throws Exception {
|
|
@@ -476,7 +546,7 @@ public class LiveUpdate {
|
|
|
476
546
|
|
|
477
547
|
private void addBundleOfTypeZip(@NonNull String bundleId, @NonNull File zipFile) throws Exception {
|
|
478
548
|
// Unzip the file to the bundle directory
|
|
479
|
-
File unzippedDirectory =
|
|
549
|
+
File unzippedDirectory = unzipDownloadedBundle(zipFile);
|
|
480
550
|
// Add the bundle
|
|
481
551
|
addBundle(bundleId, unzippedDirectory);
|
|
482
552
|
}
|
|
@@ -489,14 +559,8 @@ public class LiveUpdate {
|
|
|
489
559
|
return new File(plugin.getContext().getFilesDir(), bundlesDirectory + "/" + bundleId);
|
|
490
560
|
}
|
|
491
561
|
|
|
492
|
-
private File
|
|
493
|
-
|
|
494
|
-
return new File(plugin.getContext().getCacheDir(), fileName);
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
private File buildTemporaryZipFile() {
|
|
498
|
-
String fileName = UUID.randomUUID().toString() + ".zip";
|
|
499
|
-
return new File(plugin.getContext().getCacheDir(), fileName);
|
|
562
|
+
private File buildDownloadsDirectory() {
|
|
563
|
+
return new File(plugin.getContext().getCacheDir(), downloadsDirectory);
|
|
500
564
|
}
|
|
501
565
|
|
|
502
566
|
private void copyCurrentBundleFile(@NonNull ManifestItem fileToCopy, @NonNull File destinationDirectory) throws IOException {
|
|
@@ -583,10 +647,10 @@ public class LiveUpdate {
|
|
|
583
647
|
}
|
|
584
648
|
}
|
|
585
649
|
|
|
586
|
-
private File
|
|
587
|
-
File
|
|
588
|
-
|
|
589
|
-
return
|
|
650
|
+
private File createTemporaryDownloadDirectory() {
|
|
651
|
+
File temporaryDirectory = new File(buildDownloadsDirectory(), UUID.randomUUID().toString());
|
|
652
|
+
temporaryDirectory.mkdirs();
|
|
653
|
+
return temporaryDirectory;
|
|
590
654
|
}
|
|
591
655
|
|
|
592
656
|
private void deleteBundleById(@NonNull String bundleId) {
|
|
@@ -602,13 +666,33 @@ public class LiveUpdate {
|
|
|
602
666
|
|
|
603
667
|
private void deleteFileRecursively(@NonNull File file) {
|
|
604
668
|
if (file.isDirectory()) {
|
|
605
|
-
|
|
606
|
-
|
|
669
|
+
File[] children = file.listFiles();
|
|
670
|
+
if (children != null) {
|
|
671
|
+
for (File child : children) {
|
|
672
|
+
deleteFileRecursively(child);
|
|
673
|
+
}
|
|
607
674
|
}
|
|
608
675
|
}
|
|
609
676
|
file.delete();
|
|
610
677
|
}
|
|
611
678
|
|
|
679
|
+
@NonNull
|
|
680
|
+
private EmptyCallback deleteTemporaryDirectoryOnCompletion(@NonNull File temporaryDirectory, @NonNull EmptyCallback callback) {
|
|
681
|
+
return new EmptyCallback() {
|
|
682
|
+
@Override
|
|
683
|
+
public void success() {
|
|
684
|
+
deleteFileRecursively(temporaryDirectory);
|
|
685
|
+
callback.success();
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
@Override
|
|
689
|
+
public void error(@NonNull Exception exception) {
|
|
690
|
+
deleteFileRecursively(temporaryDirectory);
|
|
691
|
+
callback.error(exception);
|
|
692
|
+
}
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
|
|
612
696
|
private void deleteUnusedBundles() {
|
|
613
697
|
String[] bundleIds = getDownloadedBundleIds();
|
|
614
698
|
for (String bundleId : bundleIds) {
|
|
@@ -779,10 +863,10 @@ public class LiveUpdate {
|
|
|
779
863
|
@NonNull String downloadUrl,
|
|
780
864
|
@NonNull EmptyCallback completionCallback
|
|
781
865
|
) {
|
|
866
|
+
// Create a temporary directory that is deleted once the download completed or failed
|
|
867
|
+
File temporaryDirectory = createTemporaryDownloadDirectory();
|
|
868
|
+
EmptyCallback callback = deleteTemporaryDirectoryOnCompletion(temporaryDirectory, completionCallback);
|
|
782
869
|
try {
|
|
783
|
-
// Create a temporary directory
|
|
784
|
-
File temporaryDirectory = createTemporaryDirectory();
|
|
785
|
-
|
|
786
870
|
// Download the latest manifest
|
|
787
871
|
downloadBundleFile(
|
|
788
872
|
downloadUrl,
|
|
@@ -832,31 +916,31 @@ public class LiveUpdate {
|
|
|
832
916
|
try {
|
|
833
917
|
// Add the bundle
|
|
834
918
|
addBundleOfTypeManifest(bundleId, temporaryDirectory);
|
|
835
|
-
|
|
919
|
+
callback.success();
|
|
836
920
|
} catch (Exception e) {
|
|
837
|
-
|
|
921
|
+
callback.error(e);
|
|
838
922
|
}
|
|
839
923
|
}
|
|
840
924
|
|
|
841
925
|
@Override
|
|
842
926
|
public void error(@NonNull Exception exception) {
|
|
843
|
-
|
|
927
|
+
callback.error(exception);
|
|
844
928
|
}
|
|
845
929
|
}
|
|
846
930
|
);
|
|
847
931
|
} catch (Exception e) {
|
|
848
|
-
|
|
932
|
+
callback.error(e);
|
|
849
933
|
}
|
|
850
934
|
}
|
|
851
935
|
|
|
852
936
|
@Override
|
|
853
937
|
public void error(@NonNull Exception exception) {
|
|
854
|
-
|
|
938
|
+
callback.error(exception);
|
|
855
939
|
}
|
|
856
940
|
}
|
|
857
941
|
);
|
|
858
942
|
} catch (Exception e) {
|
|
859
|
-
|
|
943
|
+
callback.error(e);
|
|
860
944
|
}
|
|
861
945
|
}
|
|
862
946
|
|
|
@@ -867,39 +951,42 @@ public class LiveUpdate {
|
|
|
867
951
|
@NonNull String downloadUrl,
|
|
868
952
|
@NonNull EmptyCallback completionCallback
|
|
869
953
|
) {
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
954
|
+
// Create a temporary directory that is deleted once the download completed or failed
|
|
955
|
+
File temporaryDirectory = createTemporaryDownloadDirectory();
|
|
956
|
+
EmptyCallback callback = deleteTemporaryDirectoryOnCompletion(temporaryDirectory, completionCallback);
|
|
957
|
+
File zipFile = new File(temporaryDirectory, "bundle.zip");
|
|
958
|
+
try {
|
|
959
|
+
// Download the bundle
|
|
960
|
+
downloadAndVerifyFile(
|
|
961
|
+
downloadUrl,
|
|
962
|
+
zipFile,
|
|
963
|
+
checksum,
|
|
964
|
+
signature,
|
|
965
|
+
(downloadedBytes, totalBytes) -> {
|
|
966
|
+
DownloadBundleProgressEvent event = new DownloadBundleProgressEvent(bundleId, downloadedBytes, totalBytes);
|
|
967
|
+
notifyDownloadBundleProgressListeners(event);
|
|
968
|
+
},
|
|
969
|
+
new EmptyCallback() {
|
|
970
|
+
@Override
|
|
971
|
+
public void success() {
|
|
972
|
+
try {
|
|
973
|
+
// Add the bundle
|
|
974
|
+
addBundleOfTypeZip(bundleId, zipFile);
|
|
975
|
+
callback.success();
|
|
976
|
+
} catch (Exception e) {
|
|
977
|
+
callback.error(e);
|
|
978
|
+
}
|
|
892
979
|
}
|
|
893
|
-
}
|
|
894
980
|
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
completionCallback.error(exception);
|
|
981
|
+
@Override
|
|
982
|
+
public void error(@NonNull Exception exception) {
|
|
983
|
+
callback.error(exception);
|
|
984
|
+
}
|
|
900
985
|
}
|
|
901
|
-
|
|
902
|
-
)
|
|
986
|
+
);
|
|
987
|
+
} catch (Exception e) {
|
|
988
|
+
callback.error(e);
|
|
989
|
+
}
|
|
903
990
|
}
|
|
904
991
|
|
|
905
992
|
private void fetchLatestBundleInternal(
|
|
@@ -1012,6 +1099,10 @@ public class LiveUpdate {
|
|
|
1012
1099
|
@Nullable
|
|
1013
1100
|
private String getChannel() {
|
|
1014
1101
|
String channel = null;
|
|
1102
|
+
String nativeChannel = getNativeChannel();
|
|
1103
|
+
if (nativeChannel != null) {
|
|
1104
|
+
channel = nativeChannel;
|
|
1105
|
+
}
|
|
1015
1106
|
if (config.getDefaultChannel() != null) {
|
|
1016
1107
|
channel = config.getDefaultChannel();
|
|
1017
1108
|
}
|
|
@@ -1021,6 +1112,18 @@ public class LiveUpdate {
|
|
|
1021
1112
|
return channel;
|
|
1022
1113
|
}
|
|
1023
1114
|
|
|
1115
|
+
@Nullable
|
|
1116
|
+
private String getNativeChannel() {
|
|
1117
|
+
int resId = plugin
|
|
1118
|
+
.getContext()
|
|
1119
|
+
.getResources()
|
|
1120
|
+
.getIdentifier("capawesome_live_update_default_channel", "string", plugin.getContext().getPackageName());
|
|
1121
|
+
if (resId == 0) {
|
|
1122
|
+
return null;
|
|
1123
|
+
}
|
|
1124
|
+
return plugin.getContext().getResources().getString(resId);
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1024
1127
|
/**
|
|
1025
1128
|
* @return The current bundle ID or `null` if the default bundle is in use.
|
|
1026
1129
|
*/
|
|
@@ -1349,10 +1452,20 @@ public class LiveUpdate {
|
|
|
1349
1452
|
}
|
|
1350
1453
|
}
|
|
1351
1454
|
|
|
1352
|
-
private
|
|
1353
|
-
|
|
1455
|
+
private void tryDeleteDownloadsDirectory() {
|
|
1456
|
+
deleteFileRecursively(buildDownloadsDirectory());
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
private File unzipDownloadedBundle(@NonNull File zipFile) throws IOException {
|
|
1460
|
+
File destination = new File(zipFile.getParentFile(), "bundle");
|
|
1354
1461
|
String destinationPath = destination.getPath();
|
|
1355
|
-
new ZipFile(zipFile)
|
|
1462
|
+
ZipFile zip = new ZipFile(zipFile);
|
|
1463
|
+
// Clear stored Unix permissions to prevent EACCES errors on newer Android versions
|
|
1464
|
+
// where restrictive directory permissions from the zip block file creation.
|
|
1465
|
+
for (FileHeader fileHeader : zip.getFileHeaders()) {
|
|
1466
|
+
fileHeader.setExternalFileAttributes(null);
|
|
1467
|
+
}
|
|
1468
|
+
zip.extractAll(destinationPath);
|
|
1356
1469
|
return destination;
|
|
1357
1470
|
}
|
|
1358
1471
|
|
package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdateHttpClient.java
CHANGED
|
@@ -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
|
|
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();
|
|
67
78
|
|
|
68
79
|
Call call = okHttpClient.newCall(request);
|
|
69
80
|
call.enqueue(
|
package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdatePlugin.java
CHANGED
|
@@ -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,12 +37,13 @@ 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.
|
|
40
|
+
public static final String VERSION = "7.5.1";
|
|
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.";
|
|
42
44
|
public static final String ERROR_BUNDLE_ID_MISSING = "bundleId must be provided.";
|
|
43
45
|
public static final String ERROR_BUNDLE_INDEX_HTML_MISSING = "The bundle does not contain an index.html file.";
|
|
46
|
+
public static final String ERROR_BUNDLE_MOVE_FAILED = "Bundle could not be moved.";
|
|
44
47
|
public static final String ERROR_BUNDLE_NOT_FOUND = "bundle not found.";
|
|
45
48
|
public static final String ERROR_CHECKSUM_CALCULATION_FAILED = "Failed to calculate checksum.";
|
|
46
49
|
public static final String ERROR_CHECKSUM_MISMATCH = "Checksum mismatch.";
|
|
@@ -178,6 +181,34 @@ public class LiveUpdatePlugin extends Plugin {
|
|
|
178
181
|
}
|
|
179
182
|
}
|
|
180
183
|
|
|
184
|
+
@PluginMethod
|
|
185
|
+
public void fetchChannels(PluginCall call) {
|
|
186
|
+
try {
|
|
187
|
+
String appId = config.getAppId();
|
|
188
|
+
if (appId == null || appId.isEmpty()) {
|
|
189
|
+
call.reject(ERROR_APP_ID_MISSING);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
FetchChannelsOptions options = new FetchChannelsOptions(call);
|
|
194
|
+
NonEmptyCallback<FetchChannelsResult> callback = new NonEmptyCallback<>() {
|
|
195
|
+
@Override
|
|
196
|
+
public void success(FetchChannelsResult result) {
|
|
197
|
+
resolveCall(call, result.toJSObject());
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
@Override
|
|
201
|
+
public void error(Exception exception) {
|
|
202
|
+
rejectCall(call, exception);
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
implementation.fetchChannels(options, callback);
|
|
207
|
+
} catch (Exception exception) {
|
|
208
|
+
rejectCall(call, exception);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
181
212
|
@PluginMethod
|
|
182
213
|
public void fetchLatestBundle(PluginCall call) {
|
|
183
214
|
try {
|