@capgo/capacitor-updater 8.45.1 → 8.45.8

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/Package.swift CHANGED
@@ -11,7 +11,7 @@ let package = Package(
11
11
  ],
12
12
  dependencies: [
13
13
  .package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", from: "8.0.0"),
14
- .package(url: "https://github.com/Alamofire/Alamofire.git", .upToNextMajor(from: "5.11.1")),
14
+ .package(url: "https://github.com/Alamofire/Alamofire.git", .upToNextMajor(from: "5.11.2")),
15
15
  .package(url: "https://github.com/weichsel/ZIPFoundation.git", from: "0.9.20"),
16
16
  .package(url: "https://github.com/mrackwitz/Version.git", exact: "0.8.0"),
17
17
  .package(url: "https://github.com/attaswift/BigInt.git", from: "5.7.0")
package/README.md CHANGED
@@ -2063,9 +2063,10 @@ If you don't use backend, you need to provide the URL and version of the bundle.
2063
2063
 
2064
2064
  ##### ResetOptions
2065
2065
 
2066
- | Prop | Type |
2067
- | ---------------------- | -------------------- |
2068
- | **`toLastSuccessful`** | <code>boolean</code> |
2066
+ | Prop | Type | Description | Default |
2067
+ | ---------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------ |
2068
+ | **`toLastSuccessful`** | <code>boolean</code> | Reset to the last successfully loaded bundle instead of the builtin one. | <code>false</code> |
2069
+ | **`usePendingBundle`** | <code>boolean</code> | Apply the pending bundle set via {@link next} while resetting. When `true`, the plugin will switch to the pending bundle immediately and clear the pending flag. If no pending bundle exists, the reset will fail. | <code>false</code> |
2069
2070
 
2070
2071
 
2071
2072
  ##### CurrentBundleResult
@@ -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 androidx.core.content.pm.PackageInfoCompat;
25
26
  import com.getcapacitor.Bridge;
26
27
  import com.getcapacitor.CapConfig;
27
28
  import com.getcapacitor.JSArray;
@@ -88,7 +89,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
88
89
  private static final int SPLASH_SCREEN_RETRY_DELAY_MS = 100;
89
90
  private static final int SPLASH_SCREEN_MAX_RETRIES = 20;
90
91
 
91
- private final String pluginVersion = "8.45.1";
92
+ private final String pluginVersion = "8.45.8";
92
93
  private static final String DELAY_CONDITION_PREFERENCES = "";
93
94
 
94
95
  private SharedPreferences.Editor editor;
@@ -191,7 +192,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
191
192
  }
192
193
 
193
194
  private String getVersionCode(final PackageInfo packageInfo) {
194
- return Long.toString(packageInfo.getLongVersionCode());
195
+ return Long.toString(PackageInfoCompat.getLongVersionCode(packageInfo));
195
196
  }
196
197
 
