@capgo/capacitor-updater 8.47.6 → 8.47.8

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.
@@ -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.6"
82
+ private let pluginVersion: String = "8.47.8"
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"
@@ -89,6 +89,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
89
89
  static let autoUpdateModeLaunch = "onLaunch"
90
90
  static let autoUpdateModeAlways = "always"
91
91
  static let autoUpdateModeOnlyDownload = "onlyDownload"
92
+ private static let previewLoaderTimeoutMs = 60000
92
93
  private let keepUrlPathFlagKey = "__capgo_keep_url_path_after_reload"
93
94
  private let customIdDefaultsKey = "CapacitorUpdater.customId"
94
95
  private let updateUrlDefaultsKey = "CapacitorUpdater.updateUrl"
@@ -131,6 +132,10 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
131
132
  private var autoSplashscreenTimeoutWorkItem: DispatchWorkItem?
132
133
  private var splashscreenLoaderView: UIActivityIndicatorView?
133
134
  private var splashscreenLoaderContainer: UIView?
135
+ private var previewTransitionLoaderView: UIActivityIndicatorView?
136
+ private var previewTransitionLoaderContainer: UIView?
137
+ private var previewTransitionLoaderTimeoutWorkItem: DispatchWorkItem?
138
+ private var previewTransitionLoaderRequested = false
134
139
  private let splashscreenPluginName = "SplashScreen"
135
140
  private let splashscreenRetryDelayMilliseconds = 100
136
141
  private let splashscreenMaxRetries = 20
@@ -243,7 +248,8 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
243
248
  if previewSessionEnabled {
244
249
  previewSessionAlertPending = UserDefaults.standard.object(forKey: previewSessionAlertPendingDefaultsKey) as? Bool ?? true
245
250
  shakeMenuEnabled = true
246
- shakeChannelSelectorEnabled = false
251
+ shakeChannelSelectorEnabled = UserDefaults.standard.object(forKey: previewPreviousShakeChannelSelectorDefaultsKey) as? Bool
252
+ ?? shakeChannelSelectorEnabled
247
253
  }
248
254
  periodCheckDelay = Self.normalizedPeriodCheckDelaySeconds(getConfig().getInt("periodCheckDelay", 0))
249
255
 
@@ -1119,6 +1125,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
1119
1125
 
