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() {
|
||||
connectivity = .connected(.lan)
|
||||
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.
|
||||
}
|
||||
|
||||
/// 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
|
||||
/// 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
|
||||
@@ -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
|
||||
/// until the token is registered (or the background window closes), so the process isn't
|
||||
/// 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.
|
||||
func application(
|
||||
_ application: UIApplication,
|
||||
@@ -68,6 +71,14 @@ final class PushAppDelegate: NSObject, UIApplicationDelegate {
|
||||
}
|
||||
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 {
|
||||
// Fire-and-forget on the main actor (NotificationRouter is @MainActor); the removal is a
|
||||
// quick UNUserNotificationCenter call, so we can report .noData right away.
|
||||
|
||||
Reference in New Issue
Block a user