Skip to content

Commit e6494eb

Browse files
motiz88meta-codesync[bot]
authored andcommitted
Add inspector-proxy protocol docs (facebook#55055)
Summary: Pull Request resolved: facebook#55055 TSIA. Changelog: [Internal] Differential Revision: D90174644 Reviewed By: robhogan
1 parent ef10ac0 commit e6494eb

File tree

2 files changed

+301
-3
lines changed

2 files changed

+301
-3
lines changed

__docs__/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,9 @@ TODO: Explain the different components of React Native at a high level.
5555
- Globals and environment setup
5656
- Error handling
5757
- Developer Tools
58-
- React DevTools
58+
- React Native DevTools
59+
- Infrastructure
60+
- [Inspector proxy protocol](../packages/dev-middleware/src/inspector-proxy/__docs__/README.md)
5961
- LogBox
6062
- Misc
6163
- Web APIs
@@ -91,8 +93,6 @@ TODO: Explain the different components of React Native at a high level.
9193
- ESLint
9294
- Integration / E2E
9395
- [Fantom](../private/react-native-fantom/__docs__/README.md)
94-
- Tooling
95-
- React Native DevTools
9696

9797
### Used by this
9898

Lines changed: 298 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,298 @@
1+
# Inspector Proxy Protocol
2+
3+
[🏠 Home](../../../../../__docs__/README.md)
4+
5+
The inspector-proxy protocol facilitates Chrome DevTools Protocol (CDP) target
6+
discovery and communication between **debuggers** (e.g., Chrome DevTools, VS
7+
Code) and **devices** (processes containing React Native hosts). The proxy
8+
multiplexes connections over a single WebSocket per device, allowing multiple
9+
debuggers to connect to multiple pages on the same device.
10+
11+
## 🚀 Usage
12+
13+
### Target Discovery (HTTP)
14+
15+
We implement a subset of the
16+
[Chrome DevTools Protocol](https://chromedevtools.github.io/devtools-protocol/)'s
17+
[HTTP endpoints](https://chromedevtools.github.io/devtools-protocol/#:~:text=a%20reconnect%20button.-,HTTP%20Endpoints,-If%20started%20with)
18+
to allow debuggers to discover targets.
19+
20+
| Endpoint | Description |
21+
| --------------------------- | ------------------------ |
22+
| `GET /json` or `/json/list` | List of debuggable pages |
23+
| `GET /json/version` | Protocol version info |
24+
25+
### Device Registration (WebSocket)
26+
27+
Devices register themselves with the proxy by connecting to `/inspector/device`:
28+
29+
```text
30+
ws://{host}/inspector/device?device={id}&name={name}&app={bundle_id}&profiling={true|false}
31+
```
32+
33+
| Parameter | Required | Description |
34+
| ----------- | -------- | ------------------------------------------------------------ |
35+
| `device` | No\* | Logical device identifier. Auto-generated if omitted. |
36+
| `name` | No | Human-readable device name. Defaults to "Unknown". |
37+
| `app` | No | App bundle identifier. Defaults to "Unknown". |
38+
| `profiling` | No | "true" if this is a profiling build. (Used for logging only) |
39+
40+
\*Recommended for connection persistence across app restarts.
41+
42+
#### Requirements for the `device` parameter
43+
44+
The intent of the logical device ID is to help with target discovery and
45+
especially *re*discovery - to reduce the number of times users need to
46+
explicitly close and restart the debugger frontend (e.g. after an app crash).
47+
48+
If provided, the logical device ID:
49+
50+
1. SHOULD be stable for the current combination of physical device (or emulator
51+
instance) and app.
52+
2. SHOULD be stable across installs/launches of the same app on the same device
53+
(or emulator instance), though it MAY be user-resettable (so as to not
54+
require any special privacy permissions).
55+
3. MUST be unique across different apps on the same physical device (or
56+
emulator).
57+
4. MUST be unique across physical devices (or emulators).
58+
5. MUST be unique for each concurrent _instance_ of the same app on the same
59+
physical device (or emulator).
60+
61+
NOTE: The uniqueness requirements are stronger (MUST) than the stability
62+
requirements (SHOULD). In particular, on platforms that allow multiple instances
63+
of the same app to run concurrently, requirements 1 and/or 2 MAY be violated in
64+
order to meet requirement 5. This is relevant, for example, on desktop
65+
platforms.
66+
67+
### Debugger Connection (WebSocket)
68+
69+
Debuggers connect to `/inspector/debug` to form a CDP session with a page:
70+
71+
```text
72+
ws://{host}/inspector/debug?device={device_id}&page={page_id}
73+
```
74+
75+
Both `device` and `page` query parameters are required.
76+
77+
## 📐 Design
78+
79+
### Architecture
80+
81+
```text
82+
┌─────────────────┐ ┌─────────────────────────┐ ┌────────────────┐
83+
│ Debugger │────▶│ Inspector Proxy │◀────│ Device │
84+
│ (Chrome/VSCode) │ │ (Node.js) │ │ (iOS/Android) │
85+
└─────────────────┘ └─────────────────────────┘ └────────────────┘
86+
WebSocket HTTP + WebSocket WebSocket
87+
/inspector/debug /json, /json/list /inspector/device
88+
/json/version
89+
```
90+
91+
### Device ↔ Proxy Protocol
92+
93+
All messages are JSON-encoded WebSocket text frames:
94+
95+
```typescript
96+
interface Message {
97+
event: string;
98+
payload?: /* depends on event */;
99+
}
100+
```
101+
102+
#### Proxy → Device Messages
103+
104+
| Event | Payload | Description |
105+
| -------------- | ------------------------------------------ | --------------------------------------------- |
106+
| `getPages` | _(none)_ | Request current page list. Sent periodically. |
107+
| `connect` | `{ pageId: string }` | Prepare for debugger connection to page. |
108+
| `disconnect` | `{ pageId: string }` | Terminate debugger session for page. |
109+
| `wrappedEvent` | `{ pageId: string, wrappedEvent: string }` | Forward CDP message (JSON string) to page. |
110+
111+
#### Device → Proxy Messages
112+
113+
| Event | Payload | Description |
114+
| -------------- | ------------------------------------------ | ----------------------------------------------------- |
115+
| `getPages` | `Page[]` | Current list of inspectable pages. |
116+
| `disconnect` | `{ pageId: string }` | Notify that page disconnected or rejected connection. |
117+
| `wrappedEvent` | `{ pageId: string, wrappedEvent: string }` | Forward CDP message (JSON string) from page. |
118+
119+
#### Page Object
120+
121+
```typescript
122+
interface Page {
123+
id: string; // Unique page identifier (typically numeric string)
124+
title: string; // Display title
125+
app: string; // App bundle identifier
126+
description?: string; // Additional description
127+
capabilities?: {
128+
nativePageReloads?: boolean; // Target keeps the socket open across reloads
129+
nativeSourceCodeFetching?: boolean; // Target supports Network.loadNetworkResource
130+
prefersFuseboxFrontend?: boolean; // Target is designed for React Native DevTools
131+
};
132+
}
133+
```
134+
135+
### Connection Lifecycle
136+
137+
**Device Registration:**
138+
139+
```text
140+
Device Proxy
141+
│ │
142+
│──── WS Connect ─────────────────▶│
143+
│ /inspector/device?... │
144+
│ │
145+
│◀──── getPages ───────────────────│ (periodically)
146+
│ │
147+
│───── getPages response ─────────▶│
148+
│ (page list) │
149+
```
150+
151+
**Debugger Session:**
152+
153+
```text
154+
Debugger Proxy Device
155+
│ │ │
156+
│── WS Connect ───▶│ │
157+
│ ?device&page │── connect ────────────────▶│
158+
│ │ {pageId} │
159+
│ │ │
160+
│── CDP Request ──▶│── wrappedEvent ───────────▶│
161+
│ │ {pageId, wrappedEvent} │
162+
│ │ │
163+
│ │◀── wrappedEvent ───────────│
164+
│◀── CDP Response ─│ {pageId, wrappedEvent} │
165+
│ │ │
166+
│── WS Close ─────▶│── disconnect ─────────────▶│
167+
│ │ {pageId} │
168+
```
169+
170+
**Connection Rejection:**
171+
172+
If a device cannot accept a `connect` (e.g., page doesn't exist), it should send
173+
a `disconnect` back to the proxy for that `pageId`.
174+
175+
### Connection Semantics
176+
177+
1. **One Debugger Per Page**: New debugger connections to an already-connected
178+
page disconnect the existing debugger.
179+
180+
2. **Device Reconnection**: If a device reconnects with the same `device` ID,
181+
the proxy may attempt to preserve active debugger sessions by forwarding them
182+
to the new device connection.
183+
184+
### WebSocket Close Reasons
185+
186+
The proxy uses specific close reasons that DevTools frontends may recognize:
187+
188+
| Reason | Context |
189+
| ----------------------- | --------------------------------------- |
190+
| `[PAGE_NOT_FOUND]` | Debugger connected to non-existent page |
191+
| `[CONNECTION_LOST]` | Device disconnected |
192+
| `[RECREATING_DEVICE]` | Device is reconnecting |
193+
| `[NEW_DEBUGGER_OPENED]` | Another debugger took over this page |
194+
| `[UNREGISTERED_DEVICE]` | Device ID not found |
195+
| `[INCORRECT_URL]` | Missing device/page query parameters |
196+
197+
### PageDescription (HTTP Response)
198+
199+
The `/json` endpoint returns enriched page descriptions based on those reported
200+
by the device.
201+
202+
```typescript
203+
interface PageDescription {
204+
// Used for target selection
205+
id: string; // "{deviceId}-{pageId}"
206+
207+
// Used for display
208+
title: string;
209+
description: string;
210+
deviceName: string;
211+
212+
// Used for target matching
213+
appId: string;
214+
215+
// Used for debugger connection
216+
webSocketDebuggerUrl: string;
217+
218+
// React Native-specific metadata
219+
reactNative: {
220+
logicalDeviceId: string; // Used for target matching
221+
capabilities: {
222+
nativePageReloads?: boolean; // Used for target filtering
223+
prefersFuseboxFrontend?: boolean; // Used for frontend selection
224+
};
225+
};
226+
}
227+
```
228+
229+
## 🔗 Relationship with other systems
230+
231+
### Part of this
232+
233+
- **Device.js** - Per-device connection handler in the proxy
234+
- **InspectorProxy.js** - Main proxy HTTP/WebSocket server
235+
236+
### Used by this
237+
238+
- **Chrome DevTools Protocol (CDP)** - The wrapped messages are CDP messages
239+
exchanged between DevTools frontends and JavaScript runtimes.
240+
- **WebSocket** - Transport layer for device and debugger connections.
241+
242+
### Uses this
243+
244+
- **InspectorPackagerConnection (C++)** - Shared device-side protocol
245+
implementation in `ReactCommon/jsinspector-modern/`.
246+
- **Platform layers** - iOS (`RCTInspectorDevServerHelper.mm`), Android
247+
(`DevServerHelper.kt`), and ReactCxxPlatform (`Inspector.cpp`) provide
248+
WebSocket I/O and threading.
249+
- **openDebuggerMiddleware** - Uses `/json` to discover targets for the
250+
`/open-debugger` endpoint.
251+
- **OpenDebuggerKeyboardHandler** - Uses `/json` to display target selection in
252+
the CLI.
253+
254+
---
255+
256+
## Legacy Features
257+
258+
The following features exist for backward compatibility with older React Native
259+
targets that lack modern capabilities. New implementations should set
260+
appropriate capability flags and may ignore this section.
261+
262+
### Synthetic Reloadable Page (Page ID `-1`)
263+
264+
For targets without the `nativePageReloads` capability, the proxy exposes a
265+
synthetic page with ID `-1` titled "React Native Experimental (Improved Chrome
266+
Reloads)". Debuggers connecting to this page are automatically redirected to the
267+
most recent React Native page, surviving page reloads.
268+
269+
When a new React Native page appears while a debugger is connected to `-1`:
270+
271+
1. Proxy sends `disconnect` for the old page, `connect` for the new page
272+
2. Proxy sends `Runtime.enable` and `Debugger.enable` CDP commands to the new
273+
page
274+
3. When `Runtime.executionContextCreated` is received, proxy sends
275+
`Runtime.executionContextsCleared` to debugger, then `Debugger.resume` to
276+
device
277+
278+
### URL Rewriting
279+
280+
For targets without the `nativeSourceCodeFetching` capability, the proxy
281+
rewrites URLs in CDP messages:
282+
283+
- **Debugger.scriptParsed** (device → debugger): Device-relative URLs are
284+
rewritten to debugger-relative URLs
285+
- **Debugger.setBreakpointByUrl** (debugger → device): URLs are rewritten back
286+
to device-relative form
287+
- **Debugger.getScriptSource**: Intercepted and handled by proxy via HTTP fetch
288+
- **Network.loadNetworkResource**: Returns CDP error (code -32601) to force
289+
frontend fallback
290+
291+
Additionally, if a script URL matches `^[0-9a-z]+$` (alphanumeric ID), the proxy
292+
prepends `file://` to ensure Chrome downloads source maps.
293+
294+
### Legacy Reload Notification
295+
296+
For targets without `nativePageReloads`, when a `disconnect` event is received
297+
for a page, the proxy sends `{method: 'reload'}` to the connected debugger to
298+
signal a page reload.

0 commit comments

Comments
 (0)