197
198
  private void notifyBreakingEvents(final String version) {
@@ -1333,12 +1334,12 @@ public class CapacitorUpdaterPlugin extends Plugin {
1333
1334
  this.bridge.getWebView().post(() -> this.bridge.getWebView().evaluateJavascript(script, null));
1334
1335
  }
1335
1336
 
1336
- protected boolean _reload() {
1337
+ private void applyCurrentBundleToBridge() {
1337
1338
  final String path = this.implementation.getCurrentBundlePath();
1339
+ final boolean usingBuiltin = this.implementation.isUsingBuiltin();
1338
1340
  if (this.keepUrlPathAfterReload) {
1339
1341
  this.syncKeepUrlPathFlag(true);
1340
1342
  }
1341
- this.semaphoreUp();
1342
1343
  logger.info("Reloading: " + path);
1343
1344
 
1344
1345
  AtomicReference<URL> url = new AtomicReference<>();
@@ -1383,7 +1384,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
1383
1384
  }
1384
1385
 
1385
1386
  if (url.get() != null) {
1386
- if (this.implementation.isUsingBuiltin()) {
1387
+ if (usingBuiltin) {
1387
1388
  this.bridge.getLocalServer().hostAssets(path);
1388
1389
  } else {
1389
1390
  this.bridge.getLocalServer().hostFiles(path);
@@ -1403,14 +1404,14 @@ public class CapacitorUpdaterPlugin extends Plugin {
1403
1404
  } catch (MalformedURLException e) {
1404
1405
  logger.error("Cannot get finalUrl from capacitor bridge " + e.getMessage());
1405
1406
 
1406
- if (this.implementation.isUsingBuiltin()) {
1407
+ if (usingBuiltin) {
1407
1408
  this.bridge.setServerAssetPath(path);
1408
1409
  } else {
1409
1410
  this.bridge.setServerBasePath(path);
1410
1411
  }
1411
1412
  }
1412
1413
  } else {
1413
- if (this.implementation.isUsingBuiltin()) {
1414
+ if (usingBuiltin) {
1414
1415
  this.bridge.setServerAssetPath(path);
1415
1416
  } else {
1416
1417
  this.bridge.setServerBasePath(path);
@@ -1426,6 +1427,19 @@ public class CapacitorUpdaterPlugin extends Plugin {
1426
1427
  });
1427
1428
  }
1428
1429
  }
1430
+ }
1431
+
1432
+ protected void restoreLiveBundleStateAfterFailedReload() {
1433
+ try {
1434
+ this.applyCurrentBundleToBridge();
1435
+ } catch (final Exception e) {
1436
+ logger.warn("Failed to restore live bundle after rejected reload: " + e.getMessage());
1437
+ }
1438
+ }
1439
+
1440
+ protected boolean _reload() {
1441
+ this.semaphoreUp();
1442
+ this.applyCurrentBundleToBridge();
1429
1443
 
1430
1444
  this.checkAppReady();
1431
1445
  this.notifyListeners("appReloaded", new JSObject());
@@ -1444,6 +1458,38 @@ public class CapacitorUpdaterPlugin extends Plugin {
1444
1458
  @PluginMethod
1445
1459
  public void reload(final PluginCall call) {
1446
1460
  try {
1461
+ final BundleInfo current = this.implementation.getCurrentBundle();
1462
+ final BundleInfo next = this.implementation.getNextBundle();
1463
+
1464
+ if (next != null && !next.isErrorStatus() && !next.getId().equals(current.getId())) {
1465
+ final CapgoUpdater.ResetState previousState = this.implementation.captureResetState();
1466
+ final String previousBundleName = this.implementation.getCurrentBundle().getVersionName();
1467
+ logger.info("Applying pending bundle before reload: " + next.getVersionName());
1468
+ final boolean didApplyPendingBundle;
1469
+ if (next.isBuiltin()) {
1470
+ this.implementation.prepareResetStateForTransition();
1471
+ didApplyPendingBundle = true;
1472
+ } else {
1473
+ didApplyPendingBundle = this.implementation.stagePendingReload(next);
1474
+ }
1475
+ if (didApplyPendingBundle && this._reload()) {
1476
+ if (next.isBuiltin()) {
1477
+ this.implementation.finalizeResetTransition(previousBundleName, false);
1478
+ } else {
1479
+ this.implementation.finalizePendingReload(next, previousBundleName);
1480
+ }
1481
+ this.notifyBundleSet(next);
1482
+ this.implementation.setNextBundle(null);
1483
+ call.resolve();
1484
+ return;
1485
+ }
1486
+ this.implementation.restoreResetState(previousState);
1487
+ this.restoreLiveBundleStateAfterFailedReload();
1488
+ logger.error("Reload failed after applying pending bundle: " + next.getVersionName());
1489
+ call.reject("Reload failed after applying pending bundle: " + next.getVersionName());
1490
+ return;
1491
+ }
1492
+
1447
1493
  if (this._reload()) {
1448
1494
  call.resolve();
1449
1495
  } else {
@@ -1605,28 +1651,83 @@ public class CapacitorUpdaterPlugin extends Plugin {
1605
1651
  );
1606
1652
  }
1607
1653
 
1608
- private boolean _reset(final Boolean toLastSuccessful) {
1654
+ private boolean _reset(final Boolean toLastSuccessful, final Boolean usePendingBundle) {
1655
+ return this.performReset(toLastSuccessful, usePendingBundle, false);
1656
+ }
1657
+
1658
+ private boolean performReset(final Boolean toLastSuccessful, final Boolean usePendingBundle, final boolean internal) {
1609
1659
  final BundleInfo fallback = this.implementation.getFallbackBundle();
1610
- this.implementation.reset();
1660
+ final BundleInfo pending = this.implementation.getNextBundle();
1661
+ final CapgoUpdater.ResetState previousState = this.implementation.captureResetState();
1662
+ final String previousBundleName = this.implementation.getCurrentBundle().getVersionName();
1611
1663
 
1612
- if (toLastSuccessful && !fallback.isBuiltin()) {
1613
- logger.info("Resetting to: " + fallback);
1614
- if (this.implementation.set(fallback) && this._reload()) {
1615
- this.notifyBundleSet(fallback);
1664
+ if (Boolean.TRUE.equals(usePendingBundle)) {
1665
+ if (pending == null || pending.isErrorStatus()) {
1666
+ logger.error("No pending bundle available to reset to");
1667
+ return false;
1668
+ }
1669
+ if (!this.implementation.canSet(pending)) {
1670
+ logger.error("Pending bundle is not installable");
1671
+ return false;
1672
+ }
1673
+ this.implementation.prepareResetStateForTransition();
1674
+ logger.info("Resetting to pending bundle: " + pending.getVersionName());
1675
+ final boolean didApplyPendingBundle;
1676
+ if (pending.isBuiltin()) {
1677
+ didApplyPendingBundle = true;
1678
+ } else {
1679
+ didApplyPendingBundle = this.implementation.set(pending);
1680
+ }
1681
+ if (didApplyPendingBundle && this._reload()) {
1682
+ this.implementation.finalizeResetTransition(previousBundleName, internal);
1683
+ this.notifyBundleSet(pending);
1684
+ this.implementation.setNextBundle(null);
1616
1685
  return true;
1617
1686
  }
1687
+ this.implementation.restoreResetState(previousState);
1688
+ this.restoreLiveBundleStateAfterFailedReload();
1618
1689
  return false;
1619
1690
  }
1620
1691
 
1692
+ if (Boolean.TRUE.equals(toLastSuccessful) && !fallback.isBuiltin()) {
1693
+ if (this.implementation.canSet(fallback)) {
1694
+ this.implementation.prepareResetStateForTransition();
1695
+ logger.info("Resetting to: " + fallback);
1696
+ if (this.implementation.set(fallback) && this._reload()) {
1697
+ this.implementation.finalizeResetTransition(previousBundleName, internal);
1698
+ this.notifyBundleSet(fallback);
1699
+ return true;
1700
+ }
1701
+ if (!internal) {
1702
+ this.implementation.restoreResetState(previousState);
1703
+ this.restoreLiveBundleStateAfterFailedReload();
1704
+ return false;
1705
+ }
1706
+ logger.warn("Fallback reload failed during internal reset, resetting to native instead");
1707
+ } else {
1708
+ logger.warn("Fallback bundle is not installable, resetting to native instead");
1709
+ }
1710
+ }
1711
+
1712
+ this.implementation.prepareResetStateForTransition();
1621
1713
  logger.info("Resetting to native.");
1622
- return this._reload();
1714
+ if (this._reload()) {
1715
+ this.implementation.finalizeResetTransition(previousBundleName, internal);
1716
+ return true;
1717
+ }
1718
+ if (!internal) {
1719
+ this.implementation.restoreResetState(previousState);
1720
+ this.restoreLiveBundleStateAfterFailedReload();
1721
+ }
1722
+ return false;
1623
1723
  }
1624
1724
 
1625
1725
  @PluginMethod
1626
1726
  public void reset(final PluginCall call) {
1627
1727
  try {
1628
1728
  final Boolean toLastSuccessful = call.getBoolean("toLastSuccessful", false);
1629
- if (this._reset(toLastSuccessful)) {
1729
+ final Boolean usePendingBundle = call.getBoolean("usePendingBundle", false);
1730
+ if (this._reset(toLastSuccessful, usePendingBundle)) {
1630
1731
  call.resolve();
1631
1732
  return;
1632
1733
  }
@@ -1990,7 +2091,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
1990
2091
  );
1991
2092
  if (directUpdateAllowedNow) {
1992
2093
  logger.info("Direct update to builtin version");
1993
- this._reset(false);
2094
+ this._reset(false, false);
1994
2095
  CapacitorUpdaterPlugin.this.endBackGroundTaskWithNotif(
1995
2096
  "Updated to builtin version",
1996
2097
  latestVersionName,
@@ -2256,7 +2357,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
2256
2357
  this.notifyListeners("updateFailed", ret);
2257
2358
  this.implementation.sendStats("update_fail", current.getVersionName());
2258
2359
  this.implementation.setError(current);
2259
- this._reset(true);
2360
+ this.performReset(true, false, true);
2260
2361
  if (CapacitorUpdaterPlugin.this.autoDeleteFailed && !current.isBuiltin()) {
2261
2362
  logger.info("Deleting failing bundle: " + current.getVersionName());
2262
2363
  try {
@@ -991,6 +991,64 @@ public class CapgoUpdater {
991
991
  return (bundle.isDirectory() && bundle.exists() && new File(bundle.getPath(), "/index.html").exists() && !bundleInfo.isDeleted());
992
992
  }
993
993
 
994
+ static final class ResetState {
995
+
996
+ final String currentBundlePath;
997
+ final String fallbackBundleId;
998
+ final String nextBundleId;
999
+
1000
+ ResetState(final String currentBundlePath, final String fallbackBundleId, final String nextBundleId) {
1001
+ this.currentBundlePath = currentBundlePath;
1002
+ this.fallbackBundleId = fallbackBundleId;
1003
+ this.nextBundleId = nextBundleId;
1004
+ }
1005
+ }
1006
+
1007
+ ResetState captureResetState() {
1008
+ return new ResetState(
1009
+ this.getCurrentBundlePath(),
1010
+ this.prefs.getString(FALLBACK_VERSION, BundleInfo.ID_BUILTIN),
1011
+ this.prefs.getString(NEXT_VERSION, null)
1012
+ );
1013
+ }
1014
+
1015
+ void restoreResetState(final ResetState state) {
1016
+ final String currentBundlePath = state.currentBundlePath == null || state.currentBundlePath.trim().isEmpty()
1017
+ ? "public"
1018
+ : state.currentBundlePath;
1019
+ final String fallbackBundleId = state.fallbackBundleId == null || state.fallbackBundleId.isEmpty()
1020
+ ? BundleInfo.ID_BUILTIN
1021
+ : state.fallbackBundleId;
1022
+
1023
+ this.editor.putString(this.CAP_SERVER_PATH, currentBundlePath);
1024
+ this.editor.putString(FALLBACK_VERSION, fallbackBundleId);
1025
+ if (state.nextBundleId == null || state.nextBundleId.isEmpty()) {
1026
+ this.editor.remove(NEXT_VERSION);
1027
+ } else {
1028
+ this.editor.putString(NEXT_VERSION, state.nextBundleId);
1029
+ }
1030
+ this.editor.commit();
1031
+ }
1032
+
1033
+ void prepareResetStateForTransition() {
1034
+ this.setCurrentBundle(new File("public"));
1035
+ this.setFallbackBundle(null);
1036
+ this.setNextBundle(null);
1037
+ }
1038
+
1039
+ void finalizeResetTransition(final String previousBundleName, final boolean internal) {
1040
+ if (this.activity != null) {
1041
+ DownloadWorkerManager.cancelAllDownloads(this.activity);
1042
+ }
1043
+ if (!internal) {
1044
+ this.sendStats("reset", this.getCurrentBundle().getVersionName(), previousBundleName);
1045
+ }
1046
+ }
1047
+
1048
+ boolean canSet(final BundleInfo bundle) {
1049
+ return bundle != null && (bundle.isBuiltin() || this.bundleExists(bundle.getId()));
1050
+ }
1051
+
994
1052
  public Boolean set(final BundleInfo bundle) {
995
1053
  return this.set(bundle.getId());
996
1054
  }
@@ -1015,6 +1073,21 @@ public class CapgoUpdater {
1015
1073
  return false;
1016
1074
  }
1017
1075
 
1076
+ boolean stagePendingReload(final BundleInfo bundle) {
1077
+ if (bundle == null || bundle.isBuiltin() || !this.bundleExists(bundle.getId())) {
1078
+ return false;
1079
+ }
1080
+ this.setCurrentBundle(this.getBundleDirectory(bundle.getId()));
1081
+ return true;
1082
+ }
1083
+
1084
+ void finalizePendingReload(final BundleInfo bundle, final String previousBundleName) {
1085
+ if (bundle == null || bundle.isBuiltin()) {
1086
+ return;
1087
+ }
1088
+ this.sendStats("set", bundle.getVersionName(), previousBundleName);
1089
+ }
1090
+
1018
1091
  public void autoReset() {
1019
1092
  final BundleInfo currentBundle = this.getCurrentBundle();
1020
1093
  if (!currentBundle.isBuiltin() && !this.bundleExists(currentBundle.getId())) {
@@ -1058,17 +1131,9 @@ public class CapgoUpdater {
1058
1131
 
1059
1132
  public void reset(final boolean internal) {
1060
1133
  logger.debug("reset: " + internal);
1061
- var currentBundleName = this.getCurrentBundle().getVersionName();
1062
- this.setCurrentBundle(new File("public"));
1063
- this.setFallbackBundle(null);
1064
- this.setNextBundle(null);
1065
- // Cancel any ongoing downloads
1066
- if (this.activity != null) {
1067
- DownloadWorkerManager.cancelAllDownloads(this.activity);
1068
- }
1069
- if (!internal) {
1070
- this.sendStats("reset", this.getCurrentBundle().getVersionName(), currentBundleName);
1071
- }
1134
+ final String currentBundleName = this.getCurrentBundle().getVersionName();
1135
+ this.prepareResetStateForTransition();
1136
+ this.finalizeResetTransition(currentBundleName, internal);
1072
1137
  }
1073
1138
 
1074
1139
  private JSONObject createInfoObject() throws JSONException {
@@ -110,7 +110,23 @@ public class DownloadService extends Worker {
110
110
  }
111
111
 
112
112
  private static String sanitizeUserAgentValue(String value) {
113
- return value == null || value.isEmpty() ? "unknown" : value;
113
+ if (value == null || value.isEmpty()) {
114
+ return "unknown";
115
+ }
116
+
117
+ StringBuilder sanitized = new StringBuilder();
118
+ value
119
+ .codePoints()
120
+ .forEach((cp) -> {
121
+ boolean isVisibleAscii = cp >= 0x20 && cp <= 0x7E;
122
+ boolean isIso88591 = cp >= 0xA0 && cp <= 0xFF;
123
+ if (isVisibleAscii || isIso88591) {
124
+ sanitized.appendCodePoint(cp);
125
+ }
126
+ });
127
+
128
+ String result = sanitized.toString().trim();
129
+ return result.isEmpty() ? "unknown" : result;
114
130
  }
115
131
 
116
132
  // Method to update User-Agent values
package/dist/docs.json CHANGED
@@ -343,7 +343,7 @@
343
343
  },
344
344
  {
345
345
  "name": "link",
346
- "text": "ResetOptions} to control reset behavior. If `toLastSuccessful` is `false` (or omitted), resets to builtin. If `true`, resets to last successful bundle."
346
+ "text": "ResetOptions} to control reset behavior.\nIf `toLastSuccessful` is `false` (or omitted), resets to builtin.\nIf `true`, resets to last successful bundle.\nIf `usePendingBundle` is `true`, applies the pending bundle set via {@link next} and clears it."
347
347
  },
348
348
  {
349
349
  "name": "returns",
@@ -1872,10 +1872,27 @@
1872
1872
  "properties": [
1873
1873
  {
1874
1874
  "name": "toLastSuccessful",
1875
- "tags": [],
1876
- "docs": "",
1875
+ "tags": [
1876
+ {
1877
+ "text": "false",
1878
+ "name": "default"
1879
+ }
1880
+ ],
1881
+ "docs": "Reset to the last successfully loaded bundle instead of the builtin one.",
1877
1882
  "complexTypes": [],
1878
- "type": "boolean"
1883
+ "type": "boolean | undefined"
1884
+ },
1885
+ {
1886
+ "name": "usePendingBundle",
1887
+ "tags": [
1888
+ {
1889
+ "text": "false",
1890
+ "name": "default"
1891
+ }
1892
+ ],
1893
+ "docs": "Apply the pending bundle set via {@link next} while resetting.\n\nWhen `true`, the plugin will switch to the pending bundle immediately and clear the pending flag.\nIf no pending bundle exists, the reset will fail.",
1894
+ "complexTypes": [],
1895
+ "type": "boolean | undefined"
1879
1896
  }
1880
1897
  ]
1881
1898
  },
@@ -568,7 +568,10 @@ export interface CapacitorUpdaterPlugin {
568
568
  * - Testing rollback functionality
569
569
  * - Providing users a "reset to factory" option
570
570
  *
571
- * @param options {@link ResetOptions} to control reset behavior. If `toLastSuccessful` is `false` (or omitted), resets to builtin. If `true`, resets to last successful bundle.
571
+ * @param options {@link ResetOptions} to control reset behavior.
572
+ * If `toLastSuccessful` is `false` (or omitted), resets to builtin.
573
+ * If `true`, resets to last successful bundle.
574
+ * If `usePendingBundle` is `true`, applies the pending bundle set via {@link next} and clears it.
572
575
  * @returns {Promise<void>} A promise that may never resolve because the app will be reloaded.
573
576
  * @throws {Error} If the reset operation fails.
574
577
  */
@@ -1680,7 +1683,19 @@ export interface BundleListResult {
1680
1683
  bundles: BundleInfo[];
1681
1684
  }
1682
1685
  export interface ResetOptions {
1683
- toLastSuccessful: boolean;
1686
+ /**
1687
+ * Reset to the last successfully loaded bundle instead of the builtin one.
1688
+ * @default false
1689
+ */
1690
+ toLastSuccessful?: boolean;
1691
+ /**
1692
+ * Apply the pending bundle set via {@link next} while resetting.
1693
+ *
1694
+ * When `true`, the plugin will switch to the pending bundle immediately and clear the pending flag.
1695
+ * If no pending bundle exists, the reset will fail.
1696
+ * @default false
1697
+ */
1698
+ usePendingBundle?: boolean;
1684
1699
  }
1685
1700
  export interface ListOptions {
1686
1701
  /**