




















@@ -200,6 +200,8 @@ final class NodeAppModel {
200200private(set) var activeGatewayConnectConfig: GatewayConnectConfig?
201201202202private static let watchExecApprovalBridgeStateKey = "watch.execApproval.bridge.state.v1"
203+private static let backgroundAliveLastSuccessAtMsKey = "gateway.backgroundAlive.lastSuccessAtMs"
204+private static let backgroundAliveLastTriggerKey = "gateway.backgroundAlive.lastTrigger"
203205204206var cameraHUDText: String?
205207var cameraHUDKind: CameraHUDKind?
@@ -3142,32 +3144,39 @@ extension NodeAppModel {
31423144return handled
31433145}
314431463145-let result = await self.reconnectGatewaySessionsForSilentPushIfNeeded(wakeId: wakeId)
3147+let result = await self.performBackgroundAliveBeaconIfNeeded(
3148+ wakeId: wakeId,
3149+ trigger: .silentPush)
31463150let outcomeMessage =
31473151"Silent push outcome wakeId=\(wakeId) "
31483152+ "applied=\(result.applied) "
3153++ "handled=\(result.handled) "
31493154+ "reason=\(result.reason) "
31503155+ "durationMs=\(result.durationMs)"
31513156self.pushWakeLogger.info("\(outcomeMessage, privacy: .public)")
3152-return result.applied
3157+return result.handled
31533158}
3154315931553160func handleBackgroundRefreshWake(trigger: String = "bg_app_refresh") async -> Bool {
31563161let wakeId = Self.makePushWakeAttemptID()
3162+let normalizedTrigger = BackgroundAliveBeacon.normalizeTrigger(trigger)
31573163let receivedMessage =
31583164"Background refresh wake received wakeId=\(wakeId) "
3159-+ "trigger=\(trigger) "
3165++ "trigger=\(normalizedTrigger.rawValue) "
31603166+ "backgrounded=\(self.isBackgrounded) "
31613167+ "autoReconnect=\(self.gatewayAutoReconnectEnabled)"
31623168self.pushWakeLogger.info("\(receivedMessage, privacy: .public)")
3163-let result = await self.reconnectGatewaySessionsForSilentPushIfNeeded(wakeId: wakeId)
3169+let result = await self.performBackgroundAliveBeaconIfNeeded(
3170+ wakeId: wakeId,
3171+ trigger: normalizedTrigger)
31643172let outcomeMessage =
31653173"Background refresh wake outcome wakeId=\(wakeId) "
31663174+ "applied=\(result.applied) "
3175++ "handled=\(result.handled) "
31673176+ "reason=\(result.reason) "
31683177+ "durationMs=\(result.durationMs)"
31693178self.pushWakeLogger.info("\(outcomeMessage, privacy: .public)")
3170-return result.applied
3179+return result.handled
31713180}
3172318131733182func handleSignificantLocationWakeIfNeeded() async {
@@ -3196,10 +3205,13 @@ extension NodeAppModel {
31963205+ "backgrounded=\(self.isBackgrounded) "
31973206+ "autoReconnect=\(self.gatewayAutoReconnectEnabled)"
31983207self.locationWakeLogger.info("\(beginMessage, privacy: .public)")
3199-let result = await self.reconnectGatewaySessionsForSilentPushIfNeeded(wakeId: wakeId)
3208+let result = await self.performBackgroundAliveBeaconIfNeeded(
3209+ wakeId: wakeId,
3210+ trigger: .significantLocation)
32003211let triggerMessage =
32013212"Location wake trigger wakeId=\(wakeId) "
32023213+ "applied=\(result.applied) "
3214++ "handled=\(result.handled) "
32033215+ "reason=\(result.reason) "
32043216+ "durationMs=\(result.durationMs)"
32053217self.locationWakeLogger.info("\(triggerMessage, privacy: .public)")
@@ -3621,8 +3633,9 @@ extension NodeAppModel {
36213633return gatewayError.message.lowercased().contains("allow-always is unavailable")
36223634}
362336353624-private struct SilentPushWakeAttemptResult {
3636+private struct BackgroundAliveWakeAttemptResult {
36253637var applied: Bool
3638+var handled: Bool
36263639var reason: String
36273640var durationMs: Int
36283641}
@@ -3797,43 +3810,100 @@ extension NodeAppModel {
37973810return await self.waitForOperatorConnection(timeoutMs: timeoutMs, pollMs: 250)
37983811}
379938123800-private func reconnectGatewaySessionsForSilentPushIfNeeded(
3801- wakeId: String) async -> SilentPushWakeAttemptResult
3813+private func performBackgroundAliveBeaconIfNeeded(
3814+ wakeId: String,
3815+ trigger: BackgroundAliveBeacon.Trigger) async -> BackgroundAliveWakeAttemptResult
38023816{
38033817let startedAt = Date()
3804-let makeResult: (Bool, String) -> SilentPushWakeAttemptResult = { applied, reason in
3818+let makeResult: (Bool, Bool, String) -> BackgroundAliveWakeAttemptResult = { applied, handled, reason in
38053819let durationMs = Int(Date().timeIntervalSince(startedAt) * 1000)
3806-return SilentPushWakeAttemptResult(
3820+return BackgroundAliveWakeAttemptResult(
38073821 applied: applied,
3822+ handled: handled,
38083823 reason: reason,
38093824 durationMs: max(0, durationMs))
38103825}
3811382638123827guard self.isBackgrounded else {
38133828self.pushWakeLogger.info("Wake no-op wakeId=\(wakeId, privacy: .public): app not backgrounded")
3814-return makeResult(false, "not_backgrounded")
3829+return makeResult(false, false, "not_backgrounded")
38153830}
38163831guard self.gatewayAutoReconnectEnabled else {
38173832self.pushWakeLogger.info("Wake no-op wakeId=\(wakeId, privacy: .public): auto reconnect disabled")
3818-return makeResult(false, "auto_reconnect_disabled")
3833+return makeResult(false, false, "auto_reconnect_disabled")
38193834}
3820-guard let cfg = self.activeGatewayConnectConfig else {
3821-self.pushWakeLogger.info("Wake no-op wakeId=\(wakeId, privacy: .public): no active gateway config")
3822-return makeResult(false, "no_active_gateway_config")
3835+let now = Date()
3836+let gatewayConnected = await self.isGatewayConnected()
3837+3838+var appliedReconnect = false
3839+if !gatewayConnected {
3840+guard let cfg = self.activeGatewayConnectConfig else {
3841+self.pushWakeLogger.info("Wake no-op wakeId=\(wakeId, privacy: .public): no active gateway config")
3842+return makeResult(false, false, "no_active_gateway_config")
3843+}
3844+self.pushWakeLogger.info(
3845+"Wake reconnect begin wakeId=\(wakeId, privacy: .public) stableID=\(cfg.stableID, privacy: .public)")
3846+self.grantBackgroundReconnectLease(seconds: 30, reason: "wake_\(wakeId)")
3847+await self.operatorGateway.disconnect()
3848+await self.nodeGateway.disconnect()
3849+self.operatorConnected = false
3850+self.gatewayConnected = false
3851+self.gatewayStatusText = "Reconnecting…"
3852+self.talkMode.updateGatewayConnected(false)
3853+self.applyGatewayConnectConfig(cfg)
3854+ appliedReconnect = true
3855+self.pushWakeLogger.info("Wake reconnect trigger applied wakeId=\(wakeId, privacy: .public)")
3856+3857+let connected = await self.waitForGatewayConnection(timeoutMs: 12000, pollMs: 250)
3858+guard connected else {
3859+return makeResult(appliedReconnect, false, "connect_timeout")
3860+}
3861+} else if BackgroundAliveBeacon.shouldSkipRecentSuccess(
3862+ isGatewayConnected: true,
3863+ now: now,
3864+ lastSuccessAtMs: UserDefaults.standard.object(forKey: Self.backgroundAliveLastSuccessAtMsKey) as? Double)
3865+{
3866+return makeResult(false, true, "recent_success")
38233867}
382438683825-self.pushWakeLogger.info(
3826-"Wake reconnect begin wakeId=\(wakeId, privacy: .public) stableID=\(cfg.stableID, privacy: .public)")
3827-self.grantBackgroundReconnectLease(seconds: 30, reason: "wake_\(wakeId)")
3828-await self.operatorGateway.disconnect()
3829-await self.nodeGateway.disconnect()
3830-self.operatorConnected = false
3831-self.gatewayConnected = false
3832-self.gatewayStatusText = "Reconnecting…"
3833-self.talkMode.updateGatewayConnected(false)
3834-self.applyGatewayConnectConfig(cfg)
3835-self.pushWakeLogger.info("Wake reconnect trigger applied wakeId=\(wakeId, privacy: .public)")
3836-return makeResult(true, "reconnect_triggered")
3869+let beacon = await self.publishBackgroundAliveBeacon(trigger: trigger)
3870+if beacon.handled {
3871+let successAtMs = Date().timeIntervalSince1970 * 1000
3872+UserDefaults.standard.set(successAtMs, forKey: Self.backgroundAliveLastSuccessAtMsKey)
3873+UserDefaults.standard.set(trigger.rawValue, forKey: Self.backgroundAliveLastTriggerKey)
3874+return makeResult(appliedReconnect, true, beacon.reason)
3875+}
3876+return makeResult(appliedReconnect, false, beacon.reason)
3877+}
3878+3879+private func publishBackgroundAliveBeacon(
3880+ trigger: BackgroundAliveBeacon.Trigger) async -> (handled: Bool, reason: String)
3881+{
3882+do {
3883+let pushTransport = await self.pushRegistrationManager.usesRelayTransport ? "relay" : "direct"
3884+let displayName = NodeDisplayName.resolve(
3885+ existing: UserDefaults.standard.string(forKey: "node.displayName"),
3886+ deviceName: UIDevice.current.name,
3887+ interfaceIdiom: UIDevice.current.userInterfaceIdiom)
3888+let payload = BackgroundAliveBeacon.makePayload(
3889+ trigger: trigger,
3890+ displayName: displayName,
3891+ pushTransport: pushTransport)
3892+let paramsJSON = try BackgroundAliveBeacon.makeNodeEventRequestPayloadJSON(payload: payload)
3893+let response = try await self.nodeGateway.request(
3894+ method: "node.event",
3895+ paramsJSON: paramsJSON,
3896+ timeoutSeconds: 8)
3897+guard let decoded = BackgroundAliveBeacon.decodeResponse(response) else {
3898+return (false, "invalid_response")
3899+}
3900+if decoded.handled == true {
3901+return (true, decoded.reason ?? "beacon_persisted")
3902+}
3903+return (false, decoded.reason ?? "unsupported")
3904+} catch {
3905+return (false, "beacon_failed")
3906+}
38373907}
38383908}
38393909此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。