Skip to content

Commit c0060cf

Browse files
[DevTools] Enable support for the React DevTools Client to connect to different host/port/path (#35886)
## Summary This enables routing the React Dev Tools through a remote server by being able to specify host, port, and path for the client to connect to. Basically allowing the React Dev Tools server to have the client connect elsewhere. This setups a `clientOptions` which can be set up through environment variables when starting the React Dev Tools server. This change shouldn't affect the traditional usage for React Dev Tools. EDIT: the additional change was moved to another PR ## How did you test this change? Run React DevTools with ``` $ REACT_DEVTOOLS_CLIENT_HOST=<MY_HOST> REACT_DEVTOOLS_CLIENT_PORT=443 REACT_DEVTOOLS_CLIENT_USE_HTTPS=true REACT_DEVTOOLS_PATH=/__react_devtools__/ yarn start ``` Confirm that my application connects to the local React Dev Tools server/instance/electron app through my remote server.
1 parent bd76b45 commit c0060cf

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,23 +377,40 @@ 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
`var ReactDevToolsBackend = typeof ReactDevToolsBackend !== "undefined" ? ReactDevToolsBackend : require("ReactDevToolsBackend");\n` +
365390
`ReactDevToolsBackend.initialize(undefined, undefined, undefined, ${componentFiltersString});` +
366391
'\n' +
367-
`ReactDevToolsBackend.connectToDevTools({port: ${port}, host: '${host}', useHttps: ${
368-
useHttps ? 'true' : 'false'
369-
}});
392+
`ReactDevToolsBackend.connectToDevTools({port: ${clientPort}, host: '${clientHost}', useHttps: ${
393+
clientUseHttps ? 'true' : 'false'
394+
}${path != null ? `, path: '${path}'` : ''}});
370395
`,
371396
);
372397
});
373398

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

380416
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)