diff --git a/NucleicRemote/NucleicRemote/PushRegistrar.swift b/NucleicRemote/NucleicRemote/PushRegistrar.swift new file mode 100644 index 0000000..e2b1b32 --- /dev/null +++ b/NucleicRemote/NucleicRemote/PushRegistrar.swift @@ -0,0 +1,48 @@ +import Foundation +import UIKit +import UserNotifications + +/// Captures this phone's APNS device token so it can ride along in the sync `Hello` (SYNC §6) and, +/// once the relay ships, let the host wake the phone for an approval (docs/CLOUD_INFRA.md §3). +/// +/// Best-effort: the token may be nil until APNS responds, in which case it's simply included on a +/// later (re)connect — the phone still works fully on LAN without it. +@MainActor +final class PushRegistrar { + static let shared = PushRegistrar() + private init() {} + + /// Hex-encoded APNS device token, or nil until registration completes. + private(set) var tokenHex: String? + + /// Surface the notifications prompt and, if granted, register for remote notifications. Safe to + /// call on every launch; the system de-dupes registration. + func requestAuthorizationAndRegister() { + UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { granted, _ in + guard granted else { return } + Task { @MainActor in UIApplication.shared.registerForRemoteNotifications() } + } + } + + func didRegister(deviceToken: Data) { + tokenHex = deviceToken.map { String(format: "%02x", $0) }.joined() + } +} + +/// SwiftUI needs a UIKit app delegate to receive the remote-registration callbacks; wired via +/// `@UIApplicationDelegateAdaptor` in `NucleicRemoteApp`. +final class PushAppDelegate: NSObject, UIApplicationDelegate { + func application( + _ application: UIApplication, + didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data + ) { + Task { @MainActor in PushRegistrar.shared.didRegister(deviceToken: deviceToken) } + } + + func application( + _ application: UIApplication, + didFailToRegisterForRemoteNotificationsWithError error: Error + ) { + // Best-effort: no token → the phone still works on LAN, just no remote wake. + } +}