import { deferred, normalizeApprovalDecision } from "./util.mjs"; export class ClientConnection { #send; #pending = new Map(); #counter = 0; #closed = false; constructor(send) { this.#send = send; } get closed() { return this.#closed; } send(message) { if (this.#closed) return false; this.#send(message); return true; } notify(method, params = {}) { return this.send({ method, params }); } async request(method, params = {}, { signal, timeoutMs = 10 * 60_000, onRequestId = null } = {}) { if (this.#closed) throw new Error("Client connection is closed"); const requestId = `omp:${process.pid}:${++this.#counter}`; onRequestId?.(requestId); const wait = deferred(); const timer = setTimeout(() => { this.#pending.delete(requestId); wait.reject(new Error(`Client request timed out: ${method}`)); }, timeoutMs); timer.unref?.(); const onAbort = () => { this.#pending.delete(requestId); clearTimeout(timer); wait.reject(signal.reason instanceof Error ? signal.reason : new Error("Client request aborted")); }; signal?.addEventListener("abort", onAbort, { once: true }); this.#pending.set(requestId, { method, resolve: (value) => { clearTimeout(timer); signal?.removeEventListener("abort", onAbort); wait.resolve(value); }, reject: (error) => { clearTimeout(timer); signal?.removeEventListener("abort", onAbort); wait.reject(error); }, }); this.send({ id: requestId, method, params }); return wait.promise; } async requestApproval(method, params, options) { const response = await this.request(method, params, options); return normalizeApprovalDecision(response); } acceptResponse(message) { if (message?.id === undefined) return false; const pending = this.#pending.get(message.id); if (!pending) return false; this.#pending.delete(message.id); if (message.error) { const error = new Error(message.error.message || `Client request failed: ${pending.method}`); error.code = message.error.code; error.data = message.error.data; pending.reject(error); } else { pending.resolve(message.result !== undefined ? message.result : (message.response !== undefined ? message.response : message)); } return true; } close(reason = new Error("Client disconnected")) { if (this.#closed) return; this.#closed = true; for (const pending of this.#pending.values()) pending.reject(reason); this.#pending.clear(); } }