@mefistofelix/cdp.js 0.5.1 → 0.6.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 +31 -6
- package/cdp.js +3 -2
- package/cdp_ext/cdp_ext.js +51 -0
- package/cdp_ext/manifest.json +7 -2
- package/cdp_ext/sidebar.html +35 -0
- package/cdp_ext/sidebar.js +43 -0
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -24,7 +24,7 @@ import api, { util, jsrpc, browser, cdp } from '@mefistofelix/cdp.js'
|
|
|
24
24
|
|
|
25
25
|
The npm package exports the same file and API. Direct file imports in the examples
|
|
26
26
|
below remain supported. Publication metadata is isolated in `npm/`.
|
|
27
|
-
The package includes
|
|
27
|
+
The package includes the worker, manifest and debug sidebar files. With `cdp_ext: true`, the manager
|
|
28
28
|
prepares and loads the extension automatically with a custom user-data directory.
|
|
29
29
|
The system profile uses an already installed extension by default. Reverse mode still
|
|
30
30
|
requires Deno or Bun. In Deno, import from `npm:@mefistofelix/cdp.js`.
|
|
@@ -491,7 +491,7 @@ can configure its native incognito scope. See
|
|
|
491
491
|
## Reverse Chrome extension transport
|
|
492
492
|
|
|
493
493
|
[cdp_ext/manifest.json](cdp_ext/manifest.json) and
|
|
494
|
-
[cdp_ext/cdp_ext.js](cdp_ext/cdp_ext.js) form a Manifest V3 extension for Chrome
|
|
494
|
+
[cdp_ext/cdp_ext.js](cdp_ext/cdp_ext.js), with the local debug sidebar, form a Manifest V3 extension for Chrome
|
|
495
495
|
125+. The extension connects out to one WebSocket server. The server serves all
|
|
496
496
|
browser instances on the same port; bootstrapped extensions announce the logical
|
|
497
497
|
browser name and native extension ID. No remote-debugging argument is needed.
|
|
@@ -541,7 +541,7 @@ await client.call({
|
|
|
541
541
|
|
|
542
542
|
Provide `user_data_dir` or raw `args['user-data-dir']` to choose a custom
|
|
543
543
|
directory. An effective custom directory makes `cdp_ext_install` default to true:
|
|
544
|
-
the manager copies the
|
|
544
|
+
the manager copies the four extension files into
|
|
545
545
|
`<user-data-dir>/<profile-directory>/cdp_ext/` (`Default` when unset) and adds
|
|
546
546
|
startup loading flags. The server address comes only from the trigger URL. Normal launch
|
|
547
547
|
options and profile preferences then apply. Set `cdp_ext_install: false` to use
|
|
@@ -685,6 +685,28 @@ WebSocket attempt on every timer tick. Successful connections and later
|
|
|
685
685
|
disconnects log their status once per transition. Chrome can still emit a native
|
|
686
686
|
network diagnostic if the server disappears between the HTTP check and upgrade.
|
|
687
687
|
|
|
688
|
+
### Debug sidebar
|
|
689
|
+
|
|
690
|
+
Click the extension's toolbar icon to open its native Chrome side panel.
|
|
691
|
+
It shows the current connection status, logical browser name, WebSocket endpoint,
|
|
692
|
+
native extension ID and number of attached root/child sessions. It works before
|
|
693
|
+
the first trigger too, displaying **Waiting for trigger** without starting a
|
|
694
|
+
server connection.
|
|
695
|
+
|
|
696
|
+
The most recent 50 incoming commands appear newest first, with request ID,
|
|
697
|
+
method, optional session ID, running/done/error status, elapsed milliseconds and
|
|
698
|
+
error text. This is command metadata only: parameters, results and the trigger
|
|
699
|
+
token are not included. Empty heartbeat messages and CDP events are not logged.
|
|
700
|
+
**Clear** empties this history without cancelling commands or changing the
|
|
701
|
+
connection. Reopening the panel retrieves the current snapshot; a worker restart
|
|
702
|
+
or extension reload clears the history. No debug history is written to storage.
|
|
703
|
+
|
|
704
|
+
The panel receives updates through a local extension port. While open, a
|
|
705
|
+
20-second port heartbeat keeps the worker available, including before a trigger.
|
|
706
|
+
Closing the panel does not disconnect CDP. After reloading the extension, reopen
|
|
707
|
+
the panel if its old document reports that it has disconnected. The HTML and
|
|
708
|
+
external script follow the normal extension CSP and require no build or dependency.
|
|
709
|
+
|
|
688
710
|
### Reverse protocol and supported commands
|
|
689
711
|
|
|
690
712
|
The current extension's first announcement contains `{ browser, extension_id, token }`
|
|
@@ -1787,11 +1809,12 @@ from tool-call history. It is a caller-side recipe, not additional library API.
|
|
|
1787
1809
|
|
|
1788
1810
|
For npm packaging, account setup, and GitHub publishing, see
|
|
1789
1811
|
[Publishing](npm/PUBLISHING.md). Publishing copies `cdp.js`, this README and the
|
|
1790
|
-
|
|
1812
|
+
four `cdp_ext/` files into `npm/` alongside its manifest; no build script is needed.
|
|
1791
1813
|
|
|
1792
1814
|
```sh
|
|
1793
1815
|
node --check cdp.js
|
|
1794
1816
|
node --check cdp_ext/cdp_ext.js
|
|
1817
|
+
node --check cdp_ext/sidebar.js
|
|
1795
1818
|
node --test tests/cdp.test.js
|
|
1796
1819
|
deno test -A tests/reverse.test.js
|
|
1797
1820
|
bun test tests/cdp.test.js tests/reverse.test.js --timeout 60000
|
|
@@ -1835,5 +1858,7 @@ combinations include default/custom directories, null/false raw directory
|
|
|
1835
1858
|
overrides, forced installation and independent preference-write controls.
|
|
1836
1859
|
Offline worker checks verify silence before a trigger, deduplicated waiting logs,
|
|
1837
1860
|
no WebSocket attempts against absent/incompatible servers and concurrent retry checks.
|
|
1838
|
-
The npm tarball contains `package.json`, `cdp.js`, `README.md` and the
|
|
1839
|
-
files required by managed reverse launches.
|
|
1861
|
+
The npm tarball contains `package.json`, `cdp.js`, `README.md` and the four extension
|
|
1862
|
+
files required by managed reverse launches and their debug sidebar. Tests cover
|
|
1863
|
+
live command metadata, its 50-entry limit, error/session details, sidebar rendering,
|
|
1864
|
+
safe text display, reopening and clearing through native extension messaging.
|
package/cdp.js
CHANGED
|
@@ -658,8 +658,9 @@ export class cdp extends EventTarget {
|
|
|
658
658
|
await fs.mkdir(record.extension_path, { recursive: true })
|
|
659
659
|
record.extension_path = await fs.realpath(record.extension_path)
|
|
660
660
|
record.extension_id = browser.extension_id(record.extension_path)
|
|
661
|
-
|
|
662
|
-
|
|
661
|
+
for (const filename of ['manifest.json', 'cdp_ext.js', 'sidebar.html', 'sidebar.js']) {
|
|
662
|
+
await fs.copyFile(new URL('./cdp_ext/' + filename, import.meta.url), path.join(record.extension_path, filename))
|
|
663
|
+
}
|
|
663
664
|
}
|
|
664
665
|
if (args['user-data-dir'] && record.update_preferences !== false) {
|
|
665
666
|
await browser.update_profile_preferences(path.join(args['user-data-dir'], args['profile-directory'] || 'Default', 'Preferences'), {
|
package/cdp_ext/cdp_ext.js
CHANGED
|
@@ -12,11 +12,44 @@ let auto_attach = {}
|
|
|
12
12
|
let bootstrapping
|
|
13
13
|
let checking = false
|
|
14
14
|
let connection_message
|
|
15
|
+
const debug_commands = []
|
|
16
|
+
const debug_ports = []
|
|
17
|
+
|
|
18
|
+
function debug_update() {
|
|
19
|
+
const state = {
|
|
20
|
+
status: connection_message ?? (config.browser ? 'Connecting to server' : 'Waiting for trigger'),
|
|
21
|
+
connected: socket?.readyState === WebSocket.OPEN,
|
|
22
|
+
browser: config.browser ?? '',
|
|
23
|
+
endpoint: config.websocket_url ?? '',
|
|
24
|
+
extension_id: chrome.runtime.id,
|
|
25
|
+
sessions: Object.keys(sessions).length,
|
|
26
|
+
commands: debug_commands,
|
|
27
|
+
}
|
|
28
|
+
for (const port of debug_ports) {
|
|
29
|
+
try {
|
|
30
|
+
port.postMessage(state)
|
|
31
|
+
} catch {}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
chrome.runtime.onConnect.addListener(port => {
|
|
36
|
+
if (port.name !== 'cdp_debug') return
|
|
37
|
+
debug_ports.push(port)
|
|
38
|
+
port.onDisconnect.addListener(() => debug_ports.splice(debug_ports.indexOf(port), 1))
|
|
39
|
+
port.onMessage.addListener(message => {
|
|
40
|
+
if (message !== 'clear') return
|
|
41
|
+
debug_commands.length = 0
|
|
42
|
+
debug_update()
|
|
43
|
+
})
|
|
44
|
+
configured.then(debug_update)
|
|
45
|
+
})
|
|
46
|
+
chrome.sidePanel.setPanelBehavior({ openPanelOnActionClick: true })
|
|
15
47
|
|
|
16
48
|
function report_connection(message) {
|
|
17
49
|
if (connection_message === message) return
|
|
18
50
|
connection_message = message
|
|
19
51
|
console.info('[cdp_ext] ' + message)
|
|
52
|
+
debug_update()
|
|
20
53
|
}
|
|
21
54
|
|
|
22
55
|
async function bootstrap(tab) {
|
|
@@ -47,6 +80,8 @@ async function bootstrap(tab) {
|
|
|
47
80
|
}
|
|
48
81
|
if (changed) {
|
|
49
82
|
Object.assign(config, settings)
|
|
83
|
+
connection_message = undefined
|
|
84
|
+
debug_update()
|
|
50
85
|
await chrome.storage.session.set({ cdp_bootstrap: settings })
|
|
51
86
|
if (socket && socket.readyState !== WebSocket.CLOSED) {
|
|
52
87
|
await new Promise(resolve => {
|
|
@@ -91,6 +126,7 @@ function detached(source) {
|
|
|
91
126
|
targetId: String(source.tabId),
|
|
92
127
|
},
|
|
93
128
|
})
|
|
129
|
+
debug_update()
|
|
94
130
|
}
|
|
95
131
|
|
|
96
132
|
async function attach_tab(tabId) {
|
|
@@ -206,6 +242,7 @@ chrome.debugger.onEvent.addListener((source, method, params) => {
|
|
|
206
242
|
sessionId: params.sessionId,
|
|
207
243
|
}
|
|
208
244
|
else delete sessions[params.sessionId]
|
|
245
|
+
debug_update()
|
|
209
246
|
}
|
|
210
247
|
send({
|
|
211
248
|
method,
|
|
@@ -296,6 +333,16 @@ async function connect() {
|
|
|
296
333
|
if (!event.data) return
|
|
297
334
|
const request = JSON.parse(event.data)
|
|
298
335
|
const { method, params = {}, sessionId, id } = request
|
|
336
|
+
const command = {
|
|
337
|
+
time: Date.now(),
|
|
338
|
+
id,
|
|
339
|
+
method,
|
|
340
|
+
sessionId,
|
|
341
|
+
status: 'running',
|
|
342
|
+
}
|
|
343
|
+
debug_commands.unshift(command)
|
|
344
|
+
debug_commands.length = Math.min(debug_commands.length, 50)
|
|
345
|
+
debug_update()
|
|
299
346
|
let response
|
|
300
347
|
try {
|
|
301
348
|
let result
|
|
@@ -335,6 +382,10 @@ async function connect() {
|
|
|
335
382
|
},
|
|
336
383
|
}
|
|
337
384
|
}
|
|
385
|
+
command.status = response.error ? 'error' : 'done'
|
|
386
|
+
command.error = response.error?.message
|
|
387
|
+
command.duration_ms = Date.now() - command.time
|
|
388
|
+
debug_update()
|
|
338
389
|
if (id != null && connection.readyState === WebSocket.OPEN) connection.send(JSON.stringify(response))
|
|
339
390
|
}
|
|
340
391
|
connection.onclose = () => {
|
package/cdp_ext/manifest.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest_version": 3,
|
|
3
3
|
"name": "CDP reverse connection",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.6.0",
|
|
5
5
|
"minimum_chrome_version": "125",
|
|
6
6
|
"permissions": [
|
|
7
7
|
"accessibilityFeatures.read",
|
|
@@ -64,7 +64,12 @@
|
|
|
64
64
|
"host_permissions": [
|
|
65
65
|
"<all_urls>"
|
|
66
66
|
],
|
|
67
|
-
"action": {
|
|
67
|
+
"action": {
|
|
68
|
+
"default_title": "CDP.js debug"
|
|
69
|
+
},
|
|
70
|
+
"side_panel": {
|
|
71
|
+
"default_path": "sidebar.html"
|
|
72
|
+
},
|
|
68
73
|
"incognito": "spanning",
|
|
69
74
|
"background": {
|
|
70
75
|
"service_worker": "cdp_ext.js"
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<meta charset="utf-8">
|
|
4
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
5
|
+
<title>CDP.js debug</title>
|
|
6
|
+
<style>
|
|
7
|
+
:root { color-scheme: light dark; font: 14px system-ui, sans-serif; }
|
|
8
|
+
body { margin: 16px; }
|
|
9
|
+
header { display: flex; align-items: center; justify-content: space-between; }
|
|
10
|
+
h1 { font-size: 18px; margin: 0; }
|
|
11
|
+
h2 { font-size: 14px; margin: 24px 0 12px; }
|
|
12
|
+
button { font: inherit; cursor: pointer; padding: 5px 10px; }
|
|
13
|
+
#status { border-left: 3px solid #d99b24; padding-left: 10px; overflow-wrap: anywhere; }
|
|
14
|
+
#status.connected { border-color: #35a76a; }
|
|
15
|
+
dt { opacity: .65; margin-top: 12px; }
|
|
16
|
+
dd { margin: 3px 0; overflow-wrap: anywhere; font-family: monospace; }
|
|
17
|
+
#commands { padding: 0; list-style: none; }
|
|
18
|
+
li { border-top: 1px solid #8885; padding: 10px 0; }
|
|
19
|
+
strong { display: block; overflow-wrap: anywhere; font: 13px monospace; }
|
|
20
|
+
small { display: block; opacity: .7; margin-top: 5px; font-size: 12px; }
|
|
21
|
+
.error { color: light-dark(#b42318, #ff938a); white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
22
|
+
</style>
|
|
23
|
+
<header><h1>CDP.js debug</h1><button id="clear" type="button" title="Clear command history">Clear</button></header>
|
|
24
|
+
<p id="status" role="status">Connecting to extension…</p>
|
|
25
|
+
<dl>
|
|
26
|
+
<dt>Browser</dt><dd id="browser">—</dd>
|
|
27
|
+
<dt>Server</dt><dd id="endpoint">—</dd>
|
|
28
|
+
<dt>Extension</dt><dd id="extension">—</dd>
|
|
29
|
+
<dt>Attached sessions</dt><dd id="sessions">0</dd>
|
|
30
|
+
</dl>
|
|
31
|
+
<h2>Recent commands <small>Latest 50 · memory only</small></h2>
|
|
32
|
+
<p id="empty">No commands received.</p>
|
|
33
|
+
<ol id="commands"></ol>
|
|
34
|
+
<script src="sidebar.js"></script>
|
|
35
|
+
</html>
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const port = chrome.runtime.connect({ name: 'cdp_debug' })
|
|
2
|
+
const status = document.querySelector('#status')
|
|
3
|
+
const heartbeat = setInterval(() => port.postMessage('ping'), 20000)
|
|
4
|
+
|
|
5
|
+
port.onMessage.addListener(state => {
|
|
6
|
+
status.textContent = state.status
|
|
7
|
+
status.classList.toggle('connected', state.connected)
|
|
8
|
+
for (const [id, value] of Object.entries({
|
|
9
|
+
browser: state.browser,
|
|
10
|
+
endpoint: state.endpoint,
|
|
11
|
+
extension: state.extension_id,
|
|
12
|
+
sessions: state.sessions,
|
|
13
|
+
})) document.getElementById(id).textContent = value === '' ? '—' : value
|
|
14
|
+
document.querySelector('#empty').hidden = state.commands.length > 0
|
|
15
|
+
document.querySelector('#commands').replaceChildren(...state.commands.map(command => {
|
|
16
|
+
const row = document.createElement('li')
|
|
17
|
+
const method = document.createElement('strong')
|
|
18
|
+
method.textContent = command.method
|
|
19
|
+
const details = document.createElement('small')
|
|
20
|
+
details.textContent = [
|
|
21
|
+
new Date(command.time).toLocaleTimeString(),
|
|
22
|
+
'#' + (command.id ?? 'notification'),
|
|
23
|
+
command.sessionId ? 'session ' + command.sessionId : 'browser',
|
|
24
|
+
command.status,
|
|
25
|
+
command.duration_ms == null ? '' : command.duration_ms + ' ms',
|
|
26
|
+
].filter(Boolean).join(' · ')
|
|
27
|
+
row.append(method, details)
|
|
28
|
+
if (command.error) {
|
|
29
|
+
const error = document.createElement('p')
|
|
30
|
+
error.className = 'error'
|
|
31
|
+
error.textContent = command.error
|
|
32
|
+
row.append(error)
|
|
33
|
+
}
|
|
34
|
+
return row
|
|
35
|
+
}))
|
|
36
|
+
})
|
|
37
|
+
port.onDisconnect.addListener(() => {
|
|
38
|
+
clearInterval(heartbeat)
|
|
39
|
+
status.textContent = 'Extension disconnected. Reopen this panel after reloading the extension.'
|
|
40
|
+
status.classList.remove('connected')
|
|
41
|
+
document.querySelector('#clear').disabled = true
|
|
42
|
+
})
|
|
43
|
+
document.querySelector('#clear').addEventListener('click', () => port.postMessage('clear'))
|
package/package.json
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mefistofelix/cdp.js",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
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": [
|
|
8
8
|
"cdp.js",
|
|
9
9
|
"cdp_ext/manifest.json",
|
|
10
|
-
"cdp_ext/cdp_ext.js"
|
|
10
|
+
"cdp_ext/cdp_ext.js",
|
|
11
|
+
"cdp_ext/sidebar.html",
|
|
12
|
+
"cdp_ext/sidebar.js"
|
|
11
13
|
],
|
|
12
14
|
"engines": {
|
|
13
15
|
"node": ">=22.19.0"
|