@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 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 both `cdp_ext/` files. With `cdp_ext: true`, the manager
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 two extension files into
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
- two `cdp_ext/` files into `npm/` alongside its manifest; no build script is needed.
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 two extension
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
- await fs.copyFile(new URL('./cdp_ext/manifest.json', import.meta.url), path.join(record.extension_path, 'manifest.json'))
662
- await fs.copyFile(new URL('./cdp_ext/cdp_ext.js', import.meta.url), path.join(record.extension_path, 'cdp_ext.js'))
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'), {
@@ -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 = () => {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "CDP reverse connection",
4
- "version": "0.5.1",
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.5.1",
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"