@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.
- package/README.md +102 -12
- package/android/build.gradle +3 -3
- package/android/src/main/java/ee/forgr/capacitor_updater/BundleInfo.java +60 -8
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdaterPlugin.java +75 -36
- package/android/src/main/java/ee/forgr/capacitor_updater/CapgoUpdater.java +41 -104
- package/android/src/main/java/ee/forgr/capacitor_updater/DownloadService.java +84 -75
- package/dist/docs.json +105 -8
- package/dist/esm/definitions.d.ts +97 -1
- package/dist/esm/definitions.js.map +1 -1
- package/ios/Sources/CapacitorUpdaterPlugin/BundleInfo.swift +37 -10
- package/ios/Sources/CapacitorUpdaterPlugin/CapacitorUpdaterPlugin.swift +26 -8
- package/ios/Sources/CapacitorUpdaterPlugin/CapgoUpdater.swift +68 -71
- package/ios/Sources/CapacitorUpdaterPlugin/InternalUtils.swift +4 -0
- package/package.json +2 -1
|
@@ -968,115 +968,41 @@ public class CapgoUpdater {
|
|
|
968
968
|
makeJsonRequest(updateUrl, json, callback);
|
|
969
969
|
}
|
|
970
970
|
|
|
971
|
-
public void unsetChannel(
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
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
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
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", "
|
|
998
|
-
retError.put("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,
|
|
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
|
-
|
|
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
|
-
//
|
|
582
|
-
|
|
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
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
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
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
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
|
-
|
|
605
|
-
|
|
607
|
+
// Use OkIO for atomic write
|
|
608
|
+
writeFileAtomic(compressedFile, responseBody.byteStream(), null);
|
|
606
609
|
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
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
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
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
|
-
|
|
630
|
-
|
|
631
|
-
|
|
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
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
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}
|
|
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": [
|