| 320 | } |
| 321 | |
| 322 | async function createCdpClient(wsUrl) { |
| 323 | return new Promise((resolve, reject) => { |
| 324 | const pending = new Map() |
| 325 | let nextId = 0 |
| 326 | const socket = new WebSocket(wsUrl) |
| 327 | |
| 328 | socket.addEventListener('open', () => { |
| 329 | resolve({ |
| 330 | async send(method, params) { |
| 331 | const id = ++nextId |
| 332 | const payload = { id, method, params } |
| 333 | const response = await new Promise((resolveSend, rejectSend) => { |
| 334 | pending.set(id, { resolve: resolveSend, reject: rejectSend }) |
| 335 | socket.send(JSON.stringify(payload)) |
| 336 | }) |
| 337 | if (response.error) { |
| 338 | throw new Error(response.error.message || `CDP ${method} failed`) |
| 339 | } |
| 340 | return response.result |
| 341 | }, |
| 342 | close() { |
| 343 | socket.close() |
| 344 | }, |
| 345 | }) |
| 346 | }) |
| 347 | |
| 348 | socket.addEventListener('message', (event) => { |
| 349 | const payload = JSON.parse(String(event.data)) |
| 350 | if (!payload.id) return |
| 351 | const handlers = pending.get(payload.id) |
| 352 | if (!handlers) return |
| 353 | pending.delete(payload.id) |
| 354 | handlers.resolve(payload) |
| 355 | }) |
| 356 | |
| 357 | socket.addEventListener('error', (error) => { |
| 358 | reject(error.error || error) |
| 359 | }) |
| 360 | |
| 361 | socket.addEventListener('close', () => { |
| 362 | for (const [, handlers] of pending) { |
| 363 | handlers.reject(new Error('CDP socket closed')) |
| 364 | } |
| 365 | pending.clear() |
| 366 | }) |
| 367 | }) |
| 368 | } |
| 369 | |
| 370 | async function evaluate(client, expression) { |
| 371 | const result = await client.send('Runtime.evaluate', { |