@capgo/capacitor-updater 5.30.0 → 5.34.0
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 +98 -12
- package/android/build.gradle +3 -3
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdaterPlugin.java +76 -36
- package/android/src/main/java/ee/forgr/capacitor_updater/CapgoUpdater.java +60 -104
- package/android/src/main/java/ee/forgr/capacitor_updater/CryptoCipher.java +76 -1
- package/android/src/main/java/ee/forgr/capacitor_updater/DownloadService.java +4 -0
- package/dist/docs.json +81 -8
- package/dist/esm/definitions.d.ts +87 -1
- package/dist/esm/definitions.js.map +1 -1
- package/ios/Sources/CapacitorUpdaterPlugin/CapacitorUpdaterPlugin.swift +28 -5
- package/ios/Sources/CapacitorUpdaterPlugin/CapgoUpdater.swift +41 -46
- package/ios/Sources/CapacitorUpdaterPlugin/CryptoCipher.swift +48 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -66,6 +66,14 @@ The most complete [documentation here](https://capgo.app/docs/).
|
|
|
66
66
|
## Community
|
|
67
67
|
Join the [discord](https://discord.gg/VnYRvBfgA6) to get help.
|
|
68
68
|
|
|
69
|
+
## Migration to v7.34
|
|
70
|
+
|
|
71
|
+
- **Channel storage change**: `setChannel()` now stores channel assignments locally on the device instead of in the cloud. This provides better offline support and reduces backend load.
|
|
72
|
+
- Channel assignments persist between app restarts
|
|
73
|
+
- Use `unsetChannel()` to clear the local assignment and revert to `defaultChannel`
|
|
74
|
+
- Old devices (< v7.34.0) will continue using cloud-based storage
|
|
75
|
+
- **New event**: Listen to the `channelPrivate` event to handle cases where a user tries to assign themselves to a private channel (one that doesn't allow self-assignment). See example in the `setChannel()` documentation above.
|
|
76
|
+
|
|
69
77
|
## Migration to v7
|
|
70
78
|
|
|
71
79
|
- `privateKey` is not available anymore, it was used for the old encryption method. to migrate follow this guide : [https://capgo.app/docs/plugin/cloud-mode/getting-started/](https://capgo.app/docs/cli/migrations/encryption/)
|
|
@@ -271,6 +279,7 @@ CapacitorUpdater can be configured with these options:
|
|
|
271
279
|
| **`allowManualBundleError`** | <code>boolean</code> | Allow marking bundles as errored from JavaScript while using manual update flows. When enabled, {@link CapacitorUpdaterPlugin.setBundleError} can change a bundle status to `error`. | <code>false</code> | 7.20.0 |
|
|
272
280
|
| **`persistCustomId`** | <code>boolean</code> | Persist the customId set through {@link CapacitorUpdaterPlugin.setCustomId} across app restarts. Only available for Android and iOS. | <code>false (will be true by default in a future major release v8.x.x)</code> | 7.17.3 |
|
|
273
281
|
| **`persistModifyUrl`** | <code>boolean</code> | Persist the updateUrl, statsUrl and channelUrl set through {@link CapacitorUpdaterPlugin.setUpdateUrl}, {@link CapacitorUpdaterPlugin.setStatsUrl} and {@link CapacitorUpdaterPlugin.setChannelUrl} across app restarts. Only available for Android and iOS. | <code>false</code> | 7.20.0 |
|
|
282
|
+
| **`allowSetDefaultChannel`** | <code>boolean</code> | Allow or disallow the {@link CapacitorUpdaterPlugin.setChannel} method to modify the defaultChannel. When set to `false`, calling `setChannel()` will return an error with code `disabled_by_config`. | <code>true</code> | 7.34.0 |
|
|
274
283
|
| **`defaultChannel`** | <code>string</code> | Set the default channel for the app in the config. Case sensitive. This will setting will override the default channel set in the cloud, but will still respect overrides made in the cloud. This requires the channel to allow devices to self dissociate/associate in the channel settings. https://capgo.app/docs/public-api/channels/#channel-configuration-options | <code>undefined</code> | 5.5.0 |
|
|
275
284
|
| **`appId`** | <code>string</code> | Configure the app id for the app in the config. | <code>undefined</code> | 6.0.0 |
|
|
276
285
|
| **`keepUrlPathAfterReload`** | <code>boolean</code> | Configure the plugin to keep the URL path after a reload. WARNING: When a reload is triggered, 'window.history' will be cleared. | <code>false</code> | 6.8.0 |
|
|
@@ -313,6 +322,7 @@ In `capacitor.config.json`:
|
|
|
313
322
|
"allowManualBundleError": undefined,
|
|
314
323
|
"persistCustomId": undefined,
|
|
315
324
|
"persistModifyUrl": undefined,
|
|
325
|
+
"allowSetDefaultChannel": undefined,
|
|
316
326
|
"defaultChannel": undefined,
|
|
317
327
|
"appId": undefined,
|
|
318
328
|
"keepUrlPathAfterReload": undefined,
|
|
@@ -361,6 +371,7 @@ const config: CapacitorConfig = {
|
|
|
361
371
|
allowManualBundleError: undefined,
|
|
362
372
|
persistCustomId: undefined,
|
|
363
373
|
persistModifyUrl: undefined,
|
|
374
|
+
allowSetDefaultChannel: undefined,
|
|
364
375
|
defaultChannel: undefined,
|
|
365
376
|
appId: undefined,
|
|
366
377
|
keepUrlPathAfterReload: undefined,
|
|
@@ -416,6 +427,7 @@ export default config;
|
|
|
416
427
|
* [`addListener('downloadFailed', ...)`](#addlistenerdownloadfailed-)
|
|
417
428
|
* [`addListener('appReloaded', ...)`](#addlistenerappreloaded-)
|
|
418
429
|
* [`addListener('appReady', ...)`](#addlistenerappready-)
|
|
430
|
+
* [`addListener('channelPrivate', ...)`](#addlistenerchannelprivate-)
|
|
419
431
|
* [`isAutoUpdateAvailable()`](#isautoupdateavailable)
|
|
420
432
|
* [`getNextBundle()`](#getnextbundle)
|
|
421
433
|
* [`getFailedUpdate()`](#getfailedupdate)
|
|
@@ -863,6 +875,34 @@ After receiving the latest version info, you can:
|
|
|
863
875
|
2. Download it using {@link download}
|
|
864
876
|
3. Apply it using {@link next} or {@link set}
|
|
865
877
|
|
|
878
|
+
**Important: Error handling for "no new version available"**
|
|
879
|
+
|
|
880
|
+
When the device's current version matches the latest version on the server (i.e., the device is already
|
|
881
|
+
up-to-date), the server returns a 200 response with `error: "no_new_version_available"` and
|
|
882
|
+
`message: "No new version available"`. **This causes `getLatest()` to throw an error**, even though
|
|
883
|
+
this is a normal, expected condition.
|
|
884
|
+
|
|
885
|
+
You should catch this specific error to handle it gracefully:
|
|
886
|
+
|
|
887
|
+
```typescript
|
|
888
|
+
try {
|
|
889
|
+
const latest = await CapacitorUpdater.getLatest();
|
|
890
|
+
// New version is available, proceed with download
|
|
891
|
+
} catch (error) {
|
|
892
|
+
if (error.message === 'No new version available') {
|
|
893
|
+
// Device is already on the latest version - this is normal
|
|
894
|
+
console.log('Already up to date');
|
|
895
|
+
} else {
|
|
896
|
+
// Actual error occurred
|
|
897
|
+
console.error('Failed to check for updates:', error);
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
```
|
|
901
|
+
|
|
902
|
+
In this scenario, the server:
|
|
903
|
+
- Logs the request with a "No new version available" message
|
|
904
|
+
- Sends a "noNew" stat action to track that the device checked for updates but was already current (done on the backend)
|
|
905
|
+
|
|
866
906
|
| Param | Type | Description |
|
|
867
907
|
| ------------- | ------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
|
|
868
908
|
| **`options`** | <code><a href="#getlatestoptions">GetLatestOptions</a></code> | Optional {@link <a href="#getlatestoptions">GetLatestOptions</a>} to specify which channel to check. |
|
|
@@ -898,6 +938,19 @@ Channels allow you to distribute different bundle versions to different groups o
|
|
|
898
938
|
- At app boot/initialization - use {@link PluginsConfig.CapacitorUpdater.defaultChannel} config instead
|
|
899
939
|
- Before user interaction
|
|
900
940
|
|
|
941
|
+
**Important: Listen for the `channelPrivate` event**
|
|
942
|
+
|
|
943
|
+
When a user attempts to set a channel that doesn't allow device self-assignment, the method will
|
|
944
|
+
throw an error AND fire a {@link addListener}('channelPrivate') event. You should listen to this event
|
|
945
|
+
to provide appropriate feedback to users:
|
|
946
|
+
|
|
947
|
+
```typescript
|
|
948
|
+
CapacitorUpdater.addListener('channelPrivate', (data) => {
|
|
949
|
+
console.warn(`Cannot access channel "${data.channel}": ${data.message}`);
|
|
950
|
+
// Show user-friendly message
|
|
951
|
+
});
|
|
952
|
+
```
|
|
953
|
+
|
|
901
954
|
This sends a request to the Capgo backend linking your device ID to the specified channel.
|
|
902
955
|
|
|
903
956
|
| Param | Type | Description |
|
|
@@ -1362,6 +1415,31 @@ Listen for app ready event in the App, let you know when app is ready to use, th
|
|
|
1362
1415
|
--------------------
|
|
1363
1416
|
|
|
1364
1417
|
|
|
1418
|
+
#### addListener('channelPrivate', ...)
|
|
1419
|
+
|
|
1420
|
+
```typescript
|
|
1421
|
+
addListener(eventName: 'channelPrivate', listenerFunc: (state: ChannelPrivateEvent) => void) => Promise<PluginListenerHandle>
|
|
1422
|
+
```
|
|
1423
|
+
|
|
1424
|
+
Listen for channel private event, fired when attempting to set a channel that doesn't allow device self-assignment.
|
|
1425
|
+
|
|
1426
|
+
This event is useful for:
|
|
1427
|
+
- Informing users they don't have permission to switch to a specific channel
|
|
1428
|
+
- Implementing custom error handling for channel restrictions
|
|
1429
|
+
- Logging unauthorized channel access attempts
|
|
1430
|
+
|
|
1431
|
+
| Param | Type |
|
|
1432
|
+
| ------------------ | --------------------------------------------------------------------------------------- |
|
|
1433
|
+
| **`eventName`** | <code>'channelPrivate'</code> |
|
|
1434
|
+
| **`listenerFunc`** | <code>(state: <a href="#channelprivateevent">ChannelPrivateEvent</a>) => void</code> |
|
|
1435
|
+
|
|
1436
|
+
**Returns:** <code>Promise<<a href="#pluginlistenerhandle">PluginListenerHandle</a>></code>
|
|
1437
|
+
|
|
1438
|
+
**Since:** 7.34.0
|
|
1439
|
+
|
|
1440
|
+
--------------------
|
|
1441
|
+
|
|
1442
|
+
|
|
1365
1443
|
#### isAutoUpdateAvailable()
|
|
1366
1444
|
|
|
1367
1445
|
```typescript
|
|
@@ -1678,18 +1756,18 @@ If you don't use backend, you need to provide the URL and version of the bundle.
|
|
|
1678
1756
|
|
|
1679
1757
|
##### LatestVersion
|
|
1680
1758
|
|
|
1681
|
-
| Prop | Type | Description
|
|
1682
|
-
| ---------------- | ---------------------------- |
|
|
1683
|
-
| **`version`** | <code>string</code> | Result of getLatest method
|
|
1684
|
-
| **`checksum`** | <code>string</code> |
|
|
1685
|
-
| **`breaking`** | <code>boolean</code> | Indicates whether the update was flagged as breaking by the backend.
|
|
1686
|
-
| **`major`** | <code>boolean</code> |
|
|
1687
|
-
| **`message`** | <code>string</code> |
|
|
1688
|
-
| **`sessionKey`** | <code>string</code> |
|
|
1689
|
-
| **`error`** | <code>string</code> |
|
|
1690
|
-
| **`old`** | <code>string</code> |
|
|
1691
|
-
| **`url`** | <code>string</code> |
|
|
1692
|
-
| **`manifest`** | <code>ManifestEntry[]</code> |
|
|
1759
|
+
| Prop | Type | Description | Since |
|
|
1760
|
+
| ---------------- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ |
|
|
1761
|
+
| **`version`** | <code>string</code> | Result of getLatest method | 4.0.0 |
|
|
1762
|
+
| **`checksum`** | <code>string</code> | | 6 |
|
|
1763
|
+
| **`breaking`** | <code>boolean</code> | Indicates whether the update was flagged as breaking by the backend. | 7.22.0 |
|
|
1764
|
+
| **`major`** | <code>boolean</code> | | |
|
|
1765
|
+
| **`message`** | <code>string</code> | Optional message from the server. When no new version is available, this will be "No new version available". | |
|
|
1766
|
+
| **`sessionKey`** | <code>string</code> | | |
|
|
1767
|
+
| **`error`** | <code>string</code> | Error code from the server, if any. Common values: - `"no_new_version_available"`: Device is already on the latest version (not a failure) - Other error codes indicate actual failures in the update process | |
|
|
1768
|
+
| **`old`** | <code>string</code> | The previous/current version name (provided for reference). | |
|
|
1769
|
+
| **`url`** | <code>string</code> | Download URL for the bundle (when a new version is available). | |
|
|
1770
|
+
| **`manifest`** | <code>ManifestEntry[]</code> | File list for partial updates (when using multi-file downloads). | 6.1 |
|
|
1693
1771
|
|
|
1694
1772
|
|
|
1695
1773
|
##### GetLatestOptions
|
|
@@ -1851,6 +1929,14 @@ If you don't use backend, you need to provide the URL and version of the bundle.
|
|
|
1851
1929
|
| **`status`** | <code>string</code> | | |
|
|
1852
1930
|
|
|
1853
1931
|
|
|
1932
|
+
##### ChannelPrivateEvent
|
|
1933
|
+
|
|
1934
|
+
| Prop | Type | Description | Since |
|
|
1935
|
+
| ------------- | ------------------- | ----------------------------------------------------------------------------------- | ------ |
|
|
1936
|
+
| **`channel`** | <code>string</code> | Emitted when attempting to set a channel that doesn't allow device self-assignment. | 7.34.0 |
|
|
1937
|
+
| **`message`** | <code>string</code> | | |
|
|
1938
|
+
|
|
1939
|
+
|
|
1854
1940
|
##### AutoUpdateAvailable
|
|
1855
1941
|
|
|
1856
1942
|
| Prop | Type |
|
package/android/build.gradle
CHANGED
|
@@ -49,16 +49,16 @@ repositories {
|
|
|
49
49
|
|
|
50
50
|
|
|
51
51
|
dependencies {
|
|
52
|
-
def work_version = "2.
|
|
52
|
+
def work_version = "2.10.5"
|
|
53
53
|
implementation "androidx.work:work-runtime:$work_version"
|
|
54
54
|
implementation "com.google.android.gms:play-services-tasks:18.4.0"
|
|
55
|
-
implementation "com.google.guava:guava:33.
|
|
55
|
+
implementation "com.google.guava:guava:33.5.0-android"
|
|
56
56
|
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
|
57
57
|
implementation project(':capacitor-android')
|
|
58
58
|
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
|
|
59
59
|
implementation 'io.github.g00fy2:versioncompare:1.5.0'
|
|
60
60
|
testImplementation "junit:junit:$junitVersion"
|
|
61
|
-
testImplementation 'org.mockito:mockito-core:5.
|
|
61
|
+
testImplementation 'org.mockito:mockito-core:5.20.0'
|
|
62
62
|
testImplementation 'org.json:json:20250517'
|
|
63
63
|
testImplementation 'org.robolectric:robolectric:4.13'
|
|
64
64
|
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
|
|
@@ -68,10 +68,11 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
68
68
|
private static final String UPDATE_URL_PREF_KEY = "CapacitorUpdater.updateUrl";
|
|
69
69
|
private static final String STATS_URL_PREF_KEY = "CapacitorUpdater.statsUrl";
|
|
70
70
|
private static final String CHANNEL_URL_PREF_KEY = "CapacitorUpdater.channelUrl";
|
|
71
|
+
private static final String DEFAULT_CHANNEL_PREF_KEY = "CapacitorUpdater.defaultChannel";
|
|
71
72
|
private static final String[] BREAKING_EVENT_NAMES = { "breakingAvailable", "majorAvailable" };
|
|
72
73
|
private static final String LAST_FAILED_BUNDLE_PREF_KEY = "CapacitorUpdater.lastFailedBundle";
|
|
73
74
|
|
|
74
|
-
private final String pluginVersion = "5.
|
|
75
|
+
private final String pluginVersion = "5.31.0";
|
|
75
76
|
private static final String DELAY_CONDITION_PREFERENCES = "";
|
|
76
77
|
|
|
77
78
|
private SharedPreferences.Editor editor;
|
|
@@ -100,6 +101,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
100
101
|
private Boolean onLaunchDirectUpdateUsed = false;
|
|
101
102
|
Boolean shakeMenuEnabled = false;
|
|
102
103
|
private Boolean allowManualBundleError = false;
|
|
104
|
+
private Boolean allowSetDefaultChannel = true;
|
|
103
105
|
|
|
104
106
|
private Boolean isPreviousMainActivity = true;
|
|
105
107
|
|
|
@@ -301,6 +303,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
301
303
|
|
|
302
304
|
this.persistCustomId = this.getConfig().getBoolean("persistCustomId", false);
|
|
303
305
|
this.persistModifyUrl = this.getConfig().getBoolean("persistModifyUrl", false);
|
|
306
|
+
this.allowSetDefaultChannel = this.getConfig().getBoolean("allowSetDefaultChannel", true);
|
|
304
307
|
this.implementation.publicKey = this.getConfig().getString("publicKey", "");
|
|
305
308
|
this.implementation.statsUrl = this.getConfig().getString("statsUrl", statsUrlDefault);
|
|
306
309
|
this.implementation.channelUrl = this.getConfig().getString("channelUrl", channelUrlDefault);
|
|
@@ -320,8 +323,21 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
320
323
|
}
|
|
321
324
|
}
|
|
322
325
|
}
|
|
326
|
+
|
|
327
|
+
// Load defaultChannel: first try from persistent storage (set via setChannel), then fall back to config
|
|
328
|
+
if (this.prefs.contains(DEFAULT_CHANNEL_PREF_KEY)) {
|
|
329
|
+
final String storedDefaultChannel = this.prefs.getString(DEFAULT_CHANNEL_PREF_KEY, "");
|
|
330
|
+
if (storedDefaultChannel != null && !storedDefaultChannel.isEmpty()) {
|
|
331
|
+
this.implementation.defaultChannel = storedDefaultChannel;
|
|
332
|
+
logger.info("Loaded persisted defaultChannel from setChannel()");
|
|
333
|
+
} else {
|
|
334
|
+
this.implementation.defaultChannel = this.getConfig().getString("defaultChannel", "");
|
|
335
|
+
}
|
|
336
|
+
} else {
|
|
337
|
+
this.implementation.defaultChannel = this.getConfig().getString("defaultChannel", "");
|
|
338
|
+
}
|
|
339
|
+
|
|
323
340
|
int userValue = this.getConfig().getInt("periodCheckDelay", 0);
|
|
324
|
-
this.implementation.defaultChannel = this.getConfig().getString("defaultChannel", "");
|
|
325
341
|
|
|
326
342
|
if (userValue >= 0 && userValue <= 600) {
|
|
327
343
|
this.periodCheckDelay = 600 * 1000;
|
|
@@ -703,6 +719,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
703
719
|
}
|
|
704
720
|
}
|
|
705
721
|
this.implementation.cleanupDownloadDirectories(allowedIds);
|
|
722
|
+
this.implementation.cleanupDeltaCache();
|
|
706
723
|
}
|
|
707
724
|
this.editor.putString("LatestNativeBuildVersion", this.currentBuildVersion);
|
|
708
725
|
this.editor.apply();
|
|
@@ -862,27 +879,33 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
862
879
|
|
|
863
880
|
try {
|
|
864
881
|
logger.info("unsetChannel triggerAutoUpdate: " + triggerAutoUpdate);
|
|
865
|
-
startNewThread(() ->
|
|
866
|
-
CapacitorUpdaterPlugin.this.
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
JSObject
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
882
|
+
startNewThread(() -> {
|
|
883
|
+
String configDefaultChannel = CapacitorUpdaterPlugin.this.getConfig().getString("defaultChannel", "");
|
|
884
|
+
CapacitorUpdaterPlugin.this.implementation.unsetChannel(
|
|
885
|
+
CapacitorUpdaterPlugin.this.editor,
|
|
886
|
+
DEFAULT_CHANNEL_PREF_KEY,
|
|
887
|
+
configDefaultChannel,
|
|
888
|
+
(res) -> {
|
|
889
|
+
JSObject jsRes = mapToJSObject(res);
|
|
890
|
+
if (jsRes.has("error")) {
|
|
891
|
+
String errorMessage = jsRes.has("message") ? jsRes.getString("message") : jsRes.getString("error");
|
|
892
|
+
String errorCode = jsRes.getString("error");
|
|
893
|
+
|
|
894
|
+
JSObject errorObj = new JSObject();
|
|
895
|
+
errorObj.put("message", errorMessage);
|
|
896
|
+
errorObj.put("error", errorCode);
|
|
897
|
+
|
|
898
|
+
call.reject(errorMessage, "UNSETCHANNEL_FAILED", null, errorObj);
|
|
899
|
+
} else {
|
|
900
|
+
if (CapacitorUpdaterPlugin.this._isAutoUpdateEnabled() && Boolean.TRUE.equals(triggerAutoUpdate)) {
|
|
901
|
+
logger.info("Calling autoupdater after channel change!");
|
|
902
|
+
backgroundDownload();
|
|
903
|
+
}
|
|
904
|
+
call.resolve(jsRes);
|
|
881
905
|
}
|
|
882
|
-
call.resolve(jsRes);
|
|
883
906
|
}
|
|
884
|
-
|
|
885
|
-
);
|
|
907
|
+
);
|
|
908
|
+
});
|
|
886
909
|
} catch (final Exception e) {
|
|
887
910
|
logger.error("Failed to unsetChannel: " + e.getMessage());
|
|
888
911
|
call.reject("Failed to unsetChannel: ", e);
|
|
@@ -905,25 +928,42 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
905
928
|
try {
|
|
906
929
|
logger.info("setChannel " + channel + " triggerAutoUpdate: " + triggerAutoUpdate);
|
|
907
930
|
startNewThread(() ->
|
|
908
|
-
CapacitorUpdaterPlugin.this.implementation.setChannel(
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
931
|
+
CapacitorUpdaterPlugin.this.implementation.setChannel(
|
|
932
|
+
channel,
|
|
933
|
+
CapacitorUpdaterPlugin.this.editor,
|
|
934
|
+
DEFAULT_CHANNEL_PREF_KEY,
|
|
935
|
+
CapacitorUpdaterPlugin.this.allowSetDefaultChannel,
|
|
936
|
+
(res) -> {
|
|
937
|
+
JSObject jsRes = mapToJSObject(res);
|
|
938
|
+
if (jsRes.has("error")) {
|
|
939
|
+
String errorMessage = jsRes.has("message") ? jsRes.getString("message") : jsRes.getString("error");
|
|
940
|
+
String errorCode = jsRes.getString("error");
|
|
941
|
+
|
|
942
|
+
// Fire channelPrivate event if channel doesn't allow self-assignment
|
|
943
|
+
if (
|
|
944
|
+
errorCode.contains("cannot_update_via_private_channel") ||
|
|
945
|
+
errorCode.contains("channel_self_set_not_allowed")
|
|
946
|
+
) {
|
|
947
|
+
JSObject eventData = new JSObject();
|
|
948
|
+
eventData.put("channel", channel);
|
|
949
|
+
eventData.put("message", errorMessage);
|
|
950
|
+
notifyListeners("channelPrivate", eventData);
|
|
951
|
+
}
|
|
913
952
|
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
953
|
+
JSObject errorObj = new JSObject();
|
|
954
|
+
errorObj.put("message", errorMessage);
|
|
955
|
+
errorObj.put("error", errorCode);
|
|
917
956
|
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
957
|
+
call.reject(errorMessage, "SETCHANNEL_FAILED", null, errorObj);
|
|
958
|
+
} else {
|
|
959
|
+
if (CapacitorUpdaterPlugin.this._isAutoUpdateEnabled() && Boolean.TRUE.equals(triggerAutoUpdate)) {
|
|
960
|
+
logger.info("Calling autoupdater after channel change!");
|
|
961
|
+
backgroundDownload();
|
|
962
|
+
}
|
|
963
|
+
call.resolve(jsRes);
|
|
923
964
|
}
|
|
924
|
-
call.resolve(jsRes);
|
|
925
965
|
}
|
|
926
|
-
|
|
966
|
+
)
|
|
927
967
|
);
|
|
928
968
|
} catch (final Exception e) {
|
|
929
969
|
logger.error("Failed to setChannel: " + channel + " " + e.getMessage());
|
|
@@ -403,6 +403,8 @@ public class CapgoUpdater {
|
|
|
403
403
|
} else {
|
|
404
404
|
checksum = CryptoCipher.calcChecksum(downloaded);
|
|
405
405
|
}
|
|
406
|
+
CryptoCipher.logChecksumInfo("Calculated checksum", checksum);
|
|
407
|
+
CryptoCipher.logChecksumInfo("Expected checksum", checksumDecrypted);
|
|
406
408
|
if ((!checksumDecrypted.isEmpty() || !this.publicKey.isEmpty()) && !checksumDecrypted.equals(checksum)) {
|
|
407
409
|
logger.error("Error checksum '" + checksumDecrypted + "' '" + checksum + "' '");
|
|
408
410
|
this.sendStats("checksum_fail");
|
|
@@ -491,6 +493,23 @@ public class CapgoUpdater {
|
|
|
491
493
|
}
|
|
492
494
|
}
|
|
493
495
|
|
|
496
|
+
public void cleanupDeltaCache() {
|
|
497
|
+
if (this.activity == null) {
|
|
498
|
+
logger.warn("Activity is null, skipping delta cache cleanup");
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
final File cacheFolder = new File(this.activity.getCacheDir(), "capgo_downloads");
|
|
502
|
+
if (!cacheFolder.exists()) {
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
try {
|
|
506
|
+
this.deleteDirectory(cacheFolder);
|
|
507
|
+
logger.info("Cleaned up delta cache folder");
|
|
508
|
+
} catch (IOException e) {
|
|
509
|
+
logger.error("Failed to cleanup delta cache: " + e.getMessage());
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
494
513
|
public void cleanupDownloadDirectories(final Set<String> allowedIds) {
|
|
495
514
|
if (this.documentsDir == null) {
|
|
496
515
|
logger.warn("Documents directory is null, skipping download cleanup");
|
|
@@ -949,115 +968,41 @@ public class CapgoUpdater {
|
|
|
949
968
|
makeJsonRequest(updateUrl, json, callback);
|
|
950
969
|
}
|
|
951
970
|
|
|
952
|
-
public void unsetChannel(
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
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
|
+
}
|
|
962
988
|
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
try {
|
|
974
|
-
json = this.createInfoObject();
|
|
975
|
-
} catch (JSONException e) {
|
|
976
|
-
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");
|
|
977
999
|
final Map<String, Object> retError = new HashMap<>();
|
|
978
|
-
retError.put("message", "
|
|
979
|
-
retError.put("error", "
|
|
1000
|
+
retError.put("message", "setChannel is disabled by configuration");
|
|
1001
|
+
retError.put("error", "disabled_by_config");
|
|
980
1002
|
callback.callback(retError);
|
|
981
1003
|
return;
|
|
982
1004
|
}
|
|
983
1005
|
|
|
984
|
-
Request request = new Request.Builder()
|
|
985
|
-
.url(channelUrl)
|
|
986
|
-
.delete(RequestBody.create(json.toString(), MediaType.get("application/json")))
|
|
987
|
-
.build();
|
|
988
|
-
|
|
989
|
-
DownloadService.sharedClient
|
|
990
|
-
.newCall(request)
|
|
991
|
-
.enqueue(
|
|
992
|
-
new okhttp3.Callback() {
|
|
993
|
-
@Override
|
|
994
|
-
public void onFailure(@NonNull Call call, @NonNull IOException e) {
|
|
995
|
-
Map<String, Object> retError = new HashMap<>();
|
|
996
|
-
retError.put("message", "Request failed: " + e.getMessage());
|
|
997
|
-
retError.put("error", "network_error");
|
|
998
|
-
callback.callback(retError);
|
|
999
|
-
}
|
|
1000
|
-
|
|
1001
|
-
@Override
|
|
1002
|
-
public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
|
|
1003
|
-
try (ResponseBody responseBody = response.body()) {
|
|
1004
|
-
// Check for 429 rate limit
|
|
1005
|
-
if (checkAndHandleRateLimitResponse(response)) {
|
|
1006
|
-
Map<String, Object> retError = new HashMap<>();
|
|
1007
|
-
retError.put("message", "Rate limit exceeded");
|
|
1008
|
-
retError.put("error", "rate_limit_exceeded");
|
|
1009
|
-
callback.callback(retError);
|
|
1010
|
-
return;
|
|
1011
|
-
}
|
|
1012
|
-
|
|
1013
|
-
if (!response.isSuccessful()) {
|
|
1014
|
-
Map<String, Object> retError = new HashMap<>();
|
|
1015
|
-
retError.put("message", "Server error: " + response.code());
|
|
1016
|
-
retError.put("error", "response_error");
|
|
1017
|
-
callback.callback(retError);
|
|
1018
|
-
return;
|
|
1019
|
-
}
|
|
1020
|
-
|
|
1021
|
-
assert responseBody != null;
|
|
1022
|
-
String responseData = responseBody.string();
|
|
1023
|
-
JSONObject jsonResponse = new JSONObject(responseData);
|
|
1024
|
-
|
|
1025
|
-
// Check for server-side errors first
|
|
1026
|
-
if (jsonResponse.has("error")) {
|
|
1027
|
-
Map<String, Object> retError = new HashMap<>();
|
|
1028
|
-
retError.put("error", jsonResponse.getString("error"));
|
|
1029
|
-
if (jsonResponse.has("message")) {
|
|
1030
|
-
retError.put("message", jsonResponse.getString("message"));
|
|
1031
|
-
} else {
|
|
1032
|
-
retError.put("message", "server did not provide a message");
|
|
1033
|
-
}
|
|
1034
|
-
callback.callback(retError);
|
|
1035
|
-
return;
|
|
1036
|
-
}
|
|
1037
|
-
|
|
1038
|
-
Map<String, Object> ret = new HashMap<>();
|
|
1039
|
-
|
|
1040
|
-
Iterator<String> keys = jsonResponse.keys();
|
|
1041
|
-
while (keys.hasNext()) {
|
|
1042
|
-
String key = keys.next();
|
|
1043
|
-
if (jsonResponse.has(key)) {
|
|
1044
|
-
ret.put(key, jsonResponse.get(key));
|
|
1045
|
-
}
|
|
1046
|
-
}
|
|
1047
|
-
logger.info("Channel unset");
|
|
1048
|
-
callback.callback(ret);
|
|
1049
|
-
} catch (JSONException e) {
|
|
1050
|
-
Map<String, Object> retError = new HashMap<>();
|
|
1051
|
-
retError.put("message", "JSON parse error: " + e.getMessage());
|
|
1052
|
-
retError.put("error", "parse_error");
|
|
1053
|
-
callback.callback(retError);
|
|
1054
|
-
}
|
|
1055
|
-
}
|
|
1056
|
-
}
|
|
1057
|
-
);
|
|
1058
|
-
}
|
|
1059
|
-
|
|
1060
|
-
public void setChannel(final String channel, final Callback callback) {
|
|
1061
1006
|
// Check if rate limit was exceeded
|
|
1062
1007
|
if (rateLimitExceeded) {
|
|
1063
1008
|
logger.debug("Skipping setChannel due to rate limit (429). Requests will resume after app restart.");
|
|
@@ -1090,7 +1035,18 @@ public class CapgoUpdater {
|
|
|
1090
1035
|
return;
|
|
1091
1036
|
}
|
|
1092
1037
|
|
|
1093
|
-
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
|
+
});
|
|
1094
1050
|
}
|
|
1095
1051
|
|
|
1096
1052
|
public void getChannel(final Callback callback) {
|