Frontend Development 19 min read

San DevTools Technical Analysis – Message Channel and DevTools Protocol

San DevTools implements a four‑module remote‑debugging architecture where the Message Channel uses WebSocket to multiplex bidirectional frontend‑backend communication, Chrome extensions exchange messages via ports and postMessage, and both a custom San DevTools Protocol and the standard Chrome DevTools Protocol are supported through hook‑based agents and client examples.

Baidu App Technology
Baidu App Technology
Baidu App Technology
San DevTools Technical Analysis – Message Channel and DevTools Protocol

Message Channel Overview

San DevTools separates its remote debugging architecture into four core modules. This part covers the Message Channel, which handles bidirectional communication between frontend and backend via WebSocket.

WebSocket Communication

WebSocket is supported by modern browsers. The protocol defines ws and wss URIs for plain and secure connections. The server uses the ws npm package.

Server Implementation

const http = require('http');
const WebSocket = require('ws');
const url = require('url');

const server = http.createServer();
const wss = new WebSocket.Server({noServer: true});

wss.on('connection', ws => {
    // ...
});

server.on('upgrade', (request, socket, head) => {
    const pathname = url.parse(request.url).pathname;
    const [, role, id] = pathname.split('/');
    wss.handleUpgrade(request, socket, head, ws => {
        ws.role = role;
        ws.id = id;
        wss.emit('connection', ws, request);
    });
});

server.listen(8080);

The handshake upgrades HTTP to WebSocket using the Upgrade header.

Communication Channel Classes

Key classes:

WebSocketServer – creates the WebSocket service and manages channels via ChannelMultiplex .

ChannelMultiplex – stores backend and frontend channel maps and creates paired channels.

Channel – represents a pair of WebSocket endpoints and forwards messages between them.

class WebSocketServer {
    constructor() {
        this.channelManager = new ChannelMultiplex();
        const ws = new WebSocket.Server({noServer: true});
        this._wss.on('connection', ws => {
            const {id, role} = ws;
            switch (role) {
                case 'backend':
                    this.channelManager.createBackendChannel(id, ws);
                    break;
                case 'frontend':
                    this.channelManager.createFrontendChannel(id, ws, ws.backend);
                    break;
            }
        });
    }
}

Client Implementation

Both frontend and backend run in the browser and use window.WebSocket to connect.

class WebSocketMultiplex extends EventEmitter {
    constructor(sockUrl) {
        this._ws = new window.WebSocket(sockUrl);
        this._ws.onmessage = event => {
            this.emit('message', event);
        };
    }
    send(message) {
        this._ws.send(message);
    }
}

Extension Communication

Chrome extensions use long‑living ports ( chrome.runtime.connect ) and short messages ( chrome.runtime.sendMessage ). The article shows how content scripts, background pages, and devtools panels exchange messages via window.postMessage and ports.

const port = chrome.runtime.connect({name: 'content-script'});
window.addEventListener('message', evt => {
    port.postMessage(evt.data.payload);
});
port.onMessage.addListener(message => {
    window.postMessage({source: 'san-devtools-content-script', payload: message}, '*');
});

Debugger Protocols

The DevTools Protocol defines JSON‑based commands and events for debugging. San DevTools implements two protocols:

San DevTools Protocol (SDP) – custom protocol with domains such as HandShake, Component, Store, History, Message, Event.

Chrome DevTools Protocol (CDP) – standard Chrome protocol, used via third‑party libraries ( chobitsu , chii ).

SDP Server – Hook and Agent

A Hook object is injected into the page (global __san_devtool__ ) to collect events from san.dev.js . An Agent subscribes to hook events and to the message channel, forwarding data to the frontend.

class Hook {
    constructor() {
        this.san = null;
        this.data = {totalCompNums: 0, selectedComponentId: '', treeData: []};
        this.storeMap = new Map();
        this.componentMap = new Map();
        this.listeners = new Map();
    }
    on(eventName, listener) { /* ... */ }
    emit(eventName, data) { /* ... */ }
}
function install(target) {
    const sanHook = new Hook();
    Object.defineProperty(target, '__san_devtool__', {
        configurable: true,
        get() { return sanHook; }
    });
    return sanHook;
}

CDP Client Example (Browser)

Creates a new target, attaches to it, and retrieves the DOM tree using raw WebSocket messages.

(async () => {
    const ws = new WebSocket('ws://127.0.0.1:9222/devtools/browser/...');
    await new Promise(resolve => ws.onopen = resolve);
    const pageTarget = await SEND(ws, {id:1, method:'Target.createTarget', params:{url:'http://www.baidu.com'}});
    const sessionId = (await SEND(ws, {id:2, method:'Target.attachToTarget', params:{targetId: pageTarget.result.targetId, flatten:true}})).result.sessionId;
    const domTree = await SEND(ws, {sessionId, id:4, method:'DOM.getDocument', params:{}});
    console.log(domTree.result);
})();
function SEND(ws, command) {
    ws.send(JSON.stringify(command));
    return new Promise(resolve => {
        ws.onmessage = msg => {
            const response = JSON.parse(msg.data);
            if (response.id === command.id) {
                ws.onmessage = null;
                resolve(response);
            }
        };
    });
}

CDP Client Example (Node)

Uses the chrome-remote-interface library to enable Network, Page, and DOM domains, navigate to a page, and log the DOM tree.

const CDP = require('chrome-remote-interface');

(async () => {
    let client;
    try {
        client = await CDP();
        const {Network, Page, DOM} = client;
        Network.requestWillBeSent(params => console.log(params.request.url));
        await Network.enable();
        await Page.enable();
        await Page.navigate({url:'https://www.baidu.com'});
        await Page.loadEventFired();
        const domTree = await DOM.getDocument();
        console.log(domTree);
    } catch (err) {
        console.error(err);
    } finally {
        if (client) await client.close();
    }
})();

Conclusion

The article demonstrates how San DevTools builds a flexible message channel, integrates with Chrome extensions, and implements both custom and standard debugging protocols, providing a solid foundation for remote debugging of San applications.

JavaScriptnode.jschrome extensionWebSocketremote debuggingDevTools ProtocolMessage Channel
Baidu App Technology
Written by

Baidu App Technology

Official Baidu App Tech Account

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.