refactor(server): split daemon client transport helpers into module
Reasoning:\n- daemon-client.ts had transport construction, ws adapter, relay e2ee wrapper, encoding helpers, and core client behavior mixed in one class file.\n- extracted transport/encoding concerns to a dedicated module to reduce logic density and tighten boundaries while preserving runtime behavior.\n\nAccomplishments:\n- added packages/server/src/client/daemon-client-transport.ts with websocket transport factory, relay e2ee transport wrapper, and transport utility helpers.\n- kept daemon-client public transport type exports stable by re-exporting types from the new module.\n- removed duplicated helper implementations from daemon-client.ts and replaced them with imports.\n- added focused tests in packages/server/src/client/daemon-client-transport.test.ts for websocket adapter behavior and helper normalization/encoding paths.\n- validated with @getpaseo/server typecheck and targeted daemon-client test suite.\n\nChallenges / handoff notes for next agent:\n- daemon-client.ts is still large and retains agent config resolution + high-level RPC orchestration concerns; next safe split is request/response waiter coordination or terminal stream buffering into dedicated modules.\n- a pre-existing unrelated workspace modification remains in scripts/codex-refactor-loop.sh and was intentionally left untouched.
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
import { describe, expect, test, vi } from "vitest";
|
||||
import {
|
||||
createWebSocketTransportFactory,
|
||||
decodeMessageData,
|
||||
describeTransportClose,
|
||||
describeTransportError,
|
||||
encodeUtf8String,
|
||||
extractRelayMessageData,
|
||||
} from "./daemon-client-transport.js";
|
||||
|
||||
describe("daemon-client transport helpers", () => {
|
||||
test("createWebSocketTransportFactory forwards sends when socket is open", () => {
|
||||
const addEventListener = vi.fn();
|
||||
const removeEventListener = vi.fn();
|
||||
const send = vi.fn();
|
||||
const close = vi.fn();
|
||||
|
||||
const factory = createWebSocketTransportFactory(() => ({
|
||||
readyState: 1,
|
||||
send,
|
||||
close,
|
||||
binaryType: "blob",
|
||||
addEventListener,
|
||||
removeEventListener,
|
||||
}));
|
||||
|
||||
const transport = factory({ url: "ws://example.test" });
|
||||
transport.send("hello");
|
||||
transport.close(1000, "bye");
|
||||
|
||||
expect(send).toHaveBeenCalledWith("hello");
|
||||
expect(close).toHaveBeenCalledWith(1000, "bye");
|
||||
});
|
||||
|
||||
test("createWebSocketTransportFactory rejects sends when socket is not open", () => {
|
||||
const factory = createWebSocketTransportFactory(() => ({
|
||||
readyState: 3,
|
||||
send: vi.fn(),
|
||||
close: vi.fn(),
|
||||
addEventListener: vi.fn(),
|
||||
removeEventListener: vi.fn(),
|
||||
}));
|
||||
|
||||
const transport = factory({ url: "ws://example.test" });
|
||||
expect(() => transport.send("hello")).toThrow("WebSocket not open (readyState=3)");
|
||||
});
|
||||
|
||||
test("createWebSocketTransportFactory binds and unbinds event listeners", () => {
|
||||
const listeners = new Map<string, (...args: any[]) => void>();
|
||||
const ws = {
|
||||
readyState: 1,
|
||||
send: vi.fn(),
|
||||
close: vi.fn(),
|
||||
addEventListener: vi.fn((event: string, handler: (...args: any[]) => void) => {
|
||||
listeners.set(event, handler);
|
||||
}),
|
||||
removeEventListener: vi.fn((event: string) => {
|
||||
listeners.delete(event);
|
||||
}),
|
||||
};
|
||||
|
||||
const transport = createWebSocketTransportFactory(() => ws)({ url: "ws://example.test" });
|
||||
|
||||
const onMessage = vi.fn();
|
||||
const unsubscribe = transport.onMessage(onMessage);
|
||||
|
||||
const message = { data: "payload" };
|
||||
listeners.get("message")?.(message);
|
||||
expect(onMessage).toHaveBeenCalledWith(message);
|
||||
|
||||
unsubscribe();
|
||||
expect(ws.removeEventListener).toHaveBeenCalledWith("message", expect.any(Function));
|
||||
});
|
||||
|
||||
test("describeTransportClose prefers reason, then message, then code", () => {
|
||||
expect(describeTransportClose({ reason: "peer closed" })).toBe("peer closed");
|
||||
expect(describeTransportClose({ message: "closed" })).toBe("closed");
|
||||
expect(describeTransportClose({ code: 1001 })).toBe("Transport closed (code 1001)");
|
||||
expect(describeTransportClose()).toBe("Transport closed");
|
||||
});
|
||||
|
||||
test("describeTransportError returns normalized messages", () => {
|
||||
expect(describeTransportError(new Error("boom"))).toBe("boom");
|
||||
expect(describeTransportError({ message: "bad frame" })).toBe("bad frame");
|
||||
expect(describeTransportError()).toBe("Transport error");
|
||||
});
|
||||
|
||||
test("extractRelayMessageData returns strings and array buffers", () => {
|
||||
expect(extractRelayMessageData({ data: "hello" })).toBe("hello");
|
||||
|
||||
const view = new Uint8Array([1, 2, 3]);
|
||||
const extracted = extractRelayMessageData({ data: view });
|
||||
expect(extracted).toBeInstanceOf(ArrayBuffer);
|
||||
expect(Array.from(new Uint8Array(extracted as ArrayBuffer))).toEqual([1, 2, 3]);
|
||||
});
|
||||
|
||||
test("decodeMessageData decodes strings, array buffers, and typed arrays", () => {
|
||||
const bytes = encodeUtf8String("hello");
|
||||
expect(decodeMessageData("hello")).toBe("hello");
|
||||
expect(decodeMessageData(bytes.buffer)).toBe("hello");
|
||||
expect(decodeMessageData(bytes)).toBe("hello");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user