Browse Source

Rename EnsureDeviceAppInteraction -> EnsureDeviceApp, and get rid of the old one

master
meriadec 7 years ago
parent
commit
554461ad2b
No known key found for this signature in database GPG Key ID: 1D2FC2305E2CB399
  1. 319
      src/components/EnsureDeviceApp.js
  2. 131
      src/components/EnsureDeviceAppInteraction.js
  3. 4
      src/components/modals/Receive/steps/02-step-connect-device.js
  4. 4
      src/components/modals/StepConnectDevice.js

319
src/components/EnsureDeviceApp.js

@ -1,238 +1,131 @@
// @flow
import { PureComponent } from 'react'
import { connect } from 'react-redux'
import logger from 'logger'
import invariant from 'invariant'
import { isSegwitAccount } from 'helpers/bip32'
import React, { Component } from 'react'
import invariant from 'invariant'
import { connect } from 'react-redux'
import { Trans } from 'react-i18next'
import type { Account, CryptoCurrency } from '@ledgerhq/live-common/lib/types'
import type { Device } from 'types/common'
import { getCryptoCurrencyIcon } from '@ledgerhq/live-common/lib/react'
import { getDevices } from 'reducers/devices'
import type { State as StoreState } from 'reducers/index'
import logger from 'logger'
import getAddress from 'commands/getAddress'
import { createCancelablePolling } from 'helpers/promise'
import { standardDerivation } from 'helpers/derivations'
import isDashboardOpen from 'commands/isDashboardOpen'
import { createCustomErrorClass } from 'helpers/errors'
import { CHECK_APP_INTERVAL_WHEN_VALID, CHECK_APP_INTERVAL_WHEN_INVALID } from 'config/constants'
export const WrongAppOpened = createCustomErrorClass('WrongAppOpened')
export const WrongDeviceForAccount = createCustomErrorClass('WrongDeviceForAccount')
import { isSegwitAccount } from 'helpers/bip32'
type OwnProps = {
currency?: ?CryptoCurrency,
deviceSelected: ?Device,
withGenuineCheck?: boolean,
account?: ?Account,
onStatusChange?: (DeviceStatus, AppStatus, ?string) => void,
onGenuineCheck?: (isGenuine: boolean) => void,
// TODO prefer children function
render?: ({
appStatus: AppStatus,
genuineCheckStatus: GenuineCheckStatus,
currency: ?CryptoCurrency,
devices: Device[],
deviceSelected: ?Device,
deviceStatus: DeviceStatus,
error: ?Error,
}) => React$Node,
}
import DeviceInteraction from 'components/DeviceInteraction'
import Text from 'components/base/Text'
type Props = OwnProps & {
devices: Device[],
}
import IconUsb from 'icons/Usb'
type DeviceStatus = 'unconnected' | 'connected'
import type { Device } from 'types/common'
type AppStatus = 'success' | 'fail' | 'progress'
import { createCustomErrorClass } from 'helpers/errors'
import { getCurrentDevice } from 'reducers/devices'
type GenuineCheckStatus = 'success' | 'fail' | 'progress'
export const WrongAppOpened = createCustomErrorClass('WrongAppOpened')
export const WrongDeviceForAccount = createCustomErrorClass('WrongDeviceForAccount')
type State = {
deviceStatus: DeviceStatus,
appStatus: AppStatus,
error: ?Error,
genuineCheckStatus: GenuineCheckStatus,
}
const usbIcon = <IconUsb size={36} />
const Bold = props => <Text ff="Open Sans|Bold" {...props} />
const mapStateToProps = (state: StoreState) => ({
devices: getDevices(state),
const mapStateToProps = state => ({
device: getCurrentDevice(state),
})
// TODO we want to split into <EnsureDeviceCurrency/> and <EnsureDeviceAccount/>
// and minimize the current codebase AF
class EnsureDeviceApp extends PureComponent<Props, State> {
state = {
appStatus: 'progress',
deviceStatus: this.props.deviceSelected ? 'connected' : 'unconnected',
error: null,
genuineCheckStatus: 'progress',
}
componentDidMount() {
if (this.props.deviceSelected !== null) {
this.checkAppOpened()
}
}
componentWillReceiveProps(nextProps) {
const { deviceStatus } = this.state
const { deviceSelected, devices } = this.props
const { devices: nextDevices, deviceSelected: nextDeviceSelected } = nextProps
if (deviceStatus === 'unconnected' && !deviceSelected && nextDeviceSelected) {
this.handleStatusChange('connected', 'progress')
}
if (deviceStatus !== 'unconnected' && devices !== nextDevices) {
const isConnected = nextDevices.find(d => d === nextDeviceSelected)
if (!isConnected) {
this.handleStatusChange('unconnected', 'progress')
}
}
}
componentDidUpdate(prevProps) {
const { deviceSelected } = this.props
const { deviceSelected: prevDeviceSelected } = prevProps
if (prevDeviceSelected !== deviceSelected) {
this.handleStatusChange('connected', 'progress')
// TODO: refacto to more generic/global way
clearTimeout(this._timeout)
this._timeout = setTimeout(this.checkAppOpened, 250)
}
}
componentWillUnmount() {
clearTimeout(this._timeout)
this._unmounted = true
}
checkAppOpened = async () => {
const { deviceSelected, account, currency, withGenuineCheck } = this.props
const { appStatus } = this.state
if (!deviceSelected) {
return
}
let isSuccess = true
try {
if (account || currency) {
const cur = account ? account.currency : currency
invariant(cur, 'currency is available')
const { address } = await getAddress
.send({
devicePath: deviceSelected.path,
currencyId: cur.id,
path: account
? account.freshAddressPath
: standardDerivation({ currency: cur, segwit: false, x: 0 }),
segwit: account ? isSegwitAccount(account) : false,
})
.toPromise()
.catch(e => {
if (
e &&
(e.name === 'TransportStatusError' ||
// we don't want these error to appear (caused by usb disconnect..)
e.message === 'could not read from HID device' ||
e.message === 'Cannot write to HID device')
) {
logger.log(e)
throw new WrongAppOpened(`WrongAppOpened ${cur.id}`, { currencyName: cur.name })
}
throw e
})
if (account) {
const { freshAddress } = account
if (account && freshAddress !== address) {
logger.warn({ freshAddress, address })
throw new WrongDeviceForAccount(`WrongDeviceForAccount ${account.name}`, {
accountName: account.name,
})
class EnsureDeviceApp extends Component<{
device: ?Device,
account?: ?Account,
currency?: ?CryptoCurrency,
}> {
connectInteractionHandler = () =>
createCancelablePolling(() => {
if (!this.props.device) return Promise.reject()
return Promise.resolve(this.props.device)
})
openAppInteractionHandler = ({ device }) =>
createCancelablePolling(async () => {
const { account, currency } = this.props
const cur = account ? account.currency : currency
invariant(cur, 'No currency given')
const { address } = await getAddress
.send({
devicePath: device.path,
currencyId: cur.id,
path: account
? account.freshAddressPath
: standardDerivation({ currency: cur, segwit: false, x: 0 }),
segwit: account ? isSegwitAccount(account) : false,
})
.toPromise()
.catch(e => {
if (
e &&
(e.name === 'TransportStatusError' ||
// we don't want these error to appear (caused by usb disconnect..)
e.message === 'could not read from HID device' ||
e.message === 'Cannot write to HID device')
) {
throw new WrongAppOpened(`WrongAppOpened ${cur.id}`, { currencyName: cur.name })
}
throw e
})
if (account) {
const { freshAddress } = account
if (account && freshAddress !== address) {
logger.warn({ freshAddress, address })
throw new WrongDeviceForAccount(`WrongDeviceForAccount ${account.name}`, {
accountName: account.name,
})
}
} else {
logger.warn('EnsureDeviceApp for using dashboard is DEPRECATED !!!')
// TODO: FIXME REMOVE THIS ! should use EnsureDashboard dedicated component.
const isDashboard = isDashboardOpen.send({ devicePath: deviceSelected.path }).toPromise()
if (!isDashboard) {
throw new Error(`dashboard is not opened`)
}
}
this.handleStatusChange(this.state.deviceStatus, 'success')
if (withGenuineCheck && appStatus !== 'success') {
this.handleGenuineCheck()
}
} catch (e) {
this.handleStatusChange(this.state.deviceStatus, 'fail', e)
isSuccess = false
}
// TODO: refacto to more generic/global way
if (!this._unmounted) {
this._timeout = setTimeout(
this.checkAppOpened,
isSuccess ? CHECK_APP_INTERVAL_WHEN_VALID : CHECK_APP_INTERVAL_WHEN_INVALID,
)
}
}
_timeout: *
_unmounted = false
handleStatusChange = (deviceStatus, appStatus, error = null) => {
const { onStatusChange } = this.props
clearTimeout(this._timeout)
if (!this._unmounted) {
this.setState({ deviceStatus, appStatus, error })
onStatusChange && onStatusChange(deviceStatus, appStatus, error)
}
}
handleGenuineCheck = async () => {
// TODO: do a *real* genuine check
await sleep(1)
if (!this._unmounted) {
this.setState({ genuineCheckStatus: 'success' })
this.props.onGenuineCheck && this.props.onGenuineCheck(true)
}
return address
})
renderOpenAppTitle = () => {
const { account, currency } = this.props
const cur = account ? account.currency : currency
invariant(cur, 'No currency given')
return (
<Trans i18nKey="deviceConnect:step2.open" parent="div">
{'Open the '}
<strong>{cur.name}</strong>
{' app on your device'}
</Trans>
)
}
render() {
const { currency, account, devices, deviceSelected, render } = this.props
const { appStatus, deviceStatus, genuineCheckStatus, error } = this.state
if (render) {
// if cur is not provided, we assume we want to check if user is on
// the dashboard
const cur = account ? account.currency : currency
return render({
appStatus,
currency: cur,
devices,
deviceSelected: deviceStatus === 'connected' ? deviceSelected : null,
deviceStatus,
genuineCheckStatus,
error,
})
}
return null
const { account, currency, ...props } = this.props
const cur = account ? account.currency : currency
const Icon = cur ? getCryptoCurrencyIcon(cur) : null
return (
<DeviceInteraction
steps={[
{
id: 'device',
title: (
<Trans i18nKey="app:deviceConnect.step1.connect" parent="div">
{'Connect and unlock your '}
<Bold>{'Ledger device'}</Bold>
</Trans>
),
icon: usbIcon,
run: this.connectInteractionHandler,
},
{
id: 'address',
title: this.renderOpenAppTitle,
icon: Icon ? <Icon size={24} /> : null,
run: this.openAppInteractionHandler,
},
]}
{...props}
/>
)
}
}
export default connect(mapStateToProps)(EnsureDeviceApp)
async function sleep(s) {
return new Promise(resolve => setTimeout(resolve, s * 1e3))
}

131
src/components/EnsureDeviceAppInteraction.js

@ -1,131 +0,0 @@
// @flow
import React, { Component } from 'react'
import invariant from 'invariant'
import { connect } from 'react-redux'
import { Trans } from 'react-i18next'
import type { Account, CryptoCurrency } from '@ledgerhq/live-common/lib/types'
import { getCryptoCurrencyIcon } from '@ledgerhq/live-common/lib/react'
import logger from 'logger'
import getAddress from 'commands/getAddress'
import { createCancelablePolling } from 'helpers/promise'
import { standardDerivation } from 'helpers/derivations'
import { isSegwitAccount } from 'helpers/bip32'
import DeviceInteraction from 'components/DeviceInteraction'
import Text from 'components/base/Text'
import IconUsb from 'icons/Usb'
import type { Device } from 'types/common'
import { createCustomErrorClass } from 'helpers/errors'
import { getCurrentDevice } from 'reducers/devices'
export const WrongAppOpened = createCustomErrorClass('WrongAppOpened')
export const WrongDeviceForAccount = createCustomErrorClass('WrongDeviceForAccount')
const usbIcon = <IconUsb size={36} />
const Bold = props => <Text ff="Open Sans|Bold" {...props} />
const mapStateToProps = state => ({
device: getCurrentDevice(state),
})
class EnsureDeviceAppInteraction extends Component<{
device: ?Device,
account?: ?Account,
currency?: ?CryptoCurrency,
}> {
connectInteractionHandler = () =>
createCancelablePolling(() => {
if (!this.props.device) return Promise.reject()
return Promise.resolve(this.props.device)
})
openAppInteractionHandler = ({ device }) =>
createCancelablePolling(async () => {
const { account, currency } = this.props
const cur = account ? account.currency : currency
invariant(cur, 'No currency given')
const { address } = await getAddress
.send({
devicePath: device.path,
currencyId: cur.id,
path: account
? account.freshAddressPath
: standardDerivation({ currency: cur, segwit: false, x: 0 }),
segwit: account ? isSegwitAccount(account) : false,
})
.toPromise()
.catch(e => {
if (
e &&
(e.name === 'TransportStatusError' ||
// we don't want these error to appear (caused by usb disconnect..)
e.message === 'could not read from HID device' ||
e.message === 'Cannot write to HID device')
) {
throw new WrongAppOpened(`WrongAppOpened ${cur.id}`, { currencyName: cur.name })
}
throw e
})
if (account) {
const { freshAddress } = account
if (account && freshAddress !== address) {
logger.warn({ freshAddress, address })
throw new WrongDeviceForAccount(`WrongDeviceForAccount ${account.name}`, {
accountName: account.name,
})
}
}
return address
})
renderOpenAppTitle = () => {
const { account, currency } = this.props
const cur = account ? account.currency : currency
invariant(cur, 'No currency given')
return (
<Trans i18nKey="deviceConnect:step2.open" parent="div">
{'Open the '}
<strong>{cur.name}</strong>
{' app on your device'}
</Trans>
)
}
render() {
const { account, currency, ...props } = this.props
const cur = account ? account.currency : currency
const Icon = cur ? getCryptoCurrencyIcon(cur) : null
return (
<DeviceInteraction
steps={[
{
id: 'device',
title: (
<Trans i18nKey="app:deviceConnect.step1.connect" parent="div">
{'Connect and unlock your '}
<Bold>{'Ledger device'}</Bold>
</Trans>
),
icon: usbIcon,
run: this.connectInteractionHandler,
},
{
id: 'address',
title: this.renderOpenAppTitle,
icon: Icon ? <Icon size={24} /> : null,
run: this.openAppInteractionHandler,
},
]}
{...props}
/>
)
}
}
export default connect(mapStateToProps)(EnsureDeviceAppInteraction)

4
src/components/modals/Receive/steps/02-step-connect-device.js

@ -4,13 +4,13 @@ import React from 'react'
import Box from 'components/base/Box'
import Button from 'components/base/Button'
import EnsureDeviceAppInteraction from 'components/EnsureDeviceAppInteraction'
import EnsureDeviceApp from 'components/EnsureDeviceApp'
import type { StepProps } from '../index'
export default function StepConnectDevice({ account, onChangeAppOpened }: StepProps) {
return (
<EnsureDeviceAppInteraction
<EnsureDeviceApp
account={account}
waitBeforeSuccess={200}
onSuccess={() => onChangeAppOpened(true)}

4
src/components/modals/StepConnectDevice.js

@ -5,7 +5,7 @@ import React from 'react'
import type { Account, CryptoCurrency } from '@ledgerhq/live-common/lib/types'
import type { Device } from 'types/common'
import EnsureDeviceAppInteraction from 'components/EnsureDeviceAppInteraction'
import EnsureDeviceApp from 'components/EnsureDeviceApp'
type Props = {
account?: ?Account,
@ -16,7 +16,7 @@ type Props = {
const StepConnectDevice = ({ account, currency, onChangeDevice, onStatusChange }: Props) =>
account || currency ? (
<EnsureDeviceAppInteraction
<EnsureDeviceApp
account={account}
currency={currency}
waitBeforeSuccess={500}

Loading…
Cancel
Save