@capgo/capacitor-updater 8.47.4 → 8.47.6

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.4"
82
+ private let pluginVersion: String = "8.47.6"
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,8 @@ 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
168
+ private var previewTransitionClearWorkItem: DispatchWorkItem?
161
169
  let semaphoreReady = DispatchSemaphore(value: 0)
162
170
 
163
171
  private var delayUpdateUtils: DelayUpdateUtils!
@@ -233,6 +241,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
233
241
  previewSessionEnabled = allowPreview && storedPreviewSessionEnabled
234
242
  implementation.previewSession = previewSessionEnabled
235
243
  if previewSessionEnabled {
244
+ previewSessionAlertPending = UserDefaults.standard.object(forKey: previewSessionAlertPendingDefaultsKey) as? Bool ?? true
236
245
  shakeMenuEnabled = true
237
246
  shakeChannelSelectorEnabled = false
238
247
  }
@@ -315,6 +324,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
315
324
  if nativeBuildVersionChanged {
316
325
  self.clearPreviewSessionForNativeBuildChange()
317
326
  }
327
+ self.leavePreviewSessionForLaunchURLIfNeeded()
318
328
 
319
329
  if resetWhenUpdate {
320
330
  let didResetCurrentBundle = self.resetCurrentBundleForNativeBuildChangeIfNeeded()
@@ -332,11 +342,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
332
342
  logger.error("unable to force reload, the plugin might fallback to the builtin version")
333
343
  }
334
344
 
335
- let nc = NotificationCenter.default
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)
345
+ self.registerNotificationObservers()
340
346
 
341
347
  // Check for 'kill' delay condition on app launch
342
348
  // This handles cases where the app was killed (willTerminateNotification is not reliable for system kills)
@@ -344,6 +350,47 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
344
350
 
345
351
  self.appMovedToForeground()
346
352
  self.checkForUpdateAfterDelay()
353
+ self.showPreviewSessionNoticeIfNeeded()
354
+ }
355
+
356
+ private func registerNotificationObservers() {
357
+ let notificationCenter = NotificationCenter.default
358
+ notificationCenter.addObserver(
359
+ self,
360
+ selector: #selector(appMovedToBackground),
361
+ name: UIApplication.didEnterBackgroundNotification,
362
+ object: nil
363
+ )
364
+ notificationCenter.addObserver(
365
+ self,
366
+ selector: #selector(appMovedToForeground),
367
+ name: UIApplication.willEnterForegroundNotification,
368
+ object: nil
369
+ )
370
+ notificationCenter.addObserver(
371
+ self,
372
+ selector: #selector(appWillTerminate),
373
+ name: UIApplication.willTerminateNotification,
374
+ object: nil
375
+ )
376
+ notificationCenter.addObserver(
377
+ self,
378
+ selector: #selector(appDidReceiveMemoryWarning),
379
+ name: UIApplication.didReceiveMemoryWarningNotification,
380
+ object: nil
381
+ )
382
+ notificationCenter.addObserver(
383
+ self,
384
+ selector: #selector(handleOpenURLForPreviewSession(notification:)),
385
+ name: Notification.Name.capacitorOpenURL,
386
+ object: nil
387
+ )
388
+ notificationCenter.addObserver(
389
+ self,
390
+ selector: #selector(handleOpenURLForPreviewSession(notification:)),
391
+ name: Notification.Name.capacitorOpenUniversalLink,
392
+ object: nil
393
+ )
347
394
  }
348
395
 