1120
1126
  @objc func startPreviewSession(_ call: CAPPluginCall) {
1121
1127
  guard self.allowPreview else {
1128
+ self.hidePreviewTransitionLoader(reason: "preview-session-not-allowed")
1122
1129
  logger.error("startPreviewSession called without allowPreview")
1123
1130
  call.reject("startPreviewSession not allowed. Set allowPreview to true in your config to enable it.")
1124
1131
  return
@@ -1127,6 +1134,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
1127
1134
  let rawPayloadUrl = call.getString("payloadUrl")
1128
1135
  let previewPayloadUrl = self.normalizedPreviewPayloadUrl(rawPayloadUrl)
1129
1136
  if let rawPayloadUrl = rawPayloadUrl, !rawPayloadUrl.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty, previewPayloadUrl == nil {
1137
+ self.hidePreviewTransitionLoader(reason: "preview-session-invalid-payload")
1130
1138
  logger.error("startPreviewSession called with invalid payloadUrl")
1131
1139
  call.reject("Invalid preview payloadUrl")
1132
1140
  return
@@ -1135,6 +1143,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
1135
1143
  if !self.previewSessionEnabled {
1136
1144
  let current = self.implementation.getCurrentBundle()
1137
1145
  guard self.implementation.setPreviewFallbackBundle(fallback: current.getId()) else {
1146
+ self.hidePreviewTransitionLoader(reason: "preview-session-fallback-failed")
1138
1147
  logger.error("Could not save current bundle as preview fallback")
1139
1148
  call.reject("Could not save current bundle as preview fallback")
1140
1149
  return
@@ -1175,11 +1184,11 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
1175
1184
  }
1176
1185
 
1177
1186
  self.clearIncomingPreviewTransition()
1187
+ self.hidePreviewTransitionLoader(reason: "preview-session-started")
1178
1188
  self.previewSessionEnabled = true
1179
1189
  self.previewSessionAlertPending = true
1180
1190
  self.implementation.previewSession = true
1181
1191
  self.shakeMenuEnabled = true
1182
- self.shakeChannelSelectorEnabled = false
1183
1192
  UserDefaults.standard.set(true, forKey: self.previewSessionDefaultsKey)
1184
1193
  UserDefaults.standard.set(true, forKey: self.previewSessionAlertPendingDefaultsKey)
1185
1194
  UserDefaults.standard.synchronize()
@@ -1189,8 +1198,10 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
1189
1198
  func leavePreviewSessionFromShakeMenu() -> Bool {
1190
1199
  let previewBundle = self.implementation.getCurrentBundle()
1191
1200
 
1201
+ self.showPreviewTransitionLoader(reason: "leave-preview-session")
1192
1202
  let didReset = self.resetToPreviewFallbackBundle()
1193
1203
  guard didReset else {
1204
+ self.hidePreviewTransitionLoader(reason: "leave-preview-session-failed")
1194
1205
  return false
1195
1206
  }
1196
1207
 
@@ -1210,10 +1221,12 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
1210
1221
  }
1211
1222
 
1212
1223
  self.isLeavingPreviewForIncomingLink = true
1224
+ self.showPreviewTransitionLoader(reason: "preview-launch-deeplink")
1213
1225
  logger.info("Preview deeplink launch detected while preview session is active; restoring fallback before initial load")
1214
1226
  if !self.leavePreviewSessionWithoutReload() {
1215
1227
  logger.error("Could not leave preview session before initial preview deeplink routing")
1216
1228
  self.isLeavingPreviewForIncomingLink = false
1229
+ self.hidePreviewTransitionLoader(reason: "preview-launch-deeplink-failed")
1217
1230
  }
1218
1231
  }
1219
1232
 
@@ -1239,15 +1252,18 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
1239
1252
  }
1240
1253
 
1241
1254
  private func leavePreviewSessionForIncomingPreviewLink() -> Bool {
1255
+ self.showPreviewTransitionLoader(reason: "incoming-preview-deeplink")
1242
1256
  let previewBundle = self.implementation.getCurrentBundle()
1243
1257
  guard let previewFallbackBundle = self.implementation.getPreviewFallbackBundle(), !previewFallbackBundle.isErrorStatus() else {
1244
1258
  logger.error("No preview fallback bundle available")
1245
1259
  self.clearIncomingPreviewTransition()
1260
+ self.hidePreviewTransitionLoader(reason: "incoming-preview-deeplink-failed")
1246
1261
  return false
1247
1262
  }
1248
1263
  guard self.implementation.canSet(bundle: previewFallbackBundle) else {
1249
1264
  logger.error("Preview fallback bundle is not installable")
1250
1265
  self.clearIncomingPreviewTransition()
1266
+ self.hidePreviewTransitionLoader(reason: "incoming-preview-deeplink-failed")
1251
1267
  return false
1252
1268
  }
1253
1269
 
@@ -1255,6 +1271,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
1255
1271
  guard self.implementation.stagePreviewFallbackReload(bundle: previewFallbackBundle) else {
1256
1272
  logger.error("Could not stage preview fallback bundle")
1257
1273
  self.clearIncomingPreviewTransition()
1274
+ self.hidePreviewTransitionLoader(reason: "incoming-preview-deeplink-failed")
1258
1275
  return false
1259
1276
  }
1260
1277
 
@@ -1272,6 +1289,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
1272
1289
  self.implementation.restoreResetState(previousState)
1273
1290
  self.restoreLiveBundleStateAfterFailedReload()
1274
1291
  self.clearIncomingPreviewTransition()
1292
+ self.hidePreviewTransitionLoader(reason: "incoming-preview-deeplink-reload-failed")
1275
1293
  }
1276
1294
  return didReload
1277
1295
  }
@@ -1289,11 +1307,18 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
1289
1307
  }
