diff --git a/packages/react-devtools-core/README.md b/packages/react-devtools-core/README.md index a42e569697fd..3cced5e0e4bb 100644 --- a/packages/react-devtools-core/README.md +++ b/packages/react-devtools-core/README.md @@ -54,6 +54,7 @@ Each filter object must include `type` and `isEnabled`. Some filters also requir |------------------------|---------------|---------------------------------------------------------------------------------------------------------------------------| | `host` | `"localhost"` | Socket connection to frontend should use this host. | | `isAppActive` | | (Optional) function that returns true/false, telling DevTools when it's ready to connect to React. | +| `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. | | `port` | `8097` | Socket connection to frontend should use this port. | | `resolveRNStyle` | | (Optional) function that accepts a key (number) and returns a style (object); used by React Native. | | `retryConnectionDelay` | `200` | Delay (ms) to wait between retrying a failed Websocket connection | @@ -141,16 +142,51 @@ function onStatus( } ``` -#### `startServer(port?: number, host?: string, httpsOptions?: Object, loggerOptions?: Object)` +#### `startServer(port?, host?, httpsOptions?, loggerOptions?, path?, clientOptions?)` Start a socket server (used to communicate between backend and frontend) and renders the DevTools UI. This method accepts the following parameters: | Name | Default | Description | |---|---|---| -| `port` | `8097` | Socket connection to backend should use this port. | -| `host` | `"localhost"` | Socket connection to backend should use this host. | +| `port` | `8097` | Port the local server listens on. | +| `host` | `"localhost"` | Host the local server binds to. | | `httpsOptions` | | _Optional_ object defining `key` and `cert` strings. | | `loggerOptions` | | _Optional_ object defining a `surface` string (to be included with DevTools logging events). | +| `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. | +| `clientOptions` | | _Optional_ object with client-facing overrides (see below). | + +##### `clientOptions` + +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. + +| Field | Default | Description | +|---|---|---| +| `host` | server `host` | Host the client connects to. | +| `port` | server `port` | Port the client connects to. | +| `useHttps` | server `useHttps` | Whether the client should use `wss://`. | + +These can also be set via environment variables in the Electron app: + +| Env Var | Description | +|---|---| +| `REACT_DEVTOOLS_CLIENT_HOST` | Overrides the host in the served client script. | +| `REACT_DEVTOOLS_CLIENT_PORT` | Overrides the port in the served client script. | +| `REACT_DEVTOOLS_CLIENT_USE_HTTPS` | Set to `"true"` to make the served client script use `wss://`. | + +##### Reverse proxy example + +Run DevTools locally on the default port, but tell clients to connect through a remote proxy: +```sh +REACT_DEVTOOLS_CLIENT_HOST=remote.example.com \ +REACT_DEVTOOLS_CLIENT_PORT=443 \ +REACT_DEVTOOLS_CLIENT_USE_HTTPS=true \ +REACT_DEVTOOLS_PATH=/__react_devtools__/ \ +react-devtools +``` +The server listens on `localhost:8097`. The served script tells clients: +```js +connectToDevTools({host: 'remote.example.com', port: 443, useHttps: true, path: '/__react_devtools__/'}) +``` # Development diff --git a/packages/react-devtools-core/src/backend.js b/packages/react-devtools-core/src/backend.js index 262b3bc04127..075758f78500 100644 --- a/packages/react-devtools-core/src/backend.js +++ b/packages/react-devtools-core/src/backend.js @@ -33,6 +33,7 @@ import type {ResolveNativeStyle} from 'react-devtools-shared/src/backend/NativeS type ConnectOptions = { host?: string, nativeStyleEditorValidAttributes?: $ReadOnlyArray, + path?: string, port?: number, useHttps?: boolean, resolveRNStyle?: ResolveNativeStyle, @@ -93,6 +94,7 @@ export function connectToDevTools(options: ?ConnectOptions) { const { host = 'localhost', nativeStyleEditorValidAttributes, + path = '', useHttps = false, port = 8097, websocket, @@ -107,6 +109,7 @@ export function connectToDevTools(options: ?ConnectOptions) { } = options || {}; const protocol = useHttps ? 'wss' : 'ws'; + const prefixedPath = path !== '' && !path.startsWith('/') ? '/' + path : path; let retryTimeoutID: TimeoutID | null = null; function scheduleRetry() { @@ -129,7 +132,7 @@ export function connectToDevTools(options: ?ConnectOptions) { let bridge: BackendBridge | null = null; const messageListeners = []; - const uri = protocol + '://' + host + ':' + port; + const uri = protocol + '://' + host + ':' + port + prefixedPath; // If existing websocket is passed, use it. // This is necessary to support our custom integrations. diff --git a/packages/react-devtools-core/src/standalone.js b/packages/react-devtools-core/src/standalone.js index df89a728c308..81f357751913 100644 --- a/packages/react-devtools-core/src/standalone.js +++ b/packages/react-devtools-core/src/standalone.js @@ -306,11 +306,19 @@ type LoggerOptions = { surface?: ?string, }; +type ClientOptions = { + host?: string, + port?: number, + useHttps?: boolean, +}; + function startServer( port: number = 8097, host: string = 'localhost', httpsOptions?: ServerOptions, loggerOptions?: LoggerOptions, + path?: string, + clientOptions?: ClientOptions, ): {close(): void} { registerDevToolsEventLogger(loggerOptions?.surface ?? 'standalone'); @@ -345,7 +353,18 @@ function startServer( server.on('error', (event: $FlowFixMe) => { onError(event); log.error('Failed to start the DevTools server', event); - startServerTimeoutID = setTimeout(() => startServer(port), 1000); + startServerTimeoutID = setTimeout( + () => + startServer( + port, + host, + httpsOptions, + loggerOptions, + path, + clientOptions, + ), + 1000, + ); }); httpServer.on('request', (request: $FlowFixMe, response: $FlowFixMe) => { @@ -358,14 +377,21 @@ function startServer( // This will ensure that saved filters are shared across different web pages. const componentFiltersString = JSON.stringify(getSavedComponentFilters()); + // Client overrides: when connecting through a reverse proxy, the client + // may need to connect to a different host/port/protocol than the server. + const clientHost = clientOptions?.host ?? host; + const clientPort = clientOptions?.port ?? port; + const clientUseHttps = clientOptions?.useHttps ?? useHttps; + response.end( backendFile.toString() + '\n;' + + `var ReactDevToolsBackend = typeof ReactDevToolsBackend !== "undefined" ? ReactDevToolsBackend : require("ReactDevToolsBackend");\n` + `ReactDevToolsBackend.initialize(undefined, undefined, undefined, ${componentFiltersString});` + '\n' + - `ReactDevToolsBackend.connectToDevTools({port: ${port}, host: '${host}', useHttps: ${ - useHttps ? 'true' : 'false' - }}); + `ReactDevToolsBackend.connectToDevTools({port: ${clientPort}, host: '${clientHost}', useHttps: ${ + clientUseHttps ? 'true' : 'false' + }${path != null ? `, path: '${path}'` : ''}}); `, ); }); @@ -373,7 +399,18 @@ function startServer( httpServer.on('error', (event: $FlowFixMe) => { onError(event); statusListener('Failed to start the server.', 'error'); - startServerTimeoutID = setTimeout(() => startServer(port), 1000); + startServerTimeoutID = setTimeout( + () => + startServer( + port, + host, + httpsOptions, + loggerOptions, + path, + clientOptions, + ), + 1000, + ); }); httpServer.listen(port, () => { diff --git a/packages/react-devtools-core/webpack.backend.js b/packages/react-devtools-core/webpack.backend.js index 32d4fadcb588..67b714825c87 100644 --- a/packages/react-devtools-core/webpack.backend.js +++ b/packages/react-devtools-core/webpack.backend.js @@ -44,6 +44,7 @@ module.exports = { // This name is important; standalone references it in order to connect. library: 'ReactDevToolsBackend', libraryTarget: 'umd', + umdNamedDefine: true, }, resolve: { alias: { diff --git a/packages/react-devtools/README.md b/packages/react-devtools/README.md index 8c436c1fb2b4..faa71dcc596d 100644 --- a/packages/react-devtools/README.md +++ b/packages/react-devtools/README.md @@ -87,7 +87,31 @@ This will ensure the developer tools are connected. **Don’t forget to remove i ## Advanced -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. +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: + +| Env Var | Default | Description | +|---|---|---| +| `HOST` | `"localhost"` | Host the local server binds to. | +| `PORT` | `8097` | Port the local server listens on. | +| `REACT_DEVTOOLS_PORT` | | Alias for `PORT`. Takes precedence if both are set. | +| `KEY` | | Path to an SSL key file. Enables HTTPS when set alongside `CERT`. | +| `CERT` | | Path to an SSL certificate file. Enables HTTPS when set alongside `KEY`. | +| `REACT_DEVTOOLS_PATH` | | Path appended to the WebSocket URI served to clients (e.g. `/__react_devtools__/`). | +| `REACT_DEVTOOLS_CLIENT_HOST` | `HOST` | Overrides the host in the script served to connecting clients. | +| `REACT_DEVTOOLS_CLIENT_PORT` | `PORT` | Overrides the port in the script served to connecting clients. | +| `REACT_DEVTOOLS_CLIENT_USE_HTTPS` | | Set to `"true"` to make the served client script use `wss://`. | + +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: + +```sh +REACT_DEVTOOLS_CLIENT_HOST=remote.example.com \ +REACT_DEVTOOLS_CLIENT_PORT=443 \ +REACT_DEVTOOLS_CLIENT_USE_HTTPS=true \ +REACT_DEVTOOLS_PATH=/__react_devtools__/ \ +react-devtools +``` + +For more details, see the [`react-devtools-core` documentation](https://github.com/facebook/react/tree/main/packages/react-devtools-core). ## FAQ diff --git a/packages/react-devtools/app.html b/packages/react-devtools/app.html index 7b55e0d37db9..1d11394c9427 100644 --- a/packages/react-devtools/app.html +++ b/packages/react-devtools/app.html @@ -158,12 +158,19 @@ diff --git a/packages/react-devtools/preload.js b/packages/react-devtools/preload.js index 33a9e3d6dd46..3286d4442097 100644 --- a/packages/react-devtools/preload.js +++ b/packages/react-devtools/preload.js @@ -36,6 +36,23 @@ contextBridge.exposeInMainWorld('api', { const host = process.env.HOST || 'localhost'; const protocol = useHttps ? 'https' : 'http'; const port = +process.env.REACT_DEVTOOLS_PORT || +process.env.PORT || 8097; - return {options, useHttps, host, protocol, port}; + const path = process.env.REACT_DEVTOOLS_PATH || undefined; + const clientHost = process.env.REACT_DEVTOOLS_CLIENT_HOST || undefined; + const clientPort = process.env.REACT_DEVTOOLS_CLIENT_PORT + ? +process.env.REACT_DEVTOOLS_CLIENT_PORT + : undefined; + const clientUseHttps = + process.env.REACT_DEVTOOLS_CLIENT_USE_HTTPS === 'true' ? true : undefined; + return { + options, + useHttps, + host, + protocol, + port, + path, + clientHost, + clientPort, + clientUseHttps, + }; }, });