diff --git a/NucleicRemote/NucleicRemote/LiveActivityManager.swift b/NucleicRemote/NucleicRemote/LiveActivityManager.swift index dbe03f6..0f12bf4 100644 --- a/NucleicRemote/NucleicRemote/LiveActivityManager.swift +++ b/NucleicRemote/NucleicRemote/LiveActivityManager.swift @@ -90,17 +90,34 @@ final class LiveActivityManager { // recovered one still needs the fresh state, so fall through to the update pipeline. if let existing = Activity.activities.first { activity = existing + observePushToken(existing) enqueue(state) } else { - activity = try? Activity.request( + // `pushType: .token` opts the activity into APNs updates — the Macs push new + // content-state to the token so the glance stays fresh while the phone is locked. + let started = try? Activity.request( attributes: NucleicSessionAttributes(hostName: hostName), - content: ActivityContent(state: state, staleDate: nil)) + content: ActivityContent(state: state, staleDate: nil), + pushType: .token) + activity = started + if let started { observePushToken(started) } } return } enqueue(state) } + /// Forward the activity's APNS update token (and its rotations) to `RemoteStore`. + private func observePushToken(_ activity: Activity) { + tokenObservation?.cancel() + tokenObservation = Task { [weak self] in + for await tokenData in activity.pushTokenUpdates { + let hex = tokenData.map { String(format: "%02x", $0) }.joined() + self?.onPushToken?(hex, activity.id) + } + } + } + /// Hand the newest state to the serial drainer (starting it if idle). private func enqueue(_ state: NucleicSessionAttributes.ContentState) { pendingState = state