@capgo/capacitor-updater 8.45.9 → 8.45.11

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
@@ -462,6 +462,7 @@ export default config;
462
462
  * [`removeAllListeners()`](#removealllisteners)
463
463
  * [`addListener('download', ...)`](#addlistenerdownload-)
464
464
  * [`addListener('noNeedUpdate', ...)`](#addlistenernoneedupdate-)
465
+ * [`addListener('updateCheckResult', ...)`](#addlistenerupdatecheckresult-)
465
466
  * [`addListener('updateAvailable', ...)`](#addlistenerupdateavailable-)
466
467
  * [`addListener('downloadComplete', ...)`](#addlistenerdownloadcomplete-)
467
468
  * [`addListener('breakingAvailable', ...)`](#addlistenerbreakingavailable-)
@@ -929,27 +930,23 @@ After receiving the latest version info, you can:
929
930
  2. Download it using {@link download}
930
931
  3. Apply it using {@link next} or {@link set}
931
932
 
932
- **Important: Error handling for "no new version available"**
933
+ **Important: Handling "no new version available"**
933
934
 
934
935
  When the device's current version matches the latest version on the server (i.e., the device is already
935
936
  up-to-date), the server returns a 200 response with `error: "no_new_version_available"` and
936
- `message: "No new version available"`. **This causes `getLatest()` to throw an error**, even though
937
- this is a normal, expected condition.
937
+ `message: "No new version available"`. This is a normal, expected condition and resolves with
938
+ `kind: "up_to_date"` when the backend provides that classification.
938
939
 
939
- You should catch this specific error to handle it gracefully:
940
+ You should check `kind` and `error` before attempting to download:
940
941
 
941
942
  ```typescript
942
- try {
943
- const latest = await CapacitorUpdater.getLatest();
943
+ const latest = await CapacitorUpdater.getLatest();
944
+ if (latest.kind === 'up_to_date') {
945
+ console.log('Already up to date');
946
+ } else if (latest.kind === 'blocked') {
947
+ console.log('Update is blocked:', latest.error);
948
+ } else if (latest.url) {
944
949
  // New version is available, proceed with download
945
- } catch (error) {
946
- if (error.message === 'No new version available') {
947
- // Device is already on the latest version - this is normal
948
- console.log('Already up to date');
949
- } else {
950
- // Actual error occurred
951
- console.error('Failed to check for updates:', error);
952
- }
953
950
  }
954
951
  ```
955
952
 
@@ -1251,6 +1248,7 @@ Remove all event listeners registered for this plugin.
1251
1248
  This unregisters all listeners added via {@link addListener} for all event types:
1252
1249
  - `download`
1253
1250
  - `noNeedUpdate`
1251
+ - `updateCheckResult`
1254
1252
  - `updateAvailable`
1255
1253
  - `downloadComplete`
1256
1254
  - `downloadFailed`
@@ -1308,6 +1306,31 @@ Listen for no need to update event, useful when you want force check every time
1308
1306
  --------------------
1309
1307
 
1310
1308
 
1309
+ #### addListener('updateCheckResult', ...)
1310
+
1311
+ ```typescript
1312
+ addListener(eventName: 'updateCheckResult', listenerFunc: (state: UpdateCheckResultEvent) => void) => Promise<PluginListenerHandle>
1313
+ ```
1314
+
1315
+ Listen for update check results before the updater decides whether to download.
1316
+ The backend can classify the <a href="#updatecheckresultevent">UpdateCheckResultEvent</a> payload as `up_to_date`, `blocked`, or `failed`.
1317
+
1318
+ This event is emitted alongside legacy events. For `up_to_date` and `blocked`, it is emitted before
1319
+ `noNeedUpdate` and does not emit `downloadFailed`. For `failed`, it is emitted before the legacy
1320
+ `downloadFailed` event and keeps the existing failure stats behavior.
1321
+
1322
+ | Param | Type |
1323
+ | ------------------ | --------------------------------------------------------------------------------------------- |
1324
+ | **`eventName`** | <code>'updateCheckResult'</code> |
1325
+ | **`listenerFunc`** | <code>(state: <a href="#updatecheckresultevent">UpdateCheckResultEvent</a>) =&gt; void</code> |
1326
+
1327
+ **Returns:** <code>Promise&lt;<a href="#pluginlistenerhandle">PluginListenerHandle</a>&gt;</code>
1328
+
1329
+ **Since:** 8.45.11
1330
+
1331
+ --------------------
1332
+
1333
+
1311
1334
  #### addListener('updateAvailable', ...)
1312
1335
 
1313
1336
  ```typescript
@@ -2094,20 +2117,22 @@ If you don't use backend, you need to provide the URL and version of the bundle.
2094
2117
 
2095
2118
  ##### LatestVersion
2096
2119
 
2097
- | Prop | Type | Description | Since |
2098
- | ---------------- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ |
2099
- | **`version`** | <code>string</code> | Result of getLatest method | 4.0.0 |
2100
- | **`checksum`** | <code>string</code> | | 6 |
2101
- | **`breaking`** | <code>boolean</code> | Indicates whether the update was flagged as breaking by the backend. | 7.22.0 |
2102
- | **`major`** | <code>boolean</code> | | |
2103
- | **`message`** | <code>string</code> | Optional message from the server. When no new version is available, this will be "No new version available". | |
2104
- | **`sessionKey`** | <code>string</code> | | |
2105
- | **`error`** | <code>string</code> | Error code from the server, if any. Common values: - `"no_new_version_available"`: Device is already on the latest version (not a failure) - Other error codes indicate actual failures in the update process | |
2106
- | **`old`** | <code>string</code> | The previous/current version name (provided for reference). | |
2107
- | **`url`** | <code>string</code> | Download URL for the bundle (when a new version is available). | |
2108
- | **`manifest`** | <code>ManifestEntry[]</code> | File list for delta updates (when using multi-file downloads). | 6.1 |
2109
- | **`link`** | <code>string</code> | Optional link associated with this bundle version (e.g., release notes URL, changelog, GitHub release). | 7.35.0 |
2110
- | **`comment`** | <code>string</code> | Optional comment or description for this bundle version. | 7.35.0 |
2120
+ | Prop | Type | Description | Since |
2121
+ | ---------------- | ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | ------- |
2122
+ | **`version`** | <code>string</code> | Result of getLatest method | 4.0.0 |
2123
+ | **`checksum`** | <code>string</code> | | 6 |
2124
+ | **`breaking`** | <code>boolean</code> | Indicates whether the update was flagged as breaking by the backend. | 7.22.0 |
2125
+ | **`major`** | <code>boolean</code> | | |
2126
+ | **`message`** | <code>string</code> | Optional message from the server. When no new version is available, this will be "No new version available". | |
2127
+ | **`sessionKey`** | <code>string</code> | | |
2128
+ | **`error`** | <code>string</code> | Error code from the server, if any. Use `kind` for classification instead of parsing this value. | |
2129
+ | **`kind`** | <code><a href="#updateresponsekind">UpdateResponseKind</a></code> | Classification for this response, provided by the backend. | 8.45.11 |
2130
+ | **`statusCode`** | <code>number</code> | HTTP status code returned by the update server for classified update-check responses. | 8.45.11 |
2131
+ | **`old`** | <code>string</code> | The previous/current version name (provided for reference). | |
2132
+ | **`url`** | <code>string</code> | Download URL for the bundle (when a new version is available). | |
2133
+ | **`manifest`** | <code>ManifestEntry[]</code> | File list for delta updates (when using multi-file downloads). | 6.1 |
2134
+ | **`link`** | <code>string</code> | Optional link associated with this bundle version (e.g., release notes URL, changelog, GitHub release). | 7.35.0 |
2135
+ | **`comment`** | <code>string</code> | Optional comment or description for this bundle version. | 7.35.0 |
2111
2136
 
2112
2137
 
2113
2138
  ##### GetLatestOptions
@@ -2226,6 +2251,18 @@ If you don't use backend, you need to provide the URL and version of the bundle.
2226
2251
  | **`bundle`** | <code><a href="#bundleinfo">BundleInfo</a></code> | Current status of download, between 0 and 100. | 4.0.0 |
2227
2252
 
2228
2253
 
2254
+ ##### UpdateCheckResultEvent
2255
+
2256
+ | Prop | Type | Description | Since |
2257
+ | ---------------- | ----------------------------------------------------------------- | -------------------------------------------------------------------- | ------- |
2258
+ | **`kind`** | <code><a href="#updateresponsekind">UpdateResponseKind</a></code> | Classification for the update check result, provided by the backend. | 8.45.11 |
2259
+ | **`error`** | <code>string</code> | Backend error code, when provided. | 8.45.11 |
2260
+ | **`message`** | <code>string</code> | Backend message, when provided. | 8.45.11 |
2261
+ | **`statusCode`** | <code>number</code> | HTTP status code returned by the update endpoint. | 8.45.11 |
2262
+ | **`version`** | <code>string</code> | Version referenced by the update check result. | 8.45.11 |
2263
+ | **`bundle`** | <code><a href="#bundleinfo">BundleInfo</a></code> | Current bundle on the device. | 8.45.11 |
2264
+
2265
+
2229
2266
  ##### UpdateAvailableEvent
2230
2267
 
2231
2268
  | Prop | Type | Description | Since |
@@ -2417,6 +2454,15 @@ error: The bundle has failed to download.
2417
2454
  <code>'background' | 'kill' | 'nativeVersion' | 'date'</code>
2418
2455
 
2419
2456
 
2457
+ ##### UpdateResponseKind
2458
+
2459
+ Classification for update-check responses that do not provide a downloadable bundle.
2460
+ The update backend provides this field directly. Missing or unknown values are treated as
2461
+ failed by native clients.
2462
+
2463
+ <code>'up_to_date' | 'blocked' | 'failed'</code>
2464
+
2465
+
2420
2466
  ##### BreakingAvailableEvent
2421
2467
 
2422
2468
  Payload emitted by {@link CapacitorUpdaterPlugin.addListener} with `breakingAvailable`.
@@ -54,7 +54,7 @@ dependencies {
54
54
  implementation "androidx.work:work-runtime:$work_version"
55
55
  implementation "androidx.lifecycle:lifecycle-process:$lifecycle_version"
56
56
  implementation "com.google.android.gms:play-services-tasks:18.4.1"
57
- implementation "com.google.guava:guava:33.5.0-android"
57
+ implementation "com.google.guava:guava:33.6.0-android"
58
58
  implementation fileTree(dir: 'libs', include: ['*.jar'])
59
59
  implementation project(':capacitor-android')
60
60
  implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
@@ -50,7 +50,6 @@ import java.io.IOException;
50
50
  import java.net.MalformedURLException;
51
51
  import java.net.URL;
52
52
  import java.util.ArrayList;
53
- import java.util.Arrays;
54
53
  import java.util.Date;
55
54
  import java.util.HashSet;
56
55
  import java.util.List;
@@ -90,7 +89,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
90
89
  private static final int SPLASH_SCREEN_MAX_RETRIES = 20;
91
90
  private static final long PENDING_BUNDLE_APP_READY_MIN_TIMEOUT_MS = 30000L;
92
91
 
93
- private final String pluginVersion = "8.45.9";
92
+ private final String pluginVersion = "8.45.11";
94
93
  private static final String DELAY_CONDITION_PREFERENCES = "";
95
94
 
96
95
  private SharedPreferences.Editor editor;
@@ -1293,6 +1292,23 @@ public class CapacitorUpdaterPlugin extends Plugin {
1293
1292
  startNewThread(() ->
1294
1293
  CapacitorUpdaterPlugin.this.implementation.listChannels((res) -> {
1295
1294
  JSObject jsRes = InternalUtils.mapToJSObject(res);
1295
+ Object channels = res.get("channels");
1296
+ if (channels instanceof List<?> channelsList) {
1297
+ JSArray channelsArray = new JSArray();
1298
+ for (Object channel : channelsList) {
1299
+ if (channel instanceof Map<?, ?> channelMap) {
1300
+ JSObject channelObject = new JSObject();
1301
+ for (Map.Entry<?, ?> entry : channelMap.entrySet()) {
1302
+ Object key = entry.getKey();
1303
+ if (key != null) {
1304
+ channelObject.put(key.toString(), entry.getValue());
1305
+ }
1306
+ }
1307
+ channelsArray.put(channelObject);
1308
+ }
1309
+ }
1310
+ jsRes.put("channels", channelsArray);
1311
+ }
1296
1312
  if (jsRes.has("error")) {
1297
1313
  String errorMessage = jsRes.has("message") ? jsRes.getString("message") : jsRes.getString("error");
1298
1314
  String errorCode = jsRes.getString("error");
@@ -1505,49 +1521,51 @@ public class CapacitorUpdaterPlugin extends Plugin {
1505
1521
 
1506
1522
  @PluginMethod
1507
1523
  public void reload(final PluginCall call) {
1508
- try {
1509
- final BundleInfo current = this.implementation.getCurrentBundle();
1510
- final BundleInfo next = this.implementation.getNextBundle();
1511
-
1512
- if (next != null && !next.isErrorStatus() && !next.getId().equals(current.getId())) {
1513
- final CapgoUpdater.ResetState previousState = this.implementation.captureResetState();
1514
- final String previousBundleName = this.implementation.getCurrentBundle().getVersionName();
1515
- logger.info("Applying pending bundle before reload: " + next.getVersionName());
1516
- final boolean didApplyPendingBundle;
1517
- if (next.isBuiltin()) {
1518
- this.implementation.prepareResetStateForTransition();
1519
- didApplyPendingBundle = true;
1520
- } else {
1521
- didApplyPendingBundle = this.implementation.stagePendingReload(next);
1522
- }
1523
- if (didApplyPendingBundle && this._reload()) {
1524
+ startNewThread(() -> {
1525
+ try {
1526
+ final BundleInfo current = this.implementation.getCurrentBundle();
1527
+ final BundleInfo next = this.implementation.getNextBundle();
1528
+
1529
+ if (next != null && !next.isErrorStatus() && !next.getId().equals(current.getId())) {
1530
+ final CapgoUpdater.ResetState previousState = this.implementation.captureResetState();
1531
+ final String previousBundleName = this.implementation.getCurrentBundle().getVersionName();
1532
+ logger.info("Applying pending bundle before reload: " + next.getVersionName());
1533
+ final boolean didApplyPendingBundle;
1524
1534
  if (next.isBuiltin()) {
1525
- this.implementation.finalizeResetTransition(previousBundleName, false);
1535
+ this.implementation.prepareResetStateForTransition();
1536
+ didApplyPendingBundle = true;
1526
1537
  } else {
1527
- this.implementation.finalizePendingReload(next, previousBundleName);
1538
+ didApplyPendingBundle = this.implementation.stagePendingReload(next);
1528
1539
  }
1529
- this.notifyBundleSet(next);
1530
- this.implementation.setNextBundle(null);
1531
- call.resolve();
1540
+ if (didApplyPendingBundle && this._reload()) {
1541
+ if (next.isBuiltin()) {
1542
+ this.implementation.finalizeResetTransition(previousBundleName, false);
1543
+ } else {
1544
+ this.implementation.finalizePendingReload(next, previousBundleName);
1545
+ }
1546
+ this.notifyBundleSet(next);
1547
+ this.implementation.setNextBundle(null);
1548
+ call.resolve();
1549
+ return;
1550
+ }
1551
+ this.implementation.restoreResetState(previousState);
1552
+ this.restoreLiveBundleStateAfterFailedReload();
1553
+ logger.error("Reload failed after applying pending bundle: " + next.getVersionName());
1554
+ call.reject("Reload failed after applying pending bundle: " + next.getVersionName());
1532
1555
  return;
1533
1556
  }
1534
- this.implementation.restoreResetState(previousState);
1535
- this.restoreLiveBundleStateAfterFailedReload();
1536
- logger.error("Reload failed after applying pending bundle: " + next.getVersionName());
1537
- call.reject("Reload failed after applying pending bundle: " + next.getVersionName());
1538
- return;
1539
- }
1540
1557
 
1541
- if (this._reload()) {
1542
- call.resolve();
1543
- } else {
1544
- logger.error("Reload failed");
1545
- call.reject("Reload failed");
1558
+ if (this._reload()) {
1559
+ call.resolve();
1560
+ } else {
1561
+ logger.error("Reload failed");
1562
+ call.reject("Reload failed");
1563
+ }
1564
+ } catch (final Exception e) {
1565
+ logger.error("Could not reload " + e.getMessage());
1566
+ call.reject("Could not reload", e);
1546
1567
  }
1547
- } catch (final Exception e) {
1548
- logger.error("Could not reload " + e.getMessage());
1549
- call.reject("Could not reload", e);
1550
- }
1568
+ });
1551
1569
  }
1552
1570
 
1553
1571
  @PluginMethod
@@ -1580,23 +1598,25 @@ public class CapacitorUpdaterPlugin extends Plugin {
1580
1598
  call.reject("Set called without id");
1581
1599
  return;
1582
1600
  }
1583
- try {
1584
- logger.info("Setting active bundle " + id);
1585
- if (!this.implementation.set(id)) {
1586
- logger.info("No such bundle " + id);
1587
- call.reject("Update failed, id " + id + " does not exist.");
1588
- } else if (!this._reload()) {
1589
- logger.error("Reload failed after setting bundle " + id);
1590
- call.reject("Reload failed after setting bundle " + id);
1591
- } else {
1592
- logger.info("Bundle successfully set to " + id);
1593
- this.notifyBundleSet(this.implementation.getBundleInfo(id));
1594
- call.resolve();
1601
+ startNewThread(() -> {
1602
+ try {
1603
+ logger.info("Setting active bundle " + id);
1604
+ if (!this.implementation.set(id)) {
1605
+ logger.info("No such bundle " + id);
1606
+ call.reject("Update failed, id " + id + " does not exist.");
1607
+ } else if (!this._reload()) {
1608
+ logger.error("Reload failed after setting bundle " + id);
1609
+ call.reject("Reload failed after setting bundle " + id);
1610
+ } else {
1611
+ logger.info("Bundle successfully set to " + id);
1612
+ this.notifyBundleSet(this.implementation.getBundleInfo(id));
1613
+ call.resolve();
1614
+ }
1615
+ } catch (final Exception e) {
1616
+ logger.error("Could not set id " + id + " " + e.getMessage());
1617
+ call.reject("Could not set id " + id, e);
1595
1618
  }
1596
- } catch (final Exception e) {
1597
- logger.error("Could not set id " + id + " " + e.getMessage());
1598
- call.reject("Could not set id " + id, e);
1599
- }
1619
+ });
1600
1620
  }
1601
1621
 
1602
1622
  @PluginMethod
@@ -1683,11 +1703,21 @@ public class CapacitorUpdaterPlugin extends Plugin {
1683
1703
  startNewThread(() ->
1684
1704
  CapacitorUpdaterPlugin.this.implementation.getLatest(CapacitorUpdaterPlugin.this.updateUrl, channel, (res) -> {
1685
1705
  JSObject jsRes = InternalUtils.mapToJSObject(res);
1686
- if (jsRes.has("error")) {
1687
- String error = jsRes.getString("error");
1706
+ if (jsRes.has("error") || jsRes.has("kind")) {
1707
+ String error = jsRes.has("error") ? jsRes.getString("error") : "";
1688
1708
  String errorMessage = jsRes.has("message") ? jsRes.getString("message") : "server did not provide a message";
1689
- logger.error("getLatest failed with error: " + error + ", message: " + errorMessage);
1690
- call.reject(jsRes.getString("error"));
1709
+ String kind = CapacitorUpdaterPlugin.this.getUpdateResponseKind(jsRes.has("kind") ? jsRes.getString("kind") : null);
1710
+ jsRes.put("kind", kind);
1711
+ if ("failed".equals(kind)) {
1712
+ logger.error("getLatest failed with error: " + error + ", message: " + errorMessage);
1713
+ call.reject(error.isEmpty() ? errorMessage : error);
1714
+ } else {
1715
+ if (!jsRes.has("version") || jsRes.getString("version").isEmpty()) {
1716
+ jsRes.put("version", CapacitorUpdaterPlugin.this.implementation.getCurrentBundle().getVersionName());
1717
+ }
1718
+ logger.info("getLatest returned " + kind + ": " + errorMessage);
1719
+ call.resolve(jsRes);
1720
+ }
1691
1721
  return;
1692
1722
  } else if (jsRes.has("message")) {
1693
1723
  call.reject(jsRes.getString("message"));
@@ -1772,19 +1802,21 @@ public class CapacitorUpdaterPlugin extends Plugin {
1772
1802
 
1773
1803
  @PluginMethod
1774
1804
  public void reset(final PluginCall call) {
1775
- try {
1776
- final Boolean toLastSuccessful = call.getBoolean("toLastSuccessful", false);
1777
- final Boolean usePendingBundle = call.getBoolean("usePendingBundle", false);
1778
- if (this._reset(toLastSuccessful, usePendingBundle)) {
1779
- call.resolve();
1780
- return;
1805
+ startNewThread(() -> {
1806
+ try {
1807
+ final Boolean toLastSuccessful = call.getBoolean("toLastSuccessful", false);
1808
+ final Boolean usePendingBundle = call.getBoolean("usePendingBundle", false);
1809
+ if (this._reset(toLastSuccessful, usePendingBundle)) {
1810
+ call.resolve();
1811
+ return;
1812
+ }
1813
+ logger.error("Reset failed");
1814
+ call.reject("Reset failed");
1815
+ } catch (final Exception e) {
1816
+ logger.error("Reset failed " + e.getMessage());
1817
+ call.reject("Reset failed", e);
1781
1818
  }
1782
- logger.error("Reset failed");
1783
- call.reject("Reset failed");
1784
- } catch (final Exception e) {
1785
- logger.error("Reset failed " + e.getMessage());
1786
- call.reject("Reset failed", e);
1787
- }
1819
+ });
1788
1820
  }
1789
1821
 
1790
1822
  @PluginMethod
@@ -1850,12 +1882,33 @@ public class CapacitorUpdaterPlugin extends Plugin {
1850
1882
  try {
1851
1883
  CapacitorUpdaterPlugin.this.implementation.getLatest(CapacitorUpdaterPlugin.this.updateUrl, null, (res) -> {
1852
1884
  JSObject jsRes = InternalUtils.mapToJSObject(res);
1853
- if (jsRes.has("error")) {
1854
- String error = jsRes.getString("error");
1885
+ if (jsRes.has("error") || jsRes.has("kind")) {
1886
+ final BundleInfo current = CapacitorUpdaterPlugin.this.implementation.getCurrentBundle();
1887
+ String error = jsRes.has("error") ? jsRes.getString("error") : "";
1855
1888
  String errorMessage = jsRes.has("message")
1856
1889
  ? jsRes.getString("message")
1857
1890
  : "server did not provide a message";
1858
- logger.error("getLatest failed with error: " + error + ", message: " + errorMessage);
1891
+ int statusCode = jsRes.has("statusCode") ? jsRes.optInt("statusCode", 0) : 0;
1892
+ String kind = CapacitorUpdaterPlugin.this.getUpdateResponseKind(
1893
+ jsRes.has("kind") ? jsRes.getString("kind") : null
1894
+ );
1895
+ String latestVersion = jsRes.has("version") ? jsRes.getString("version") : current.getVersionName();
1896
+ CapacitorUpdaterPlugin.this.notifyUpdateCheckResult(
1897
+ kind,
1898
+ error,
1899
+ errorMessage,
1900
+ statusCode,
1901
+ latestVersion,
1902
+ current
1903
+ );
1904
+
1905
+ if ("failed".equals(kind)) {
1906
+ logger.error("getLatest failed with error: " + error + ", message: " + errorMessage);
1907
+ } else if ("blocked".equals(kind)) {
1908
+ logger.info("Update check blocked with error: " + error);
1909
+ } else {
1910
+ logger.info("No new version available");
1911
+ }
1859
1912
  } else if (jsRes.has("version")) {
1860
1913
  String newVersion = jsRes.getString("version");
1861
1914
  String currentVersion = String.valueOf(CapacitorUpdaterPlugin.this.implementation.getCurrentBundle());
@@ -1980,10 +2033,22 @@ public class CapacitorUpdaterPlugin extends Plugin {
1980
2033
  this.checkAppReady(this.resolveAppReadyCheckTimeoutMs());
1981
2034
  }
1982
2035
 
2036
+ synchronized boolean shouldInterruptAppReadyCheck(final Thread existingCheck, final Thread currentThread) {
2037
+ return existingCheck != null && existingCheck != currentThread;
2038
+ }
2039
+
2040
+ synchronized void clearAppReadyCheckIfCurrent(final Thread expectedThread) {
2041
+ if (this.appReadyCheck == expectedThread) {
2042
+ this.appReadyCheck = null;
2043
+ }
2044
+ }
2045
+
1983
2046
  private void checkAppReady(final long waitTimeMs) {
1984
2047
  try {
1985
- if (this.appReadyCheck != null) {
1986
- this.appReadyCheck.interrupt();
2048
+ final Thread currentThread = Thread.currentThread();
2049
+ final Thread existingCheck = this.appReadyCheck;
2050
+ if (this.shouldInterruptAppReadyCheck(existingCheck, currentThread)) {
2051
+ existingCheck.interrupt();
1987
2052
  }
1988
2053
  this.appReadyCheck = startNewThread(new DeferredNotifyAppReadyCheck(waitTimeMs));
1989
2054
  } catch (final Exception e) {
@@ -2000,6 +2065,31 @@ public class CapacitorUpdaterPlugin extends Plugin {
2000
2065
  }
2001
2066
  }
2002
2067
 
2068
+ private String getUpdateResponseKind(final String kind) {
2069
+ if ("up_to_date".equals(kind) || "blocked".equals(kind) || "failed".equals(kind)) {
2070
+ return kind;
2071
+ }
2072
+ return "failed";
2073
+ }
2074
+
2075
+ private void notifyUpdateCheckResult(
2076
+ final String kind,
2077
+ final String error,
2078
+ final String message,
2079
+ final int statusCode,
2080
+ final String version,
2081
+ final BundleInfo current
2082
+ ) {
2083
+ JSObject ret = new JSObject();
2084
+ ret.put("kind", kind);
2085
+ ret.put("error", error);
2086
+ ret.put("message", message);
2087
+ ret.put("statusCode", statusCode);
2088
+ ret.put("version", version);
2089
+ ret.put("bundle", InternalUtils.mapToJSObject(current.toJSONMap()));
2090
+ this.notifyListeners("updateCheckResult", ret);
2091
+ }
2092
+
2003
2093
  private void ensureBridgeSet() {
2004
2094
  if (this.bridge != null && this.bridge.getWebView() != null) {
2005
2095
  logger.setBridge(this.bridge);
@@ -2109,30 +2199,37 @@ public class CapacitorUpdaterPlugin extends Plugin {
2109
2199
  final BundleInfo current = CapacitorUpdaterPlugin.this.implementation.getCurrentBundle();
2110
2200
 
2111
2201
  // Handle network errors and other failures first
2112
- if (jsRes.has("error")) {
2113
- String error = jsRes.getString("error");
2202
+ if (jsRes.has("error") || jsRes.has("kind")) {
2203
+ String error = jsRes.has("error") ? jsRes.getString("error") : "";
2114
2204
  String errorMessage = jsRes.has("message") ? jsRes.getString("message") : "server did not provide a message";
2115
2205
  int statusCode = jsRes.has("statusCode") ? jsRes.optInt("statusCode", 0) : 0;
2116
- boolean responseIsOk = statusCode >= 200 && statusCode < 300;
2117
-
2118
- logger.error(
2119
- "getLatest failed with error: " + error + ", message: " + errorMessage + ", statusCode: " + statusCode
2120
- );
2206
+ String kind = CapacitorUpdaterPlugin.this.getUpdateResponseKind(jsRes.has("kind") ? jsRes.getString("kind") : null);
2121
2207
  String latestVersion = jsRes.has("version") ? jsRes.getString("version") : current.getVersionName();
2208
+ CapacitorUpdaterPlugin.this.notifyUpdateCheckResult(kind, error, errorMessage, statusCode, latestVersion, current);
2122
2209
 
2210
+ if ("up_to_date".equals(kind)) {
2211
+ logger.info("No new version available");
2212
+ } else if ("blocked".equals(kind)) {
2213
+ logger.info("Update check blocked with error: " + error);
2214
+ } else {
2215
+ logger.error(
2216
+ "getLatest failed with error: " + error + ", message: " + errorMessage + ", statusCode: " + statusCode
2217
+ );
2218
+ }
2219
+
2220
+ boolean isFailure = "failed".equals(kind);
2123
2221
  CapacitorUpdaterPlugin.this.endBackGroundTaskWithNotif(
2124
2222
  errorMessage,
2125
2223
  latestVersion,
2126
2224
  current,
2127
- true,
2225
+ isFailure,
2128
2226
  plannedDirectUpdate,
2129
2227
  "download_fail",
2130
2228
  "downloadFailed",
2131
- !responseIsOk
2229
+ isFailure
2132
2230
  );
2133
2231
  return;
2134
2232
  }
2135
-
2136
2233
  try {
2137
2234
  final String latestVersionName = jsRes.getString("version");
2138
2235
 
@@ -2438,12 +2535,14 @@ public class CapacitorUpdaterPlugin extends Plugin {
2438
2535
 
2439
2536
  @Override
2440
2537
  public void run() {
2538
+ final Thread currentThread = Thread.currentThread();
2441
2539
  try {
2442
2540
  logger.info("Wait for " + this.waitTimeMs + "ms, then check for notifyAppReady");
2443
2541
  Thread.sleep(this.waitTimeMs);
2444
2542
  CapacitorUpdaterPlugin.this.checkRevert();
2445
- CapacitorUpdaterPlugin.this.appReadyCheck = null;
2543
+ CapacitorUpdaterPlugin.this.clearAppReadyCheckIfCurrent(currentThread);
2446
2544
  } catch (final InterruptedException e) {
2545
+ CapacitorUpdaterPlugin.this.clearAppReadyCheckIfCurrent(currentThread);
2447
2546
  logger.info(DeferredNotifyAppReadyCheck.class.getName() + " was interrupted.");
2448
2547
  }
2449
2548
  }