From 56297d0cfc3d5ee645efcd9838981f0d18d2a742 Mon Sep 17 00:00:00 2001 From: Michel Weststrate Date: Mon, 17 Feb 2020 03:36:59 -0800 Subject: [PATCH] Automatically cause reload to trigger devTools Summary: To connect to the React Devtools, the app has to be reload (or the dev menu opened). This diff does force a reload if the React Devtools don't connect timely Reviewed By: passy Differential Revision: D19878172 fbshipit-source-id: 0b3e22f70c9d24dae1fdbf0cc351d23367654ae1 --- src/chrome/MetroButton.tsx | 10 +-- src/devices/MetroDevice.tsx | 17 ++++- src/plugins/reactdevtools/index.tsx | 107 ++++++++++++++++++++-------- 3 files changed, 95 insertions(+), 39 deletions(-) diff --git a/src/chrome/MetroButton.tsx b/src/chrome/MetroButton.tsx index fdb2bc3f6..0a3cb4afe 100644 --- a/src/chrome/MetroButton.tsx +++ b/src/chrome/MetroButton.tsx @@ -65,15 +65,7 @@ const ProgressBarBar = styled.div<{progress: number; color: string}>( function MetroButton({device}: Props) { const sendCommand = useCallback( (command: string) => { - if (device.ws) { - device.ws.send( - JSON.stringify({ - version: 2, - type: 'command', - command, - }), - ); - } + device.sendCommand(command); }, [device], ); diff --git a/src/devices/MetroDevice.tsx b/src/devices/MetroDevice.tsx index cc72e8889..a6e7cfb43 100644 --- a/src/devices/MetroDevice.tsx +++ b/src/devices/MetroDevice.tsx @@ -150,7 +150,7 @@ export default class MetroDevice extends BaseDevice { ws.onmessage = this._handleWSMessage; } - _handleWSMessage = ({data}: any) => { + private _handleWSMessage = ({data}: any) => { const message: MetroReportableEvent = JSON.parse(data); if (message.type === 'client_log') { const type: LogLevel = metroLogLevelMapping[message.level] || 'unknown'; @@ -182,6 +182,21 @@ export default class MetroDevice extends BaseDevice { this.metroEventEmitter.emit('event', message); }; + sendCommand(command: string, params?: any) { + if (this.ws) { + this.ws.send( + JSON.stringify({ + version: 2, + type: 'command', + command, + params, + }), + ); + } else { + console.warn('Cannot send command, no connection', command); + } + } + archive() { return new ArchivedDevice( this.serial + v4(), diff --git a/src/plugins/reactdevtools/index.tsx b/src/plugins/reactdevtools/index.tsx index cebd089b9..8975b8475 100644 --- a/src/plugins/reactdevtools/index.tsx +++ b/src/plugins/reactdevtools/index.tsx @@ -7,9 +7,19 @@ * @format */ +// import {ReactReduxContext} from 'react-redux'; import ReactDevToolsStandalone from 'react-devtools-core/standalone'; -import {FlipperPlugin, AndroidDevice, styled, View, Toolbar} from 'flipper'; -import React from 'react'; +import { + FlipperPlugin, + AndroidDevice, + styled, + View, + Toolbar, + MetroDevice, + ReduxState, + connect, +} from 'flipper'; +import React, {useEffect} from 'react'; import getPort from 'get-port'; import address from 'address'; @@ -54,7 +64,30 @@ function detachDevTools(devToolsNode: HTMLElement) { const CONNECTED = 'DevTools connected'; -export default class extends FlipperPlugin< +type GrabMetroDeviceStoreProps = {metroDevice: MetroDevice}; +type GrabMetroDeviceOwnProps = {onHasDevice(device: MetroDevice): void}; + +// Utility component to grab the metroDevice from the store if there is one +const GrabMetroDevice = connect< + GrabMetroDeviceStoreProps, + {}, + GrabMetroDeviceOwnProps, + ReduxState +>(({connections: {devices}}) => ({ + metroDevice: devices.find( + device => device.os === 'Metro' && !device.isArchived, + ) as MetroDevice, +}))(function({ + metroDevice, + onHasDevice, +}: GrabMetroDeviceStoreProps & GrabMetroDeviceOwnProps) { + useEffect(() => { + onHasDevice(metroDevice); + }, [metroDevice]); + return null; +}); + +export default class ReactDevTools extends FlipperPlugin< { status: string; }, @@ -64,6 +97,7 @@ export default class extends FlipperPlugin< pollHandle?: NodeJS.Timeout; containerRef: React.RefObject = React.createRef(); triedToAutoConnect = false; + metroDevice?: MetroDevice; state = { status: 'initializing', @@ -98,7 +132,7 @@ export default class extends FlipperPlugin< } setStatus(status: string) { - console.log(`[ReactDevtoolsPlugin] ${status}`); + // console.log(`[ReactDevtoolsPlugin] ${status}`); if (status.startsWith('The server is listening on')) { this.setState({status: status + ' Waiting for connection...'}); } else { @@ -114,9 +148,14 @@ export default class extends FlipperPlugin< if (!this.triedToAutoConnect) { this.triedToAutoConnect = true; this.setStatus( - "The DevTools didn't connect yet. Please open the DevMenu or Reload to connect", + "The DevTools didn't connect yet. Please open the DevMenu in the React Native app, or Reload it to connect", ); - // TODO: send reload command + if (this.metroDevice) { + this.setStatus( + "Sending 'reload' to the Metro to force the DevTools to connect...", + ); + this.metroDevice?.sendCommand('reload'); + } } this.startPollForConnection(); } @@ -124,32 +163,37 @@ export default class extends FlipperPlugin< } async initializeDevTools(devToolsNode: HTMLElement) { - this.setStatus('Waiting for port 8097'); - const port = await getPort({port: 8097}); // default port for dev tools - 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(); + try { + this.setStatus('Waiting for port 8097'); + const port = await getPort({port: 8097}); // default port for dev tools + 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(); - if (device) { - const host = - device.deviceType === 'physical' - ? address.ip() - : device instanceof AndroidDevice - ? '10.0.2.2' // Host IP for Android emulator host system - : 'localhost'; - this.setStatus(`Updating config to ${host}:${port}`); - this.client.call('config', {port, host}); + if (device) { + const host = + device.deviceType === 'physical' + ? address.ip() + : device instanceof AndroidDevice + ? '10.0.2.2' // Host IP for Android emulator host system + : 'localhost'; + this.setStatus(`Updating config to ${host}:${port}`); + this.client.call('config', {port, host}); - if (['quest', 'go', 'pacific'].includes(device.title.toLowerCase())) { - const device = await this.getDevice(); - this.setStatus(`Setting up reverse port mapping: ${port}:${port}`); - (device as AndroidDevice).reverse([port, port]); + if (['quest', 'go', 'pacific'].includes(device.title.toLowerCase())) { + const device = await this.getDevice(); + this.setStatus(`Setting up reverse port mapping: ${port}:${port}`); + (device as AndroidDevice).reverse([port, port]); + } } + } catch (e) { + console.error(e); + this.setStatus('Failed to initialize DevTools: ' + e); } } @@ -160,6 +204,11 @@ export default class extends FlipperPlugin< {this.state.status} ) : null} + { + this.metroDevice = device; + }} + /> ); }