refactor(server): split daemon client transport into focused modules
Why:\n- daemon-client-transport.ts had multiple unrelated concerns (runtime WebSocket adaptation, relay/e2ee orchestration, and message/ID utility helpers) in one dense file.\n- This made control flow harder to follow and increased coupling for future edits in server transport code.\n\nWhat changed:\n- Added transport type contracts in packages/server/src/client/daemon-client-transport-types.ts.\n- Moved websocket-specific factory/binding logic to packages/server/src/client/daemon-client-websocket-transport.ts.\n- Moved relay/e2ee handshake + encrypted transport orchestration to packages/server/src/client/daemon-client-relay-e2ee-transport.ts.\n- Moved payload normalization, close/error description, id generation, and encode/decode helpers to packages/server/src/client/daemon-client-transport-utils.ts.\n- Converted packages/server/src/client/daemon-client-transport.ts into a compatibility barrel that re-exports the existing API so daemon-client callsites remain unchanged.\n- Reduced nested payload conversion logic by introducing shared normalization helpers used by relay/e2ee paths.\n\nVerification:\n- npm run -w packages/server test -- src/client/daemon-client-transport.test.ts\n- npm run -w packages/server test -- src/client/daemon-client.test.ts\n- npm run typecheck\n\nNext-agent notes (accomplishments/challenges):\n- Accomplishment: transport responsibilities are now separated by concern, with no consumer API break in daemon-client.ts.\n- Accomplishment: existing transport and daemon-client tests pass unchanged after the split.\n- Challenge to watch: createEncryptedTransport remains stateful by design (opened/closed/channel lifecycle); future changes should keep this sequencing behavior identical and covered by deterministic tests.
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
import type {
|
||||
DaemonTransportFactory,
|
||||
WebSocketFactory,
|
||||
WebSocketLike,
|
||||
} from "./daemon-client-transport-types.js";
|
||||
|
||||
export function defaultWebSocketFactory(
|
||||
url: string,
|
||||
_options?: { headers?: Record<string, string> }
|
||||
): WebSocketLike {
|
||||
const globalWs = (globalThis as { WebSocket?: any }).WebSocket;
|
||||
if (!globalWs) {
|
||||
throw new Error("WebSocket is not available in this runtime");
|
||||
}
|
||||
return new globalWs(url);
|
||||
}
|
||||
|
||||
export function createWebSocketTransportFactory(
|
||||
factory: WebSocketFactory
|
||||
): DaemonTransportFactory {
|
||||
return ({ url, headers }) => {
|
||||
const ws = factory(url, { headers });
|
||||
if ("binaryType" in ws) {
|
||||
try {
|
||||
ws.binaryType = "arraybuffer";
|
||||
} catch {
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
return {
|
||||
send: (data) => {
|
||||
if (typeof ws.readyState === "number" && ws.readyState !== 1) {
|
||||
throw new Error(`WebSocket not open (readyState=${ws.readyState})`);
|
||||
}
|
||||
ws.send(data);
|
||||
},
|
||||
close: (code?: number, reason?: string) => ws.close(code, reason),
|
||||
onOpen: (handler) => bindWsHandler(ws, "open", handler),
|
||||
onClose: (handler) => bindWsHandler(ws, "close", handler),
|
||||
onError: (handler) => bindWsHandler(ws, "error", handler),
|
||||
onMessage: (handler) => bindWsHandler(ws, "message", handler),
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export function bindWsHandler(
|
||||
ws: WebSocketLike,
|
||||
event: "open" | "close" | "error" | "message",
|
||||
handler: (...args: any[]) => void
|
||||
): () => void {
|
||||
if (typeof ws.addEventListener === "function") {
|
||||
ws.addEventListener(event, handler);
|
||||
return () => {
|
||||
if (typeof ws.removeEventListener === "function") {
|
||||
ws.removeEventListener(event, handler);
|
||||
}
|
||||
};
|
||||
}
|
||||
if (typeof ws.on === "function") {
|
||||
ws.on(event, handler);
|
||||
return () => {
|
||||
if (typeof ws.off === "function") {
|
||||
ws.off(event, handler);
|
||||
return;
|
||||
}
|
||||
if (typeof ws.removeListener === "function") {
|
||||
ws.removeListener(event, handler);
|
||||
}
|
||||
};
|
||||
}
|
||||
const prop = `on${event}` as "onopen" | "onclose" | "onerror" | "onmessage";
|
||||
const previous = (ws as any)[prop];
|
||||
(ws as any)[prop] = handler;
|
||||
return () => {
|
||||
if ((ws as any)[prop] === handler) {
|
||||
(ws as any)[prop] = previous ?? null;
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user