I don’t write low-level code that often — mostly PowerShell automation these days. But sometimes, either by inspiration or necessity, I do. This time, I needed to generate classes to interact with external systems. So I’m documenting the whole thing here — just in case I forget.
Chrome DevTools Protocol (CDP)
One day I got a weird itch — to build a tool that would capture what a web page is trying to go. When a page loads in the browser, it can do all sorts of things: load images, fetch and execute scripts, which in turn may load more scripts, which may fetch even more… and so on, infinitely. I really wanted to see that entire stream of events, capture it, and analyze it.
Sure, browser devtools can show you this manually. But what if you want to process it automatically — to update a firewall rule or create a visualization, for example? There are tools like Selenium or similar that can sort of do this. But they come with their own browsers, dump stuff into random places, and generally act like black boxes. I wanted something simpler — and ideally, something that didn’t make me write too much code.
Turns out Chromium has a thing called Chrome DevTools Protocol (CDP). In short, it’s a browser control protocol over WebSocket. If you launch the browser with specific flags, you can connect to it and drive it programmatically.
Problem is, I couldn’t find a .NET library that did just that — without all the bloat of browser management. And honestly, I wanted to tinker. There’s Puppeteer, sure, but that’s JavaScript. There’s Microsoft Playwright, but their browser installation process is… weird. Who knows where those things end up.
Along the way I also discovered that even raw Chromium tracks you — but that’s a different story.
Eventually, I stumbled upon a “code generator”. Sounds easy enough: clone, build, run, and you’re done. But — surprise — it didn’t work out of the box. Had to dig in.
Turns out the generator downloads a protocol definition and runs a Python script to turn it into a JSON version. The script was originally meant to run via IronPython.
Following the trend, I thought — why not replace IronPython with CSnakes, a shiny bridge between Python and .NET. And it worked — got it running in a couple of hours.
On top of that, I learned about Handlebars, a templating engine the generator uses to produce C# code: both for the CDP protocol and the browser session manager.
Long story short, with some brute force, ChatGPT, and sheer willpower, I got the generator working. I broke its CLI argument parsing in the process, but hey — it still generates code. Just always into the default path. To make it work, I:
- swapped IronPython for CSnakes
- replaced WebSocket4Net with System.Net.WebSockets
- updated all the NuGet packages
- did a bunch of other things I already forgot
And in the end, I had a working version that:
- connects to the browser
- sends commands
- subscribes to events
Why build a custom generator in the first place in this case? Because Google apparently didn’t bother to use JSON Schema — they seem to generate JSON from some internal format. I think. Maybe.
Working with a Real JSON Schema (Still Not Painless)
Second front: I wanted to hook up Vega to PowerShell. But for newer schema versions — no ready-to-use classes. Or at least I didn’t find any.
And — whether I missed it in the docs or just did something wrong — I didn’t find the schema the first time. Found it the second time though, right here. Things got easier from there. There’s quicktype, which, thankfully, does a pretty decent job.
But of course — there’s a catch. First off, it couldn’t fetch the schema from a URL like it claims to. Had to download it manually. Maybe I did something wrong. And then there’s patternProperties
— this thing just doesn’t work. Some code ends up malformed.
Honestly, I have no idea what the authors wanted to achieve with those regex-based properties — but they’re there. And quicktype code generation is broken for them. I didn’t dive into fixing that — just patched the output manually. Maybe someday I’ll look deeper.
"patternProperties": {
"^(?!interactive|name|style).+$": {
"$ref": "#/definitions/encodeEntry"
}
}
In the end — for my use case — it worked well enough. But as for what I actually needed it for… I’ll save that for another post.