| 72 | |
| 73 | // --- minimal CDP page client over the built-in WebSocket ------------------- |
| 74 | class Cdp { |
| 75 | #ws; |
| 76 | #id = 1; |
| 77 | #pending = new Map(); |
| 78 | constructor(ws) { |
| 79 | this.#ws = ws; |
| 80 | ws.addEventListener("message", (e) => { |
| 81 | const m = JSON.parse(e.data); |
| 82 | if (!m.id) return; |
| 83 | const p = this.#pending.get(m.id); |
| 84 | if (!p) return; |
| 85 | this.#pending.delete(m.id); |
| 86 | m.error ? p.reject(new Error(m.error.message)) : p.resolve(m.result); |
| 87 | }); |
| 88 | } |
| 89 | static connect(url) { |
| 90 | return new Promise((resolve, reject) => { |
| 91 | const ws = new WebSocket(url); |
| 92 | ws.addEventListener("open", () => resolve(new Cdp(ws)), { once: true }); |
| 93 | ws.addEventListener("error", () => reject(new Error(`CDP connect failed ${url}`)), { |
| 94 | once: true, |
| 95 | }); |
| 96 | }); |
| 97 | } |
| 98 | cmd(method, params = {}) { |
| 99 | const id = this.#id++; |
| 100 | const p = new Promise((resolve, reject) => this.#pending.set(id, { resolve, reject })); |
| 101 | this.#ws.send(JSON.stringify({ id, method, params })); |
| 102 | return p; |
| 103 | } |
| 104 | async eval(expression) { |
| 105 | const r = await this.cmd("Runtime.evaluate", { |
| 106 | expression, |
| 107 | awaitPromise: true, |
| 108 | returnByValue: true, |
| 109 | }); |
| 110 | return r?.result?.value; |
| 111 | } |
| 112 | } |
| 113 | |
| 114 | const pageWsFor = async (debugPort) => { |
| 115 | const targets = await fetch(`http://127.0.0.1:${debugPort}/json/list`).then((r) => r.json()); |
nothing calls this directly
no outgoing calls
no test coverage detected