@capgo/capacitor-updater 7.4.0 → 7.6.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 CHANGED
@@ -225,33 +225,35 @@ Capacitor Updater works by unzipping a compiled app bundle to the native device
225
225
 
226
226
  CapacitorUpdater can be configured with these options:
227
227
 
228
- | Prop | Type | Description | Default | Since |
229
- | ---------------------------- | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------- | ------- |
230
- | **`appReadyTimeout`** | <code>number</code> | Configure the number of milliseconds the native plugin should wait before considering an update 'failed'. Only available for Android and iOS. | <code>10000 // (10 seconds)</code> | |
231
- | **`responseTimeout`** | <code>number</code> | Configure the number of milliseconds the native plugin should wait before considering API timeout. Only available for Android and iOS. | <code>20 // (20 second)</code> | |
232
- | **`autoDeleteFailed`** | <code>boolean</code> | Configure whether the plugin should use automatically delete failed bundles. Only available for Android and iOS. | <code>true</code> | |
233
- | **`autoDeletePrevious`** | <code>boolean</code> | Configure whether the plugin should use automatically delete previous bundles after a successful update. Only available for Android and iOS. | <code>true</code> | |
234
- | **`autoUpdate`** | <code>boolean</code> | Configure whether the plugin should use Auto Update via an update server. Only available for Android and iOS. | <code>true</code> | |
235
- | **`resetWhenUpdate`** | <code>boolean</code> | Automatically delete previous downloaded bundles when a newer native app bundle is installed to the device. Only available for Android and iOS. | <code>true</code> | |
236
- | **`updateUrl`** | <code>string</code> | Configure the URL / endpoint to which update checks are sent. Only available for Android and iOS. | <code>https://plugin.capgo.app/updates</code> | |
237
- | **`channelUrl`** | <code>string</code> | Configure the URL / endpoint for channel operations. Only available for Android and iOS. | <code>https://plugin.capgo.app/channel_self</code> | |
238
- | **`statsUrl`** | <code>string</code> | Configure the URL / endpoint to which update statistics are sent. Only available for Android and iOS. Set to "" to disable stats reporting. | <code>https://plugin.capgo.app/stats</code> | |
239
- | **`publicKey`** | <code>string</code> | Configure the public key for end to end live update encryption Version 2 Only available for Android and iOS. | <code>undefined</code> | 6.2.0 |
240
- | **`version`** | <code>string</code> | Configure the current version of the app. This will be used for the first update request. If not set, the plugin will get the version from the native code. Only available for Android and iOS. | <code>undefined</code> | 4.17.48 |
241
- | **`directUpdate`** | <code>boolean</code> | Make the plugin direct install the update when the app what just updated/installed. Only for autoUpdate mode. Only available for Android and iOS. | <code>undefined</code> | 5.1.0 |
242
- | **`periodCheckDelay`** | <code>number</code> | Configure the delay period for period update check. the unit is in seconds. Only available for Android and iOS. Cannot be less than 600 seconds (10 minutes). | <code>600 // (10 minutes)</code> | |
243
- | **`localS3`** | <code>boolean</code> | Configure the CLI to use a local server for testing or self-hosted update server. | <code>undefined</code> | 4.17.48 |
244
- | **`localHost`** | <code>string</code> | Configure the CLI to use a local server for testing or self-hosted update server. | <code>undefined</code> | 4.17.48 |
245
- | **`localWebHost`** | <code>string</code> | Configure the CLI to use a local server for testing or self-hosted update server. | <code>undefined</code> | 4.17.48 |
246
- | **`localSupa`** | <code>string</code> | Configure the CLI to use a local server for testing or self-hosted update server. | <code>undefined</code> | 4.17.48 |
247
- | **`localSupaAnon`** | <code>string</code> | Configure the CLI to use a local server for testing. | <code>undefined</code> | 4.17.48 |
248
- | **`localApi`** | <code>string</code> | Configure the CLI to use a local api for testing. | <code>undefined</code> | 6.3.3 |
249
- | **`localApiFiles`** | <code>string</code> | Configure the CLI to use a local file api for testing. | <code>undefined</code> | 6.3.3 |
250
- | **`allowModifyUrl`** | <code>boolean</code> | Allow the plugin to modify the updateUrl, statsUrl and channelUrl dynamically from the JavaScript side. | <code>false</code> | 5.4.0 |
251
- | **`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. | <code>undefined</code> | 5.5.0 |
252
- | **`appId`** | <code>string</code> | Configure the app id for the app in the config. | <code>undefined</code> | 6.0.0 |
253
- | **`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 |
254
- | **`disableJSLogging`** | <code>boolean</code> | Disable the JavaScript logging of the plugin. if true, the plugin will not log to the JavaScript console. only the native log will be done | <code>false</code> | 7.3.0 |
228
+ | Prop | Type | Description | Default | Since |
229
+ | ---------------------------- | ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------- | ------- |
230
+ | **`appReadyTimeout`** | <code>number</code> | Configure the number of milliseconds the native plugin should wait before considering an update 'failed'. Only available for Android and iOS. | <code>10000 // (10 seconds)</code> | |
231
+ | **`responseTimeout`** | <code>number</code> | Configure the number of milliseconds the native plugin should wait before considering API timeout. Only available for Android and iOS. | <code>20 // (20 second)</code> | |
232
+ | **`autoDeleteFailed`** | <code>boolean</code> | Configure whether the plugin should use automatically delete failed bundles. Only available for Android and iOS. | <code>true</code> | |
233
+ | **`autoDeletePrevious`** | <code>boolean</code> | Configure whether the plugin should use automatically delete previous bundles after a successful update. Only available for Android and iOS. | <code>true</code> | |
234
+ | **`autoUpdate`** | <code>boolean</code> | Configure whether the plugin should use Auto Update via an update server. Only available for Android and iOS. | <code>true</code> | |
235
+ | **`resetWhenUpdate`** | <code>boolean</code> | Automatically delete previous downloaded bundles when a newer native app bundle is installed to the device. Only available for Android and iOS. | <code>true</code> | |
236
+ | **`updateUrl`** | <code>string</code> | Configure the URL / endpoint to which update checks are sent. Only available for Android and iOS. | <code>https://plugin.capgo.app/updates</code> | |
237
+ | **`channelUrl`** | <code>string</code> | Configure the URL / endpoint for channel operations. Only available for Android and iOS. | <code>https://plugin.capgo.app/channel_self</code> | |
238
+ | **`statsUrl`** | <code>string</code> | Configure the URL / endpoint to which update statistics are sent. Only available for Android and iOS. Set to "" to disable stats reporting. | <code>https://plugin.capgo.app/stats</code> | |
239
+ | **`publicKey`** | <code>string</code> | Configure the public key for end to end live update encryption Version 2 Only available for Android and iOS. | <code>undefined</code> | 6.2.0 |
240
+ | **`version`** | <code>string</code> | Configure the current version of the app. This will be used for the first update request. If not set, the plugin will get the version from the native code. Only available for Android and iOS. | <code>undefined</code> | 4.17.48 |
241
+ | **`directUpdate`** | <code>boolean \| 'always' \| 'atInstall'</code> | Configure when the plugin should direct install updates. Only for autoUpdate mode. - false: Never do direct updates (default behavior) - atInstall: Direct update only when app is installed/updated from store, otherwise use normal background update - always: Always do direct updates immediately when available - true: (deprecated) Same as "always" for backward compatibility Only available for Android and iOS. | <code>false</code> | 5.1.0 |
242
+ | **`autoSplashscreen`** | <code>boolean</code> | Automatically handle splashscreen hiding when using directUpdate. When enabled, the plugin will automatically hide the splashscreen after updates are applied or when no update is needed. This removes the need to manually listen for appReady events and call SplashScreen.hide(). Only works when directUpdate is set to "atInstall", "always", or true. Requires the @capacitor/splash-screen plugin to be installed and configured with launchAutoHide: false. Only available for Android and iOS. | <code>false</code> | 7.6.0 |
243
+ | **`periodCheckDelay`** | <code>number</code> | Configure the delay period for period update check. the unit is in seconds. Only available for Android and iOS. Cannot be less than 600 seconds (10 minutes). | <code>600 // (10 minutes)</code> | |
244
+ | **`localS3`** | <code>boolean</code> | Configure the CLI to use a local server for testing or self-hosted update server. | <code>undefined</code> | 4.17.48 |
245
+ | **`localHost`** | <code>string</code> | Configure the CLI to use a local server for testing or self-hosted update server. | <code>undefined</code> | 4.17.48 |
246
+ | **`localWebHost`** | <code>string</code> | Configure the CLI to use a local server for testing or self-hosted update server. | <code>undefined</code> | 4.17.48 |
247
+ | **`localSupa`** | <code>string</code> | Configure the CLI to use a local server for testing or self-hosted update server. | <code>undefined</code> | 4.17.48 |
248
+ | **`localSupaAnon`** | <code>string</code> | Configure the CLI to use a local server for testing. | <code>undefined</code> | 4.17.48 |
249
+ | **`localApi`** | <code>string</code> | Configure the CLI to use a local api for testing. | <code>undefined</code> | 6.3.3 |
250
+ | **`localApiFiles`** | <code>string</code> | Configure the CLI to use a local file api for testing. | <code>undefined</code> | 6.3.3 |
251
+ | **`allowModifyUrl`** | <code>boolean</code> | Allow the plugin to modify the updateUrl, statsUrl and channelUrl dynamically from the JavaScript side. | <code>false</code> | 5.4.0 |
252
+ | **`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. | <code>undefined</code> | 5.5.0 |
253
+ | **`appId`** | <code>string</code> | Configure the app id for the app in the config. | <code>undefined</code> | 6.0.0 |
254
+ | **`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 |
255
+ | **`disableJSLogging`** | <code>boolean</code> | Disable the JavaScript logging of the plugin. if true, the plugin will not log to the JavaScript console. only the native log will be done | <code>false</code> | 7.3.0 |
256
+ | **`shakeMenu`** | <code>boolean</code> | Enable shake gesture to show update menu for debugging/testing purposes | <code>false</code> | 7.5.0 |
255
257
 
