Add SSL support to React devtools standalone (#19191)
Co-authored-by: Brian Vaughn <brian.david.vaughn@gmail.com>
Ittai Baratz committed
Jul 6, 2020 at 07:09 UTC
1cbaf48889c6362232d907e57ce6c132744445d0
4 files changed
+65
-8
packages/react-devtools-core/README.md
+19
@@ -21,6 +21,7 @@ Be sure to run this function *before* importing e.g. `react`, `react-dom`, `reac
21
The `config` object may contain:
22
* `host: string` (defaults to "localhost") - Websocket will connect to this host.
23
* `port: number` (defaults to `8097`) - Websocket will connect to this port.
24
+* `useHttps: boolean` (defaults to `false`) - Websocked should use a secure protocol (wss).
25
* `websocket: Websocket` - Custom websocket to use. Overrides `host` and `port` settings if provided.
26
* `resolveRNStyle: (style: number) => ?Object` - Used by the React Native style plug-in.
27
* `isAppActive: () => boolean` - If provided, DevTools will poll this method and wait until it returns true before connecting to React.
@@ -38,6 +39,24 @@ require("react-devtools-core/standalone")
39
.startServer(port);
40
```
41
42
+Renders DevTools interface into a DOM node over SSL using a custom host name (Default is localhost).
43
+
44
+```js
45
+const host = 'dev.server.com';
46
+const options = {
47
+ key: fs.readFileSync('test/fixtures/keys/agent2-key.pem'),
48
+ cert: fs.readFileSync('test/fixtures/keys/agent2-cert.pem')
49
+};
50
+
51
+
52
+require("react-devtools-core/standalone")
53
+ .setContentDOMNode(document.getElementById("container"))
54
+ .setStatusListener(status => {
55
+ // This callback is optional...
56
+ })
57
+ .startServer(port, host, options);
58
+```
59
+
60
Reference the `react-devtools` package for a complete integration example.
61
62
## Development
packages/react-devtools-core/src/backend.js
+4
-1
@@ -24,6 +24,7 @@ type ConnectOptions = {
24
host?: string,
25
nativeStyleEditorValidAttributes?: $ReadOnlyArray<string>,
26
port?: number,
27
+ useHttps?: boolean,
28
resolveRNStyle?: ResolveNativeStyle,
29
isAppActive?: () => boolean,
30
websocket?: ?WebSocket,
@@ -55,12 +56,14 @@ export function connectToDevTools(options: ?ConnectOptions) {
56
const {
57
host = 'localhost',
58
nativeStyleEditorValidAttributes,
59
+ useHttps = false,
60
port = 8097,
61
websocket,
62
resolveRNStyle = null,
63
isAppActive = () => true,
64
} = options || {};
65
66
+ const protocol = useHttps ? 'wss' : 'ws';
67
let retryTimeoutID: TimeoutID | null = null;
68
69
function scheduleRetry() {
@@ -80,7 +83,7 @@ export function connectToDevTools(options: ?ConnectOptions) {
83
let bridge: BackendBridge | null = null;
84
85
const messageListeners = [];
83
- const uri = 'ws://' + host + ':' + port;
86
+ const uri = protocol + '://' + host + ':' + port;
87
88
// If existing websocket is passed, use it.
89
// This is necessary to support our custom integrations.
packages/react-devtools-core/src/standalone.js
+17
-3
@@ -242,8 +242,20 @@ function connectToSocket(socket: WebSocket) {
242
};
243
}
244
245
-function startServer(port?: number = 8097) {
246
- const httpServer = require('http').createServer();
245
+type ServerOptions = {
246
+ key?: string,
247
+ cert?: string,
248
+};
249
+
250
+function startServer(
251
+ port?: number = 8097,
252
+ host?: string = 'localhost',
253
+ httpsOptions?: ServerOptions,
254
+) {
255
+ const useHttps = !!httpsOptions;
256
+ const httpServer = useHttps
257
+ ? require('https').createServer(httpsOptions)
258
+ : require('http').createServer();
259
const server = new Server({server: httpServer});
260
let connected: WebSocket | null = null;
261
server.on('connection', (socket: WebSocket) => {
@@ -298,7 +310,9 @@ function startServer(port?: number = 8097) {
310
'\n;' +
311
backendFile.toString() +
312
'\n;' +
301
- 'ReactDevToolsBackend.connectToDevTools();',
313
+ `ReactDevToolsBackend.connectToDevTools({port: ${port}, host: '${host}', useHttps: ${
314
+ useHttps ? 'true' : 'false'
315
+ }});`,
316
);
317
});
318
packages/react-devtools/app.html
+25
-4
@@ -145,12 +145,33 @@
145
</div>
146
</div>
147
<script>
148
+ const fs = require('fs');
149
+ let options;
150
+ let useHttps = false;
151
+
152
+ try {
153
+ if (process.env.KEY && process.env.CERT) {
154
+ options.key = fs.readFileSync(process.env.KEY);
155
+ options.cert = fs.readFileSync(process.env.CERT);
156
+ useHttps = true;
157
+ }
158
+ } catch (err) {
159
+ console.error('Failed to process SSL options - ', err);
160
+ options = undefined;
161
+ }
162
+
163
const {clipboard} = require("electron");
149
- const port = process.env.PORT || 8097;
164
+ const host = process.env.HOST || 'localhost';
165
+ const protocol = useHttps ? 'https' : 'http';
166
+ const port = Number(process.env.PORT || 8097);
167
const localIp = require("ip").address();
168
+ const defaultPort = (port === 443 && useHttps) || (port === 80 && !useHttps);
169
+ const server = defaultPort ? `${protocol}://${host}` : `${protocol}://${host}:${port}`;
170
+ const serverIp = defaultPort ? `${protocol}://${localIp}` : `${protocol}://${localIp}:${port}`;
171
const $ = document.querySelector.bind(document);
172
const $promptDiv = $("#box-content-prompt");
173
const $confirmationDiv = $("#box-content-confirmation");
174
+
175
let timeoutID;
176
177
function selectAllAndCopy(event) {
@@ -184,12 +205,12 @@
205
});
206
207
const $localhost = $("#localhost");
187
- $localhost.innerText = `<script src="http://localhost:${port}"></` + 'script>';
208
+ $localhost.innerText = `<script src="${server}"></` + 'script>';
209
$localhost.addEventListener('click', selectAllAndCopy);
210
$localhost.addEventListener('focus', selectAllAndCopy);
211
212
const $byIp = $("#byip");
192
- $byIp.innerText = `<script src="http://${localIp}:${port}"></` + 'script>';
213
+ $byIp.innerText = `<script src="${serverIp}"></` + 'script>';
214
$byIp.addEventListener('click', selectAllAndCopy);
215
$byIp.addEventListener('focus', selectAllAndCopy);
216
@@ -211,7 +232,7 @@
232
element.innerText = status;
233
}
234
})
214
- .startServer(port);
235
+ .startServer(port, host, options);
236
</script>
237
</body>
238
</html>