1290
1308
 
1291
1309
  func reloadPreviewSessionFromShakeMenu() -> Bool {
1310
+ self.showPreviewTransitionLoader(reason: "reload-preview-session")
1311
+ let didReload: Bool
1292
1312
  if let payloadUrl = self.storedPreviewPayloadUrl() {
1293
- return self.refreshPreviewSessionFromPayloadUrl(payloadUrl)
1313
+ didReload = self.refreshPreviewSessionFromPayloadUrl(payloadUrl)
1314
+ } else {
1315
+ didReload = self._reload()
1294
1316
  }
1295
1317
 
1296
- return self._reload()
1318
+ if !didReload {
1319
+ self.hidePreviewTransitionLoader(reason: "reload-preview-session-failed")
1320
+ }
1321
+ return didReload
1297
1322
  }
1298
1323
 
1299
1324
  func hasActivePreviewSession() -> Bool {
@@ -1370,6 +1395,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
1370
1395
  self.previewSessionAlertPending = false
1371
1396
  self.isLeavingPreviewForIncomingLink = false
1372
1397
  self.implementation.previewSession = false
1398
+ self.hidePreviewTransitionLoader(reason: "preview-session-disabled")
1373
1399
  self.shakeMenuEnabled = getConfig().getBoolean("shakeMenu", false)
1374
1400
  self.shakeChannelSelectorEnabled = getConfig().getBoolean("allowShakeChannelSelector", false)
1375
1401
  self.clearPreviewSessionPreferences()
@@ -1492,12 +1518,14 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
1492
1518
  }
1493
1519
 
1494
1520
  self.isLeavingPreviewForIncomingLink = true
1521
+ self.showPreviewTransitionLoader(reason: "incoming-preview-deeplink")
1495
1522
  logger.info("Preview deeplink received while preview session is active; restoring fallback before routing")
1496
1523
  DispatchQueue.global(qos: .userInitiated).async {
1497
1524
  let didLeave = self.leavePreviewSessionForIncomingPreviewLink()
1498
1525
  if !didLeave {
1499
1526
  self.logger.error("Could not leave preview session before routing incoming preview deeplink")
1500
1527
  self.isLeavingPreviewForIncomingLink = false
1528
+ self.hidePreviewTransitionLoader(reason: "incoming-preview-deeplink-failed")
1501
1529
  }
1502
1530
  }
1503
1531
  }
@@ -2040,6 +2068,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
2040
2068
  self.implementation.setSuccess(bundle: bundle, autoDeletePrevious: self.autoDeletePrevious)
2041
2069
  logger.info("Current bundle loaded successfully. [notifyAppReady was called] \(bundle.toString())")
2042
2070
  self.clearIncomingPreviewTransition()
2071
+ self.hidePreviewTransitionLoader(reason: "notify-app-ready")
2043
2072
 
2044
2073
  call.resolve(["bundle": bundle.toJSON()])
2045
2074
  }
@@ -2186,6 +2215,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
2186
2215
  if self.autoSplashscreen {
2187
2216
  self.hideSplashscreen()
2188
2217
  }
2218
+ self.hidePreviewTransitionLoader(reason: "app-ready")
2189
2219
  }
2190
2220
  }
2191
2221
 
@@ -2353,6 +2383,51 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
2353
2383
  }
2354
2384
  }
2355
2385
 
