Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

browser-interception

Minimal examples of intercepting and modifying browser requests using the Chrome DevTools Protocol (CDP) — the same mechanism Playwright uses under the hood for page.route().

No Playwright. No CDP client library. Just a WebSocket and raw JSON.


How it works

Chrome's Fetch domain lets you pause requests before they hit the network and decide what to do with them:

Page.navigate(url)
        │
        ▼
  Chrome loader
        │
        ▼
Fetch.requestPaused        ← Chrome tells you about the request
        │
   ┌────┴────┐
   │         │
fulfillRequest    continueRequest
   │         │
   ▼         ▼
local       network
response

Three CDP commands cover both cases:

Command What it does
Fetch.enable Tell Chrome which requests to pause and when
Fetch.fulfillRequest Provide the response yourself — nothing goes to the network
Fetch.continueRequest Let the request proceed, optionally with modified headers/url/body

Examples

intercept-fulfill — replace with local response

src/intercept-fulfill.mjs

Navigates to a hostname that does not exist. Before Chrome even attempts DNS, we intercept the request and return a local HTML page. The tab loads successfully at the nonexistent URL.

Key calls:

await cdp.send('Fetch.enable', {
  patterns: [{ urlPattern: 'https://app.this-does-not-exist.example/', requestStage: 'Request' }],
});

// Chrome fires Fetch.requestPaused — we answer:
cdp.send('Fetch.fulfillRequest', {
  requestId,
  responseCode: 200,
  responseHeaders: [{ name: 'Content-Type', value: 'text/html; charset=utf-8' }],
  body: Buffer.from('<h1>Served locally</h1>').toString('base64'),
});

intercept-modify — inject a header, let it through

src/intercept-modify.mjs

Navigates to https://httpbun.com/get. We intercept the request, inject X-Intercepted: true into its headers, then call continueRequest. Chrome sends the modified request to the network. The httpbun response echoes the injected header back, proving Chrome sent it.

Key calls:

await cdp.send('Fetch.enable', {
  patterns: [{ urlPattern: 'https://httpbun.com/get', requestStage: 'Request', resourceType: 'Document' }],
});

// Chrome fires Fetch.requestPaused — we answer:
cdp.send('Fetch.continueRequest', {
  requestId,
  headers: [...existingHeaders, { name: 'X-Intercepted', value: 'true' }],
});

Running

1. Install dependencies

npm install

2. Start Chrome with remote debugging

npm run chrome

This launches Chrome with --remote-debugging-port=9222, creating a temporary profile directory automatically. If Chrome is already running on port 9222 it exits immediately.

3. Run an example

node demo.mjs fulfill   # serves local HTML at a nonexistent URL
node demo.mjs modify    # injects a header into a real request

# or via npm
npm run fulfill
npm run modify

demo.mjs starts Chrome automatically if it isn't running yet, then runs the example.


Project structure

src/
  cdp.mjs                  raw WebSocket CDP transport (connect, send, on)
  intercept-fulfill.mjs    intercept + fulfill with local response
  intercept-modify.mjs     intercept + modify headers + continue to network
scripts/
  start-chrome.mjs         launches Chrome with remote debugging

src/cdp.mjs

The transport is intentionally minimal — it is not a library:

const cdp = await connect('http://localhost:9222');

await cdp.send('Fetch.enable', { patterns: [...] });
cdp.on('Fetch.requestPaused', ({ requestId, request }) => { ... });

connect() creates a new Chrome tab via PUT /json/new, opens its WebSocket debugger URL, and returns { send, on }. That is all it does.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages