@capgo/capacitor-updater 8.0.0-alpha → 8.0.0-alpha.2

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.
@@ -968,115 +968,41 @@ public class CapgoUpdater {
968
968
  makeJsonRequest(updateUrl, json, callback);
969
969
  }
970
970
 
971
- public void unsetChannel(final Callback callback) {
972
- // Check if rate limit was exceeded
973
- if (rateLimitExceeded) {
974
- logger.debug("Skipping unsetChannel due to rate limit (429). Requests will resume after app restart.");
975
- final Map<String, Object> retError = new HashMap<>();
976
- retError.put("message", "Rate limit exceeded");
977
- retError.put("error", "rate_limit_exceeded");
978
- callback.callback(retError);
979
- return;
980
- }
971
+ public void unsetChannel(
972
+ final SharedPreferences.Editor editor,
973
+ final String defaultChannelKey,
974
+ final String configDefaultChannel,
975
+ final Callback callback
976
+ ) {
977
+ // Clear persisted defaultChannel and revert to config value
978
+ editor.remove(defaultChannelKey);
979
+ editor.apply();
980
+ this.defaultChannel = configDefaultChannel;
981
+ logger.info("Persisted defaultChannel cleared, reverted to config value: " + configDefaultChannel);
982
+
983
+ Map<String, Object> ret = new HashMap<>();
984
+ ret.put("status", "ok");
985
+ ret.put("message", "Channel override removed");
986
+ callback.callback(ret);
987
+ }
981
988
 
982
- String channelUrl = this.channelUrl;
983
- if (channelUrl == null || channelUrl.isEmpty()) {
984
- logger.error("Channel URL is not set");
985
- final Map<String, Object> retError = new HashMap<>();
986
- retError.put("message", "channelUrl missing");
987
- retError.put("error", "missing_config");
988
- callback.callback(retError);
989
- return;
990
- }
991
- JSONObject json;
992
- try {
993
- json = this.createInfoObject();
994
- } catch (JSONException e) {
995
- logger.error("Error unsetChannel JSONException " + e.getMessage());
989
+ public void setChannel(
990
+ final String channel,
991
+ final SharedPreferences.Editor editor,
992
+ final String defaultChannelKey,
993
+ final boolean allowSetDefaultChannel,
994
+ final Callback callback
995
+ ) {
996
+ // Check if setting defaultChannel is allowed
997
+ if (!allowSetDefaultChannel) {
998
+ logger.error("setChannel is disabled by allowSetDefaultChannel config");
996
999
  final Map<String, Object> retError = new HashMap<>();
997
- retError.put("message", "Cannot get info: " + e);
998
- retError.put("error", "json_error");
1000
+ retError.put("message", "setChannel is disabled by configuration");
1001
+ retError.put("error", "disabled_by_config");
999
1002
  callback.callback(retError);
1000
1003
  return;
1001
1004
  }
1002
1005
 
1003
- Request request = new Request.Builder()
1004
- .url(channelUrl)
1005
- .delete(RequestBody.create(json.toString(), MediaType.get("application/json")))
1006
- .build();
1007
-
1008
- DownloadService.sharedClient
1009
- .newCall(request)
1010
- .enqueue(
1011
- new okhttp3.Callback() {
1012
- @Override
1013
- public void onFailure(@NonNull Call call, @NonNull IOException e) {
1014
- Map<String, Object> retError = new HashMap<>();
1015
- retError.put("message", "Request failed: " + e.getMessage());
1016
- retError.put("error", "network_error");
1017
- callback.callback(retError);
1018
- }
1019
-
1020
- @Override
1021
- public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
1022
- try (ResponseBody responseBody = response.body()) {
1023
- // Check for 429 rate limit
1024
- if (checkAndHandleRateLimitResponse(response)) {
1025
- Map<String, Object> retError = new HashMap<>();
1026
- retError.put("message", "Rate limit exceeded");
1027
- retError.put("error", "rate_limit_exceeded");
1028
- callback.callback(retError);
1029
- return;
1030
- }
1031
-
1032
- if (!response.isSuccessful()) {
1033
- Map<String, Object> retError = new HashMap<>();
1034
- retError.put("message", "Server error: " + response.code());
1035
- retError.put("error", "response_error");
1036
- callback.callback(retError);
1037
- return;
1038
- }
1039
-
1040
- assert responseBody != null;
1041
- String responseData = responseBody.string();
1042
- JSONObject jsonResponse = new JSONObject(responseData);
1043
-
1044
- // Check for server-side errors first
1045
- if (jsonResponse.has("error")) {
1046
- Map<String, Object> retError = new HashMap<>();
1047
- retError.put("error", jsonResponse.getString("error"));
1048
- if (jsonResponse.has("message")) {
1049
- retError.put("message", jsonResponse.getString("message"));
1050
- } else {
1051
- retError.put("message", "server did not provide a message");
1052
- }
1053
- callback.callback(retError);
1054
- return;
1055
- }
1056
-
1057
- Map<String, Object> ret = new HashMap<>();
1058
-
1059
- Iterator<String> keys = jsonResponse.keys();
1060
- while (keys.hasNext()) {
1061
- String key = keys.next();
1062
- if (jsonResponse.has(key)) {
1063
- ret.put(key, jsonResponse.get(key));
1064
- }
1065
- }
1066
- logger.info("Channel unset");
1067
- callback.callback(ret);
1068
- } catch (JSONException e) {
1069
- Map<String, Object> retError = new HashMap<>();
1070
- retError.put("message", "JSON parse error: " + e.getMessage());
1071
- retError.put("error", "parse_error");
1072
- callback.callback(retError);
1073
- }
1074
- }
1075
- }
1076
- );
1077
- }
1078
-
1079
- public void setChannel(final String channel, final Callback callback) {
1080
1006
  // Check if rate limit was exceeded
1081
1007
  if (rateLimitExceeded) {
1082
1008
  logger.debug("Skipping setChannel due to rate limit (429). Requests will resume after app restart.");
@@ -1109,7 +1035,18 @@ public class CapgoUpdater {
1109
1035
  return;
1110
1036
  }
1111
1037
 
1112
- makeJsonRequest(channelUrl, json, callback);
1038
+ makeJsonRequest(channelUrl, json, (res) -> {
1039
+ if (res.containsKey("error")) {
1040
+ callback.callback(res);
1041
+ } else {
1042
+ // Success - persist defaultChannel
1043
+ this.defaultChannel = channel;
1044
+ editor.putString(defaultChannelKey, channel);
1045
+ editor.apply();
1046
+ logger.info("defaultChannel persisted locally: " + channel);
1047
+ callback.callback(res);
1048
+ }
1049
+ });
1113
1050
  }
1114
1051
 
1115
1052
  public void getChannel(final Callback callback) {
@@ -302,7 +302,12 @@ public class DownloadService extends Worker {
302
302
  }
303
303
 
304
304
  final String finalFileHash = fileHash;
305
- File targetFile = new File(destFolder, fileName);
305
+
306
+ // Check if file is a Brotli file and remove .br extension from target
307
+ boolean isBrotli = fileName.endsWith(".br");
308
+ String targetFileName = isBrotli ? fileName.substring(0, fileName.length() - 3) : fileName;
309
+
310
+ File targetFile = new File(destFolder, targetFileName);
306
311
  File cacheFile = new File(cacheFolder, finalFileHash + "_" + new File(fileName).getName());
307
312
  File builtinFile = new File(builtinFolder, fileName);
308
313
 
@@ -313,6 +318,7 @@ public class DownloadService extends Worker {
313
318
  continue;
314
319
  }
315
320
 
321
+ final boolean finalIsBrotli = isBrotli;
316
322
  Future<?> future = executor.submit(() -> {
317
323
  try {
318
324
  if (builtinFile.exists() && verifyChecksum(builtinFile, finalFileHash)) {
@@ -322,7 +328,7 @@ public class DownloadService extends Worker {
322
328
  copyFile(cacheFile, targetFile);
323
329
  logger.debug("already cached " + fileName);
324
330
  } else {
325
- downloadAndVerify(downloadUrl, targetFile, cacheFile, finalFileHash, sessionKey, publicKey);
331
+ downloadAndVerify(downloadUrl, targetFile, cacheFile, finalFileHash, sessionKey, publicKey, finalIsBrotli);
326
332
  }
327
333
 
328
334
  long completed = completedFiles.incrementAndGet();
@@ -572,100 +578,103 @@ public class DownloadService extends Worker {
572
578
  File cacheFile,
573
579
  String expectedHash,
574
580
  String sessionKey,
575
- String publicKey
581
+ String publicKey,
582
+ boolean isBrotli
576
583
  ) throws Exception {
577
584
  logger.debug("downloadAndVerify " + downloadUrl);
578
585
 
579
586
  Request request = new Request.Builder().url(downloadUrl).build();
580
587
 
581
- // Check if file is a Brotli file
582
- boolean isBrotli = targetFile.getName().endsWith(".br");
583
-
584
- // Create final target file with .br extension removed if it's a Brotli file
585
- File finalTargetFile = isBrotli
586
- ? new File(targetFile.getParentFile(), targetFile.getName().substring(0, targetFile.getName().length() - 3))
587
- : targetFile;
588
+ // targetFile is already the final destination without .br extension
589
+ File finalTargetFile = targetFile;
588
590
 
589
591
  // Create a temporary file for the compressed data
590
592
  File compressedFile = new File(getApplicationContext().getCacheDir(), "temp_" + targetFile.getName() + ".tmp");
591
593
 
592
- try (Response response = sharedClient.newCall(request).execute()) {
593
- if (!response.isSuccessful()) {
594
- sendStatsAsync("download_manifest_file_fail", getInputData().getString(VERSION) + ":" + finalTargetFile.getName());
595
- throw new IOException("Unexpected response code: " + response.code());
596
- }
594
+ try {
595
+ try (Response response = sharedClient.newCall(request).execute()) {
596
+ if (!response.isSuccessful()) {
597
+ sendStatsAsync("download_manifest_file_fail", getInputData().getString(VERSION) + ":" + finalTargetFile.getName());
598
+ throw new IOException("Unexpected response code: " + response.code());
599
+ }
597
600
 
598
- // Download compressed file atomically
599
- ResponseBody responseBody = response.body();
600
- if (responseBody == null) {
601
- throw new IOException("Response body is null");
602
- }
601
+ // Download compressed file atomically
602
+ ResponseBody responseBody = response.body();
603
+ if (responseBody == null) {
604
+ throw new IOException("Response body is null");
605
+ }
603
606
 
604
- // Use OkIO for atomic write
605
- writeFileAtomic(compressedFile, responseBody.byteStream(), null);
607
+ // Use OkIO for atomic write
608
+ writeFileAtomic(compressedFile, responseBody.byteStream(), null);
606
609
 
607
- if (!publicKey.isEmpty() && sessionKey != null && !sessionKey.isEmpty()) {
608
- logger.debug("Decrypting file " + targetFile.getName());
609
- CryptoCipher.decryptFile(compressedFile, publicKey, sessionKey);
610
- }
610
+ if (!publicKey.isEmpty() && sessionKey != null && !sessionKey.isEmpty()) {
611
+ logger.debug("Decrypting file " + targetFile.getName());
612
+ CryptoCipher.decryptFile(compressedFile, publicKey, sessionKey);
613
+ }
611
614
 
612
- // Only decompress if file has .br extension
613
- if (isBrotli) {
614
- // Use new decompression method with atomic write
615
- try (FileInputStream fis = new FileInputStream(compressedFile)) {
616
- byte[] compressedData = new byte[(int) compressedFile.length()];
617
- fis.read(compressedData);
618
- byte[] decompressedData;
619
- try {
620
- decompressedData = decompressBrotli(compressedData, targetFile.getName());
621
- } catch (IOException e) {
622
- sendStatsAsync(
623
- "download_manifest_brotli_fail",
624
- getInputData().getString(VERSION) + ":" + finalTargetFile.getName()
625
- );
626
- throw e;
627
- }
615
+ // Only decompress if file has .br extension
616
+ if (isBrotli) {
617
+ // Use new decompression method with atomic write
618
+ try (FileInputStream fis = new FileInputStream(compressedFile)) {
619
+ byte[] compressedData = new byte[(int) compressedFile.length()];
620
+ fis.read(compressedData);
621
+ byte[] decompressedData;
622
+ try {
623
+ decompressedData = decompressBrotli(compressedData, targetFile.getName());
624
+ } catch (IOException e) {
625
+ sendStatsAsync(
626
+ "download_manifest_brotli_fail",
627
+ getInputData().getString(VERSION) + ":" + finalTargetFile.getName()
628
+ );
629
+ throw e;
630
+ }
628
631
 
629
- // Write decompressed data atomically
630
- try (java.io.ByteArrayInputStream bais = new java.io.ByteArrayInputStream(decompressedData)) {
631
- writeFileAtomic(finalTargetFile, bais, null);
632
+ // Write decompressed data atomically
633
+ try (java.io.ByteArrayInputStream bais = new java.io.ByteArrayInputStream(decompressedData)) {
634
+ writeFileAtomic(finalTargetFile, bais, null);
635
+ }
636
+ }
637
+ } else {
638
+ // Just copy the file without decompression using atomic operation
639
+ try (FileInputStream fis = new FileInputStream(compressedFile)) {
640
+ writeFileAtomic(finalTargetFile, fis, null);
632
641
  }
633
642
  }
634
- } else {
635
- // Just copy the file without decompression using atomic operation
636
- try (FileInputStream fis = new FileInputStream(compressedFile)) {
637
- writeFileAtomic(finalTargetFile, fis, null);
638
- }
639
- }
640
643
 
641
- // Delete the compressed file
642
- compressedFile.delete();
643
- String calculatedHash = CryptoCipher.calcChecksum(finalTargetFile);
644
- CryptoCipher.logChecksumInfo("Calculated checksum", calculatedHash);
645
- CryptoCipher.logChecksumInfo("Expected checksum", expectedHash);
646
-
647
- // Verify checksum
648
- if (calculatedHash.equals(expectedHash)) {
649
- // Only cache if checksum is correct - use atomic copy
650
- try (FileInputStream fis = new FileInputStream(finalTargetFile)) {
651
- writeFileAtomic(cacheFile, fis, expectedHash);
644
+ // Delete the compressed file
645
+ compressedFile.delete();
646
+ String calculatedHash = CryptoCipher.calcChecksum(finalTargetFile);
647
+ CryptoCipher.logChecksumInfo("Calculated checksum", calculatedHash);
648
+ CryptoCipher.logChecksumInfo("Expected checksum", expectedHash);
649
+
650
+ // Verify checksum
651
+ if (calculatedHash.equals(expectedHash)) {
652
+ // Only cache if checksum is correct - use atomic copy
653
+ try (FileInputStream fis = new FileInputStream(finalTargetFile)) {
654
+ writeFileAtomic(cacheFile, fis, expectedHash);
655
+ }
656
+ } else {
657
+ finalTargetFile.delete();
658
+ sendStatsAsync("download_manifest_checksum_fail", getInputData().getString(VERSION) + ":" + finalTargetFile.getName());
659
+ throw new IOException(
660
+ "Checksum verification failed for: " +
661
+ downloadUrl +
662
+ " " +
663
+ targetFile.getName() +
664
+ " expected: " +
665
+ expectedHash +
666
+ " calculated: " +
667
+ calculatedHash
668
+ );
652
669
  }
653
- } else {
654
- finalTargetFile.delete();
655
- sendStatsAsync("download_manifest_checksum_fail", getInputData().getString(VERSION) + ":" + finalTargetFile.getName());
656
- throw new IOException(
657
- "Checksum verification failed for: " +
658
- downloadUrl +
659
- " " +
660
- targetFile.getName() +
661
- " expected: " +
662
- expectedHash +
663
- " calculated: " +
664
- calculatedHash
665
- );
666
670
  }
667
671
  } catch (Exception e) {
668
672
  throw new IOException("Error in downloadAndVerify: " + e.getMessage());
673
+ } finally {
674
+ // Always cleanup the compressed temp file if it still exists
675
+ if (compressedFile.exists()) {
676
+ compressedFile.delete();
677
+ }
669
678
  }
670
679
  }
671
680
 
package/dist/docs.json CHANGED
@@ -492,14 +492,14 @@
492
492
  },
493
493
  {
494
494
  "name": "throws",
495
- "text": "{Error} If the request fails or the server returns an error."
495
+ "text": "{Error} Always throws when no new version is available (`error: \"no_new_version_available\"`), or when the request fails."
496
496
  },
497
497
  {
498
498
  "name": "since",
499
499
  "text": "4.0.0"
500
500
  }
501
501
  ],
502
- "docs": "Check the update server for the latest available bundle version.\n\nThis queries your configured update URL (or Capgo backend) to see if a newer bundle\nis available for download. It does NOT download the bundle automatically.\n\nThe response includes:\n- `version`: The latest available version identifier\n- `url`: Download URL for the bundle (if available)\n- `breaking`: Whether this update is marked as incompatible (requires native app update)\n- `message`: Optional message from the server\n- `manifest`: File list for partial updates (if using multi-file downloads)\n\nAfter receiving the latest version info, you can:\n1. Compare it with your current version\n2. Download it using {@link download}\n3. Apply it using {@link next} or {@link set}",
502
+ "docs": "Check the update server for the latest available bundle version.\n\nThis queries your configured update URL (or Capgo backend) to see if a newer bundle\nis available for download. It does NOT download the bundle automatically.\n\nThe response includes:\n- `version`: The latest available version identifier\n- `url`: Download URL for the bundle (if available)\n- `breaking`: Whether this update is marked as incompatible (requires native app update)\n- `message`: Optional message from the server\n- `manifest`: File list for partial updates (if using multi-file downloads)\n\nAfter receiving the latest version info, you can:\n1. Compare it with your current version\n2. Download it using {@link download}\n3. Apply it using {@link next} or {@link set}\n\n**Important: Error handling for \"no new version available\"**\n\nWhen the device's current version matches the latest version on the server (i.e., the device is already\nup-to-date), the server returns a 200 response with `error: \"no_new_version_available\"` and\n`message: \"No new version available\"`. **This causes `getLatest()` to throw an error**, even though\nthis is a normal, expected condition.\n\nYou should catch this specific error to handle it gracefully:\n\n```typescript\ntry {\n const latest = await CapacitorUpdater.getLatest();\n // New version is available, proceed with download\n} catch (error) {\n if (error.message === 'No new version available') {\n // Device is already on the latest version - this is normal\n console.log('Already up to date');\n } else {\n // Actual error occurred\n console.error('Failed to check for updates:', error);\n }\n}\n```\n\nIn this scenario, the server:\n- Logs the request with a \"No new version available\" message\n- Sends a \"noNew\" stat action to track that the device checked for updates but was already current (done on the backend)",
503
503
  "complexTypes": [
504
504
  "LatestVersion",
505
505
  "GetLatestOptions"
@@ -535,7 +535,7 @@
535
535
  "text": "4.7.0"
536
536
  }
537
537
  ],
538
- "docs": "Assign this device to a specific update channel at runtime.\n\nChannels allow you to distribute different bundle versions to different groups of users\n(e.g., \"production\", \"beta\", \"staging\"). This method switches the device to a new channel.\n\n**Requirements:**\n- The target channel must allow self-assignment (configured in your Capgo dashboard or backend)\n- The backend may accept or reject the request based on channel settings\n\n**When to use:**\n- After the app is ready and the user has interacted (e.g., opted into beta program)\n- To implement in-app channel switching (beta toggle, tester access, etc.)\n- For user-driven channel changes\n\n**When NOT to use:**\n- At app boot/initialization - use {@link PluginsConfig.CapacitorUpdater.defaultChannel} config instead\n- Before user interaction\n\nThis sends a request to the Capgo backend linking your device ID to the specified channel.",
538
+ "docs": "Assign this device to a specific update channel at runtime.\n\nChannels allow you to distribute different bundle versions to different groups of users\n(e.g., \"production\", \"beta\", \"staging\"). This method switches the device to a new channel.\n\n**Requirements:**\n- The target channel must allow self-assignment (configured in your Capgo dashboard or backend)\n- The backend may accept or reject the request based on channel settings\n\n**When to use:**\n- After the app is ready and the user has interacted (e.g., opted into beta program)\n- To implement in-app channel switching (beta toggle, tester access, etc.)\n- For user-driven channel changes\n\n**When NOT to use:**\n- At app boot/initialization - use {@link PluginsConfig.CapacitorUpdater.defaultChannel} config instead\n- Before user interaction\n\n**Important: Listen for the `channelPrivate` event**\n\nWhen a user attempts to set a channel that doesn't allow device self-assignment, the method will\nthrow an error AND fire a {@link addListener}('channelPrivate') event. You should listen to this event\nto provide appropriate feedback to users:\n\n```typescript\nCapacitorUpdater.addListener('channelPrivate', (data) => {\n console.warn(`Cannot access channel \"${data.channel}\": ${data.message}`);\n // Show user-friendly message\n});\n```\n\nThis sends a request to the Capgo backend linking your device ID to the specified channel.",
539
539
  "complexTypes": [
540
540
  "ChannelRes",
541
541
  "SetChannelOptions"
@@ -1062,6 +1062,35 @@
1062
1062
  ],
1063
1063
  "slug": "addlistenerappready-"
1064
1064
  },
1065
+ {
1066
+ "name": "addListener",
1067
+ "signature": "(eventName: 'channelPrivate', listenerFunc: (state: ChannelPrivateEvent) => void) => Promise<PluginListenerHandle>",
1068
+ "parameters": [
1069
+ {
1070
+ "name": "eventName",
1071
+ "docs": "",
1072
+ "type": "'channelPrivate'"
1073
+ },
1074
+ {
1075
+ "name": "listenerFunc",
1076
+ "docs": "",
1077
+ "type": "(state: ChannelPrivateEvent) => void"
1078
+ }
1079
+ ],
1080
+ "returns": "Promise<PluginListenerHandle>",
1081
+ "tags": [
1082
+ {
1083
+ "name": "since",
1084
+ "text": "7.34.0"
1085
+ }
1086
+ ],
1087
+ "docs": "Listen for channel private event, fired when attempting to set a channel that doesn't allow device self-assignment.\n\nThis event is useful for:\n- Informing users they don't have permission to switch to a specific channel\n- Implementing custom error handling for channel restrictions\n- Logging unauthorized channel access attempts",
1088
+ "complexTypes": [
1089
+ "PluginListenerHandle",
1090
+ "ChannelPrivateEvent"
1091
+ ],
1092
+ "slug": "addlistenerchannelprivate-"
1093
+ },
1065
1094
  {
1066
1095
  "name": "isAutoUpdateAvailable",
1067
1096
  "signature": "() => Promise<AutoUpdateAvailable>",
@@ -1690,7 +1719,7 @@
1690
1719
  {
1691
1720
  "name": "message",
1692
1721
  "tags": [],
1693
- "docs": "",
1722
+ "docs": "Optional message from the server.\nWhen no new version is available, this will be \"No new version available\".",
1694
1723
  "complexTypes": [],
1695
1724
  "type": "string | undefined"
1696
1725
  },
@@ -1704,21 +1733,21 @@
1704
1733
  {
1705
1734
  "name": "error",
1706
1735
  "tags": [],
1707
- "docs": "",
1736
+ "docs": "Error code from the server, if any.\nCommon values:\n- `\"no_new_version_available\"`: Device is already on the latest version (not a failure)\n- Other error codes indicate actual failures in the update process",
1708
1737
  "complexTypes": [],
1709
1738
  "type": "string | undefined"
1710
1739
  },
1711
1740
  {
1712
1741
  "name": "old",
1713
1742
  "tags": [],
1714
- "docs": "",
1743
+ "docs": "The previous/current version name (provided for reference).",
1715
1744
  "complexTypes": [],
1716
1745
  "type": "string | undefined"
1717
1746
  },
1718
1747
  {
1719
1748
  "name": "url",
1720
1749
  "tags": [],
1721
- "docs": "",
1750
+ "docs": "Download URL for the bundle (when a new version is available).",
1722
1751
  "complexTypes": [],
1723
1752
  "type": "string | undefined"
1724
1753
  },
@@ -1730,11 +1759,35 @@
1730
1759
  "name": "since"
1731
1760
  }
1732
1761
  ],
1733
- "docs": "",
1762
+ "docs": "File list for partial updates (when using multi-file downloads).",
1734
1763
  "complexTypes": [
1735
1764
  "ManifestEntry"
1736
1765
  ],
1737
1766
  "type": "ManifestEntry[] | undefined"
1767
+ },
1768
+ {
1769
+ "name": "link",
1770
+ "tags": [
1771
+ {
1772
+ "text": "7.35.0",
1773
+ "name": "since"
1774
+ }
1775
+ ],
1776
+ "docs": "Optional link associated with this bundle version (e.g., release notes URL, changelog, GitHub release).",
1777
+ "complexTypes": [],
1778
+ "type": "string | undefined"
1779
+ },
1780
+ {
1781
+ "name": "comment",
1782
+ "tags": [
1783
+ {
1784
+ "text": "7.35.0",
1785
+ "name": "since"
1786
+ }
1787
+ ],
1788
+ "docs": "Optional comment or description for this bundle version.",
1789
+ "complexTypes": [],
1790
+ "type": "string | undefined"
1738
1791
  }
1739
1792
  ]
1740
1793
  },
@@ -2260,6 +2313,34 @@
2260
2313
  }
2261
2314
  ]
2262
2315
  },
2316
+ {
2317
+ "name": "ChannelPrivateEvent",
2318
+ "slug": "channelprivateevent",
2319
+ "docs": "",
2320
+ "tags": [],
2321
+ "methods": [],
2322
+ "properties": [
2323
+ {
2324
+ "name": "channel",
2325
+ "tags": [
2326
+ {
2327
+ "text": "7.34.0",
2328
+ "name": "since"
2329
+ }
2330
+ ],
2331
+ "docs": "Emitted when attempting to set a channel that doesn't allow device self-assignment.",
2332
+ "complexTypes": [],
2333
+ "type": "string"
2334
+ },
2335
+ {
2336
+ "name": "message",
2337
+ "tags": [],
2338
+ "docs": "",
2339
+ "complexTypes": [],
2340
+ "type": "string"
2341
+ }
2342
+ ]
2343
+ },
2263
2344
  {
2264
2345
  "name": "AutoUpdateAvailable",
2265
2346
  "slug": "autoupdateavailable",
@@ -2860,6 +2941,22 @@
2860
2941
  "complexTypes": [],
2861
2942
  "type": "boolean | undefined"
2862
2943
  },
2944
+ {
2945
+ "name": "allowSetDefaultChannel",
2946
+ "tags": [
2947
+ {
2948
+ "text": "true",
2949
+ "name": "default"
2950
+ },
2951
+ {
2952
+ "text": "7.34.0",
2953
+ "name": "since"
2954
+ }
2955
+ ],
2956
+ "docs": "Allow or disallow the {@link CapacitorUpdaterPlugin.setChannel} method to modify the defaultChannel.\nWhen set to `false`, calling `setChannel()` will return an error with code `disabled_by_config`.",
2957
+ "complexTypes": [],
2958
+ "type": "boolean | undefined"
2959
+ },
2863
2960
  {
2864
2961
  "name": "defaultChannel",
2865
2962
  "tags": [