@capgo/capacitor-updater 8.47.4 → 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.
|
@@ -106,6 +106,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
106
106
|
private static final String PREVIEW_PREVIOUS_DEFAULT_CHANNEL_WAS_SET_PREF_KEY = "CapacitorUpdater.previewPreviousDefaultChannelWasSet";
|
|
107
107
|
private static final String PREVIEW_APP_ID_PREF_KEY = "CapacitorUpdater.previewAppId";
|
|
108
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";
|
|
109
110
|
private static final String[] BREAKING_EVENT_NAMES = { "breakingAvailable", "majorAvailable" };
|
|
110
111
|
private static final String LAST_FAILED_BUNDLE_PREF_KEY = "CapacitorUpdater.lastFailedBundle";
|
|
111
112
|
private static final String LAST_REPORTED_APP_EXIT_TIMESTAMP_PREF_KEY = "CapacitorUpdater.lastReportedAppExitTimestamp";
|
|
@@ -127,7 +128,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
127
128
|
static final int APPLICATION_EXIT_REASON_USER_REQUESTED = 10;
|
|
128
129
|
static final int APPLICATION_EXIT_REASON_DEPENDENCY_DIED = 12;
|
|
129
130
|
|
|
130
|
-
private final String pluginVersion = "8.47.
|
|
131
|
+
private final String pluginVersion = "8.47.5";
|
|
131
132
|
private static final String DELAY_CONDITION_PREFERENCES = "";
|
|
132
133
|
|
|
133
134
|
private SharedPreferences.Editor editor;
|
|
@@ -160,6 +161,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
160
161
|
Boolean shakeChannelSelectorEnabled = false;
|
|
161
162
|
Boolean previewSessionEnabled = false;
|
|
162
163
|
private Boolean previewSessionAlertPending = false;
|
|
164
|
+
private Boolean isLeavingPreviewForIncomingLink = false;
|
|
163
165
|
private Boolean allowManualBundleError = false;
|
|
164
166
|
private Boolean allowPreview = false;
|
|
165
167
|
Boolean allowSetDefaultChannel = true;
|
|
@@ -502,6 +504,9 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
502
504
|
}
|
|
503
505
|
this.implementation.previewSession = Boolean.TRUE.equals(this.previewSessionEnabled);
|
|
504
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;
|
|
505
510
|
final String previewAppId = this.prefs.getString(PREVIEW_APP_ID_PREF_KEY, "");
|
|
506
511
|
if (previewAppId != null && !previewAppId.isEmpty()) {
|
|
507
512
|
this.setActiveAppId(previewAppId);
|
|
@@ -520,6 +525,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
520
525
|
if (nativeBuildVersionChanged) {
|
|
521
526
|
this.clearPreviewSessionForNativeBuildChange();
|
|
522
527
|
}
|
|
528
|
+
this.leavePreviewSessionForLaunchIntentIfNeeded();
|
|
523
529
|
this.reportPreviousAppExitReasons();
|
|
524
530
|
this.reportPreviousWebViewRenderProcessGone();
|
|
525
531
|
this.installWebViewStatsReporter();
|
|
@@ -534,6 +540,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
534
540
|
this.delayUpdateUtils.checkCancelDelay(DelayUpdateUtils.CancelDelaySource.KILLED);
|
|
535
541
|
|
|
536
542
|
this.checkForUpdateAfterDelay();
|
|
543
|
+
this.showPreviewSessionNoticeIfNeeded();
|
|
537
544
|
|
|
538
545
|
// On Android 14+ (API 34+), topActivity in RecentTaskInfo returns null due to
|
|
539
546
|
// security restrictions (StrandHogg task hijacking mitigations). Use ProcessLifecycleOwner
|
|
@@ -2301,6 +2308,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
2301
2308
|
this.shakeMenuEnabled = true;
|
|
2302
2309
|
this.shakeChannelSelectorEnabled = false;
|
|
2303
2310
|
this.editor.putBoolean(PREVIEW_SESSION_PREF_KEY, true);
|
|
2311
|
+
this.editor.putBoolean(PREVIEW_SESSION_ALERT_PENDING_PREF_KEY, true);
|
|
2304
2312
|
this.editor.apply();
|
|
2305
2313
|
this.ensureShakeMenuStarted();
|
|
2306
2314
|
call.resolve();
|
|
@@ -2322,6 +2330,58 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
2322
2330
|
final BundleInfo previewFallbackBundle = this.implementation.getPreviewFallbackBundle();
|
|
2323
2331
|
this.endPreviewSession();
|
|
2324
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;
|
|
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();
|
|
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
|
+
|
|
2374
|
+
this.endPreviewSession();
|
|
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
|
+
) {
|
|
2325
2385
|
if (
|
|
2326
2386
|
!previewBundle.isBuiltin() &&
|
|
2327
2387
|
(previewFallbackBundle == null || !previewBundle.getId().equals(previewFallbackBundle.getId())) &&
|
|
@@ -2333,7 +2393,6 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
2333
2393
|
logger.warn("Cannot delete preview bundle " + previewBundle.getId() + ": " + err.getMessage());
|
|
2334
2394
|
}
|
|
2335
2395
|
}
|
|
2336
|
-
return true;
|
|
2337
2396
|
}
|
|
2338
2397
|
|
|
2339
2398
|
public boolean reloadPreviewSessionFromShakeMenu() {
|
|
@@ -2388,6 +2447,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
2388
2447
|
|
|
2389
2448
|
this.previewSessionEnabled = false;
|
|
2390
2449
|
this.previewSessionAlertPending = false;
|
|
2450
|
+
this.isLeavingPreviewForIncomingLink = false;
|
|
2391
2451
|
this.implementation.previewSession = false;
|
|
2392
2452
|
this.shakeMenuEnabled = previousShakeMenuEnabled;
|
|
2393
2453
|
this.shakeChannelSelectorEnabled = previousShakeChannelSelectorEnabled;
|
|
@@ -2414,6 +2474,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
2414
2474
|
this.restorePreviewPreviousDefaultChannel();
|
|
2415
2475
|
this.previewSessionEnabled = false;
|
|
2416
2476
|
this.previewSessionAlertPending = false;
|
|
2477
|
+
this.isLeavingPreviewForIncomingLink = false;
|
|
2417
2478
|
this.implementation.previewSession = false;
|
|
2418
2479
|
this.shakeMenuEnabled = this.getConfig().getBoolean("shakeMenu", false);
|
|
2419
2480
|
this.shakeChannelSelectorEnabled = this.getConfig().getBoolean("allowShakeChannelSelector", false);
|
|
@@ -2433,6 +2494,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
2433
2494
|
this.editor.remove(PREVIEW_PREVIOUS_DEFAULT_CHANNEL_WAS_SET_PREF_KEY);
|
|
2434
2495
|
this.editor.remove(PREVIEW_APP_ID_PREF_KEY);
|
|
2435
2496
|
this.editor.remove(PREVIEW_PAYLOAD_URL_PREF_KEY);
|
|
2497
|
+
this.editor.remove(PREVIEW_SESSION_ALERT_PENDING_PREF_KEY);
|
|
2436
2498
|
this.editor.apply();
|
|
2437
2499
|
}
|
|
2438
2500
|
|
|
@@ -2523,6 +2585,21 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
2523
2585
|
return this.normalizePreviewPayloadUrl(this.prefs.getString(PREVIEW_PAYLOAD_URL_PREF_KEY, null));
|
|
2524
2586
|
}
|
|
2525
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
|
+
|
|
2526
2603
|
private String readResponseBody(final InputStream stream) throws IOException {
|
|
2527
2604
|
if (stream == null) {
|
|
2528
2605
|
return "";
|
|
@@ -2619,6 +2696,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
2619
2696
|
logger.info("Native build changed; clearing preview session state");
|
|
2620
2697
|
this.previewSessionEnabled = false;
|
|
2621
2698
|
this.previewSessionAlertPending = false;
|
|
2699
|
+
this.isLeavingPreviewForIncomingLink = false;
|
|
2622
2700
|
this.implementation.previewSession = false;
|
|
2623
2701
|
this.shakeMenuEnabled = this.getConfig().getBoolean("shakeMenu", false);
|
|
2624
2702
|
this.shakeChannelSelectorEnabled = this.getConfig().getBoolean("allowShakeChannelSelector", false);
|
|
@@ -2657,6 +2735,8 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
2657
2735
|
return;
|
|
2658
2736
|
}
|
|
2659
2737
|
this.previewSessionAlertPending = false;
|
|
2738
|
+
this.editor.putBoolean(PREVIEW_SESSION_ALERT_PENDING_PREF_KEY, false);
|
|
2739
|
+
this.editor.apply();
|
|
2660
2740
|
|
|
2661
2741
|
new Handler(Looper.getMainLooper()).postDelayed(
|
|
2662
2742
|
() -> {
|
|
@@ -2666,6 +2746,8 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
2666
2746
|
}
|
|
2667
2747
|
if (getActivity() == null || getActivity().isFinishing()) {
|
|
2668
2748
|
this.previewSessionAlertPending = true;
|
|
2749
|
+
this.editor.putBoolean(PREVIEW_SESSION_ALERT_PENDING_PREF_KEY, true);
|
|
2750
|
+
this.editor.apply();
|
|
2669
2751
|
return;
|
|
2670
2752
|
}
|
|
2671
2753
|
|
|
@@ -2676,6 +2758,8 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
2676
2758
|
.show();
|
|
2677
2759
|
} catch (final Exception e) {
|
|
2678
2760
|
this.previewSessionAlertPending = true;
|
|
2761
|
+
this.editor.putBoolean(PREVIEW_SESSION_ALERT_PENDING_PREF_KEY, true);
|
|
2762
|
+
this.editor.apply();
|
|
2679
2763
|
logger.warn("Could not show preview session notice: " + e.getMessage());
|
|
2680
2764
|
}
|
|
2681
2765
|
},
|
|
@@ -3920,6 +4004,34 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
3920
4004
|
}
|
|
3921
4005
|
}
|
|
3922
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
|
+
|
|
3923
4035
|
@Override
|
|
3924
4036
|
public void handleOnStart() {
|
|
3925
4037
|
try {
|
|
@@ -79,7 +79,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
79
79
|
CAPPluginMethod(name: "completeFlexibleUpdate", returnType: CAPPluginReturnPromise)
|
|
80
80
|
]
|
|
81
81
|
public var implementation = CapgoUpdater()
|
|
82
|
-
private let pluginVersion: String = "8.47.
|
|
82
|
+
private let pluginVersion: String = "8.47.5"
|
|
83
83
|
static let updateUrlDefault = "https://plugin.capgo.app/updates"
|
|
84
84
|
static let statsUrlDefault = "https://plugin.capgo.app/stats"
|
|
85
85
|
static let channelUrlDefault = "https://plugin.capgo.app/channel_self"
|
|
@@ -105,6 +105,12 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
105
105
|
private let previewPreviousDefaultChannelWasSetDefaultsKey = "CapacitorUpdater.previewPreviousDefaultChannelWasSet"
|
|
106
106
|
private let previewAppIdDefaultsKey = "CapacitorUpdater.previewAppId"
|
|
107
107
|
private let previewPayloadUrlDefaultsKey = "CapacitorUpdater.previewPayloadUrl"
|
|
108
|
+
private let previewSessionAlertPendingDefaultsKey = "CapacitorUpdater.previewSessionAlertPending"
|
|
109
|
+
private let previewDeepLinkScheme = "capgo"
|
|
110
|
+
private let previewDeepLinkRootComponent = "preview"
|
|
111
|
+
private let previewDeepLinkChannelComponent = "channel"
|
|
112
|
+
private let previewDeepLinkBundleComponent = "bundle"
|
|
113
|
+
private let previewPathSeparator = Character(UnicodeScalar(UInt8(47)))
|
|
108
114
|
// Note: DELAY_CONDITION_PREFERENCES is now defined in DelayUpdateUtils.DELAY_CONDITION_PREFERENCES
|
|
109
115
|
private var updateUrl = ""
|
|
110
116
|
private var backgroundTaskID: UIBackgroundTaskIdentifier = UIBackgroundTaskIdentifier.invalid
|
|
@@ -158,6 +164,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
158
164
|
public var shakeChannelSelectorEnabled = false
|
|
159
165
|
public var previewSessionEnabled = false
|
|
160
166
|
private var previewSessionAlertPending = false
|
|
167
|
+
private var isLeavingPreviewForIncomingLink = false
|
|
161
168
|
let semaphoreReady = DispatchSemaphore(value: 0)
|
|
162
169
|
|
|
163
170
|
private var delayUpdateUtils: DelayUpdateUtils!
|
|
@@ -233,6 +240,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
233
240
|
previewSessionEnabled = allowPreview && storedPreviewSessionEnabled
|
|
234
241
|
implementation.previewSession = previewSessionEnabled
|
|
235
242
|
if previewSessionEnabled {
|
|
243
|
+
previewSessionAlertPending = UserDefaults.standard.object(forKey: previewSessionAlertPendingDefaultsKey) as? Bool ?? true
|
|
236
244
|
shakeMenuEnabled = true
|
|
237
245
|
shakeChannelSelectorEnabled = false
|
|
238
246
|
}
|
|
@@ -315,6 +323,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
315
323
|
if nativeBuildVersionChanged {
|
|
316
324
|
self.clearPreviewSessionForNativeBuildChange()
|
|
317
325
|
}
|
|
326
|
+
self.leavePreviewSessionForLaunchURLIfNeeded()
|
|
318
327
|
|
|
319
328
|
if resetWhenUpdate {
|
|
320
329
|
let didResetCurrentBundle = self.resetCurrentBundleForNativeBuildChangeIfNeeded()
|
|
@@ -332,11 +341,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
332
341
|
logger.error("unable to force reload, the plugin might fallback to the builtin version")
|
|
333
342
|
}
|
|
334
343
|
|
|
335
|
-
|
|
336
|
-
nc.addObserver(self, selector: #selector(appMovedToBackground), name: UIApplication.didEnterBackgroundNotification, object: nil)
|
|
337
|
-
nc.addObserver(self, selector: #selector(appMovedToForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
|
|
338
|
-
nc.addObserver(self, selector: #selector(appWillTerminate), name: UIApplication.willTerminateNotification, object: nil)
|
|
339
|
-
nc.addObserver(self, selector: #selector(appDidReceiveMemoryWarning), name: UIApplication.didReceiveMemoryWarningNotification, object: nil)
|
|
344
|
+
self.registerNotificationObservers()
|
|
340
345
|
|
|
341
346
|
// Check for 'kill' delay condition on app launch
|
|
342
347
|
// This handles cases where the app was killed (willTerminateNotification is not reliable for system kills)
|
|
@@ -344,6 +349,47 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
344
349
|
|
|
345
350
|
self.appMovedToForeground()
|
|
346
351
|
self.checkForUpdateAfterDelay()
|
|
352
|
+
self.showPreviewSessionNoticeIfNeeded()
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
private func registerNotificationObservers() {
|
|
356
|
+
let notificationCenter = NotificationCenter.default
|
|
357
|
+
notificationCenter.addObserver(
|
|
358
|
+
self,
|
|
359
|
+
selector: #selector(appMovedToBackground),
|
|
360
|
+
name: UIApplication.didEnterBackgroundNotification,
|
|
361
|
+
object: nil
|
|
362
|
+
)
|
|
363
|
+
notificationCenter.addObserver(
|
|
364
|
+
self,
|
|
365
|
+
selector: #selector(appMovedToForeground),
|
|
366
|
+
name: UIApplication.willEnterForegroundNotification,
|
|
367
|
+
object: nil
|
|
368
|
+
)
|
|
369
|
+
notificationCenter.addObserver(
|
|
370
|
+
self,
|
|
371
|
+
selector: #selector(appWillTerminate),
|
|
372
|
+
name: UIApplication.willTerminateNotification,
|
|
373
|
+
object: nil
|
|
374
|
+
)
|
|
375
|
+
notificationCenter.addObserver(
|
|
376
|
+
self,
|
|
377
|
+
selector: #selector(appDidReceiveMemoryWarning),
|
|
378
|
+
name: UIApplication.didReceiveMemoryWarningNotification,
|
|
379
|
+
object: nil
|
|
380
|
+
)
|
|
381
|
+
notificationCenter.addObserver(
|
|
382
|
+
self,
|
|
383
|
+
selector: #selector(handleOpenURLForPreviewSession(notification:)),
|
|
384
|
+
name: Notification.Name.capacitorOpenURL,
|
|
385
|
+
object: nil
|
|
386
|
+
)
|
|
387
|
+
notificationCenter.addObserver(
|
|
388
|
+
self,
|
|
389
|
+
selector: #selector(handleOpenURLForPreviewSession(notification:)),
|
|
390
|
+
name: Notification.Name.capacitorOpenUniversalLink,
|
|
391
|
+
object: nil
|
|
392
|
+
)
|
|
347
393
|
}
|
|
348
394
|
|
|
349
395
|
private func syncKeepUrlPathFlag(enabled: Bool) {
|
|
@@ -1077,6 +1123,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
1077
1123
|
self.shakeMenuEnabled = true
|
|
1078
1124
|
self.shakeChannelSelectorEnabled = false
|
|
1079
1125
|
UserDefaults.standard.set(true, forKey: self.previewSessionDefaultsKey)
|
|
1126
|
+
UserDefaults.standard.set(true, forKey: self.previewSessionAlertPendingDefaultsKey)
|
|
1080
1127
|
UserDefaults.standard.synchronize()
|
|
1081
1128
|
call.resolve()
|
|
1082
1129
|
}
|
|
@@ -1092,12 +1139,57 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
1092
1139
|
let previewFallbackBundle = self.implementation.getPreviewFallbackBundle()
|
|
1093
1140
|
self.endPreviewSession()
|
|
1094
1141
|
let restoredNextBundle = self.implementation.getNextBundle()
|
|
1142
|
+
self.deletePreviewBundleIfUnused(previewBundle, previewFallbackBundle: previewFallbackBundle, restoredNextBundle: restoredNextBundle)
|
|
1143
|
+
return true
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
private func leavePreviewSessionForLaunchURLIfNeeded() {
|
|
1147
|
+
guard self.previewSessionEnabled,
|
|
1148
|
+
!self.isLeavingPreviewForIncomingLink,
|
|
1149
|
+
let launchUrl = ApplicationDelegateProxy.shared.lastURL,
|
|
1150
|
+
self.isPreviewDeepLink(launchUrl) else {
|
|
1151
|
+
return
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
self.isLeavingPreviewForIncomingLink = true
|
|
1155
|
+
logger.info("Preview deeplink launch detected while preview session is active; restoring fallback before initial load")
|
|
1156
|
+
if !self.leavePreviewSessionWithoutReload() {
|
|
1157
|
+
logger.error("Could not leave preview session before initial preview deeplink routing")
|
|
1158
|
+
self.isLeavingPreviewForIncomingLink = false
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
private func leavePreviewSessionWithoutReload() -> Bool {
|
|
1163
|
+
let previewBundle = self.implementation.getCurrentBundle()
|
|
1164
|
+
guard let previewFallbackBundle = self.implementation.getPreviewFallbackBundle(), !previewFallbackBundle.isErrorStatus() else {
|
|
1165
|
+
logger.error("No preview fallback bundle available")
|
|
1166
|
+
return false
|
|
1167
|
+
}
|
|
1168
|
+
guard self.implementation.canSet(bundle: previewFallbackBundle) else {
|
|
1169
|
+
logger.error("Preview fallback bundle is not installable")
|
|
1170
|
+
return false
|
|
1171
|
+
}
|
|
1172
|
+
guard self.implementation.stagePreviewFallbackReload(bundle: previewFallbackBundle) else {
|
|
1173
|
+
logger.error("Could not stage preview fallback bundle")
|
|
1174
|
+
return false
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
self.endPreviewSession()
|
|
1178
|
+
let restoredNextBundle = self.implementation.getNextBundle()
|
|
1179
|
+
self.deletePreviewBundleIfUnused(previewBundle, previewFallbackBundle: previewFallbackBundle, restoredNextBundle: restoredNextBundle)
|
|
1180
|
+
return true
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
private func deletePreviewBundleIfUnused(
|
|
1184
|
+
_ previewBundle: BundleInfo,
|
|
1185
|
+
previewFallbackBundle: BundleInfo?,
|
|
1186
|
+
restoredNextBundle: BundleInfo?
|
|
1187
|
+
) {
|
|
1095
1188
|
if !previewBundle.isBuiltin() &&
|
|
1096
1189
|
previewFallbackBundle?.getId() != previewBundle.getId() &&
|
|
1097
1190
|
restoredNextBundle?.getId() != previewBundle.getId() {
|
|
1098
1191
|
_ = self.implementation.delete(id: previewBundle.getId(), removeInfo: false)
|
|
1099
1192
|
}
|
|
1100
|
-
return true
|
|
1101
1193
|
}
|
|
1102
1194
|
|
|
1103
1195
|
func reloadPreviewSessionFromShakeMenu() -> Bool {
|
|
@@ -1147,6 +1239,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
1147
1239
|
|
|
1148
1240
|
self.previewSessionEnabled = false
|
|
1149
1241
|
self.previewSessionAlertPending = false
|
|
1242
|
+
self.isLeavingPreviewForIncomingLink = false
|
|
1150
1243
|
self.implementation.previewSession = false
|
|
1151
1244
|
self.shakeMenuEnabled = previousShakeMenuEnabled
|
|
1152
1245
|
self.shakeChannelSelectorEnabled = previousShakeChannelSelectorEnabled
|
|
@@ -1176,6 +1269,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
1176
1269
|
self.restorePreviewPreviousDefaultChannel()
|
|
1177
1270
|
self.previewSessionEnabled = false
|
|
1178
1271
|
self.previewSessionAlertPending = false
|
|
1272
|
+
self.isLeavingPreviewForIncomingLink = false
|
|
1179
1273
|
self.implementation.previewSession = false
|
|
1180
1274
|
self.shakeMenuEnabled = getConfig().getBoolean("shakeMenu", false)
|
|
1181
1275
|
self.shakeChannelSelectorEnabled = getConfig().getBoolean("allowShakeChannelSelector", false)
|
|
@@ -1193,6 +1287,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
1193
1287
|
UserDefaults.standard.removeObject(forKey: self.previewPreviousDefaultChannelWasSetDefaultsKey)
|
|
1194
1288
|
UserDefaults.standard.removeObject(forKey: self.previewAppIdDefaultsKey)
|
|
1195
1289
|
UserDefaults.standard.removeObject(forKey: self.previewPayloadUrlDefaultsKey)
|
|
1290
|
+
UserDefaults.standard.removeObject(forKey: self.previewSessionAlertPendingDefaultsKey)
|
|
1196
1291
|
UserDefaults.standard.synchronize()
|
|
1197
1292
|
}
|
|
1198
1293
|
|
|
@@ -1259,6 +1354,55 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
1259
1354
|
normalizedPreviewPayloadUrl(UserDefaults.standard.string(forKey: self.previewPayloadUrlDefaultsKey))
|
|
1260
1355
|
}
|
|
1261
1356
|
|
|
1357
|
+
private func previewPath(from url: URL) -> String {
|
|
1358
|
+
if url.scheme == self.previewDeepLinkScheme {
|
|
1359
|
+
var components: [String] = []
|
|
1360
|
+
if let host = url.host, !host.isEmpty {
|
|
1361
|
+
components.append(host)
|
|
1362
|
+
}
|
|
1363
|
+
components.append(contentsOf: url.path.split(separator: self.previewPathSeparator).map(String.init))
|
|
1364
|
+
return self.normalizedPreviewPath(components)
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
return url.path
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
private func normalizedPreviewPath(_ components: [String]) -> String {
|
|
1371
|
+
let separator = String(self.previewPathSeparator)
|
|
1372
|
+
return separator + components.filter { !$0.isEmpty }.joined(separator: separator)
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
private func previewDeepLinkPath(_ leafComponent: String) -> String {
|
|
1376
|
+
self.normalizedPreviewPath([self.previewDeepLinkRootComponent, leafComponent])
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
private func isPreviewDeepLink(_ url: URL) -> Bool {
|
|
1380
|
+
let path = self.previewPath(from: url)
|
|
1381
|
+
return path == self.previewDeepLinkPath(self.previewDeepLinkChannelComponent) ||
|
|
1382
|
+
path == self.previewDeepLinkPath(self.previewDeepLinkBundleComponent)
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
@objc private func handleOpenURLForPreviewSession(notification: NSNotification) {
|
|
1386
|
+
let rawUrl = (notification.object as? [String: Any])?["url"]
|
|
1387
|
+
let url = rawUrl as? URL ?? (rawUrl as? NSURL).map { $0 as URL }
|
|
1388
|
+
guard self.previewSessionEnabled,
|
|
1389
|
+
!self.isLeavingPreviewForIncomingLink,
|
|
1390
|
+
let url,
|
|
1391
|
+
self.isPreviewDeepLink(url) else {
|
|
1392
|
+
return
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
self.isLeavingPreviewForIncomingLink = true
|
|
1396
|
+
logger.info("Preview deeplink received while preview session is active; restoring fallback before routing")
|
|
1397
|
+
DispatchQueue.global(qos: .userInitiated).async {
|
|
1398
|
+
let didLeave = self.leavePreviewSessionFromShakeMenu()
|
|
1399
|
+
if !didLeave {
|
|
1400
|
+
self.logger.error("Could not leave preview session before routing incoming preview deeplink")
|
|
1401
|
+
self.isLeavingPreviewForIncomingLink = false
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1262
1406
|
private func fetchPreviewPayload(_ payloadUrl: URL) throws -> PreviewPayload {
|
|
1263
1407
|
var request = URLRequest(url: payloadUrl)
|
|
1264
1408
|
request.setValue("application/json", forHTTPHeaderField: "Accept")
|
|
@@ -1340,6 +1484,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
1340
1484
|
logger.info("Native build changed; clearing preview session state")
|
|
1341
1485
|
self.previewSessionEnabled = false
|
|
1342
1486
|
self.previewSessionAlertPending = false
|
|
1487
|
+
self.isLeavingPreviewForIncomingLink = false
|
|
1343
1488
|
self.implementation.previewSession = false
|
|
1344
1489
|
self.shakeMenuEnabled = getConfig().getBoolean("shakeMenu", false)
|
|
1345
1490
|
self.shakeChannelSelectorEnabled = getConfig().getBoolean("allowShakeChannelSelector", false)
|
|
@@ -1367,6 +1512,8 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
1367
1512
|
return
|
|
1368
1513
|
}
|
|
1369
1514
|
self.previewSessionAlertPending = false
|
|
1515
|
+
UserDefaults.standard.set(false, forKey: self.previewSessionAlertPendingDefaultsKey)
|
|
1516
|
+
UserDefaults.standard.synchronize()
|
|
1370
1517
|
|
|
1371
1518
|
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(600)) {
|
|
1372
1519
|
guard self.previewSessionEnabled else {
|
|
@@ -1375,6 +1522,8 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
1375
1522
|
if let topVC = UIApplication.topViewController(),
|
|
1376
1523
|
topVC.isKind(of: UIAlertController.self) {
|
|
1377
1524
|
self.previewSessionAlertPending = true
|
|
1525
|
+
UserDefaults.standard.set(true, forKey: self.previewSessionAlertPendingDefaultsKey)
|
|
1526
|
+
UserDefaults.standard.synchronize()
|
|
1378
1527
|
return
|
|
1379
1528
|
}
|
|
1380
1529
|
|
|
@@ -1386,6 +1535,10 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
1386
1535
|
alert.addAction(UIAlertAction(title: "Got it", style: .default))
|
|
1387
1536
|
if let topVC = UIApplication.topViewController() {
|
|
1388
1537
|
topVC.present(alert, animated: true)
|
|
1538
|
+
} else {
|
|
1539
|
+
self.previewSessionAlertPending = true
|
|
1540
|
+
UserDefaults.standard.set(true, forKey: self.previewSessionAlertPendingDefaultsKey)
|
|
1541
|
+
UserDefaults.standard.synchronize()
|
|
1389
1542
|
}
|
|
1390
1543
|
}
|
|
1391
1544
|
}
|
|
@@ -29,7 +29,7 @@ extension UIWindow {
|
|
|
29
29
|
// Find the CapacitorUpdaterPlugin instance
|
|
30
30
|
guard let bridgeViewController = rootViewController as? CAPBridgeViewController,
|
|
31
31
|
let bridge = bridgeViewController.bridge,
|
|
32
|
-
let plugin = bridge.plugin(withName: "
|
|
32
|
+
let plugin = bridge.plugin(withName: "CapacitorUpdater") as? CapacitorUpdaterPlugin else {
|
|
33
33
|
return
|
|
34
34
|
}
|
|
35
35
|
|