Files
Mushaheed Kapadia c0060cf2a6 [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.
2026-02-24 15:36:32 +00:00

248 lines
7.5 KiB
HTML

<!DOCTYPE html>
<html>
<head>
<title>React Developer Tools</title>
<meta charset="utf8" />
<style>
html {
height: 100%;
font-family: sans-serif;
}
body {
height: 100%;
margin: 0;
padding: 0;
background-color: #fff;
color: #777d88;
}
.container {
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
overflow: auto;
}
p {
padding: 0;
margin: 0;
}
.input {
display: block;
font-weight: 100;
padding: 0 0.25rem;
border: 1px solid #aaa;
background-color: #fff;
color: #666;
}
.link {
color: #1478fa;
text-decoration: none;
}
.link:hover {
text-decoration: underline;
}
.waiting-header {
padding: 0.5rem;
display: inline-block;
position: absolute;
right: 0.5rem;
top: 0.5rem;
border-radius: 0.25rem;
background-color: rgba(0,1,2,.6);
color: white;
border: none;
font-weight: 100;
font-style: italic;
}
.boxes {
display: flex;
flex-direction: column;
align-items: stretch;
justify-content: center;
padding: 1rem;
}
.box {
text-align: center;
border-radius: 0.5rem;
background-color: #f7f7f7;
border: 1px solid #eee;
color: #777d88;
padding: 1rem;
margin-top: 1rem;
}
.box:first-of-type {
margin-top: 0;
}
.box-header {
text-align: center;
color: #5f6673;
font-size: 1.25rem;
margin-bottom: 0.5rem;
}
.box-content {
line-height: 1.5rem;
}
#loading-status {
text-align: center;
margin-top: 1rem;
}
.prompt,
.confirmation {
margin-bottom: 0.25rem;
}
.confirmation {
font-style: italic;
}
.hidden {
display: none;
}
</style>
</head>
<body>
<div id="container" class="container" style="-webkit-user-select: none; -webkit-app-region: drag;">
<div class="waiting-header">Waiting for React to connect…</div>
<div class="boxes" style="-webkit-app-region: none;">
<div class="box">
<div class="box-header">React Native</div>
<div class="box-content">
Open the <a
id="rn-help-link"
class="link"
target="_blank"
rel="noopener noreferrer"
href="https://reactnative.dev/docs/debugging#accessing-the-in-app-developer-menu"
>in-app developer menu</a> to connect.
</div>
</div>
<div class="box">
<div class="box-header">React DOM</div>
<div class="box-content">
<div id="box-content-prompt" class="prompt">
Add one of the following (click to copy):
</div>
<div id="box-content-confirmation" class="confirmation hidden">
Copied to clipboard.
</div>
<span class="input" contenteditable="true" id="localhost"></span>
<span class="input" contenteditable="true" id="byip"></span>
to the top of the page you want to debug,
<br />
<strong>before</strong> importing React DOM.
</div>
</div>
<div class="box">
<div class="box-header">Profiler</div>
<div class="box-content">
Open the <a
id="profiler"
class="link"
href="#"
>Profiler tab</a> to inspect saved profiles.
</div>
</div>
<div id="loading-status">Starting the server…</div>
</div>
</div>
<script>
// window.api is defined in preload.js
const {electron, readEnv, ip, getDevTools} = window.api;
const {options, useHttps, host, protocol, port, path, clientHost, clientPort, clientUseHttps} = readEnv();
const localIp = ip.address();
// Effective values for display URLs: client overrides take precedence over server values.
const effectiveHost = clientHost != null ? clientHost : host;
const effectivePort = clientPort != null ? clientPort : port;
const effectiveUseHttps = clientUseHttps != null ? clientUseHttps : useHttps;
const effectiveProtocol = effectiveUseHttps ? 'https' : 'http';
const defaultPort = (effectivePort === 443 && effectiveUseHttps) || (effectivePort === 80 && !effectiveUseHttps);
const pathStr = path != null ? path : '';
const server = defaultPort ? `${effectiveProtocol}://${effectiveHost}${pathStr}` : `${effectiveProtocol}://${effectiveHost}:${effectivePort}${pathStr}`;
const serverIp = defaultPort ? `${effectiveProtocol}://${localIp}${pathStr}` : `${effectiveProtocol}://${localIp}:${effectivePort}${pathStr}`;
const $ = document.querySelector.bind(document);
let timeoutID;
function selectAllAndCopy(event) {
const element = event.target;
if (window.getSelection) {
const selection = window.getSelection();
const range = document.createRange();
range.selectNodeContents(element);
selection.removeAllRanges();
selection.addRange(range);
electron.clipboard.writeText(event.target.textContent);
const $promptDiv = $("#box-content-prompt");
const $confirmationDiv = $("#box-content-confirmation");
$promptDiv.classList.add('hidden');
$confirmationDiv.classList.remove('hidden');
if (timeoutID) {
clearTimeout(timeoutID);
}
timeoutID = setTimeout(() => {
$promptDiv.classList.remove('hidden');
$confirmationDiv.classList.add('hidden');
}, 1000);
}
}
function openProfiler() {
window.devtools
.setContentDOMNode(document.getElementById("container"))
.openProfiler();
}
function attachListeners() {
const link = $('#rn-help-link');
link.addEventListener('click', event => {
event.preventDefault();
electron.shell.openExternal(link.href);
});
const $localhost = $("#localhost");
$localhost.innerText = `<script src="${server}"></` + 'script>';
$localhost.addEventListener('click', selectAllAndCopy);
$localhost.addEventListener('focus', selectAllAndCopy);
const $byIp = $("#byip");
$byIp.innerText = `<script src="${serverIp}"></` + 'script>';
$byIp.addEventListener('click', selectAllAndCopy);
$byIp.addEventListener('focus', selectAllAndCopy);
const $profiler = $("#profiler");
$profiler.addEventListener('click', openProfiler);
};
// Initially attach the listeners
attachListeners();
window.devtools = getDevTools();
window.server = window.devtools
.setContentDOMNode(document.getElementById("container"))
.setDisconnectedCallback(attachListeners)
.setStatusListener(function(status) {
const element = document.getElementById("loading-status");
if (element) {
element.innerText = status;
}
})
.startServer(port, host, options, undefined, path, {host: clientHost, port: clientPort, useHttps: clientUseHttps});
</script>
</body>
</html>