gateway: harden ws protocol and liveness

This commit is contained in:
Peter Steinberger
2025-12-09 17:02:58 +01:00
parent 20d247b3f7
commit 72eb240c3b
6 changed files with 108 additions and 42 deletions

View File

@@ -26,6 +26,8 @@ private actor GatewayChannelActor {
private var backoffMs: Double = 500
private var shouldReconnect = true
private var lastSeq: Int?
private var lastTick: Date?
private var tickIntervalMs: Double = 30_000
private let decoder = JSONDecoder()
private let encoder = JSONEncoder()
@@ -49,8 +51,8 @@ private actor GatewayChannelActor {
private func sendHello() async throws {
let hello: [String: Any] = [
"type": "hello",
"minProtocol": 1,
"maxProtocol": 1,
"minProtocol": GATEWAY_PROTOCOL_VERSION,
"maxProtocol": GATEWAY_PROTOCOL_VERSION,
"client": [
"name": "clawdis-mac",
"version": Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "dev",
@@ -80,6 +82,12 @@ private actor GatewayChannelActor {
guard let obj = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
let type = obj["type"] as? String else { return false }
if type == "hello-ok" {
if let policy = obj["policy"] as? [String: Any],
let tick = policy["tickIntervalMs"] as? Double {
self.tickIntervalMs = tick
}
self.lastTick = Date()
Task { await self.watchTicks() }
NotificationCenter.default.post(name: .gatewaySnapshot, object: nil, userInfo: obj)
return true
}
@@ -134,14 +142,33 @@ private actor GatewayChannelActor {
}
self.lastSeq = seq
}
if evt.event == "tick" { self.lastTick = Date() }
NotificationCenter.default.post(name: .gatewayEvent, object: frame)
case .helloOk:
self.lastTick = Date()
NotificationCenter.default.post(name: .gatewaySnapshot, object: frame)
default:
break
}
}
private func watchTicks() async {
let tolerance = self.tickIntervalMs * 2
while self.connected {
try? await Task.sleep(nanoseconds: UInt64(tolerance * 1_000_000))
guard self.connected else { return }
if let last = self.lastTick {
let delta = Date().timeIntervalSince(last) * 1000
if delta > tolerance {
self.logger.error("gateway tick missed; reconnecting")
self.connected = false
await self.scheduleReconnect()
return
}
}
}
}
private func scheduleReconnect() async {
guard self.shouldReconnect else { return }
let delay = self.backoffMs / 1000