mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-14 22:54:01 -06:00
c70aee247e
* 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
95 lines
2.8 KiB
TypeScript
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);
|
|
});
|
|
}
|