From ab9b12e883ef4c5b0d4c04f884c455472ba25128 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 9 Dec 2025 19:09:06 +0000 Subject: [PATCH] gateway: enforce hello order and modern json --- .../Sources/Clawdis/GatewayChannel.swift | 30 +++++++++---------- src/gateway/server.ts | 6 ++-- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/apps/macos/Sources/Clawdis/GatewayChannel.swift b/apps/macos/Sources/Clawdis/GatewayChannel.swift index 2497a5c4f..815cd1736 100644 --- a/apps/macos/Sources/Clawdis/GatewayChannel.swift +++ b/apps/macos/Sources/Clawdis/GatewayChannel.swift @@ -202,16 +202,16 @@ private actor GatewayChannelActor { throw self.wrap(error, context: "gateway connect") } let id = UUID().uuidString - let paramsObject = params?.reduce(into: [String: Any]()) { dict, entry in - dict[entry.key] = entry.value.value - } ?? [:] - let frame: [String: Any] = [ - "type": "req", - "id": id, - "method": method, - "params": paramsObject, - ] - let data = try JSONSerialization.data(withJSONObject: frame) + // Encode request using the generated models to avoid JSONSerialization/ObjC bridging pitfalls. + let paramsObject = params?.reduce(into: [String: AnyCodable]()) { dict, entry in + dict[entry.key] = entry.value + } + let frame = RequestFrame( + type: "req", + id: id, + method: method, + params: paramsObject.map { AnyCodable($0) }) + let data = try self.encoder.encode(frame) let response = try await withCheckedThrowingContinuation { (cont: CheckedContinuation) in self.pending[id] = cont Task { @@ -230,13 +230,11 @@ private actor GatewayChannelActor { let msg = (res.error?["message"]?.value as? String) ?? "gateway error" throw NSError(domain: "Gateway", code: 3, userInfo: [NSLocalizedDescriptionKey: msg]) } - if let payload = res.payload?.value { - if JSONSerialization.isValidJSONObject(payload) { - let payloadData = try JSONSerialization.data(withJSONObject: payload) - return payloadData - } + if let payload = res.payload { + // Encode back to JSON with Swift's encoder to preserve types and avoid ObjC bridging exceptions. + return try self.encoder.encode(payload) } - return Data() + return Data() // Should not happen, but tolerate empty payloads. } // Wrap low-level URLSession/WebSocket errors with context so UI can surface them. diff --git a/src/gateway/server.ts b/src/gateway/server.ts index 7faa85c97..3daf495a2 100644 --- a/src/gateway/server.ts +++ b/src/gateway/server.ts @@ -312,8 +312,6 @@ export async function startGatewayServer(port = 18789): Promise { return; } - client = { socket, hello, connId }; - clients.add(client); // synthesize presence entry for this connection (client fingerprint) const presenceKey = hello.client.instanceId || connId; const remoteAddr = ( @@ -354,7 +352,11 @@ export async function startGatewayServer(port = 18789): Promise { }, }; clearTimeout(handshakeTimer); + // Add the client only after the hello response is ready so no tick/presence + // events reach it before the handshake completes. + client = { socket, hello, connId }; send(helloOk); + clients.add(client); return; }