Skip to content

Commit d987502

Browse files
[React][DevTools] Add the ability to pass a path to React DevTools
1 parent b354bbd commit d987502

6 files changed

Lines changed: 139 additions & 16 deletions

File tree

packages/react-devtools-core/README.md

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ Each filter object must include `type` and `isEnabled`. Some filters also requir
5454
|------------------------|---------------|---------------------------------------------------------------------------------------------------------------------------|
5555
| `host` | `"localhost"` | Socket connection to frontend should use this host. |
5656
| `isAppActive` | | (Optional) function that returns true/false, telling DevTools when it's ready to connect to React. |
57+
| `path` | `""` | Path appended to the WebSocket URI (e.g. `"/__react_devtools__/"`). Useful when proxying through a reverse proxy on a subpath. A leading `/` is added automatically if missing. |
5758
| `port` | `8097` | Socket connection to frontend should use this port. |
5859
| `resolveRNStyle` | | (Optional) function that accepts a key (number) and returns a style (object); used by React Native. |
5960
| `retryConnectionDelay` | `200` | Delay (ms) to wait between retrying a failed Websocket connection |
@@ -141,16 +142,51 @@ function onStatus(
141142
}
142143
```
143144

144-
#### `startServer(port?: number, host?: string, httpsOptions?: Object, loggerOptions?: Object)`
145+
#### `startServer(port?, host?, httpsOptions?, loggerOptions?, path?, clientOptions?)`
145146
Start a socket server (used to communicate between backend and frontend) and renders the DevTools UI.
146147

147148
This method accepts the following parameters:
148149
| Name | Default | Description |
149150
|---|---|---|
150-
| `port` | `8097` | Socket connection to backend should use this port. |
151-
| `host` | `"localhost"` | Socket connection to backend should use this host. |
151+
| `port` | `8097` | Port the local server listens on. |
152+
| `host` | `"localhost"` | Host the local server binds to. |
152153
| `httpsOptions` | | _Optional_ object defining `key` and `cert` strings. |
153154
| `loggerOptions` | | _Optional_ object defining a `surface` string (to be included with DevTools logging events). |
155+
| `path` | | _Optional_ path to append to the WebSocket URI served to connecting clients (e.g. `"/__react_devtools__/"`). Also set via the `REACT_DEVTOOLS_PATH` env var in the Electron app. |
156+
| `clientOptions` | | _Optional_ object with client-facing overrides (see below). |
157+
158+
##### `clientOptions`
159+
160+
When connecting through a reverse proxy, the client may need to connect to a different host, port, or protocol than the local server. Use `clientOptions` to override what appears in the `connectToDevTools()` script served to clients. Any field not set falls back to the corresponding server value.
161+
162+
| Field | Default | Description |
163+
|---|---|---|
164+
| `host` | server `host` | Host the client connects to. |
165+
| `port` | server `port` | Port the client connects to. |
166+
| `useHttps` | server `useHttps` | Whether the client should use `wss://`. |
167+
168+
These can also be set via environment variables in the Electron app:
169+
170+
| Env Var | Description |
171+
|---|---|
172+
| `REACT_DEVTOOLS_CLIENT_HOST` | Overrides the host in the served client script. |
173+
| `REACT_DEVTOOLS_CLIENT_PORT` | Overrides the port in the served client script. |
174+
| `REACT_DEVTOOLS_CLIENT_USE_HTTPS` | Set to `"true"` to make the served client script use `wss://`. |
175+
176+
##### Reverse proxy example
177+
178+
Run DevTools locally on the default port, but tell clients to connect through a remote proxy:
179+
```sh
180+
REACT_DEVTOOLS_CLIENT_HOST=remote.example.com \
181+
REACT_DEVTOOLS_CLIENT_PORT=443 \
182+
REACT_DEVTOOLS_CLIENT_USE_HTTPS=true \
183+
REACT_DEVTOOLS_PATH=/__react_devtools__/ \
184+
react-devtools
185+
```
186+
The server listens on `localhost:8097`. The served script tells clients:
187+
```js
188+
connectToDevTools({host: 'remote.example.com', port: 443, useHttps: true, path: '/__react_devtools__/'})
189+
```
154190

155191
# Development
156192

