Download apps/ios/WatchExtension/Sources/WatchInboxStore.swift from Darochin/openskynet: direct link, hf CLI and curl.
- Browser
- Download file 7.76 kB
-
https://huggingface.co/Darochin/openskynet/resolve/main/apps/ios/WatchExtension/Sources/WatchInboxStore.swift
- Command line
-
hf download hf://Darochin/openskynet/apps/ios/WatchExtension/Sources/WatchInboxStore.swift
-
curl -L -o WatchInboxStore.swift https://huggingface.co/Darochin/openskynet/resolve/main/apps/ios/WatchExtension/Sources/WatchInboxStore.swift
7.76 kB
| import Foundation | |
| import Observation | |
| import UserNotifications | |
| import WatchKit | |
| struct WatchPromptAction: Codable, Sendable, Equatable, Identifiable { | |
| var id: String | |
| var label: String | |
| var style: String? | |
| } | |
| struct WatchNotifyMessage: Sendable { | |
| var id: String? | |
| var title: String | |
| var body: String | |
| var sentAtMs: Int? | |
| var promptId: String? | |
| var sessionKey: String? | |
| var kind: String? | |
| var details: String? | |
| var expiresAtMs: Int? | |
| var risk: String? | |
| var actions: [WatchPromptAction] | |
| } | |
| final class WatchInboxStore { | |
| private struct PersistedState: Codable { | |
| var title: String | |
| var body: String | |
| var transport: String | |
| var updatedAt: Date | |
| var lastDeliveryKey: String? | |
| var promptId: String? | |
| var sessionKey: String? | |
| var kind: String? | |
| var details: String? | |
| var expiresAtMs: Int? | |
| var risk: String? | |
| var actions: [WatchPromptAction]? | |
| var replyStatusText: String? | |
| var replyStatusAt: Date? | |
| } | |
| private static let persistedStateKey = "watch.inbox.state.v1" | |
| private let defaults: UserDefaults | |
| var title = "OpenClaw" | |
| var body = "Waiting for messages from your iPhone." | |
| var transport = "none" | |
| var updatedAt: Date? | |
| var promptId: String? | |
| var sessionKey: String? | |
| var kind: String? | |
| var details: String? | |
| var expiresAtMs: Int? | |
| var risk: String? | |
| var actions: [WatchPromptAction] = [] | |
| var replyStatusText: String? | |
| var replyStatusAt: Date? | |
| var isReplySending = false | |
| private var lastDeliveryKey: String? | |
| init(defaults: UserDefaults = .standard) { | |
| self.defaults = defaults | |
| self.restorePersistedState() | |
| Task { | |
| await self.ensureNotificationAuthorization() | |
| } | |
| } | |
| func consume(message: WatchNotifyMessage, transport: String) { | |
| let messageID = message.id? | |
| .trimmingCharacters(in: .whitespacesAndNewlines) | |
| let deliveryKey = self.deliveryKey( | |
| messageID: messageID, | |
| title: message.title, | |
| body: message.body, | |
| sentAtMs: message.sentAtMs) | |
| guard deliveryKey != self.lastDeliveryKey else { return } | |
| let normalizedTitle = message.title.isEmpty ? "OpenClaw" : message.title | |
| self.title = normalizedTitle | |
| self.body = message.body | |
| self.transport = transport | |
| self.updatedAt = Date() | |
| self.promptId = message.promptId | |
| self.sessionKey = message.sessionKey | |
| self.kind = message.kind | |
| self.details = message.details | |
| self.expiresAtMs = message.expiresAtMs | |
| self.risk = message.risk | |
| self.actions = message.actions | |
| self.lastDeliveryKey = deliveryKey | |
| self.replyStatusText = nil | |
| self.replyStatusAt = nil | |
| self.isReplySending = false | |
| self.persistState() | |
| Task { | |
| await self.postLocalNotification( | |
| identifier: deliveryKey, | |
| title: normalizedTitle, | |
| body: message.body, | |
| risk: message.risk) | |
| } | |
| } | |
| private func restorePersistedState() { | |
| guard let data = self.defaults.data(forKey: Self.persistedStateKey), | |
| let state = try? JSONDecoder().decode(PersistedState.self, from: data) | |
| else { | |
| return | |
| } | |
| self.title = state.title | |
| self.body = state.body | |
| self.transport = state.transport | |
| self.updatedAt = state.updatedAt | |
| self.lastDeliveryKey = state.lastDeliveryKey | |
| self.promptId = state.promptId | |
| self.sessionKey = state.sessionKey | |
| self.kind = state.kind | |
| self.details = state.details | |
| self.expiresAtMs = state.expiresAtMs | |
| self.risk = state.risk | |
| self.actions = state.actions ?? [] | |
| self.replyStatusText = state.replyStatusText | |
| self.replyStatusAt = state.replyStatusAt | |
| } | |
| private func persistState() { | |
| guard let updatedAt = self.updatedAt else { return } | |
| let state = PersistedState( | |
| title: self.title, | |
| body: self.body, | |
| transport: self.transport, | |
| updatedAt: updatedAt, | |
| lastDeliveryKey: self.lastDeliveryKey, | |
| promptId: self.promptId, | |
| sessionKey: self.sessionKey, | |
| kind: self.kind, | |
| details: self.details, | |
| expiresAtMs: self.expiresAtMs, | |
| risk: self.risk, | |
| actions: self.actions, | |
| replyStatusText: self.replyStatusText, | |
| replyStatusAt: self.replyStatusAt) | |
| guard let data = try? JSONEncoder().encode(state) else { return } | |
| self.defaults.set(data, forKey: Self.persistedStateKey) | |
| } | |
| private func deliveryKey(messageID: String?, title: String, body: String, sentAtMs: Int?) -> String { | |
| if let messageID, messageID.isEmpty == false { | |
| return "id:\(messageID)" | |
| } | |
| return "content:\(title)|\(body)|\(sentAtMs ?? 0)" | |
| } | |
| private func ensureNotificationAuthorization() async { | |
| let center = UNUserNotificationCenter.current() | |
| let settings = await center.notificationSettings() | |
| switch settings.authorizationStatus { | |
| case .notDetermined: | |
| _ = try? await center.requestAuthorization(options: [.alert, .sound]) | |
| default: | |
| break | |
| } | |
| } | |
| private func mapHapticRisk(_ risk: String?) -> WKHapticType { | |
| switch risk?.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() { | |
| case "high": | |
| return .failure | |
| case "medium": | |
| return .notification | |
| default: | |
| return .click | |
| } | |
| } | |
| func makeReplyDraft(action: WatchPromptAction) -> WatchReplyDraft { | |
| let prompt = self.promptId?.trimmingCharacters(in: .whitespacesAndNewlines) | |
| return WatchReplyDraft( | |
| replyId: UUID().uuidString, | |
| promptId: (prompt?.isEmpty == false) ? prompt! : "unknown", | |
| actionId: action.id, | |
| actionLabel: action.label, | |
| sessionKey: self.sessionKey, | |
| note: nil, | |
| sentAtMs: Int(Date().timeIntervalSince1970 * 1000)) | |
| } | |
| func markReplySending(actionLabel: String) { | |
| self.isReplySending = true | |
| self.replyStatusText = "Sending \(actionLabel)…" | |
| self.replyStatusAt = Date() | |
| self.persistState() | |
| } | |
| func markReplyResult(_ result: WatchReplySendResult, actionLabel: String) { | |
| self.isReplySending = false | |
| if let errorMessage = result.errorMessage, !errorMessage.isEmpty { | |
| self.replyStatusText = "Failed: \(errorMessage)" | |
| } else if result.deliveredImmediately { | |
| self.replyStatusText = "\(actionLabel): sent" | |
| } else if result.queuedForDelivery { | |
| self.replyStatusText = "\(actionLabel): queued" | |
| } else { | |
| self.replyStatusText = "\(actionLabel): sent" | |
| } | |
| self.replyStatusAt = Date() | |
| self.persistState() | |
| } | |
| private func postLocalNotification(identifier: String, title: String, body: String, risk: String?) async { | |
| let content = UNMutableNotificationContent() | |
| content.title = title | |
| content.body = body | |
| content.sound = .default | |
| content.threadIdentifier = "openclaw-watch" | |
| let request = UNNotificationRequest( | |
| identifier: identifier, | |
| content: content, | |
| trigger: UNTimeIntervalNotificationTrigger(timeInterval: 0.2, repeats: false)) | |
| _ = try? await UNUserNotificationCenter.current().add(request) | |
| WKInterfaceDevice.current().play(self.mapHapticRisk(risk)) | |
| } | |
| } | |