Mac: use typed GatewayFrame + forward-compatible Swift generator

This commit is contained in:
Peter Steinberger
2025-12-09 15:26:31 +01:00
parent f244aba03d
commit a7737912b0
5 changed files with 83 additions and 47 deletions

View File

@@ -1,5 +1,6 @@
import Foundation
import OSLog
import ClawdisProtocol
struct GatewayEvent: Codable {
let type: String
@@ -17,7 +18,7 @@ extension Notification.Name {
private actor GatewayChannelActor {
private let logger = Logger(subsystem: "com.steipete.clawdis", category: "gateway")
private var task: URLSessionWebSocketTask?
private var pending: [String: CheckedContinuation<Data, Error>] = [:]
private var pending: [String: CheckedContinuation<GatewayFrame, Error>] = [:]
private var connected = false
private var url: URL
private var token: String?
@@ -25,6 +26,8 @@ private actor GatewayChannelActor {
private var backoffMs: Double = 500
private var shouldReconnect = true
private var lastSeq: Int?
private let decoder = JSONDecoder()
private let encoder = JSONEncoder()
init(url: URL, token: String?) {
self.url = url
@@ -90,8 +93,10 @@ private actor GatewayChannelActor {
case let .failure(err):
Task { await self.handleReceiveFailure(err) }
case let .success(msg):
Task { await self.handle(msg) }
self.listen()
Task {
await self.handle(msg)
await self.listen()
}
}
}
}
@@ -109,26 +114,28 @@ private actor GatewayChannelActor {
@unknown default: nil
}
guard let data else { return }
guard let obj = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
let type = obj["type"] as? String else { return }
switch type {
case "res":
if let id = obj["id"] as? String, let waiter = pending.removeValue(forKey: id) {
waiter.resume(returning: data)
guard let frame = try? self.decoder.decode(GatewayFrame.self, from: data) else {
self.logger.error("gateway decode failed")
return
}
switch frame {
case let .res(res):
if let id = res.id, let waiter = pending.removeValue(forKey: id) {
waiter.resume(returning: .res(res))
}
case "event":
if let seq = obj["seq"] as? Int {
case let .event(evt):
if let seq = evt.seq {
if let last = lastSeq, seq > last + 1 {
NotificationCenter.default.post(
name: .gatewaySeqGap,
object: nil,
object: frame,
userInfo: ["expected": last + 1, "received": seq])
}
self.lastSeq = seq
}
NotificationCenter.default.post(name: .gatewayEvent, object: nil, userInfo: obj)
case "hello-ok":
NotificationCenter.default.post(name: .gatewaySnapshot, object: nil, userInfo: obj)
NotificationCenter.default.post(name: .gatewayEvent, object: frame)
case .helloOk:
NotificationCenter.default.post(name: .gatewaySnapshot, object: frame)
default:
break
}
@@ -160,7 +167,7 @@ private actor GatewayChannelActor {
"params": paramsObject,
]
let data = try JSONSerialization.data(withJSONObject: frame)
let response = try await withCheckedThrowingContinuation { (cont: CheckedContinuation<Data, Error>) in
let response = try await withCheckedThrowingContinuation { (cont: CheckedContinuation<GatewayFrame, Error>) in
self.pending[id] = cont
Task {
do {
@@ -171,7 +178,18 @@ private actor GatewayChannelActor {
}
}
}
return response
guard case let .res(res) = response else {
throw NSError(domain: "Gateway", code: 2, userInfo: [NSLocalizedDescriptionKey: "unexpected frame"])
}
if res.ok == false {
let msg = (res.error?.message) ?? "gateway error"
throw NSError(domain: "Gateway", code: 3, userInfo: [NSLocalizedDescriptionKey: msg])
}
if let payload = res.payload?.value {
let payloadData = try JSONSerialization.data(withJSONObject: payload)
return payloadData
}
return Data()
}
}