@mefistofelix/cdp.js 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -20
- package/cdp.js +57 -13
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
# cdp.js
|
|
2
2
|
|
|
3
|
-
A low-level Chromium DevTools Protocol library for Node.js, with an
|
|
4
|
-
native Deno server for Chrome extension reverse connections. One ES module,
|
|
3
|
+
A low-level Chromium DevTools Protocol library for Node.js, Deno and Bun, with an
|
|
4
|
+
optional native Deno/Bun server for Chrome extension reverse connections. One ES module,
|
|
5
5
|
standard-library imports, no runtime dependencies or root `package.json`.
|
|
6
6
|
|
|
7
7
|
Use Node.js 22.19+ and an installed Chrome, Chromium or Edge. Import `cdp.js`
|
|
8
8
|
directly from an ES module, for example `example.mjs`, and run `node example.mjs`.
|
|
9
9
|
Browser discovery covers common Windows, macOS and Linux installation paths;
|
|
10
10
|
use `executable_path` or the `CDP_BROWSER` environment variable for other paths.
|
|
11
|
+
The same module also runs with `bun example.mjs` or `deno run -A example.mjs`.
|
|
12
|
+
Direct and reverse modes are tested with Bun 1.4.3; no Bun-specific package,
|
|
13
|
+
configuration file or installation step is required by the library.
|
|
11
14
|
|
|
12
15
|
For a published npm release:
|
|
13
16
|
|
|
@@ -24,7 +27,8 @@ below remain supported. Publication metadata is isolated in `npm/`.
|
|
|
24
27
|
The package includes both `cdp_ext/` files. With `cdp_ext: true`, the manager
|
|
25
28
|
writes a configured extension copy into the browser's profile directory and loads
|
|
26
29
|
it automatically; no manual extraction or build is needed. Reverse mode still
|
|
27
|
-
requires Deno. In Deno, import from `npm:@mefistofelix/cdp.js`.
|
|
30
|
+
requires Deno or Bun. In Deno, import from `npm:@mefistofelix/cdp.js`.
|
|
31
|
+
In Bun, use `bun add @mefistofelix/cdp.js` and the ordinary package import above.
|
|
28
32
|
|
|
29
33
|
## Features and scope
|
|
30
34
|
|
|
@@ -34,7 +38,7 @@ requires Deno. In Deno, import from `npm:@mefistofelix/cdp.js`.
|
|
|
34
38
|
and automatic cleanup after their last target closes.
|
|
35
39
|
- Incognito targets in direct and reverse modes.
|
|
36
40
|
- Attachment through WebSocket, HTTP discovery or a debugging port.
|
|
37
|
-
- Optional Chrome extension reverse connections through one native Deno server,
|
|
41
|
+
- Optional Chrome extension reverse connections through one native Deno/Bun server,
|
|
38
42
|
without browser remote-debugging arguments.
|
|
39
43
|
- Generic `chrome.x.y` extension API calls on that connection, including scripting,
|
|
40
44
|
tabs/windows, cookies, storage, downloads and browser settings.
|
|
@@ -116,8 +120,8 @@ import api, { util, jsrpc, browser, cdp } from './cdp.js'
|
|
|
116
120
|
| `client.call({ method, params = {} })` | Resolve routing and return a promise for the native CDP result, or a custom handler's result. |
|
|
117
121
|
| `client.evaluate_xpath(params, fn, options)` | Execute a serialized function with a normalized XPath in a target; return the native `Runtime.evaluate` result. |
|
|
118
122
|
| `client.close()` | Close owned processes, disconnect attached/reverse browsers, stop the reverse server and clear browser/socket mappings. Profiles and external browsers remain. |
|
|
119
|
-
| `new jsrpc(urlOrSocket, options = {})` | Open a raw WebSocket JSON-RPC connection or equip an accepted
|
|
120
|
-
| `client.listen_reverse(options = {})` | Start the native Deno WebSocket server; returns
|
|
123
|
+
| `new jsrpc(urlOrSocket, options = {})` | Open a raw WebSocket JSON-RPC connection or equip an accepted socket with the same RPC methods. |
|
|
124
|
+
| `client.listen_reverse(options = {})` | Start the native Deno or Bun WebSocket server; returns that server with common lifecycle fields documented below. |
|
|
121
125
|
| `socket.req(request, timeout_ms)` | Assign a request ID and await its native result. |
|
|
122
126
|
| `socket.notify(request)` | Serialize and send a raw message; no ID allocation or response wait. |
|
|
123
127
|
| `socket.close()` | Native WebSocket close; does not terminate the browser. |
|
|
@@ -237,8 +241,8 @@ local configuration.
|
|
|
237
241
|
| `request_timeout_ms` | number, `30000` | Default timeout for each protocol request on this browser's socket. |
|
|
238
242
|
| `waitForDebuggerOnStart` | boolean, `false` | Ask Chromium to pause newly auto-attached targets. Applied to `Target.setAutoAttach` when connecting; not a CLI argument or target option. |
|
|
239
243
|
| `websocket_url` | string, unset | Attach directly to a browser WebSocket endpoint. |
|
|
240
|
-
| `cdp_ext` | boolean, `false` | Launch with the reverse extension under Deno. Start one shared server automatically if needed, prepare a profile-specific extension copy and wait for its connection. |
|
|
241
|
-
| `reverse` | boolean, `false` | Attach to an externally launched extension instead of launching or discovering a browser. Starts the shared listener on first use under Deno. |
|
|
244
|
+
| `cdp_ext` | boolean, `false` | Launch with the reverse extension under Deno or Bun. Start one shared server automatically if needed, prepare a profile-specific extension copy and wait for its connection. |
|
|
245
|
+
| `reverse` | boolean, `false` | Attach to an externally launched extension instead of launching or discovering a browser. Starts the shared listener on first use under Deno or Bun. |
|
|
242
246
|
| `extension_id` | string, browser name for external reverse attachment | Incoming extension ID to associate with this logical browser. Set automatically for managed `cdp_ext` launches. |
|
|
243
247
|
| `http_url` | string, unset | Discover the endpoint from an HTTP(S) base URL or exact `/json/version` URL. |
|
|
244
248
|
| `port` | number, unset | Discover an existing browser at `http://<host>:<port>/json/version`. |
|
|
@@ -343,7 +347,7 @@ Native proxy rules can use semicolon-separated strings; the generic CLI converte
|
|
|
343
347
|
still comma-joins every array. Bypass lists accept both separators. Chrome gives
|
|
344
348
|
`no-proxy-server` precedence, so overriding `proxy-server` alone does not cancel
|
|
345
349
|
that flag; override `args['no-proxy-server']: false` too if needed.
|
|
346
|
-
Browser proxy options affect Chrome traffic, not
|
|
350
|
+
Browser proxy options affect Chrome traffic, not the runtime's endpoint discovery
|
|
347
351
|
or CDP transport. Chrome normally bypasses loopback addresses. To proxy them,
|
|
348
352
|
use `args['proxy-bypass-list']: '<-loopback>'`; in reverse mode keep the extension's
|
|
349
353
|
local WebSocket listener reachable. The library inserts no extra bypass rule.
|
|
@@ -491,12 +495,13 @@ browser instances on the same port; each extension announces its native ID
|
|
|
491
495
|
in its first message. No remote-debugging argument is needed.
|
|
492
496
|
|
|
493
497
|
Run the server under Deno using its native
|
|
494
|
-
[WebSocket upgrade](https://docs.deno.com/api/deno/websockets/)
|
|
498
|
+
[WebSocket upgrade](https://docs.deno.com/api/deno/websockets/) or under Bun using
|
|
499
|
+
[Bun.serve](https://bun.sh/docs/runtime/http/websockets).
|
|
495
500
|
Node's direct CDP mode remains supported; `listen_reverse()` under Node rejects
|
|
496
|
-
with an explicit Deno requirement. There is no WebSocket framing implementation,
|
|
501
|
+
with an explicit Deno/Bun requirement. There is no WebSocket framing implementation,
|
|
497
502
|
third-party server dependency or additional build step.
|
|
498
503
|
|
|
499
|
-
Run `deno run -A examples/reverse.mjs` to launch Chrome with the extension, create
|
|
504
|
+
Run `deno run -A examples/reverse.mjs` or `bun examples/reverse.mjs` to launch Chrome with the extension, create
|
|
500
505
|
a target and evaluate `21 * 2`. Ctrl+C closes the owned browser and server.
|
|
501
506
|
The same mode is available per browser:
|
|
502
507
|
|
|
@@ -589,15 +594,24 @@ retry. Direct browsers do not keep this listener alive.
|
|
|
589
594
|
`client.reverse_server` is the public shared configuration object. After startup
|
|
590
595
|
it contains the effective listening port, including when `port: 0` originally
|
|
591
596
|
requested an available port. Restarts reuse that port so existing extensions can
|
|
592
|
-
still reconnect. `client.server` contains the current native Deno `HttpServer`,
|
|
597
|
+
still reconnect. `client.server` contains the current native Deno `HttpServer` or Bun `Server`,
|
|
593
598
|
or is unset/null while stopped. A call arriving during shutdown waits for it to
|
|
594
599
|
finish before starting a replacement server.
|
|
595
600
|
|
|
596
601
|
`listen_reverse(options = client.reverse_server)` remains available to start the
|
|
597
|
-
listener explicitly ahead of a browser call. It returns the native
|
|
602
|
+
listener explicitly ahead of a browser call. It returns the native runtime server;
|
|
598
603
|
calling it while a server is running rejects. Explicit options replace the stored
|
|
599
604
|
settings, with omitted fields taking the hostname/port defaults above.
|
|
600
605
|
|
|
606
|
+
Both runtimes expose `server.addr.hostname`, `server.addr.port`,
|
|
607
|
+
`server.shutdown()` and `server.finished`. On Bun, the library adds these fields:
|
|
608
|
+
`shutdown()` calls native `stop()` and resolves `finished` after shutdown.
|
|
609
|
+
Use `client.close()` to also close owned browsers and incoming sockets.
|
|
610
|
+
Bun's callback-based server sockets receive a small EventTarget adapter with
|
|
611
|
+
native send/close and live ready state. The same `jsrpc` handles both runtimes;
|
|
612
|
+
registration, request IDs, pending waits, events and session routing are shared.
|
|
613
|
+
Native Bun WebSocket limits still apply.
|
|
614
|
+
|
|
601
615
|
Reverse calls reuse the normal target setup, custom handlers, mappings, routed
|
|
602
616
|
events, request correlation, timeout and error behavior. `reverse: true` alone
|
|
603
617
|
attaches externally: the manager neither launches nor updates that browser.
|
|
@@ -1530,9 +1544,9 @@ dictionary containers have a null prototype.
|
|
|
1530
1544
|
| `client.reverse_server` | Shared listener configuration; the effective hostname/port are stored after startup. |
|
|
1531
1545
|
| `client.custom_methods[name]` | Handler function. |
|
|
1532
1546
|
| `client.browsers[name]` | Browser configuration fields plus live state. |
|
|
1533
|
-
| `client.reverse_sockets[extension_id]` | Announced incoming
|
|
1547
|
+
| `client.reverse_sockets[extension_id]` | Announced incoming socket, available even before the first call; native on Deno, an EventTarget adapter on Bun. |
|
|
1534
1548
|
| `record.extension_id`, `record.extension_path` | Expected extension identity and the managed copy's canonical directory. |
|
|
1535
|
-
| `client.server` | Current native Deno reverse
|
|
1549
|
+
| `client.server` | Current native Deno/Bun reverse server with shared lifecycle fields, or unset/null when not listening. |
|
|
1536
1550
|
| `client.server_closing` | Shutdown promise while the reverse listener is stopping, otherwise unset/null. |
|
|
1537
1551
|
| `record.socket` | Current `jsrpc` connection once created. |
|
|
1538
1552
|
| `record.proc` | Owned child process; absent for external attachment. |
|
|
@@ -1599,11 +1613,13 @@ subject to the debugging-port restrictions described above.
|
|
|
1599
1613
|
Readiness timeout or launch failure rejects and attempts to
|
|
1600
1614
|
terminate the spawned process.
|
|
1601
1615
|
|
|
1602
|
-
`jsrpc` takes a WebSocket URL or an accepted
|
|
1616
|
+
`jsrpc` takes a WebSocket URL or an accepted EventTarget socket and an options
|
|
1603
1617
|
object whose recognized option is
|
|
1604
1618
|
`request_timeout_ms` (default `30000`). Wait for the socket to open before `req`.
|
|
1605
|
-
For an accepted socket, it returns that same
|
|
1606
|
-
|
|
1619
|
+
For an accepted socket, it returns that same object with RPC fields/methods.
|
|
1620
|
+
The object supplies `send`, `close`, live `readyState` and normal message/close/error
|
|
1621
|
+
events. Deno sockets remain native WebSockets; Bun uses the adapter described
|
|
1622
|
+
above. Accepted sockets need not be instances of the `jsrpc` subclass.
|
|
1607
1623
|
The reverse server consumes the initial browser announcement before equipping
|
|
1608
1624
|
the socket with RPC behavior. Empty-text heartbeats are ignored.
|
|
1609
1625
|
The request is a raw envelope: `method`, optional `params`, and optional top-level
|
|
@@ -1662,12 +1678,13 @@ node --check cdp.js
|
|
|
1662
1678
|
node --check cdp_ext/cdp_ext.js
|
|
1663
1679
|
node --test tests/cdp.test.js
|
|
1664
1680
|
deno test -A tests/reverse.test.js
|
|
1681
|
+
bun test tests/cdp.test.js tests/reverse.test.js --timeout 60000
|
|
1665
1682
|
```
|
|
1666
1683
|
|
|
1667
1684
|
Integration tests launch local headless Chromium with disposable profiles under
|
|
1668
1685
|
`build/tests/`. Set `CDP_BROWSER` if discovery does not match the machine. Direct
|
|
1669
1686
|
library use needs no build or package installation; npm packaging is optional.
|
|
1670
|
-
The Deno
|
|
1687
|
+
The shared Deno/Bun reverse suite loads the real extension into two headless Chrome instances on one
|
|
1671
1688
|
server port without remote-debugging arguments. It checks native/custom calls,
|
|
1672
1689
|
Network events, webRequest notifications without debugger attachment, bindings,
|
|
1673
1690
|
child sessions, reconnect, detach/recreate, incognito and isolation.
|
package/cdp.js
CHANGED
|
@@ -46,7 +46,7 @@ export class util {
|
|
|
46
46
|
|
|
47
47
|
export class jsrpc extends WebSocket {
|
|
48
48
|
constructor(url, options = {}) {
|
|
49
|
-
const socket = url instanceof
|
|
49
|
+
const socket = typeof url === 'string' || url instanceof URL ? super(url) : url
|
|
50
50
|
setMaxListeners(0, socket)
|
|
51
51
|
socket.id = 0
|
|
52
52
|
socket.pending = {}
|
|
@@ -355,18 +355,9 @@ export class cdp extends EventTarget {
|
|
|
355
355
|
hostname = '127.0.0.1',
|
|
356
356
|
port = 9223,
|
|
357
357
|
} = this.reverse_server) {
|
|
358
|
-
if (!globalThis.Deno) throw new Error('The reverse WebSocket server requires Deno')
|
|
358
|
+
if (!globalThis.Deno && !globalThis.Bun) throw new Error('The reverse WebSocket server requires Deno or Bun')
|
|
359
359
|
if (this.server) throw new Error('Reverse server is already listening')
|
|
360
|
-
|
|
361
|
-
hostname,
|
|
362
|
-
port,
|
|
363
|
-
}, request => {
|
|
364
|
-
const origin = request.headers.get('origin')
|
|
365
|
-
if (request.headers.get('upgrade') !== 'websocket' ||
|
|
366
|
-
!origin?.startsWith('chrome-extension://')) {
|
|
367
|
-
return new Response(null, { status: 403 })
|
|
368
|
-
}
|
|
369
|
-
const { socket, response } = Deno.upgradeWebSocket(request)
|
|
360
|
+
const accept = (socket, origin) => {
|
|
370
361
|
socket.addEventListener('message', event => {
|
|
371
362
|
const id = JSON.parse(event.data).browser
|
|
372
363
|
if (id !== origin.slice('chrome-extension://'.length) ||
|
|
@@ -380,8 +371,61 @@ export class cdp extends EventTarget {
|
|
|
380
371
|
})
|
|
381
372
|
util.emit(this, 'reverse_' + encodeURIComponent(id), socket)
|
|
382
373
|
}, { once: true })
|
|
374
|
+
}
|
|
375
|
+
const fetch = (request, server) => {
|
|
376
|
+
const origin = request.headers.get('origin')
|
|
377
|
+
if (request.headers.get('upgrade') !== 'websocket' ||
|
|
378
|
+
!origin?.startsWith('chrome-extension://')) {
|
|
379
|
+
return new Response(null, { status: 403 })
|
|
380
|
+
}
|
|
381
|
+
if (globalThis.Bun) {
|
|
382
|
+
if (server.upgrade(request, {
|
|
383
|
+
data: {
|
|
384
|
+
origin,
|
|
385
|
+
socket: new EventTarget(),
|
|
386
|
+
},
|
|
387
|
+
})) return
|
|
388
|
+
return new Response(null, { status: 400 })
|
|
389
|
+
}
|
|
390
|
+
const { socket, response } = Deno.upgradeWebSocket(request)
|
|
391
|
+
accept(socket, origin)
|
|
383
392
|
return response
|
|
384
|
-
}
|
|
393
|
+
}
|
|
394
|
+
if (globalThis.Bun) {
|
|
395
|
+
const server = this.server = Bun.serve({
|
|
396
|
+
hostname,
|
|
397
|
+
port,
|
|
398
|
+
fetch,
|
|
399
|
+
websocket: {
|
|
400
|
+
open(socket) {
|
|
401
|
+
const events = socket.data.socket
|
|
402
|
+
events.send = socket.send.bind(socket)
|
|
403
|
+
events.close = socket.close.bind(socket)
|
|
404
|
+
Object.defineProperty(events, 'readyState', { get: () => socket.readyState })
|
|
405
|
+
accept(events, socket.data.origin)
|
|
406
|
+
},
|
|
407
|
+
message(socket, data) {
|
|
408
|
+
socket.data.socket.dispatchEvent(new MessageEvent('message', { data }))
|
|
409
|
+
},
|
|
410
|
+
close(socket, code, reason) {
|
|
411
|
+
socket.data.socket.dispatchEvent(Object.assign(new Event('close'), {
|
|
412
|
+
code,
|
|
413
|
+
reason,
|
|
414
|
+
}))
|
|
415
|
+
},
|
|
416
|
+
},
|
|
417
|
+
})
|
|
418
|
+
server.addr = {
|
|
419
|
+
hostname,
|
|
420
|
+
port: server.port,
|
|
421
|
+
}
|
|
422
|
+
const finished = Promise.withResolvers()
|
|
423
|
+
server.finished = finished.promise
|
|
424
|
+
server.shutdown = () => server.stop().then(finished.resolve)
|
|
425
|
+
} else this.server = Deno.serve({
|
|
426
|
+
hostname,
|
|
427
|
+
port,
|
|
428
|
+
}, fetch)
|
|
385
429
|
this.reverse_server = {
|
|
386
430
|
hostname,
|
|
387
431
|
port: this.server.addr.port,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mefistofelix/cdp.js",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Low-level Chromium DevTools Protocol client for Node.js, with no runtime dependencies.",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Low-level Chromium DevTools Protocol client for Node.js, Deno and Bun, with no runtime dependencies.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": "./cdp.js",
|
|
7
7
|
"files": [
|