scripts / connection.mjs
// Shared by single commands and workflows. No external dependencies.
export function connect(webSocketUrl, { timeoutMs = 30000 } = {}) {
const socket = new WebSocket(webSocketUrl);
const pending = new Map();
let nextRequestId = 0;
let closed = false;
let resolveReady;
let rejectReady;
const ready = new Promise((resolve, reject) => { resolveReady = resolve; rejectReady = reject; });
// The connection may fail after readiness or before a caller starts awaiting it.
void ready.catch(() => {});
const fail = error => {
if (closed) return;
closed = true;
clearTimeout(timer);
rejectReady(error);
for (const request of pending.values()) request.reject(error);
pending.clear();
socket.close();
};
const timer = setTimeout(() => fail(new Error("CDP deadline exceeded.")), timeoutMs);
timer.unref();
socket.onopen = () => resolveReady();
socket.onerror = () => fail(new Error("CDP connection failed."));
socket.onclose = () => fail(new Error("CDP connection closed."));
socket.onmessage = event => {
let message;
try { message = JSON.parse(event.data); }
catch { fail(new Error("Invalid CDP response.")); return; }
const request = pending.get(message.id);
if (request === undefined) return;
pending.delete(message.id);
if (message.error !== undefined) request.reject(new Error(String(message.error.message ?? "CDP request failed.").slice(0, 500)));
else request.resolve(message.result);
};
return {
ready,
send(method, parameters = {}) {
if (closed) return Promise.reject(new Error("CDP connection closed."));
return new Promise((resolve, reject) => {
const requestId = ++nextRequestId;
pending.set(requestId, { resolve, reject });
try { socket.send(JSON.stringify({ id: requestId, method, params: parameters })); }
catch (error) { pending.delete(requestId); reject(error); }
});
},
close() { fail(new Error("CDP connection closed.")); },
};
}
export async function evaluate(connection, expression) {
const result = await connection.send("Runtime.evaluate", {
expression, returnByValue: true, awaitPromise: true, timeout: 5000,
});
if (result.exceptionDetails !== undefined) {
throw new Error(String(result.exceptionDetails.exception?.description ?? result.exceptionDetails.text ?? "Page evaluation failed.").slice(0, 500));
}
return result.result?.value;
}
scripts / connection.mjs
// Shared by single commands and workflows. No external dependencies.
export function connect(webSocketUrl, { timeoutMs = 30000 } = {}) {
const socket = new WebSocket(webSocketUrl);
const pending = new Map();
let nextRequestId = 0;
let closed = false;
let resolveReady;
let rejectReady;
const ready = new Promise((resolve, reject) => { resolveReady = resolve; rejectReady = reject; });
// The connection may fail after readiness or before a caller starts awaiting it.
void ready.catch(() => {});
const fail = error => {
if (closed) return;
closed = true;
clearTimeout(timer);
rejectReady(error);
for (const request of pending.values()) request.reject(error);
pending.clear();
socket.close();
};
const timer = setTimeout(() => fail(new Error("CDP deadline exceeded.")), timeoutMs);
timer.unref();
socket.onopen = () => resolveReady();
socket.onerror = () => fail(new Error("CDP connection failed."));
socket.onclose = () => fail(new Error("CDP connection closed."));
socket.onmessage = event => {
let message;
try { message = JSON.parse(event.data); }
catch { fail(new Error("Invalid CDP response.")); return; }
const request = pending.get(message.id);
if (request === undefined) return;
pending.delete(message.id);
if (message.error !== undefined) request.reject(new Error(String(message.error.message ?? "CDP request failed.").slice(0, 500)));
else request.resolve(message.result);
};
return {
ready,
send(method, parameters = {}) {
if (closed) return Promise.reject(new Error("CDP connection closed."));
return new Promise((resolve, reject) => {
const requestId = ++nextRequestId;
pending.set(requestId, { resolve, reject });
try { socket.send(JSON.stringify({ id: requestId, method, params: parameters })); }
catch (error) { pending.delete(requestId); reject(error); }
});
},
close() { fail(new Error("CDP connection closed.")); },
};
}
export async function evaluate(connection, expression) {
const result = await connection.send("Runtime.evaluate", {
expression, returnByValue: true, awaitPromise: true, timeout: 5000,
});
if (result.exceptionDetails !== undefined) {
throw new Error(String(result.exceptionDetails.exception?.description ?? result.exceptionDetails.text ?? "Page evaluation failed.").slice(0, 500));
}
return result.result?.value;
}