@mefistofelix/cdp.js 0.5.0 → 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 +70 -15
- package/cdp.js +5 -6
- package/cdp_ext/cdp_ext.js +99 -9
- package/cdp_ext/manifest.json +7 -6
- 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,9 +541,9 @@ 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
|
|
545
|
-
`<user-data-dir>/<profile-directory>/cdp_ext/` (`Default` when unset)
|
|
546
|
-
|
|
544
|
+
the manager copies the four extension files into
|
|
545
|
+
`<user-data-dir>/<profile-directory>/cdp_ext/` (`Default` when unset) and adds
|
|
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
|
|
548
548
|
an extension already installed in that profile, or true to force preparation and
|
|
549
549
|
loading. A null/false raw directory override makes automatic installation default off.
|
|
@@ -568,6 +568,13 @@ and enable the bridge. An `extensions` array adds its directories;
|
|
|
568
568
|
`extensions: true` retains enabled profile extensions through `'*'`. Raw `args`
|
|
569
569
|
still win and can prevent loading or connection.
|
|
570
570
|
|
|
571
|
+
Both launch paths keep `--no-first-run` and
|
|
572
|
+
`--disable-search-engine-choice-screen`, including when opening a new custom
|
|
573
|
+
profile with installation disabled. This suppresses onboarding and the search
|
|
574
|
+
engine selection prompt without changing the chosen search provider or writing
|
|
575
|
+
search preferences. Raw `args` can override either flag. An existing Chrome only
|
|
576
|
+
receives the URL; new startup flags require restarting that instance.
|
|
577
|
+
|
|
571
578
|
The manager opens an actual local HTTP URL on the shared listener, for example
|
|
572
579
|
`http://127.0.0.1:9223/cdp_ext?browser=work&websocket_url=...&token=...`.
|
|
573
580
|
The port is the effective listening port, including an automatically allocated
|
|
@@ -590,15 +597,22 @@ manager while connected returns an explicit error, preserving the existing
|
|
|
590
597
|
connection. After disconnection, a new bootstrap can rebind it.
|
|
591
598
|
|
|
592
599
|
Passive external attachment remains available through `reverse: true`: it opens
|
|
593
|
-
no URL and waits for `extension_id` (or the browser label).
|
|
594
|
-
|
|
595
|
-
|
|
600
|
+
no URL and waits for `extension_id` (or the browser label). This compatibility
|
|
601
|
+
mode is optional; `cdp_ext: true` needs neither `reverse` nor an ID.
|
|
602
|
+
The current extension still requires a trigger URL in passive mode; it never
|
|
603
|
+
connects automatically to a manifest endpoint or a default port.
|
|
604
|
+
|
|
605
|
+
The extension stays idle until a valid trigger URL supplies the logical name,
|
|
606
|
+
WebSocket endpoint and token. Before that it makes no server requests and logs
|
|
607
|
+
no connection messages. A worker restart may resume the same browser session's
|
|
608
|
+
previous trigger from `chrome.storage.session`.
|
|
596
609
|
|
|
597
|
-
The manifest
|
|
610
|
+
The shipped manifest needs no custom configuration keys. An optional custom
|
|
611
|
+
`cdp.reconnect_ms` override remains supported (Chrome displays a harmless
|
|
612
|
+
unrecognized-key warning if it is added):
|
|
598
613
|
|
|
599
614
|
| Manifest `cdp` key | Default | Meaning |
|
|
600
615
|
| --- | --- | --- |
|
|
601
|
-
| `websocket_url` | `ws://127.0.0.1:9223` | Initial shared endpoint; URL bootstrap overrides it. |
|
|
602
616
|
| `reconnect_ms` | `5000` | Awake retry and connected heartbeat interval; use a positive value below 30000. |
|
|
603
617
|
|
|
604
618
|
Construction and direct CDP calls do not start a listener. The first browser call
|
|
@@ -662,10 +676,42 @@ another connection attempt; the configured interval applies while it is awake.
|
|
|
662
676
|
Connected empty-text heartbeats keep an otherwise idle WebSocket worker active.
|
|
663
677
|
These are transport messages, not event buffering or polling of browser state.
|
|
664
678
|
|
|
679
|
+
After a trigger, the server may be offline normally. Before each disconnected
|
|
680
|
+
WebSocket attempt the extension checks the same server's HTTP `/cdp_ext` endpoint
|
|
681
|
+
with a two-second timeout and no cache. It opens the socket only when that
|
|
682
|
+
endpoint identifies CDP.js. Concurrent retries share the check; an unavailable or
|
|
683
|
+
incompatible server causes one informational waiting message, not a failing
|
|
684
|
+
WebSocket attempt on every timer tick. Successful connections and later
|
|
685
|
+
disconnects log their status once per transition. Chrome can still emit a native
|
|
686
|
+
network diagnostic if the server disappears between the HTTP check and upgrade.
|
|
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
|
+
|
|
665
710
|
### Reverse protocol and supported commands
|
|
666
711
|
|
|
667
|
-
The first announcement contains `{ browser, extension_id, token }`
|
|
668
|
-
|
|
712
|
+
The current extension's first announcement contains `{ browser, extension_id, token }`
|
|
713
|
+
after a trigger. The server also accepts legacy native-ID announcements.
|
|
714
|
+
Subsequent messages use CDP envelopes:
|
|
669
715
|
|
|
670
716
|
- Request: `{ id, method, params, sessionId? }`.
|
|
671
717
|
- Response: `{ id, result }` or `{ id, error: { code, message } }`.
|
|
@@ -1763,11 +1809,12 @@ from tool-call history. It is a caller-side recipe, not additional library API.
|
|
|
1763
1809
|
|
|
1764
1810
|
For npm packaging, account setup, and GitHub publishing, see
|
|
1765
1811
|
[Publishing](npm/PUBLISHING.md). Publishing copies `cdp.js`, this README and the
|
|
1766
|
-
|
|
1812
|
+
four `cdp_ext/` files into `npm/` alongside its manifest; no build script is needed.
|
|
1767
1813
|
|
|
1768
1814
|
```sh
|
|
1769
1815
|
node --check cdp.js
|
|
1770
1816
|
node --check cdp_ext/cdp_ext.js
|
|
1817
|
+
node --check cdp_ext/sidebar.js
|
|
1771
1818
|
node --test tests/cdp.test.js
|
|
1772
1819
|
deno test -A tests/reverse.test.js
|
|
1773
1820
|
bun test tests/cdp.test.js tests/reverse.test.js --timeout 60000
|
|
@@ -1805,5 +1852,13 @@ already running instance, startup when absent, one shared extension directory,
|
|
|
1805
1852
|
logical names, binding tokens, conflicting names and preservation on manager close.
|
|
1806
1853
|
Installation defaults, raw directory overrides, profile writes independent of
|
|
1807
1854
|
installation and shared wildcard expansion are covered without opening personal profiles.
|
|
1808
|
-
|
|
1809
|
-
|
|
1855
|
+
Argument coverage crosses direct/extension launch modes with headless, proxy,
|
|
1856
|
+
image/login/translation options, extension selectors and raw overrides. Profile
|
|
1857
|
+
combinations include default/custom directories, null/false raw directory
|
|
1858
|
+
overrides, forced installation and independent preference-write controls.
|
|
1859
|
+
Offline worker checks verify silence before a trigger, deduplicated waiting logs,
|
|
1860
|
+
no WebSocket attempts against absent/incompatible servers and concurrent retry checks.
|
|
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
|
@@ -153,12 +153,10 @@ export class browser {
|
|
|
153
153
|
'disable-prompt-on-repost': true,
|
|
154
154
|
'force-color-profile': 'srgb',
|
|
155
155
|
'metrics-recording-only': true,
|
|
156
|
-
'no-first-run': true,
|
|
157
156
|
'password-store': 'basic',
|
|
158
157
|
'use-mock-keychain': true,
|
|
159
158
|
'no-service-autorun': true,
|
|
160
159
|
'export-tagged-pdf': true,
|
|
161
|
-
'disable-search-engine-choice-screen': true,
|
|
162
160
|
'disable-features': [
|
|
163
161
|
'InfiniteSessionRestore',
|
|
164
162
|
'ImprovedCookieControls',
|
|
@@ -177,6 +175,8 @@ export class browser {
|
|
|
177
175
|
'user-data-dir': options.user_data_dir,
|
|
178
176
|
'remote-debugging-port': 0,
|
|
179
177
|
}
|
|
178
|
+
args['no-first-run'] = true
|
|
179
|
+
args['disable-search-engine-choice-screen'] = true
|
|
180
180
|
if (options.headless) {
|
|
181
181
|
args.headless = 'new'
|
|
182
182
|
args['window-size'] = '1440,900'
|
|
@@ -658,10 +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
|
-
const
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
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
|
+
}
|
|
665
664
|
}
|
|
666
665
|
if (args['user-data-dir'] && record.update_preferences !== false) {
|
|
667
666
|
await browser.update_profile_preferences(path.join(args['user-data-dir'], args['profile-directory'] || 'Default', 'Preferences'), {
|
package/cdp_ext/cdp_ext.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
const config =
|
|
1
|
+
const config = {
|
|
2
|
+
reconnect_ms: chrome.runtime.getManifest().cdp?.reconnect_ms ?? 5000,
|
|
3
|
+
}
|
|
2
4
|
const configured = chrome.storage.session.get('cdp_bootstrap').then(value => Object.assign(config, value.cdp_bootstrap))
|
|
3
5
|
const sessions = {}
|
|
4
6
|
const attached = {}
|
|
@@ -8,6 +10,47 @@ let disconnecting
|
|
|
8
10
|
let discovering = false
|
|
9
11
|
let auto_attach = {}
|
|
10
12
|
let bootstrapping
|
|
13
|
+
let checking = false
|
|
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 })
|
|
47
|
+
|
|
48
|
+
function report_connection(message) {
|
|
49
|
+
if (connection_message === message) return
|
|
50
|
+
connection_message = message
|
|
51
|
+
console.info('[cdp_ext] ' + message)
|
|
52
|
+
debug_update()
|
|
53
|
+
}
|
|
11
54
|
|
|
12
55
|
async function bootstrap(tab) {
|
|
13
56
|
const text = tab.pendingUrl ?? tab.url ?? ''
|
|
@@ -37,6 +80,8 @@ async function bootstrap(tab) {
|
|
|
37
80
|
}
|
|
38
81
|
if (changed) {
|
|
39
82
|
Object.assign(config, settings)
|
|
83
|
+
connection_message = undefined
|
|
84
|
+
debug_update()
|
|
40
85
|
await chrome.storage.session.set({ cdp_bootstrap: settings })
|
|
41
86
|
if (socket && socket.readyState !== WebSocket.CLOSED) {
|
|
42
87
|
await new Promise(resolve => {
|
|
@@ -81,6 +126,7 @@ function detached(source) {
|
|
|
81
126
|
targetId: String(source.tabId),
|
|
82
127
|
},
|
|
83
128
|
})
|
|
129
|
+
debug_update()
|
|
84
130
|
}
|
|
85
131
|
|
|
86
132
|
async function attach_tab(tabId) {
|
|
@@ -196,6 +242,7 @@ chrome.debugger.onEvent.addListener((source, method, params) => {
|
|
|
196
242
|
sessionId: params.sessionId,
|
|
197
243
|
}
|
|
198
244
|
else delete sessions[params.sessionId]
|
|
245
|
+
debug_update()
|
|
199
246
|
}
|
|
200
247
|
send({
|
|
201
248
|
method,
|
|
@@ -245,19 +292,57 @@ for (const [name, options] of Object.entries({
|
|
|
245
292
|
|
|
246
293
|
async function connect() {
|
|
247
294
|
await configured
|
|
248
|
-
if (
|
|
295
|
+
if (!config.browser || !config.websocket_url || !config.token) return
|
|
296
|
+
if (disconnecting || checking) return
|
|
249
297
|
if (socket?.readyState === WebSocket.CONNECTING) return
|
|
250
298
|
if (socket?.readyState === WebSocket.OPEN) return socket.send('')
|
|
251
|
-
const
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
299
|
+
const endpoint = config.websocket_url
|
|
300
|
+
const url = new URL(endpoint)
|
|
301
|
+
url.protocol = url.protocol === 'wss:' ? 'https:' : 'http:'
|
|
302
|
+
url.pathname = '/cdp_ext'
|
|
303
|
+
url.search = ''
|
|
304
|
+
url.hash = ''
|
|
305
|
+
let available = false
|
|
306
|
+
checking = true
|
|
307
|
+
try {
|
|
308
|
+
const response = await fetch(url, {
|
|
309
|
+
cache: 'no-store',
|
|
310
|
+
signal: AbortSignal.timeout(2000),
|
|
311
|
+
})
|
|
312
|
+
available = response.ok && await response.text() === 'CDP.js browser connection'
|
|
313
|
+
} catch (error) {
|
|
314
|
+
if (!(error instanceof TypeError) && error.name !== 'TimeoutError') throw error
|
|
315
|
+
} finally {
|
|
316
|
+
checking = false
|
|
317
|
+
}
|
|
318
|
+
if (endpoint !== config.websocket_url) return connect()
|
|
319
|
+
if (!available) {
|
|
320
|
+
report_connection('Waiting for CDP.js server at ' + endpoint + '; retrying automatically.')
|
|
321
|
+
return
|
|
322
|
+
}
|
|
323
|
+
const connection = socket = new WebSocket(endpoint)
|
|
324
|
+
connection.onopen = () => {
|
|
325
|
+
report_connection('Connected to CDP.js server at ' + endpoint + '.')
|
|
326
|
+
connection.send(JSON.stringify({
|
|
327
|
+
browser: config.browser,
|
|
328
|
+
extension_id: chrome.runtime.id,
|
|
329
|
+
token: config.token,
|
|
330
|
+
}))
|
|
331
|
+
}
|
|
257
332
|
connection.onmessage = async event => {
|
|
258
333
|
if (!event.data) return
|
|
259
334
|
const request = JSON.parse(event.data)
|
|
260
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()
|
|
261
346
|
let response
|
|
262
347
|
try {
|
|
263
348
|
let result
|
|
@@ -297,9 +382,14 @@ async function connect() {
|
|
|
297
382
|
},
|
|
298
383
|
}
|
|
299
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()
|
|
300
389
|
if (id != null && connection.readyState === WebSocket.OPEN) connection.send(JSON.stringify(response))
|
|
301
390
|
}
|
|
302
391
|
connection.onclose = () => {
|
|
392
|
+
report_connection('Waiting for CDP.js server at ' + endpoint + '; retrying automatically.')
|
|
303
393
|
discovering = false
|
|
304
394
|
disconnecting = Promise.all(Object.keys(attached).map(id => {
|
|
305
395
|
const source = { tabId: Number(id) }
|
|
@@ -323,5 +413,5 @@ async function start() {
|
|
|
323
413
|
chrome.runtime.onStartup.addListener(start)
|
|
324
414
|
chrome.alarms.onAlarm.addListener(connect)
|
|
325
415
|
chrome.alarms.create('cdp-reconnect', { periodInMinutes: 0.5 })
|
|
326
|
-
setInterval(connect, config.reconnect_ms
|
|
416
|
+
setInterval(connect, config.reconnect_ms)
|
|
327
417
|
start()
|
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,13 +64,14 @@
|
|
|
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"
|
|
71
|
-
},
|
|
72
|
-
"cdp": {
|
|
73
|
-
"websocket_url": "ws://127.0.0.1:9223",
|
|
74
|
-
"reconnect_ms": 5000
|
|
75
76
|
}
|
|
76
77
|
}
|
|
@@ -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"
|