@capawesome/capacitor-live-update 6.7.1 → 7.0.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 (43) hide show
  1. package/CapawesomeCapacitorLiveUpdate.podspec +2 -2
  2. package/Package.swift +32 -0
  3. package/README.md +91 -81
  4. package/android/build.gradle +9 -9
  5. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdate.java +165 -132
  6. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdateConfig.java +6 -27
  7. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdateHttpClient.java +7 -2
  8. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdatePlugin.java +50 -78
  9. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/LiveUpdatePreferences.java +12 -5
  10. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/api/GetLatestBundleResponse.java +9 -0
  11. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/events/DownloadBundleProgressEvent.java +32 -0
  12. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/options/DownloadBundleOptions.java +1 -10
  13. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/results/FetchLatestBundleResult.java +13 -1
  14. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/results/ReadyResult.java +32 -0
  15. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/interfaces/DownloadProgressCallback.java +5 -0
  16. package/dist/docs.json +191 -158
  17. package/dist/esm/definitions.d.ts +107 -52
  18. package/dist/esm/definitions.js.map +1 -1
  19. package/dist/esm/index.js +1 -1
  20. package/dist/esm/index.js.map +1 -1
  21. package/dist/esm/web.d.ts +2 -2
  22. package/dist/esm/web.js.map +1 -1
  23. package/dist/plugin.cjs.js +1 -3
  24. package/dist/plugin.cjs.js.map +1 -1
  25. package/dist/plugin.js +1 -3
  26. package/dist/plugin.js.map +1 -1
  27. package/ios/Plugin/Classes/Events/DownloadBundleProgressEvent.swift +25 -0
  28. package/ios/Plugin/Classes/Options/DownloadBundleOptions.swift +1 -7
  29. package/ios/Plugin/Classes/Results/{FetchLatestBundle.swift → FetchLatestBundleResult.swift} +6 -1
  30. package/ios/Plugin/Classes/Results/ReadyResult.swift +22 -0
  31. package/ios/Plugin/LiveUpdate.swift +167 -148
  32. package/ios/Plugin/LiveUpdateConfig.swift +2 -4
  33. package/ios/Plugin/LiveUpdateHttpClient.swift +6 -2
  34. package/ios/Plugin/LiveUpdatePlugin.swift +44 -38
  35. package/ios/Plugin/LiveUpdatePreferences.swift +9 -5
  36. package/ios/Plugin/Protocols/Api/GetLatestBundleResponse.swift +1 -0
  37. package/package.json +17 -18
  38. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/options/SetBundleOptions.java +0 -18
  39. package/android/src/main/java/io/capawesome/capacitorjs/plugins/liveupdate/classes/results/GetBundleResult.java +0 -24
  40. package/ios/Plugin/Classes/Options/SetBundleOptions.swift +0 -14
  41. package/ios/Plugin/Classes/Results/GetBundleResult.swift +0 -16
  42. package/ios/Plugin/LiveUpdatePlugin.h +0 -10
  43. 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,38 +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
- if (config.getEnabled()) {
106
- if (wasUpdated() && config.getResetOnUpdate()) {
107
- reset();
108
- } else {
109
- startRollbackTimer();
110
- }
111
- saveCurrentVersionCode();
112
- }
100
+ // Start the rollback timer to rollback to the default bundle
101
+ // if the app is not ready after a certain time
102
+ startRollbackTimer();
113
103
  }
114
104
 