2386
+ private func createLoaderOverlay(
2387
+ backgroundColor: UIColor,
2388
+ isUserInteractionEnabled: Bool,
2389
+ indicatorColor: UIColor?
2390
+ ) -> (container: UIView, indicator: UIActivityIndicatorView) {
2391
+ let container = UIView()
2392
+ container.translatesAutoresizingMaskIntoConstraints = false
2393
+ container.backgroundColor = backgroundColor
2394
+ container.isUserInteractionEnabled = isUserInteractionEnabled
2395
+
2396
+ let indicatorStyle: UIActivityIndicatorView.Style
2397
+ if #available(iOS 13.0, *) {
2398
+ indicatorStyle = .large
2399
+ } else {
2400
+ indicatorStyle = .whiteLarge
2401
+ }
2402
+
2403
+ let indicator = UIActivityIndicatorView(style: indicatorStyle)
2404
+ indicator.translatesAutoresizingMaskIntoConstraints = false
2405
+ indicator.hidesWhenStopped = false
2406
+ if let indicatorColor = indicatorColor {
2407
+ indicator.color = indicatorColor
2408
+ }
2409
+ indicator.startAnimating()
2410
+
2411
+ return (container, indicator)
2412
+ }
2413
+
2414
+ private func attachLoaderOverlay(
2415
+ _ overlay: (container: UIView, indicator: UIActivityIndicatorView),
2416
+ to rootView: UIView
2417
+ ) {
2418
+ overlay.container.addSubview(overlay.indicator)
2419
+ rootView.addSubview(overlay.container)
2420
+
2421
+ NSLayoutConstraint.activate([
2422
+ overlay.container.leadingAnchor.constraint(equalTo: rootView.leadingAnchor),
2423
+ overlay.container.trailingAnchor.constraint(equalTo: rootView.trailingAnchor),
2424
+ overlay.container.topAnchor.constraint(equalTo: rootView.topAnchor),
2425
+ overlay.container.bottomAnchor.constraint(equalTo: rootView.bottomAnchor),
2426
+ overlay.indicator.centerXAnchor.constraint(equalTo: overlay.container.centerXAnchor),
2427
+ overlay.indicator.centerYAnchor.constraint(equalTo: overlay.container.centerYAnchor)
2428
+ ])
2429
+ }
2430
+
2356
2431
  private func addSplashscreenLoaderIfNeeded() {
2357
2432
  guard self.autoSplashscreenLoader else {
2358
2433
  return
@@ -2367,40 +2442,20 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
2367
2442
  return
2368
2443
  }
2369
2444
 
2370
- let container = UIView()
2371
- container.translatesAutoresizingMaskIntoConstraints = false
2372
- container.backgroundColor = UIColor.clear
2373
- container.isUserInteractionEnabled = false
2374
-
2375
- let indicatorStyle: UIActivityIndicatorView.Style
2445
+ let indicatorColor: UIColor?
2376
2446
  if #available(iOS 13.0, *) {
2377
- indicatorStyle = .large
2447
+ indicatorColor = UIColor.label
2378
2448
  } else {
2379
- indicatorStyle = .whiteLarge
2380
- }
2381
-
2382
- let indicator = UIActivityIndicatorView(style: indicatorStyle)
2383
- indicator.translatesAutoresizingMaskIntoConstraints = false
2384
- indicator.hidesWhenStopped = false
2385
- if #available(iOS 13.0, *) {
2386
- indicator.color = UIColor.label
2449
+ indicatorColor = nil
2387
2450
  }
2388
- indicator.startAnimating()
2389
-
2390
- container.addSubview(indicator)
2391
- rootView.addSubview(container)
2392
-
2393
- NSLayoutConstraint.activate([
2394
- container.leadingAnchor.constraint(equalTo: rootView.leadingAnchor),
2395
- container.trailingAnchor.constraint(equalTo: rootView.trailingAnchor),
2396
- container.topAnchor.constraint(equalTo: rootView.topAnchor),
2397
- container.bottomAnchor.constraint(equalTo: rootView.bottomAnchor),
2398
- indicator.centerXAnchor.constraint(equalTo: container.centerXAnchor),
2399
- indicator.centerYAnchor.constraint(equalTo: container.centerYAnchor)
2400
- ])
2401
-
2402
- self.splashscreenLoaderContainer = container
2403
- self.splashscreenLoaderView = indicator
2451
+ let overlay = self.createLoaderOverlay(
2452
+ backgroundColor: UIColor.clear,
2453
+ isUserInteractionEnabled: false,
2454
+ indicatorColor: indicatorColor
2455
+ )
2456
+ self.attachLoaderOverlay(overlay, to: rootView)
2457
+ self.splashscreenLoaderContainer = overlay.container
2458
+ self.splashscreenLoaderView = overlay.indicator
2404
2459
  }