349
396
  private func syncKeepUrlPathFlag(enabled: Bool) {
@@ -940,7 +987,10 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
940
987
  let current: BundleInfo = self.implementation.getCurrentBundle()
941
988
  let next: BundleInfo? = self.implementation.getNextBundle()
942
989
 
943
- if let next = next, !next.isErrorStatus(), next.getId() != current.getId() {
990
+ if !self.isPreviewSessionStateActive(),
991
+ let next = next,
992
+ !next.isErrorStatus(),
993
+ next.getId() != current.getId() {
944
994
  let previousState = self.implementation.captureResetState()
945
995
  let previousBundleName = self.implementation.getCurrentBundle().getVersionName()
946
996
  logger.info("Applying pending bundle before reload: \(next.toString())")
@@ -979,6 +1029,28 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
979
1029
  }
980
1030
  }
981
1031
 
1032
+ private func applyDownloadedBundleForDirectUpdate(_ next: BundleInfo) -> Bool {
1033
+ let previousState = self.implementation.captureResetState()
1034
+ let previousBundleName = self.implementation.getCurrentBundle().getVersionName()
1035
+
1036
+ guard self.implementation.stagePendingReload(bundle: next) else {
1037
+ self.implementation.restoreResetState(previousState)
1038
+ logger.error("Direct update failed to stage downloaded bundle: \(next.toString())")
1039
+ return false
1040
+ }
1041
+
1042
+ if self._reload() {
1043
+ self.implementation.finalizePendingReload(bundle: next, previousBundleName: previousBundleName)
1044
+ _ = self.implementation.setNextBundle(next: Optional<String>.none)
1045
+ return true
1046
+ }
1047
+
1048
+ self.implementation.restoreResetState(previousState)
1049
+ self.restoreLiveBundleStateAfterFailedReload()
1050
+ logger.error("Direct update reload failed after staging bundle: \(next.toString())")
1051
+ return false
1052
+ }
1053
+
982
1054
  @objc func next(_ call: CAPPluginCall) {
983
1055
  guard let id = call.getString("id") else {
984
1056
  logger.error("Next called without id")
@@ -1014,6 +1086,37 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
1014
1086
  }
1015
1087
  }
1016
1088
 
1089
+ private func isPreviewSessionStateActive() -> Bool {
1090
+ self.previewSessionEnabled || self.isLeavingPreviewForIncomingLink || self.implementation.previewSession
1091
+ }
1092
+
1093
+ private func shouldBlockAutoUpdateForPreviewSession() -> Bool {
1094
+ guard self.isPreviewSessionStateActive() else {
1095
+ return false
1096
+ }
1097
+
1098
+ logger.info("Preview session is active. Skipping normal auto-update work.")
1099
+ return true
1100
+ }
1101
+
1102
+ private func clearIncomingPreviewTransition() {
1103
+ self.previewTransitionClearWorkItem?.cancel()
1104
+ self.previewTransitionClearWorkItem = nil
1105
+ self.isLeavingPreviewForIncomingLink = false
1106
+ if !self.previewSessionEnabled {
1107
+ self.implementation.previewSession = false
1108
+ }
1109
+ }
1110
+
1111
+ private func scheduleIncomingPreviewTransitionFallbackClear() {
1112
+ self.previewTransitionClearWorkItem?.cancel()
1113
+ let workItem = DispatchWorkItem { [weak self] in
1114
+ self?.clearIncomingPreviewTransition()
1115
+ }
1116
+ self.previewTransitionClearWorkItem = workItem
1117
+ DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(self.appReadyTimeout), execute: workItem)
1118
+ }
1119
+
1017
1120
  @objc func startPreviewSession(_ call: CAPPluginCall) {
1018
1121
  guard self.allowPreview else {
1019
1122
  logger.error("startPreviewSession called without allowPreview")
@@ -1071,12 +1174,14 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
1071
1174
  UserDefaults.standard.removeObject(forKey: self.previewPayloadUrlDefaultsKey)
1072
1175
  }
1073
1176
 
1177
+ self.clearIncomingPreviewTransition()
1074
1178
  self.previewSessionEnabled = true
1075
1179
  self.previewSessionAlertPending = true
1076
1180
  self.implementation.previewSession = true
1077
1181
  self.shakeMenuEnabled = true
1078
1182
  self.shakeChannelSelectorEnabled = false
1079
1183
  UserDefaults.standard.set(true, forKey: self.previewSessionDefaultsKey)
1184
+ UserDefaults.standard.set(true, forKey: self.previewSessionAlertPendingDefaultsKey)
1080
1185
  UserDefaults.standard.synchronize()
1081
1186
  call.resolve()
1082
1187
  }
@@ -1092,12 +1197,95 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
1092
1197
  let previewFallbackBundle = self.implementation.getPreviewFallbackBundle()
1093
1198
  self.endPreviewSession()
