mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
e2e: fix helper imports and ws cleanup
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import { EventEmitter } from "node:events";
|
||||
import { describe, expect, test, vi } from "vitest";
|
||||
import {
|
||||
createEncryptedTransport,
|
||||
@@ -114,6 +115,22 @@ describe("daemon-client transport helpers", () => {
|
||||
expect(ws.removeEventListener).toHaveBeenCalledWith("message", expect.any(Function));
|
||||
});
|
||||
|
||||
test("createWebSocketTransportFactory suppresses close-before-open ws errors", () => {
|
||||
class MockNodeWebSocket extends EventEmitter {
|
||||
readyState = 0;
|
||||
send = vi.fn();
|
||||
close = vi.fn((code?: number, reason?: string) => {
|
||||
this.emit("error", new Error("WebSocket was closed before the connection was established"));
|
||||
this.emit("close", { code, reason });
|
||||
});
|
||||
}
|
||||
|
||||
const ws = new MockNodeWebSocket();
|
||||
const transport = createWebSocketTransportFactory(() => ws)({ url: "ws://example.test" });
|
||||
|
||||
expect(() => transport.close(1001, "Connection timed out")).not.toThrow();
|
||||
});
|
||||
|
||||
test("describeTransportClose prefers reason, then message, then code", () => {
|
||||
expect(describeTransportClose({ reason: "peer closed" })).toBe("peer closed");
|
||||
expect(describeTransportClose({ message: "closed" })).toBe("closed");
|
||||
|
||||
@@ -32,7 +32,19 @@ export function createWebSocketTransportFactory(factory: WebSocketFactory): Daem
|
||||
}
|
||||
ws.send(data);
|
||||
},
|
||||
close: (code?: number, reason?: string) => ws.close(code, reason),
|
||||
close: (code?: number, reason?: string) => {
|
||||
// Node's `ws` may emit an `error` when a connecting socket is closed before the
|
||||
// handshake completes. Keep a temporary no-op handler attached so cleanup during
|
||||
// connect timeouts does not crash the CLI with an unhandled error event.
|
||||
const suppressEarlyCloseError = bindTemporaryEarlyCloseErrorHandler(ws);
|
||||
try {
|
||||
ws.close(code, reason);
|
||||
} finally {
|
||||
if (typeof ws.on !== "function" && typeof ws.addEventListener !== "function") {
|
||||
suppressEarlyCloseError();
|
||||
}
|
||||
}
|
||||
},
|
||||
onOpen: (handler) => bindWsHandler(ws, "open", handler),
|
||||
onClose: (handler) => bindWsHandler(ws, "close", handler),
|
||||
onError: (handler) => bindWsHandler(ws, "error", handler),
|
||||
@@ -41,6 +53,52 @@ export function createWebSocketTransportFactory(factory: WebSocketFactory): Daem
|
||||
};
|
||||
}
|
||||
|
||||
function bindTemporaryEarlyCloseErrorHandler(ws: WebSocketLike): () => void {
|
||||
const noop = () => {};
|
||||
|
||||
if (typeof ws.addEventListener === "function") {
|
||||
ws.addEventListener("error", noop);
|
||||
const removeOnClose = bindWsHandler(ws, "close", () => {
|
||||
removeOnClose();
|
||||
if (typeof ws.removeEventListener === "function") {
|
||||
ws.removeEventListener("error", noop);
|
||||
}
|
||||
});
|
||||
return () => {
|
||||
removeOnClose();
|
||||
if (typeof ws.removeEventListener === "function") {
|
||||
ws.removeEventListener("error", noop);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (typeof ws.on === "function") {
|
||||
ws.on("error", noop);
|
||||
const removeOnClose = bindWsHandler(ws, "close", () => {
|
||||
removeOnClose();
|
||||
if (typeof ws.off === "function") {
|
||||
ws.off("error", noop);
|
||||
return;
|
||||
}
|
||||
if (typeof ws.removeListener === "function") {
|
||||
ws.removeListener("error", noop);
|
||||
}
|
||||
});
|
||||
return () => {
|
||||
removeOnClose();
|
||||
if (typeof ws.off === "function") {
|
||||
ws.off("error", noop);
|
||||
return;
|
||||
}
|
||||
if (typeof ws.removeListener === "function") {
|
||||
ws.removeListener("error", noop);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return () => {};
|
||||
}
|
||||
|
||||
export function bindWsHandler(
|
||||
ws: WebSocketLike,
|
||||
event: "open" | "close" | "error" | "message",
|
||||
|
||||
Reference in New Issue
Block a user