packages/dev-middleware/src/inspector-proxy/__docs__/README.md
The inspector-proxy protocol facilitates Chrome DevTools Protocol (CDP) target discovery and communication between debuggers (e.g., Chrome DevTools, VS Code) and devices (processes containing React Native hosts). The proxy multiplexes connections over a single WebSocket per device, allowing multiple debuggers to connect to multiple pages on the same device.
We implement a subset of the Chrome DevTools Protocol's HTTP endpoints to allow debuggers to discover targets.
| Endpoint | Description |
|---|---|
GET /json or /json/list | List of debuggable pages |
GET /json/version | Protocol version info |
Devices register themselves with the proxy by connecting to /inspector/device:
ws://{host}/inspector/device?device={id}&name={name}&app={bundle_id}&profiling={true|false}
| Parameter | Required | Description |
|---|---|---|
device | No* | Logical device identifier. Auto-generated if omitted. |
name | No | Human-readable device name. Defaults to "Unknown". |
app | No | App bundle identifier. Defaults to "Unknown". |
profiling | No | "true" if this is a profiling build. (Used for logging only) |
*Recommended for connection persistence across app restarts.
device parameterThe intent of the logical device ID is to help with target discovery and especially rediscovery - to reduce the number of times users need to explicitly close and restart the debugger frontend (e.g. after an app crash).
If provided, the logical device ID:
NOTE: The uniqueness requirements are stronger (MUST) than the stability requirements (SHOULD). In particular, on platforms that allow multiple instances of the same app to run concurrently, requirements 1 and/or 2 MAY be violated in order to meet requirement 5. This is relevant, for example, on desktop platforms.
Debuggers connect to /inspector/debug to form a CDP session with a page:
ws://{host}/inspector/debug?device={device_id}&page={page_id}
Both device and page query parameters are required.
βββββββββββββββββββ βββββββββββββββββββββββββββ ββββββββββββββββββ
β Debugger ββββββΆβ Inspector Proxy βββββββ Device β
β (Chrome/VSCode) β β (Node.js) β β (iOS/Android) β
βββββββββββββββββββ βββββββββββββββββββββββββββ ββββββββββββββββββ
WebSocket HTTP + WebSocket WebSocket
/inspector/debug /json, /json/list /inspector/device
/json/version
All messages are JSON-encoded WebSocket text frames:
interface Message {
event: string;
payload?: /* depends on event */;
}
| Event | Payload | Description |
|---|---|---|
getPages | (none) | Request current page list. Sent periodically. |
connect | { pageId: string, sessionId: string } | Prepare for debugger connection to page. |
disconnect | { pageId: string, sessionId: string } | Terminate debugger session for page. |
wrappedEvent | { pageId: string, sessionId: string, wrappedEvent: string } | Forward CDP message (JSON string) to page. |
| Event | Payload | Description |
|---|---|---|
getPages | Page[] | Current list of inspectable pages. |
disconnect | { pageId: string, sessionId?: string } | Notify that page disconnected or rejected connection. |
wrappedEvent | { pageId: string, sessionId?: string, wrappedEvent: string } | Forward CDP message (JSON string) from page. |
interface Page {
id: string; // Unique page identifier (typically numeric string)
title: string; // Display title
app: string; // App bundle identifier
description?: string; // Additional description
capabilities?: {
nativePageReloads?: boolean; // Target keeps the socket open across reloads
nativeSourceCodeFetching?: boolean; // Target supports Network.loadNetworkResource
supportsMultipleDebuggers?: boolean; // Supports concurrent debugger sessions
};
}
Note: The value of supportsMultipleDebuggers SHOULD be consistent across
all pages for a given device.
Device Registration:
Device Proxy
β β
βββββ WS Connect ββββββββββββββββββΆβ
β /inspector/device?... β
β β
ββββββ getPages ββββββββββββββββββββ (periodically)
β β
ββββββ getPages response ββββββββββΆβ
β (page list) β
Debugger Session:
Debugger Proxy Device
β β β
βββ WS Connect ββββΆβ β
β ?device&page βββ connect βββββββββββββββββΆβ
β β {pageId, sessionId} β
β β β
βββ CDP Request βββΆβββ wrappedEvent ββββββββββββΆβ
β β {pageId, sessionId, β
β β wrappedEvent} β
β β β
β ββββ wrappedEvent ββββββββββββ
ββββ CDP Response ββ {pageId, sessionId, β
β β wrappedEvent} β
β β β
βββ WS Close ββββββΆβββ disconnect ββββββββββββββΆβ
β β {pageId, sessionId} β
Connection Rejection:
If a device cannot accept a connect (e.g., page doesn't exist), it should send
a disconnect back to the proxy for that pageId.
Multiple debuggers can connect simultaneously to the same page when both the proxy and device support session multiplexing:
Session IDs: The proxy assigns a unique, non-empty sessionId to each
debugger connection. All messages include this sessionId for routing. This
SHOULD be a UUID or other suitably unique and ephemeral identifier.
Capability Detection: Devices report supportsMultipleDebuggers: true in
their page capabilities to indicate session support.
Backwards Compatibility: Legacy devices ignore sessionId fields in
incoming messages and don't include them in responses.
Session-Capable Device: Multiple debuggers can connect to the same page simultaneously. Each connection has an independent session.
Legacy Device (no supportsMultipleDebuggers): New debugger connections
to an already-connected page disconnect the existing debugger. The proxy MUST
NOT allow multiple debuggers to connect to the same page.
Device Reconnection: If a device reconnects with the same device ID
while debugger connections to the same logical device are open in the proxy,
the proxy may attempt to preserve active debugger sessions by forwarding them
to the new device.
The proxy uses specific close reasons that DevTools frontends may recognize:
| Reason | Context |
|---|---|
[PAGE_NOT_FOUND] | Debugger connected to non-existent page |
[CONNECTION_LOST] | Device disconnected |
[RECREATING_DEVICE] | Device is reconnecting |
[NEW_DEBUGGER_OPENED] | Another debugger took over this page |
[UNREGISTERED_DEVICE] | Device ID not found |
[INCORRECT_URL] | Missing device/page query parameters |
The /json endpoint returns enriched page descriptions based on those reported
by the device.
interface PageDescription {
// Used for target selection
id: string; // "{deviceId}-{pageId}"
// Used for display
title: string;
description: string;
deviceName: string;
// Used for target matching
appId: string;
// Used for debugger connection
webSocketDebuggerUrl: string;
// React Native-specific metadata
reactNative: {
logicalDeviceId: string; // Used for target matching
capabilities: {
nativePageReloads?: boolean; // Used for target filtering
};
};
}
ReactCommon/jsinspector-modern/.RCTInspectorDevServerHelper.mm), Android
(DevServerHelper.kt), and ReactCxxPlatform (Inspector.cpp) provide
WebSocket I/O and threading./json to discover targets for the
/open-debugger endpoint./json to display target selection in
the CLI.The following features exist for backward compatibility with older React Native targets that lack modern capabilities. New implementations should set appropriate capability flags and may ignore this section.
-1)For targets without the nativePageReloads capability, the proxy exposes a
synthetic page with ID -1 titled "React Native Experimental (Improved Chrome
Reloads)". Debuggers connecting to this page are automatically redirected to the
most recent React Native page, surviving page reloads.
When a new React Native page appears while a debugger is connected to -1:
disconnect for the old page, connect for the new pageRuntime.enable and Debugger.enable CDP commands to the new
pageRuntime.executionContextCreated is received, proxy sends
Runtime.executionContextsCleared to debugger, then Debugger.resume to
deviceFor targets without the nativeSourceCodeFetching capability, the proxy
rewrites URLs in CDP messages:
Additionally, if a script URL matches ^[0-9a-z]+$ (alphanumeric ID), the proxy
prepends file:// to ensure Chrome downloads source maps.
For targets without nativePageReloads, when a disconnect event is received
for a page, the proxy sends {method: 'reload'} to the connected debugger to
signal a page reload.