@capgo/capacitor-updater 8.47.3 → 8.47.5
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 +4 -3
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdaterPlugin.java +312 -42
- package/dist/docs.json +16 -0
- package/dist/esm/definitions.d.ts +9 -0
- package/dist/esm/definitions.js.map +1 -1
- package/ios/Sources/CapacitorUpdaterPlugin/CapacitorUpdaterPlugin.swift +372 -50
- package/ios/Sources/CapacitorUpdaterPlugin/ShakeMenu.swift +5 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2200,9 +2200,10 @@ If you don't use backend, you need to provide the URL and version of the bundle.
|
|
|
2200
2200
|
|
|
2201
2201
|
##### StartPreviewSessionOptions
|
|
2202
2202
|
|
|
2203
|
-
| Prop
|
|
2204
|
-
|
|
|
2205
|
-
| **`appId`**
|
|
2203
|
+
| Prop | Type | Description | Default | Since |
|
|
2204
|
+
| ---------------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- | ------ |
|
|
2205
|
+
| **`appId`** | <code>string</code> | App id to use while the preview session is active. The previous app id is restored when leaving the preview session. Requires {@link PluginsConfig.CapacitorUpdater.allowPreview} to be `true`. | <code>undefined</code> | 8.47.0 |
|
|
2206
|
+
| **`payloadUrl`** | <code>string</code> | HTTP(S) URL returning a preview download payload. When provided, the native shake reload action fetches this payload again before reloading so channel previews can move to the latest bundle. Requires {@link PluginsConfig.CapacitorUpdater.allowPreview} to be `true`. | <code>undefined</code> | 8.48.0 |
|
|
2206
2207
|
|
|
2207
2208
|
|
|
2208
2209
|
##### BundleListResult
|
|
@@ -49,9 +49,13 @@ import com.google.android.play.core.install.model.AppUpdateType;
|
|
|
49
49
|
import com.google.android.play.core.install.model.InstallStatus;
|
|
50
50
|
import com.google.android.play.core.install.model.UpdateAvailability;
|
|
51
51
|
import io.github.g00fy2.versioncompare.Version;
|
|
52
|
+
import java.io.ByteArrayOutputStream;
|
|
52
53
|
import java.io.IOException;
|
|
54
|
+
import java.io.InputStream;
|
|
55
|
+
import java.net.HttpURLConnection;
|
|
53
56
|
import java.net.MalformedURLException;
|
|
54
57
|
import java.net.URL;
|
|
58
|
+
import java.nio.charset.StandardCharsets;
|
|
55
59
|
import java.util.ArrayList;
|
|
56
60
|
import java.util.Date;
|
|
57
61
|
import java.util.HashMap;
|
|
@@ -98,7 +102,11 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
98
102
|
private static final String PREVIEW_PREVIOUS_SHAKE_CHANNEL_SELECTOR_PREF_KEY = "CapacitorUpdater.previewPreviousShakeChannelSelector";
|
|
99
103
|
private static final String PREVIEW_PREVIOUS_NEXT_BUNDLE_PREF_KEY = "CapacitorUpdater.previewPreviousNextBundle";
|
|
100
104
|
private static final String PREVIEW_PREVIOUS_APP_ID_PREF_KEY = "CapacitorUpdater.previewPreviousAppId";
|
|
105
|
+
private static final String PREVIEW_PREVIOUS_DEFAULT_CHANNEL_PREF_KEY = "CapacitorUpdater.previewPreviousDefaultChannel";
|
|
106
|
+
private static final String PREVIEW_PREVIOUS_DEFAULT_CHANNEL_WAS_SET_PREF_KEY = "CapacitorUpdater.previewPreviousDefaultChannelWasSet";
|
|
101
107
|
private static final String PREVIEW_APP_ID_PREF_KEY = "CapacitorUpdater.previewAppId";
|
|
108
|
+
private static final String PREVIEW_PAYLOAD_URL_PREF_KEY = "CapacitorUpdater.previewPayloadUrl";
|
|
109
|
+
private static final String PREVIEW_SESSION_ALERT_PENDING_PREF_KEY = "CapacitorUpdater.previewSessionAlertPending";
|
|
102
110
|
private static final String[] BREAKING_EVENT_NAMES = { "breakingAvailable", "majorAvailable" };
|
|
103
111
|
private static final String LAST_FAILED_BUNDLE_PREF_KEY = "CapacitorUpdater.lastFailedBundle";
|
|
104
112
|
private static final String LAST_REPORTED_APP_EXIT_TIMESTAMP_PREF_KEY = "CapacitorUpdater.lastReportedAppExitTimestamp";
|
|
@@ -120,7 +128,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
120
128
|
static final int APPLICATION_EXIT_REASON_USER_REQUESTED = 10;
|
|
121
129
|
static final int APPLICATION_EXIT_REASON_DEPENDENCY_DIED = 12;
|
|
122
130
|
|
|
123
|
-
private final String pluginVersion = "8.47.
|
|
131
|
+
private final String pluginVersion = "8.47.5";
|
|
124
132
|
private static final String DELAY_CONDITION_PREFERENCES = "";
|
|
125
133
|
|
|
126
134
|
private SharedPreferences.Editor editor;
|
|
@@ -153,6 +161,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
153
161
|
Boolean shakeChannelSelectorEnabled = false;
|
|
154
162
|
Boolean previewSessionEnabled = false;
|
|
155
163
|
private Boolean previewSessionAlertPending = false;
|
|
164
|
+
private Boolean isLeavingPreviewForIncomingLink = false;
|
|
156
165
|
private Boolean allowManualBundleError = false;
|
|
157
166
|
private Boolean allowPreview = false;
|
|
158
167
|
Boolean allowSetDefaultChannel = true;
|
|
@@ -495,6 +504,9 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
495
504
|
}
|
|
496
505
|
this.implementation.previewSession = Boolean.TRUE.equals(this.previewSessionEnabled);
|
|
497
506
|
if (Boolean.TRUE.equals(this.previewSessionEnabled)) {
|
|
507
|
+
this.previewSessionAlertPending = this.prefs.contains(PREVIEW_SESSION_ALERT_PENDING_PREF_KEY)
|
|
508
|
+
? this.prefs.getBoolean(PREVIEW_SESSION_ALERT_PENDING_PREF_KEY, false)
|
|
509
|
+
: true;
|
|
498
510
|
final String previewAppId = this.prefs.getString(PREVIEW_APP_ID_PREF_KEY, "");
|
|
499
511
|
if (previewAppId != null && !previewAppId.isEmpty()) {
|
|
500
512
|
this.setActiveAppId(previewAppId);
|
|
@@ -513,6 +525,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
513
525
|
if (nativeBuildVersionChanged) {
|
|
514
526
|
this.clearPreviewSessionForNativeBuildChange();
|
|
515
527
|
}
|
|
528
|
+
this.leavePreviewSessionForLaunchIntentIfNeeded();
|
|
516
529
|
this.reportPreviousAppExitReasons();
|
|
517
530
|
this.reportPreviousWebViewRenderProcessGone();
|
|
518
531
|
this.installWebViewStatsReporter();
|
|
@@ -527,6 +540,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
527
540
|
this.delayUpdateUtils.checkCancelDelay(DelayUpdateUtils.CancelDelaySource.KILLED);
|
|
528
541
|
|
|
529
542
|
this.checkForUpdateAfterDelay();
|
|
543
|
+
this.showPreviewSessionNoticeIfNeeded();
|
|
530
544
|
|
|
531
545
|
// On Android 14+ (API 34+), topActivity in RecentTaskInfo returns null due to
|
|
532
546
|
// security restrictions (StrandHogg task hijacking mitigations). Use ProcessLifecycleOwner
|
|
@@ -1930,6 +1944,20 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
1930
1944
|
}
|
|
1931
1945
|
}
|
|
1932
1946
|
|
|
1947
|
+
private BundleInfo downloadBundle(
|
|
1948
|
+
final String url,
|
|
1949
|
+
final String version,
|
|
1950
|
+
final String sessionKey,
|
|
1951
|
+
final String checksum,
|
|
1952
|
+
final JSONArray manifest
|
|
1953
|
+
) throws IOException {
|
|
1954
|
+
if (manifest != null) {
|
|
1955
|
+
return this.implementation.downloadManifest(url, version, sessionKey, checksum, manifest);
|
|
1956
|
+
}
|
|
1957
|
+
|
|
1958
|
+
return this.implementation.download(url, version, sessionKey, checksum);
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1933
1961
|
@PluginMethod
|
|
1934
1962
|
public void download(final PluginCall call) {
|
|
1935
1963
|
final String url = call.getString("url");
|
|
@@ -1951,20 +1979,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
1951
1979
|
logger.info("Downloading " + url);
|
|
1952
1980
|
startNewThread(() -> {
|
|
1953
1981
|
try {
|
|
1954
|
-
final BundleInfo downloaded;
|
|
1955
|
-
if (manifest != null) {
|
|
1956
|
-
// For manifest downloads, we need to handle this asynchronously
|
|
1957
|
-
// to avoid automatically scheduling/applying the downloaded bundle.
|
|
1958
|
-
// Manual download must not schedule/apply the bundle automatically.
|
|
1959
|
-
CapacitorUpdaterPlugin.this.implementation.downloadBackground(url, version, sessionKey, checksum, manifest, false);
|
|
1960
|
-
// Return immediately with a pending status - the actual result will come via listeners
|
|
1961
|
-
final String id = CapacitorUpdaterPlugin.this.implementation.randomString();
|
|
1962
|
-
downloaded = new BundleInfo(id, version, BundleStatus.DOWNLOADING, new Date(System.currentTimeMillis()), "");
|
|
1963
|
-
call.resolve(InternalUtils.mapToJSObject(downloaded.toJSONMap()));
|
|
1964
|
-
return;
|
|
1965
|
-
} else {
|
|
1966
|
-
downloaded = CapacitorUpdaterPlugin.this.implementation.download(url, version, sessionKey, checksum);
|
|
1967
|
-
}
|
|
1982
|
+
final BundleInfo downloaded = this.downloadBundle(url, version, sessionKey, checksum, manifest);
|
|
1968
1983
|
if (downloaded.isErrorStatus()) {
|
|
1969
1984
|
throw new RuntimeException("Download failed: " + downloaded.getStatus());
|
|
1970
1985
|
} else {
|
|
@@ -2231,6 +2246,13 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
2231
2246
|
return;
|
|
2232
2247
|
}
|
|
2233
2248
|
final String previewAppId = this.normalizePreviewAppId(call.getString("appId"));
|
|
2249
|
+
final String rawPayloadUrl = call.getString("payloadUrl");
|
|
2250
|
+
final String previewPayloadUrl = this.normalizePreviewPayloadUrl(rawPayloadUrl);
|
|
2251
|
+
if (this.hasPreviewPayloadUrl(rawPayloadUrl) && previewPayloadUrl == null) {
|
|
2252
|
+
logger.error("startPreviewSession called with invalid payloadUrl");
|
|
2253
|
+
call.reject("Invalid preview payloadUrl");
|
|
2254
|
+
return;
|
|
2255
|
+
}
|
|
2234
2256
|
startNewThread(() -> {
|
|
2235
2257
|
try {
|
|
2236
2258
|
if (!Boolean.TRUE.equals(this.previewSessionEnabled)) {
|
|
@@ -2249,6 +2271,16 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
2249
2271
|
}
|
|
2250
2272
|
|
|
2251
2273
|
this.editor.putString(PREVIEW_PREVIOUS_APP_ID_PREF_KEY, this.implementation.appId);
|
|
2274
|
+
if (this.prefs.contains(DEFAULT_CHANNEL_PREF_KEY)) {
|
|
2275
|
+
this.editor.putString(
|
|
2276
|
+
PREVIEW_PREVIOUS_DEFAULT_CHANNEL_PREF_KEY,
|
|
2277
|
+
this.prefs.getString(DEFAULT_CHANNEL_PREF_KEY, "")
|
|
2278
|
+
);
|
|
2279
|
+
this.editor.putBoolean(PREVIEW_PREVIOUS_DEFAULT_CHANNEL_WAS_SET_PREF_KEY, true);
|
|
2280
|
+
} else {
|
|
2281
|
+
this.editor.remove(PREVIEW_PREVIOUS_DEFAULT_CHANNEL_PREF_KEY);
|
|
2282
|
+
this.editor.putBoolean(PREVIEW_PREVIOUS_DEFAULT_CHANNEL_WAS_SET_PREF_KEY, false);
|
|
2283
|
+
}
|
|
2252
2284
|
this.editor.putBoolean(PREVIEW_PREVIOUS_SHAKE_MENU_PREF_KEY, Boolean.TRUE.equals(this.shakeMenuEnabled));
|
|
2253
2285
|
this.editor.putBoolean(
|
|
2254
2286
|
PREVIEW_PREVIOUS_SHAKE_CHANNEL_SELECTOR_PREF_KEY,
|
|
@@ -2263,12 +2295,20 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
2263
2295
|
logger.info("Preview session using appId: " + previewAppId);
|
|
2264
2296
|
}
|
|
2265
2297
|
|
|
2298
|
+
if (previewPayloadUrl != null) {
|
|
2299
|
+
this.editor.putString(PREVIEW_PAYLOAD_URL_PREF_KEY, previewPayloadUrl);
|
|
2300
|
+
logger.info("Preview session using payload URL");
|
|
2301
|
+
} else {
|
|
2302
|
+
this.editor.remove(PREVIEW_PAYLOAD_URL_PREF_KEY);
|
|
2303
|
+
}
|
|
2304
|
+
|
|
2266
2305
|
this.previewSessionEnabled = true;
|
|
2267
2306
|
this.previewSessionAlertPending = true;
|
|
2268
2307
|
this.implementation.previewSession = true;
|
|
2269
2308
|
this.shakeMenuEnabled = true;
|
|
2270
2309
|
this.shakeChannelSelectorEnabled = false;
|
|
2271
2310
|
this.editor.putBoolean(PREVIEW_SESSION_PREF_KEY, true);
|
|
2311
|
+
this.editor.putBoolean(PREVIEW_SESSION_ALERT_PENDING_PREF_KEY, true);
|
|
2272
2312
|
this.editor.apply();
|
|
2273
2313
|
this.ensureShakeMenuStarted();
|
|
2274
2314
|
call.resolve();
|
|
@@ -2287,12 +2327,61 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
2287
2327
|
return false;
|
|
2288
2328
|
}
|
|
2289
2329
|
|
|
2290
|
-
|
|
2291
|
-
|
|
2330
|
+
final BundleInfo previewFallbackBundle = this.implementation.getPreviewFallbackBundle();
|
|
2331
|
+
this.endPreviewSession();
|
|
2332
|
+
final BundleInfo restoredNextBundle = this.implementation.getNextBundle();
|
|
2333
|
+
this.deletePreviewBundleIfUnused(previewBundle, previewFallbackBundle, restoredNextBundle);
|
|
2334
|
+
return true;
|
|
2335
|
+
}
|
|
2336
|
+
|
|
2337
|
+
private void leavePreviewSessionForLaunchIntentIfNeeded() {
|
|
2338
|
+
final Intent intent = getActivity() == null ? null : getActivity().getIntent();
|
|
2339
|
+
if (
|
|
2340
|
+
intent == null ||
|
|
2341
|
+
!Intent.ACTION_VIEW.equals(intent.getAction()) ||
|
|
2342
|
+
intent.getData() == null ||
|
|
2343
|
+
!Boolean.TRUE.equals(this.previewSessionEnabled) ||
|
|
2344
|
+
!isPreviewDeepLink(intent.getData()) ||
|
|
2345
|
+
Boolean.TRUE.equals(this.isLeavingPreviewForIncomingLink)
|
|
2346
|
+
) {
|
|
2347
|
+
return;
|
|
2292
2348
|
}
|
|
2349
|
+
|
|
2350
|
+
this.isLeavingPreviewForIncomingLink = true;
|
|
2351
|
+
logger.info("Preview deeplink launch detected while preview session is active; restoring fallback before initial load");
|
|
2352
|
+
if (!this.leavePreviewSessionWithoutReload()) {
|
|
2353
|
+
logger.error("Could not leave preview session before initial preview deeplink routing");
|
|
2354
|
+
this.isLeavingPreviewForIncomingLink = false;
|
|
2355
|
+
}
|
|
2356
|
+
}
|
|
2357
|
+
|
|
2358
|
+
private boolean leavePreviewSessionWithoutReload() {
|
|
2359
|
+
final BundleInfo previewBundle = this.implementation.getCurrentBundle();
|
|
2293
2360
|
final BundleInfo previewFallbackBundle = this.implementation.getPreviewFallbackBundle();
|
|
2361
|
+
if (previewFallbackBundle == null || previewFallbackBundle.isErrorStatus()) {
|
|
2362
|
+
logger.error("No preview fallback bundle available");
|
|
2363
|
+
return false;
|
|
2364
|
+
}
|
|
2365
|
+
if (!this.implementation.canSet(previewFallbackBundle)) {
|
|
2366
|
+
logger.error("Preview fallback bundle is not installable");
|
|
2367
|
+
return false;
|
|
2368
|
+
}
|
|
2369
|
+
if (!this.implementation.stagePreviewFallbackReload(previewFallbackBundle)) {
|
|
2370
|
+
logger.error("Could not stage preview fallback bundle");
|
|
2371
|
+
return false;
|
|
2372
|
+
}
|
|
2373
|
+
|
|
2294
2374
|
this.endPreviewSession();
|
|
2295
2375
|
final BundleInfo restoredNextBundle = this.implementation.getNextBundle();
|
|
2376
|
+
this.deletePreviewBundleIfUnused(previewBundle, previewFallbackBundle, restoredNextBundle);
|
|
2377
|
+
return true;
|
|
2378
|
+
}
|
|
2379
|
+
|
|
2380
|
+
private void deletePreviewBundleIfUnused(
|
|
2381
|
+
final BundleInfo previewBundle,
|
|
2382
|
+
final BundleInfo previewFallbackBundle,
|
|
2383
|
+
final BundleInfo restoredNextBundle
|
|
2384
|
+
) {
|
|
2296
2385
|
if (
|
|
2297
2386
|
!previewBundle.isBuiltin() &&
|
|
2298
2387
|
(previewFallbackBundle == null || !previewBundle.getId().equals(previewFallbackBundle.getId())) &&
|
|
@@ -2304,10 +2393,14 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
2304
2393
|
logger.warn("Cannot delete preview bundle " + previewBundle.getId() + ": " + err.getMessage());
|
|
2305
2394
|
}
|
|
2306
2395
|
}
|
|
2307
|
-
return true;
|
|
2308
2396
|
}
|
|
2309
2397
|
|
|
2310
2398
|
public boolean reloadPreviewSessionFromShakeMenu() {
|
|
2399
|
+
final String payloadUrl = this.storedPreviewPayloadUrl();
|
|
2400
|
+
if (payloadUrl != null) {
|
|
2401
|
+
return this.refreshPreviewSessionFromPayloadUrl(payloadUrl);
|
|
2402
|
+
}
|
|
2403
|
+
|
|
2311
2404
|
return this._reload();
|
|
2312
2405
|
}
|
|
2313
2406
|
|
|
@@ -2350,9 +2443,11 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
2350
2443
|
);
|
|
2351
2444
|
this.restorePreviewPreviousNextBundle();
|
|
2352
2445
|
this.restorePreviewPreviousAppId();
|
|
2446
|
+
this.restorePreviewPreviousDefaultChannel();
|
|
2353
2447
|
|
|
2354
2448
|
this.previewSessionEnabled = false;
|
|
2355
2449
|
this.previewSessionAlertPending = false;
|
|
2450
|
+
this.isLeavingPreviewForIncomingLink = false;
|
|
2356
2451
|
this.implementation.previewSession = false;
|
|
2357
2452
|
this.shakeMenuEnabled = previousShakeMenuEnabled;
|
|
2358
2453
|
this.shakeChannelSelectorEnabled = previousShakeChannelSelectorEnabled;
|
|
@@ -2376,8 +2471,10 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
2376
2471
|
|
|
2377
2472
|
this.restorePreviewPreviousNextBundle();
|
|
2378
2473
|
this.restorePreviewPreviousAppId();
|
|
2474
|
+
this.restorePreviewPreviousDefaultChannel();
|
|
2379
2475
|
this.previewSessionEnabled = false;
|
|
2380
2476
|
this.previewSessionAlertPending = false;
|
|
2477
|
+
this.isLeavingPreviewForIncomingLink = false;
|
|
2381
2478
|
this.implementation.previewSession = false;
|
|
2382
2479
|
this.shakeMenuEnabled = this.getConfig().getBoolean("shakeMenu", false);
|
|
2383
2480
|
this.shakeChannelSelectorEnabled = this.getConfig().getBoolean("allowShakeChannelSelector", false);
|
|
@@ -2393,7 +2490,11 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
2393
2490
|
this.editor.remove(PREVIEW_PREVIOUS_SHAKE_CHANNEL_SELECTOR_PREF_KEY);
|
|
2394
2491
|
this.editor.remove(PREVIEW_PREVIOUS_NEXT_BUNDLE_PREF_KEY);
|
|
2395
2492
|
this.editor.remove(PREVIEW_PREVIOUS_APP_ID_PREF_KEY);
|
|
2493
|
+
this.editor.remove(PREVIEW_PREVIOUS_DEFAULT_CHANNEL_PREF_KEY);
|
|
2494
|
+
this.editor.remove(PREVIEW_PREVIOUS_DEFAULT_CHANNEL_WAS_SET_PREF_KEY);
|
|
2396
2495
|
this.editor.remove(PREVIEW_APP_ID_PREF_KEY);
|
|
2496
|
+
this.editor.remove(PREVIEW_PAYLOAD_URL_PREF_KEY);
|
|
2497
|
+
this.editor.remove(PREVIEW_SESSION_ALERT_PENDING_PREF_KEY);
|
|
2397
2498
|
this.editor.apply();
|
|
2398
2499
|
}
|
|
2399
2500
|
|
|
@@ -2413,6 +2514,23 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
2413
2514
|
logger.info("Restored appId after preview: " + previousAppId);
|
|
2414
2515
|
}
|
|
2415
2516
|
|
|
2517
|
+
private void restorePreviewPreviousDefaultChannel() {
|
|
2518
|
+
final String configDefaultChannel = this.getConfig().getString("defaultChannel", "");
|
|
2519
|
+
if (this.prefs.getBoolean(PREVIEW_PREVIOUS_DEFAULT_CHANNEL_WAS_SET_PREF_KEY, false)) {
|
|
2520
|
+
final String previousDefaultChannel = this.prefs.getString(PREVIEW_PREVIOUS_DEFAULT_CHANNEL_PREF_KEY, "");
|
|
2521
|
+
this.editor.putString(DEFAULT_CHANNEL_PREF_KEY, previousDefaultChannel);
|
|
2522
|
+
this.implementation.defaultChannel = previousDefaultChannel;
|
|
2523
|
+
this.editor.apply();
|
|
2524
|
+
logger.info("Restored defaultChannel after preview");
|
|
2525
|
+
return;
|
|
2526
|
+
}
|
|
2527
|
+
|
|
2528
|
+
this.editor.remove(DEFAULT_CHANNEL_PREF_KEY);
|
|
2529
|
+
this.implementation.defaultChannel = configDefaultChannel;
|
|
2530
|
+
this.editor.apply();
|
|
2531
|
+
logger.info("Restored defaultChannel after preview to config value");
|
|
2532
|
+
}
|
|
2533
|
+
|
|
2416
2534
|
private String normalizePreviewAppId(final String rawAppId) {
|
|
2417
2535
|
if (rawAppId == null) {
|
|
2418
2536
|
return null;
|
|
@@ -2431,6 +2549,146 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
2431
2549
|
return appId;
|
|
2432
2550
|
}
|
|
2433
2551
|
|
|
2552
|
+
private boolean hasPreviewPayloadUrl(final String rawPayloadUrl) {
|
|
2553
|
+
if (rawPayloadUrl == null) {
|
|
2554
|
+
return false;
|
|
2555
|
+
}
|
|
2556
|
+
|
|
2557
|
+
final String payloadUrl = rawPayloadUrl.trim();
|
|
2558
|
+
if (payloadUrl.isEmpty()) {
|
|
2559
|
+
return false;
|
|
2560
|
+
}
|
|
2561
|
+
|
|
2562
|
+
final String lowercasedPayloadUrl = payloadUrl.toLowerCase(java.util.Locale.ROOT);
|
|
2563
|
+
return !"undefined".equals(lowercasedPayloadUrl) && !"null".equals(lowercasedPayloadUrl);
|
|
2564
|
+
}
|
|
2565
|
+
|
|
2566
|
+
private String normalizePreviewPayloadUrl(final String rawPayloadUrl) {
|
|
2567
|
+
if (!this.hasPreviewPayloadUrl(rawPayloadUrl)) {
|
|
2568
|
+
return null;
|
|
2569
|
+
}
|
|
2570
|
+
|
|
2571
|
+
final String payloadUrl = rawPayloadUrl.trim();
|
|
2572
|
+
try {
|
|
2573
|
+
final URL parsedUrl = new URL(payloadUrl);
|
|
2574
|
+
final String protocol = parsedUrl.getProtocol();
|
|
2575
|
+
if (!"https".equals(protocol) && !"http".equals(protocol)) {
|
|
2576
|
+
return null;
|
|
2577
|
+
}
|
|
2578
|
+
return parsedUrl.toString();
|
|
2579
|
+
} catch (final MalformedURLException ignored) {
|
|
2580
|
+
return null;
|
|
2581
|
+
}
|
|
2582
|
+
}
|
|
2583
|
+
|
|
2584
|
+
private String storedPreviewPayloadUrl() {
|
|
2585
|
+
return this.normalizePreviewPayloadUrl(this.prefs.getString(PREVIEW_PAYLOAD_URL_PREF_KEY, null));
|
|
2586
|
+
}
|
|
2587
|
+
|
|
2588
|
+
private String previewPathFromUri(final Uri uri) {
|
|
2589
|
+
if ("capgo".equals(uri.getScheme())) {
|
|
2590
|
+
final String host = uri.getHost();
|
|
2591
|
+
final String path = uri.getPath();
|
|
2592
|
+
return ("/" + (host == null ? "" : host) + (path == null ? "" : path)).replaceAll("/+", "/");
|
|
2593
|
+
}
|
|
2594
|
+
|
|
2595
|
+
return uri.getPath();
|
|
2596
|
+
}
|
|
2597
|
+
|
|
2598
|
+
private boolean isPreviewDeepLink(final Uri uri) {
|
|
2599
|
+
final String path = this.previewPathFromUri(uri);
|
|
2600
|
+
return "/preview/channel".equals(path) || "/preview/bundle".equals(path);
|
|
2601
|
+
}
|
|
2602
|
+
|
|
2603
|
+
private String readResponseBody(final InputStream stream) throws IOException {
|
|
2604
|
+
if (stream == null) {
|
|
2605
|
+
return "";
|
|
2606
|
+
}
|
|
2607
|
+
|
|
2608
|
+
try (InputStream input = stream; ByteArrayOutputStream output = new ByteArrayOutputStream()) {
|
|
2609
|
+
final byte[] buffer = new byte[8192];
|
|
2610
|
+
int read;
|
|
2611
|
+
while ((read = input.read(buffer)) != -1) {
|
|
2612
|
+
output.write(buffer, 0, read);
|
|
2613
|
+
}
|
|
2614
|
+
return output.toString(StandardCharsets.UTF_8.name());
|
|
2615
|
+
}
|
|
2616
|
+
}
|
|
2617
|
+
|
|
2618
|
+
private JSONObject fetchPreviewPayload(final String payloadUrl) throws IOException, JSONException {
|
|
2619
|
+
final HttpURLConnection connection = (HttpURLConnection) new URL(payloadUrl).openConnection();
|
|
2620
|
+
connection.setRequestMethod("GET");
|
|
2621
|
+
connection.setRequestProperty("Accept", "application/json");
|
|
2622
|
+
connection.setConnectTimeout(30000);
|
|
2623
|
+
connection.setReadTimeout(60000);
|
|
2624
|
+
|
|
2625
|
+
try {
|
|
2626
|
+
final int statusCode = connection.getResponseCode();
|
|
2627
|
+
final String body = this.readResponseBody(
|
|
2628
|
+
statusCode >= 200 && statusCode < 300 ? connection.getInputStream() : connection.getErrorStream()
|
|
2629
|
+
);
|
|
2630
|
+
final JSONObject payload = new JSONObject(body);
|
|
2631
|
+
if (statusCode < 200 || statusCode >= 300) {
|
|
2632
|
+
throw new IOException(
|
|
2633
|
+
payload.optString("message", payload.optString("error", "Preview payload request failed with HTTP " + statusCode))
|
|
2634
|
+
);
|
|
2635
|
+
}
|
|
2636
|
+
return payload;
|
|
2637
|
+
} finally {
|
|
2638
|
+
connection.disconnect();
|
|
2639
|
+
}
|
|
2640
|
+
}
|
|
2641
|
+
|
|
2642
|
+
private BundleInfo downloadPreviewPayloadBundle(final JSONObject payload) throws IOException, JSONException {
|
|
2643
|
+
final String version = payload.optString("version", "").trim();
|
|
2644
|
+
if (version.isEmpty()) {
|
|
2645
|
+
throw new IOException("Preview payload is missing a version");
|
|
2646
|
+
}
|
|
2647
|
+
|
|
2648
|
+
final JSONArray manifest = payload.optJSONArray("manifest");
|
|
2649
|
+
final String url = payload.optString("url", "");
|
|
2650
|
+
if ((url == null || url.isEmpty()) && (manifest == null || manifest.length() == 0)) {
|
|
2651
|
+
throw new IOException("Preview payload is missing download information");
|
|
2652
|
+
}
|
|
2653
|
+
|
|
2654
|
+
return this.downloadBundle(
|
|
2655
|
+
url == null || url.isEmpty() ? "https://404.capgo.app/no.zip" : url,
|
|
2656
|
+
version,
|
|
2657
|
+
payload.optString("sessionKey", ""),
|
|
2658
|
+
payload.optString("checksum", ""),
|
|
2659
|
+
manifest
|
|
2660
|
+
);
|
|
2661
|
+
}
|
|
2662
|
+
|
|
2663
|
+
private boolean refreshPreviewSessionFromPayloadUrl(final String payloadUrl) {
|
|
2664
|
+
try {
|
|
2665
|
+
final JSONObject payload = this.fetchPreviewPayload(payloadUrl);
|
|
2666
|
+
final String version = payload.optString("version", "").trim();
|
|
2667
|
+
if (version.isEmpty()) {
|
|
2668
|
+
throw new IOException("Preview payload is missing a version");
|
|
2669
|
+
}
|
|
2670
|
+
|
|
2671
|
+
if (version.equals(this.implementation.getCurrentBundle().getVersionName())) {
|
|
2672
|
+
logger.info("Preview payload unchanged, reloading current bundle");
|
|
2673
|
+
return this._reload();
|
|
2674
|
+
}
|
|
2675
|
+
|
|
2676
|
+
final BundleInfo next = this.downloadPreviewPayloadBundle(payload);
|
|
2677
|
+
if (next.isErrorStatus()) {
|
|
2678
|
+
throw new IOException("Download failed: " + next.getStatus());
|
|
2679
|
+
}
|
|
2680
|
+
if (!this.implementation.set(next.getId())) {
|
|
2681
|
+
throw new IOException("Downloaded preview bundle cannot be applied");
|
|
2682
|
+
}
|
|
2683
|
+
|
|
2684
|
+
this.notifyBundleSet(next);
|
|
2685
|
+
return this._reload();
|
|
2686
|
+
} catch (final Exception err) {
|
|
2687
|
+
logger.error("Could not refresh preview session: " + err.getMessage());
|
|
2688
|
+
return false;
|
|
2689
|
+
}
|
|
2690
|
+
}
|
|
2691
|
+
|
|
2434
2692
|
private void clearPreviewSessionForNativeBuildChange() {
|
|
2435
2693
|
if (!Boolean.TRUE.equals(this.previewSessionEnabled) && this.implementation.getPreviewFallbackBundle() == null) {
|
|
2436
2694
|
return;
|
|
@@ -2438,39 +2696,17 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
2438
2696
|
logger.info("Native build changed; clearing preview session state");
|
|
2439
2697
|
this.previewSessionEnabled = false;
|
|
2440
2698
|
this.previewSessionAlertPending = false;
|
|
2699
|
+
this.isLeavingPreviewForIncomingLink = false;
|
|
2441
2700
|
this.implementation.previewSession = false;
|
|
2442
2701
|
this.shakeMenuEnabled = this.getConfig().getBoolean("shakeMenu", false);
|
|
2443
2702
|
this.shakeChannelSelectorEnabled = this.getConfig().getBoolean("allowShakeChannelSelector", false);
|
|
2444
2703
|
this.restorePreviewPreviousAppId();
|
|
2704
|
+
this.restorePreviewPreviousDefaultChannel();
|
|
2445
2705
|
this.implementation.setPreviewFallbackBundle(null);
|
|
2446
2706
|
this.implementation.setNextBundle(null);
|
|
2447
|
-
this.clearPreviewChannelOverride();
|
|
2448
2707
|
this.clearPreviewSessionPreferences();
|
|
2449
2708
|
}
|
|
2450
2709
|
|
|
2451
|
-
private boolean clearPreviewChannelOverride() {
|
|
2452
|
-
final String configDefaultChannel = this.getConfig().getString("defaultChannel", "");
|
|
2453
|
-
final AtomicReference<Map<String, Object>> unsetChannelResult = new AtomicReference<>();
|
|
2454
|
-
try {
|
|
2455
|
-
this.implementation.unsetChannel(this.editor, DEFAULT_CHANNEL_PREF_KEY, configDefaultChannel, unsetChannelResult::set);
|
|
2456
|
-
} catch (final Exception err) {
|
|
2457
|
-
logger.error("Could not clear preview channel override: " + err.getMessage());
|
|
2458
|
-
return false;
|
|
2459
|
-
}
|
|
2460
|
-
|
|
2461
|
-
final Map<String, Object> result = unsetChannelResult.get();
|
|
2462
|
-
if (result == null) {
|
|
2463
|
-
logger.error("Could not clear preview channel override: no result");
|
|
2464
|
-
return false;
|
|
2465
|
-
}
|
|
2466
|
-
if (result.containsKey("error")) {
|
|
2467
|
-
final Object message = result.getOrDefault("message", result.get("error"));
|
|
2468
|
-
logger.error("Could not clear preview channel override: " + message);
|
|
2469
|
-
return false;
|
|
2470
|
-
}
|
|
2471
|
-
return true;
|
|
2472
|
-
}
|
|
2473
|
-
|
|
2474
2710
|
private void restorePreviewPreviousNextBundle() {
|
|
2475
2711
|
final String previousNextBundleId = this.prefs.getString(PREVIEW_PREVIOUS_NEXT_BUNDLE_PREF_KEY, null);
|
|
2476
2712
|
if (previousNextBundleId == null || previousNextBundleId.isEmpty()) {
|
|
@@ -2499,6 +2735,8 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
2499
2735
|
return;
|
|
2500
2736
|
}
|
|
2501
2737
|
this.previewSessionAlertPending = false;
|
|
2738
|
+
this.editor.putBoolean(PREVIEW_SESSION_ALERT_PENDING_PREF_KEY, false);
|
|
2739
|
+
this.editor.apply();
|
|
2502
2740
|
|
|
2503
2741
|
new Handler(Looper.getMainLooper()).postDelayed(
|
|
2504
2742
|
() -> {
|
|
@@ -2508,6 +2746,8 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
2508
2746
|
}
|
|
2509
2747
|
if (getActivity() == null || getActivity().isFinishing()) {
|
|
2510
2748
|
this.previewSessionAlertPending = true;
|
|
2749
|
+
this.editor.putBoolean(PREVIEW_SESSION_ALERT_PENDING_PREF_KEY, true);
|
|
2750
|
+
this.editor.apply();
|
|
2511
2751
|
return;
|
|
2512
2752
|
}
|
|
2513
2753
|
|
|
@@ -2518,6 +2758,8 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
2518
2758
|
.show();
|
|
2519
2759
|
} catch (final Exception e) {
|
|
2520
2760
|
this.previewSessionAlertPending = true;
|
|
2761
|
+
this.editor.putBoolean(PREVIEW_SESSION_ALERT_PENDING_PREF_KEY, true);
|
|
2762
|
+
this.editor.apply();
|
|
2521
2763
|
logger.warn("Could not show preview session notice: " + e.getMessage());
|
|
2522
2764
|
}
|
|
2523
2765
|
},
|
|
@@ -3762,6 +4004,34 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
3762
4004
|
}
|
|
3763
4005
|
}
|
|
3764
4006
|
|
|
4007
|
+
@Override
|
|
4008
|
+
protected void handleOnNewIntent(Intent intent) {
|
|
4009
|
+
super.handleOnNewIntent(intent);
|
|
4010
|
+
if (
|
|
4011
|
+
intent == null ||
|
|
4012
|
+
!Intent.ACTION_VIEW.equals(intent.getAction()) ||
|
|
4013
|
+
intent.getData() == null ||
|
|
4014
|
+
!Boolean.TRUE.equals(this.previewSessionEnabled) ||
|
|
4015
|
+
!isPreviewDeepLink(intent.getData()) ||
|
|
4016
|
+
Boolean.TRUE.equals(this.isLeavingPreviewForIncomingLink)
|
|
4017
|
+
) {
|
|
4018
|
+
return;
|
|
4019
|
+
}
|
|
4020
|
+
|
|
4021
|
+
this.isLeavingPreviewForIncomingLink = true;
|
|
4022
|
+
if (getActivity() != null) {
|
|
4023
|
+
getActivity().setIntent(intent);
|
|
4024
|
+
}
|
|
4025
|
+
logger.info("Preview deeplink received while preview session is active; restoring fallback before routing");
|
|
4026
|
+
startNewThread(() -> {
|
|
4027
|
+
final boolean didLeave = this.leavePreviewSessionFromShakeMenu();
|
|
4028
|
+
if (!didLeave) {
|
|
4029
|
+
logger.error("Could not leave preview session before routing incoming preview deeplink");
|
|
4030
|
+
this.isLeavingPreviewForIncomingLink = false;
|
|
4031
|
+
}
|
|
4032
|
+
});
|
|
4033
|
+
}
|
|
4034
|
+
|
|
3765
4035
|
@Override
|
|
3766
4036
|
public void handleOnStart() {
|
|
3767
4037
|
try {
|
package/dist/docs.json
CHANGED
|
@@ -1991,6 +1991,22 @@
|
|
|
1991
1991
|
"docs": "App id to use while the preview session is active.\nThe previous app id is restored when leaving the preview session.\nRequires {@link PluginsConfig.CapacitorUpdater.allowPreview} to be `true`.",
|
|
1992
1992
|
"complexTypes": [],
|
|
1993
1993
|
"type": "string | undefined"
|
|
1994
|
+
},
|
|
1995
|
+
{
|
|
1996
|
+
"name": "payloadUrl",
|
|
1997
|
+
"tags": [
|
|
1998
|
+
{
|
|
1999
|
+
"text": "8.48.0",
|
|
2000
|
+
"name": "since"
|
|
2001
|
+
},
|
|
2002
|
+
{
|
|
2003
|
+
"text": "undefined",
|
|
2004
|
+
"name": "default"
|
|
2005
|
+
}
|
|
2006
|
+
],
|
|
2007
|
+
"docs": "HTTP(S) URL returning a preview download payload.\nWhen provided, the native shake reload action fetches this payload again\nbefore reloading so channel previews can move to the latest bundle.\nRequires {@link PluginsConfig.CapacitorUpdater.allowPreview} to be `true`.",
|
|
2008
|
+
"complexTypes": [],
|
|
2009
|
+
"type": "string | undefined"
|
|
1994
2010
|
}
|
|
1995
2011
|
]
|
|
1996
2012
|
},
|
|
@@ -1973,6 +1973,15 @@ export interface StartPreviewSessionOptions {
|
|
|
1973
1973
|
* @default undefined
|
|
1974
1974
|
*/
|
|
1975
1975
|
appId?: string;
|
|
1976
|
+
/**
|
|
1977
|
+
* HTTP(S) URL returning a preview download payload.
|
|
1978
|
+
* When provided, the native shake reload action fetches this payload again
|
|
1979
|
+
* before reloading so channel previews can move to the latest bundle.
|
|
1980
|
+
* Requires {@link PluginsConfig.CapacitorUpdater.allowPreview} to be `true`.
|
|
1981
|
+
* @since 8.48.0
|
|
1982
|
+
* @default undefined
|
|
1983
|
+
*/
|
|
1984
|
+
payloadUrl?: string;
|
|
1976
1985
|
}
|
|
1977
1986
|
export interface AppReadyResult {
|
|
1978
1987
|
bundle: BundleInfo;
|