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:
Pascal Hartig
2020-03-30 09:22:17 -07:00
committed by Facebook GitHub Bot
parent f803cb3cb1
commit d86da8f1d2
7 changed files with 526 additions and 0 deletions

View 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}} />;
}
}

View 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>
);
}

View 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>
);
}

View 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>
);
}

View 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;
}
}
}

View 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"
}
}

View File

@@ -0,0 +1,4 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1