@capgo/capacitor-updater 8.47.8 → 8.47.10

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
@@ -633,6 +633,11 @@ The bundle must include an `index.html` file at the root level.
633
633
  For encrypted bundles, provide the `sessionKey` and `checksum` parameters.
634
634
  For multi-file delta updates, provide the `manifest` array.
635
635
 
636
+ **Android Background Runner note:** `@capacitor/background-runner` loads its
637
+ configured runner script from native APK assets. Live updates cannot replace
638
+ that runner script. Keep it stable across OTA updates and ship a native app
639
+ update when the runner code changes.
640
+
636
641
  | Param | Type | Description |
637
642
  | ------------- | ----------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
638
643
  | **`options`** | <code><a href="#downloadoptions">DownloadOptions</a></code> | The {@link <a href="#downloadoptions">DownloadOptions</a>} for downloading a new bundle zip. |
@@ -1188,6 +1193,9 @@ Use this to:
1188
1193
  - Check if a device is on a specific channel before showing features
1189
1194
  - Verify channel assignment after calling {@link setChannel}
1190
1195
 
1196
+ On native platforms, a successful response also refreshes the locally persisted
1197
+ default channel used by update checks.
1198
+
1191
1199
  **Returns:** <code>Promise&lt;<a href="#getchannelres">GetChannelRes</a>&gt;</code>
1192
1200
 
1193
1201
  **Since:** 4.8.0
@@ -129,7 +129,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
129
129
  static final int APPLICATION_EXIT_REASON_USER_REQUESTED = 10;
130
130
  static final int APPLICATION_EXIT_REASON_DEPENDENCY_DIED = 12;
131
131
 
132
- private final String pluginVersion = "8.47.8";
132
+ private final String pluginVersion = "8.47.9";
133
133
  private static final String DELAY_CONDITION_PREFERENCES = "";
134
134
 
135
135
  private SharedPreferences.Editor editor;
@@ -1995,6 +1995,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
1995
1995
  CapacitorUpdaterPlugin.this.editor,
1996
1996
  DEFAULT_CHANNEL_PREF_KEY,
1997
1997
  CapacitorUpdaterPlugin.this.allowSetDefaultChannel,
