@capgo/capacitor-updater 8.46.3 → 8.47.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.
@@ -7,6 +7,7 @@
7
7
  package ee.forgr.capacitor_updater;
8
8
 
9
9
  import android.app.Activity;
10
+ import android.app.AlertDialog;
10
11
  import android.content.Context;
11
12
  import android.content.Intent;
12
13
  import android.content.SharedPreferences;
@@ -85,6 +86,12 @@ public class CapacitorUpdaterPlugin extends Plugin {
85
86
  private static final String STATS_URL_PREF_KEY = "CapacitorUpdater.statsUrl";
86
87
  private static final String CHANNEL_URL_PREF_KEY = "CapacitorUpdater.channelUrl";
87
88
  private static final String DEFAULT_CHANNEL_PREF_KEY = "CapacitorUpdater.defaultChannel";
89
+ private static final String PREVIEW_SESSION_PREF_KEY = "CapacitorUpdater.previewSession";
90
+ private static final String PREVIEW_PREVIOUS_SHAKE_MENU_PREF_KEY = "CapacitorUpdater.previewPreviousShakeMenu";
91
+ private static final String PREVIEW_PREVIOUS_SHAKE_CHANNEL_SELECTOR_PREF_KEY = "CapacitorUpdater.previewPreviousShakeChannelSelector";
92
+ private static final String PREVIEW_PREVIOUS_NEXT_BUNDLE_PREF_KEY = "CapacitorUpdater.previewPreviousNextBundle";
93
+ private static final String PREVIEW_PREVIOUS_APP_ID_PREF_KEY = "CapacitorUpdater.previewPreviousAppId";
94
+ private static final String PREVIEW_APP_ID_PREF_KEY = "CapacitorUpdater.previewAppId";
88
95
  private static final String[] BREAKING_EVENT_NAMES = { "breakingAvailable", "majorAvailable" };
89
96
  private static final String LAST_FAILED_BUNDLE_PREF_KEY = "CapacitorUpdater.lastFailedBundle";
90
97
  private static final String LAST_REPORTED_APP_EXIT_TIMESTAMP_PREF_KEY = "CapacitorUpdater.lastReportedAppExitTimestamp";
@@ -106,7 +113,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
106
113
  static final int APPLICATION_EXIT_REASON_USER_REQUESTED = 10;
107
114
  static final int APPLICATION_EXIT_REASON_DEPENDENCY_DIED = 12;
108
115
 
109
- private final String pluginVersion = "8.46.3";
116
+ private final String pluginVersion = "8.47.0";
110
117
  private static final String DELAY_CONDITION_PREFERENCES = "";
111
118
 
112
119
  private SharedPreferences.Editor editor;
@@ -136,7 +143,10 @@ public class CapacitorUpdaterPlugin extends Plugin {
136
143
  private volatile boolean onLaunchDirectUpdateUsed = false;
137
144
  Boolean shakeMenuEnabled = false;
138
145
  Boolean shakeChannelSelectorEnabled = false;
146
+ Boolean previewSessionEnabled = false;
147
+ private Boolean previewSessionAlertPending = false;
139
148
  private Boolean allowManualBundleError = false;
149
+ private Boolean allowPreview = false;
140
150
  Boolean allowSetDefaultChannel = true;
141
151
 
142
152
  String getUpdateUrl() {
@@ -421,6 +431,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
421
431
  "appId is missing in capacitor.config.json or plugin config, and cannot be retrieved from the native app, please add it globally or in the plugin config"
422
432
  );
423
433
  }
434
+ this.allowPreview = this.getConfig().getBoolean("allowPreview", false);
424
435
  logger.info("appId: " + implementation.appId);
425
436
 
426
437
  this.persistCustomId = this.getConfig().getBoolean("persistCustomId", false);
@@ -509,12 +520,30 @@ public class CapacitorUpdaterPlugin extends Plugin {
509
520
  this.implementation.timeout = this.getConfig().getInt("responseTimeout", 20) * 1000;
510
521
  this.shakeMenuEnabled = this.getConfig().getBoolean("shakeMenu", false);
511
522
  this.shakeChannelSelectorEnabled = this.getConfig().getBoolean("allowShakeChannelSelector", false);
523
+ this.previewSessionEnabled = Boolean.TRUE.equals(this.allowPreview) && this.prefs.getBoolean(PREVIEW_SESSION_PREF_KEY, false);
524
+ if (!Boolean.TRUE.equals(this.allowPreview) && this.prefs.getBoolean(PREVIEW_SESSION_PREF_KEY, false)) {
525
+ this.clearPreviewSessionBecauseDisabled();
526
+ }
527
+ this.implementation.previewSession = Boolean.TRUE.equals(this.previewSessionEnabled);
528
+ if (Boolean.TRUE.equals(this.previewSessionEnabled)) {
529
+ final String previewAppId = this.prefs.getString(PREVIEW_APP_ID_PREF_KEY, "");
530
+ if (previewAppId != null && !previewAppId.isEmpty()) {
531
+ this.setActiveAppId(previewAppId);
532
+ logger.info("Using preview appId " + previewAppId);
533
+ }
534
+ this.shakeMenuEnabled = true;
535
+ this.shakeChannelSelectorEnabled = false;
536
+ }
512
537
  boolean resetWhenUpdate = this.getConfig().getBoolean("resetWhenUpdate", true);
513
538
 
514
539
  // Check if app was recently installed/updated BEFORE cleanupObsoleteVersions updates LatestVersionNative
515
540
  this.wasRecentlyInstalledOrUpdated = this.checkIfRecentlyInstalledOrUpdated();
541
+ final boolean nativeBuildVersionChanged = this.hasNativeBuildVersionChanged();
516
542
 
517
543
  this.implementation.autoReset(this.currentBuildVersion, resetWhenUpdate);
544
+ if (nativeBuildVersionChanged) {
545
+ this.clearPreviewSessionForNativeBuildChange();
546
+ }
518
547
  this.reportPreviousAppExitReasons();
519
548
  this.reportPreviousWebViewRenderProcessGone();
520
549
  this.installWebViewStatsReporter();
@@ -860,6 +889,11 @@ public class CapacitorUpdaterPlugin extends Plugin {
860
889
  return false;
861
890
  }
862
891
 
892
+ private boolean hasNativeBuildVersionChanged() {
893
+ final String lastKnownVersion = this.getStoredNativeBuildVersion();
894
+ return !lastKnownVersion.isEmpty() && !lastKnownVersion.equals(this.currentBuildVersion);
895
+ }
896
+
863
897
  private void reportPreviousAppExitReasons() {
864
898
  if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R || this.implementation == null || this.implementation.statsUrl.isEmpty()) {
865
899
  return;
@@ -1057,6 +1091,19 @@ public class CapacitorUpdaterPlugin extends Plugin {
1057
1091
  return map;
1058
1092
  }
1059
1093
 
1094
+ private static JSObject jsonObjectToJSObject(final JSONObject json) throws JSONException {
1095
+ final JSObject ret = new JSObject();
1096
+ final JSONArray names = json.names();
1097
+ if (names == null) {
1098
+ return ret;
1099
+ }
1100
+ for (int i = 0; i < names.length(); i++) {
1101
+ final String key = names.getString(i);
1102
+ ret.put(key, json.get(key));
1103
+ }
1104
+ return ret;
1105
+ }
1106
+
1060
1107
  @PluginMethod
1061
1108
  public void reportWebViewError(final PluginCall call) {
1062
1109
  final JSObject data = call.getData();
@@ -2004,6 +2051,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
2004
2051
  }
2005
2052
  this.notifyBundleSet(next);
2006
2053
  this.implementation.setNextBundle(null);
2054
+ this.showPreviewSessionNoticeIfNeeded();
2007
2055
  call.resolve();
2008
2056
  return;
2009
2057
  }
@@ -2015,6 +2063,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
2015
2063
  }
2016
2064
 
2017
2065
  if (this._reload()) {
2066
+ this.showPreviewSessionNoticeIfNeeded();
2018
2067
  call.resolve();
2019
2068
  } else {
2020
2069
  logger.error("Reload failed");
@@ -2069,6 +2118,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
2069
2118
  } else {
2070
2119
  logger.info("Bundle successfully set to " + id);
2071
2120
  this.notifyBundleSet(this.implementation.getBundleInfo(id));
2121
+ this.showPreviewSessionNoticeIfNeeded();
2072
2122
  call.resolve();
2073
2123
  }
2074
2124
  } catch (final Exception e) {
@@ -2078,6 +2128,308 @@ public class CapacitorUpdaterPlugin extends Plugin {
2078
2128
  });
