{"id":23763723,"url":"https://github.com/tomashubelbauer/node-cdp-ws","last_synced_at":"2026-05-19T03:32:23.137Z","repository":{"id":107986185,"uuid":"314392389","full_name":"TomasHubelbauer/node-cdp-ws","owner":"TomasHubelbauer","description":"Pure Node websocket client","archived":false,"fork":false,"pushed_at":"2022-04-14T20:36:43.000Z","size":25,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-06-01T16:42:13.131Z","etag":null,"topics":["cdp","debugger","electron","websocket"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/TomasHubelbauer.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2020-11-19T23:14:19.000Z","updated_at":"2022-04-16T22:56:18.000Z","dependencies_parsed_at":null,"dependency_job_id":"c5bdd383-70c4-450c-87c4-fa6cfab46471","html_url":"https://github.com/TomasHubelbauer/node-cdp-ws","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/TomasHubelbauer/node-cdp-ws","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TomasHubelbauer%2Fnode-cdp-ws","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TomasHubelbauer%2Fnode-cdp-ws/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TomasHubelbauer%2Fnode-cdp-ws/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TomasHubelbauer%2Fnode-cdp-ws/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/TomasHubelbauer","download_url":"https://codeload.github.com/TomasHubelbauer/node-cdp-ws/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TomasHubelbauer%2Fnode-cdp-ws/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":264892028,"owners_count":23679208,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cdp","debugger","electron","websocket"],"created_at":"2024-12-31T22:13:22.442Z","updated_at":"2026-05-19T03:32:23.059Z","avatar_url":"https://github.com/TomasHubelbauer.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Node Chrome DevTools Protocol Web Socket\r\n\r\nPure Node web socket client talking to CDP (Chrome DevTools Protocol) web socket\r\nobtained by attaching to a process by its PID.\r\n\r\n## Installation\r\n\r\n`git submodule add https://github.com/tomashubelbauer/node-cdp-ws`\r\n\r\n## Usage\r\n\r\n### `(url: string, receive: function)`\r\n\r\n```js\r\nimport cdp from './node-cdp-ws/index.js';\r\n\r\nconst send = await cdp('ws://localhost:9229/${guid}', console.log);\r\n\r\n// https://chromedevtools.github.io/devtools-protocol/tot/Runtime/#method-evaluate\r\nsend({ id: 1, method: 'Runtime.evaluate', params: { expression: 'new Date().toLocaleTimeString()' } });\r\n```\r\n\r\n### `(pid: number, receive: function, port?: number = 9229)`\r\n\r\n```js\r\nimport cdp from './node-cdp-ws/index.js';\r\n\r\nconst send = await cdp(process.pid, console.log);\r\n\r\n// https://chromedevtools.github.io/devtools-protocol/tot/Runtime/#method-evaluate\r\nsend({ id: 1, method: 'Runtime.evaluate', params: { expression: 'new Date().toLocaleTimeString()' } });\r\n```\r\n\r\n## Development\r\n\r\nRun using `node test`.\r\n\r\nUse `chrome://inspect` in Chrome/Edge to be able to use dev tools to debug.\r\n\r\n### To-Do\r\n\r\n#### Test out with CDP methods which return large results, like Electron screenshot\r\n\r\nhttps://github.com/TomasHubelbauer/electron-inspect-require\r\n\r\nhttps://github.com/TomasHubelbauer/electron-self-screenshot\r\n\r\nThe `electron` example in `example` is WIP. To test with it, install Electron\r\nglobally using `npm i -g electron` and go to the `example/electron` directory\r\nand run `electron .` or skip installing Electron globally and use the command\r\n`npx electron .` instead of `electron .`.\r\n\r\nThe Electron application will print its PID. Split the terminal and run\r\n`node test ${pid}` providing the printed PID. This will attach a debugger to the\r\nElectron binary and work against it rather than own process.\r\n\r\n- [ ] Use `node-win-pid` and pass `electron` instead of the PID and look it up\r\n\r\nI looked through `electron-self-require` repository and it shows this snippet:\r\n\r\n```js\r\nconst electron = process.mainModule.require('electron')\r\nconst fs = process.mainModule.require('fs')\r\nconst webContents = electron.webContents.getAllWebContents()[0] // [1] is the shared process\r\nwebContents.capturePage(image =\u003e fs.writeFileSync('screenshot.png', image.toPNG()))\r\n// Look in `process.cwd()`\r\n```\r\n\r\nI used `node-win-pid` and in it `node test code.exe` to find the PID of the main\r\nVS Code window. I then used `process._debugProcess` to put it in debug mode and\r\nused `chrome://inspect` to open dev tools for it.\r\n\r\nThe dev tools instance for this main process PID shows one JS context:\r\n*Electron Main Context*.\r\n\r\nI ran `process.mainModule` in it and checked the paths and the child modules.\r\nI didn't see anything relating to the API or the extensions.\r\n\r\nNext up I opened `C:\\Users\\…\\AppData\\Local\\Programs\\Microsoft VS Code\\` in Code\r\nand searched for various API method names. I know that the main process module\r\npath is in `resources\\app\\out` so I limited my search to there for now.\r\n\r\nI was able to identity these files as potential interests:\r\n\r\n- vs\\workbench\\services\\extensions\\node\\extensionHostProcess.js\r\n- vs\\workbench\\services\\extensions\\worker\\extensionHostWorker.js\r\n\r\nI am not sure how to get to these modules from the main process I have debug\r\naccess to. The snipped there looks useless as I'm pretty sure the access to the\r\nAPI is not from the render process but from the main process in some way.\r\n\r\nI tried running `process.mainModule.require('vscode')` to no luck in the main\r\nprocess. I also tried `process.mainModule.children[${index}].require('vscode')`\r\nfor all the child modules of the main module, again, to no luck.\r\n\r\n```js\r\nprocess.mainModule.require('./vs/workbench/services/extensions/node/extensionHostProcess.js')\r\n```\r\n\r\nThis failed in a new way: *`define` is not defined*, not *Cannot find module*.\r\n\r\nThe next step should be to attach to all `code.exe` PIDs, not just the main one,\r\nand seeing if one of those processes has a path `extensionHostProcess.js` or has\r\nsuch path in its `children` modules. Use either of:\r\n\r\n- `tasklist /FI \"ImageName eq Code.exe\"`\r\n- `wmic process get processid,executablepath|findstr Code.exe`\r\n\r\nIt always seems to return the processes in the same order (main, some 3/4 ones\r\nwhich are not possible to attach to), then more processes. This is good, because\r\nsince we start the debugging externally, we cannot  close it. Normally closing\r\nit would be done using `require('inspector').close()` but that has to be done in\r\nthe process and when doing it using the devtools at `chrome://inspect`, it does\r\nnothing. So, we are forced to restart VS Code after each process resetting all\r\nthe PIDs, so the stable order of the processes means we do not have to try and\r\ndistinguish which processes we already checked or not. We just go one by one:\r\n\r\n- Run `node -e \"process._debugProcess(…)\"` with the PID at the current index\r\n- Check http://localhost:9229/json and verify the PID matches there\r\n- Refresh `chrome://inspect` until it shows and verify the PID matches there\r\n- Evaluate `process.mainModule.filename` in the dev tools and note it\r\n\r\nThe processes tried and paths found:\r\n\r\n1. `resources\\app\\out\\main.js`\r\n2. unattachable\r\n3. unattachable\r\n4. unattachable\r\n5. unattachable\r\n6. undiscoverable\r\n7. `resources\\app\\out\\bootstrap-fork.js`\r\n8. unattachable\r\n9. `resources\\app\\extensions\\json-language-features\\server\\dist\\node\\jsonServerMain.js`\r\n\r\n*unattachable* means `process._debugProcess` throws *The system cannot find the file specified.*.\r\n\r\n*undiscoverable* means `process._debugProcess` seemingly succeeds, but\r\n`chome://inspect` lists nothing and http://localhost:9229 is unreachable. I have\r\nlater found this is actually the extension host process. Read on…\r\n\r\nThe test - kill Code - advance to the next index, repeat cycle has been repeated\r\ntwice to really make sure the order of the processes is always the same and it\r\nis.\r\n\r\nThis leaves us with two processes we can control only: `main.js` and\r\n`jsonServerMain.js`. Nothing like `extensionHostProcess.js` or anything. I guess\r\nthat means we have to find our way to the extension host process from the main\r\nprocess somehow.\r\n\r\nIt is also possible that the undiscoverable process is what we need and it just\r\nlistens on a different port, but I find it unlikely and have not tested it.\r\n\r\nIt is interesting that in the main module, these commands behave differently:\r\n\r\n`require('./vs/workbench/services/extensions/node/extensionHostProcess.js')`\r\n\r\nPrint *Cannot find module './vs/workbench/services/extensions/node/extensionHostProcess.js'*\r\n\r\n`process.mainModule.require('./vs/workbench/services/extensions/node/extensionHostProcess.js')`\r\n\r\nPrints *define is not defined*\r\n\r\nI have used VS Code \u003e Help \u003e Process Explorer to determine the extension host\r\nprocess is the one which accepts `console._debugProcess` but will not listen on\r\nhttp://localhost:9229 or show up in chrome://inspect.\r\n\r\nAlso the dev tools that open in VS Code \u003e Help \u003e Toggle Develop Tools are for\r\nthe Electron renderer process, #3 in the PID list, unattachable.\r\n\r\nI found something I thought might have been preventing the extension host from\r\nbeing debuggable, as per https://github.com/microsoft/vscode/issues/85490 and\r\nthe commits referenced therein:\r\n\r\n```js\r\n,function(){for(let e=0;e\u003cprocess.execArgv.length;e++)\"--inspect-port=0\"===process.execArgv[e]\u0026\u0026(process.execArgv.splice(e,1),e--)}()\r\n```\r\n\r\nRemoving it did not help make the extension host debuggable.\r\n\r\nI also found an interesting tooltip when hovering out the extension host in the\r\nProcess Explorer window:\r\n\r\n![](ext-host.png)\r\n\r\nThis is looking kinda janky, perhaps rather than hoping to obtain the interface\r\nobject for the VS Code by attaching to the main VS Code process and navigating\r\nour way to it, it might be easier to start VS Code with a CLI argument telling\r\nit to also load a wrapper extension (look into making single-file extensions)\r\nwhich would bootstrap the CDP WS logic, run the scenario VS Code API commands,\r\nrecognize the scenarion finishing and then would use VS Code CDP WS to capture a\r\nscreenshot and save it.\r\n\r\nThis circles back to https://github.com/TomasHubelbauer/code-extension-screencast\r\n\r\nUse https://github.com/TomasHubelbauer/vscode-barebones-extension to decouple\r\nthe screenshot taking from test running. This way in one step, `npm test` is run\r\nas usual, and in another, `code --extensions-dir` is invoked this way, pointing\r\nto a directory with an extension which uses the VS Code API to carry out a demo\r\nof whatever the user wants to demo, including the extension should they choose\r\nto show it (by making sure the extension is also in the directory used for the\r\n`--extensions-dir` CLI argument).\r\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftomashubelbauer%2Fnode-cdp-ws","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftomashubelbauer%2Fnode-cdp-ws","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftomashubelbauer%2Fnode-cdp-ws/lists"}