Move hermesdebugger to OSS
Summary: flybefree Only moved, formatted the JSON and changed the headers. changelog: New Hermes Debugger plugin for React Native apps. Reviewed By: rickhanlonii Differential Revision: D20673166 fbshipit-source-id: 9ecf4708f8834e00699d2d39aa6daa51c46a771b
This commit is contained in:
committed by
Facebook GitHub Bot
parent
f803cb3cb1
commit
d86da8f1d2
103
desktop/plugins/hermesdebuggerrn/ChromeDevTools.tsx
Normal file
103
desktop/plugins/hermesdebuggerrn/ChromeDevTools.tsx
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*
|
||||||
|
* @format
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import electron from 'electron';
|
||||||
|
|
||||||
|
const devToolsNodeId = (url: string) =>
|
||||||
|
`hermes-chromedevtools-out-of-react-node-${url.replace(
|
||||||
|
/[^a-zA-Z0-9]+/g,
|
||||||
|
'-',
|
||||||
|
)}`;
|
||||||
|
|
||||||
|
// TODO: build abstractionf or this: T62306732
|
||||||
|
const TARGET_CONTAINER_ID = 'flipper-out-of-contents-container'; // should be a hook in the future
|
||||||
|
|
||||||
|
function createDevToolsNode(url: string): HTMLElement {
|
||||||
|
const existing = findDevToolsNode(url);
|
||||||
|
if (existing) {
|
||||||
|
return existing;
|
||||||
|
}
|
||||||
|
|
||||||
|
// It is necessary to activate chrome devtools in electron
|
||||||
|
electron.remote.getCurrentWindow().webContents.toggleDevTools();
|
||||||
|
electron.remote.getCurrentWindow().webContents.closeDevTools();
|
||||||
|
|
||||||
|
const wrapper = document.createElement('div');
|
||||||
|
wrapper.id = devToolsNodeId(url);
|
||||||
|
wrapper.style.height = '100%';
|
||||||
|
wrapper.style.width = '100%';
|
||||||
|
|
||||||
|
const iframe = document.createElement('webview');
|
||||||
|
iframe.style.height = '100%';
|
||||||
|
iframe.style.width = '100%';
|
||||||
|
|
||||||
|
// // HACK: chrome-devtools:// is blocked by the sandbox but devtools:// isn't for some reason.
|
||||||
|
iframe.src = url.replace(/^chrome-/, '');
|
||||||
|
|
||||||
|
wrapper.appendChild(iframe);
|
||||||
|
document.getElementById(TARGET_CONTAINER_ID)!.appendChild(wrapper);
|
||||||
|
return wrapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
function findDevToolsNode(url: string): HTMLElement | null {
|
||||||
|
return document.querySelector('#' + devToolsNodeId(url));
|
||||||
|
}
|
||||||
|
|
||||||
|
function attachDevTools(devToolsNode: HTMLElement) {
|
||||||
|
devToolsNode.style.display = 'block';
|
||||||
|
document.getElementById(TARGET_CONTAINER_ID)!.style.display = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
function detachDevTools(devToolsNode: HTMLElement | null) {
|
||||||
|
document.getElementById(TARGET_CONTAINER_ID)!.style.display = 'none';
|
||||||
|
|
||||||
|
if (devToolsNode) {
|
||||||
|
devToolsNode.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChromeDevToolsProps = {
|
||||||
|
url: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class ChromeDevTools extends React.Component<
|
||||||
|
ChromeDevToolsProps
|
||||||
|
> {
|
||||||
|
createDevTools(url: string) {
|
||||||
|
const devToolsNode = createDevToolsNode(url);
|
||||||
|
attachDevTools(devToolsNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
hideDevTools(_url: string) {
|
||||||
|
detachDevTools(findDevToolsNode(this.props.url));
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.createDevTools(this.props.url);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.hideDevTools(this.props.url);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps: ChromeDevToolsProps) {
|
||||||
|
const oldUrl = prevProps.url;
|
||||||
|
const newUrl = this.props.url;
|
||||||
|
if (oldUrl != newUrl) {
|
||||||
|
this.hideDevTools(oldUrl);
|
||||||
|
this.createDevTools(newUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return <div style={{height: 0}} />;
|
||||||
|
}
|
||||||
|
}
|
||||||
108
desktop/plugins/hermesdebuggerrn/ErrorScreen.tsx
Normal file
108
desktop/plugins/hermesdebuggerrn/ErrorScreen.tsx
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*
|
||||||
|
* @format
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import {styled, FlexColumn, FlexRow, Text, Glyph, colors} from 'flipper';
|
||||||
|
|
||||||
|
const Container = styled(FlexColumn)({
|
||||||
|
height: '100%',
|
||||||
|
width: '100%',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
backgroundColor: colors.light02,
|
||||||
|
});
|
||||||
|
|
||||||
|
const Welcome = styled(FlexColumn)({
|
||||||
|
width: 460,
|
||||||
|
background: colors.white,
|
||||||
|
borderRadius: 10,
|
||||||
|
boxShadow: '0 1px 3px rgba(0,0,0,0.25)',
|
||||||
|
overflow: 'hidden',
|
||||||
|
transition: '0.6s all ease-out',
|
||||||
|
});
|
||||||
|
|
||||||
|
const Title = styled(Text)({
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: 300,
|
||||||
|
textAlign: 'center',
|
||||||
|
color: colors.light50,
|
||||||
|
marginTop: 16,
|
||||||
|
marginBottom: 16,
|
||||||
|
});
|
||||||
|
|
||||||
|
const Item = styled(FlexRow)({
|
||||||
|
padding: 10,
|
||||||
|
alignItems: 'center',
|
||||||
|
borderTop: `1px solid ${colors.light10}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
const ItemTitle = styled(Text)({
|
||||||
|
color: colors.light50,
|
||||||
|
fontSize: 14,
|
||||||
|
lineHeight: '20px',
|
||||||
|
});
|
||||||
|
|
||||||
|
const Bold = styled(Text)({
|
||||||
|
fontWeight: 600,
|
||||||
|
});
|
||||||
|
|
||||||
|
const Icon = styled(Glyph)({
|
||||||
|
marginRight: 11,
|
||||||
|
marginLeft: 6,
|
||||||
|
});
|
||||||
|
|
||||||
|
// As more known failures are found, add them to this list with better error information.
|
||||||
|
const KNOWN_FAILURE_MESSAGES: Record<
|
||||||
|
string,
|
||||||
|
Record<'message' | 'hint', string>
|
||||||
|
> = {
|
||||||
|
'Failed to fetch': {
|
||||||
|
// This is the error that is returned specifcally when Metro is turned off.
|
||||||
|
message: 'Metro disconnected.',
|
||||||
|
hint: 'Please check that metro is running and Flipper can connect to it.',
|
||||||
|
},
|
||||||
|
default: {
|
||||||
|
// All we really know in this case is that we can't connect to metro.
|
||||||
|
// Do not try and be more specific here.
|
||||||
|
message: 'Cannot connect to Metro.',
|
||||||
|
hint: 'Please check that metro is running and Flipper can connect to it.',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
function getReason(error: Error) {
|
||||||
|
let failure_message = KNOWN_FAILURE_MESSAGES.default;
|
||||||
|
if (error != null && KNOWN_FAILURE_MESSAGES[error.message]) {
|
||||||
|
failure_message = KNOWN_FAILURE_MESSAGES[error.message];
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ItemTitle>
|
||||||
|
<Bold>{failure_message.message} </Bold>
|
||||||
|
{failure_message.hint}
|
||||||
|
</ItemTitle>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
type Props = Readonly<{
|
||||||
|
error: Error;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export default function ErrorScreen(props: Props) {
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<Welcome>
|
||||||
|
<Title>Hermes Debugger Error</Title>
|
||||||
|
<Item>
|
||||||
|
<Icon size={20} name="caution-octagon" color={colors.red} />
|
||||||
|
<FlexColumn>{getReason(props.error)}</FlexColumn>
|
||||||
|
</Item>
|
||||||
|
</Welcome>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
79
desktop/plugins/hermesdebuggerrn/LaunchScreen.tsx
Normal file
79
desktop/plugins/hermesdebuggerrn/LaunchScreen.tsx
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*
|
||||||
|
* @format
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import {styled, FlexColumn, FlexRow, Text, Glyph, colors} from 'flipper';
|
||||||
|
|
||||||
|
const Container = styled(FlexColumn)({
|
||||||
|
height: '100%',
|
||||||
|
width: '100%',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
backgroundColor: colors.light02,
|
||||||
|
});
|
||||||
|
|
||||||
|
const Welcome = styled(FlexColumn)({
|
||||||
|
width: 460,
|
||||||
|
background: colors.white,
|
||||||
|
borderRadius: 10,
|
||||||
|
boxShadow: '0 1px 3px rgba(0,0,0,0.25)',
|
||||||
|
overflow: 'hidden',
|
||||||
|
transition: '0.6s all ease-out',
|
||||||
|
});
|
||||||
|
|
||||||
|
const Title = styled(Text)({
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: 300,
|
||||||
|
textAlign: 'center',
|
||||||
|
color: colors.light50,
|
||||||
|
marginTop: 16,
|
||||||
|
marginBottom: 16,
|
||||||
|
});
|
||||||
|
|
||||||
|
const Item = styled(FlexRow)({
|
||||||
|
padding: 10,
|
||||||
|
alignItems: 'center',
|
||||||
|
borderTop: `1px solid ${colors.light10}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
const ItemTitle = styled(Text)({
|
||||||
|
color: colors.light50,
|
||||||
|
fontSize: 14,
|
||||||
|
lineHeight: '20px',
|
||||||
|
});
|
||||||
|
|
||||||
|
const Bold = styled(Text)({
|
||||||
|
fontWeight: 600,
|
||||||
|
});
|
||||||
|
|
||||||
|
const Icon = styled(Glyph)({
|
||||||
|
marginRight: 11,
|
||||||
|
marginLeft: 6,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default function LaunchScreen() {
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<Welcome>
|
||||||
|
<Title>Hermes Debugger</Title>
|
||||||
|
<Item>
|
||||||
|
<Icon size={20} name="question-circle" color={colors.info} />
|
||||||
|
<FlexColumn>
|
||||||
|
<ItemTitle>
|
||||||
|
<Bold>Metro is connected but no Hermes apps were found.</Bold>{' '}
|
||||||
|
Open a React Native screen with Hermes enabled to connect. Note:
|
||||||
|
you may need to reload the app in order to reconnect the device to
|
||||||
|
Metro.
|
||||||
|
</ItemTitle>
|
||||||
|
</FlexColumn>
|
||||||
|
</Item>
|
||||||
|
</Welcome>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
85
desktop/plugins/hermesdebuggerrn/SelectScreen.tsx
Normal file
85
desktop/plugins/hermesdebuggerrn/SelectScreen.tsx
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*
|
||||||
|
* @format
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import {styled, FlexColumn, FlexRow, Text, Glyph, colors} from 'flipper';
|
||||||
|
import {Target, Targets} from './index';
|
||||||
|
|
||||||
|
const Container = styled(FlexColumn)({
|
||||||
|
height: '100%',
|
||||||
|
width: '100%',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
backgroundColor: colors.light02,
|
||||||
|
});
|
||||||
|
|
||||||
|
const Welcome = styled(FlexColumn)({
|
||||||
|
width: 460,
|
||||||
|
background: colors.white,
|
||||||
|
borderRadius: 10,
|
||||||
|
boxShadow: '0 1px 3px rgba(0,0,0,0.25)',
|
||||||
|
overflow: 'hidden',
|
||||||
|
transition: '0.6s all ease-out',
|
||||||
|
});
|
||||||
|
|
||||||
|
const Title = styled(Text)({
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: 300,
|
||||||
|
textAlign: 'center',
|
||||||
|
color: colors.light50,
|
||||||
|
marginTop: 16,
|
||||||
|
marginBottom: 16,
|
||||||
|
});
|
||||||
|
|
||||||
|
const Item = styled(FlexRow)({
|
||||||
|
padding: 10,
|
||||||
|
alignItems: 'center',
|
||||||
|
borderTop: `1px solid ${colors.light10}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
const ItemTitle = styled(Text)({
|
||||||
|
color: colors.light50,
|
||||||
|
fontSize: 14,
|
||||||
|
lineHeight: '20px',
|
||||||
|
});
|
||||||
|
|
||||||
|
const Icon = styled(Glyph)({
|
||||||
|
marginRight: 11,
|
||||||
|
marginLeft: 6,
|
||||||
|
});
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
readonly targets: Targets;
|
||||||
|
readonly onSelect: (target: Target) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function SelectScreen(props: Props) {
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<Welcome>
|
||||||
|
<Title>Hermes Debugger Select</Title>
|
||||||
|
<Item>
|
||||||
|
<FlexColumn>
|
||||||
|
<ItemTitle>Please select a target:</ItemTitle>
|
||||||
|
</FlexColumn>
|
||||||
|
</Item>
|
||||||
|
{props.targets.map((target) => {
|
||||||
|
return (
|
||||||
|
<Item onClick={() => props.onSelect(target)}>
|
||||||
|
<Icon size={20} name="code" color={colors.info} />
|
||||||
|
<FlexColumn>
|
||||||
|
<ItemTitle>{target.title}</ItemTitle>
|
||||||
|
</FlexColumn>
|
||||||
|
</Item>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Welcome>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
135
desktop/plugins/hermesdebuggerrn/index.tsx
Normal file
135
desktop/plugins/hermesdebuggerrn/index.tsx
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*
|
||||||
|
* @format
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import {FlipperDevicePlugin, Device} from 'flipper';
|
||||||
|
import LaunchScreen from './LaunchScreen';
|
||||||
|
import SelectScreen from './SelectScreen';
|
||||||
|
import ErrorScreen from './ErrorScreen';
|
||||||
|
import ChromeDevTools from './ChromeDevTools';
|
||||||
|
|
||||||
|
const POLL_SECS = 5 * 1000;
|
||||||
|
const METRO_HOST = 'http://localhost:8081';
|
||||||
|
|
||||||
|
export type Target = Readonly<{
|
||||||
|
id: string;
|
||||||
|
description: string;
|
||||||
|
title: string;
|
||||||
|
faviconUrl: string;
|
||||||
|
devtoolsFrontendUrl: string;
|
||||||
|
type: string;
|
||||||
|
webSocketDebuggerUrl: string;
|
||||||
|
vm: string;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export type Targets = ReadonlyArray<Target>;
|
||||||
|
|
||||||
|
type State = Readonly<{
|
||||||
|
targets?: Targets | null;
|
||||||
|
selectedTarget?: Target | null;
|
||||||
|
error?: Error | null;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export default class extends FlipperDevicePlugin<State, any, any> {
|
||||||
|
static title = 'Hermes Debugger';
|
||||||
|
static id = 'Hermesdebuggerrn';
|
||||||
|
static icon = 'code';
|
||||||
|
|
||||||
|
static supportsDevice(device: Device) {
|
||||||
|
return !device.isArchived && device.os === 'Metro';
|
||||||
|
}
|
||||||
|
|
||||||
|
state: State = {
|
||||||
|
targets: null,
|
||||||
|
selectedTarget: null,
|
||||||
|
error: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
poll?: NodeJS.Timeout;
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
// This is a pretty basic polling mechnaism. We ask Metro every POLL_SECS what the
|
||||||
|
// current available targets are and only handle a few basic state transitions.
|
||||||
|
this.poll = setInterval(this.checkDebugTargets, POLL_SECS);
|
||||||
|
this.checkDebugTargets();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
if (this.poll) {
|
||||||
|
clearInterval(this.poll);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkDebugTargets = () => {
|
||||||
|
fetch(`${METRO_HOST}/json`)
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then(
|
||||||
|
(result) => {
|
||||||
|
// We only want to use the Chrome Reload targets.
|
||||||
|
const targets = result.filter(
|
||||||
|
(target: any) =>
|
||||||
|
target.title ===
|
||||||
|
'React Native Experimental (Improved Chrome Reloads)',
|
||||||
|
);
|
||||||
|
|
||||||
|
// Find the currently selected target.
|
||||||
|
// If the current selectedTarget isn't returned, clear it.
|
||||||
|
let currentlySelected = null;
|
||||||
|
if (this.state.selectedTarget != null) {
|
||||||
|
for (const target of result) {
|
||||||
|
if (
|
||||||
|
this.state.selectedTarget?.webSocketDebuggerUrl ===
|
||||||
|
target.webSocketDebuggerUrl
|
||||||
|
) {
|
||||||
|
currentlySelected = this.state.selectedTarget;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto-select the first target if there is one,
|
||||||
|
// but don't change the one that's already selected.
|
||||||
|
const selectedTarget =
|
||||||
|
currentlySelected == null && targets.length === 1
|
||||||
|
? targets[0]
|
||||||
|
: currentlySelected;
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
error: null,
|
||||||
|
targets,
|
||||||
|
selectedTarget,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
this.setState({
|
||||||
|
targets: null,
|
||||||
|
selectedTarget: null,
|
||||||
|
error,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
handleSelect = (selectedTarget: Target) => this.setState({selectedTarget});
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {error, selectedTarget, targets} = this.state;
|
||||||
|
|
||||||
|
if (selectedTarget) {
|
||||||
|
return <ChromeDevTools url={selectedTarget.devtoolsFrontendUrl} />;
|
||||||
|
} else if (targets != null && targets.length === 0) {
|
||||||
|
return <LaunchScreen />;
|
||||||
|
} else if (targets != null && targets.length > 0) {
|
||||||
|
return <SelectScreen targets={targets} onSelect={this.handleSelect} />;
|
||||||
|
} else if (error != null) {
|
||||||
|
return <ErrorScreen error={error} />;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
12
desktop/plugins/hermesdebuggerrn/package.json
Normal file
12
desktop/plugins/hermesdebuggerrn/package.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"name": "flipper-plugin-hermesdebuggerrn",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"main": "index.tsx",
|
||||||
|
"license": "MIT",
|
||||||
|
"title": "Hermes Debugger (RN)",
|
||||||
|
"icon": "apps",
|
||||||
|
"keywords": ["flipper-plugin"],
|
||||||
|
"bugs": {
|
||||||
|
"email": "rickhanlonii@fb.com"
|
||||||
|
}
|
||||||
|
}
|
||||||
4
desktop/plugins/hermesdebuggerrn/yarn.lock
Normal file
4
desktop/plugins/hermesdebuggerrn/yarn.lock
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||||
|
# yarn lockfile v1
|
||||||
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user