2079
2129
  }
2080
2130
 
2131
+ @PluginMethod
2132
+ public void startPreviewSession(final PluginCall call) {
2133
+ if (!Boolean.TRUE.equals(this.allowPreview)) {
2134
+ logger.error("startPreviewSession not allowed set allowPreview in your config to true to enable it");
2135
+ call.reject("startPreviewSession not allowed");
2136
+ return;
2137
+ }
2138
+ final String previewAppId = this.normalizePreviewAppId(call.getString("appId"));
2139
+ startNewThread(() -> {
2140
+ try {
2141
+ if (!Boolean.TRUE.equals(this.previewSessionEnabled)) {
2142
+ final BundleInfo current = this.implementation.getCurrentBundle();
2143
+ if (!this.implementation.setPreviewFallbackBundle(current.getId())) {
2144
+ logger.error("Could not save current bundle as preview fallback");
2145
+ call.reject("Could not save current bundle as preview fallback");
2146
+ return;
2147
+ }
2148
+
2149
+ final BundleInfo previousNext = this.implementation.getNextBundle();
2150
+ if (previousNext == null || previousNext.isDeleted() || previousNext.isErrorStatus()) {
2151
+ this.editor.remove(PREVIEW_PREVIOUS_NEXT_BUNDLE_PREF_KEY);
2152
+ } else {
2153
+ this.editor.putString(PREVIEW_PREVIOUS_NEXT_BUNDLE_PREF_KEY, previousNext.getId());
2154
+ }
2155
+
2156
+ this.editor.putString(PREVIEW_PREVIOUS_APP_ID_PREF_KEY, this.implementation.appId);
2157
+ this.editor.putBoolean(PREVIEW_PREVIOUS_SHAKE_MENU_PREF_KEY, Boolean.TRUE.equals(this.shakeMenuEnabled));
2158
+ this.editor.putBoolean(
2159
+ PREVIEW_PREVIOUS_SHAKE_CHANNEL_SELECTOR_PREF_KEY,
2160
+ Boolean.TRUE.equals(this.shakeChannelSelectorEnabled)
2161
+ );
2162
+ logger.info("Preview session started with fallback bundle: " + current);
2163
+ }
2164
+
2165
+ if (previewAppId != null) {
2166
+ this.setActiveAppId(previewAppId);
2167
+ this.editor.putString(PREVIEW_APP_ID_PREF_KEY, previewAppId);
2168
+ logger.info("Preview session using appId: " + previewAppId);
2169
+ }
2170
+
2171
+ this.previewSessionEnabled = true;
2172
+ this.previewSessionAlertPending = true;
2173
+ this.implementation.previewSession = true;
2174
+ this.shakeMenuEnabled = true;
2175
+ this.shakeChannelSelectorEnabled = false;
2176
+ this.editor.putBoolean(PREVIEW_SESSION_PREF_KEY, true);
2177
+ this.editor.apply();
2178
+ this.ensureShakeMenuStarted();
2179
+ call.resolve();
2180
+ } catch (final Exception e) {
2181
+ logger.error("Could not start preview session " + e.getMessage());
2182
+ call.reject("Could not start preview session", e);
2183
+ }
2184
+ });
2185
+ }
2186
+
2187
+ public boolean leavePreviewSessionFromShakeMenu() {
2188
+ final BundleInfo previewBundle = this.implementation.getCurrentBundle();
2189
+
2190
+ final boolean didReset = this.resetToPreviewFallbackBundle();
2191
+ if (!didReset) {
2192
+ return false;
2193
+ }
2194
+
2195
+ if (!this.clearPreviewChannelOverride()) {
2196
+ return false;
2197
+ }
2198
+ final BundleInfo previewFallbackBundle = this.implementation.getPreviewFallbackBundle();
2199
+ this.endPreviewSession();
2200
+ final BundleInfo restoredNextBundle = this.implementation.getNextBundle();
2201
+ if (
2202
+ !previewBundle.isBuiltin() &&
2203
+ (previewFallbackBundle == null || !previewBundle.getId().equals(previewFallbackBundle.getId())) &&
2204
+ (restoredNextBundle == null || !previewBundle.getId().equals(restoredNextBundle.getId()))
2205
+ ) {
2206
+ try {
2207
+ this.implementation.delete(previewBundle.getId(), false);
2208
+ } catch (final Exception err) {
2209
+ logger.warn("Cannot delete preview bundle " + previewBundle.getId() + ": " + err.getMessage());
2210
+ }
2211
+ }
2212
+ return true;
2213
+ }
2214
+
2215
+ public boolean reloadPreviewSessionFromShakeMenu() {
2216
+ return this._reload();
2217
+ }
2218
+
2219
+ public boolean hasActivePreviewSession() {
2220
+ return Boolean.TRUE.equals(this.previewSessionEnabled);
2221
+ }
2222
+
2223
+ private boolean resetToPreviewFallbackBundle() {
2224
+ final BundleInfo fallback = this.implementation.getPreviewFallbackBundle();
2225
+ if (fallback == null || fallback.isErrorStatus()) {
2226
+ logger.error("No preview fallback bundle available");
2227
+ return false;
2228
+ }
2229
+ if (!this.implementation.canSet(fallback)) {
2230
+ logger.error("Preview fallback bundle is not installable");
2231
+ return false;
2232
+ }
2233
+
2234
+ final CapgoUpdater.ResetState previousState = this.implementation.captureResetState();
2235
+ final String previousBundleName = this.implementation.getCurrentBundle().getVersionName();
2236
+ logger.info("Resetting to preview fallback bundle: " + fallback.getVersionName());
2237
+ if (this.implementation.stagePreviewFallbackReload(fallback) && this._reload()) {
2238
+ this.implementation.finalizeResetTransition(previousBundleName, false);
2239
+ this.notifyBundleSet(fallback);
2240
+ return true;
2241
+ }
2242
+ this.implementation.restoreResetState(previousState);
2243
+ this.restoreLiveBundleStateAfterFailedReload();
2244
+ return false;
2245
+ }
2246
+
2247
+ private void endPreviewSession() {
2248
+ final boolean previousShakeMenuEnabled = this.prefs.getBoolean(
2249
+ PREVIEW_PREVIOUS_SHAKE_MENU_PREF_KEY,
2250
+ this.getConfig().getBoolean("shakeMenu", false)
2251
+ );
2252
+ final boolean previousShakeChannelSelectorEnabled = this.prefs.getBoolean(
2253
+ PREVIEW_PREVIOUS_SHAKE_CHANNEL_SELECTOR_PREF_KEY,
2254
+ this.getConfig().getBoolean("allowShakeChannelSelector", false)
2255
+ );
2256
+ this.restorePreviewPreviousNextBundle();
2257
+ this.restorePreviewPreviousAppId();
2258
+
2259
+ this.previewSessionEnabled = false;
2260
+ this.previewSessionAlertPending = false;
2261
+ this.implementation.previewSession = false;
2262
+ this.shakeMenuEnabled = previousShakeMenuEnabled;
2263
+ this.shakeChannelSelectorEnabled = previousShakeChannelSelectorEnabled;
2264
+ this.implementation.setPreviewFallbackBundle(null);
2265
+ this.clearPreviewSessionPreferences();
2266
+ logger.info("Preview session ended");
2267
+ }
2268
+
2269
+ private void clearPreviewSessionBecauseDisabled() {
2270
+ logger.info("Preview session disabled by config; restoring preview fallback");
2271
+ final BundleInfo fallback = this.implementation.getPreviewFallbackBundle();
2272
+ final BundleInfo bundleToRestore = fallback == null || fallback.isErrorStatus()
2273
+ ? this.implementation.getBundleInfo(BundleInfo.ID_BUILTIN)
2274
+ : fallback;
2275
+
2276
+ if (this.implementation.canSet(bundleToRestore)) {
2277
+ this.implementation.stagePreviewFallbackReload(bundleToRestore);
2278
+ } else {
2279
+ logger.warn("Could not restore preview fallback while disabling preview");
2280
+ }
2281
+
2282
+ this.restorePreviewPreviousNextBundle();
2283
+ this.restorePreviewPreviousAppId();
2284
+ this.previewSessionEnabled = false;
2285
+ this.previewSessionAlertPending = false;
2286
+ this.implementation.previewSession = false;
2287
+ this.shakeMenuEnabled = this.getConfig().getBoolean("shakeMenu", false);
2288
+ this.shakeChannelSelectorEnabled = this.getConfig().getBoolean("allowShakeChannelSelector", false);
2289
+ this.clearPreviewSessionPreferences();
2290
+ }
2291
+
2292
+ private void clearPreviewSessionPreferences() {
2293
+ if (this.implementation != null) {
2294
+ this.implementation.setPreviewFallbackBundle(null);
2295
+ }
2296
+ this.editor.remove(PREVIEW_SESSION_PREF_KEY);
2297
+ this.editor.remove(PREVIEW_PREVIOUS_SHAKE_MENU_PREF_KEY);
2298
+ this.editor.remove(PREVIEW_PREVIOUS_SHAKE_CHANNEL_SELECTOR_PREF_KEY);
2299
+ this.editor.remove(PREVIEW_PREVIOUS_NEXT_BUNDLE_PREF_KEY);
2300
+ this.editor.remove(PREVIEW_PREVIOUS_APP_ID_PREF_KEY);
2301
+ this.editor.remove(PREVIEW_APP_ID_PREF_KEY);
2302
+ this.editor.apply();
2303
+ }
2304
+
2305
+ private void setActiveAppId(final String appId) {
2306
+ this.implementation.appId = appId;
2307
+ if (this.implementation.versionOs != null) {
2308
+ DownloadService.updateUserAgent(this.implementation.appId, this.pluginVersion, this.implementation.versionOs);
2309
+ }
2310
+ }
2311
+
2312
+ private void restorePreviewPreviousAppId() {
2313
+ final String previousAppId = this.prefs.getString(PREVIEW_PREVIOUS_APP_ID_PREF_KEY, "");
2314
+ if (previousAppId == null || previousAppId.isEmpty()) {
2315
+ return;
2316
+ }
2317
+ this.setActiveAppId(previousAppId);
2318
+ logger.info("Restored appId after preview: " + previousAppId);
2319
+ }
2320
+
2321
+ private String normalizePreviewAppId(final String rawAppId) {
2322
+ if (rawAppId == null) {
2323
+ return null;
2324
+ }
2325
+
2326
+ final String appId = rawAppId.trim();
2327
+ if (appId.isEmpty()) {
2328
+ return null;
2329
+ }
2330
+
2331
+ final String lowercasedAppId = appId.toLowerCase(java.util.Locale.ROOT);
2332
+ if ("undefined".equals(lowercasedAppId) || "null".equals(lowercasedAppId)) {
2333
+ return null;
2334
+ }
2335
+
2336
+ return appId;
2337
+ }
2338
+
2339
+ private void clearPreviewSessionForNativeBuildChange() {
2340
+ if (!Boolean.TRUE.equals(this.previewSessionEnabled) && this.implementation.getPreviewFallbackBundle() == null) {
2341
+ return;
2342
+ }
2343
+ logger.info("Native build changed; clearing preview session state");
2344
+ this.previewSessionEnabled = false;
2345
+ this.previewSessionAlertPending = false;
2346
+ this.implementation.previewSession = false;
2347
+ this.shakeMenuEnabled = this.getConfig().getBoolean("shakeMenu", false);
2348
+ this.shakeChannelSelectorEnabled = this.getConfig().getBoolean("allowShakeChannelSelector", false);
2349
+ this.restorePreviewPreviousAppId();
2350
+ this.implementation.setPreviewFallbackBundle(null);
2351
+ this.implementation.setNextBundle(null);
2352
+ this.clearPreviewChannelOverride();
2353
+ this.clearPreviewSessionPreferences();
2354
+ }
2355
+
2356
+ private boolean clearPreviewChannelOverride() {
2357
+ final String configDefaultChannel = this.getConfig().getString("defaultChannel", "");
2358
+ final AtomicReference<Map<String, Object>> unsetChannelResult = new AtomicReference<>();
2359
+ try {
2360
+ this.implementation.unsetChannel(this.editor, DEFAULT_CHANNEL_PREF_KEY, configDefaultChannel, unsetChannelResult::set);
2361
+ } catch (final Exception err) {
2362
+ logger.error("Could not clear preview channel override: " + err.getMessage());
2363
+ return false;
2364
+ }
2365
+
2366
+ final Map<String, Object> result = unsetChannelResult.get();
2367
+ if (result == null) {
2368
+ logger.error("Could not clear preview channel override: no result");
2369
+ return false;
2370
+ }
2371
+ if (result.containsKey("error")) {
2372
+ final Object message = result.getOrDefault("message", result.get("error"));
2373
+ logger.error("Could not clear preview channel override: " + message);
2374
+ return false;
2375
+ }
2376
+ return true;
2377
+ }
2378
+
2379
+ private void restorePreviewPreviousNextBundle() {
2380
+ final String previousNextBundleId = this.prefs.getString(PREVIEW_PREVIOUS_NEXT_BUNDLE_PREF_KEY, null);
2381
+ if (previousNextBundleId == null || previousNextBundleId.isEmpty()) {
2382
+ this.implementation.setNextBundle(null);
2383
+ return;
2384
+ }
2385
+ if (!this.implementation.setNextBundle(previousNextBundleId)) {
2386
+ logger.warn("Could not restore pre-preview next bundle: " + previousNextBundleId);
2387
+ this.implementation.setNextBundle(null);
2388
+ }
2389
+ }
2390
+
2391
+ private void ensureShakeMenuStarted() {
2392
+ if (getActivity() instanceof com.getcapacitor.BridgeActivity && shakeMenu == null) {
2393
+ try {
2394
+ shakeMenu = new ShakeMenu(this, (com.getcapacitor.BridgeActivity) getActivity(), logger);
2395
+ logger.info("Shake menu initialized");
2396
+ } catch (Exception e) {
2397
+ logger.error("Failed to initialize shake menu: " + e.getMessage());
2398
+ }
2399
+ }
2400
+ }
2401
+
2402
+ private void showPreviewSessionNoticeIfNeeded() {
2403
+ if (!Boolean.TRUE.equals(this.previewSessionEnabled) || !Boolean.TRUE.equals(this.previewSessionAlertPending)) {
2404
+ return;
2405
+ }
2406
+ this.previewSessionAlertPending = false;
2407
+
2408
+ new Handler(Looper.getMainLooper()).postDelayed(
2409
+ () -> {
2410
+ try {
2411
+ if (!Boolean.TRUE.equals(this.previewSessionEnabled)) {
2412
+ return;
2413
+ }
2414
+ if (getActivity() == null || getActivity().isFinishing()) {
2415
+ this.previewSessionAlertPending = true;
2416
+ return;
2417
+ }
2418
+
2419
+ new AlertDialog.Builder(getActivity())
2420
+ .setTitle("Preview started")
2421
+ .setMessage("Shake your device anytime to reload or leave the test app.")
2422
+ .setPositiveButton("Got it", (dialog, which) -> dialog.dismiss())
2423
+ .show();
2424
+ } catch (final Exception e) {
2425
+ this.previewSessionAlertPending = true;
2426
+ logger.warn("Could not show preview session notice: " + e.getMessage());
2427
+ }
2428
+ },
2429
+ 600
2430
+ );
2431
+ }
2432
+
2081
2433
  @PluginMethod
