Show status of connection, so that user knows what is happening

Summary: In many cases the React DevTools fail to show up for RN. usually that is because the app didn't enter devMode yet. This diff adds the necessary logic to query the state of the DevTools and communicate back the current connection status

Reviewed By: passy

Differential Revision: D19878127

fbshipit-source-id: f5c3f5a92b23c87c87d778a468122210325eed17
This commit is contained in:
Michel Weststrate
2020-02-17 03:36:59 -08:00
committed by Facebook Github Bot
parent 14ebfb8439
commit 3849807d6b
2 changed files with 82 additions and 10 deletions

View File

@@ -7,9 +7,8 @@
* @format * @format
*/ */
import ReactDOM from 'react-dom';
import ReactDevToolsStandalone from 'react-devtools-core/standalone'; import ReactDevToolsStandalone from 'react-devtools-core/standalone';
import {FlipperPlugin, AndroidDevice, styled} from 'flipper'; import {FlipperPlugin, AndroidDevice, styled, View, Toolbar} from 'flipper';
import React from 'react'; import React from 'react';
import getPort from 'get-port'; import getPort from 'get-port';
import address from 'address'; import address from 'address';
@@ -19,6 +18,7 @@ const Container = styled.div({
flex: '1 1 0%', flex: '1 1 0%',
justifyContent: 'center', justifyContent: 'center',
alignItems: 'stretch', alignItems: 'stretch',
height: '100%',
}); });
const DEV_TOOLS_NODE_ID = 'reactdevtools-out-of-react-node'; const DEV_TOOLS_NODE_ID = 'reactdevtools-out-of-react-node';
@@ -43,7 +43,7 @@ function findDevToolsNode(): HTMLElement | null {
} }
function attachDevTools(target: Element | Text, devToolsNode: HTMLElement) { function attachDevTools(target: Element | Text, devToolsNode: HTMLElement) {
target.insertBefore(devToolsNode, target.childNodes[0]); target.appendChild(devToolsNode);
devToolsNode.style.display = 'flex'; devToolsNode.style.display = 'flex';
} }
@@ -52,27 +52,89 @@ function detachDevTools(devToolsNode: HTMLElement) {
document.body && document.body.appendChild(devToolsNode); document.body && document.body.appendChild(devToolsNode);
} }
export default class extends FlipperPlugin<{}, any, {}> { const CONNECTED = 'DevTools connected';
export default class extends FlipperPlugin<
{
status: string;
},
any,
{}
> {
pollHandle?: NodeJS.Timeout;
containerRef: React.RefObject<HTMLDivElement> = React.createRef();
triedToAutoConnect = false;
state = {
status: 'initializing',
};
componentDidMount() { componentDidMount() {
let devToolsNode = findDevToolsNode(); let devToolsNode = findDevToolsNode();
if (!devToolsNode) { if (!devToolsNode) {
devToolsNode = createDevToolsNode(); devToolsNode = createDevToolsNode();
this.initializeDevTools(devToolsNode); this.initializeDevTools(devToolsNode);
} else {
this.setStatus(
'DevTools have been initialized, waiting for connection...',
);
if (devToolsNode.innerHTML) {
this.setStatus(CONNECTED);
} else {
this.startPollForConnection();
}
} }
const currentComponentNode = ReactDOM.findDOMNode(this); attachDevTools(this.containerRef?.current!, devToolsNode);
currentComponentNode && attachDevTools(currentComponentNode, devToolsNode); this.startPollForConnection();
} }
componentWillUnmount() { componentWillUnmount() {
if (this.pollHandle) {
clearTimeout(this.pollHandle);
}
const devToolsNode = findDevToolsNode(); const devToolsNode = findDevToolsNode();
devToolsNode && detachDevTools(devToolsNode); devToolsNode && detachDevTools(devToolsNode);
} }
setStatus(status: string) {
console.log(`[ReactDevtoolsPlugin] ${status}`);
if (status.startsWith('The server is listening on')) {
this.setState({status: status + ' Waiting for connection...'});
} else {
this.setState({status});
}
}
startPollForConnection() {
this.pollHandle = setTimeout(() => {
if (findDevToolsNode()?.innerHTML) {
this.setStatus(CONNECTED);
} else {
if (!this.triedToAutoConnect) {
this.triedToAutoConnect = true;
this.setStatus(
"The DevTools didn't connect yet. Please open the DevMenu or Reload to connect",
);
// TODO: send reload command
}
this.startPollForConnection();
}
}, 3000);
}
async initializeDevTools(devToolsNode: HTMLElement) { async initializeDevTools(devToolsNode: HTMLElement) {
this.setStatus('Waiting for port 8097');
const port = await getPort({port: 8097}); // default port for dev tools const port = await getPort({port: 8097}); // default port for dev tools
ReactDevToolsStandalone.setContentDOMNode(devToolsNode).startServer(port); this.setStatus('Starting DevTools server on ' + port);
ReactDevToolsStandalone.setContentDOMNode(devToolsNode)
.setStatusListener(status => {
this.setStatus(status);
})
.startServer(port);
this.setStatus('Waiting for device');
const device = await this.getDevice(); const device = await this.getDevice();
if (device) { if (device) {
const host = const host =
device.deviceType === 'physical' device.deviceType === 'physical'
@@ -80,16 +142,25 @@ export default class extends FlipperPlugin<{}, any, {}> {
: device instanceof AndroidDevice : device instanceof AndroidDevice
? '10.0.2.2' // Host IP for Android emulator host system ? '10.0.2.2' // Host IP for Android emulator host system
: 'localhost'; : 'localhost';
this.setStatus(`Updating config to ${host}:${port}`);
this.client.call('config', {port, host}); this.client.call('config', {port, host});
if (['quest', 'go', 'pacific'].includes(device.title.toLowerCase())) { if (['quest', 'go', 'pacific'].includes(device.title.toLowerCase())) {
const device = await this.getDevice(); const device = await this.getDevice();
this.setStatus(`Setting up reverse port mapping: ${port}:${port}`);
(device as AndroidDevice).reverse([port, port]); (device as AndroidDevice).reverse([port, port]);
} }
} }
} }
render() { render() {
return <Container />; return (
<View grow>
{this.state.status !== CONNECTED ? (
<Toolbar>{this.state.status}</Toolbar>
) : null}
<Container ref={this.containerRef} />
</View>
);
} }
} }

View File

@@ -9,8 +9,9 @@
declare module 'react-devtools-core/standalone' { declare module 'react-devtools-core/standalone' {
interface DevTools { interface DevTools {
setContentDOMNode(node: HTMLElement): DevTools; setContentDOMNode(node: HTMLElement): this;
startServer(port: number): DevTools; startServer(port: number): this;
setStatusListener(listener: (message: string) => void): this;
} }
const DevTools: DevTools; const DevTools: DevTools;
export default DevTools; export default DevTools;