256
258
  ### Examples
257
259
 
@@ -273,6 +275,7 @@ In `capacitor.config.json`:
273
275
  "publicKey": undefined,
274
276
  "version": undefined,
275
277
  "directUpdate": undefined,
278
+ "autoSplashscreen": undefined,
276
279
  "periodCheckDelay": undefined,
277
280
  "localS3": undefined,
278
281
  "localHost": undefined,
@@ -285,7 +288,8 @@ In `capacitor.config.json`:
285
288
  "defaultChannel": undefined,
286
289
  "appId": undefined,
287
290
  "keepUrlPathAfterReload": undefined,
288
- "disableJSLogging": undefined
291
+ "disableJSLogging": undefined,
292
+ "shakeMenu": undefined
289
293
  }
290
294
  }
291
295
  }
@@ -313,6 +317,7 @@ const config: CapacitorConfig = {
313
317
  publicKey: undefined,
314
318
  version: undefined,
315
319
  directUpdate: undefined,
320
+ autoSplashscreen: undefined,
316
321
  periodCheckDelay: undefined,
317
322
  localS3: undefined,
318
323
  localHost: undefined,
@@ -326,6 +331,7 @@ const config: CapacitorConfig = {
326
331
  appId: undefined,
327
332
  keepUrlPathAfterReload: undefined,
328
333
  disableJSLogging: undefined,
334
+ shakeMenu: undefined,
329
335
  },
330
336
  },
