How to Extend Chrome DevTools for React Native Network Debugging
This article explains how the NetEase Cloud Music team built a custom React Native debugging tool by extending Chrome DevTools, covering the devtools frontend module loading, React Native Debugger internals, Chrome DevTools Protocol, and a proxy-based solution to display network requests within the inspector.
Chrome DevTools Overview
Chrome DevTools is a front‑end debugging tool built into Chrome. Web applications communicate with the DevTools frontend via the Chrome DevTools Protocol (CDP) over a WebSocket connection, allowing the inspected app’s data to be displayed in the DevTools UI.
The open‑source project ChromeDevTools/devtools-frontend provides the DevTools frontend code. The version used in this article is 3964.
DevTools Frontend Module Loading
Start Chrome with a remote‑debugging port: --remote-debugging-port=9222 Open http://localhost:9222/ to list debugging targets, e.g.:
http://localhost:9222/devtools/inspector.html?ws=localhost:9222/devtools/page/7B2421304FE8EF659B264D4F476083DAThe inspector.html loads Runtime.js and inspector.js. inspector.js simply calls: Runtime.startApplication('inspector'); Module loading proceeds as follows:
Read module.json to obtain module metadata.
Instantiate a new Runtime, establishing Runtime → Module → Extension dependencies.
Load core module resources (scripts and CSS).
Start the core module entry point ( Main.Main).
Inspector Startup
The inspector.json file shows that the inspector inherits from devtools_app and adds a screencast module for live page snapshots:
{
"modules": [
{"name": "screencast", "type": "autostart"}
],
"extends": "devtools_app",
"has_html": true
}The devtools_app definition includes modules such as network, elements, browser_debugger, etc., and extends shell, which loads core modules like bindings, common, protocol, and ui:
{
"modules": [
{"name": "bindings", "type": "autostart"},
{"name": "common", "type": "autostart"},
{"name": "protocol", "type": "autostart"},
...
],
"extends": "shell",
"has_html": true
}The inspector establishes a WebSocket connection using the ws query parameter:
SDK._createMainConnection = function() {
const wsParam = Runtime.queryParam('ws');
const wssParam = Runtime.queryParam('wss');
if (wsParam || wssParam) {
const ws = wsParam ? `ws://${wsParam}` : `wss://${wssParam}`;
SDK._mainConnection = new SDK.WebSocketConnection(ws, SDK._websocketConnectionLost);
} else if (InspectorFrontendHost.isHostedMode()) {
SDK._mainConnection = new SDK.StubConnection();
} else {
SDK._mainConnection = new SDK.MainConnection();
}
return SDK._mainConnection;
};React Native Debugger Process
React Native Debugger is an Electron‑based standalone program built on top of the official remote‑debugging feature, with added react-devtools-core and redux-devtools-extension support.
When remote debugging is enabled, the workflow is:
The app sends a /launch-js-devtools request to the server, opening a debugger tab and establishing a socket.
The tab loads the debugger‑ui page and creates a socket connection to /debugger-proxy?role=debugger&name=Chrome.
A worker runs debuggerWorker.js to execute the bundled JavaScript.
The inspector communicates with the worker to debug the React Native app.
Network requests made via fetch or XMLHttpRequest in React Native can be captured in the DevTools frontend, but the Cloud Music app required a custom solution to forward client‑side network data into the DevTools Network panel.
Chrome DevTools Protocol (CDP)
CDP is a WebSocket‑based protocol that groups capabilities into domains. Each domain defines Methods, Events, and Types.
Method : request/response pattern identified by a message ID.
Event : publish/subscribe notification.
Type : data structures used in the protocol.
Example Method call:
request: {"id":1,"method":"Page.canScreencast"}
response: {"id":1,"result":{"result":false}}Example Event:
{"method":"Network.loadingFinished","params":{"requestId":"14307.143","timestamp":1424097364.31611,"encodedDataLength":0}}Methods can be sent from Chrome’s “Protocol Monitor” or programmatically from Electron.
Proxy‑Based Cross‑Domain Debugging
When the inspected page and the DevTools frontend are on different networks, a proxy service can forward CDP messages between them. The proxy runs a WebSocket server that:
Queries http://127.0.0.1:9222/json to obtain the webSocketDebuggerUrl of the React Native Debugger page.
Establishes a debugConnection to the debugger.
Forwards messages from the debugger to the frontend and vice‑versa.
Handles client‑side messages (e.g., intercepted network data) and routes them to the debugger.
Key proxy implementation ( proxy.js):
const WS_PROXY_PORT = 9233; // proxy ws port
const CHROME_REMOTE_DEBUG_PORT = 9222; // devtools frontend port
const proxyStart = async () => {
const server = http.createServer((request, response) => {
response.writeHead(404);
response.end();
});
server.listen(WS_PROXY_PORT);
const wss = new WebSocket.Server({ server });
wss.on('connection', (ws, request, client) => {
// remote‑debug handling
if (request.headers['sec-websocket-protocol'] === 'remote-debug') {
axios.get(`http://127.0.0.1:${CHROME_REMOTE_DEBUG_PORT}/json`).then(res => {
const { data } = res;
if (data && data.length > 0) {
for (let i = 0; i < data.length; i++) {
const page = data[i];
if (page.title.indexOf('React Native Debugger') === 0) {
debugConnection = new WebSocket(page.webSocketDebuggerUrl);
debugConnection.on('message', message => {
if (frontendConnection) {
if (debugMessageArr.length) {
debugMessageArr.forEach(item => frontendConnection.send(item));
debugMessageArr = [];
}
frontendConnection.send(message);
} else {
debugMessageArr.push(message);
console.log('Cannot forward to frontend, no connection');
}
});
break;
}
}
}
}).catch(error => console.log(error));
}
// frontend handling
if (request.url === '/frontend') {
frontendConnection = ws;
frontendConnection.on('message', message => {
if (debugConnection) {
if (frontendMessageArr.length) {
frontendMessageArr.forEach(item => debugConnection.send(item));
frontendMessageArr = [];
}
debugConnection.send(message);
} else {
frontendMessageArr.push(message);
console.log('Debugger backend not ready, open the page first');
}
});
}
// app handling
if (request.url === '/app') {
appConnection = ws;
appConnection.on('message', message => {
if (frontendConnection) {
if (debugMessageArr.length) {
debugMessageArr.forEach(item => frontendConnection.send(item));
debugMessageArr = [];
}
frontendConnection.send(message);
} else {
debugMessageArr.push(message);
console.log('Cannot forward to frontend, no connection');
}
});
}
});
};The proxy processes three streams:
remote‑debug : data from the inspected page, obtained via the /json endpoint.
frontend : messages from the DevTools UI, forwarded to the debugger.
app : network data intercepted on the client side, forwarded to the frontend.
Local Frontend Service
Because the DevTools frontend version must match the Chrome version used by Electron, the article retrieves the Chrome version via process.versions.chrome (e.g., 78.0.3904.130) and selects the corresponding DevTools build (3964). The service is started by running devtools-frontend/scripts/server.js, which launches a local HTTP server on port 8090.
http://localhost:8090/front_end/devtools_app.html?ws=localhost:9233/frontendThis URL points the DevTools UI to the proxy WebSocket, enabling the custom network panel.
References
https://github.com/jhen0409/react-native-debugger
https://segmentfault.com/a/1190000013665754
https://github.com/ChromeDevTools/devtools-frontend
https://chromedevtools.github.io/devtools-protocol
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
