@react-native/dev-middleware 0.85.0-nightly-20260119-2e9b7fb2a β†’ 0.85.0-nightly-20260121-c1f5445f4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@react-native/dev-middleware",
3
- "version": "0.85.0-nightly-20260119-2e9b7fb2a",
3
+ "version": "0.85.0-nightly-20260121-c1f5445f4",
4
4
  "description": "Dev server middleware for React Native",
5
5
  "keywords": [
6
6
  "react-native",
@@ -26,8 +26,8 @@
26
26
  },
27
27
  "dependencies": {
28
28
  "@isaacs/ttlcache": "^1.4.1",
29
- "@react-native/debugger-frontend": "0.85.0-nightly-20260119-2e9b7fb2a",
30
- "@react-native/debugger-shell": "0.85.0-nightly-20260119-2e9b7fb2a",
29
+ "@react-native/debugger-frontend": "0.85.0-nightly-20260121-c1f5445f4",
30
+ "@react-native/debugger-shell": "0.85.0-nightly-20260121-c1f5445f4",
31
31
  "chrome-launcher": "^0.15.2",
32
32
  "chromium-edge-launcher": "^0.3.0",
33
33
  "connect": "^3.6.5",
@@ -39,7 +39,7 @@
39
39
  "ws": "^7.5.10"
40
40
  },
41
41
  "devDependencies": {
42
- "@react-native/debugger-shell": "0.85.0-nightly-20260119-2e9b7fb2a",
42
+ "@react-native/debugger-shell": "0.85.0-nightly-20260121-c1f5445f4",
43
43
  "selfsigned": "^4.0.0",
44
44
  "undici": "^5.29.0",
45
45
  "wait-for-expect": "^3.0.2"