diff --git a/src/actions/RouterActions.js b/src/actions/RouterActions.js index a5ce0788..1c1c91eb 100644 --- a/src/actions/RouterActions.js +++ b/src/actions/RouterActions.js @@ -2,6 +2,7 @@ import { push, LOCATION_CHANGE } from 'react-router-redux'; import { routes } from 'support/routes'; +import * as deviceUtils from 'utils/device'; import type { RouterLocationState, @@ -59,6 +60,11 @@ export const paramsValidation = (params: RouterLocationState): PayloadAction => let url: ?string; if (!device.features) { 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`; - } else if (!device.features.initialized) { + } else if (device.mode === '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') { url = `/device/${device.features.device_id}:${device.instance}`; } else { diff --git a/src/actions/TrezorConnectActions.js b/src/actions/TrezorConnectActions.js index 7553edd4..b54a97a0 100644 --- a/src/actions/TrezorConnectActions.js +++ b/src/actions/TrezorConnectActions.js @@ -154,50 +154,50 @@ export const postInit = (): ThunkAction => (dispatch: Dispatch): void => { export const getSelectedDeviceState = (): AsyncAction => async (dispatch: Dispatch, getState: GetState): Promise => { const selected = getState().wallet.selectedDevice; - if (selected - && selected.connected - && (selected.features && !selected.features.bootloader_mode && selected.features.initialized) - && !selected.state) { - const response = await TrezorConnect.getDeviceState({ - device: { - path: selected.path, - instance: selected.instance, - state: selected.state, - }, - useEmptyPassphrase: !selected.instance, - }); + if (!selected) return; + const isDeviceReady = selected.connected && selected.features && !selected.state && selected.mode === 'normal' && selected.firmware !== 'required'; + if (!isDeviceReady) return; - if (response && response.success) { - dispatch({ - type: CONNECT.AUTH_DEVICE, - device: selected, - state: response.payload.state, - }); - } else { - dispatch({ - type: NOTIFICATION.ADD, - payload: { - devicePath: selected.path, - type: 'error', - title: 'Authentication error', - message: response.payload.error, - cancelable: false, - actions: [ - { - label: 'Try again', - callback: () => { - dispatch({ - type: NOTIFICATION.CLOSE, - payload: { devicePath: selected.path }, - }); - dispatch(getSelectedDeviceState()); - }, + const response = await TrezorConnect.getDeviceState({ + device: { + path: selected.path, + instance: selected.instance, + state: selected.state, + }, + useEmptyPassphrase: !selected.instance, + }); + + if (response && response.success) { + dispatch({ + type: CONNECT.AUTH_DEVICE, + device: selected, + state: response.payload.state, + }); + } else { + dispatch({ + type: NOTIFICATION.ADD, + payload: { + devicePath: selected.path, + type: 'error', + title: 'Authentication error', + message: response.payload.error, + 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 => { diff --git a/src/actions/WalletActions.js b/src/actions/WalletActions.js index 9d68a29d..a7325d25 100644 --- a/src/actions/WalletActions.js +++ b/src/actions/WalletActions.js @@ -5,6 +5,7 @@ import { DEVICE } from 'trezor-connect'; import * as CONNECT from 'actions/constants/TrezorConnect'; import * as WALLET from 'actions/constants/wallet'; import * as reducerUtils from 'reducers/utils'; +import * as deviceUtils from 'utils/device'; import type { Device, @@ -104,7 +105,7 @@ export const observe = (prevState: State, action: Action): PayloadAction { @@ -123,7 +123,7 @@ const DeviceHeader = ({ {getStatusName(status)} - {icon && !disabled && !isBootloader && icon} + {icon && !disabled && isAccessible && icon} @@ -131,7 +131,7 @@ const DeviceHeader = ({ }; DeviceHeader.propTypes = { - isBootloader: PropTypes.bool, + isAccessible: PropTypes.bool, device: PropTypes.object, icon: PropTypes.element, isHoverable: PropTypes.bool, diff --git a/src/views/Wallet/components/LeftNavigation/components/DeviceMenu/components/DeviceList/index.js b/src/views/Wallet/components/LeftNavigation/components/DeviceMenu/components/DeviceList/index.js index 723e177b..e6c8baa9 100644 --- a/src/views/Wallet/components/LeftNavigation/components/DeviceMenu/components/DeviceList/index.js +++ b/src/views/Wallet/components/LeftNavigation/components/DeviceMenu/components/DeviceList/index.js @@ -1,24 +1,33 @@ +/* @flow */ + import React, { Component } from 'react'; import styled from 'styled-components'; import Icon from 'components/Icon'; import DeviceHeader from 'components/DeviceHeader'; +import * as deviceUtils from 'utils/device'; import icons from 'config/icons'; 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 IconClick = styled.div``; -class DeviceList extends Component { - sortByInstance(a, b) { +type Props = { + devices: $ElementType; + selectedDevice: $ElementType<$ElementType, 'selectedDevice'>; + onSelectDevice: $ElementType; + forgetDevice: $ElementType; +}; + +class DeviceList extends Component { + sortByInstance(a: TrezorDevice, b: TrezorDevice) { if (!a.instance || !b.instance) return -1; return a.instance > b.instance ? 1 : -1; } - redirectToBootloader(selectedDevice) { - this.props.history.push(`/device/${selectedDevice.features.device_id}/bootloader`); - } - render() { const { devices, selectedDevice, onSelectDevice, forgetDevice, @@ -28,16 +37,11 @@ class DeviceList extends Component { {devices .sort(this.sortByInstance) .map(device => ( - device !== selectedDevice && ( + !deviceUtils.isSelectedDevice(selectedDevice, device) && ( { - if (device.features) { - if (device.features.bootloader_mode) { - this.redirectToBootloader(selectedDevice); - } - } onSelectDevice(device); }} onClickIcon={() => forgetDevice(device)} @@ -69,4 +73,4 @@ class DeviceList extends Component { } } -export default withRouter(DeviceList); \ No newline at end of file +export default DeviceList; \ No newline at end of file diff --git a/src/views/Wallet/components/LeftNavigation/components/DeviceMenu/components/MenuItems/index.js b/src/views/Wallet/components/LeftNavigation/components/DeviceMenu/components/MenuItems/index.js index 7ca2c305..a52bb769 100644 --- a/src/views/Wallet/components/LeftNavigation/components/DeviceMenu/components/MenuItems/index.js +++ b/src/views/Wallet/components/LeftNavigation/components/DeviceMenu/components/MenuItems/index.js @@ -42,8 +42,8 @@ class MenuItems extends Component { } showDeviceMenu() { - const device = this.props.device; - return device && device.features && !device.features.bootloader_mode && device.features.initialized; + const { device } = this.props; + return device && device.mode === 'normal'; } showClone() { diff --git a/src/views/Wallet/components/LeftNavigation/components/DeviceMenu/index.js b/src/views/Wallet/components/LeftNavigation/components/DeviceMenu/index.js index dc34884e..af12c149 100644 --- a/src/views/Wallet/components/LeftNavigation/components/DeviceMenu/index.js +++ b/src/views/Wallet/components/LeftNavigation/components/DeviceMenu/index.js @@ -4,7 +4,7 @@ import styled from 'styled-components'; import TrezorConnect from 'trezor-connect'; import type { TrezorDevice } from 'flowtype'; import Button from 'components/Button'; -import { isWebUSB } from 'utils/device'; +import * as deviceUtils from 'utils/device'; import MenuItems from './components/MenuItems'; import DeviceList from './components/DeviceList'; @@ -28,10 +28,6 @@ type DeviceMenuItem = { } class DeviceMenu extends Component { - mouseDownHandler: (event: MouseEvent) => void; - - blurHandler: (event: FocusEvent) => void; - constructor(props: Props) { super(props); this.mouseDownHandler = this.mouseDownHandler.bind(this); @@ -42,12 +38,33 @@ class DeviceMenu extends Component { window.addEventListener('mousedown', this.mouseDownHandler, false); // window.addEventListener('blur', this.blurHandler, false); 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() { 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 { @@ -67,34 +84,16 @@ class DeviceMenu extends Component { } } - blurHandler(): void { - this.props.toggleDeviceDropdown(false); - } + mouseDownHandler: (event: MouseEvent) => void; - 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); - } - } - - componentWillUnmount(): void { - window.removeEventListener('mousedown', this.mouseDownHandler, false); - } + blurHandler: (event: FocusEvent) => void; showDivider() { return this.props.devices.length > 1; } showMenuItems() { - const { selectedDevice } = this.props.wallet; - return selectedDevice && selectedDevice.features; + return deviceUtils.isDeviceAccessible(this.props.wallet.selectedDevice); } render() { @@ -113,7 +112,7 @@ class DeviceMenu extends Component { forgetDevice={forgetDevice} /> - {isWebUSB(transport) && ( + {deviceUtils.isWebUSB(transport) && ( Check for devices )} diff --git a/src/views/Wallet/components/LeftNavigation/index.js b/src/views/Wallet/components/LeftNavigation/index.js index 2f2c87f5..db79af1f 100644 --- a/src/views/Wallet/components/LeftNavigation/index.js +++ b/src/views/Wallet/components/LeftNavigation/index.js @@ -1,3 +1,5 @@ +/* @flow */ + import * as React from 'react'; import PropTypes from 'prop-types'; import colors from 'config/colors'; @@ -6,10 +8,13 @@ import icons from 'config/icons'; import { TransitionGroup, CSSTransition } from 'react-transition-group'; import styled from 'styled-components'; import DeviceHeader from 'components/DeviceHeader'; +import * as deviceUtils from 'utils/device'; + import AccountMenu from './components/AccountMenu'; import CoinMenu from './components/CoinMenu'; import DeviceMenu from './components/DeviceMenu'; import StickyContainer from './components/StickyContainer'; + import type { Props } from './components/common'; const Header = styled(DeviceHeader)` @@ -105,6 +110,7 @@ const TransitionMenu = (props: TransitionMenuProps): React$Element { @@ -119,11 +125,11 @@ class LeftNavigation extends React.PureComponent { }; } - componentWillReceiveProps(nextProps) { + componentWillReceiveProps(nextProps: Props) { const { dropdownOpened, selectedDevice } = nextProps.wallet; - const hasNetwork = nextProps.location.state && nextProps.location.state.network; - const hasFeatures = selectedDevice && selectedDevice.features; - const deviceReady = hasFeatures && !selectedDevice.features.bootloader_mode && selectedDevice.features.initialized; + const { location } = nextProps.router; + const hasNetwork = location && location.state.network; + const deviceReady = selectedDevice && selectedDevice.features && selectedDevice.mode === 'normal'; if (dropdownOpened) { this.setState({ shouldRenderDeviceSelection: true }); } else if (hasNetwork) { @@ -176,22 +182,23 @@ class LeftNavigation extends React.PureComponent { ); } - const isDeviceInBootloader = this.props.wallet.selectedDevice.features && this.props.wallet.selectedDevice.features.bootloader_mode; + const { selectedDevice } = props.wallet; + const isDeviceAccessible = deviceUtils.isDeviceAccessible(selectedDevice); return (
{ - if (!isDeviceInBootloader || this.props.devices.length > 1) { + if (isDeviceAccessible || this.props.devices.length > 1) { this.handleOpen(); } }} device={this.props.wallet.selectedDevice} - disabled={isDeviceInBootloader && this.props.devices.length === 1} + disabled={!isDeviceAccessible && this.props.devices.length === 1} isOpen={this.props.wallet.dropdownOpened} icon={( @@ -211,7 +218,7 @@ class LeftNavigation extends React.PureComponent { /> {this.state.shouldRenderDeviceSelection && } - {!isDeviceInBootloader && menu} + {isDeviceAccessible && menu}