Merge nucleic/lucid-slate-bison into dev
This commit is contained in:
@@ -595,6 +595,53 @@ final class RemoteStore: ObservableObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Handle the host's silent "reconnect" nudge (SYNC_PROTOCOL §3.2): the Mac noticed our LAN link
|
||||||
|
/// drop and is asking us to re-dial over a non-local transport before the connection silently
|
||||||
|
/// goes dark. Called from `PushAppDelegate` on a `content-available` background push, which may
|
||||||
|
/// relaunch the app with no scene, so this does the setup `onAppear` normally would. Re-running
|
||||||
|
/// `reconnect()` walks each host's LAN→tailnet→relay candidate loop — LAN just failed, so it
|
||||||
|
/// lands on tailnet or relay from wherever the phone now is. Holds a background-task assertion
|
||||||
|
/// until a host is live again or the window closes. Best-effort: iOS may deny background runtime
|
||||||
|
/// (budget, force-quit), in which case the reconnect simply happens on the next foreground.
|
||||||
|
func handleReconnectWake(completion: @escaping () -> Void) {
|
||||||
|
guard !demoMode, isPaired else { completion(); return }
|
||||||
|
// A silent wake means we're background (possibly cold-launched with no scene, where
|
||||||
|
// `isForeground`'s default would read stale-true); record that so the reconnect reports
|
||||||
|
// background and the host keeps owning the Live Activity via push-to-start (UX_IOS §5.3).
|
||||||
|
setForegroundState(false)
|
||||||
|
startNetworkingIfNeeded()
|
||||||
|
#if canImport(UIKit)
|
||||||
|
var bgTask: UIBackgroundTaskIdentifier = .invalid
|
||||||
|
var finished = false
|
||||||
|
let finish = {
|
||||||
|
guard !finished else { return }
|
||||||
|
finished = true
|
||||||
|
if bgTask != .invalid {
|
||||||
|
UIApplication.shared.endBackgroundTask(bgTask)
|
||||||
|
bgTask = .invalid
|
||||||
|
}
|
||||||
|
completion()
|
||||||
|
}
|
||||||
|
bgTask = UIApplication.shared.beginBackgroundTask(withName: "nucleic.sync.reconnect") {
|
||||||
|
finish() // the OS reclaimed the grant before we finished — report what we have
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
let finish = completion
|
||||||
|
#endif
|
||||||
|
pathMonitor.refresh()
|
||||||
|
reconnect()
|
||||||
|
// Poll until at least one host is live again (the dial loop found a working path), or we run
|
||||||
|
// out of background runtime.
|
||||||
|
Task { @MainActor [weak self] in
|
||||||
|
for _ in 0..<50 { // ~25s at a 0.5s cadence, inside iOS's ~30s background grant
|
||||||
|
guard let self, !self.connections.values.contains(where: { $0.connectivity.isLive })
|
||||||
|
else { break }
|
||||||
|
try? await Task.sleep(for: .milliseconds(500))
|
||||||
|
}
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func seedDemo() {
|
private func seedDemo() {
|
||||||
connectivity = .connected(.lan)
|
connectivity = .connected(.lan)
|
||||||
hostName = "Andrew's Mac"
|
hostName = "Andrew's Mac"
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ final class PushAppDelegate: NSObject, UIApplicationDelegate {
|
|||||||
// Best-effort: no token → the phone still works on LAN, just no remote wake.
|
// Best-effort: no token → the phone still works on LAN, just no remote wake.
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Silent pushes (content-available). Two kinds route here:
|
/// Silent pushes (content-available). Three kinds route here:
|
||||||
/// - `clear`: recall the "waiting for your approval" tickle once the approval is resolved on
|
/// - `clear`: recall the "waiting for your approval" tickle once the approval is resolved on
|
||||||
/// another device — wake briefly to withdraw the stale notification.
|
/// another device — wake briefly to withdraw the stale notification.
|
||||||
/// - `adopt`: a Mac push-started a Live Activity while the app was closed; come online long
|
/// - `adopt`: a Mac push-started a Live Activity while the app was closed; come online long
|
||||||
@@ -54,6 +54,9 @@ final class PushAppDelegate: NSObject, UIApplicationDelegate {
|
|||||||
/// resume without the user opening the app (UX_IOS §5.3). This one holds the completion handler
|
/// resume without the user opening the app (UX_IOS §5.3). This one holds the completion handler
|
||||||
/// until the token is registered (or the background window closes), so the process isn't
|
/// until the token is registered (or the background window closes), so the process isn't
|
||||||
/// suspended mid-connect.
|
/// suspended mid-connect.
|
||||||
|
/// - `reconnect`: the Mac noticed our LAN link drop and is nudging us to re-dial over a non-local
|
||||||
|
/// transport (tailnet/relay). Come online long enough to re-run the candidate loop so the
|
||||||
|
/// connection follows the phone off-LAN without the user opening the app (SYNC_PROTOCOL §3.2).
|
||||||
/// Other pushes (e.g. the approval wake tickle, which is an alert) don't route here.
|
/// Other pushes (e.g. the approval wake tickle, which is an alert) don't route here.
|
||||||
func application(
|
func application(
|
||||||
_ application: UIApplication,
|
_ application: UIApplication,
|
||||||
@@ -68,6 +71,14 @@ final class PushAppDelegate: NSObject, UIApplicationDelegate {
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if userInfo["reconnect"] != nil {
|
||||||
|
// The store holds its own background-task assertion while it re-dials; report .newData
|
||||||
|
// once a link is back up (or the background window closed).
|
||||||
|
Task { @MainActor in
|
||||||
|
RemoteStore.shared.handleReconnectWake { completionHandler(.newData) }
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
if userInfo["clear"] != nil {
|
if userInfo["clear"] != nil {
|
||||||
// Fire-and-forget on the main actor (NotificationRouter is @MainActor); the removal is a
|
// Fire-and-forget on the main actor (NotificationRouter is @MainActor); the removal is a
|
||||||
// quick UNUserNotificationCenter call, so we can report .noData right away.
|
// quick UNUserNotificationCenter call, so we can report .noData right away.
|
||||||
|
|||||||
Reference in New Issue
Block a user