@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.
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdaterPlugin.java +314 -36
- package/android/src/main/java/ee/forgr/capacitor_updater/CapgoUpdater.java +278 -260
- package/android/src/main/java/ee/forgr/capacitor_updater/DownloadService.java +23 -26
- package/android/src/main/java/ee/forgr/capacitor_updater/ShakeMenu.java +45 -52
- package/ios/Sources/CapacitorUpdaterPlugin/CapacitorUpdaterPlugin.swift +317 -13
- package/ios/Sources/CapacitorUpdaterPlugin/ShakeMenu.swift +1 -1
- package/package.json +1 -1
|
@@ -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.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
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
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: "
|
|
3054
|
+
msg: "Direct update reload failed, update will install next background",
|
|
2761
3055
|
latestVersionName: latestVersionName,
|
|
2762
|
-
current:
|
|
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: "
|
|
32
|
+
let plugin = bridge.plugin(withName: "CapacitorUpdater") as? CapacitorUpdaterPlugin else {
|
|
33
33
|
return
|
|
34
34
|
}
|
|
35
35
|
|