1094
1199
  let restoredNextBundle = self.implementation.getNextBundle()
1200
+ self.deletePreviewBundleIfUnused(previewBundle, previewFallbackBundle: previewFallbackBundle, restoredNextBundle: restoredNextBundle)
1201
+ return true
1202
+ }
1203
+
1204
+ private func leavePreviewSessionForLaunchURLIfNeeded() {
1205
+ guard self.previewSessionEnabled,
1206
+ !self.isLeavingPreviewForIncomingLink,
1207
+ let launchUrl = ApplicationDelegateProxy.shared.lastURL,
1208
+ self.isPreviewDeepLink(launchUrl) else {
1209
+ return
1210
+ }
1211
+
1212
+ self.isLeavingPreviewForIncomingLink = true
1213
+ logger.info("Preview deeplink launch detected while preview session is active; restoring fallback before initial load")
1214
+ if !self.leavePreviewSessionWithoutReload() {
1215
+ logger.error("Could not leave preview session before initial preview deeplink routing")
1216
+ self.isLeavingPreviewForIncomingLink = false
1217
+ }
1218
+ }
1219
+
1220
+ private func leavePreviewSessionWithoutReload(keepPreviewGuard: Bool = false) -> Bool {
1221
+ let previewBundle = self.implementation.getCurrentBundle()
1222
+ guard let previewFallbackBundle = self.implementation.getPreviewFallbackBundle(), !previewFallbackBundle.isErrorStatus() else {
1223
+ logger.error("No preview fallback bundle available")
1224
+ return false
1225
+ }
1226
+ guard self.implementation.canSet(bundle: previewFallbackBundle) else {
1227
+ logger.error("Preview fallback bundle is not installable")
1228
+ return false
1229
+ }
1230
+ guard self.implementation.stagePreviewFallbackReload(bundle: previewFallbackBundle) else {
1231
+ logger.error("Could not stage preview fallback bundle")
1232
+ return false
1233
+ }
1234
+
1235
+ self.endPreviewSession(keepPreviewGuard: keepPreviewGuard)
1236
+ let restoredNextBundle = self.implementation.getNextBundle()
1237
+ self.deletePreviewBundleIfUnused(previewBundle, previewFallbackBundle: previewFallbackBundle, restoredNextBundle: restoredNextBundle)
1238
+ return true
1239
+ }
1240
+
1241
+ private func leavePreviewSessionForIncomingPreviewLink() -> Bool {
1242
+ let previewBundle = self.implementation.getCurrentBundle()
1243
+ guard let previewFallbackBundle = self.implementation.getPreviewFallbackBundle(), !previewFallbackBundle.isErrorStatus() else {
1244
+ logger.error("No preview fallback bundle available")
1245
+ self.clearIncomingPreviewTransition()
1246
+ return false
1247
+ }
1248
+ guard self.implementation.canSet(bundle: previewFallbackBundle) else {
1249
+ logger.error("Preview fallback bundle is not installable")
1250
+ self.clearIncomingPreviewTransition()
1251
+ return false
1252
+ }
1253
+
1254
+ let previousState = self.implementation.captureResetState()
1255
+ guard self.implementation.stagePreviewFallbackReload(bundle: previewFallbackBundle) else {
1256
+ logger.error("Could not stage preview fallback bundle")
1257
+ self.clearIncomingPreviewTransition()
1258
+ return false
1259
+ }
1260
+
1261
+ let didReload = self._reload()
1262
+ if didReload {
1263
+ self.endPreviewSession(keepPreviewGuard: true)
1264
+ let restoredNextBundle = self.implementation.getNextBundle()
1265
+ self.deletePreviewBundleIfUnused(
1266
+ previewBundle,
1267
+ previewFallbackBundle: previewFallbackBundle,
1268
+ restoredNextBundle: restoredNextBundle
1269
+ )
1270
+ self.scheduleIncomingPreviewTransitionFallbackClear()
1271
+ } else {
1272
+ self.implementation.restoreResetState(previousState)
1273
+ self.restoreLiveBundleStateAfterFailedReload()
1274
+ self.clearIncomingPreviewTransition()
1275
+ }
1276
+ return didReload
1277
+ }
1278
+
1279
+ private func deletePreviewBundleIfUnused(
1280
+ _ previewBundle: BundleInfo,
1281
+ previewFallbackBundle: BundleInfo?,
1282
+ restoredNextBundle: BundleInfo?
1283
+ ) {
1095
1284
  if !previewBundle.isBuiltin() &&
1096
1285
  previewFallbackBundle?.getId() != previewBundle.getId() &&
1097
1286
  restoredNextBundle?.getId() != previewBundle.getId() {
1098
1287
  _ = self.implementation.delete(id: previewBundle.getId(), removeInfo: false)
1099
1288
  }
1100
- return true
1101
1289
  }
1102
1290
 
1103
1291
  func reloadPreviewSessionFromShakeMenu() -> Bool {
@@ -1136,7 +1324,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
1136
1324
  return false
1137
1325
  }
1138
1326
 
1139
- private func endPreviewSession() {
1327
+ private func endPreviewSession(keepPreviewGuard: Bool = false) {
1140
1328
  let previousShakeMenuEnabled = UserDefaults.standard.object(forKey: self.previewPreviousShakeMenuDefaultsKey) as? Bool
1141
1329
  ?? getConfig().getBoolean("shakeMenu", false)
1142
1330
  let previousShakeChannelSelectorEnabled = UserDefaults.standard.object(forKey: self.previewPreviousShakeChannelSelectorDefaultsKey) as? Bool
@@ -1147,7 +1335,11 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
1147
1335
 
1148
1336
  self.previewSessionEnabled = false
1149
1337
  self.previewSessionAlertPending = false
1150
- self.implementation.previewSession = false
1338
+ if keepPreviewGuard {
1339
+ self.implementation.previewSession = true
1340
+ } else {
1341
+ self.clearIncomingPreviewTransition()
1342
+ }
1151
1343
  self.shakeMenuEnabled = previousShakeMenuEnabled
1152
1344
  self.shakeChannelSelectorEnabled = previousShakeChannelSelectorEnabled
1153
1345
  _ = self.implementation.setPreviewFallbackBundle(fallback: nil)
@@ -1176,6 +1368,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
1176
1368
  self.restorePreviewPreviousDefaultChannel()
1177
1369
  self.previewSessionEnabled = false
1178
1370
  self.previewSessionAlertPending = false
1371
+ self.isLeavingPreviewForIncomingLink = false
1179
1372
  self.implementation.previewSession = false
1180
1373
  self.shakeMenuEnabled = getConfig().getBoolean("shakeMenu", false)
1181
1374
  self.shakeChannelSelectorEnabled = getConfig().getBoolean("allowShakeChannelSelector", false)
@@ -1193,6 +1386,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
1193
1386
  UserDefaults.standard.removeObject(forKey: self.previewPreviousDefaultChannelWasSetDefaultsKey)
1194
1387
  UserDefaults.standard.removeObject(forKey: self.previewAppIdDefaultsKey)
1195
1388
  UserDefaults.standard.removeObject(forKey: self.previewPayloadUrlDefaultsKey)
1389
+ UserDefaults.standard.removeObject(forKey: self.previewSessionAlertPendingDefaultsKey)
1196
1390
  UserDefaults.standard.synchronize()
1197
1391
  }
1198
1392
 
@@ -1259,6 +1453,55 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
1259
1453
  normalizedPreviewPayloadUrl(UserDefaults.standard.string(forKey: self.previewPayloadUrlDefaultsKey))
1260
1454
  }
1261
1455
 
