@capgo/capacitor-updater 8.44.0 → 8.45.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/README.md CHANGED
@@ -467,6 +467,7 @@ export default config;
467
467
  * [`addListener('breakingAvailable', ...)`](#addlistenerbreakingavailable-)
468
468
  * [`addListener('majorAvailable', ...)`](#addlistenermajoravailable-)
469
469
  * [`addListener('updateFailed', ...)`](#addlistenerupdatefailed-)
470
+ * [`addListener('set', ...)`](#addlistenerset-)
470
471
  * [`addListener('setNext', ...)`](#addlistenersetnext-)
471
472
  * [`addListener('downloadFailed', ...)`](#addlistenerdownloadfailed-)
472
473
  * [`addListener('appReloaded', ...)`](#addlistenerappreloaded-)
@@ -1408,6 +1409,28 @@ Listen for update fail event in the App, let you know when update has fail to in
1408
1409
  --------------------
1409
1410
 
1410
1411
 
1412
+ #### addListener('set', ...)
1413
+
1414
+ ```typescript
1415
+ addListener(eventName: 'set', listenerFunc: (state: SetEvent) => void) => Promise<PluginListenerHandle>
1416
+ ```
1417
+
1418
+ Listen for set event in the App, let you know when a bundle has been applied successfully.
1419
+ This event is retained natively until JavaScript consumes it, so if the app reloads before your
1420
+ listener is attached, the last pending `set` event is delivered once the listener subscribes.
1421
+
1422
+ | Param | Type |
1423
+ | ------------------ | ----------------------------------------------------------------- |
1424
+ | **`eventName`** | <code>'set'</code> |
1425
+ | **`listenerFunc`** | <code>(state: <a href="#setevent">SetEvent</a>) =&gt; void</code> |
1426
+
1427
+ **Returns:** <code>Promise&lt;<a href="#pluginlistenerhandle">PluginListenerHandle</a>&gt;</code>
1428
+
1429
+ **Since:** 8.43.12
1430
+
1431
+ --------------------
1432
+
1433
+
1411
1434
  #### addListener('setNext', ...)
1412
1435
 
1413
1436
  ```typescript
@@ -1474,7 +1497,9 @@ Listen for reload event in the App, let you know when reload has happened
1474
1497
  addListener(eventName: 'appReady', listenerFunc: (state: AppReadyEvent) => void) => Promise<PluginListenerHandle>
1475
1498
  ```
1476
1499
 
1477
- Listen for app ready event in the App, let you know when app is ready to use, this event is retain till consumed.
1500
+ Listen for app ready event in the App, let you know when app is ready to use.
1501
+ This event is retained natively until JavaScript consumes it, so it can still be delivered after
1502
+ a reload even if the listener is attached later in app startup.
1478
1503
 
1479
1504
  | Param | Type |
1480
1505
  | ------------------ | --------------------------------------------------------------------------- |
@@ -2228,6 +2253,13 @@ If you don't use backend, you need to provide the URL and version of the bundle.
2228
2253
  | **`bundle`** | <code><a href="#bundleinfo">BundleInfo</a></code> | Emit when a update failed to install. | 4.0.0 |
2229
2254
 
2230
2255
 
2256
+ ##### SetEvent
2257
+
2258
+ | Prop | Type | Description | Since |
2259
+ | ------------ | ------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | ------- |
2260
+ | **`bundle`** | <code><a href="#bundleinfo">BundleInfo</a></code> | Emit when a bundle has been applied successfully. This event uses native `retainUntilConsumed` behavior. | 8.43.12 |
2261
+
2262
+
2231
2263
  ##### SetNextEvent
2232
2264
 
2233
2265
  | Prop | Type | Description | Since |
@@ -2244,10 +2276,10 @@ If you don't use backend, you need to provide the URL and version of the bundle.
2244
2276
 
2245
2277
  ##### AppReadyEvent
2246
2278
 
2247
- | Prop | Type | Description | Since |
2248
- | ------------ | ------------------------------------------------- | ------------------------------------- | ----- |
2249
- | **`bundle`** | <code><a href="#bundleinfo">BundleInfo</a></code> | Emitted when the app is ready to use. | 5.2.0 |
2250
- | **`status`** | <code>string</code> | | |
2279
+ | Prop | Type | Description | Since |
2280
+ | ------------ | ------------------------------------------------- | -------------------------------------------------------------------------------------------- | ----- |
2281
+ | **`bundle`** | <code><a href="#bundleinfo">BundleInfo</a></code> | Emitted when the app is ready to use. This event uses native `retainUntilConsumed` behavior. | 5.2.0 |
2282
+ | **`status`** | <code>string</code> | | |
2251
2283
 
2252
2284
 
2253
2285
  ##### ChannelPrivateEvent
@@ -22,6 +22,7 @@ import android.view.View;
22
22
  import android.view.ViewGroup;
23
23
  import android.widget.FrameLayout;
24
24
  import android.widget.ProgressBar;
25
+ import com.getcapacitor.Bridge;
25
26
  import com.getcapacitor.CapConfig;
26
27
  import com.getcapacitor.JSArray;
27
28
  import com.getcapacitor.JSObject;
@@ -29,6 +30,7 @@ import com.getcapacitor.Plugin;
29
30
  import com.getcapacitor.PluginCall;
30
31
  import com.getcapacitor.PluginHandle;
31
32
  import com.getcapacitor.PluginMethod;
33
+ import com.getcapacitor.PluginResult;
32
34
  import com.getcapacitor.annotation.CapacitorPlugin;
33
35
  import com.getcapacitor.plugin.WebView;
34
36
  import com.google.android.gms.tasks.Task;
@@ -56,7 +58,6 @@ import java.util.Objects;
56
58
  import java.util.Set;
57
59
  import java.util.Timer;
58
60
  import java.util.TimerTask;
59
- import java.util.UUID;
60
61
  import java.util.concurrent.Phaser;
61
62
  import java.util.concurrent.Semaphore;
62
63
  import java.util.concurrent.TimeUnit;
@@ -83,8 +84,11 @@ public class CapacitorUpdaterPlugin extends Plugin {
83
84
  private static final String DEFAULT_CHANNEL_PREF_KEY = "CapacitorUpdater.defaultChannel";
84
85
  private static final String[] BREAKING_EVENT_NAMES = { "breakingAvailable", "majorAvailable" };
85
86
  private static final String LAST_FAILED_BUNDLE_PREF_KEY = "CapacitorUpdater.lastFailedBundle";
87
+ private static final String SPLASH_SCREEN_PLUGIN_ID = "SplashScreen";
88
+ private static final int SPLASH_SCREEN_RETRY_DELAY_MS = 100;
89
+ private static final int SPLASH_SCREEN_MAX_RETRIES = 20;
86
90
 
87
- private final String pluginVersion = "8.44.0";
91
+ private final String pluginVersion = "8.45.1";
88
92
  private static final String DELAY_CONDITION_PREFERENCES = "";
89
93
 
90
94
  private SharedPreferences.Editor editor;
@@ -108,9 +112,10 @@ public class CapacitorUpdaterPlugin extends Plugin {
108
112
  private Boolean autoSplashscreenLoader = false;
109
113
  private Integer autoSplashscreenTimeout = 10000;
110
114
  private Boolean autoSplashscreenTimedOut = false;
115
+ private int splashscreenInvocationToken = 0;
111
116
  private String directUpdateMode = "false";
112
117
  private Boolean wasRecentlyInstalledOrUpdated = false;
113
- private Boolean onLaunchDirectUpdateUsed = false;
118
+ private volatile boolean onLaunchDirectUpdateUsed = false;
114
119
  Boolean shakeMenuEnabled = false;
115
120
  Boolean shakeChannelSelectorEnabled = false;
116
121
  private Boolean allowManualBundleError = false;
@@ -145,6 +150,28 @@ public class CapacitorUpdaterPlugin extends Plugin {
145
150
  private FrameLayout splashscreenLoaderOverlay;
146
151
  private Runnable splashscreenTimeoutRunnable;
147
152
 
153
+ private static final class FireAndForgetPluginCall extends PluginCall {
154
+
155
+ FireAndForgetPluginCall(final String methodName, final JSObject data) {
156
+ super(null, SPLASH_SCREEN_PLUGIN_ID, PluginCall.CALLBACK_ID_DANGLING, methodName, data);
157
+ }
158
+
159
+ @Override
160
+ public void successCallback(final PluginResult successResult) {}
161
+
162
+ @Override
163
+ public void resolve(final JSObject data) {}
164
+
165
+ @Override
166
+ public void resolve() {}
167
+
168
+ @Override
169
+ public void errorCallback(final String msg) {}
170
+
171
+ @Override
172
+ public void reject(final String msg, final String code, final Exception ex, final JSObject data) {}
173
+ }
174
+
148
175
  // App lifecycle observer using ProcessLifecycleOwner for reliable foreground/background detection
149
176
  private AppLifecycleObserver appLifecycleObserver;
150
177
 
@@ -521,6 +548,15 @@ public class CapacitorUpdaterPlugin extends Plugin {
521
548
  sendReadyToJs(current, msg, false);
522
549
  }
523
550
 
551
+ private void notifyBundleSet(final BundleInfo bundle) {
552
+ if (bundle == null) {
553
+ return;
554
+ }
555
+ final JSObject ret = new JSObject();
556
+ ret.put("bundle", InternalUtils.mapToJSObject(bundle.toJSONMap()));
557
+ this.notifyListeners("set", ret, true);
558
+ }
559
+
524
560
  private void sendReadyToJs(final BundleInfo current, final String msg, final boolean isDirectUpdate) {
525
561
  logger.info("sendReadyToJs: " + msg);
526
562
  final JSObject ret = new JSObject();
@@ -548,47 +584,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
548
584
  private void hideSplashscreenInternal() {
549
585
  cancelSplashscreenTimeout();
550
586
  removeSplashscreenLoader();
551
-
552
- try {
553
- if (getBridge() == null) {
554
- logger.warn("Bridge not ready for hiding splashscreen with autoSplashscreen");
555
- return;
556
- }
557
-
558
- // Try to call the SplashScreen plugin directly through the bridge
559
- PluginHandle splashScreenPlugin = getBridge().getPlugin("SplashScreen");
560
- if (splashScreenPlugin != null) {
561
- try {
562
- // Create a plugin call for the hide method using reflection to access private msgHandler
563
- JSObject options = new JSObject();
564
- java.lang.reflect.Field msgHandlerField = getBridge().getClass().getDeclaredField("msgHandler");
565
- msgHandlerField.setAccessible(true);
566
- Object msgHandler = msgHandlerField.get(getBridge());
567
-
568
- PluginCall call = new PluginCall(
569
- (com.getcapacitor.MessageHandler) msgHandler,
570
- "SplashScreen",
571
- "FAKE_CALLBACK_ID_HIDE",
572
- "hide",
573
- options
574
- );
575
-
576
- // Call the hide method directly
577
- splashScreenPlugin.invoke("hide", call);
578
- logger.info("Splashscreen hidden automatically via direct plugin call");
579
- } catch (Exception e) {
580
- logger.error("Failed to call SplashScreen hide method: " + e.getMessage());
581
- }
582
- } else {
583
- logger.warn("autoSplashscreen: SplashScreen plugin not found. Install @capacitor/splash-screen plugin.");
584
- }
585
- } catch (Exception e) {
586
- logger.error(
587
- "Error hiding splashscreen with autoSplashscreen: " +
588
- e.getMessage() +
589
- ". Make sure @capacitor/splash-screen plugin is installed and configured."
590
- );
591
- }
587
+ invokeSplashScreenPluginMethod("hide", new JSObject(), SPLASH_SCREEN_MAX_RETRIES, ++this.splashscreenInvocationToken);
592
588
  }
593
589
 
594
590
  private void showSplashscreen() {
@@ -603,37 +599,87 @@ public class CapacitorUpdaterPlugin extends Plugin {
603
599
  cancelSplashscreenTimeout();
604
600
  this.autoSplashscreenTimedOut = false;
605
601
 
602
+ final JSObject options = new JSObject();
603
+ options.put("autoHide", false);
604
+ invokeSplashScreenPluginMethod("show", options, SPLASH_SCREEN_MAX_RETRIES, ++this.splashscreenInvocationToken);
605
+
606
+ addSplashscreenLoaderIfNeeded();
607
+ scheduleSplashscreenTimeout();
608
+ }
609
+
610
+ private void invokeSplashScreenPluginMethod(
611
+ final String methodName,
612
+ final JSObject options,
613
+ final int retriesRemaining,
614
+ final int requestToken
615
+ ) {
616
+ if (requestToken != this.splashscreenInvocationToken) {
617
+ return;
618
+ }
619
+
606
620
  try {
607
- if (getBridge() == null) {
608
- logger.warn("Bridge not ready for showing splashscreen with autoSplashscreen");
609
- } else {
610
- PluginHandle splashScreenPlugin = getBridge().getPlugin("SplashScreen");
611
- if (splashScreenPlugin != null) {
612
- JSObject options = new JSObject();
613
- java.lang.reflect.Field msgHandlerField = getBridge().getClass().getDeclaredField("msgHandler");
614
- msgHandlerField.setAccessible(true);
615
- Object msgHandler = msgHandlerField.get(getBridge());
616
-
617
- PluginCall call = new PluginCall(
618
- (com.getcapacitor.MessageHandler) msgHandler,
619
- "SplashScreen",
620
- "FAKE_CALLBACK_ID_SHOW",
621
- "show",
622
- options
623
- );
621
+ final Bridge bridge = getBridge();
622
+ if (bridge == null) {
623
+ retrySplashScreenInvocation(
624
+ methodName,
625
+ options,
626
+ retriesRemaining,
627
+ requestToken,
628
+ "Bridge not ready for " + ("show".equals(methodName) ? "showing" : "hiding") + " splashscreen"
629
+ );
630
+ return;
631
+ }
624
632
 
625
- splashScreenPlugin.invoke("show", call);
626
- logger.info("Splashscreen shown synchronously to prevent flash");
627
- } else {
628
- logger.warn("autoSplashscreen: SplashScreen plugin not found");
629
- }
633
+ final PluginHandle splashScreenPlugin = bridge.getPlugin(SPLASH_SCREEN_PLUGIN_ID);
634
+ if (splashScreenPlugin == null) {
635
+ retrySplashScreenInvocation(
636
+ methodName,
637
+ options,
638
+ retriesRemaining,
639
+ requestToken,
640
+ "autoSplashscreen: SplashScreen plugin not found. Install @capacitor/splash-screen plugin."
641
+ );
642
+ return;
630
643
  }
631
- } catch (Exception e) {
632
- logger.error("Failed to show splashscreen synchronously: " + e.getMessage());
644
+
645
+ splashScreenPlugin.invoke(methodName, new FireAndForgetPluginCall(methodName, options));
646
+ logger.info("Splashscreen " + methodName + " invoked automatically");
647
+ } catch (final Exception e) {
648
+ retrySplashScreenInvocation(
649
+ methodName,
650
+ options,
651
+ retriesRemaining,
652
+ requestToken,
653
+ "Failed to call SplashScreen " + methodName + " method: " + e.getMessage()
654
+ );
633
655
  }
656
+ }
634
657
 
635
- addSplashscreenLoaderIfNeeded();
636
- scheduleSplashscreenTimeout();
658
+ private void retrySplashScreenInvocation(
659
+ final String methodName,
660
+ final JSObject options,
661
+ final int retriesRemaining,
662
+ final int requestToken,
663
+ final String message
664
+ ) {
665
+ if (retriesRemaining > 0) {
666
+ logger.info(message + ". Retrying.");
667
+ this.mainHandler.postDelayed(
668
+ () -> invokeSplashScreenPluginMethod(methodName, options, retriesRemaining - 1, requestToken),
669
+ SPLASH_SCREEN_RETRY_DELAY_MS
670
+ );
671
+ return;
672
+ }
673
+
674
+ if ("show".equals(methodName)) {
675
+ logger.warn(message);
676
+ } else {
677
+ logger.error(message);
678
+ }
679
+ }
680
+
681
+ boolean isCurrentSplashscreenInvocationTokenForTesting(final int requestToken) {
682
+ return requestToken == this.splashscreenInvocationToken;
637
683
  }
638
684
 
639
685
  private void addSplashscreenLoaderIfNeeded() {
@@ -774,14 +820,58 @@ public class CapacitorUpdaterPlugin extends Plugin {
774
820
  return plannedDirectUpdate && !Boolean.TRUE.equals(this.autoSplashscreenTimedOut);
775
821
  }
776
822
 
823
+ static boolean shouldConsumeOnLaunchDirectUpdate(final String directUpdateMode, final boolean plannedDirectUpdate) {
824
+ return plannedDirectUpdate && "onLaunch".equals(directUpdateMode);
825
+ }
826
+
827
+ private void consumeOnLaunchDirectUpdateAttempt(final boolean plannedDirectUpdate) {
828
+ if (!shouldConsumeOnLaunchDirectUpdate(this.directUpdateMode, plannedDirectUpdate)) {
829
+ return;
830
+ }
831
+
832
+ this.onLaunchDirectUpdateUsed = true;
833
+ }
834
+
835
+ void configureDirectUpdateModeForTesting(final String directUpdateMode, final boolean onLaunchDirectUpdateUsed) {
836
+ this.directUpdateMode = directUpdateMode;
837
+ this.onLaunchDirectUpdateUsed = onLaunchDirectUpdateUsed;
838
+ }
839
+
840
+ boolean shouldUseDirectUpdateForTesting() {
841
+ return this.shouldUseDirectUpdate();
842
+ }
843
+
844
+ boolean hasConsumedOnLaunchDirectUpdateForTesting() {
845
+ return this.onLaunchDirectUpdateUsed;
846
+ }
847
+
848
+ boolean isVersionDownloadInProgress(final String version) {
849
+ return (
850
+ version != null &&
851
+ !version.isEmpty() &&
852
+ this.implementation != null &&
853
+ this.implementation.activity != null &&
854
+ DownloadWorkerManager.isVersionDownloading(this.implementation.activity, version)
855
+ );
856
+ }
857
+
858
+ void setLoggerForTesting(final Logger logger) {
859
+ this.logger = logger;
860
+ }
861
+
862
+ void completeBackgroundTaskForTesting(final BundleInfo current, final boolean plannedDirectUpdate) {
863
+ this.endBackGroundTaskWithNotif("test", current.getVersionName(), current, false, plannedDirectUpdate);
864
+ }
865
+
777
866
  private void directUpdateFinish(final BundleInfo latest) {
778
867
  if ("onLaunch".equals(this.directUpdateMode)) {
779
868
  this.onLaunchDirectUpdateUsed = true;
780
869
  this.implementation.directUpdate = false;
781
870
  }
782
- CapacitorUpdaterPlugin.this.implementation.set(latest);
783
- CapacitorUpdaterPlugin.this._reload();
784
- sendReadyToJs(latest, "update installed", true);
871
+ if (CapacitorUpdaterPlugin.this.implementation.set(latest) && CapacitorUpdaterPlugin.this._reload()) {
872
+ this.notifyBundleSet(latest);
873
+ sendReadyToJs(latest, "update installed", true);
874
+ }
785
875
  }
786
876
 
787
877
  private void cleanupObsoleteVersions() {
@@ -1401,9 +1491,13 @@ public class CapacitorUpdaterPlugin extends Plugin {
1401
1491
  if (!this.implementation.set(id)) {
1402
1492
  logger.info("No such bundle " + id);
1403
1493
  call.reject("Update failed, id " + id + " does not exist.");
1494
+ } else if (!this._reload()) {
1495
+ logger.error("Reload failed after setting bundle " + id);
1496
+ call.reject("Reload failed after setting bundle " + id);
1404
1497
  } else {
1405
1498
  logger.info("Bundle successfully set to " + id);
1406
- this.reload(call);
1499
+ this.notifyBundleSet(this.implementation.getBundleInfo(id));
1500
+ call.resolve();
1407
1501
  }
1408
1502
  } catch (final Exception e) {
1409
1503
  logger.error("Could not set id " + id + " " + e.getMessage());
@@ -1517,7 +1611,11 @@ public class CapacitorUpdaterPlugin extends Plugin {
1517
1611
 
1518
1612
  if (toLastSuccessful && !fallback.isBuiltin()) {
1519
1613
  logger.info("Resetting to: " + fallback);
1520
- return this.implementation.set(fallback) && this._reload();
1614
+ if (this.implementation.set(fallback) && this._reload()) {
1615
+ this.notifyBundleSet(fallback);
1616
+ return true;
1617
+ }
1618
+ return false;
1521
1619
  }
1522
1620
 
1523
1621
  logger.info("Resetting to native.");
@@ -1786,11 +1884,12 @@ public class CapacitorUpdaterPlugin extends Plugin {
1786
1884
  String latestVersionName,
1787
1885
  BundleInfo current,
1788
1886
  Boolean error,
1789
- Boolean isDirectUpdate,
1887
+ Boolean plannedDirectUpdate,
1790
1888
  String failureAction,
1791
1889
  String failureEvent,
1792
1890
  boolean shouldSendStats
1793
1891
  ) {
1892
+ this.consumeOnLaunchDirectUpdateAttempt(Boolean.TRUE.equals(plannedDirectUpdate));
1794
1893
  if (error) {
1795
1894
  logger.info(
1796
1895
  "endBackGroundTaskWithNotif error: " +
@@ -1810,7 +1909,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
1810
1909
  final JSObject ret = new JSObject();
1811
1910
  ret.put("bundle", InternalUtils.mapToJSObject(current.toJSONMap()));
1812
1911
  this.notifyListeners("noNeedUpdate", ret);
1813
- this.sendReadyToJs(current, msg, isDirectUpdate);
1912
+ this.sendReadyToJs(current, msg, plannedDirectUpdate);
1814
1913
  this.backgroundDownloadTask = null;
1815
1914
  this.downloadStartTimeMs = 0;
1816
1915
  logger.info("endBackGroundTaskWithNotif " + msg);
@@ -1844,7 +1943,6 @@ public class CapacitorUpdaterPlugin extends Plugin {
1844
1943
  private Thread backgroundDownload() {
1845
1944
  final boolean plannedDirectUpdate = this.shouldUseDirectUpdate();
1846
1945
  final boolean initialDirectUpdateAllowed = this.isDirectUpdateCurrentlyAllowed(plannedDirectUpdate);
1847
- this.implementation.directUpdate = initialDirectUpdateAllowed;
1848
1946
  final String messageUpdate = initialDirectUpdateAllowed
1849
1947
  ? "Update will occur now."
1850
1948
  : "Update will occur next time app moves to background.";
@@ -1912,7 +2010,8 @@ public class CapacitorUpdaterPlugin extends Plugin {
1912
2010
  "Next update will be to builtin version",
1913
2011
  latestVersionName,
1914
2012
  current,
1915
- false
2013
+ false,
2014
+ plannedDirectUpdate
1916
2015
  );
1917
2016
  }
1918
2017
  return;
@@ -1948,7 +2047,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
1948
2047
  );
1949
2048
  return;
1950
2049
  }
1951
- if (latest.isDownloaded()) {
2050
+ if (latest.isDownloaded() && BundleStatus.DOWNLOADING != latest.getStatus()) {
1952
2051
  logger.info("Latest bundle already exists and download is NOT required. " + messageUpdate);
1953
2052
  final boolean directUpdateAllowedNow = CapacitorUpdaterPlugin.this.isDirectUpdateCurrentlyAllowed(
1954
2053
  plannedDirectUpdate
@@ -1969,15 +2068,26 @@ public class CapacitorUpdaterPlugin extends Plugin {
1969
2068
  );
1970
2069
  return;
1971
2070
  }
1972
- CapacitorUpdaterPlugin.this.implementation.set(latest);
1973
- CapacitorUpdaterPlugin.this._reload();
1974
- CapacitorUpdaterPlugin.this.endBackGroundTaskWithNotif(
1975
- "Update installed",
1976
- latestVersionName,
1977
- latest,
1978
- false,
1979
- true
1980
- );
2071
+ if (
2072
+ CapacitorUpdaterPlugin.this.implementation.set(latest) && CapacitorUpdaterPlugin.this._reload()
2073
+ ) {
2074
+ CapacitorUpdaterPlugin.this.notifyBundleSet(latest);
2075
+ CapacitorUpdaterPlugin.this.endBackGroundTaskWithNotif(
2076
+ "Update installed",
2077
+ latestVersionName,
2078
+ latest,
2079
+ false,
2080
+ true
2081
+ );
2082
+ } else {
2083
+ CapacitorUpdaterPlugin.this.endBackGroundTaskWithNotif(
2084
+ "Update install failed",
2085
+ latestVersionName,
2086
+ latest,
2087
+ true,
2088
+ true
2089
+ );
2090
+ }
1981
2091
  } else {
1982
2092
  if (plannedDirectUpdate && !directUpdateAllowedNow) {
1983
2093
  logger.info(
@@ -1990,7 +2100,8 @@ public class CapacitorUpdaterPlugin extends Plugin {
1990
2100
  "update downloaded, will install next background",
1991
2101
  latestVersionName,
1992
2102
  latest,
1993
- false
2103
+ false,
2104
+ plannedDirectUpdate
1994
2105
  );
1995
2106
  }
1996
2107
  return;
@@ -2007,6 +2118,14 @@ public class CapacitorUpdaterPlugin extends Plugin {
2007
2118
  }
2008
2119
  }
2009
2120
  }
2121
+ final boolean retryingInFlightDownload =
2122
+ latest != null &&
2123
+ BundleStatus.DOWNLOADING == latest.getStatus() &&
2124
+ CapacitorUpdaterPlugin.this.isVersionDownloadInProgress(latest.getVersionName());
2125
+ CapacitorUpdaterPlugin.this.consumeOnLaunchDirectUpdateAttempt(plannedDirectUpdate);
2126
+ CapacitorUpdaterPlugin.this.implementation.directUpdate = retryingInFlightDownload
2127
+ ? Boolean.TRUE.equals(CapacitorUpdaterPlugin.this.implementation.directUpdate) || initialDirectUpdateAllowed
2128
+ : initialDirectUpdateAllowed;
2010
2129
  startNewThread(() -> {
2011
2130
  try {
2012
2131
  logger.info(
@@ -2055,7 +2174,13 @@ public class CapacitorUpdaterPlugin extends Plugin {
2055
2174
  });
2056
2175
  } else {
2057
2176
  logger.info("No need to update, " + current.getId() + " is the latest bundle.");
2058
- CapacitorUpdaterPlugin.this.endBackGroundTaskWithNotif("No need to update", latestVersionName, current, false);
2177
+ CapacitorUpdaterPlugin.this.endBackGroundTaskWithNotif(
2178
+ "No need to update",
2179
+ latestVersionName,
2180
+ current,
2181
+ false,
2182
+ plannedDirectUpdate
2183
+ );
2059
2184
  }
2060
2185
  } catch (final Exception e) {
2061
2186
  logger.error("error in update check " + e.getMessage());
@@ -2101,6 +2226,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
2101
2226
  logger.debug("Next bundle is: " + next.getVersionName());
2102
2227
  if (this.implementation.set(next) && this._reload()) {
2103
2228
  logger.info("Updated to bundle: " + next.getVersionName());
2229
+ this.notifyBundleSet(next);
2104
2230
  this.implementation.setNextBundle(null);
2105
2231
  } else {
2106
2232
  logger.error("Update to bundle: " + next.getVersionName() + " Failed!");
package/dist/docs.json CHANGED
@@ -976,6 +976,35 @@
976
976
  ],
977
977
  "slug": "addlistenerupdatefailed-"
978
978
  },
979
+ {
980
+ "name": "addListener",
981
+ "signature": "(eventName: 'set', listenerFunc: (state: SetEvent) => void) => Promise<PluginListenerHandle>",
982
+ "parameters": [
983
+ {
984
+ "name": "eventName",
985
+ "docs": "",
986
+ "type": "'set'"
987
+ },
988
+ {
989
+ "name": "listenerFunc",
990
+ "docs": "",
991
+ "type": "(state: SetEvent) => void"
992
+ }
993
+ ],
994
+ "returns": "Promise<PluginListenerHandle>",
995
+ "tags": [
996
+ {
997
+ "name": "since",
998
+ "text": "8.43.12"
999
+ }
1000
+ ],
1001
+ "docs": "Listen for set event in the App, let you know when a bundle has been applied successfully.\nThis event is retained natively until JavaScript consumes it, so if the app reloads before your\nlistener is attached, the last pending `set` event is delivered once the listener subscribes.",
1002
+ "complexTypes": [
1003
+ "PluginListenerHandle",
1004
+ "SetEvent"
1005
+ ],
1006
+ "slug": "addlistenerset-"
1007
+ },
979
1008
  {
980
1009
  "name": "addListener",
981
1010
  "signature": "(eventName: 'setNext', listenerFunc: (state: SetNextEvent) => void) => Promise<PluginListenerHandle>",
@@ -1084,7 +1113,7 @@
1084
1113
  "text": "5.1.0"
1085
1114
  }
1086
1115
  ],
1087
- "docs": "Listen for app ready event in the App, let you know when app is ready to use, this event is retain till consumed.",
1116
+ "docs": "Listen for app ready event in the App, let you know when app is ready to use.\nThis event is retained natively until JavaScript consumes it, so it can still be delivered after\na reload even if the listener is attached later in app startup.",
1088
1117
  "complexTypes": [
1089
1118
  "PluginListenerHandle",
1090
1119
  "AppReadyEvent"
@@ -2528,6 +2557,29 @@
2528
2557
  }
2529
2558
  ]
2530
2559
  },
2560
+ {
2561
+ "name": "SetEvent",
2562
+ "slug": "setevent",
2563
+ "docs": "",
2564
+ "tags": [],
2565
+ "methods": [],
2566
+ "properties": [
2567
+ {
2568
+ "name": "bundle",
2569
+ "tags": [
2570
+ {
2571
+ "text": "8.43.12",
2572
+ "name": "since"
2573
+ }
2574
+ ],
2575
+ "docs": "Emit when a bundle has been applied successfully.\nThis event uses native `retainUntilConsumed` behavior.",
2576
+ "complexTypes": [
2577
+ "BundleInfo"
2578
+ ],
2579
+ "type": "BundleInfo"
2580
+ }
2581
+ ]
2582
+ },
2531
2583
  {
2532
2584
  "name": "SetNextEvent",
2533
2585
  "slug": "setnextevent",
@@ -2587,7 +2639,7 @@
2587
2639
  "name": "since"
2588
2640
  }
2589
2641
  ],
2590
- "docs": "Emitted when the app is ready to use.",
2642
+ "docs": "Emitted when the app is ready to use.\nThis event uses native `retainUntilConsumed` behavior.",
2591
2643
  "complexTypes": [
2592
2644
  "BundleInfo"
2593
2645
  ],