115
105
  public void deleteBundle(@NonNull DeleteBundleOptions options, @NonNull EmptyCallback callback) {
116
106
  String bundleId = options.getBundleId();
117
107
 
118
- if (!hasBundle(bundleId)) {
108
+ if (!hasBundleById(bundleId)) {
119
109
  Exception exception = new Exception(LiveUpdatePlugin.ERROR_BUNDLE_NOT_FOUND);
120
110
  callback.error(exception);
121
111
  return;
122
112
  }
123
- deleteBundle(bundleId);
113
+ deleteBundleById(bundleId);
124
114
 
125
115
  callback.success();
126
116
  }
@@ -128,11 +118,10 @@ public class LiveUpdate {
128
118
  public void downloadBundle(@NonNull DownloadBundleOptions options, @NonNull EmptyCallback callback) throws Exception {
129
119
  ArtifactType artifactType = options.getArtifactType();
130
120
  String bundleId = options.getBundleId();
131
- String checksum = options.getChecksum();
132
121
  String url = options.getUrl();
133
122
 
134
123
  // Check if the bundle already exists
135
- if (hasBundle(bundleId)) {
124
+ if (hasBundleById(bundleId)) {
136
125
  Exception exception = new Exception(LiveUpdatePlugin.ERROR_BUNDLE_EXISTS);
137
126
  callback.error(exception);
138
127
  return;
@@ -142,7 +131,7 @@ public class LiveUpdate {
142
131
  if (artifactType == ArtifactType.MANIFEST) {
143
132
  downloadBundleOfTypeManifest(bundleId, url);
144
133
  } else {
145
- downloadBundleOfTypeZip(bundleId, url, checksum);
134
+ downloadBundleOfTypeZip(bundleId, url);
146
135
  }
147
136
  callback.success();
148
137
  }
@@ -151,17 +140,9 @@ public class LiveUpdate {
151
140
  GetLatestBundleResponse response = fetchLatestBundle(options);
152
141
  ArtifactType artifactType = response == null ? null : response.getArtifactType();
153
142
  String bundleId = response == null ? null : response.getBundleId();
143
+ JSONObject customProperties = response == null ? null : response.getCustomProperties();
154
144
  String downloadUrl = response == null ? null : response.getUrl();
155
- FetchLatestBundleResult result = new FetchLatestBundleResult(artifactType, bundleId, downloadUrl);
156
- callback.success(result);
157
- }
158
-
159
- public void getBundle(@NonNull NonEmptyCallback callback) {
160
- String bundleId = getCurrentBundleId();
161
- if (bundleId.equals(defaultWebAssetDir)) {
162
- bundleId = null;
163
- }
164
- GetBundleResult result = new GetBundleResult(bundleId);
145
+ FetchLatestBundleResult result = new FetchLatestBundleResult(artifactType, bundleId, customProperties, downloadUrl);
165
146
  callback.success(result);
166
147
  }
167
148
 
@@ -179,9 +160,6 @@ public class LiveUpdate {
179
160
 
180
161
  public void getCurrentBundle(@NonNull NonEmptyCallback<GetCurrentBundleResult> callback) {
181
162
  String bundleId = getCurrentBundleId();
182
- if (bundleId.equals(defaultWebAssetDir)) {
183
- bundleId = null;
184
- }
185
163
  GetCurrentBundleResult result = new GetCurrentBundleResult(bundleId);
186
164
  callback.success(result);
187
165
  }
@@ -200,9 +178,6 @@ public class LiveUpdate {
200
178
 
201
179
  public void getNextBundle(@NonNull NonEmptyCallback<GetNextBundleResult> callback) {
202
180
  String bundleId = getNextBundleId();
203
- if (bundleId.equals(defaultWebAssetDir)) {
204
- bundleId = null;
205
- }
206
181
  GetNextBundleResult result = new GetNextBundleResult(bundleId);
207
182
  callback.success(result);
208
183
  }
@@ -219,14 +194,27 @@ public class LiveUpdate {
219
194
  callback.success(result);
220
195
  }
221
196
 
222
- public void ready() {
197
+ public void ready(@NonNull NonEmptyCallback callback) {
223
198
  Logger.debug(LiveUpdatePlugin.TAG, "App is ready.");
199
+ if (config.getReadyTimeout() <= 0) {
200
+ Logger.warn(LiveUpdatePlugin.TAG, "Ready timeout is set to 0. Automatic rollback is disabled.");
201
+ }
224
202
  // Stop the rollback timer
225
203
  stopRollbackTimer();
226
204
  // Delete unused bundles
227
205
  if (config.getAutoDeleteBundles()) {
228
206
  deleteUnusedBundles();
229
207
  }
208
+ // Get the current and previous bundle IDs
209
+ String currentBundleId = getCurrentBundleId();
210
+ String previousBundleId = getPreviousBundleId();
211
+ // Return the result
212
+ ReadyResult result = new ReadyResult(currentBundleId, previousBundleId, rollbackPerformed);
213
+ callback.success(result);
214
+ // Set the new previous bundle ID
215
+ setPreviousBundleId(currentBundleId);
216
+ // Reset the rollback flag
217
+ rollbackPerformed = false;
230
218
  }
231
219
 
232
220
  public void reload() {
@@ -236,20 +224,7 @@ public class LiveUpdate {
236
224
  }
237
225
 
238
226
  public void reset() {
239
- setNextCapacitorServerPathToDefaultWebAssetDir();
240
- }
241
-
242
- public void setBundle(@NonNull SetBundleOptions options, @NonNull EmptyCallback callback) {
243
- String bundleId = options.getBundleId();
244
-
245
- if (!hasBundle(bundleId)) {
246
- Exception exception = new Exception(LiveUpdatePlugin.ERROR_BUNDLE_NOT_FOUND);
247
- callback.error(exception);
248
- return;
249
- }
250
-
251
- setNextBundle(bundleId);
252
- callback.success();
227
+ setNextBundleById(null);
253
228
  }
254
229
 
255
230
  public void setChannel(@NonNull SetChannelOptions options, @NonNull EmptyCallback callback) {
@@ -272,8 +247,8 @@ public class LiveUpdate {
272
247
  if (bundleId == null) {
273
248
  reset();
274
249
  } else {
275
- if (hasBundle(bundleId)) {
276
- setNextBundle(bundleId);
250
+ if (hasBundleById(bundleId)) {
251
+ setNextBundleById(bundleId);
277
252
  } else {
278
253
  Exception exception = new Exception(LiveUpdatePlugin.ERROR_BUNDLE_NOT_FOUND);
279
254
  callback.error(exception);
@@ -298,12 +273,12 @@ public class LiveUpdate {
298
273
  String latestBundleId = response.getBundleId();
299
274
  String url = response.getUrl();
300
275
  // Check if the bundle already exists
301
- if (hasBundle(latestBundleId)) {
276
+ if (hasBundleById(latestBundleId)) {
302
277
  String nextBundleId = null;
303
278
  String currentBundleId = getCurrentBundleId();
304
279
  if (!latestBundleId.equals(currentBundleId)) {
305
280
  // Set the next bundle
306
- setNextBundle(latestBundleId);
281
+ setNextBundleById(latestBundleId);
307
282
  nextBundleId = latestBundleId;
308
283
  }
309
284
  SyncResult syncResult = new SyncResult(nextBundleId);
@@ -314,10 +289,10 @@ public class LiveUpdate {
314
289
  if (artifactType == ArtifactType.MANIFEST) {
315
290
  downloadBundleOfTypeManifest(latestBundleId, url);
316
291
  } else {
317
- downloadBundleOfTypeZip(latestBundleId, url, null);
292
+ downloadBundleOfTypeZip(latestBundleId, url);
318
293
  }
319
294
  // Set the next bundle
320
- setNextBundle(latestBundleId);
295
+ setNextBundleById(latestBundleId);
321
296
  SyncResult syncResult = new SyncResult(latestBundleId);
322
297
  callback.success(syncResult);
323
298
  }
@@ -366,9 +341,10 @@ public class LiveUpdate {
366
341
  return new File(plugin.getContext().getCacheDir(), fileName);
367
342
  }
368
343
 
369
- private void copyCurrentBundleFile(@NonNull String href, @NonNull File destinationDirectory) throws IOException {
344
+ private void copyCurrentBundleFile(@NonNull ManifestItem fileToCopy, @NonNull File destinationDirectory) throws IOException {
345
+ String href = fileToCopy.getHref();
370
346
  String currentBundleId = getCurrentBundleId();
371
- if (currentBundleId.equals(defaultWebAssetDir)) {
347
+ if (currentBundleId == null) {
372
348
  // Create the source input stream
373
349
  AssetManager assets = plugin.getContext().getAssets();
374
350
  InputStream inputStream = assets.open(defaultWebAssetDir + "/" + href);
@@ -390,9 +366,9 @@ public class LiveUpdate {
390
366
  }
391
367
  }
392
368
 
393
- private void copyCurrentBundleFiles(@NonNull List<String> hrefs, @NonNull File destinationDirectory) throws IOException {
394
- for (String href : hrefs) {
395
- copyCurrentBundleFile(href, destinationDirectory);
369
+ private void copyCurrentBundleFiles(@NonNull List<ManifestItem> filesToCopy, @NonNull File destinationDirectory) throws IOException {
370
+ for (ManifestItem fileToCopy : filesToCopy) {
371
+ copyCurrentBundleFile(fileToCopy, destinationDirectory);
396
372
  }
397
373
  }
398
374
 
@@ -445,14 +421,14 @@ public class LiveUpdate {
445
421
  return file;
446
422
  }
447
423
 
448
- private void deleteBundle(@NonNull String bundleId) {
424
+ private void deleteBundleById(@NonNull String bundleId) {
449
425
  // Delete the bundle directory
450
426
  File bundleDirectory = buildBundleDirectoryFor(bundleId);
451
427
  deleteFileRecursively(bundleDirectory);
452
428
  // Reset the next bundle if it is the deleted bundle
453
429
  String nextBundleId = getNextBundleId();
454
430
  if (bundleId.equals(nextBundleId)) {
455
- setNextCapacitorServerPathToDefaultWebAssetDir();
431
+ setNextBundleById(null);
456
432
  }
457
433
  }
458
434
 
@@ -469,18 +445,19 @@ public class LiveUpdate {
469
445
  String[] bundleIds = getBundleIds();
470
446
  for (String bundleId : bundleIds) {
471
447
  if (!isBundleInUse(bundleId)) {
472
- deleteBundle(bundleId);
448
+ deleteBundleById(bundleId);
473
449
  }
474
450
  }
475
451
  }
476
452
 
477
- private void downloadAndVerifyFile(@NonNull String url, @NonNull File file, @Nullable String checksum) throws Exception {
453
+ private void downloadAndVerifyFile(@NonNull String url, @NonNull File file, @Nullable DownloadProgressCallback callback)
454
+ throws Exception {
478
455
  Response response = httpClient.execute(url);
479
456
  ResponseBody responseBody = response.body();
480
457
  if (response.isSuccessful()) {
481
- LiveUpdateHttpClient.writeResponseBodyToFile(responseBody, file);
458
+ LiveUpdateHttpClient.writeResponseBodyToFile(responseBody, file, callback);
482
459
  // Verify the file
483
- checksum = checksum == null ? LiveUpdateHttpClient.getChecksumFromResponse(response) : checksum;
460
+ String checksum = LiveUpdateHttpClient.getChecksumFromResponse(response);
484
461
  String signature = LiveUpdateHttpClient.getSignatureFromResponse(response);
485
462
  verifyFile(file, checksum, signature);
486
463
  } else {
@@ -490,24 +467,39 @@ public class LiveUpdate {
490
467
  }
491
468
  }
492
469
 
493
- private File downloadBundleFile(@NonNull String baseUrl, @NonNull String href, @NonNull File destinationDirectory) throws Exception {
470
+ private File downloadBundleFile(
471
+ @NonNull String baseUrl,
472
+ @NonNull String href,
473
+ @NonNull File destinationDirectory,
474
+ @Nullable DownloadProgressCallback callback
475
+ ) throws Exception {
494
476
  HttpUrl.Builder urlBuilder = HttpUrl.parse(baseUrl).newBuilder();
495
477
  urlBuilder.addQueryParameter("href", href);
496
478
  String url = urlBuilder.build().toString();
497
479
 
498
480
  File destinationFile = new File(destinationDirectory, href);
499
481
  destinationFile.getParentFile().mkdirs();
500
- downloadAndVerifyFile(url, destinationFile, null);
482
+ downloadAndVerifyFile(url, destinationFile, callback);
501
483
  return destinationFile;
502
484
  }
503
485
 
504
- private void downloadBundleFiles(@NonNull String baseUrl, @NonNull List<String> hrefs, @NonNull File destinationDirectory)
505
- throws Exception {
486
+ private void downloadBundleFiles(
487
+ @NonNull String baseUrl,
488
+ @NonNull List<ManifestItem> filesToDownload,
489
+ @NonNull File destinationDirectory,
490
+ @Nullable DownloadProgressCallback callback
491
+ ) throws Exception {
492
+ // Track the total downloaded bytes and total bytes to download for the progress callback
493
+ final long[] totalBytesDownloaded = { 0 };
494
+ long totalBytesToDownload = 0;
495
+ for (ManifestItem fileToDownload : filesToDownload) {
496
+ totalBytesToDownload += fileToDownload.getSizeInBytes();
497
+ }
506
498
  // Limit the number of threads to twice the number of processors
507
499
  int maxThreads = Runtime.getRuntime().availableProcessors() * 2;
508
500
  List<Thread> threads = new ArrayList<>();
509
501
  AtomicReference<Exception> exceptionReference = new AtomicReference<>();
510
- for (String href : hrefs) {
502
+ for (ManifestItem fileToDownload : filesToDownload) {
511
503
  // Wait until a thread is available
512
504
  while (threads.size() >= maxThreads) {
513
505
  for (Thread thread : threads) {
@@ -523,13 +515,23 @@ public class LiveUpdate {
523
515
  if (exceptionReference.get() != null) {
524
516
  break;
525
517
  }
518
+ // Final variable that can be accessed from within inner class
519
+ long finalTotalBytesToDownload = totalBytesToDownload;
526
520
  // Start a new thread
527
521
  Thread thread = new Thread(() -> {
528
522
  try {
529
- downloadBundleFile(baseUrl, href, destinationDirectory);
523
+ downloadBundleFile(baseUrl, fileToDownload.getHref(), destinationDirectory, (downloadedBytes, totalBytes) -> {
524
+ if (callback != null) {
525
+ callback.onProgress(totalBytesDownloaded[0] + downloadedBytes, finalTotalBytesToDownload);
526
+ }
527
+ });
528
+ if (callback != null) {
529
+ totalBytesDownloaded[0] += fileToDownload.getSizeInBytes();
530
+ callback.onProgress(totalBytesDownloaded[0], finalTotalBytesToDownload);
531
+ }
530
532
  } catch (Exception exception) {
531
533
  exceptionReference.set(exception);
532
- Logger.error(LiveUpdatePlugin.TAG, "Failed to download file: " + href, exception);
534
+ Logger.error(LiveUpdatePlugin.TAG, "Failed to download file: " + fileToDownload.getHref(), exception);
533
535
  }
534
536
  });
535
537
  threads.add(thread);
@@ -544,13 +546,17 @@ public class LiveUpdate {
544
546
  if (exception != null) {
545
547
  throw exception;
546
548
  }
549
+ // Call the callback one last time to make sure the progress is at 100%
550
+ if (callback != null) {
551
+ callback.onProgress(totalBytesToDownload, totalBytesToDownload);
552
+ }
547
553
  }
548
554
 
549
555
  private void downloadBundleOfTypeManifest(@NonNull String bundleId, @NonNull String downloadUrl) throws Exception {
550
556
  // Create a temporary directory
551
557
  File temporaryDirectory = createTemporaryDirectory();
552
558
  // Download the latest manifest
553
- File latestManifestFile = downloadBundleFile(downloadUrl, manifestFileName, temporaryDirectory);
559
+ File latestManifestFile = downloadBundleFile(downloadUrl, manifestFileName, temporaryDirectory, null);
554
560
  Manifest latestManifest = loadManifest(latestManifestFile);
555
561
  // Load the current manifest
556
562
  Manifest currentManifest = loadCurrentManifest();
@@ -564,26 +570,23 @@ public class LiveUpdate {
564
570
  itemsToDownload.addAll(Manifest.findMissingItems(latestManifest, currentManifest));
565
571
  }
566
572
  // Copy the files
567
- List<String> hrefsToCopy = new ArrayList<>();
568
- for (ManifestItem item : itemsToCopy) {
569
- hrefsToCopy.add(item.getHref());
570
- }
571
- copyCurrentBundleFiles(hrefsToCopy, temporaryDirectory);
573
+ copyCurrentBundleFiles(itemsToCopy, temporaryDirectory);
572
574
  // Download the files
573
- List<String> hrefsToDownload = new ArrayList<>();
574
- for (ManifestItem item : itemsToDownload) {
575
- hrefsToDownload.add(item.getHref());
576
- }
577
- downloadBundleFiles(downloadUrl, hrefsToDownload, temporaryDirectory);
575
+ downloadBundleFiles(downloadUrl, itemsToDownload, temporaryDirectory, (downloadedBytes, totalBytes) -> {
576
+ DownloadBundleProgressEvent event = new DownloadBundleProgressEvent(bundleId, downloadedBytes, totalBytes);
577
+ notifyDownloadBundleProgressListeners(event);
578
+ });
578
579
  // Add the bundle
579
580
  addBundleOfTypeManifest(bundleId, temporaryDirectory);
580
581
  }
581
582
 
582
- private void downloadBundleOfTypeZip(@NonNull String bundleId, @NonNull String downloadUrl, @Nullable String checksum)
583
- throws Exception {
583
+ private void downloadBundleOfTypeZip(@NonNull String bundleId, @NonNull String downloadUrl) throws Exception {
584
584
  File file = buildTemporaryZipFile();
585
585
  // Download the bundle
586
- downloadAndVerifyFile(downloadUrl, file, checksum);
586
+ downloadAndVerifyFile(downloadUrl, file, (downloadedBytes, totalBytes) -> {
587
+ DownloadBundleProgressEvent event = new DownloadBundleProgressEvent(bundleId, downloadedBytes, totalBytes);
588
+ notifyDownloadBundleProgressListeners(event);
589
+ });
587
590
  // Add the bundle
588
591
  addBundleOfTypeZip(bundleId, file);
589
592
  // Delete the temporary file
@@ -595,7 +598,7 @@ public class LiveUpdate {
595
598
  String channel = options.getChannel() == null ? getChannel() : options.getChannel();
596
599
  String url = new HttpUrl.Builder()
597
600
  .scheme("https")
598
- .host(host)
601
+ .host(config.getServerDomain())
599
602
  .addPathSegment("v1")
600
603
  .addPathSegment("apps")
601
604
  .addPathSegment(config.getAppId())
@@ -677,12 +680,13 @@ public class LiveUpdate {
677
680
  }
678
681
 
679
682
  /**
680
- * @return The current bundle ID (`public` for the built-in bundle).
683
+ * @return The current bundle ID or `null` if the default bundle is in use.
681
684
  */
685
+ @Nullable
682
686
  private String getCurrentBundleId() {
683
687
  String currentPath = getCurrentCapacitorServerPath();
684
688
  if (currentPath.equals(defaultWebAssetDir)) {
685
- return defaultWebAssetDir;
689
+ return null;
686
690
  }
687
691
  return new File(currentPath).getName();
688
692
  }
@@ -705,13 +709,13 @@ public class LiveUpdate {
705
709
  }
706
710
 
707
711
  /**
708
- * @return The next bundle ID (`public` for the built-in bundle).
712
+ * @return The next bundle ID or `null` if the default bundle will be used.
709
713
  */
710
- @NonNull
714
+ @Nullable
711
715
  private String getNextBundleId() {
712
716
  String nextPath = getNextCapacitorServerPath();
713
717
  if (nextPath.equals(defaultWebAssetDir)) {
714
- return defaultWebAssetDir;
718
+ return null;
715
719
  }
716
720
  return new File(nextPath).getName();
717
721
  }
@@ -727,6 +731,14 @@ public class LiveUpdate {
727
731
  .getString(WebView.CAP_SERVER_PATH, defaultWebAssetDir);
728
732
  }
729
733
 
734
+ /**
735
+ * @return The previous bundle ID or `null` if the default bundle was used.
736
+ */
737
+ @Nullable
738
+ private String getPreviousBundleId() {
739
+ return preferences.getPreviousBundleId();
740
+ }
741
+
730
742
  private String getVersionCode() throws PackageManager.NameNotFoundException {
731
743
  return String.valueOf(getPackageInfo().versionCode);
732
744
  }
@@ -735,17 +747,23 @@ public class LiveUpdate {
735
747
  return getPackageInfo().versionName;
736
748
  }
737
749
 
738
- private boolean hasBundle(@NonNull String bundleId) {
750
+ private boolean hasBundleById(@NonNull String bundleId) {
739
751
  File bundleDirectory = buildBundleDirectoryFor(bundleId);
740
752
  return bundleDirectory.exists();
741
753
  }
742
754
 
755
+ private boolean isBundleInUse(@NonNull String bundleId) {
756
+ String currentBundleId = getCurrentBundleId();
757
+ String nextBundleId = getNextBundleId();
758
+ return bundleId.equals(currentBundleId) || bundleId.equals(nextBundleId);
759
+ }
760
+
743
761
  @Nullable
744
762
  private Manifest loadCurrentManifest() throws Exception {
745
763
  String currentBundleId = getCurrentBundleId();
746
- if (currentBundleId.equals(defaultWebAssetDir)) {
764
+ if (currentBundleId == null) {
747
765
  AssetManager assets = plugin.getContext().getAssets();
748
- Boolean manifestFileExists = Arrays.asList(assets.list(defaultWebAssetDir)).contains(manifestFileName);
766
+ boolean manifestFileExists = Arrays.asList(assets.list(defaultWebAssetDir)).contains(manifestFileName);
749
767
  if (manifestFileExists) {
750
768
  InputStream inputStream = assets.open(defaultWebAssetDir + "/" + manifestFileName);
751
769
  BufferedSource source = Okio.buffer(Okio.source(inputStream));
@@ -775,25 +793,25 @@ public class LiveUpdate {
775
793
  return loadManifest(source);
776
794
  }
777
795
 
778
- private boolean isBundleInUse(@NonNull String bundleId) {
779
- String currentBundleId = getCurrentBundleId();
780
- String nextBundleId = getNextBundleId();
781
- return bundleId.equals(currentBundleId) || bundleId.equals(nextBundleId);
796
+ public void notifyDownloadBundleProgressListeners(@NonNull final DownloadBundleProgressEvent event) {
797
+ plugin.notifyDownloadBundleProgressListeners(event);
782
798
  }
783
799
 
784
800
  private void rollback() {
785
- if (getCurrentBundleId() == defaultWebAssetDir) {
801
+ // Set the rollback flag
802
+ rollbackPerformed = true;
803
+ // Set the new previous bundle ID
804
+ String currentBundleId = getCurrentBundleId();
805
+ setPreviousBundleId(currentBundleId);
806
+ // Log the rollback result
807
+ if (currentBundleId == null) {
786
808
  Logger.debug(LiveUpdatePlugin.TAG, "App is not ready. Default bundle is already in use.");
787
- return;
809
+ } else {
810
+ Logger.debug(LiveUpdatePlugin.TAG, "App is not ready. Rolling back to default bundle.");
811
+ // Rollback to the default bundle
812
+ setNextBundleById(null);
813
+ setCurrentBundleById(null);
788
814
  }
789
- Logger.debug(LiveUpdatePlugin.TAG, "App is not ready. Rolling back to default bundle.");
790
- setNextCapacitorServerPathToDefaultWebAssetDir();
791
- setCurrentCapacitorServerPathToDefaultWebAssetDir();
792
- }
793
-
794
- private void saveCurrentVersionCode() throws PackageManager.NameNotFoundException {
795
- int currentVersionCode = getPackageInfo().versionCode;
796
- preferences.setLastVersionCode(currentVersionCode);
797
815
  }
798
816
 
799
817
  @Nullable
@@ -821,6 +839,18 @@ public class LiveUpdate {
821
839
  return null;
822
840
  }
823
841
 
842
+ /**
843
+ * @param bundleId The bundle ID to set as the current bundle. If `null`, the default bundle will be used.
844
+ */
845
+ private void setCurrentBundleById(@Nullable String bundleId) {
846
+ if (bundleId == null) {
847
+ setCurrentCapacitorServerPath(defaultWebAssetDir);
848
+ } else {
849
+ File bundleDirectory = buildBundleDirectoryFor(bundleId);
850
+ setCurrentCapacitorServerPath(bundleDirectory.getPath());
851
+ }
852
+ }
853
+
824
854
  private void setCurrentCapacitorServerPath(@NonNull String path) {
825
855
  if (path.equals(defaultWebAssetDir)) {
826
856
  this.plugin.getBridge().setServerAssetPath(path);
@@ -830,13 +860,16 @@ public class LiveUpdate {
830
860
  this.plugin.getBridge().reload();
831
861
  }
832
862
 
833
- private void setCurrentCapacitorServerPathToDefaultWebAssetDir() {
834
- setCurrentCapacitorServerPath(defaultWebAssetDir);
835
- }
836
-
837
- private void setNextBundle(@NonNull String bundleId) {
838
- File bundleDirectory = buildBundleDirectoryFor(bundleId);
839
- setNextCapacitorServerPath(bundleDirectory.getPath());
863
+ /**
864
+ * @param bundleId The bundle ID to set as the next bundle. If `null`, the default bundle will be used.
865
+ */
866
+ private void setNextBundleById(@Nullable String bundleId) {
867
+ if (bundleId == null) {
868
+ setNextCapacitorServerPath(defaultWebAssetDir);
869
+ } else {
870
+ File bundleDirectory = buildBundleDirectoryFor(bundleId);
871
+ setNextCapacitorServerPath(bundleDirectory.getPath());
872
+ }
840
873
  }
841
874
 
842
875
  private void setNextCapacitorServerPath(@NonNull String path) {
@@ -844,11 +877,17 @@ public class LiveUpdate {
844
877
  this.webViewSettingsEditor.commit();
845
878
  }
846
879
 
847
- private void setNextCapacitorServerPathToDefaultWebAssetDir() {
848
- setNextCapacitorServerPath(defaultWebAssetDir);
880
+ /**
881
+ * @param bundleId The bundle ID to save as the previous bundle. If `null`, the value will be removed.
882
+ */
883
+ private void setPreviousBundleId(@Nullable String bundleId) {
884
+ preferences.setPreviousBundleId(bundleId);
849
885
  }
850
886
 
851
887
  private void startRollbackTimer() {
888
+ if (config.getReadyTimeout() <= 0) {
889
+ return;
890
+ }
852
891
  stopRollbackTimer();
853
892
  rollbackHandler.postDelayed(() -> rollback(), config.getReadyTimeout());
854
893
  }
@@ -915,12 +954,6 @@ public class LiveUpdate {
915
954
  }
916
955
  }
917
956
 
918
- private boolean wasUpdated() throws PackageManager.NameNotFoundException {
919
- int lastVersionCode = preferences.getLastVersionCode();
920
- int currentVersionCode = getPackageInfo().versionCode;
921
- return lastVersionCode != currentVersionCode;
922
- }
923
-
924
957
  private PackageInfo getPackageInfo() throws PackageManager.NameNotFoundException {
925
958
  String packageName = this.plugin.getContext().getPackageName();
926
959
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {