Files
openclaw/scripts/e2e/lib/gateway-network/ws-frames.mts
T
Peter Steinberger c70aee247e refactor(scripts): migrate JavaScript tools to TypeScript (#121005)
* refactor(scripts): migrate JavaScript tools to TypeScript

* fix(ci): keep changed-scope preflight zero-install

* fix(ci): preserve zero-install script owners

* fix(ci): complete script migration follow-through

* fix(release): keep stable closeout zero-install

* fix(scripts): preserve standalone execution boundaries

* fix(scripts): repair standalone loader boundaries

* fix(scripts): normalize gateway observation ids

* fix(scripts): keep Docker packager standalone

* test(scripts): preserve rebase cleanup helpers

* test(sessions): use tracked temp directory
2026-08-09 07:21:35 -07:00

95 lines
2.8 KiB
TypeScript

// WebSocket frame helpers for gateway network E2E fixtures.
type FrameSocket = {
off?: (event: string, listener: (...args: unknown[]) => void) => unknown;
on: (event: "message", listener: (data: unknown) => void) => unknown;
once: {
(event: "error", listener: (error: unknown) => void): unknown;
(event: "close", listener: (code: unknown, reason: unknown) => void): unknown;
};
};
function isFrameSocket(value: unknown): value is FrameSocket {
return (
typeof value === "object" &&
value !== null &&
"on" in value &&
typeof value.on === "function" &&
"once" in value &&
typeof value.once === "function"
);
}
function formatCloseValue(value: unknown) {
if (value === undefined || value === null) {
return "";
}
if (typeof value === "string") {
return value;
}
if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
return value.toString();
}
if (value instanceof Uint8Array) {
return Buffer.from(value).toString();
}
return JSON.stringify(value) ?? "";
}
export function onceFrame(
ws: unknown,
filter: (message: Record<string, unknown>) => boolean,
timeoutMs = 10_000,
): Promise<Record<string, unknown>> {
if (!isFrameSocket(ws)) {
return Promise.reject(new Error("websocket frame source does not expose event handlers"));
}
return new Promise<Record<string, unknown>>((resolve, reject) => {
let settled = false;
const cleanup = () => {
clearTimeout(timer);
ws.off?.("message", onMessage);
ws.off?.("error", onError);
ws.off?.("close", onClose);
};
const settle = <Value,>(fn: (value: Value) => void, value: Value) => {
if (settled) {
return;
}
settled = true;
cleanup();
fn(value);
};
const onMessage = (data: unknown) => {
let obj: Record<string, unknown>;
try {
obj = JSON.parse(String(data));
if (!filter(obj)) {
return;
}
} catch (error) {
settle(reject, error instanceof Error ? error : new Error(String(error)));
return;
}
settle(resolve, obj);
};
const onError = (error: unknown) =>
settle(reject, error instanceof Error ? error : new Error(String(error)));
const onClose = (code: unknown, reason: unknown) => {
const closeDetails = [formatCloseValue(code), formatCloseValue(reason)]
.filter(Boolean)
.join(" ");
const suffix = closeDetails ? `: ${closeDetails}` : "";
settle(reject, new Error(`closed before frame${suffix}`));
};
const timer = setTimeout(() => {
settle(reject, new Error("timeout"));
}, timeoutMs);
timer.unref?.();
ws.on("message", onMessage);
ws.once("error", onError);
ws.once("close", onClose);
});
}