1998
+ CapacitorUpdaterPlugin.this.getConfig().getString("defaultChannel", ""),
1998
1999
  (res) -> {
1999
2000
  JSObject jsRes = InternalUtils.mapToJSObject(res);
2000
2001
  if (jsRes.has("error")) {
@@ -2043,21 +2044,25 @@ public class CapacitorUpdaterPlugin extends Plugin {
2043
2044
  try {
2044
2045
  logger.info("getChannel");
2045
2046
  startNewThread(() ->
2046
- CapacitorUpdaterPlugin.this.implementation.getChannel((res) -> {
2047
- JSObject jsRes = InternalUtils.mapToJSObject(res);
2048
- if (jsRes.has("error")) {
2049
- String errorMessage = jsRes.has("message") ? jsRes.getString("message") : jsRes.getString("error");
2050
- String errorCode = jsRes.getString("error");
2047
+ CapacitorUpdaterPlugin.this.implementation.getChannel(
2048
+ (res) -> {
2049
+ JSObject jsRes = InternalUtils.mapToJSObject(res);
2050
+ if (jsRes.has("error")) {
2051
+ String errorMessage = jsRes.has("message") ? jsRes.getString("message") : jsRes.getString("error");
2052
+ String errorCode = jsRes.getString("error");
2051
2053
 
2052
- JSObject errorObj = new JSObject();
2053
- errorObj.put("message", errorMessage);
2054
- errorObj.put("error", errorCode);
2054
+ JSObject errorObj = new JSObject();
2055
+ errorObj.put("message", errorMessage);
2056
+ errorObj.put("error", errorCode);
2055
2057
 
2056
- call.reject(errorMessage, "GETCHANNEL_FAILED", null, errorObj);
2057
- } else {
2058
- call.resolve(jsRes);
2059
- }
2060
- })
2058
+ call.reject(errorMessage, "GETCHANNEL_FAILED", null, errorObj);
2059
+ } else {
2060
+ call.resolve(jsRes);
2061
+ }
2062
+ },
2063
+ CapacitorUpdaterPlugin.this.editor,
2064
+ DEFAULT_CHANNEL_PREF_KEY
2065
+ )
2061
2066
  );
2062
2067
  } catch (final Exception e) {
2063
2068
  logger.error("Failed to getChannel " + e.getMessage());
@@ -2514,7 +2519,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
2514
2519
  }
2515
2520
 
2516
2521
  final BundleInfo previewFallbackBundle = this.implementation.getPreviewFallbackBundle();
2517
- this.endPreviewSession();
2522
+ this.endPreviewSession(true);
2518
2523
  final BundleInfo restoredNextBundle = this.implementation.getNextBundle();
2519
2524
  this.deletePreviewBundleIfUnused(previewBundle, previewFallbackBundle, restoredNextBundle);
2520
2525
  return true;
@@ -2523,16 +2528,11 @@ public class CapacitorUpdaterPlugin extends Plugin {
2523
2528
  private boolean leavePreviewSessionForIncomingPreviewLink() {
2524
2529
  this.showPreviewTransitionLoader("incoming-preview-deeplink");
2525
2530
  final BundleInfo previewBundle = this.implementation.getCurrentBundle();
2526
- final BundleInfo previewFallbackBundle = this.implementation.getPreviewFallbackBundle();
2531
+ final BundleInfo previewFallbackBundle = this.resolvePreviewFallbackBundle("incoming preview deeplink");
2527
2532
  boolean didReload = false;
2528
2533
 
2529
2534
  try {
2530
- if (previewFallbackBundle == null || previewFallbackBundle.isErrorStatus()) {
2531
- logger.error("No preview fallback bundle available");
2532
- return false;
2533
- }
2534
- if (!this.implementation.canSet(previewFallbackBundle)) {
2535
- logger.error("Preview fallback bundle is not installable");
2535
+ if (previewFallbackBundle == null) {
2536
2536
  return false;
2537
2537
  }
2538
2538
 
@@ -2542,7 +2542,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
2542
2542
  return false;
2543
2543
  }
2544
2544
 
2545
- if (!this._reload()) {
2545
+ if (!this.reloadWithoutWaitingForAppReady()) {
2546
2546
  this.implementation.restoreResetState(previousState);
2547
2547
  this.restoreLiveBundleStateAfterFailedReload();
2548
2548
  return false;
@@ -2590,13 +2590,8 @@ public class CapacitorUpdaterPlugin extends Plugin {
2590
2590
 
2591
2591
  private boolean leavePreviewSessionWithoutReload(final boolean keepPreviewGuard) {
2592
2592
  final BundleInfo previewBundle = this.implementation.getCurrentBundle();
2593
- final BundleInfo previewFallbackBundle = this.implementation.getPreviewFallbackBundle();
2594
- if (previewFallbackBundle == null || previewFallbackBundle.isErrorStatus()) {
2595
- logger.error("No preview fallback bundle available");
2596
- return false;
2597
- }
2598
- if (!this.implementation.canSet(previewFallbackBundle)) {
2599
- logger.error("Preview fallback bundle is not installable");
2593
+ final BundleInfo previewFallbackBundle = this.resolvePreviewFallbackBundle("preview deeplink launch");
2594
+ if (previewFallbackBundle == null) {
2600
2595
  return false;
2601
2596
  }
2602
2597
  if (!this.implementation.stagePreviewFallbackReload(previewFallbackBundle)) {
@@ -2649,20 +2644,15 @@ public class CapacitorUpdaterPlugin extends Plugin {
2649
2644
  }
2650
2645
 
2651
2646
  private boolean resetToPreviewFallbackBundle() {
2652
- final BundleInfo fallback = this.implementation.getPreviewFallbackBundle();
2653
- if (fallback == null || fallback.isErrorStatus()) {
2654
- logger.error("No preview fallback bundle available");
2655
- return false;
2656
- }
2657
- if (!this.implementation.canSet(fallback)) {
2658
- logger.error("Preview fallback bundle is not installable");
2647
+ final BundleInfo fallback = this.resolvePreviewFallbackBundle("leave preview");
2648
+ if (fallback == null) {
2659
2649
  return false;
2660
2650
  }
2661
2651
 
2662
2652
  final CapgoUpdater.ResetState previousState = this.implementation.captureResetState();
2663
2653
  final String previousBundleName = this.implementation.getCurrentBundle().getVersionName();
2664
2654
  logger.info("Resetting to preview fallback bundle: " + fallback.getVersionName());
2665
- if (this.implementation.stagePreviewFallbackReload(fallback) && this._reload()) {
2655
+ if (this.implementation.stagePreviewFallbackReload(fallback) && this.reloadWithoutWaitingForAppReady()) {
2666
2656
  this.implementation.finalizeResetTransition(previousBundleName, false);
2667
2657
  this.notifyBundleSet(fallback);
2668
2658
  return true;
@@ -2672,6 +2662,29 @@ public class CapacitorUpdaterPlugin extends Plugin {
2672
2662
  return false;
2673
2663
  }
2674
2664
 
2665
+ private BundleInfo resolvePreviewFallbackBundle(final String reason) {
2666
+ final BundleInfo fallback = this.implementation.getPreviewFallbackBundle();
2667
+ if (fallback != null && !fallback.isErrorStatus() && this.implementation.canSet(fallback)) {
2668
+ return fallback;
2669
+ }
2670
+
2671
+ if (fallback == null) {
2672
+ logger.warn("No preview fallback bundle available for " + reason + ". Falling back to builtin bundle.");
2673
+ } else if (fallback.isErrorStatus()) {
2674
+ logger.warn("Preview fallback bundle is in error state for " + reason + ". Falling back to builtin bundle.");
2675
+ } else {
2676
+ logger.warn("Preview fallback bundle is not installable for " + reason + ". Falling back to builtin bundle.");
2677
+ }
2678
+
2679
+ final BundleInfo builtin = this.implementation.getBundleInfo(BundleInfo.ID_BUILTIN);
2680
+ if (builtin != null && !builtin.isErrorStatus() && this.implementation.canSet(builtin)) {
2681
+ return builtin;
2682
+ }
2683
+
2684
+ logger.error("Builtin bundle is not available to leave preview for " + reason);
2685
+ return null;
2686
+ }
2687
+
2675
2688
  private void endPreviewSession() {
2676
2689
  this.endPreviewSession(false);
2677
2690
  }
@@ -2706,11 +2719,8 @@ public class CapacitorUpdaterPlugin extends Plugin {
2706
2719
 
2707
2720
  private void clearPreviewSessionBecauseDisabled() {
2708
2721
  logger.info("Preview session disabled by config; restoring preview fallback");
2709
- final BundleInfo fallback = this.implementation.getPreviewFallbackBundle();
2710
- final BundleInfo bundleToRestore =
2711
- fallback == null || fallback.isErrorStatus() ? this.implementation.getBundleInfo(BundleInfo.ID_BUILTIN) : fallback;
2712
-
2713
- if (this.implementation.canSet(bundleToRestore)) {
2722
+ final BundleInfo bundleToRestore = this.resolvePreviewFallbackBundle("preview disabled");
2723
+ if (bundleToRestore != null) {
2714
2724
  this.implementation.stagePreviewFallbackReload(bundleToRestore);
2715
2725
  } else {
2716
2726
  logger.warn("Could not restore preview fallback while disabling preview");
@@ -18,12 +18,15 @@ import androidx.work.WorkManager;
18
18
  import com.google.common.util.concurrent.Futures;
19
19
  import com.google.common.util.concurrent.ListenableFuture;
20
20
  import java.io.BufferedInputStream;
21
+ import java.io.BufferedReader;
21
22
  import java.io.File;
22
23
  import java.io.FileInputStream;
23
24
  import java.io.FileNotFoundException;
24
25
  import java.io.FileOutputStream;
25
26
  import java.io.FilenameFilter;
26
27
  import java.io.IOException;
28
+ import java.io.InputStreamReader;
29
+ import java.nio.charset.StandardCharsets;
27
30
  import java.security.SecureRandom;
28
31
  import java.util.ArrayList;
29
32
  import java.util.Date;
@@ -63,6 +66,8 @@ public class CapgoUpdater {
63
66
  private static final String PREVIEW_FALLBACK_VERSION = "previewFallbackVersion";
64
67
  private static final String bundleDirectory = "versions";
65
68
  private static final String TEMP_UNZIP_PREFIX = "capgo_unzip_";
69
+ private static final String CAPACITOR_CONFIG_ASSET = "capacitor.config.json";
70
+ private static final String BACKGROUND_RUNNER_CONFIG_KEY = "BackgroundRunner";
66
71
 
67
72
  public static final String TAG = "Capacitor-updater";
68
73
  public SharedPreferences.Editor editor;
@@ -920,6 +925,7 @@ public class CapgoUpdater {
920
925
  }
921
926
 
922
927
  private void setCurrentBundle(final File bundle) {
928
+ this.cancelBackgroundRunnerWorkBeforeBundleSwitch();
923
929
  this.editor.putString(this.CAP_SERVER_PATH, bundle.getPath());
924
930
  logger.info("Current bundle set to: " + bundle);
925
931
  this.editor.commit();
@@ -929,6 +935,73 @@ public class CapgoUpdater {
929
935
  return bundlePath != null && !bundlePath.trim().isEmpty() && !isBuiltin && !hasStoredBundleInfo;
930
936
  }
931
937
 
938
+ static String getBackgroundRunnerLabelFromConfig(final String configJson) {
939
+ if (configJson == null || configJson.trim().isEmpty()) {
940
+ return null;
941
+ }
942
+
943
+ try {
944
+ final JSONObject config = new JSONObject(configJson);
945
+ final JSONObject plugins = config.optJSONObject("plugins");
946
+ if (plugins == null) {
947
+ return null;
948
+ }
949
+
950
+ final JSONObject backgroundRunner = plugins.optJSONObject(BACKGROUND_RUNNER_CONFIG_KEY);
951
+ if (backgroundRunner == null) {
952
+ return null;
953
+ }
954
+
955
+ final String label = backgroundRunner.optString("label", "").trim();
956
+ return label.isEmpty() ? null : label;
957
+ } catch (JSONException ignored) {
958
+ return null;
959
+ }
960
+ }
961
+
962
+ private String readAssetAsString(final String assetPath) throws IOException {
963
+ final StringBuilder buffer = new StringBuilder();
964
+ try (
965
+ final BufferedReader reader = new BufferedReader(
966
+ new InputStreamReader(this.activity.getAssets().open(assetPath), StandardCharsets.UTF_8)
967
+ )
968
+ ) {
969
+ String line;
970
+ while ((line = reader.readLine()) != null) {
971
+ buffer.append(line).append('\n');
972
+ }
973
+ }
974
+ return buffer.toString();
975
+ }
976
+
977
+ private void cancelBackgroundRunnerWorkBeforeBundleSwitch() {
978
+ if (this.activity == null) {
979
+ return;
980
+ }
981
+
982
+ final String label;
983
+ try {
984
+ label = getBackgroundRunnerLabelFromConfig(this.readAssetAsString(CAPACITOR_CONFIG_ASSET));
985
+ } catch (IOException ignored) {
986
+ return;
987
+ }
988
+
989
+ if (label == null) {
990
+ return;
991
+ }
992
+
993
+ try {
994
+ final WorkManager workManager = WorkManager.getInstance(this.activity.getApplicationContext());
995
+ workManager.cancelUniqueWork(label);
996
+ workManager.cancelAllWorkByTag(label);
997
+ logger.info("Cancelled Background Runner work before bundle switch.");
998
+ logger.debug("Background Runner label: " + label);
999
+ } catch (Exception e) {
1000
+ logger.warn("Failed to cancel Background Runner work before bundle switch.");
1001
+ logger.debug("Background Runner cancellation error: " + e.getMessage());
1002
+ }
1003
+ }
1004
+
932
1005
  private boolean hasStoredBundleInfo(final String id) {
933
1006
  return (
934
1007
  id != null &&
@@ -1614,6 +1687,17 @@ public class CapgoUpdater {
1614
1687
  final String defaultChannelKey,
1615
1688
  final boolean allowSetDefaultChannel,
1616
1689
  final Callback callback
1690
+ ) {
1691
+ this.setChannel(channel, editor, defaultChannelKey, allowSetDefaultChannel, "", callback);
1692
+ }
1693
+
1694
+ public void setChannel(
1695
+ final String channel,
1696
+ final SharedPreferences.Editor editor,
1697
+ final String defaultChannelKey,
1698
+ final boolean allowSetDefaultChannel,
1699
+ final String configDefaultChannel,
1700
+ final Callback callback
1617
1701
  ) {
1618
1702
  // Check if setting defaultChannel is allowed
1619
1703
  if (!allowSetDefaultChannel) {
@@ -1666,6 +1750,7 @@ public class CapgoUpdater {
1666
1750
  // Clear persisted defaultChannel and revert to config value
1667
1751
  editor.remove(defaultChannelKey);
1668
1752
  editor.apply();
1753
+ this.defaultChannel = configDefaultChannel;
1669
1754
  logger.info("Public channel requested, channel override removed");
1670
1755
  callback.callback(res);
1671
1756
  } else {
@@ -1680,6 +1765,10 @@ public class CapgoUpdater {
1680
1765
  }
1681
1766
 
1682
1767
  public void getChannel(final Callback callback) {
1768
+ this.getChannel(callback, null, null);
1769
+ }
1770
+
1771
+ public void getChannel(final Callback callback, final SharedPreferences.Editor editor, final String defaultChannelKey) {
1683
1772
  // Check if rate limit was exceeded
1684
1773
  if (rateLimitExceeded) {
1685
1774
  logger.debug("Skipping getChannel due to rate limit (429). Requests will resume after app restart.");
@@ -1798,6 +1887,7 @@ public class CapgoUpdater {
1798
1887
  ret.put(key, jsonResponse.get(key));
1799
1888
  }
1800
1889
  }
1890
+ persistDefaultChannelFromResponse(ret.get("channel"), editor, defaultChannelKey);
1801
1891
  logger.info("Channel get to \"" + ret);
1802
1892
  callback.callback(ret);
1803
1893
  } catch (JSONException e) {
@@ -1811,6 +1901,24 @@ public class CapgoUpdater {
1811
1901
  );
1812
1902
  }
1813
1903
 
1904
+ void persistDefaultChannelFromResponse(final Object channel, final SharedPreferences.Editor editor, final String defaultChannelKey) {
1905
+ if (!(channel instanceof String)) {
1906
+ return;
1907
+ }
1908
+
1909
+ final String channelName = ((String) channel).trim();
1910
+ if (channelName.isEmpty() || BundleInfo.ID_BUILTIN.equals(channelName)) {
1911
+ return;
1912
+ }
1913
+
1914
+ this.defaultChannel = channelName;
1915
+ if (editor != null && defaultChannelKey != null && !defaultChannelKey.isEmpty()) {
1916
+ editor.putString(defaultChannelKey, channelName);
1917
+ editor.apply();
1918
+ }
1919
+ logger.info("defaultChannel synchronized from getChannel(): " + channelName);
1920
+ }
1921
+
1814
1922
  public void listChannels(final Callback callback) {
1815
1923
  // Check if rate limit was exceeded
1816
1924
  if (rateLimitExceeded) {
@@ -17,8 +17,8 @@ public class ShakeDetector implements SensorEventListener {
17
17
  void onShakeDetected();
18
18
  }
19
19
 
20
- private static final float SHAKE_THRESHOLD = 12.0f; // Acceleration threshold for shake detection
21
- private static final int SHAKE_TIMEOUT = 500; // Minimum time between shake events (ms)
20
+ private static final float SHAKE_THRESHOLD = 16.0f; // Acceleration threshold for shake detection
21
+ private static final int SHAKE_TIMEOUT = 1000; // Minimum time between shake events (ms)
22
22
 
23
23
  private Listener listener;
24
24
  private SensorManager sensorManager;
@@ -34,7 +34,7 @@ public class ShakeDetector implements SensorEventListener {
34
34
  this.accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
35
35
 
36
36
  if (accelerometer != null) {
37
- sensorManager.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_GAME);
37
+ sensorManager.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_UI);
38
38
  }
39
39
  }
40
40
 
@@ -542,6 +542,7 @@ public class ShakeMenu implements ShakeDetector.Listener {
542
542
  new Thread(() -> {
543
543
  final CapgoUpdater updater = plugin.implementation;
544
544
  final Bridge bridge = activity.getBridge();
545
+ final String configDefaultChannel = plugin.getConfig().getString("defaultChannel", "");
545
546
 
546
547
  // Set the channel - respect plugin's allowSetDefaultChannel config
547
548
  updater.setChannel(
@@ -549,6 +550,7 @@ public class ShakeMenu implements ShakeDetector.Listener {
549
550
  updater.editor,
550
551
  "CapacitorUpdater.defaultChannel",
551
552
  plugin.allowSetDefaultChannel,
553
+ configDefaultChannel,
552
554
  (setRes) -> {
553
555
  if (setRes == null) {
554
556
  activity.runOnUiThread(() -> {
package/dist/docs.json CHANGED
@@ -156,7 +156,7 @@
156
156
  "text": "{Error} If the download fails or the bundle is invalid."
157
157
  }
158
158
  ],
159
- "docs": "Download a new bundle from the provided URL for later installation.\n\nThe downloaded bundle is stored locally but not activated. To use it:\n- Call {@link next} to set it for installation on next app backgrounding/restart\n- Call {@link set} to activate it immediately (destroys current JavaScript context)\n\nThe URL should point to a zip file containing either:\n- Your app files directly in the zip root, or\n- A single folder containing all your app files\n\nThe bundle must include an `index.html` file at the root level.\n\nFor encrypted bundles, provide the `sessionKey` and `checksum` parameters.\nFor multi-file delta updates, provide the `manifest` array.",
159
+ "docs": "Download a new bundle from the provided URL for later installation.\n\nThe downloaded bundle is stored locally but not activated. To use it:\n- Call {@link next} to set it for installation on next app backgrounding/restart\n- Call {@link set} to activate it immediately (destroys current JavaScript context)\n\nThe URL should point to a zip file containing either:\n- Your app files directly in the zip root, or\n- A single folder containing all your app files\n\nThe bundle must include an `index.html` file at the root level.\n\nFor encrypted bundles, provide the `sessionKey` and `checksum` parameters.\nFor multi-file delta updates, provide the `manifest` array.\n\n**Android Background Runner note:** `@capacitor/background-runner` loads its\nconfigured runner script from native APK assets. Live updates cannot replace\nthat runner script. Keep it stable across OTA updates and ship a native app\nupdate when the runner code changes.",
160
160
  "complexTypes": [
161
161
  "BundleInfo",
162
162
  "DownloadOptions"
@@ -720,7 +720,7 @@
720
720
  "text": "4.8.0"
721
721
  }
722
722
  ],
723
- "docs": "Get the current channel assigned to this device.\n\nReturns information about:\n- `channel`: The currently assigned channel name (if any)\n- `allowSet`: Whether the channel allows self-assignment\n- `status`: Operation status\n- `error`/`message`: Additional information (if applicable)\n\nUse this to:\n- Display current channel to users (e.g., \"You're on the Beta channel\")\n- Check if a device is on a specific channel before showing features\n- Verify channel assignment after calling {@link setChannel}",
723
+ "docs": "Get the current channel assigned to this device.\n\nReturns information about:\n- `channel`: The currently assigned channel name (if any)\n- `allowSet`: Whether the channel allows self-assignment\n- `status`: Operation status\n- `error`/`message`: Additional information (if applicable)\n\nUse this to:\n- Display current channel to users (e.g., \"You're on the Beta channel\")\n- Check if a device is on a specific channel before showing features\n- Verify channel assignment after calling {@link setChannel}\n\nOn native platforms, a successful response also refreshes the locally persisted\ndefault channel used by update checks.",
724
724
  "complexTypes": [
725
725
  "GetChannelRes"
726
726
  ],
@@ -467,6 +467,11 @@ export interface CapacitorUpdaterPlugin {
467
467
  * For encrypted bundles, provide the `sessionKey` and `checksum` parameters.
468
468
  * For multi-file delta updates, provide the `manifest` array.
469
469
  *
470
+ * **Android Background Runner note:** `@capacitor/background-runner` loads its
471
+ * configured runner script from native APK assets. Live updates cannot replace
472
+ * that runner script. Keep it stable across OTA updates and ship a native app
473
+ * update when the runner code changes.
474
+ *
470
475
  * @example
471
476
  * const bundle = await CapacitorUpdater.download({
472
477
  * url: `https://example.com/versions/${version}/dist.zip`,
@@ -909,6 +914,9 @@ export interface CapacitorUpdaterPlugin {
909
914
  * - Check if a device is on a specific channel before showing features
910
915
  * - Verify channel assignment after calling {@link setChannel}
911
916
  *
917
+ * On native platforms, a successful response also refreshes the locally persisted
918
+ * default channel used by update checks.
919
+ *
912
920
  * @returns {Promise<GetChannelRes>} The current channel information.
913
921
  * @throws {Error} If the operation fails.
914
922
  * @since 4.8.0