1456
+ private func previewPath(from url: URL) -> String {
1457
+ if url.scheme == self.previewDeepLinkScheme {
1458
+ var components: [String] = []
1459
+ if let host = url.host, !host.isEmpty {
1460
+ components.append(host)
1461
+ }
1462
+ components.append(contentsOf: url.path.split(separator: self.previewPathSeparator).map(String.init))
1463
+ return self.normalizedPreviewPath(components)
1464
+ }
1465
+
1466
+ return url.path
1467
+ }
1468
+
1469
+ private func normalizedPreviewPath(_ components: [String]) -> String {
1470
+ let separator = String(self.previewPathSeparator)
1471
+ return separator + components.filter { !$0.isEmpty }.joined(separator: separator)
1472
+ }
1473
+
1474
+ private func previewDeepLinkPath(_ leafComponent: String) -> String {
1475
+ self.normalizedPreviewPath([self.previewDeepLinkRootComponent, leafComponent])
1476
+ }
1477
+
1478
+ private func isPreviewDeepLink(_ url: URL) -> Bool {
1479
+ let path = self.previewPath(from: url)
1480
+ return path == self.previewDeepLinkPath(self.previewDeepLinkChannelComponent) ||
1481
+ path == self.previewDeepLinkPath(self.previewDeepLinkBundleComponent)
1482
+ }
1483
+
1484
+ @objc private func handleOpenURLForPreviewSession(notification: NSNotification) {
1485
+ let rawUrl = (notification.object as? [String: Any])?["url"]
1486
+ let url = rawUrl as? URL ?? (rawUrl as? NSURL).map { $0 as URL }
1487
+ guard self.previewSessionEnabled,
1488
+ !self.isLeavingPreviewForIncomingLink,
1489
+ let url,
1490
+ self.isPreviewDeepLink(url) else {
1491
+ return
1492
+ }
1493
+
1494
+ self.isLeavingPreviewForIncomingLink = true
1495
+ logger.info("Preview deeplink received while preview session is active; restoring fallback before routing")
1496
+ DispatchQueue.global(qos: .userInitiated).async {
1497
+ let didLeave = self.leavePreviewSessionForIncomingPreviewLink()
1498
+ if !didLeave {
1499
+ self.logger.error("Could not leave preview session before routing incoming preview deeplink")
1500
+ self.isLeavingPreviewForIncomingLink = false
1501
+ }
1502
+ }
1503
+ }
1504
+
1262
1505
  private func fetchPreviewPayload(_ payloadUrl: URL) throws -> PreviewPayload {
1263
1506
  var request = URLRequest(url: payloadUrl)
1264
1507
  request.setValue("application/json", forHTTPHeaderField: "Accept")
@@ -1340,6 +1583,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
1340
1583
  logger.info("Native build changed; clearing preview session state")
1341
1584
  self.previewSessionEnabled = false
1342
1585
  self.previewSessionAlertPending = false
1586
+ self.isLeavingPreviewForIncomingLink = false
1343
1587
  self.implementation.previewSession = false
1344
1588
  self.shakeMenuEnabled = getConfig().getBoolean("shakeMenu", false)
1345
1589
  self.shakeChannelSelectorEnabled = getConfig().getBoolean("allowShakeChannelSelector", false)
@@ -1367,6 +1611,8 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
1367
1611
  return
1368
1612
  }
1369
1613
  self.previewSessionAlertPending = false
1614
+ UserDefaults.standard.set(false, forKey: self.previewSessionAlertPendingDefaultsKey)
1615
+ UserDefaults.standard.synchronize()
1370
1616
 
1371
1617
  DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(600)) {
1372
1618
  guard self.previewSessionEnabled else {
@@ -1375,6 +1621,8 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
1375
1621
  if let topVC = UIApplication.topViewController(),
1376
1622
  topVC.isKind(of: UIAlertController.self) {
1377
1623
  self.previewSessionAlertPending = true
1624
+ UserDefaults.standard.set(true, forKey: self.previewSessionAlertPendingDefaultsKey)
1625
+ UserDefaults.standard.synchronize()
1378
1626
  return
1379
1627
  }
1380
1628
 
@@ -1386,6 +1634,10 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
1386
1634
  alert.addAction(UIAlertAction(title: "Got it", style: .default))
1387
1635
  if let topVC = UIApplication.topViewController() {
1388
1636
  topVC.present(alert, animated: true)
1637
+ } else {
1638
+ self.previewSessionAlertPending = true
1639
+ UserDefaults.standard.set(true, forKey: self.previewSessionAlertPendingDefaultsKey)
1640
+ UserDefaults.standard.synchronize()
1389
1641
  }
1390
1642
  }
1391
1643
  }
@@ -1548,6 +1800,9 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
1548
1800
  logger.error("Error no url or wrong format")
1549
1801
  return "unavailable"
