1
0
mirror of https://github.com/trezor/trezor-wallet synced 2024-11-28 03:08:30 +00:00

refactor using device.mode and device.firmware

This commit is contained in:
Szymon Lesisz 2018-10-04 22:59:39 +02:00
parent 39e1714280
commit a4788d95fb
8 changed files with 127 additions and 104 deletions

View File

@ -2,6 +2,7 @@
import { push, LOCATION_CHANGE } from 'react-router-redux'; import { push, LOCATION_CHANGE } from 'react-router-redux';
import { routes } from 'support/routes'; import { routes } from 'support/routes';
import * as deviceUtils from 'utils/device';
import type { import type {
RouterLocationState, RouterLocationState,
@ -59,6 +60,11 @@ export const paramsValidation = (params: RouterLocationState): PayloadAction<boo
} }
if (!device) return false; if (!device) return false;
if (!deviceUtils.isDeviceAccessible(device)) {
// TODO: there should be no access to deep links if device has incorrect mode/firmware
// if (params.hasOwnProperty('network') || params.hasOwnProperty('account')) return false;
}
} }
// validate requested network // validate requested network
@ -177,10 +183,12 @@ const getDeviceUrl = (device: TrezorDevice | Device): PayloadAction<?string> =>
let url: ?string; let url: ?string;
if (!device.features) { if (!device.features) {
url = `/device/${device.path}/${device.type === 'unreadable' ? 'unreadable' : 'acquire'}`; url = `/device/${device.path}/${device.type === 'unreadable' ? 'unreadable' : 'acquire'}`;
} else if (device.features.bootloader_mode) { } else if (device.mode === 'bootloader') { // device in bootloader doesn't have device_id
url = `/device/${device.path}/bootloader`; url = `/device/${device.path}/bootloader`;
} else if (!device.features.initialized) { } else if (device.mode === 'initialize') {
url = `/device/${device.features.device_id}/initialize`; url = `/device/${device.features.device_id}/initialize`;
} else if (device.firmware === 'required') {
url = `/device/${device.features.device_id}/firmware-update`;
} else if (typeof device.instance === 'number') { } else if (typeof device.instance === 'number') {
url = `/device/${device.features.device_id}:${device.instance}`; url = `/device/${device.features.device_id}:${device.instance}`;
} else { } else {

View File

@ -154,50 +154,50 @@ export const postInit = (): ThunkAction => (dispatch: Dispatch): void => {
export const getSelectedDeviceState = (): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => { export const getSelectedDeviceState = (): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {
const selected = getState().wallet.selectedDevice; const selected = getState().wallet.selectedDevice;
if (selected if (!selected) return;
&& selected.connected const isDeviceReady = selected.connected && selected.features && !selected.state && selected.mode === 'normal' && selected.firmware !== 'required';
&& (selected.features && !selected.features.bootloader_mode && selected.features.initialized) if (!isDeviceReady) return;
&& !selected.state) {
const response = await TrezorConnect.getDeviceState({
device: {
path: selected.path,
instance: selected.instance,
state: selected.state,
},
useEmptyPassphrase: !selected.instance,
});
if (response && response.success) { const response = await TrezorConnect.getDeviceState({
dispatch({ device: {
type: CONNECT.AUTH_DEVICE, path: selected.path,
device: selected, instance: selected.instance,
state: response.payload.state, state: selected.state,
}); },
} else { useEmptyPassphrase: !selected.instance,
dispatch({ });
type: NOTIFICATION.ADD,
payload: { if (response && response.success) {
devicePath: selected.path, dispatch({
type: 'error', type: CONNECT.AUTH_DEVICE,
title: 'Authentication error', device: selected,
message: response.payload.error, state: response.payload.state,
cancelable: false, });
actions: [ } else {
{ dispatch({
label: 'Try again', type: NOTIFICATION.ADD,
callback: () => { payload: {
dispatch({ devicePath: selected.path,
type: NOTIFICATION.CLOSE, type: 'error',
payload: { devicePath: selected.path }, title: 'Authentication error',
}); message: response.payload.error,
dispatch(getSelectedDeviceState()); cancelable: false,
}, actions: [
{
label: 'Try again',
callback: () => {
dispatch({
type: NOTIFICATION.CLOSE,
payload: { devicePath: selected.path },
});
dispatch(getSelectedDeviceState());
}, },
], },
}, ],
}); },
} });
} }
}; };
export const deviceDisconnect = (device: Device): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => { export const deviceDisconnect = (device: Device): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise<void> => {

View File

@ -5,6 +5,7 @@ import { DEVICE } from 'trezor-connect';
import * as CONNECT from 'actions/constants/TrezorConnect'; import * as CONNECT from 'actions/constants/TrezorConnect';
import * as WALLET from 'actions/constants/wallet'; import * as WALLET from 'actions/constants/wallet';
import * as reducerUtils from 'reducers/utils'; import * as reducerUtils from 'reducers/utils';
import * as deviceUtils from 'utils/device';
import type { import type {
Device, Device,
@ -104,7 +105,7 @@ export const observe = (prevState: State, action: Action): PayloadAction<boolean
// handle devices state change (from trezor-connect events or location change) // handle devices state change (from trezor-connect events or location change)
if (locationChanged || selectedDeviceChanged) { if (locationChanged || selectedDeviceChanged) {
if (device && reducerUtils.isSelectedDevice(state.wallet.selectedDevice, device)) { if (device && deviceUtils.isSelectedDevice(state.wallet.selectedDevice, device)) {
dispatch({ dispatch({
type: WALLET.UPDATE_SELECTED_DEVICE, type: WALLET.UPDATE_SELECTED_DEVICE,
device, device,

View File

@ -98,7 +98,7 @@ const DeviceHeader = ({
device, device,
isHoverable = true, isHoverable = true,
onClickWrapper, onClickWrapper,
isBootloader = false, isAccessible = true,
disabled = false, disabled = false,
isSelected = false, isSelected = false,
}) => { }) => {
@ -123,7 +123,7 @@ const DeviceHeader = ({
<Status>{getStatusName(status)}</Status> <Status>{getStatusName(status)}</Status>
</LabelWrapper> </LabelWrapper>
<IconWrapper> <IconWrapper>
{icon && !disabled && !isBootloader && icon} {icon && !disabled && isAccessible && icon}
</IconWrapper> </IconWrapper>
</ClickWrapper> </ClickWrapper>
</Wrapper> </Wrapper>
@ -131,7 +131,7 @@ const DeviceHeader = ({
}; };
DeviceHeader.propTypes = { DeviceHeader.propTypes = {
isBootloader: PropTypes.bool, isAccessible: PropTypes.bool,
device: PropTypes.object, device: PropTypes.object,
icon: PropTypes.element, icon: PropTypes.element,
isHoverable: PropTypes.bool, isHoverable: PropTypes.bool,

View File

@ -1,24 +1,33 @@
/* @flow */
import React, { Component } from 'react'; import React, { Component } from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import Icon from 'components/Icon'; import Icon from 'components/Icon';
import DeviceHeader from 'components/DeviceHeader'; import DeviceHeader from 'components/DeviceHeader';
import * as deviceUtils from 'utils/device';
import icons from 'config/icons'; import icons from 'config/icons';
import colors from 'config/colors'; import colors from 'config/colors';
import { withRouter } from 'react-router-dom';
import type { TrezorDevice } from 'flowtype';
import type { Props as CommonProps } from '../../../common';
const Wrapper = styled.div``; const Wrapper = styled.div``;
const IconClick = styled.div``; const IconClick = styled.div``;
class DeviceList extends Component { type Props = {
sortByInstance(a, b) { devices: $ElementType<CommonProps, 'devices'>;
selectedDevice: $ElementType<$ElementType<CommonProps, 'wallet'>, 'selectedDevice'>;
onSelectDevice: $ElementType<CommonProps, 'onSelectDevice'>;
forgetDevice: $ElementType<CommonProps, 'forgetDevice'>;
};
class DeviceList extends Component<Props> {
sortByInstance(a: TrezorDevice, b: TrezorDevice) {
if (!a.instance || !b.instance) return -1; if (!a.instance || !b.instance) return -1;
return a.instance > b.instance ? 1 : -1; return a.instance > b.instance ? 1 : -1;
} }
redirectToBootloader(selectedDevice) {
this.props.history.push(`/device/${selectedDevice.features.device_id}/bootloader`);
}
render() { render() {
const { const {
devices, selectedDevice, onSelectDevice, forgetDevice, devices, selectedDevice, onSelectDevice, forgetDevice,
@ -28,16 +37,11 @@ class DeviceList extends Component {
{devices {devices
.sort(this.sortByInstance) .sort(this.sortByInstance)
.map(device => ( .map(device => (
device !== selectedDevice && ( !deviceUtils.isSelectedDevice(selectedDevice, device) && (
<DeviceHeader <DeviceHeader
key={device.state || device.path} key={device.state || device.path}
isBootloader={device.features && device.features.bootloader_mode} isAccessible={deviceUtils.isDeviceAccessible(device)}
onClickWrapper={() => { onClickWrapper={() => {
if (device.features) {
if (device.features.bootloader_mode) {
this.redirectToBootloader(selectedDevice);
}
}
onSelectDevice(device); onSelectDevice(device);
}} }}
onClickIcon={() => forgetDevice(device)} onClickIcon={() => forgetDevice(device)}
@ -69,4 +73,4 @@ class DeviceList extends Component {
} }
} }
export default withRouter(DeviceList); export default DeviceList;

View File

@ -42,8 +42,8 @@ class MenuItems extends Component {
} }
showDeviceMenu() { showDeviceMenu() {
const device = this.props.device; const { device } = this.props;
return device && device.features && !device.features.bootloader_mode && device.features.initialized; return device && device.mode === 'normal';
} }
showClone() { showClone() {

View File

@ -4,7 +4,7 @@ import styled from 'styled-components';
import TrezorConnect from 'trezor-connect'; import TrezorConnect from 'trezor-connect';
import type { TrezorDevice } from 'flowtype'; import type { TrezorDevice } from 'flowtype';
import Button from 'components/Button'; import Button from 'components/Button';
import { isWebUSB } from 'utils/device'; import * as deviceUtils from 'utils/device';
import MenuItems from './components/MenuItems'; import MenuItems from './components/MenuItems';
import DeviceList from './components/DeviceList'; import DeviceList from './components/DeviceList';
@ -28,10 +28,6 @@ type DeviceMenuItem = {
} }
class DeviceMenu extends Component<Props> { class DeviceMenu extends Component<Props> {
mouseDownHandler: (event: MouseEvent) => void;
blurHandler: (event: FocusEvent) => void;
constructor(props: Props) { constructor(props: Props) {
super(props); super(props);
this.mouseDownHandler = this.mouseDownHandler.bind(this); this.mouseDownHandler = this.mouseDownHandler.bind(this);
@ -42,12 +38,33 @@ class DeviceMenu extends Component<Props> {
window.addEventListener('mousedown', this.mouseDownHandler, false); window.addEventListener('mousedown', this.mouseDownHandler, false);
// window.addEventListener('blur', this.blurHandler, false); // window.addEventListener('blur', this.blurHandler, false);
const { transport } = this.props.connect; const { transport } = this.props.connect;
if (transport && transport.version.indexOf('webusb') >= 0) TrezorConnect.renderWebUSBButton(); if (transport.type && transport.version.indexOf('webusb') >= 0) TrezorConnect.renderWebUSBButton();
} }
componentDidUpdate() { componentDidUpdate() {
const { transport } = this.props.connect; const { transport } = this.props.connect;
if (isWebUSB(transport)) TrezorConnect.renderWebUSBButton(); if (deviceUtils.isWebUSB(transport)) TrezorConnect.renderWebUSBButton();
}
componentWillUnmount(): void {
window.removeEventListener('mousedown', this.mouseDownHandler, false);
}
onDeviceMenuClick(item: DeviceMenuItem, device: TrezorDevice): void {
if (item.type === 'reload') {
this.props.acquireDevice();
} else if (item.type === 'forget') {
this.props.forgetDevice(device);
} else if (item.type === 'clone') {
this.props.duplicateDevice(device);
} else if (item.type === 'settings') {
this.props.toggleDeviceDropdown(false);
this.props.gotoDeviceSettings(device);
}
}
blurHandler(): void {
this.props.toggleDeviceDropdown(false);
} }
mouseDownHandler(event: MouseEvent): void { mouseDownHandler(event: MouseEvent): void {
@ -67,34 +84,16 @@ class DeviceMenu extends Component<Props> {
} }
} }
blurHandler(): void { mouseDownHandler: (event: MouseEvent) => void;
this.props.toggleDeviceDropdown(false);
}
onDeviceMenuClick(item: DeviceMenuItem, device: TrezorDevice): void { blurHandler: (event: FocusEvent) => void;
if (item.type === 'reload') {
this.props.acquireDevice();
} else if (item.type === 'forget') {
this.props.forgetDevice(device);
} else if (item.type === 'clone') {
this.props.duplicateDevice(device);
} else if (item.type === 'settings') {
this.props.toggleDeviceDropdown(false);
this.props.gotoDeviceSettings(device);
}
}
componentWillUnmount(): void {
window.removeEventListener('mousedown', this.mouseDownHandler, false);
}
showDivider() { showDivider() {
return this.props.devices.length > 1; return this.props.devices.length > 1;
} }
showMenuItems() { showMenuItems() {
const { selectedDevice } = this.props.wallet; return deviceUtils.isDeviceAccessible(this.props.wallet.selectedDevice);
return selectedDevice && selectedDevice.features;
} }
render() { render() {
@ -113,7 +112,7 @@ class DeviceMenu extends Component<Props> {
forgetDevice={forgetDevice} forgetDevice={forgetDevice}
/> />
<ButtonWrapper> <ButtonWrapper>
{isWebUSB(transport) && ( {deviceUtils.isWebUSB(transport) && (
<StyledButton isWebUsb>Check for devices</StyledButton> <StyledButton isWebUsb>Check for devices</StyledButton>
)} )}
</ButtonWrapper> </ButtonWrapper>

View File

@ -1,3 +1,5 @@
/* @flow */
import * as React from 'react'; import * as React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import colors from 'config/colors'; import colors from 'config/colors';
@ -6,10 +8,13 @@ import icons from 'config/icons';
import { TransitionGroup, CSSTransition } from 'react-transition-group'; import { TransitionGroup, CSSTransition } from 'react-transition-group';
import styled from 'styled-components'; import styled from 'styled-components';
import DeviceHeader from 'components/DeviceHeader'; import DeviceHeader from 'components/DeviceHeader';
import * as deviceUtils from 'utils/device';
import AccountMenu from './components/AccountMenu'; import AccountMenu from './components/AccountMenu';
import CoinMenu from './components/CoinMenu'; import CoinMenu from './components/CoinMenu';
import DeviceMenu from './components/DeviceMenu'; import DeviceMenu from './components/DeviceMenu';
import StickyContainer from './components/StickyContainer'; import StickyContainer from './components/StickyContainer';
import type { Props } from './components/common'; import type { Props } from './components/common';
const Header = styled(DeviceHeader)` const Header = styled(DeviceHeader)`
@ -105,6 +110,7 @@ const TransitionMenu = (props: TransitionMenuProps): React$Element<TransitionGro
type State = { type State = {
animationType: ?string; animationType: ?string;
shouldRenderDeviceSelection: boolean; shouldRenderDeviceSelection: boolean;
clicked: boolean;
} }
class LeftNavigation extends React.PureComponent<Props, State> { class LeftNavigation extends React.PureComponent<Props, State> {
@ -119,11 +125,11 @@ class LeftNavigation extends React.PureComponent<Props, State> {
}; };
} }
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps: Props) {
const { dropdownOpened, selectedDevice } = nextProps.wallet; const { dropdownOpened, selectedDevice } = nextProps.wallet;
const hasNetwork = nextProps.location.state && nextProps.location.state.network; const { location } = nextProps.router;
const hasFeatures = selectedDevice && selectedDevice.features; const hasNetwork = location && location.state.network;
const deviceReady = hasFeatures && !selectedDevice.features.bootloader_mode && selectedDevice.features.initialized; const deviceReady = selectedDevice && selectedDevice.features && selectedDevice.mode === 'normal';
if (dropdownOpened) { if (dropdownOpened) {
this.setState({ shouldRenderDeviceSelection: true }); this.setState({ shouldRenderDeviceSelection: true });
} else if (hasNetwork) { } else if (hasNetwork) {
@ -176,22 +182,23 @@ class LeftNavigation extends React.PureComponent<Props, State> {
); );
} }
const isDeviceInBootloader = this.props.wallet.selectedDevice.features && this.props.wallet.selectedDevice.features.bootloader_mode; const { selectedDevice } = props.wallet;
const isDeviceAccessible = deviceUtils.isDeviceAccessible(selectedDevice);
return ( return (
<StickyContainer <StickyContainer
location={this.props.location.pathname} location={props.router.location.pathname}
deviceSelection={this.props.wallet.dropdownOpened} deviceSelection={this.props.wallet.dropdownOpened}
> >
<Header <Header
isSelected isSelected
isHoverable={false} isHoverable={false}
onClickWrapper={() => { onClickWrapper={() => {
if (!isDeviceInBootloader || this.props.devices.length > 1) { if (isDeviceAccessible || this.props.devices.length > 1) {
this.handleOpen(); this.handleOpen();
} }
}} }}
device={this.props.wallet.selectedDevice} device={this.props.wallet.selectedDevice}
disabled={isDeviceInBootloader && this.props.devices.length === 1} disabled={!isDeviceAccessible && this.props.devices.length === 1}
isOpen={this.props.wallet.dropdownOpened} isOpen={this.props.wallet.dropdownOpened}
icon={( icon={(
<React.Fragment> <React.Fragment>
@ -211,7 +218,7 @@ class LeftNavigation extends React.PureComponent<Props, State> {
/> />
<Body> <Body>
{this.state.shouldRenderDeviceSelection && <DeviceMenu {...this.props} />} {this.state.shouldRenderDeviceSelection && <DeviceMenu {...this.props} />}
{!isDeviceInBootloader && menu} {isDeviceAccessible && menu}
</Body> </Body>
<Footer key="sticky-footer"> <Footer key="sticky-footer">
<Help> <Help>
@ -241,8 +248,12 @@ LeftNavigation.propTypes = {
pending: PropTypes.array, pending: PropTypes.array,
toggleDeviceDropdown: PropTypes.func, toggleDeviceDropdown: PropTypes.func,
selectedDevice: PropTypes.object, addAccount: PropTypes.func,
acquireDevice: PropTypes.func,
forgetDevice: PropTypes.func,
duplicateDevice: PropTypes.func,
gotoDeviceSettings: PropTypes.func,
onSelectDevice: PropTypes.func,
}; };
export default LeftNavigation; export default LeftNavigation;