mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-12 07:20:45 +00:00
iOS/Gateway: wake disconnected iOS nodes via APNs before invoke (#20332)
Merged via /review-pr -> /prepare-pr -> /merge-pr.
Prepared head SHA: 7751f9c531
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Reviewed-by: @mbelinky
This commit is contained in:
@@ -41,6 +41,7 @@ private final class NotificationInvokeLatch<T: Sendable>: @unchecked Sendable {
|
||||
@Observable
|
||||
final class NodeAppModel {
|
||||
private let deepLinkLogger = Logger(subsystem: "ai.openclaw.ios", category: "DeepLink")
|
||||
private let pushWakeLogger = Logger(subsystem: "ai.openclaw.ios", category: "PushWake")
|
||||
enum CameraHUDKind {
|
||||
case photo
|
||||
case recording
|
||||
@@ -2125,6 +2126,15 @@ extension NodeAppModel {
|
||||
await self.registerAPNsTokenIfNeeded()
|
||||
}
|
||||
|
||||
func handleSilentPushWake(_ userInfo: [AnyHashable: Any]) async -> Bool {
|
||||
guard Self.isSilentPushPayload(userInfo) else {
|
||||
self.pushWakeLogger.info("Ignored APNs payload: not silent push")
|
||||
return false
|
||||
}
|
||||
self.pushWakeLogger.info("Silent push received; attempting reconnect if needed")
|
||||
return await self.reconnectGatewaySessionsForSilentPushIfNeeded()
|
||||
}
|
||||
|
||||
func updateAPNsDeviceToken(_ tokenData: Data) {
|
||||
let tokenHex = tokenData.map { String(format: "%02x", $0) }.joined()
|
||||
let trimmed = tokenHex.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
@@ -2170,6 +2180,51 @@ extension NodeAppModel {
|
||||
// Best-effort only.
|
||||
}
|
||||
}
|
||||
|
||||
private static func isSilentPushPayload(_ userInfo: [AnyHashable: Any]) -> Bool {
|
||||
guard let apsAny = userInfo["aps"] else { return false }
|
||||
if let aps = apsAny as? [AnyHashable: Any] {
|
||||
return Self.hasContentAvailable(aps["content-available"])
|
||||
}
|
||||
if let aps = apsAny as? [String: Any] {
|
||||
return Self.hasContentAvailable(aps["content-available"])
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private static func hasContentAvailable(_ value: Any?) -> Bool {
|
||||
if let number = value as? NSNumber {
|
||||
return number.intValue == 1
|
||||
}
|
||||
if let text = value as? String {
|
||||
return text.trimmingCharacters(in: .whitespacesAndNewlines) == "1"
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private func reconnectGatewaySessionsForSilentPushIfNeeded() async -> Bool {
|
||||
guard self.isBackgrounded else {
|
||||
self.pushWakeLogger.info("Wake no-op: app not backgrounded")
|
||||
return false
|
||||
}
|
||||
guard self.gatewayAutoReconnectEnabled else {
|
||||
self.pushWakeLogger.info("Wake no-op: auto reconnect disabled")
|
||||
return false
|
||||
}
|
||||
guard self.activeGatewayConnectConfig != nil else {
|
||||
self.pushWakeLogger.info("Wake no-op: no active gateway config")
|
||||
return false
|
||||
}
|
||||
|
||||
await self.operatorGateway.disconnect()
|
||||
await self.nodeGateway.disconnect()
|
||||
self.operatorConnected = false
|
||||
self.gatewayConnected = false
|
||||
self.gatewayStatusText = "Reconnecting…"
|
||||
self.talkMode.updateGatewayConnected(false)
|
||||
self.pushWakeLogger.info("Wake reconnect trigger applied")
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
|
||||
Reference in New Issue
Block a user