@capawesome/capacitor-live-update 6.7.2 → 7.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CapawesomeCapacitorLiveUpdate.podspec +2 -2
- package/Package.swift +32 -0
- package/README.md +102 -88
- package/android/build.gradle +9 -9
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdate.java +192 -133
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdateConfig.java +6 -27
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdateHttpClient.java +7 -2
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdatePlugin.java +51 -77
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdatePreferences.java +12 -5
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/api/GetLatestBundleResponse.java +27 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/events/DownloadBundleProgressEvent.java +32 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/options/DownloadBundleOptions.java +16 -1
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/results/FetchLatestBundleResult.java +29 -1
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/results/ReadyResult.java +32 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/interfaces/DownloadProgressCallback.java +5 -0
- package/dist/docs.json +228 -147
- package/dist/esm/definitions.d.ts +140 -49
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/web.d.ts +2 -2
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +1 -3
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +1 -3
- package/dist/plugin.js.map +1 -1
- package/ios/Plugin/Classes/Events/DownloadBundleProgressEvent.swift +25 -0
- package/ios/Plugin/Classes/Options/DownloadBundleOptions.swift +7 -1
- package/ios/Plugin/Classes/Results/{FetchLatestBundle.swift → FetchLatestBundleResult.swift} +16 -1
- package/ios/Plugin/Classes/Results/ReadyResult.swift +22 -0
- package/ios/Plugin/LiveUpdate.swift +171 -154
- package/ios/Plugin/LiveUpdateConfig.swift +2 -4
- package/ios/Plugin/LiveUpdateHttpClient.swift +6 -2
- package/ios/Plugin/LiveUpdatePlugin.swift +45 -37
- package/ios/Plugin/LiveUpdatePreferences.swift +6 -20
- package/ios/Plugin/Protocols/Api/GetLatestBundleResponse.swift +3 -0
- package/package.json +17 -18
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/options/SetBundleOptions.java +0 -18
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/results/GetBundleResult.java +0 -24
- package/ios/Plugin/Classes/Options/SetBundleOptions.swift +0 -14
- package/ios/Plugin/Classes/Results/GetBundleResult.swift +0 -16
- package/ios/Plugin/LiveUpdatePlugin.h +0 -10
- package/ios/Plugin/LiveUpdatePlugin.m +0 -27
|
@@ -10,21 +10,21 @@ import android.os.Handler;
|
|
|
10
10
|
import android.util.Base64;
|
|
11
11
|
import androidx.annotation.NonNull;
|
|
12
12
|
import androidx.annotation.Nullable;
|
|
13
|
+
import com.getcapacitor.Bridge;
|
|
13
14
|
import com.getcapacitor.Logger;
|
|
14
15
|
import com.getcapacitor.plugin.WebView;
|
|
15
16
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.Manifest;
|
|
16
17
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.ManifestItem;
|
|
17
18
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.api.GetLatestBundleResponse;
|
|
19
|
+
import io.capawesome.capacitorjs.plugins.liveupdate.classes.events.DownloadBundleProgressEvent;
|
|
18
20
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.options.DeleteBundleOptions;
|
|
19
21
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.options.DownloadBundleOptions;
|
|
20
22
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.options.FetchLatestBundleOptions;
|
|
21
|
-
import io.capawesome.capacitorjs.plugins.liveupdate.classes.options.SetBundleOptions;
|
|
22
23
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.options.SetChannelOptions;
|
|
23
24
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.options.SetCustomIdOptions;
|
|
24
25
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.options.SetNextBundleOptions;
|
|
25
26
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.options.SyncOptions;
|
|
26
27
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.results.FetchLatestBundleResult;
|
|
27
|
-
import io.capawesome.capacitorjs.plugins.liveupdate.classes.results.GetBundleResult;
|
|
28
28
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.results.GetBundlesResult;
|
|
29
29
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.results.GetChannelResult;
|
|
30
30
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.results.GetCurrentBundleResult;
|
|
@@ -33,8 +33,10 @@ import io.capawesome.capacitorjs.plugins.liveupdate.classes.results.GetDeviceIdR
|
|
|
33
33
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.results.GetNextBundleResult;
|
|
34
34
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.results.GetVersionCodeResult;
|
|
35
35
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.results.GetVersionNameResult;
|
|
36
|
+
import io.capawesome.capacitorjs.plugins.liveupdate.classes.results.ReadyResult;
|
|
36
37
|
import io.capawesome.capacitorjs.plugins.liveupdate.classes.results.SyncResult;
|
|
37
38
|
import io.capawesome.capacitorjs.plugins.liveupdate.enums.ArtifactType;
|
|
39
|
+
import io.capawesome.capacitorjs.plugins.liveupdate.interfaces.DownloadProgressCallback;
|
|
38
40
|
import io.capawesome.capacitorjs.plugins.liveupdate.interfaces.EmptyCallback;
|
|
39
41
|
import io.capawesome.capacitorjs.plugins.liveupdate.interfaces.NonEmptyCallback;
|
|
40
42
|
import io.capawesome.capacitorjs.plugins.liveupdate.interfaces.Result;
|
|
@@ -68,10 +70,7 @@ public class LiveUpdate {
|
|
|
68
70
|
@NonNull
|
|
69
71
|
private final LiveUpdateConfig config;
|
|
70
72
|
|
|
71
|
-
private final String defaultWebAssetDir;
|
|
72
|
-
|
|
73
|
-
@NonNull
|
|
74
|
-
private final String host;
|
|
73
|
+
private final String defaultWebAssetDir = Bridge.DEFAULT_WEB_ASSET_DIR;
|
|
75
74
|
|
|
76
75
|
@NonNull
|
|
77
76
|
private final LiveUpdateHttpClient httpClient;
|
|
@@ -89,39 +88,29 @@ public class LiveUpdate {
|
|
|
89
88
|
private final Handler rollbackHandler = new Handler();
|
|
90
89
|
private final String manifestFileName = "capawesome-live-update-manifest.json"; // DO NOT CHANGE!
|
|
91
90
|
|
|
91
|
+
private boolean rollbackPerformed = false;
|
|
92
|
+
|
|
92
93
|
public LiveUpdate(@NonNull LiveUpdateConfig config, @NonNull LiveUpdatePlugin plugin) throws PackageManager.NameNotFoundException {
|
|
93
94
|
this.config = config;
|
|
94
|
-
this.defaultWebAssetDir = plugin.getBridge().DEFAULT_WEB_ASSET_DIR;
|
|
95
|
-
if (config.getLocation() != null && config.getLocation().equals("eu")) {
|
|
96
|
-
this.host = "api.cloud.capawesome.eu";
|
|
97
|
-
} else {
|
|
98
|
-
this.host = "api.cloud.capawesome.io";
|
|
99
|
-
}
|
|
100
95
|
this.httpClient = new LiveUpdateHttpClient(config);
|
|
101
96
|
this.plugin = plugin;
|
|
102
97
|
this.preferences = new LiveUpdatePreferences(plugin.getContext());
|
|
103
98
|
this.webViewSettingsEditor = plugin.getContext().getSharedPreferences(WebView.WEBVIEW_PREFS_NAME, Activity.MODE_PRIVATE).edit();
|
|
104
99
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
reset();
|
|
109
|
-
} else {
|
|
110
|
-
startRollbackTimer();
|
|
111
|
-
}
|
|
112
|
-
saveCurrentVersionCode();
|
|
113
|
-
}
|
|
100
|
+
// Start the rollback timer to rollback to the default bundle
|
|
101
|
+
// if the app is not ready after a certain time
|
|
102
|
+
startRollbackTimer();
|
|
114
103
|
}
|
|
115
104
|
|
|
116
105
|
public void deleteBundle(@NonNull DeleteBundleOptions options, @NonNull EmptyCallback callback) {
|
|
117
106
|
String bundleId = options.getBundleId();
|
|
118
107
|
|
|
119
|
-
if (!
|
|
108
|
+
if (!hasBundleById(bundleId)) {
|
|
120
109
|
Exception exception = new Exception(LiveUpdatePlugin.ERROR_BUNDLE_NOT_FOUND);
|
|
121
110
|
callback.error(exception);
|
|
122
111
|
return;
|
|
123
112
|
}
|
|
124
|
-
|
|
113
|
+
deleteBundleById(bundleId);
|
|
125
114
|
|
|
126
115
|
callback.success();
|
|
127
116
|
}
|
|
@@ -130,10 +119,11 @@ public class LiveUpdate {
|
|
|
130
119
|
ArtifactType artifactType = options.getArtifactType();
|
|
131
120
|
String bundleId = options.getBundleId();
|
|
132
121
|
String checksum = options.getChecksum();
|
|
122
|
+
String signature = options.getSignature();
|
|
133
123
|
String url = options.getUrl();
|
|
134
124
|
|
|
135
125
|
// Check if the bundle already exists
|
|
136
|
-
if (
|
|
126
|
+
if (hasBundleById(bundleId)) {
|
|
137
127
|
Exception exception = new Exception(LiveUpdatePlugin.ERROR_BUNDLE_EXISTS);
|
|
138
128
|
callback.error(exception);
|
|
139
129
|
return;
|
|
@@ -143,7 +133,7 @@ public class LiveUpdate {
|
|
|
143
133
|
if (artifactType == ArtifactType.MANIFEST) {
|
|
144
134
|
downloadBundleOfTypeManifest(bundleId, url);
|
|
145
135
|
} else {
|
|
146
|
-
downloadBundleOfTypeZip(bundleId,
|
|
136
|
+
downloadBundleOfTypeZip(bundleId, checksum, signature, url);
|
|
147
137
|
}
|
|
148
138
|
callback.success();
|
|
149
139
|
}
|
|
@@ -152,17 +142,18 @@ public class LiveUpdate {
|
|
|
152
142
|
GetLatestBundleResponse response = fetchLatestBundle(options);
|
|
153
143
|
ArtifactType artifactType = response == null ? null : response.getArtifactType();
|
|
154
144
|
String bundleId = response == null ? null : response.getBundleId();
|
|
145
|
+
String checksum = response == null ? null : response.getChecksum();
|
|
146
|
+
JSONObject customProperties = response == null ? null : response.getCustomProperties();
|
|
155
147
|
String downloadUrl = response == null ? null : response.getUrl();
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
GetBundleResult result = new GetBundleResult(bundleId);
|
|
148
|
+
String signature = response == null ? null : response.getSignature();
|
|
149
|
+
FetchLatestBundleResult result = new FetchLatestBundleResult(
|
|
150
|
+
artifactType,
|
|
151
|
+
bundleId,
|
|
152
|
+
checksum,
|
|
153
|
+
customProperties,
|
|
154
|
+
downloadUrl,
|
|
155
|
+
signature
|
|
156
|
+
);
|
|
166
157
|
callback.success(result);
|
|
167
158
|
}
|
|
168
159
|
|
|
@@ -180,9 +171,6 @@ public class LiveUpdate {
|
|
|
180
171
|
|
|
181
172
|
public void getCurrentBundle(@NonNull NonEmptyCallback<GetCurrentBundleResult> callback) {
|
|
182
173
|
String bundleId = getCurrentBundleId();
|
|
183
|
-
if (bundleId.equals(defaultWebAssetDir)) {
|
|
184
|
-
bundleId = null;
|
|
185
|
-
}
|
|
186
174
|
GetCurrentBundleResult result = new GetCurrentBundleResult(bundleId);
|
|
187
175
|
callback.success(result);
|
|
188
176
|
}
|
|
@@ -201,9 +189,6 @@ public class LiveUpdate {
|
|
|
201
189
|
|
|
202
190
|
public void getNextBundle(@NonNull NonEmptyCallback<GetNextBundleResult> callback) {
|
|
203
191
|
String bundleId = getNextBundleId();
|
|
204
|
-
if (bundleId.equals(defaultWebAssetDir)) {
|
|
205
|
-
bundleId = null;
|
|
206
|
-
}
|
|
207
192
|
GetNextBundleResult result = new GetNextBundleResult(bundleId);
|
|
208
193
|
callback.success(result);
|
|
209
194
|
}
|
|
@@ -220,14 +205,27 @@ public class LiveUpdate {
|
|
|
220
205
|
callback.success(result);
|
|
221
206
|
}
|
|
222
207
|
|
|
223
|
-
public void ready() {
|
|
208
|
+
public void ready(@NonNull NonEmptyCallback callback) {
|
|
224
209
|
Logger.debug(LiveUpdatePlugin.TAG, "App is ready.");
|
|
210
|
+
if (config.getReadyTimeout() <= 0) {
|
|
211
|
+
Logger.warn(LiveUpdatePlugin.TAG, "Ready timeout is set to 0. Automatic rollback is disabled.");
|
|
212
|
+
}
|
|
225
213
|
// Stop the rollback timer
|
|
226
214
|
stopRollbackTimer();
|
|
227
215
|
// Delete unused bundles
|
|
228
216
|
if (config.getAutoDeleteBundles()) {
|
|
229
217
|
deleteUnusedBundles();
|
|
230
218
|
}
|
|
219
|
+
// Get the current and previous bundle IDs
|
|
220
|
+
String currentBundleId = getCurrentBundleId();
|
|
221
|
+
String previousBundleId = getPreviousBundleId();
|
|
222
|
+
// Return the result
|
|
223
|
+
ReadyResult result = new ReadyResult(currentBundleId, previousBundleId, rollbackPerformed);
|
|
224
|
+
callback.success(result);
|
|
225
|
+
// Set the new previous bundle ID
|
|
226
|
+
setPreviousBundleId(currentBundleId);
|
|
227
|
+
// Reset the rollback flag
|
|
228
|
+
rollbackPerformed = false;
|
|
231
229
|
}
|
|
232
230
|
|
|
233
231
|
public void reload() {
|
|
@@ -237,20 +235,7 @@ public class LiveUpdate {
|
|
|
237
235
|
}
|
|
238
236
|
|
|
239
237
|
public void reset() {
|
|
240
|
-
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
public void setBundle(@NonNull SetBundleOptions options, @NonNull EmptyCallback callback) {
|
|
244
|
-
String bundleId = options.getBundleId();
|
|
245
|
-
|
|
246
|
-
if (!hasBundle(bundleId)) {
|
|
247
|
-
Exception exception = new Exception(LiveUpdatePlugin.ERROR_BUNDLE_NOT_FOUND);
|
|
248
|
-
callback.error(exception);
|
|
249
|
-
return;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
setNextBundle(bundleId);
|
|
253
|
-
callback.success();
|
|
238
|
+
setNextBundleById(null);
|
|
254
239
|
}
|
|
255
240
|
|
|
256
241
|
public void setChannel(@NonNull SetChannelOptions options, @NonNull EmptyCallback callback) {
|
|
@@ -273,8 +258,8 @@ public class LiveUpdate {
|
|
|
273
258
|
if (bundleId == null) {
|
|
274
259
|
reset();
|
|
275
260
|
} else {
|
|
276
|
-
if (
|
|
277
|
-
|
|
261
|
+
if (hasBundleById(bundleId)) {
|
|
262
|
+
setNextBundleById(bundleId);
|
|
278
263
|
} else {
|
|
279
264
|
Exception exception = new Exception(LiveUpdatePlugin.ERROR_BUNDLE_NOT_FOUND);
|
|
280
265
|
callback.error(exception);
|
|
@@ -297,14 +282,16 @@ public class LiveUpdate {
|
|
|
297
282
|
}
|
|
298
283
|
ArtifactType artifactType = response.getArtifactType();
|
|
299
284
|
String latestBundleId = response.getBundleId();
|
|
285
|
+
String checksum = response.getChecksum();
|
|
286
|
+
String signature = response.getSignature();
|
|
300
287
|
String url = response.getUrl();
|
|
301
288
|
// Check if the bundle already exists
|
|
302
|
-
if (
|
|
289
|
+
if (hasBundleById(latestBundleId)) {
|
|
303
290
|
String nextBundleId = null;
|
|
304
291
|
String currentBundleId = getCurrentBundleId();
|
|
305
292
|
if (!latestBundleId.equals(currentBundleId)) {
|
|
306
293
|
// Set the next bundle
|
|
307
|
-
|
|
294
|
+
setNextBundleById(latestBundleId);
|
|
308
295
|
nextBundleId = latestBundleId;
|
|
309
296
|
}
|
|
310
297
|
SyncResult syncResult = new SyncResult(nextBundleId);
|
|
@@ -315,10 +302,10 @@ public class LiveUpdate {
|
|
|
315
302
|
if (artifactType == ArtifactType.MANIFEST) {
|
|
316
303
|
downloadBundleOfTypeManifest(latestBundleId, url);
|
|
317
304
|
} else {
|
|
318
|
-
downloadBundleOfTypeZip(latestBundleId,
|
|
305
|
+
downloadBundleOfTypeZip(latestBundleId, checksum, signature, url);
|
|
319
306
|
}
|
|
320
307
|
// Set the next bundle
|
|
321
|
-
|
|
308
|
+
setNextBundleById(latestBundleId);
|
|
322
309
|
SyncResult syncResult = new SyncResult(latestBundleId);
|
|
323
310
|
callback.success(syncResult);
|
|
324
311
|
}
|
|
@@ -367,9 +354,10 @@ public class LiveUpdate {
|
|
|
367
354
|
return new File(plugin.getContext().getCacheDir(), fileName);
|
|
368
355
|
}
|
|
369
356
|
|
|
370
|
-
private void copyCurrentBundleFile(@NonNull
|
|
357
|
+
private void copyCurrentBundleFile(@NonNull ManifestItem fileToCopy, @NonNull File destinationDirectory) throws IOException {
|
|
358
|
+
String href = fileToCopy.getHref();
|
|
371
359
|
String currentBundleId = getCurrentBundleId();
|
|
372
|
-
if (currentBundleId
|
|
360
|
+
if (currentBundleId == null) {
|
|
373
361
|
// Create the source input stream
|
|
374
362
|
AssetManager assets = plugin.getContext().getAssets();
|
|
375
363
|
InputStream inputStream = assets.open(defaultWebAssetDir + "/" + href);
|
|
@@ -391,9 +379,9 @@ public class LiveUpdate {
|
|
|
391
379
|
}
|
|
392
380
|
}
|
|
393
381
|
|
|
394
|
-
private void copyCurrentBundleFiles(@NonNull List<
|
|
395
|
-
for (
|
|
396
|
-
copyCurrentBundleFile(
|
|
382
|
+
private void copyCurrentBundleFiles(@NonNull List<ManifestItem> filesToCopy, @NonNull File destinationDirectory) throws IOException {
|
|
383
|
+
for (ManifestItem fileToCopy : filesToCopy) {
|
|
384
|
+
copyCurrentBundleFile(fileToCopy, destinationDirectory);
|
|
397
385
|
}
|
|
398
386
|
}
|
|
399
387
|
|
|
@@ -446,14 +434,14 @@ public class LiveUpdate {
|
|
|
446
434
|
return file;
|
|
447
435
|
}
|
|
448
436
|
|
|
449
|
-
private void
|
|
437
|
+
private void deleteBundleById(@NonNull String bundleId) {
|
|
450
438
|
// Delete the bundle directory
|
|
451
439
|
File bundleDirectory = buildBundleDirectoryFor(bundleId);
|
|
452
440
|
deleteFileRecursively(bundleDirectory);
|
|
453
441
|
// Reset the next bundle if it is the deleted bundle
|
|
454
442
|
String nextBundleId = getNextBundleId();
|
|
455
443
|
if (bundleId.equals(nextBundleId)) {
|
|
456
|
-
|
|
444
|
+
setNextBundleById(null);
|
|
457
445
|
}
|
|
458
446
|
}
|
|
459
447
|
|
|
@@ -470,19 +458,29 @@ public class LiveUpdate {
|
|
|
470
458
|
String[] bundleIds = getBundleIds();
|
|
471
459
|
for (String bundleId : bundleIds) {
|
|
472
460
|
if (!isBundleInUse(bundleId)) {
|
|
473
|
-
|
|
461
|
+
deleteBundleById(bundleId);
|
|
474
462
|
}
|
|
475
463
|
}
|
|
476
464
|
}
|
|
477
465
|
|
|
478
|
-
private void downloadAndVerifyFile(
|
|
466
|
+
private void downloadAndVerifyFile(
|
|
467
|
+
@NonNull String url,
|
|
468
|
+
@NonNull File file,
|
|
469
|
+
@Nullable String checksum,
|
|
470
|
+
@Nullable String signature,
|
|
471
|
+
@Nullable DownloadProgressCallback callback
|
|
472
|
+
) throws Exception {
|
|
479
473
|
Response response = httpClient.execute(url);
|
|
480
474
|
ResponseBody responseBody = response.body();
|
|
481
475
|
if (response.isSuccessful()) {
|
|
482
|
-
LiveUpdateHttpClient.writeResponseBodyToFile(responseBody, file);
|
|
476
|
+
LiveUpdateHttpClient.writeResponseBodyToFile(responseBody, file, callback);
|
|
483
477
|
// Verify the file
|
|
484
|
-
|
|
485
|
-
|
|
478
|
+
if (checksum == null) {
|
|
479
|
+
checksum = LiveUpdateHttpClient.getChecksumFromResponse(response);
|
|
480
|
+
}
|
|
481
|
+
if (signature == null) {
|
|
482
|
+
signature = LiveUpdateHttpClient.getSignatureFromResponse(response);
|
|
483
|
+
}
|
|
486
484
|
verifyFile(file, checksum, signature);
|
|
487
485
|
} else {
|
|
488
486
|
Exception exception = new Exception(responseBody.string());
|
|
@@ -491,24 +489,39 @@ public class LiveUpdate {
|
|
|
491
489
|
}
|
|
492
490
|
}
|
|
493
491
|
|
|
494
|
-
private File downloadBundleFile(
|
|
492
|
+
private File downloadBundleFile(
|
|
493
|
+
@NonNull String baseUrl,
|
|
494
|
+
@NonNull String href,
|
|
495
|
+
@NonNull File destinationDirectory,
|
|
496
|
+
@Nullable DownloadProgressCallback callback
|
|
497
|
+
) throws Exception {
|
|
495
498
|
HttpUrl.Builder urlBuilder = HttpUrl.parse(baseUrl).newBuilder();
|
|
496
499
|
urlBuilder.addQueryParameter("href", href);
|
|
497
500
|
String url = urlBuilder.build().toString();
|
|
498
501
|
|
|
499
502
|
File destinationFile = new File(destinationDirectory, href);
|
|
500
503
|
destinationFile.getParentFile().mkdirs();
|
|
501
|
-
downloadAndVerifyFile(url, destinationFile, null);
|
|
504
|
+
downloadAndVerifyFile(url, destinationFile, null, null, callback);
|
|
502
505
|
return destinationFile;
|
|
503
506
|
}
|
|
504
507
|
|
|
505
|
-
private void downloadBundleFiles(
|
|
506
|
-
|
|
508
|
+
private void downloadBundleFiles(
|
|
509
|
+
@NonNull String baseUrl,
|
|
510
|
+
@NonNull List<ManifestItem> filesToDownload,
|
|
511
|
+
@NonNull File destinationDirectory,
|
|
512
|
+
@Nullable DownloadProgressCallback callback
|
|
513
|
+
) throws Exception {
|
|
514
|
+
// Track the total downloaded bytes and total bytes to download for the progress callback
|
|
515
|
+
final long[] totalBytesDownloaded = { 0 };
|
|
516
|
+
long totalBytesToDownload = 0;
|
|
517
|
+
for (ManifestItem fileToDownload : filesToDownload) {
|
|
518
|
+
totalBytesToDownload += fileToDownload.getSizeInBytes();
|
|
519
|
+
}
|
|
507
520
|
// Limit the number of threads to twice the number of processors
|
|
508
521
|
int maxThreads = Runtime.getRuntime().availableProcessors() * 2;
|
|
509
522
|
List<Thread> threads = new ArrayList<>();
|
|
510
523
|
AtomicReference<Exception> exceptionReference = new AtomicReference<>();
|
|
511
|
-
for (
|
|
524
|
+
for (ManifestItem fileToDownload : filesToDownload) {
|
|
512
525
|
// Wait until a thread is available
|
|
513
526
|
while (threads.size() >= maxThreads) {
|
|
514
527
|
for (Thread thread : threads) {
|
|
@@ -524,13 +537,23 @@ public class LiveUpdate {
|
|
|
524
537
|
if (exceptionReference.get() != null) {
|
|
525
538
|
break;
|
|
526
539
|
}
|
|
540
|
+
// Final variable that can be accessed from within inner class
|
|
541
|
+
long finalTotalBytesToDownload = totalBytesToDownload;
|
|
527
542
|
// Start a new thread
|
|
528
543
|
Thread thread = new Thread(() -> {
|
|
529
544
|
try {
|
|
530
|
-
downloadBundleFile(baseUrl,
|
|
545
|
+
downloadBundleFile(baseUrl, fileToDownload.getHref(), destinationDirectory, (downloadedBytes, totalBytes) -> {
|
|
546
|
+
if (callback != null) {
|
|
547
|
+
callback.onProgress(totalBytesDownloaded[0] + downloadedBytes, finalTotalBytesToDownload);
|
|
548
|
+
}
|
|
549
|
+
});
|
|
550
|
+
if (callback != null) {
|
|
551
|
+
totalBytesDownloaded[0] += fileToDownload.getSizeInBytes();
|
|
552
|
+
callback.onProgress(totalBytesDownloaded[0], finalTotalBytesToDownload);
|
|
553
|
+
}
|
|
531
554
|
} catch (Exception exception) {
|
|
532
555
|
exceptionReference.set(exception);
|
|
533
|
-
Logger.error(LiveUpdatePlugin.TAG, "Failed to download file: " +
|
|
556
|
+
Logger.error(LiveUpdatePlugin.TAG, "Failed to download file: " + fileToDownload.getHref(), exception);
|
|
534
557
|
}
|
|
535
558
|
});
|
|
536
559
|
threads.add(thread);
|
|
@@ -545,13 +568,17 @@ public class LiveUpdate {
|
|
|
545
568
|
if (exception != null) {
|
|
546
569
|
throw exception;
|
|
547
570
|
}
|
|
571
|
+
// Call the callback one last time to make sure the progress is at 100%
|
|
572
|
+
if (callback != null) {
|
|
573
|
+
callback.onProgress(totalBytesToDownload, totalBytesToDownload);
|
|
574
|
+
}
|
|
548
575
|
}
|
|
549
576
|
|
|
550
577
|
private void downloadBundleOfTypeManifest(@NonNull String bundleId, @NonNull String downloadUrl) throws Exception {
|
|
551
578
|
// Create a temporary directory
|
|
552
579
|
File temporaryDirectory = createTemporaryDirectory();
|
|
553
580
|
// Download the latest manifest
|
|
554
|
-
File latestManifestFile = downloadBundleFile(downloadUrl, manifestFileName, temporaryDirectory);
|
|
581
|
+
File latestManifestFile = downloadBundleFile(downloadUrl, manifestFileName, temporaryDirectory, null);
|
|
555
582
|
Manifest latestManifest = loadManifest(latestManifestFile);
|
|
556
583
|
// Load the current manifest
|
|
557
584
|
Manifest currentManifest = loadCurrentManifest();
|
|
@@ -565,26 +592,28 @@ public class LiveUpdate {
|
|
|
565
592
|
itemsToDownload.addAll(Manifest.findMissingItems(latestManifest, currentManifest));
|
|
566
593
|
}
|
|
567
594
|
// Copy the files
|
|
568
|
-
|
|
569
|
-
for (ManifestItem item : itemsToCopy) {
|
|
570
|
-
hrefsToCopy.add(item.getHref());
|
|
571
|
-
}
|
|
572
|
-
copyCurrentBundleFiles(hrefsToCopy, temporaryDirectory);
|
|
595
|
+
copyCurrentBundleFiles(itemsToCopy, temporaryDirectory);
|
|
573
596
|
// Download the files
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
}
|
|
578
|
-
downloadBundleFiles(downloadUrl, hrefsToDownload, temporaryDirectory);
|
|
597
|
+
downloadBundleFiles(downloadUrl, itemsToDownload, temporaryDirectory, (downloadedBytes, totalBytes) -> {
|
|
598
|
+
DownloadBundleProgressEvent event = new DownloadBundleProgressEvent(bundleId, downloadedBytes, totalBytes);
|
|
599
|
+
notifyDownloadBundleProgressListeners(event);
|
|
600
|
+
});
|
|
579
601
|
// Add the bundle
|
|
580
602
|
addBundleOfTypeManifest(bundleId, temporaryDirectory);
|
|
581
603
|
}
|
|
582
604
|
|
|
583
|
-
private void downloadBundleOfTypeZip(
|
|
584
|
-
|
|
605
|
+
private void downloadBundleOfTypeZip(
|
|
606
|
+
@NonNull String bundleId,
|
|
607
|
+
@Nullable String checksum,
|
|
608
|
+
@Nullable String signature,
|
|
609
|
+
@NonNull String downloadUrl
|
|
610
|
+
) throws Exception {
|
|
585
611
|
File file = buildTemporaryZipFile();
|
|
586
612
|
// Download the bundle
|
|
587
|
-
downloadAndVerifyFile(downloadUrl, file, checksum)
|
|
613
|
+
downloadAndVerifyFile(downloadUrl, file, checksum, signature, (downloadedBytes, totalBytes) -> {
|
|
614
|
+
DownloadBundleProgressEvent event = new DownloadBundleProgressEvent(bundleId, downloadedBytes, totalBytes);
|
|
615
|
+
notifyDownloadBundleProgressListeners(event);
|
|
616
|
+
});
|
|
588
617
|
// Add the bundle
|
|
589
618
|
addBundleOfTypeZip(bundleId, file);
|
|
590
619
|
// Delete the temporary file
|
|
@@ -596,7 +625,7 @@ public class LiveUpdate {
|
|
|
596
625
|
String channel = options.getChannel() == null ? getChannel() : options.getChannel();
|
|
597
626
|
String url = new HttpUrl.Builder()
|
|
598
627
|
.scheme("https")
|
|
599
|
-
.host(
|
|
628
|
+
.host(config.getServerDomain())
|
|
600
629
|
.addPathSegment("v1")
|
|
601
630
|
.addPathSegment("apps")
|
|
602
631
|
.addPathSegment(config.getAppId())
|
|
@@ -678,12 +707,13 @@ public class LiveUpdate {
|
|
|
678
707
|
}
|
|
679
708
|
|
|
680
709
|
/**
|
|
681
|
-
* @return The current bundle ID
|
|
710
|
+
* @return The current bundle ID or `null` if the default bundle is in use.
|
|
682
711
|
*/
|
|
712
|
+
@Nullable
|
|
683
713
|
private String getCurrentBundleId() {
|
|
684
714
|
String currentPath = getCurrentCapacitorServerPath();
|
|
685
715
|
if (currentPath.equals(defaultWebAssetDir)) {
|
|
686
|
-
return
|
|
716
|
+
return null;
|
|
687
717
|
}
|
|
688
718
|
return new File(currentPath).getName();
|
|
689
719
|
}
|
|
@@ -706,13 +736,13 @@ public class LiveUpdate {
|
|
|
706
736
|
}
|
|
707
737
|
|
|
708
738
|
/**
|
|
709
|
-
* @return The next bundle ID
|
|
739
|
+
* @return The next bundle ID or `null` if the default bundle will be used.
|
|
710
740
|
*/
|
|
711
|
-
@
|
|
741
|
+
@Nullable
|
|
712
742
|
private String getNextBundleId() {
|
|
713
743
|
String nextPath = getNextCapacitorServerPath();
|
|
714
744
|
if (nextPath.equals(defaultWebAssetDir)) {
|
|
715
|
-
return
|
|
745
|
+
return null;
|
|
716
746
|
}
|
|
717
747
|
return new File(nextPath).getName();
|
|
718
748
|
}
|
|
@@ -728,6 +758,14 @@ public class LiveUpdate {
|
|
|
728
758
|
.getString(WebView.CAP_SERVER_PATH, defaultWebAssetDir);
|
|
729
759
|
}
|
|
730
760
|
|
|
761
|
+
/**
|
|
762
|
+
* @return The previous bundle ID or `null` if the default bundle was used.
|
|
763
|
+
*/
|
|
764
|
+
@Nullable
|
|
765
|
+
private String getPreviousBundleId() {
|
|
766
|
+
return preferences.getPreviousBundleId();
|
|
767
|
+
}
|
|
768
|
+
|
|
731
769
|
private String getVersionCode() throws PackageManager.NameNotFoundException {
|
|
732
770
|
return String.valueOf(getPackageInfo().versionCode);
|
|
733
771
|
}
|
|
@@ -736,17 +774,23 @@ public class LiveUpdate {
|
|
|
736
774
|
return getPackageInfo().versionName;
|
|
737
775
|
}
|
|
738
776
|
|
|
739
|
-
private boolean
|
|
777
|
+
private boolean hasBundleById(@NonNull String bundleId) {
|
|
740
778
|
File bundleDirectory = buildBundleDirectoryFor(bundleId);
|
|
741
779
|
return bundleDirectory.exists();
|
|
742
780
|
}
|
|
743
781
|
|
|
782
|
+
private boolean isBundleInUse(@NonNull String bundleId) {
|
|
783
|
+
String currentBundleId = getCurrentBundleId();
|
|
784
|
+
String nextBundleId = getNextBundleId();
|
|
785
|
+
return bundleId.equals(currentBundleId) || bundleId.equals(nextBundleId);
|
|
786
|
+
}
|
|
787
|
+
|
|
744
788
|
@Nullable
|
|
745
789
|
private Manifest loadCurrentManifest() throws Exception {
|
|
746
790
|
String currentBundleId = getCurrentBundleId();
|
|
747
|
-
if (currentBundleId
|
|
791
|
+
if (currentBundleId == null) {
|
|
748
792
|
AssetManager assets = plugin.getContext().getAssets();
|
|
749
|
-
|
|
793
|
+
boolean manifestFileExists = Arrays.asList(assets.list(defaultWebAssetDir)).contains(manifestFileName);
|
|
750
794
|
if (manifestFileExists) {
|
|
751
795
|
InputStream inputStream = assets.open(defaultWebAssetDir + "/" + manifestFileName);
|
|
752
796
|
BufferedSource source = Okio.buffer(Okio.source(inputStream));
|
|
@@ -776,25 +820,25 @@ public class LiveUpdate {
|
|
|
776
820
|
return loadManifest(source);
|
|
777
821
|
}
|
|
778
822
|
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
String nextBundleId = getNextBundleId();
|
|
782
|
-
return bundleId.equals(currentBundleId) || bundleId.equals(nextBundleId);
|
|
823
|
+
public void notifyDownloadBundleProgressListeners(@NonNull final DownloadBundleProgressEvent event) {
|
|
824
|
+
plugin.notifyDownloadBundleProgressListeners(event);
|
|
783
825
|
}
|
|
784
826
|
|
|
785
827
|
private void rollback() {
|
|
786
|
-
|
|
828
|
+
// Set the rollback flag
|
|
829
|
+
rollbackPerformed = true;
|
|
830
|
+
// Set the new previous bundle ID
|
|
831
|
+
String currentBundleId = getCurrentBundleId();
|
|
832
|
+
setPreviousBundleId(currentBundleId);
|
|
833
|
+
// Log the rollback result
|
|
834
|
+
if (currentBundleId == null) {
|
|
787
835
|
Logger.debug(LiveUpdatePlugin.TAG, "App is not ready. Default bundle is already in use.");
|
|
788
|
-
|
|
836
|
+
} else {
|
|
837
|
+
Logger.debug(LiveUpdatePlugin.TAG, "App is not ready. Rolling back to default bundle.");
|
|
838
|
+
// Rollback to the default bundle
|
|
839
|
+
setNextBundleById(null);
|
|
840
|
+
setCurrentBundleById(null);
|
|
789
841
|
}
|
|
790
|
-
Logger.debug(LiveUpdatePlugin.TAG, "App is not ready. Rolling back to default bundle.");
|
|
791
|
-
setNextCapacitorServerPathToDefaultWebAssetDir();
|
|
792
|
-
setCurrentCapacitorServerPathToDefaultWebAssetDir();
|
|
793
|
-
}
|
|
794
|
-
|
|
795
|
-
private void saveCurrentVersionCode() throws PackageManager.NameNotFoundException {
|
|
796
|
-
int currentVersionCode = getPackageInfo().versionCode;
|
|
797
|
-
preferences.setLastVersionCode(currentVersionCode);
|
|
798
842
|
}
|
|
799
843
|
|
|
800
844
|
@Nullable
|
|
@@ -822,6 +866,18 @@ public class LiveUpdate {
|
|
|
822
866
|
return null;
|
|
823
867
|
}
|
|
824
868
|
|
|
869
|
+
/**
|
|
870
|
+
* @param bundleId The bundle ID to set as the current bundle. If `null`, the default bundle will be used.
|
|
871
|
+
*/
|
|
872
|
+
private void setCurrentBundleById(@Nullable String bundleId) {
|
|
873
|
+
if (bundleId == null) {
|
|
874
|
+
setCurrentCapacitorServerPath(defaultWebAssetDir);
|
|
875
|
+
} else {
|
|
876
|
+
File bundleDirectory = buildBundleDirectoryFor(bundleId);
|
|
877
|
+
setCurrentCapacitorServerPath(bundleDirectory.getPath());
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
|
|
825
881
|
private void setCurrentCapacitorServerPath(@NonNull String path) {
|
|
826
882
|
if (path.equals(defaultWebAssetDir)) {
|
|
827
883
|
this.plugin.getBridge().setServerAssetPath(path);
|
|
@@ -831,13 +887,16 @@ public class LiveUpdate {
|
|
|
831
887
|
this.plugin.getBridge().reload();
|
|
832
888
|
}
|
|
833
889
|
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
890
|
+
/**
|
|
891
|
+
* @param bundleId The bundle ID to set as the next bundle. If `null`, the default bundle will be used.
|
|
892
|
+
*/
|
|
893
|
+
private void setNextBundleById(@Nullable String bundleId) {
|
|
894
|
+
if (bundleId == null) {
|
|
895
|
+
setNextCapacitorServerPath(defaultWebAssetDir);
|
|
896
|
+
} else {
|
|
897
|
+
File bundleDirectory = buildBundleDirectoryFor(bundleId);
|
|
898
|
+
setNextCapacitorServerPath(bundleDirectory.getPath());
|
|
899
|
+
}
|
|
841
900
|
}
|
|
842
901
|
|
|
843
902
|
private void setNextCapacitorServerPath(@NonNull String path) {
|
|
@@ -845,11 +904,17 @@ public class LiveUpdate {
|
|
|
845
904
|
this.webViewSettingsEditor.commit();
|
|
846
905
|
}
|
|
847
906
|
|
|
848
|
-
|
|
849
|
-
|
|
907
|
+
/**
|
|
908
|
+
* @param bundleId The bundle ID to save as the previous bundle. If `null`, the value will be removed.
|
|
909
|
+
*/
|
|
910
|
+
private void setPreviousBundleId(@Nullable String bundleId) {
|
|
911
|
+
preferences.setPreviousBundleId(bundleId);
|
|
850
912
|
}
|
|
851
913
|
|
|
852
914
|
private void startRollbackTimer() {
|
|
915
|
+
if (config.getReadyTimeout() <= 0) {
|
|
916
|
+
return;
|
|
917
|
+
}
|
|
853
918
|
stopRollbackTimer();
|
|
854
919
|
rollbackHandler.postDelayed(() -> rollback(), config.getReadyTimeout());
|
|
855
920
|
}
|
|
@@ -916,12 +981,6 @@ public class LiveUpdate {
|
|
|
916
981
|
}
|
|
917
982
|
}
|
|
918
983
|
|
|
919
|
-
private boolean wasUpdated() throws PackageManager.NameNotFoundException {
|
|
920
|
-
int lastVersionCode = preferences.getLastVersionCode();
|
|
921
|
-
int currentVersionCode = getPackageInfo().versionCode;
|
|
922
|
-
return lastVersionCode != currentVersionCode;
|
|
923
|
-
}
|
|
924
|
-
|
|
925
984
|
private PackageInfo getPackageInfo() throws PackageManager.NameNotFoundException {
|
|
926
985
|
String packageName = this.plugin.getContext().getPackageName();
|
|
927
986
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|