gateway: enforce hello order and modern json

main
Peter Steinberger 2025-12-09 19:09:06 +00:00
parent c41b506741
commit ab9b12e883
2 changed files with 18 additions and 18 deletions

View File

@ -202,16 +202,16 @@ private actor GatewayChannelActor {
throw self.wrap(error, context: "gateway connect") throw self.wrap(error, context: "gateway connect")
} }
let id = UUID().uuidString let id = UUID().uuidString
let paramsObject = params?.reduce(into: [String: Any]()) { dict, entry in // Encode request using the generated models to avoid JSONSerialization/ObjC bridging pitfalls.
dict[entry.key] = entry.value.value let paramsObject = params?.reduce(into: [String: AnyCodable]()) { dict, entry in
} ?? [:] dict[entry.key] = entry.value
let frame: [String: Any] = [ }
"type": "req", let frame = RequestFrame(
"id": id, type: "req",
"method": method, id: id,
"params": paramsObject, method: method,
] params: paramsObject.map { AnyCodable($0) })
let data = try JSONSerialization.data(withJSONObject: frame) let data = try self.encoder.encode(frame)
let response = try await withCheckedThrowingContinuation { (cont: CheckedContinuation<GatewayFrame, Error>) in let response = try await withCheckedThrowingContinuation { (cont: CheckedContinuation<GatewayFrame, Error>) in
self.pending[id] = cont self.pending[id] = cont
Task { Task {
@ -230,13 +230,11 @@ private actor GatewayChannelActor {
let msg = (res.error?["message"]?.value as? String) ?? "gateway error" let msg = (res.error?["message"]?.value as? String) ?? "gateway error"
throw NSError(domain: "Gateway", code: 3, userInfo: [NSLocalizedDescriptionKey: msg]) throw NSError(domain: "Gateway", code: 3, userInfo: [NSLocalizedDescriptionKey: msg])
} }
if let payload = res.payload?.value { if let payload = res.payload {
if JSONSerialization.isValidJSONObject(payload) { // Encode back to JSON with Swift's encoder to preserve types and avoid ObjC bridging exceptions.
let payloadData = try JSONSerialization.data(withJSONObject: payload) return try self.encoder.encode(payload)
return payloadData
}
} }
return Data() return Data() // Should not happen, but tolerate empty payloads.
} }
// Wrap low-level URLSession/WebSocket errors with context so UI can surface them. // Wrap low-level URLSession/WebSocket errors with context so UI can surface them.

View File

@ -312,8 +312,6 @@ export async function startGatewayServer(port = 18789): Promise<GatewayServer> {
return; return;
} }
client = { socket, hello, connId };
clients.add(client);
// synthesize presence entry for this connection (client fingerprint) // synthesize presence entry for this connection (client fingerprint)
const presenceKey = hello.client.instanceId || connId; const presenceKey = hello.client.instanceId || connId;
const remoteAddr = ( const remoteAddr = (
@ -354,7 +352,11 @@ export async function startGatewayServer(port = 18789): Promise<GatewayServer> {
}, },
}; };
clearTimeout(handshakeTimer); 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); send(helloOk);
clients.add(client);
return; return;
} }