2405
2460
 
2406
2461
  if Thread.isMainThread {
@@ -2429,6 +2484,97 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
2429
2484
  }
2430
2485
  }
2431
2486
 
2487
+ private func showPreviewTransitionLoader(reason: String) {
2488
+ self.previewTransitionLoaderRequested = true
2489
+ let showLoader = {
2490
+ guard self.previewTransitionLoaderRequested else {
2491
+ return
2492
+ }
2493
+
2494
+ if let container = self.previewTransitionLoaderContainer {
2495
+ self.previewTransitionLoaderTimeoutWorkItem?.cancel()
2496
+ self.schedulePreviewTransitionLoaderTimeout()
2497
+ container.superview?.bringSubviewToFront(container)
2498
+ return
2499
+ }
2500
+
2501
+ guard let rootView = self.bridge?.viewController?.view else {
2502
+ self.logger.warn("Preview transition loader unavailable: root view missing for \(reason)")
2503
+ self.previewTransitionLoaderRequested = false
2504
+ return
2505
+ }
2506
+
2507
+ self.previewTransitionLoaderTimeoutWorkItem?.cancel()
2508
+ self.schedulePreviewTransitionLoaderTimeout()
2509
+
2510
+ let indicatorColor: UIColor?
2511
+ if #available(iOS 13.0, *) {
2512
+ indicatorColor = UIColor.white
2513
+ } else {
2514
+ indicatorColor = nil
2515
+ }
2516
+ let overlay = self.createLoaderOverlay(
2517
+ backgroundColor: UIColor.black.withAlphaComponent(0.18),
2518
+ isUserInteractionEnabled: true,
2519
+ indicatorColor: indicatorColor
2520
+ )
2521
+ self.attachLoaderOverlay(overlay, to: rootView)
2522
+ self.previewTransitionLoaderContainer = overlay.container
2523
+ self.previewTransitionLoaderView = overlay.indicator
2524
+ self.logger.info("Preview transition loader shown: \(reason)")
2525
+ }
2526
+
2527
+ if Thread.isMainThread {
2528
+ showLoader()
2529
+ } else {
2530
+ DispatchQueue.main.async {
2531
+ showLoader()
2532
+ }
2533
+ }
2534
+ }
2535
+
2536
+ private func hidePreviewTransitionLoader(reason: String) {
2537
+ if !self.previewTransitionLoaderRequested &&
2538
+ self.previewTransitionLoaderContainer == nil &&
2539
+ self.previewTransitionLoaderTimeoutWorkItem == nil {
2540
+ return
2541
+ }
2542
+
2543
+ let hideLoader = {
2544
+ self.previewTransitionLoaderRequested = false
2545
+ self.previewTransitionLoaderTimeoutWorkItem?.cancel()
2546
+ self.previewTransitionLoaderTimeoutWorkItem = nil
2547
+ guard self.previewTransitionLoaderContainer != nil else {
2548
+ return
2549
+ }
2550
+ self.previewTransitionLoaderView?.stopAnimating()
2551
+ self.previewTransitionLoaderContainer?.removeFromSuperview()
2552
+ self.previewTransitionLoaderView = nil
2553
+ self.previewTransitionLoaderContainer = nil
2554
+ self.logger.info("Preview transition loader hidden: \(reason)")
2555
+ }
2556
+
2557
+ if Thread.isMainThread {
2558
+ hideLoader()
2559
+ } else {
2560
+ DispatchQueue.main.async {
2561
+ hideLoader()
2562
+ }
2563
+ }
2564
+ }
2565
+
2566
+ private func schedulePreviewTransitionLoaderTimeout() {
2567
+ self.previewTransitionLoaderTimeoutWorkItem?.cancel()
2568
+ let workItem = DispatchWorkItem { [weak self] in
2569
+ self?.hidePreviewTransitionLoader(reason: "preview-transition-timeout")
2570
+ }
2571
+ self.previewTransitionLoaderTimeoutWorkItem = workItem
2572
+ DispatchQueue.main.asyncAfter(
2573
+ deadline: .now() + .milliseconds(Self.previewLoaderTimeoutMs),
2574
+ execute: workItem
2575
+ )
2576
+ }
2577
+
2432
2578
  private func scheduleSplashscreenTimeout() {
2433
2579
  guard self.autoSplashscreenTimeout > 0 else {
2434
2580
  return
@@ -2431,7 +2431,7 @@ import UIKit
2431
2431
  if let channels = responseValue.channels {
2432
2432
  listChannels.channels = channels.map { channel in
2433
2433
  var channelDict: [String: Any] = [:]
2434
- channelDict["id"] = channel.id ?? ""
2434
+ channelDict["id"] = channel.id
2435
2435
  channelDict["name"] = channel.name ?? ""
2436
2436
  channelDict["public"] = channel.public ?? false
2437
2437
  channelDict["allow_self_set"] = channel.allow_self_set ?? false
@@ -70,7 +70,7 @@ extension GetChannel {
70
70
  }
71
71
  // swiftlint:disable identifier_name
72
72
  struct ChannelInfo: Codable {
73
- let id: String?
73
+ let id: Int
74
74
  let name: String?
75
75
  let `public`: Bool?
76
76
  let allow_self_set: Bool?
@@ -33,17 +33,20 @@ extension UIWindow {
33
33
  return
34
34
  }
35
35
 
36
- // Check if shake menu is enabled
37
- if !plugin.shakeMenuEnabled {
36
+ let canShowPreviewMenu = plugin.shakeMenuEnabled && plugin.hasActivePreviewSession()
37
+ let canShowChannelSelector = plugin.shakeChannelSelectorEnabled
38
+
39
+ if !canShowPreviewMenu && !canShowChannelSelector {
40
+ if plugin.shakeMenuEnabled {
41
+ plugin.logger.info("Shake preview menu ignored because no preview session is active")
42
+ }
38
43
  return
39
44
  }
40
45
 
41
- if plugin.hasActivePreviewSession() {
46
+ if canShowPreviewMenu {
42
47
  showDefaultMenu(plugin: plugin, bridge: bridge)
43
- } else if plugin.shakeChannelSelectorEnabled {
44
- showChannelSelector(plugin: plugin, bridge: bridge)
45
48
  } else {
46
- showDefaultMenu(plugin: plugin, bridge: bridge)
49
+ showChannelSelector(plugin: plugin, bridge: bridge)
47
50
  }
48
51
  }
49
52
  }
@@ -56,8 +59,8 @@ extension UIWindow {
56
59
  return
57
60
  }
58
61
 
59
- if !plugin.hasActivePreviewSession() {
60
- showConfiguredDefaultMenu(plugin: plugin, bridge: bridge)
62
+ guard plugin.hasActivePreviewSession() else {
63
+ plugin.logger.info("Shake preview menu ignored because no preview session is active")
61
64
  return
62
65
  }
63
66
 
@@ -90,6 +93,20 @@ extension UIWindow {
90
93
  }
91
94
  })
92
95
 
96
+ if plugin.shakeChannelSelectorEnabled {
97
+ alertShake.addAction(UIAlertAction(title: "Switch channel", style: .default) { _ in
98
+ let showSelector = {
99
+ self.showChannelSelector(plugin: plugin, bridge: bridge)
100
+ }
101
+
102
+ if let presenter = alertShake.presentingViewController {
103
+ presenter.dismiss(animated: true, completion: showSelector)
104
+ } else {
105
+ DispatchQueue.main.async(execute: showSelector)
106
+ }
107
+ })
108
+ }
109
+
93
110
  alertShake.addAction(UIAlertAction(title: cancelButtonTitle, style: .default))
94
111
 
95
112
  DispatchQueue.main.async {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/capacitor-updater",
3
- "version": "8.47.6",
3
+ "version": "8.47.8",
4
4
  "license": "MPL-2.0",
5
5
  "description": "Live update for capacitor apps",
6
6
  "main": "dist/plugin.cjs.js",