2082
2434
  public void delete(final PluginCall call) {
2083
2435
  final String id = call.getString("id");
@@ -2159,37 +2511,121 @@ public class CapacitorUpdaterPlugin extends Plugin {
2159
2511
  @PluginMethod
2160
2512
  public void getLatest(final PluginCall call) {
2161
2513
  final String channel = call.getString("channel");
2162
- startNewThread(() ->
2163
- CapacitorUpdaterPlugin.this.implementation.getLatest(CapacitorUpdaterPlugin.this.updateUrl, channel, (res) -> {
2164
- JSObject jsRes = InternalUtils.mapToJSObject(res);
2165
- if (jsRes.has("error") || jsRes.has("kind")) {
2166
- String error = jsRes.has("error") ? jsRes.getString("error") : "";
2167
- String errorMessage = jsRes.has("message") ? jsRes.getString("message") : "server did not provide a message";
2168
- String kind = CapacitorUpdaterPlugin.this.getUpdateResponseKind(jsRes.has("kind") ? jsRes.getString("kind") : null);
2169
- String latestVersion = jsRes.has("version") ? jsRes.getString("version") : "";
2170
- jsRes.put("kind", kind);
2171
- CapacitorUpdaterPlugin.this.notifyBreakingEventsIfNeeded(jsRes, latestVersion);
2172
- if ("failed".equals(kind)) {
2173
- logger.error("getLatest failed with error: " + error + ", message: " + errorMessage);
2174
- call.reject(error.isEmpty() ? errorMessage : error);
2175
- } else {
2176
- if (!jsRes.has("version") || jsRes.getString("version").isEmpty()) {
2177
- jsRes.put("version", CapacitorUpdaterPlugin.this.implementation.getCurrentBundle().getVersionName());
2178
- }
2179
- logger.info("getLatest returned " + kind + ": " + errorMessage);
2180
- call.resolve(jsRes);
2181
- }
2182
- return;
2183
- } else if (jsRes.has("message")) {
2184
- String latestVersion = jsRes.has("version") ? jsRes.getString("version") : "";
2185
- CapacitorUpdaterPlugin.this.notifyBreakingEventsIfNeeded(jsRes, latestVersion);
2186
- call.reject(jsRes.getString("message"));
2187
- return;
2514
+ final boolean includeBundleSize = call.getBoolean("includeBundleSize", false);
2515
+ final String previewAppId = this.normalizePreviewAppId(call.getString("appId"));
2516
+ final boolean hasPreviewAppId = previewAppId != null;
2517
+ if (hasPreviewAppId && !Boolean.TRUE.equals(this.allowPreview)) {
2518
+ logger.error("getLatest preview override not allowed set allowPreview in your config to true to enable it");
2519
+ call.reject("getLatest preview override not allowed");
2520
+ return;
2521
+ }
2522
+
2523
+ final Callback latestCallback = (res) -> {
2524
+ JSObject jsRes = InternalUtils.mapToJSObject(res);
2525
+ if (jsRes.has("error") || jsRes.has("kind")) {
2526
+ String error = jsRes.has("error") ? jsRes.getString("error") : "";
2527
+ String errorMessage = jsRes.has("message") ? jsRes.getString("message") : "server did not provide a message";
2528
+ String kind = CapacitorUpdaterPlugin.this.getUpdateResponseKind(jsRes.has("kind") ? jsRes.getString("kind") : null);
2529
+ String latestVersion = jsRes.has("version") ? jsRes.getString("version") : "";
2530
+ jsRes.put("kind", kind);
2531
+ CapacitorUpdaterPlugin.this.notifyBreakingEventsIfNeeded(jsRes, latestVersion);
2532
+ if ("failed".equals(kind)) {
2533
+ logger.error("getLatest failed with error: " + error + ", message: " + errorMessage);
2534
+ call.reject(error.isEmpty() ? errorMessage : error);
2188
2535
  } else {
2536
+ if (!jsRes.has("version") || jsRes.getString("version").isEmpty()) {
2537
+ jsRes.put("version", CapacitorUpdaterPlugin.this.implementation.getCurrentBundle().getVersionName());
2538
+ }
2539
+ logger.info("getLatest returned " + kind + ": " + errorMessage);
2189
2540
  call.resolve(jsRes);
2190
2541
  }
2191
- })
2192
- );
2542
+ return;
2543
+ } else if (jsRes.has("message")) {
2544
+ String latestVersion = jsRes.has("version") ? jsRes.getString("version") : "";
2545
+ CapacitorUpdaterPlugin.this.notifyBreakingEventsIfNeeded(jsRes, latestVersion);
2546
+ call.reject(jsRes.getString("message"));
2547
+ return;
2548
+ } else {
2549
+ if (includeBundleSize) {
2550
+ CapacitorUpdaterPlugin.this.attachBundleSize(jsRes);
2551
+ }
2552
+ call.resolve(jsRes);
2553
+ }
2554
+ };
2555
+
2556
+ startNewThread(() -> {
2557
+ if (hasPreviewAppId) {
2558
+ CapacitorUpdaterPlugin.this.implementation.getLatest(
2559
+ CapacitorUpdaterPlugin.this.updateUrl,
2560
+ channel,
2561
+ previewAppId,
2562
+ latestCallback
2563
+ );
2564
+ return;
2565
+ }
2566
+ CapacitorUpdaterPlugin.this.implementation.getLatest(CapacitorUpdaterPlugin.this.updateUrl, channel, latestCallback);
2567
+ });
2568
+ }
2569
+
2570
+ private void attachBundleSize(final JSObject latest) {
2571
+ try {
2572
+ final JSONArray manifest = latest.optJSONArray("manifest");
2573
+ if (manifest == null || manifest.length() == 0) {
2574
+ return;
2575
+ }
2576
+ final String sessionKey = latest.optString("sessionKey", "");
2577
+ final JSONObject missing = this.implementation.missingBundleFilesResult(manifest, sessionKey);
2578
+ final JSONArray missingManifest = missing.getJSONArray("missing");
2579
+ final JSONObject size = this.implementation.getBundleDownloadSize(
2580
+ this.updateUrl,
2581
+ latest.optString("version", ""),
2582
+ missingManifest
2583
+ );
2584
+ latest.put("missing", missing);
2585
+ latest.put("downloadSize", size);
2586
+ } catch (Exception e) {
2587
+ logger.error("Failed to attach bundle size to getLatest result");
2588
+ logger.debug("Error: " + e.getMessage());
2589
+ }
2590
+ }
2591
+
2592
+ @PluginMethod
2593
+ public void getMissingBundleFiles(final PluginCall call) {
2594
+ final JSONArray manifest = call.getData().optJSONArray("manifest");
2595
+ if (manifest == null) {
2596
+ call.reject("getMissingBundleFiles called without manifest");
2597
+ return;
2598
+ }
2599
+ String sessionKey = call.getString("sessionKey");
2600
+ if (sessionKey == null) {
2601
+ sessionKey = "";
2602
+ }
2603
+ final String finalSessionKey = sessionKey;
2604
+ startNewThread(() -> {
2605
+ try {
2606
+ call.resolve(jsonObjectToJSObject(this.implementation.missingBundleFilesResult(manifest, finalSessionKey)));
2607
+ } catch (Exception e) {
2608
+ call.reject("Could not get missing bundle files", e);
2609
+ }
2610
+ });
2611
+ }
2612
+
2613
+ @PluginMethod
2614
+ public void getBundleDownloadSize(final PluginCall call) {
2615
+ final JSONArray manifest = call.getData().optJSONArray("manifest");
2616
+ if (manifest == null) {
2617
+ call.reject("getBundleDownloadSize called without manifest");
2618
+ return;
2619
+ }
2620
+ final String version = call.getData().optString("version", "");
2621
+ startNewThread(() -> {
2622
+ try {
2623
+ final JSONObject size = this.implementation.getBundleDownloadSize(this.updateUrl, version, manifest);
2624
+ call.resolve(jsonObjectToJSObject(size));
2625
+ } catch (Exception e) {
2626
+ call.reject("Could not get bundle download size", e);
2627
+ }
2628
+ });
2193
2629
  }
2194
2630
 
2195
2631
  public String triggerBackgroundUpdateCheck() {
@@ -3330,7 +3766,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
3330
3766
  call.reject("setAppId called without appId");
3331
3767
  return;
3332
3768
  }
3333
- this.implementation.appId = appId;
3769
+ this.setActiveAppId(appId);
3334
3770
  call.resolve();
3335
3771
  }
3336
3772