331
337
  };
@@ -357,6 +363,7 @@ export default config;
357
363
  * [`setChannel(...)`](#setchannel)
358
364
  * [`unsetChannel(...)`](#unsetchannel)
359
365
  * [`getChannel()`](#getchannel)
366
+ * [`listChannels()`](#listchannels)
360
367
  * [`setCustomId(...)`](#setcustomid)
361
368
  * [`getBuiltinVersion()`](#getbuiltinversion)
362
369
  * [`getDeviceId()`](#getdeviceid)
@@ -374,6 +381,8 @@ export default config;
374
381
  * [`addListener('appReady', ...)`](#addlistenerappready-)
375
382
  * [`isAutoUpdateAvailable()`](#isautoupdateavailable)
376
383
  * [`getNextBundle()`](#getnextbundle)
384
+ * [`setShakeMenu(...)`](#setshakemenu)
385
+ * [`isShakeMenuEnabled()`](#isshakemenuenabled)
377
386
  * [Interfaces](#interfaces)
378
387
  * [Type Aliases](#type-aliases)
379
388
 
@@ -678,6 +687,21 @@ Get the channel for this device
678
687
  --------------------
679
688
 
680
689
 
690
+ ### listChannels()
691
+
692
+ ```typescript
693
+ listChannels() => Promise<ListChannelsResult>
694
+ ```
695
+
696
+ List all channels available for this device that allow self-assignment
697
+
698
+ **Returns:** <code>Promise&lt;<a href="#listchannelsresult">ListChannelsResult</a>&gt;</code>
699
+
700
+ **Since:** 7.5.0
701
+
702
+ --------------------
703
+
704
+
681
705
  ### setCustomId(...)
682
706
 
683
707
  ```typescript
@@ -972,6 +996,38 @@ Returns null if no next bundle is set.
972
996
  --------------------
973
997
 
974
998
 
999
+ ### setShakeMenu(...)
1000
+
1001
+ ```typescript
1002
+ setShakeMenu(options: SetShakeMenuOptions) => Promise<void>
1003
+ ```
1004
+
1005
+ Enable or disable the shake menu for debugging/testing purposes
1006
+
1007
+ | Param | Type | Description |
1008
+ | ------------- | ------------------------------------------------------------------- | -------------------------------------------------------- |
1009
+ | **`options`** | <code><a href="#setshakemenuoptions">SetShakeMenuOptions</a></code> | Contains enabled boolean to enable or disable shake menu |
1010
+
1011
+ **Since:** 7.5.0
1012
+
1013
+ --------------------
1014
+
1015
+
1016
+ ### isShakeMenuEnabled()
1017
+
1018
+ ```typescript
1019
+ isShakeMenuEnabled() => Promise<ShakeMenuEnabled>
1020
+ ```
1021
+
1022
+ Get the current state of the shake menu
1023
+
1024
+ **Returns:** <code>Promise&lt;<a href="#shakemenuenabled">ShakeMenuEnabled</a>&gt;</code>
1025
+
1026
+ **Since:** 7.5.0
1027
+
1028
+ --------------------
1029
+
1030
+
975
1031
  ### Interfaces
976
1032
 
977
1033
 
@@ -1145,6 +1201,23 @@ If you don't use backend, you need to provide the URL and version of the bundle.
1145
1201
  | **`allowSet`** | <code>boolean</code> | | |
1146
1202
 
1147
1203
 
1204
+ #### ListChannelsResult
1205
+
1206
+ | Prop | Type | Description | Since |
1207
+ | -------------- | -------------------------- | -------------------------- | ----- |
1208
+ | **`channels`** | <code>ChannelInfo[]</code> | List of available channels | 7.5.0 |
1209
+
1210
+
1211
+ #### ChannelInfo
1212
+
1213
+ | Prop | Type | Description | Since |
1214
+ | -------------------- | -------------------- | ----------------------------------------------- | ----- |
1215
+ | **`id`** | <code>string</code> | The channel ID | 7.5.0 |
1216
+ | **`name`** | <code>string</code> | The channel name | 7.5.0 |
1217
+ | **`public`** | <code>boolean</code> | Whether this is a public channel | 7.5.0 |
1218
+ | **`allow_self_set`** | <code>boolean</code> | Whether devices can self-assign to this channel | 7.5.0 |
1219
+
1220
+
1148
1221
  #### SetCustomIdOptions
1149
1222
 
1150
1223
  | Prop | Type |
@@ -1252,6 +1325,20 @@ If you don't use backend, you need to provide the URL and version of the bundle.
1252
1325
  | **`available`** | <code>boolean</code> |
1253
1326
 
1254
1327
 
1328
+ #### SetShakeMenuOptions
1329
+
1330
+ | Prop | Type |
1331
+ | ------------- | -------------------- |
1332
+ | **`enabled`** | <code>boolean</code> |
1333
+
1334
+
1335
+ #### ShakeMenuEnabled
1336
+
1337
+ | Prop | Type |
1338
+ | ------------- | -------------------- |
1339
+ | **`enabled`** | <code>boolean</code> |
1340
+
1341
+
1255
1342
  ### Type Aliases
1256
1343
 
1257
1344
 
@@ -60,7 +60,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
60
60
  private static final String statsUrlDefault = "https://plugin.capgo.app/stats";
61
61
  private static final String channelUrlDefault = "https://plugin.capgo.app/channel_self";
62
62
 
63
- private final String PLUGIN_VERSION = "7.4.0";
63
+ private final String PLUGIN_VERSION = "7.6.0";
64
64
  private static final String DELAY_CONDITION_PREFERENCES = "";
65
65
 
66
66
  private SharedPreferences.Editor editor;
@@ -78,6 +78,10 @@ public class CapacitorUpdaterPlugin extends Plugin {
78
78
  private Thread backgroundTask;
79
79
  private Boolean taskRunning = false;
80
80
  private Boolean keepUrlPathAfterReload = false;
81
+ private Boolean autoSplashscreen = false;
82
+ private String directUpdateMode = "false";
83
+ private Boolean wasRecentlyInstalledOrUpdated = false;
84
+ Boolean shakeMenuEnabled = false;
81
85
 
82
86
  private Boolean isPreviousMainActivity = true;
83
87
 
@@ -91,6 +95,8 @@ public class CapacitorUpdaterPlugin extends Plugin {
91
95
 
92
96
  private DelayUpdateUtils delayUpdateUtils;
93
97
 
98
+ private ShakeMenu shakeMenu;
99
+
94
100
  private JSObject mapToJSObject(Map<String, Object> map) {
95
101
  JSObject jsObject = new JSObject();
96
102
  for (Map.Entry<String, Object> entry : map.entrySet()) {
@@ -154,7 +160,21 @@ public class CapacitorUpdaterPlugin extends Plugin {
154
160
  .readTimeout(this.implementation.timeout, TimeUnit.MILLISECONDS)
155
161
  .writeTimeout(this.implementation.timeout, TimeUnit.MILLISECONDS)
156
162
  .build();
157
- this.implementation.directUpdate = this.getConfig().getBoolean("directUpdate", false);
163
+ // Handle directUpdate configuration - support string values and backward compatibility
164
+ String directUpdateConfig = this.getConfig().getString("directUpdate", null);
165
+ if (directUpdateConfig != null) {
166
+ this.directUpdateMode = directUpdateConfig;
167
+ this.implementation.directUpdate = directUpdateConfig.equals("always") || directUpdateConfig.equals("atInstall");
168
+ } else {
169
+ Boolean directUpdateBool = this.getConfig().getBoolean("directUpdate", false);
170
+ if (directUpdateBool) {
171
+ this.directUpdateMode = "always"; // backward compatibility: true = always
172
+ this.implementation.directUpdate = true;
173
+ } else {
174
+ this.directUpdateMode = "false";
175
+ this.implementation.directUpdate = false;
176
+ }
177
+ }
158
178
  this.currentVersionNative = new Version(this.getConfig().getString("version", pInfo.versionName));
159
179
  this.delayUpdateUtils = new DelayUpdateUtils(
160
180
  this.prefs,
@@ -223,9 +243,14 @@ public class CapacitorUpdaterPlugin extends Plugin {
223
243
  this.autoUpdate = this.getConfig().getBoolean("autoUpdate", true);
224
244
  this.appReadyTimeout = this.getConfig().getInt("appReadyTimeout", 10000);
225
245
  this.keepUrlPathAfterReload = this.getConfig().getBoolean("keepUrlPathAfterReload", false);
246
+ this.autoSplashscreen = this.getConfig().getBoolean("autoSplashscreen", false);
226
247
  this.implementation.timeout = this.getConfig().getInt("responseTimeout", 20) * 1000;
248
+ this.shakeMenuEnabled = this.getConfig().getBoolean("shakeMenu", false);
227
249
  boolean resetWhenUpdate = this.getConfig().getBoolean("resetWhenUpdate", true);
228
250
 
251
+ // Check if app was recently installed/updated BEFORE cleanupObsoleteVersions updates LatestVersionNative
252
+ this.wasRecentlyInstalledOrUpdated = this.checkIfRecentlyInstalledOrUpdated();
253
+
229
254
  this.implementation.autoReset();
230
255
  if (resetWhenUpdate) {
231
256
  this.cleanupObsoleteVersions();
@@ -266,9 +291,66 @@ public class CapacitorUpdaterPlugin extends Plugin {
266
291
  semaphoreWait(CapacitorUpdaterPlugin.this.appReadyTimeout);
267
292
  logger.info("semaphoreReady sendReadyToJs done");
268
293
  CapacitorUpdaterPlugin.this.notifyListeners("appReady", ret);
294
+
295
+ // Auto hide splashscreen if enabled
296
+ if (CapacitorUpdaterPlugin.this.autoSplashscreen && CapacitorUpdaterPlugin.this.shouldUseDirectUpdate()) {
297
+ CapacitorUpdaterPlugin.this.hideSplashscreen();
298
+ }
269
299
  });
270
300
  }
271
301
 
302
+ private void hideSplashscreen() {
303
+ try {
304
+ // Create a simple PluginCall for the hide method
305
+ PluginCall call = new PluginCall(
306
+ null, // MessageHandler - can be null for this use case
307
+ "hideSplashscreen",
308
+ "hide",
309
+ "autoHideSplashscreen",
310
+ new JSObject()
311
+ );
312
+
313
+ // Use bridge's callPluginMethod to call SplashScreen.hide()
314
+ getBridge().callPluginMethod("SplashScreen", "hide", call);
315
+ logger.info("Splashscreen hidden automatically via callPluginMethod");
316
+ } catch (Exception e) {
317
+ logger.error("Error hiding splashscreen: " + e.getMessage());
318
+ }
319
+ }
320
+
321
+ private boolean checkIfRecentlyInstalledOrUpdated() {
322
+ String currentVersion = this.currentVersionNative.getOriginalString();
323
+ String lastKnownVersion = this.prefs.getString("LatestVersionNative", "");
324
+
325
+ if (lastKnownVersion.isEmpty()) {
326
+ // First time running, consider it as recently installed
327
+ return true;
328
+ } else if (!lastKnownVersion.equals(currentVersion)) {
329
+ // Version changed, consider it as recently updated
330
+ return true;
331
+ }
332
+
333
+ return false;
334
+ }
335
+
336
+ private boolean shouldUseDirectUpdate() {
337
+ switch (this.directUpdateMode) {
338
+ case "false":
339
+ return false;
340
+ case "always":
341
+ return true;
342
+ case "atInstall":
343
+ if (this.wasRecentlyInstalledOrUpdated) {
344
+ // Reset the flag after first use to prevent subsequent foreground events from using direct update
345
+ this.wasRecentlyInstalledOrUpdated = false;
346
+ return true;
347
+ }
348
+ return false;
349
+ default:
350
+ return false;
351
+ }
352
+ }
353
+
272
354
  private void directUpdateFinish(final BundleInfo latest) {
273
355
  CapacitorUpdaterPlugin.this.implementation.set(latest);
274
356
  CapacitorUpdaterPlugin.this._reload();
@@ -438,7 +520,14 @@ public class CapacitorUpdaterPlugin extends Plugin {
438
520
  CapacitorUpdaterPlugin.this.implementation.unsetChannel(res -> {
439
521
  JSObject jsRes = mapToJSObject(res);
440
522
  if (jsRes.has("error")) {
441
- call.reject(jsRes.getString("error"));
523
+ String errorMessage = jsRes.has("message") ? jsRes.getString("message") : jsRes.getString("error");
524
+ String errorCode = jsRes.getString("error");
525
+
526
+ JSObject errorObj = new JSObject();
527
+ errorObj.put("message", errorMessage);
528
+ errorObj.put("error", errorCode);
529
+
530
+ call.reject(errorMessage, "UNSETCHANNEL_FAILED", null, errorObj);
442
531
  } else {
443
532
  if (CapacitorUpdaterPlugin.this._isAutoUpdateEnabled() && Boolean.TRUE.equals(triggerAutoUpdate)) {
444
533
  logger.info("Calling autoupdater after channel change!");
@@ -461,7 +550,10 @@ public class CapacitorUpdaterPlugin extends Plugin {
461
550
 
462
551
  if (channel == null) {
463
552
  logger.error("setChannel called without channel");
464
- call.reject("setChannel called without channel");
553
+ JSObject errorObj = new JSObject();
554
+ errorObj.put("message", "setChannel called without channel");
555
+ errorObj.put("error", "missing_parameter");
556
+ call.reject("setChannel called without channel", "SETCHANNEL_INVALID_PARAMS", null, errorObj);
465
557
  return;
466
558
  }
467
559
  try {
@@ -470,7 +562,14 @@ public class CapacitorUpdaterPlugin extends Plugin {
470
562
  CapacitorUpdaterPlugin.this.implementation.setChannel(channel, res -> {
471
563
  JSObject jsRes = mapToJSObject(res);
472
564
  if (jsRes.has("error")) {
473
- call.reject(jsRes.getString("error"));
565
+ String errorMessage = jsRes.has("message") ? jsRes.getString("message") : jsRes.getString("error");
566
+ String errorCode = jsRes.getString("error");
567
+
568
+ JSObject errorObj = new JSObject();
569
+ errorObj.put("message", errorMessage);
570
+ errorObj.put("error", errorCode);
571
+
572
+ call.reject(errorMessage, "SETCHANNEL_FAILED", null, errorObj);
474
573
  } else {
475
574
  if (CapacitorUpdaterPlugin.this._isAutoUpdateEnabled() && Boolean.TRUE.equals(triggerAutoUpdate)) {
476
575
  logger.info("Calling autoupdater after channel change!");
@@ -494,7 +593,14 @@ public class CapacitorUpdaterPlugin extends Plugin {
494
593
  CapacitorUpdaterPlugin.this.implementation.getChannel(res -> {
495
594
  JSObject jsRes = mapToJSObject(res);
496
595
  if (jsRes.has("error")) {
497
- call.reject(jsRes.getString("error"));
596
+ String errorMessage = jsRes.has("message") ? jsRes.getString("message") : jsRes.getString("error");
597
+ String errorCode = jsRes.getString("error");
598
+
599
+ JSObject errorObj = new JSObject();
600
+ errorObj.put("message", errorMessage);
601
+ errorObj.put("error", errorCode);
602
+
603
+ call.reject(errorMessage, "GETCHANNEL_FAILED", null, errorObj);
498
604
  } else {
499
605
  call.resolve(jsRes);
500
606
  }
@@ -506,6 +612,33 @@ public class CapacitorUpdaterPlugin extends Plugin {
506
612
  }
507
613
  }
508
614
 
615
+ @PluginMethod
616
+ public void listChannels(final PluginCall call) {
617
+ try {
618
+ logger.info("listChannels");
619
+ startNewThread(() ->
620
+ CapacitorUpdaterPlugin.this.implementation.listChannels(res -> {
621
+ JSObject jsRes = mapToJSObject(res);
622
+ if (jsRes.has("error")) {
623
+ String errorMessage = jsRes.has("message") ? jsRes.getString("message") : jsRes.getString("error");
624
+ String errorCode = jsRes.getString("error");
625
+
626
+ JSObject errorObj = new JSObject();
627
+ errorObj.put("message", errorMessage);
628
+ errorObj.put("error", errorCode);
629
+
630
+ call.reject(errorMessage, "LISTCHANNELS_FAILED", null, errorObj);
631
+ } else {
632
+ call.resolve(jsRes);
633
+ }
634
+ })
635
+ );
636
+ } catch (final Exception e) {
637
+ logger.error("Failed to listChannels: " + e.getMessage());
638
+ call.reject("Failed to listChannels", e);
639
+ }
640
+ }
641
+
509
642
  @PluginMethod
510
643
  public void download(final PluginCall call) {
511
644
  final String url = call.getString("url");
@@ -994,9 +1127,8 @@ public class CapacitorUpdaterPlugin extends Plugin {
994
1127
  }
995
1128
 
996
1129
  private Thread backgroundDownload() {
997
- String messageUpdate = this.implementation.directUpdate
998
- ? "Update will occur now."
999
- : "Update will occur next time app moves to background.";
1130
+ boolean shouldDirectUpdate = this.shouldUseDirectUpdate();
1131
+ String messageUpdate = shouldDirectUpdate ? "Update will occur now." : "Update will occur next time app moves to background.";
1000
1132
  return startNewThread(() -> {
1001
1133
  logger.info("Check for update via: " + CapacitorUpdaterPlugin.this.updateUrl);
1002
1134
  CapacitorUpdaterPlugin.this.implementation.getLatest(CapacitorUpdaterPlugin.this.updateUrl, null, res -> {
@@ -1023,7 +1155,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
1023
1155
 
1024
1156
  if ("builtin".equals(latestVersionName)) {
1025
1157
  logger.info("Latest version is builtin");
1026
- if (CapacitorUpdaterPlugin.this.implementation.directUpdate) {
1158
+ if (shouldDirectUpdate) {
1027
1159
  logger.info("Direct update to builtin version");
1028
1160
  this._reset(false);
1029
1161
  CapacitorUpdaterPlugin.this.endBackGroundTaskWithNotif(
@@ -1075,7 +1207,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
1075
1207
  }
1076
1208
  if (latest.isDownloaded()) {
1077
1209
  logger.info("Latest bundle already exists and download is NOT required. " + messageUpdate);
1078
- if (CapacitorUpdaterPlugin.this.implementation.directUpdate) {
1210
+ if (shouldDirectUpdate) {
1079
1211
  Gson gson = new Gson();
1080
1212
  String delayUpdatePreferences = prefs.getString(DelayUpdateUtils.DELAY_CONDITION_PREFERENCES, "[]");
1081
1213
  Type type = new TypeToken<ArrayList<DelayCondition>>() {}.getType();
@@ -1328,6 +1460,16 @@ public class CapacitorUpdaterPlugin extends Plugin {
1328
1460
  }
1329
1461
  logger.info("onActivityStarted " + getActivity().getClass().getName());
1330
1462
  isPreviousMainActivity = true;
1463
+
1464
+ // Initialize shake menu if enabled and activity is BridgeActivity
1465
+ if (shakeMenuEnabled && getActivity() instanceof com.getcapacitor.BridgeActivity && shakeMenu == null) {
1466
+ try {
1467
+ shakeMenu = new ShakeMenu(this, (com.getcapacitor.BridgeActivity) getActivity(), logger);
1468
+ logger.info("Shake menu initialized");
1469
+ } catch (Exception e) {
1470
+ logger.error("Failed to initialize shake menu: " + e.getMessage());
1471
+ }
1472
+ }
1331
1473
  }
1332
1474
 
1333
1475
  @Override
@@ -1359,5 +1501,61 @@ public class CapacitorUpdaterPlugin extends Plugin {
1359
1501
  if (counterActivityCreate == 0) {
1360
1502
  this.appKilled();
1361
1503
  }
1504
+
1505
+ // Clean up shake menu
1506
+ if (shakeMenu != null) {
1507
+ try {
1508
+ shakeMenu.stop();
1509
+ shakeMenu = null;
1510
+ logger.info("Shake menu cleaned up");
1511
+ } catch (Exception e) {
1512
+ logger.error("Failed to clean up shake menu: " + e.getMessage());
1513
+ }
1514
+ }
1515
+ }
1516
+
1517
+ @PluginMethod
1518
+ public void setShakeMenu(final PluginCall call) {
1519
+ final Boolean enabled = call.getBoolean("enabled");
1520
+ if (enabled == null) {
1521
+ logger.error("setShakeMenu called without enabled parameter");
1522
+ call.reject("setShakeMenu called without enabled parameter");
1523
+ return;
1524
+ }
1525
+
1526
+ this.shakeMenuEnabled = enabled;
1527
+ logger.info("Shake menu " + (enabled ? "enabled" : "disabled"));
1528
+
1529
+ // Manage shake menu instance based on enabled state
1530
+ if (enabled && getActivity() instanceof com.getcapacitor.BridgeActivity && shakeMenu == null) {
1531
+ try {
1532
+ shakeMenu = new ShakeMenu(this, (com.getcapacitor.BridgeActivity) getActivity(), logger);
1533
+ logger.info("Shake menu initialized");
1534
+ } catch (Exception e) {
1535
+ logger.error("Failed to initialize shake menu: " + e.getMessage());
1536
+ }
1537
+ } else if (!enabled && shakeMenu != null) {
1538
+ try {
1539
+ shakeMenu.stop();
1540
+ shakeMenu = null;
1541
+ logger.info("Shake menu stopped");
1542
+ } catch (Exception e) {
1543
+ logger.error("Failed to stop shake menu: " + e.getMessage());
1544
+ }
1545
+ }
1546
+
1547
+ call.resolve();
1548
+ }
1549
+
1550
+ @PluginMethod
1551
+ public void isShakeMenuEnabled(final PluginCall call) {
1552
+ try {
1553
+ final JSObject ret = new JSObject();
1554
+ ret.put("enabled", this.shakeMenuEnabled);
1555
+ call.resolve(ret);
1556
+ } catch (final Exception e) {
1557
+ logger.error("Could not get shake menu status " + e.getMessage());
1558
+ call.reject("Could not get shake menu status", e);
1559
+ }
1362
1560
  }
1363
1561
  }