1550
1802
  }
1803
+ if self.shouldBlockAutoUpdateForPreviewSession() {
1804
+ return "preview_session"
1805
+ }
1551
1806
  if self.isDownloadStuckOrTimedOut() {
1552
1807
  logger.info("Download already in progress, skipping duplicate download request")
1553
1808
  return "already_running"
@@ -1784,6 +2039,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
1784
2039
  let bundle = self.implementation.getCurrentBundle()
1785
2040
  self.implementation.setSuccess(bundle: bundle, autoDeletePrevious: self.autoDeletePrevious)
1786
2041
  logger.info("Current bundle loaded successfully. [notifyAppReady was called] \(bundle.toString())")
2042
+ self.clearIncomingPreviewTransition()
1787
2043
 
1788
2044
  call.resolve(["bundle": bundle.toJSON()])
1789
2045
  }
@@ -1834,6 +2090,9 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
1834
2090
  // Note: _checkCancelDelay method has been moved to DelayUpdateUtils class
1835
2091
 
1836
2092
  private func _isAutoUpdateEnabled() -> Bool {
2093
+ if self.isPreviewSessionStateActive() {
2094
+ return false
2095
+ }
1837
2096
  let instanceDescriptor = (self.bridge?.viewController as? CAPBridgeViewController)?.instanceDescriptor()
1838
2097
  if instanceDescriptor?.serverURL != nil {
1839
2098
  logger.warn("AutoUpdate is automatic disabled when serverUrl is set.")
@@ -1871,6 +2130,10 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
1871
2130
  logger.info("Built-in bundle is active. We skip the check for notifyAppReady.")
1872
2131
  return
1873
2132
  }
2133
+ if self.isPreviewSessionStateActive() {
2134
+ logger.info("Preview session is active. We skip the check for notifyAppReady.")
2135
+ return
2136
+ }
1874
2137
 
1875
2138
  logger.info("Current bundle is: \(current.toString())")
1876
2139
 
@@ -2540,6 +2803,13 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
2540
2803
  return true
2541
2804
  }
2542
2805
 
2806
+ private func clearDownloadInProgressState() {
2807
+ downloadLock.lock()
2808
+ defer { downloadLock.unlock() }
2809
+ downloadInProgress = false
2810
+ downloadStartTime = nil
2811
+ }
2812
+
2543
2813
  func runBackgroundDownloadWork(_ work: @escaping () -> Void) {
2544
2814
  // Live update checks/downloads are user-visible work. Using `.background`
2545
2815
  // lets the scheduler starve them for minutes while the app is active.
@@ -2565,6 +2835,9 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
2565
2835
  }
2566
2836
 
2567
2837
  func backgroundDownload() {
2838
+ if self.shouldBlockAutoUpdateForPreviewSession() {
2839
+ return
2840
+ }
2568
2841
  // Set download in progress flag (thread-safe)
2569
2842
  downloadLock.lock()
2570
2843
  downloadInProgress = true
@@ -2593,10 +2866,19 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
2593
2866
  self.runBackgroundDownloadWork {
2594
2867
  // Wait for cleanup to complete before starting download
2595
2868
  self.waitForCleanupIfNeeded()
2869
+ if self.shouldBlockAutoUpdateForPreviewSession() {
2870
+ self.clearDownloadInProgressState()
2871
+ return
2872
+ }
2596
2873
  self.beginDownloadBackgroundTask()
2597
2874
  self.logger.info("Check for update via \(self.updateUrl)")
2598
2875
  let res = self.implementation.getLatest(url: url, channel: nil)
2599
2876
  let current = self.implementation.getCurrentBundle()
2877
+ if self.shouldBlockAutoUpdateForPreviewSession() {
2878
+ self.clearDownloadInProgressState()
2879
+ self.endBackGroundTask()
2880
+ return
2881
+ }
2600
2882
 
2601
2883
  // Handle network errors and other failures first
2602
2884
  let backendError = res.error ?? ""
@@ -2708,6 +2990,11 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
2708
2990
  )
2709
2991
  return
2710
2992
  }