packages/react-devtools-core/src/backend.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import type {ResolveNativeStyle} from 'react-devtools-shared/src/backend/NativeS
3333
type ConnectOptions = {
3434
host?: string,
3535
nativeStyleEditorValidAttributes?: $ReadOnlyArray<string>,
36+
path?: string,
3637
port?: number,
3738
useHttps?: boolean,
3839
resolveRNStyle?: ResolveNativeStyle,
@@ -93,6 +94,7 @@ export function connectToDevTools(options: ?ConnectOptions) {
9394
const {
9495
host = 'localhost',
9596
nativeStyleEditorValidAttributes,
97+
path = '',
9698
useHttps = false,
9799
port = 8097,
98100
websocket,
@@ -107,6 +109,7 @@ export function connectToDevTools(options: ?ConnectOptions) {
107109
} = options || {};
108110

109111
const protocol = useHttps ? 'wss' : 'ws';
112+
const prefixedPath = path !== '' && !path.startsWith('/') ? '/' + path : path;
110113
let retryTimeoutID: TimeoutID | null = null;
111114

112115
function scheduleRetry() {
@@ -129,7 +132,7 @@ export function connectToDevTools(options: ?ConnectOptions) {
129132
let bridge: BackendBridge | null = null;
130133

131134
const messageListeners = [];
132-
const uri = protocol + '://' + host + ':' + port;
135+
const uri = protocol + '://' + host + ':' + port + prefixedPath;
133136

134137
// If existing websocket is passed, use it.
135138
// This is necessary to support our custom integrations.

packages/react-devtools-core/src/standalone.js

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -306,11 +306,19 @@ type LoggerOptions = {
306306
surface?: ?string,
307307
};
308308

309+
type ClientOptions = {
310+
host?: string,
311+
port?: number,
312+
useHttps?: boolean,
313+
};
314+
309315
function startServer(
310316
port: number = 8097,
311317
host: string = 'localhost',
312318
httpsOptions?: ServerOptions,
313319
loggerOptions?: LoggerOptions,
320+
path?: string,
321+
clientOptions?: ClientOptions,
314322
): {close(): void} {
315323
registerDevToolsEventLogger(loggerOptions?.surface ?? 'standalone');
316324

@@ -345,7 +353,18 @@ function startServer(
345353
server.on('error', (event: $FlowFixMe) => {
346354
onError(event);
347355
log.error('Failed to start the DevTools server', event);
348-
startServerTimeoutID = setTimeout(() => startServer(port), 1000);
356+
startServerTimeoutID = setTimeout(
357+
() =>
358+
startServer(
359+
port,
360+
host,
361+
httpsOptions,
362+
loggerOptions,
363+
path,
364+
clientOptions,
365+
),
366+
1000,
367+
);
349368
});
350369

351370
httpServer.on('request', (request: $FlowFixMe, response: $FlowFixMe) => {
@@ -358,22 +377,39 @@ function startServer(
358377
// This will ensure that saved filters are shared across different web pages.
359378
const componentFiltersString = JSON.stringify(getSavedComponentFilters());
360379

380+
// Client overrides: when connecting through a reverse proxy, the client
381+
// may need to connect to a different host/port/protocol than the server.
382+
const clientHost = clientOptions?.host ?? host;
383+
const clientPort = clientOptions?.port ?? port;
384+
const clientUseHttps = clientOptions?.useHttps ?? useHttps;
385+
361386
response.end(
362387
backendFile.toString() +
363388
'\n;' +
364389
`ReactDevToolsBackend.initialize(undefined, undefined, undefined, ${componentFiltersString});` +
365390
'\n' +
366-
`ReactDevToolsBackend.connectToDevTools({port: ${port}, host: '${host}', useHttps: ${
367-
useHttps ? 'true' : 'false'
368-
}});
391+
`ReactDevToolsBackend.connectToDevTools({port: ${clientPort}, host: '${clientHost}', useHttps: ${
392+
clientUseHttps ? 'true' : 'false'
393+
}${path != null ? `, path: '${path}'` : ''}});
369394
`,
370395
);
371396
});
372397

373398
httpServer.on('error', (event: $FlowFixMe) => {
374399
onError(event);
375400
statusListener('Failed to start the server.', 'error');
376-
startServerTimeoutID = setTimeout(() => startServer(port), 1000);
401+
startServerTimeoutID = setTimeout(
402+
() =>
403+
startServer(
404+
port,
405+
host,
406+
httpsOptions,
407+
loggerOptions,
408+
path,
409+
clientOptions,
410+
),
411+
1000,
412+
);
377413
});
378414

379415
httpServer.listen(port, () => {

packages/react-devtools/README.md

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,31 @@ This will ensure the developer tools are connected. **Don’t forget to remove i
8787
8888
## Advanced
8989

90-
By default DevTools listen to port `8097` on `localhost`. The port can be modified by setting the `REACT_DEVTOOLS_PORT` environment variable. If you need to further customize host, port, or other settings, see the `react-devtools-core` package instead.
90+
By default DevTools listen to port `8097` on `localhost`. If you need to customize the server or client connection settings, the following environment variables are available:
91+
92+
| Env Var | Default | Description |
93+
|---|---|---|
94+
| `HOST` | `"localhost"` | Host the local server binds to. |
95+
| `PORT` | `8097` | Port the local server listens on. |
96+
| `REACT_DEVTOOLS_PORT` | | Alias for `PORT`. Takes precedence if both are set. |
97+
| `KEY` | | Path to an SSL key file. Enables HTTPS when set alongside `CERT`. |
98+
| `CERT` | | Path to an SSL certificate file. Enables HTTPS when set alongside `KEY`. |
99+
| `REACT_DEVTOOLS_PATH` | | Path appended to the WebSocket URI served to clients (e.g. `/__react_devtools__/`). |
100+
| `REACT_DEVTOOLS_CLIENT_HOST` | `HOST` | Overrides the host in the script served to connecting clients. |
101+
| `REACT_DEVTOOLS_CLIENT_PORT` | `PORT` | Overrides the port in the script served to connecting clients. |
102+
| `REACT_DEVTOOLS_CLIENT_USE_HTTPS` | | Set to `"true"` to make the served client script use `wss://`. |
103+
104+
When connecting through a reverse proxy, use the `REACT_DEVTOOLS_CLIENT_*` variables to tell clients to connect to a different host/port/protocol than the local server:
105+
106+
```sh
107+
REACT_DEVTOOLS_CLIENT_HOST=remote.example.com \
108+
REACT_DEVTOOLS_CLIENT_PORT=443 \
109+
REACT_DEVTOOLS_CLIENT_USE_HTTPS=true \
110+
REACT_DEVTOOLS_PATH=/__react_devtools__/ \
111+
react-devtools
112+
```
113+
114+
For more details, see the [`react-devtools-core` documentation](https://github.com/facebook/react/tree/main/packages/react-devtools-core).
91115

92116
## FAQ
93117

packages/react-devtools/app.html

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -158,12 +158,19 @@
158158
<script>
159159
// window.api is defined in preload.js
160160
const {electron, readEnv, ip, getDevTools} = window.api;
161-
const {options, useHttps, host, protocol, port} = readEnv();
161+
const {options, useHttps, host, protocol, port, path, clientHost, clientPort, clientUseHttps} = readEnv();
162162

163163
const localIp = ip.address();
164-
const defaultPort = (port === 443 && useHttps) || (port === 80 && !useHttps);
165-
const server = defaultPort ? `${protocol}://${host}` : `${protocol}://${host}:${port}`;
166-
const serverIp = defaultPort ? `${protocol}://${localIp}` : `${protocol}://${localIp}:${port}`;
164+
165+
// Effective values for display URLs: client overrides take precedence over server values.
166+
const effectiveHost = clientHost != null ? clientHost : host;
167+
const effectivePort = clientPort != null ? clientPort : port;
168+
const effectiveUseHttps = clientUseHttps != null ? clientUseHttps : useHttps;
169+
const effectiveProtocol = effectiveUseHttps ? 'https' : 'http';
170+
const defaultPort = (effectivePort === 443 && effectiveUseHttps) || (effectivePort === 80 && !effectiveUseHttps);
171+
const pathStr = path != null ? path : '';
172+
const server = defaultPort ? `${effectiveProtocol}://${effectiveHost}${pathStr}` : `${effectiveProtocol}://${effectiveHost}:${effectivePort}${pathStr}`;
173+
const serverIp = defaultPort ? `${effectiveProtocol}://${localIp}${pathStr}` : `${effectiveProtocol}://${localIp}:${effectivePort}${pathStr}`;
167174
const $ = document.querySelector.bind(document);
168175

169176
let timeoutID;
@@ -234,7 +241,7 @@
234241
element.innerText = status;
235242
}
236243
})
237-
.startServer(port, host, options);
244+
.startServer(port, host, options, undefined, path, {host: clientHost, port: clientPort, useHttps: clientUseHttps});
238245
</script>
239246
</body>
240247
</html>

packages/react-devtools/preload.js

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,23 @@ contextBridge.exposeInMainWorld('api', {
3636
const host = process.env.HOST || 'localhost';
3737
const protocol = useHttps ? 'https' : 'http';
3838
const port = +process.env.REACT_DEVTOOLS_PORT || +process.env.PORT || 8097;
39-
return {options, useHttps, host, protocol, port};
39+
const path = process.env.REACT_DEVTOOLS_PATH || undefined;
40+
const clientHost = process.env.REACT_DEVTOOLS_CLIENT_HOST || undefined;
41+
const clientPort = process.env.REACT_DEVTOOLS_CLIENT_PORT
42+
? +process.env.REACT_DEVTOOLS_CLIENT_PORT
43+
: undefined;
44+
const clientUseHttps =
45+
process.env.REACT_DEVTOOLS_CLIENT_USE_HTTPS === 'true' ? true : undefined;
46+
return {
47+
options,
48+
useHttps,
49+
host,
50+
protocol,
51+
port,
52+
path,
53+
clientHost,
54+
clientPort,
55+
clientUseHttps,
56+
};
4057
},
4158
});

0 commit comments

Comments
 (0)