Several minor connection improvements
Summary: This diff 1. Fixes an reported issue where a concurrent running standalone React Devtools results in Flipper not connecting with a clear message 2. Improves the Error UI so that it is more clear what is happening 3. Introduces a Retry button that can be smashed to retrigger the whole initialization sequence, including retrying to grab the port needed for the connection Reviewed By: jknoxville Differential Revision: D20439911 fbshipit-source-id: f37a47f1000dd3b049dae8503856f6015cd422ab
This commit is contained in:
committed by
Facebook GitHub Bot
parent
1d04fc3e34
commit
406f21bc4e
@@ -13,11 +13,14 @@ import {
|
|||||||
AndroidDevice,
|
AndroidDevice,
|
||||||
styled,
|
styled,
|
||||||
View,
|
View,
|
||||||
Toolbar,
|
|
||||||
MetroDevice,
|
MetroDevice,
|
||||||
ReduxState,
|
ReduxState,
|
||||||
connect,
|
connect,
|
||||||
Device,
|
Device,
|
||||||
|
CenteredView,
|
||||||
|
RoundedSection,
|
||||||
|
Text,
|
||||||
|
Button,
|
||||||
} from 'flipper';
|
} from 'flipper';
|
||||||
import React, {useEffect} from 'react';
|
import React, {useEffect} from 'react';
|
||||||
import getPort from 'get-port';
|
import getPort from 'get-port';
|
||||||
@@ -88,6 +91,13 @@ const GrabMetroDevice = connect<
|
|||||||
|
|
||||||
const SUPPORTED_OCULUS_DEVICE_TYPES = ['quest', 'go', 'pacific'];
|
const SUPPORTED_OCULUS_DEVICE_TYPES = ['quest', 'go', 'pacific'];
|
||||||
|
|
||||||
|
enum ConnectionStatus {
|
||||||
|
Initializing = 'Initializing...',
|
||||||
|
WaitingForReload = 'Waiting for connection from device...',
|
||||||
|
Connected = 'Connected',
|
||||||
|
Error = 'Error',
|
||||||
|
}
|
||||||
|
|
||||||
export default class ReactDevTools extends FlipperDevicePlugin<
|
export default class ReactDevTools extends FlipperDevicePlugin<
|
||||||
{
|
{
|
||||||
status: string;
|
status: string;
|
||||||
@@ -101,7 +111,7 @@ export default class ReactDevTools extends FlipperDevicePlugin<
|
|||||||
|
|
||||||
pollHandle?: NodeJS.Timeout;
|
pollHandle?: NodeJS.Timeout;
|
||||||
containerRef: React.RefObject<HTMLDivElement> = React.createRef();
|
containerRef: React.RefObject<HTMLDivElement> = React.createRef();
|
||||||
triedToAutoConnect = false;
|
connectionStatus: ConnectionStatus = ConnectionStatus.Initializing;
|
||||||
metroDevice?: MetroDevice;
|
metroDevice?: MetroDevice;
|
||||||
isMounted = true;
|
isMounted = true;
|
||||||
|
|
||||||
@@ -110,23 +120,7 @@ export default class ReactDevTools extends FlipperDevicePlugin<
|
|||||||
};
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
let devToolsNode = findDevToolsNode();
|
this.bootDevTools();
|
||||||
if (!devToolsNode) {
|
|
||||||
devToolsNode = createDevToolsNode();
|
|
||||||
this.initializeDevTools(devToolsNode);
|
|
||||||
} else {
|
|
||||||
this.setStatus(
|
|
||||||
'DevTools have been initialized, waiting for connection...',
|
|
||||||
);
|
|
||||||
if (devToolsNode.innerHTML) {
|
|
||||||
this.setStatus(CONNECTED);
|
|
||||||
} else {
|
|
||||||
this.startPollForConnection();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
attachDevTools(this.containerRef?.current!, devToolsNode);
|
|
||||||
this.startPollForConnection();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
@@ -138,7 +132,8 @@ export default class ReactDevTools extends FlipperDevicePlugin<
|
|||||||
devToolsNode && detachDevTools(devToolsNode);
|
devToolsNode && detachDevTools(devToolsNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
setStatus(status: string) {
|
setStatus(connectionStatus: ConnectionStatus, status: string) {
|
||||||
|
this.connectionStatus = connectionStatus;
|
||||||
if (!this.isMounted) {
|
if (!this.isMounted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -149,42 +144,84 @@ export default class ReactDevTools extends FlipperDevicePlugin<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
startPollForConnection() {
|
devtoolsHaveStarted() {
|
||||||
|
return !!findDevToolsNode()?.innerHTML;
|
||||||
|
}
|
||||||
|
|
||||||
|
bootDevTools() {
|
||||||
|
let devToolsNode = findDevToolsNode();
|
||||||
|
if (!devToolsNode) {
|
||||||
|
devToolsNode = createDevToolsNode();
|
||||||
|
}
|
||||||
|
this.initializeDevTools(devToolsNode);
|
||||||
|
this.setStatus(
|
||||||
|
ConnectionStatus.Initializing,
|
||||||
|
'DevTools have been initialized, waiting for connection...',
|
||||||
|
);
|
||||||
|
if (this.devtoolsHaveStarted()) {
|
||||||
|
this.setStatus(ConnectionStatus.Connected, CONNECTED);
|
||||||
|
} else {
|
||||||
|
this.startPollForConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
attachDevTools(this.containerRef?.current!, devToolsNode);
|
||||||
|
this.startPollForConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
startPollForConnection(delay = 3000) {
|
||||||
this.pollHandle = setTimeout(() => {
|
this.pollHandle = setTimeout(() => {
|
||||||
if (!this.isMounted) {
|
switch (true) {
|
||||||
return false;
|
// Closed already, ignore
|
||||||
}
|
case !this.isMounted:
|
||||||
if (findDevToolsNode()?.innerHTML) {
|
return;
|
||||||
this.setStatus(CONNECTED);
|
// Found DevTools!
|
||||||
} else {
|
case this.devtoolsHaveStarted():
|
||||||
if (!this.triedToAutoConnect) {
|
this.setStatus(ConnectionStatus.Connected, CONNECTED);
|
||||||
this.triedToAutoConnect = true;
|
return;
|
||||||
|
// Waiting for connection, but we do have an active Metro connection, lets force a reload to enter Dev Mode on app
|
||||||
|
// prettier-ignore
|
||||||
|
case this.connectionStatus === ConnectionStatus.Initializing && !!this.metroDevice?.ws:
|
||||||
this.setStatus(
|
this.setStatus(
|
||||||
"The DevTools didn't connect yet. Please open the DevMenu in the React Native app, or Reload it to connect",
|
ConnectionStatus.WaitingForReload,
|
||||||
|
"Sending 'reload' to Metro to force the DevTools to connect...",
|
||||||
);
|
);
|
||||||
if (this.metroDevice && this.metroDevice.ws) {
|
this.metroDevice!.sendCommand('reload');
|
||||||
this.setStatus(
|
this.startPollForConnection(10000);
|
||||||
"Sending 'reload' to the Metro to force the DevTools to connect...",
|
return;
|
||||||
);
|
// Waiting for initial connection, but no WS bridge available
|
||||||
this.metroDevice?.sendCommand('reload');
|
case this.connectionStatus === ConnectionStatus.Initializing:
|
||||||
}
|
this.setStatus(
|
||||||
}
|
ConnectionStatus.WaitingForReload,
|
||||||
this.startPollForConnection();
|
"The DevTools didn't connect yet. Please trigger the DevMenu in the React Native app, or Reload it to connect",
|
||||||
|
);
|
||||||
|
this.startPollForConnection(10000);
|
||||||
|
return;
|
||||||
|
// Still nothing? Users might not have done manual action, or some other tools have picked it up?
|
||||||
|
case this.connectionStatus === ConnectionStatus.WaitingForReload:
|
||||||
|
this.setStatus(
|
||||||
|
ConnectionStatus.WaitingForReload,
|
||||||
|
"The DevTools didn't connect yet. Please verify your React Native app is in development mode, and that no other instance of the React DevTools are attached to the app already.",
|
||||||
|
);
|
||||||
|
this.startPollForConnection();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}, 3000);
|
}, delay);
|
||||||
}
|
}
|
||||||
|
|
||||||
async initializeDevTools(devToolsNode: HTMLElement) {
|
async initializeDevTools(devToolsNode: HTMLElement) {
|
||||||
try {
|
try {
|
||||||
this.setStatus('Waiting for port 8097');
|
this.setStatus(ConnectionStatus.Initializing, '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
|
||||||
this.setStatus('Starting DevTools server on ' + port);
|
this.setStatus(
|
||||||
|
ConnectionStatus.Initializing,
|
||||||
|
'Starting DevTools server on ' + port,
|
||||||
|
);
|
||||||
ReactDevToolsStandalone.setContentDOMNode(devToolsNode)
|
ReactDevToolsStandalone.setContentDOMNode(devToolsNode)
|
||||||
.setStatusListener(status => {
|
.setStatusListener(status => {
|
||||||
this.setStatus(status);
|
this.setStatus(ConnectionStatus.Initializing, status);
|
||||||
})
|
})
|
||||||
.startServer(port);
|
.startServer(port);
|
||||||
this.setStatus('Waiting for device');
|
this.setStatus(ConnectionStatus.Initializing, 'Waiting for device');
|
||||||
const device = this.device;
|
const device = this.device;
|
||||||
|
|
||||||
if (device) {
|
if (device) {
|
||||||
@@ -192,22 +229,26 @@ export default class ReactDevTools extends FlipperDevicePlugin<
|
|||||||
device.deviceType === 'physical' ||
|
device.deviceType === 'physical' ||
|
||||||
SUPPORTED_OCULUS_DEVICE_TYPES.includes(device.title.toLowerCase())
|
SUPPORTED_OCULUS_DEVICE_TYPES.includes(device.title.toLowerCase())
|
||||||
) {
|
) {
|
||||||
this.setStatus(`Setting up reverse port mapping: ${port}:${port}`);
|
this.setStatus(
|
||||||
|
ConnectionStatus.Initializing,
|
||||||
|
`Setting up reverse port mapping: ${port}:${port}`,
|
||||||
|
);
|
||||||
(device as AndroidDevice).reverse([port, port]);
|
(device as AndroidDevice).reverse([port, port]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
this.setStatus('Failed to initialize DevTools: ' + e);
|
this.setStatus(
|
||||||
|
ConnectionStatus.Error,
|
||||||
|
'Failed to initialize DevTools: ' + e,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<View grow>
|
<View grow>
|
||||||
{this.state.status !== CONNECTED ? (
|
{!this.devtoolsHaveStarted() ? this.renderStatus() : null}
|
||||||
<Toolbar>{this.state.status}</Toolbar>
|
|
||||||
) : null}
|
|
||||||
<Container ref={this.containerRef} />
|
<Container ref={this.containerRef} />
|
||||||
<GrabMetroDevice
|
<GrabMetroDevice
|
||||||
onHasDevice={device => {
|
onHasDevice={device => {
|
||||||
@@ -217,4 +258,26 @@ export default class ReactDevTools extends FlipperDevicePlugin<
|
|||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderStatus() {
|
||||||
|
return (
|
||||||
|
<CenteredView>
|
||||||
|
<RoundedSection title={this.connectionStatus}>
|
||||||
|
<Text>{this.state.status}</Text>
|
||||||
|
{(this.connectionStatus === ConnectionStatus.WaitingForReload &&
|
||||||
|
this.metroDevice?.ws) ||
|
||||||
|
this.connectionStatus === ConnectionStatus.Error ? (
|
||||||
|
<Button
|
||||||
|
style={{width: 200, margin: '10px auto 0 auto'}}
|
||||||
|
onClick={() => {
|
||||||
|
this.metroDevice?.sendCommand('reload');
|
||||||
|
this.bootDevTools();
|
||||||
|
}}>
|
||||||
|
Retry
|
||||||
|
</Button>
|
||||||
|
) : null}
|
||||||
|
</RoundedSection>
|
||||||
|
</CenteredView>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user