mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-12 07:20:45 +00:00
refactor: extract ios watch reply coordinator
This commit is contained in:
@@ -129,8 +129,7 @@ final class NodeAppModel {
|
||||
private var backgroundReconnectSuppressed = false
|
||||
private var backgroundReconnectLeaseUntil: Date?
|
||||
private var lastSignificantLocationWakeAt: Date?
|
||||
private var queuedWatchReplies: [WatchQuickReplyEvent] = []
|
||||
private var seenWatchReplyIds = Set<String>()
|
||||
@ObservationIgnored private let watchReplyCoordinator = WatchReplyCoordinator()
|
||||
|
||||
private var gatewayConnected = false
|
||||
private var operatorConnected = false
|
||||
@@ -2199,37 +2198,22 @@ extension NodeAppModel {
|
||||
}
|
||||
|
||||
private func handleWatchQuickReply(_ event: WatchQuickReplyEvent) async {
|
||||
let replyId = event.replyId.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let actionId = event.actionId.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
if replyId.isEmpty || actionId.isEmpty {
|
||||
switch self.watchReplyCoordinator.ingest(event, isGatewayConnected: await self.isGatewayConnected()) {
|
||||
case .dropMissingFields:
|
||||
self.watchReplyLogger.info("watch reply dropped: missing replyId/actionId")
|
||||
return
|
||||
}
|
||||
|
||||
if self.seenWatchReplyIds.contains(replyId) {
|
||||
case .deduped(let replyId):
|
||||
self.watchReplyLogger.debug(
|
||||
"watch reply deduped replyId=\(replyId, privacy: .public)")
|
||||
return
|
||||
}
|
||||
self.seenWatchReplyIds.insert(replyId)
|
||||
|
||||
if await !self.isGatewayConnected() {
|
||||
self.queuedWatchReplies.append(event)
|
||||
case .queue(let replyId, let actionId):
|
||||
self.watchReplyLogger.info(
|
||||
"watch reply queued replyId=\(replyId, privacy: .public) action=\(actionId, privacy: .public)")
|
||||
return
|
||||
case .forward:
|
||||
await self.forwardWatchReplyToAgent(event)
|
||||
}
|
||||
|
||||
await self.forwardWatchReplyToAgent(event)
|
||||
}
|
||||
|
||||
private func flushQueuedWatchRepliesIfConnected() async {
|
||||
guard await self.isGatewayConnected() else { return }
|
||||
guard !self.queuedWatchReplies.isEmpty else { return }
|
||||
|
||||
let pending = self.queuedWatchReplies
|
||||
self.queuedWatchReplies.removeAll()
|
||||
for event in pending {
|
||||
for event in self.watchReplyCoordinator.drainIfConnected(await self.isGatewayConnected()) {
|
||||
await self.forwardWatchReplyToAgent(event)
|
||||
}
|
||||
}
|
||||
@@ -2259,7 +2243,7 @@ extension NodeAppModel {
|
||||
"watch reply forwarding failed replyId=\(event.replyId) "
|
||||
+ "error=\(error.localizedDescription)"
|
||||
self.watchReplyLogger.error("\(failedMessage, privacy: .public)")
|
||||
self.queuedWatchReplies.insert(event, at: 0)
|
||||
self.watchReplyCoordinator.requeueFront(event)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2852,7 +2836,7 @@ extension NodeAppModel {
|
||||
}
|
||||
|
||||
func _test_queuedWatchReplyCount() -> Int {
|
||||
self.queuedWatchReplies.count
|
||||
self.watchReplyCoordinator.queuedCount
|
||||
}
|
||||
|
||||
func _test_setGatewayConnected(_ connected: Bool) {
|
||||
|
||||
46
apps/ios/Sources/Model/WatchReplyCoordinator.swift
Normal file
46
apps/ios/Sources/Model/WatchReplyCoordinator.swift
Normal file
@@ -0,0 +1,46 @@
|
||||
import Foundation
|
||||
|
||||
@MainActor
|
||||
final class WatchReplyCoordinator {
|
||||
enum Decision {
|
||||
case dropMissingFields
|
||||
case deduped(replyId: String)
|
||||
case queue(replyId: String, actionId: String)
|
||||
case forward
|
||||
}
|
||||
|
||||
private var queuedReplies: [WatchQuickReplyEvent] = []
|
||||
private var seenReplyIds = Set<String>()
|
||||
|
||||
func ingest(_ event: WatchQuickReplyEvent, isGatewayConnected: Bool) -> Decision {
|
||||
let replyId = event.replyId.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let actionId = event.actionId.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
if replyId.isEmpty || actionId.isEmpty {
|
||||
return .dropMissingFields
|
||||
}
|
||||
if self.seenReplyIds.contains(replyId) {
|
||||
return .deduped(replyId: replyId)
|
||||
}
|
||||
self.seenReplyIds.insert(replyId)
|
||||
if !isGatewayConnected {
|
||||
self.queuedReplies.append(event)
|
||||
return .queue(replyId: replyId, actionId: actionId)
|
||||
}
|
||||
return .forward
|
||||
}
|
||||
|
||||
func drainIfConnected(_ isGatewayConnected: Bool) -> [WatchQuickReplyEvent] {
|
||||
guard isGatewayConnected, !self.queuedReplies.isEmpty else { return [] }
|
||||
let pending = self.queuedReplies
|
||||
self.queuedReplies.removeAll()
|
||||
return pending
|
||||
}
|
||||
|
||||
func requeueFront(_ event: WatchQuickReplyEvent) {
|
||||
self.queuedReplies.insert(event, at: 0)
|
||||
}
|
||||
|
||||
var queuedCount: Int {
|
||||
self.queuedReplies.count
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ Sources/OpenClawApp.swift
|
||||
Sources/Location/LocationService.swift
|
||||
Sources/Model/NodeAppModel.swift
|
||||
Sources/Model/NodeAppModel+Canvas.swift
|
||||
Sources/Model/WatchReplyCoordinator.swift
|
||||
Sources/RootCanvas.swift
|
||||
Sources/RootTabs.swift
|
||||
Sources/Screen/ScreenController.swift
|
||||
|
||||
Reference in New Issue
Block a user