2993
+ if self.shouldBlockAutoUpdateForPreviewSession() {
2994
+ self.clearDownloadInProgressState()
2995
+ self.endBackGroundTask()
2996
+ return
2997
+ }
2711
2998
  res.checksum = try CryptoCipher.decryptChecksum(checksum: res.checksum, publicKey: self.implementation.publicKey)
2712
2999
  CryptoCipher.logChecksumInfo(label: "Bundle checksum", hexChecksum: next.getChecksum())
2713
3000
  CryptoCipher.logChecksumInfo(label: "Expected checksum", hexChecksum: res.checksum)
@@ -2727,6 +3014,11 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
2727
3014
  )
2728
3015
  return
2729
3016
  }
3017
+ if self.shouldBlockAutoUpdateForPreviewSession() {
3018
+ self.clearDownloadInProgressState()
3019
+ self.endBackGroundTask()
3020
+ return
3021
+ }
2730
3022
  let directUpdateAllowed = plannedDirectUpdate && !self.autoSplashscreenTimedOut
2731
3023
  if directUpdateAllowed {
2732
3024
  let delayUpdatePreferences = UserDefaults.standard.string(forKey: DelayUpdateUtils.DELAY_CONDITION_PREFERENCES) ?? "[]"
@@ -2746,7 +3038,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
2746
3038
  )
2747
3039
  return
2748
3040
  }
2749
- if self.implementation.set(bundle: next) && self._reload() {
3041
+ if self.applyDownloadedBundleForDirectUpdate(next) {
2750
3042
  self.notifyBundleSet(next)
2751
3043
  self.endBackGroundTaskWithNotif(
2752
3044
  msg: "update installed",
@@ -2756,10 +3048,13 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
2756
3048
  plannedDirectUpdate: plannedDirectUpdate
2757
3049
  )
2758
3050
  } else {
3051
+ _ = self.implementation.setNextBundle(next: next.getId())
3052
+ self.notifyListeners("updateAvailable", data: ["bundle": next.toJSON()])
2759
3053
  self.endBackGroundTaskWithNotif(
2760
- msg: "Update install failed",
3054
+ msg: "Direct update reload failed, update will install next background",
2761
3055
  latestVersionName: latestVersionName,
2762
- current: next,
3056
+ current: self.implementation.getCurrentBundle(),
3057
+ error: false,
2763
3058
  plannedDirectUpdate: plannedDirectUpdate
2764
3059
  )
2765
3060
  }
@@ -2815,6 +3110,9 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
2815
3110
  }
2816
3111
 
2817
3112
  private func installNext() {
3113
+ if self.shouldBlockAutoUpdateForPreviewSession() {
3114
+ return
3115
+ }
2818
3116
  let delayUpdatePreferences = UserDefaults.standard.string(forKey: DelayUpdateUtils.DELAY_CONDITION_PREFERENCES) ?? "[]"
2819
3117
  let delayConditionList: [DelayCondition] = fromJsonArr(json: delayUpdatePreferences).map { obj -> DelayCondition in
2820
3118
  let kind: String = obj.value(forKey: "kind") as! String
@@ -2906,8 +3204,14 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
2906
3204
  return
2907
3205
  }
2908
3206
  DispatchQueue.global(qos: .utility).async {
3207
+ if self.shouldBlockAutoUpdateForPreviewSession() {
3208
+ return
3209
+ }
2909
3210
  let res = self.implementation.getLatest(url: url, channel: nil)
2910
3211
  let current = self.implementation.getCurrentBundle()
3212
+ if self.shouldBlockAutoUpdateForPreviewSession() {
3213
+ return
3214
+ }
2911
3215
 
2912
3216
  if res.version != current.getVersionName() {
2913
3217
  self.logger.info("New version found: \(res.version)")
@@ -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: "CapacitorUpdaterPlugin") as? CapacitorUpdaterPlugin else {
32
+ let plugin = bridge.plugin(withName: "CapacitorUpdater") as? CapacitorUpdaterPlugin else {
33
33
  return
34
34
  }
35
35
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/capacitor-updater",
3
- "version": "8.47.4",
3
+ "version": "8.47.6",
4
4
  "license": "MPL-2.0",
5
5
  "description": "Live update for capacitor apps",
6
6
  "main": "dist/plugin.cjs.js",