{"id":26600766,"url":"https://github.com/niminem/chromedevtoolsprotocol","last_synced_at":"2025-08-12T05:08:09.913Z","repository":{"id":229518182,"uuid":"776941148","full_name":"Niminem/ChromeDevToolsProtocol","owner":"Niminem","description":"Low-level Nim wrapper for Chrome DevTools Protocol (CDP). Bend Chrome to your will with complete control over your browser. Scrape dynamic webpages, create browser automations, and beyond. Wield responsibly ;)","archived":false,"fork":false,"pushed_at":"2024-07-11T00:46:32.000Z","size":204,"stargazers_count":37,"open_issues_count":2,"forks_count":3,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-09T16:44:03.062Z","etag":null,"topics":["cdp","chrome-devtools-protocol","chromedevtoolsprotocol","nim","nim-lang","nim-library"],"latest_commit_sha":null,"homepage":"https://niminem.github.io/CDP/cdp.html","language":"Nim","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Niminem.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","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}},"created_at":"2024-03-24T21:20:50.000Z","updated_at":"2025-02-27T11:58:15.000Z","dependencies_parsed_at":"2024-03-24T22:27:37.069Z","dependency_job_id":"c284eab3-8b76-45ad-a1ec-d43772cec035","html_url":"https://github.com/Niminem/ChromeDevToolsProtocol","commit_stats":null,"previous_names":["niminem/chromedevtoolsprotocol"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/Niminem/ChromeDevToolsProtocol","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Niminem%2FChromeDevToolsProtocol","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Niminem%2FChromeDevToolsProtocol/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Niminem%2FChromeDevToolsProtocol/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Niminem%2FChromeDevToolsProtocol/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Niminem","download_url":"https://codeload.github.com/Niminem/ChromeDevToolsProtocol/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Niminem%2FChromeDevToolsProtocol/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":270005591,"owners_count":24510939,"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","status":"online","status_checked_at":"2025-08-12T02:00:09.011Z","response_time":80,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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","chrome-devtools-protocol","chromedevtoolsprotocol","nim","nim-lang","nim-library"],"created_at":"2025-03-23T18:34:57.530Z","updated_at":"2025-08-12T05:08:09.891Z","avatar_url":"https://github.com/Niminem.png","language":"Nim","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ChromeDevToolsProtocol\nLow-level Nim wrapper for [Chrome DevTools Protocol (CDP) v1.3 stable](https://chromedevtools.github.io/devtools-protocol/1-3/).\n\n***Bend Chrome to your will*** with complete control over your browser. Scrape dynamic webpages, create browser automations, and beyond. Wield responsibly ;)\n\n\u003e **Chrome v112 or higher recommended**. [It's better](https://developer.chrome.com/docs/chromium/new-headless),\n    as the new `headless` flag uses the same browser binary rather than a completely seperate implemenation. It's likely your version of Chrome already has a more recent version.\n\nThis library is cross-platform (Windows, Mac, Linux) and supports both the C and C++ backends.\n\n#### NOTE:\n\n`cdp` is intended to be a low-level wrapper of CDP for use in a webcrawler and a few browser automation projects for my company [SEO Science](https://www.seo.science). We only target v1.3 stable version of CDP as we want to reduce the\namount of maintenance overhead, and ensure reliability of the library in\nproduction use.\n\nWe may include some of the experimental Domains, methods, and events in the future. **PRs welcome**.\n\nIf you would like to use an experimental feature (like the [Animation Domain](https://chromedevtools.github.io/devtools-protocol/tot/Animation/)), you certainly can via the `sendCommand` procedure. [Details below](#using-experimental-features).\n\n## Dependencies\n\n**A Chrome browser**. Pretty cool right? No webdriver binary. No other dependencies outside of what you probably already have on your system.\n\n*In the future, we do plan to add support for Chromium and Edge.*\n\n## Installation\n\nInstall from nimble: `nimble install cdp`\n\nAlternatively clone via git: `git clone https://github.com/Niminem/ChromeDevToolsProtocol`\n\n## Basic Usage\n\nCheck out the [tests directory](https://github.com/Niminem/ChromeDevToolsProtocol/tree/main/tests) for more examples.\n\n```nim\nimport std/[json, asyncdispatch]\nimport cdp\n\nproc main() {.async.} =\n    let\n        browser = await launchBrowser() # launch a new browser\n        tab = await browser.newTab() # open a new tab\n    await tab.enablePageDomain() # enable the Page domain (for monitoring page events)\n    discard await tab.navigate(\"https://github.com/Niminem\") # navigate to a page\n    discard await browser.waitForSessionEvent(tab.sessionId, $Page.domContentEventFired) # wait for page to load\n    let\n        resp = await tab.evaluate(\"document.title\", %*{\"returnByValue\": true}) # evaluate a script on the page\n        title = resp[\"result\"][\"result\"][\"value\"].to(string) # get the title of the page\n    echo \"Title of the page: \", title # Title of the page: Niminem (Leon Lysak) · GitHub\n    await tab.disablePageDomain() # disable the Page domain\n    await browser.close() # close the browser / cdp, websocket, delete userdatadir, terminate browser process\n\nwaitFor main()\n```\n\n## Getting Started With CDP\n\nI highly recommend reading Aslushnikov's [README](https://github.com/aslushnikov/getting-started-with-cdp) on using Chrome DevTools Protocol as a quick primer. I'll try my best to explain the concepts\nand how they relate to this API.\n\n### Introduction\n\nThe Chrome DevTools Protocol allows for tools to instrument, inspect, debug and profile Chromium, Chrome and other Blink-based browsers (like Microsoft Edge).\n\nEven Chrome DevTools uses this protocol and the team maintains its API.\n\n### Protocol Fundamentals\n\nWhen Chrome is started with a `--remote-debugging-port=\u003cnumber\u003e` flag, it starts a Chrome DevTools Protocol server and creates a WebSocket URL. Clients can create a WebSocket to connect to the URL and start sending CDP commands.\n\nChrome DevTools protocol is mostly based on [JSONRPC](https://www.jsonrpc.org/specification)- each comand is a JSON object with an `id`, a `method`, and an optional `params` (JSON object).\n\nA few things to keep in mind:\n- Every command that is sent over to CDP must have a unique `id` parameter. Message responses will be delivered over websocket and will have the same `id`.\n- Incoming WebSocket messages *without* an `id` parameter are **protocol events**.\n- Message order is important in CDP. For example, protocol events that occur as the result of a CDP command will be reported before the response to that CDP command.\n- There's a top-level \"browser\" target that always exists. More on this in the [next section](#targets--sessions).\n\nThis library provides a level of abstraction by wrapping the various CDP commands (methods) in v1.3 stable:\n\n```nim\n# Navigates current page (tab) to the given URL.\nproc navigate(tab: Tab; url: string; params: JsonNode): Future[JsonNode]\nproc navigate(tab: Tab; url: string): Future[JsonNode]\n# reference: https://chromedevtools.github.io/devtools-protocol/1-3/Page/#method-navigate\n```\n\n`cdp` also abstracts away other things, like the management of the unique `id` parameter for commands.\n\n### Targets \u0026 Sessions\n\nChrome DevTools protocol has APIs to interact with many different parts of the browser - such as pages (this library refers to them as *tabs*), serviceworkers and extensions. These parts are called **Targets** and can be fetched/tracked using the [Target domain](https://chromedevtools.github.io/devtools-protocol/1-3/Target/).\n\nWhen a client wants to interact with a target using CDP, it has to first attach to the target using `Target.attachToTarget` command. The command will establish a protocol session to the given target and return a `sessionId`.\n\nIn order to submit a CDP command to the target, every message should also include a `sessionId` parameter next to the usual JSONRPC’s `id`.\n\nThis library provides a level of abstraction for a page (tab) target:\n\n```nim\n...\nlet tab = await browser.newTab() # opens a new page (tab)\ndiscard await tab.navigate(\"https://github.com/Niminem\") # navigate to a page\n...\n```\n\nWith `browser.newTab()`, the procedure will create a target (page), attach to it, and the sessionId (a field of\nthe returned `Tab` object) will be used in all future commands from the `Tab`.\n\nSome commands **set state** which is stored **per session** (ex: `Runtime.enable` and `Targets.setDiscoverTargets`). Each session is initialized with a set of domains, the exact set depends on the attached target. For example, sessions connected to a browser don't have a \"Page\" domain, but sessions connected to pages do.\n\nWe call sessions attached to a Browser target *browser sessions*. Similarly, there are *page sessions*, *worker sessions* and so on. In fact, the WebSocket connection is an implicitly created **browser session**.\n\nAlthough this library currently provides an abstraction over a Page target (tab) and the implicit Browser target, you can create other targets with the generic `sendCommand` procedure. More on this [below](#using-experimental-features).\n\n### Session Hierarchy\n\nWhen a client connects over the WebSocket to the launched Chrome browser, a *root* browser session is created. This session is the one that receives commands if there's no `sessionId` specified. Essentially, this refers to the `Browser` object returned from the `launchBrowser` procedure. Later on, when the root browser session is used to attach to a page target, a new page session created (the `Tab` object).\n\nThe page session is created from inside the browser session and thus is a **child** of the browser session. When a parent session closes (ex: `Target.detachFromTarget`), all of its child sessions are closed as well.\n\nAs of `cdp` 0.1.0, the `browser.close()` procedure will close the browser session (thus closing all of the child sessions).\nMore granular closing procedures must be implemented on your end for now.\n\n### Stable vs Experimental methods\n\nThe Chrome DevTools Protocol has stable and experimental parts. Events, methods, and sometimes whole domains might be marked as experimental. DevTools team doesn't commit to maintaining experimental APIs and changes/removes them regularly.\n\n**!!! USE EXPERIMENTAL APIS AT YOUR OWN RISK !!!**\n\nAs history has shown, experimental APIs do change quite often. If possible, stick to the stable protocol (the wrapped commands\nand events from this library).\n\n## API Overview\n\nCheck out the full API documentation [here](https://niminem.github.io/CDP/cdp.html).\n\n### The Browser Object\n\n```nim\nproc launchBrowser*(userDataDir = \"\";\n                    portNo = 0; headlessMode = HeadlessMode.On;\n                    chromeArguments: seq[string] = @[]): Future[Browser] {.async}\n```\n\nUsing the `cdp` library begins with `Browser` instance.\n\nUse this Browser instance (browser Target / browser session) to interact with the appropriate CDP domains/methods.\n\n`launchBrowser` Launches a new Chrome browser instance and returns a `Browser` object.\n\n- `userDataDir` parameter can be used to specify a directory where the\nbrowser's user data will be stored. If an empty string is passed, a temporary\ndirectory will be created and used.\n- `portNo` parameter can be used to specify a port number for the browser to\nlisten on. If `portNo` is 0, Chrome will choose a random port.\n- `headlessMode` parameter can be used to specify whether the browser should be\nlaunched in headless mode or not. `HeadlessMode.On` (the default) will launch\nthe new version of Chrome headless mode (for Chrome \u003e= v112). **Use `HeadlessMode.Legacy`\nto launch the browser in headless mode for Chrome \u003c v112.** The new headless mode is\nrecommended as the old version of headless mode will be deprecated, and the new version\nis the actual browser rather than a separate browser implementation.\n- `chromeArguments` parameter can be used to pass additional arguments to the\nChrome browser instance. For a list of all available arguments, see:\nhttps://peter.sh/experiments/chromium-command-line-switches/ or\nhttps://github.com/puppeteer/puppeteer/blob/main/packages/puppeteer-core/src/node/ChromeLauncher.ts\nfor a list of arguments used by Puppeteer (a high-level API for CDP using NodeJs).\n\nThe following command-line arguments are *always* passed to Chrome:\n- `--remote-debugging-port=\u003cportNo\u003e`\n- `--user-data-dir=\u003cuserDataDir\u003e`\n- `--no-first-run`\n- `--headless=new` or `--headless` (if `headlessMode` is `HeadlessMode.On` (default) or `HeadlessMode.Legacy`).\nIf `HeadlessMode.Off` is passed, Chrome will open a visible window.\n\n**IMPORANT: Make sure you call `browser.close()` before quitting or ending your program or else you will have a [zombie process](https://nim-lang.org/docs/osproc.html#close%2CProcess).**\n\n### The Tab Object\n\n```nim\nproc newTab*(browser: Browser): Future[Tab] {.async.}\n```\n\n`newTab` procedure creates a new tab (Page) with the browser instance and returns a `Tab` object.\n\nUse this `Tab` object to interact with a page session. You can monitor and intercept network events, execute javascript, and so much more via enabling the appropriate domains. Page sessions will be used the most.\n\n```nim\n# Pulled from the 'Basic Usage' example above\n...\nawait tab.enablePageDomain() # enable the Page domain (for monitoring page events)\ndiscard await tab.navigate(\"https://github.com/Niminem\")\ndiscard await browser.waitForSessionEvent(tab.sessionId, $Page.domContentEventFired) # wait for page to load\n...\n# It's good practice to disable the domain when you are done\nawait tab.disablePageDomain()\n...\n```\n\nEnabling the **Page Domain** allows you monitor page events, such as the `domContentEventFired` event. With this access you\ncan, for example, scape various parts of a web page after the DOM is ready. Some Domains may need to be enabled in order to use them like\nthis one. In the [API documentation](https://niminem.github.io/CDP/cdp.html), I've provided direct references to each\ndomain and each domain's methods/events.\n\nNotice the `tab.navigate` call. This is a wrapped method for the Tab object, directly corresponding to the [navigate CDP\nmethod](https://chromedevtools.github.io/devtools-protocol/1-3/Page/#method-navigate). ALL CDP methods and events for browser and page targets have been wrapped for v1.3 stable.\n\n### Global \u0026 Session Event Callbacks\n\n```nim\ntype\n    SessionId* = string\n    ProtocolEvent* = string\n    EventCallback* = proc(data: JsonNode) {.async.}\n# Adds a callback function to the global event table for the specified event\nproc addGlobalEventCallback*(browser: Browser; event: ProtocolEvent; cb: EventCallback)\n# Adds a callback function to the session event table for the specified event.\nproc addSessionEventCallback*(browser: Browser; sessionId: SessionId;\n                              event: ProtocolEvent; cb: EventCallback)\n# Returns a `Future` that completes when the specified global event is received.\nproc waitForGlobalEvent*(browser: Browser; event: ProtocolEvent): Future[JsonNode] {.async.}\n# Returns a `Future` that completes when the specified session event is received.\nproc waitForSessionEvent*(browser: Browser; sessionId: string;\n                          event: ProtocolEvent): Future[JsonNode] {.async.}\n```\n\nAs of `cdp` 0.1.0, there are two kinds of callbacks- one for handling **Global events** (those without an `id` parameter)\nand the other for **Session events** (those corresponding to a session, like a Tab/Page, which have a `sessionId` parameter).\n\nThere are two ways you can use them.\n\nEither you register a callback procedure that executes each time the CDP event occurs via `addGlobalEventCallback` or `addSessionEventCallback`, or you can use the `waitFor` variant. `waitFor` should be used to suspend execution until the event occurs, like the example above where we waited for `Page.domContentEventFired` before interacting with the tab.\n\n**NOTE: Currently, there can only be one callback per *Global event*, and only one callback per event for\neach session for *Session events*. If you try adding another callback for the same global/session event, the current callback will be overwritten.**\n\n`deleteGlobalEventCallback` and `deleteSessionEventCallback` can be called to delete the event callbacks.\n\n```nim\nimport std/[json, asyncdispatch]\nimport cdp\n\nproc logGlobalEvent(event: JsonNode) {.async.} = echo \"Logging Global Event: \" \u0026 event[\"method\"].to(string)\nproc logSessionEvent(event: JsonNode) {.async.} = echo \"Logging Session Event: \" \u0026 event[\"method\"].to(string)\n\nproc main() {.async.} =\n    let experimentalGlobalEvent = \"Target.attachedToTarget\"\n    let browser = await launchBrowser()\n    browser.addGlobalEventCallback(experimentalGlobalEvent, logGlobalEvent)\n    let tab = await browser.newTab()\n    browser.addSessionEventCallback(tab.sessionId, $Page.frameNavigated, logSessionEvent)\n    await tab.enablePageDomain()\n    discard await tab.navigate(\"https://github.com/Niminem\")\n    await tab.disablePageDomain()\n    browser.deleteGlobalEventCallback(experimentalGlobalEvent)\n    browser.deleteSessionEventCallback(tab.sessionId, $Page.frameNavigated)\n    await browser.close()\n\nwaitFor main()\n```\n\n### Using Experimental Features\n\n`cdp` wraps all CDP methods via the `sendCommand` procedure.\n\n```nim\nproc sendCommand*(browser: Browser; mthd: string; params: JsonNode): Future[JsonNode] {.async.}\nproc sendCommand*(browser: Browser; mthd: string): Future[JsonNode] {.async.}\nproc sendCommand*(tab: Tab; mthd: string; params: JsonNode): Future[JsonNode] {.async.}\nproc sendCommand*(tab: Tab; mthd: string): Future[JsonNode] {.async.}\n```\n\nWrapped CDP method example:\n\n```nim\nproc deleteCookies*(tab: Tab; name: string; params: JsonNode) {.async.} = # optional params exist\n    params[\"name\"] = newJString(name)\n    discard await tab.sendCommand(\"Network.deleteCookies\", params)\nproc deleteCookies*(tab: Tab; name: string) {.async.} = # only 'name' parameter is required\n    discard await tab.sendCommand(\"Network.deleteCookies\", %*{\"name\": name})\n```\n\nYou can easily use any experimental method with this generic procedure.\n\nSimilarly, you can easily use any experimental *CDP event* with the callback functions as they are just strings.\n`cdp` provides enums for *stable* events as a convenience so I don't accidently shoot myself in the foot:\n\n```nim\ntype\n    Network* {.pure.} = enum\n        dataReceived = \"Network.dataReceived\",\n        eventSourceMessageReceived = \"Network.eventSourceMessageReceived\",\n        loadingFailed = \"Network.loadingFailed\",\n        ...\n# used like this:\n...\nbrowser.addGlobalEventCallback($Network.dataReceived, procName)\n...\n# alternatively for experimental events:\n...\nbrowser.addGlobalEventCallback(\"Target.attachedToTarget\", procName)\n..\n```\n\n### Other API Notes\n- ALL commands provide a generic `Future[Json]` response containing the `id` of the method called. This is how we can map sent commands to the appropriate responses as CDP is a single multiplexed web socket connection (I think I said that right). Anyways, use the `discard` statement as necessary.\n- If any commands should have a non-generic response (aka something you need to parse and use), it is still of\n        the same `Future[Json]` type. This is because some CDP `return objects` return optional\n        parameters. Example: [DOM.getNodeForLocation](https://chromedevtools.github.io/devtools-protocol/1-3/DOM/).\nThere is no efficient way to directly map them all into their return objects.\n- Exception handling is basic, accounting mostly for setup and Chrome process stuff. For now, you have to roll your own for CDP methods. I'm certain that CDP provides error message/description in the responses you receive.\n- There is no getting around it. You'll need to become familiar with CDP itself and what the different domains/events provide (depending on what you're trying to accomplish of course), but when you do- you will have complete control over your browser. The only limit will be your imagination.\n\n## Todo List:\n- Look into basic exception handling for errors in CDP responses\n- Add support for Chromium (Windows and Mac as this exists for Linux) and Microsoft Edge (Windows)\n- Add `close` procedure for `Tab` (I think we should destroy the object after it's dettached from the target)\n- Add convenience functions for handling global/session events with the `Tab` object\n- Investigate if there's a need for supporting multiple global and session events (my inclination is no)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fniminem%2Fchromedevtoolsprotocol","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fniminem%2Fchromedevtoolsprotocol","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fniminem%2Fchromedevtoolsprotocol/lists"}