cdp-web
Version:
Provides a lightweight way to communicate with the Chrome DevTools Protocol (CDP).
155 lines (152 loc) • 3.86 kB
JavaScript
// src/deferred.ts
function isPromiseLike(value) {
return typeof value === "object" && value !== null && "then" in value && typeof value.then === "function";
}
var Deferred = class extends Promise {
state = "pending";
value;
reason;
resolve;
reject;
constructor(executor) {
let resolve;
let reject;
super((_resolve, _reject) => {
resolve = (value) => {
if (this.state === "pending" && !isPromiseLike(value)) {
this.state = "fulfilled";
this.value = value;
}
return _resolve(value);
};
reject = (reason) => {
if (this.state === "pending") {
this.state = "rejected";
this.reason = reason;
}
return _reject(reason);
};
executor?.(resolve, reject);
});
this.resolve = resolve;
this.reject = reject;
}
};
// src/cdp.ts
var DEFAULT_URL = "http://localhost:9222";
var CdpMessageEvent = class extends Event {
method;
params;
constructor(init) {
super("message", init);
this.method = init.method;
this.params = init.params;
}
};
var CdpError = class extends Error {
code;
constructor(code, message, options) {
super(message, options);
this.code = code;
}
};
var CdpConnection = class _CdpConnection extends EventTarget {
static async create(options) {
const cdpConnection = new _CdpConnection(options);
await cdpConnection.#connect();
return cdpConnection;
}
#ws;
#url;
#target;
#requestCount = 0;
#requests = /* @__PURE__ */ new Map();
constructor(options) {
super();
this.#url = new URL(options?.url ?? DEFAULT_URL);
this.#target = options?.target ?? "page";
}
async #getWsUrl() {
const response = await fetch(new URL("/json/list", this.#url));
const targets = await response.json();
const wsUrl = targets.find(
(target) => target.webSocketDebuggerUrl && target.type === this.#target
).webSocketDebuggerUrl;
return wsUrl;
}
#onMessage(payload) {
if ("id" in payload) {
const deferred = this.#requests.get(payload.id);
if (!deferred) {
return;
}
if ("error" in payload) {
deferred.reject(
new CdpError(payload.error.code, payload.error.message)
);
} else {
deferred.resolve(payload.result);
}
return;
}
if ("method" in payload) {
this.dispatchEvent(
new CdpMessageEvent({
method: payload.method,
params: payload.params
})
);
return;
}
}
async #connect() {
const wsUrl = await this.#getWsUrl();
this.#ws = new WebSocket(wsUrl);
this.#ws.addEventListener("message", (event) => {
const text = typeof event.data === "string" ? event.data : new TextDecoder().decode(event.data);
const payload = JSON.parse(text);
this.#onMessage(payload);
});
return new Promise((resolve, reject) => {
this.#ws.addEventListener("open", () => {
resolve();
});
this.#ws.addEventListener("close", (event) => {
reject(new Error(event.reason));
});
this.#ws.addEventListener("error", () => {
reject(new Error());
});
});
}
send(method, params = {}) {
const id = this.#requestCount++;
const payload = {
id,
method,
params
};
this.#ws.send(JSON.stringify(payload));
const deferred = new Deferred();
this.#requests.set(id, deferred);
return deferred;
}
close() {
this.#ws.close();
}
addEventListener(...args) {
return super.addEventListener(...args);
}
removeEventListener(...args) {
return super.removeEventListener(...args);
}
};
function createCdpConnection(options) {
return CdpConnection.create(options);
}
export {
CdpMessageEvent,
CdpError,
CdpConnection,
createCdpConnection
};