Browse Source

Errors for i18n

should protect the i18n with a ''
master
Gaëtan Renaudeau 7 years ago
parent
commit
055432e1d7
  1. 7
      src/api/Ethereum.js
  2. 5
      src/api/Fees.js
  3. 14
      src/api/Ledger.js
  4. 3
      src/bridge/BridgeSyncContext.js
  5. 15
      src/bridge/RippleJSBridge.js
  6. 5
      src/commands/index.js
  7. 7
      src/components/DeviceConnect/index.js
  8. 29
      src/components/EnsureDeviceApp/index.js
  9. 4
      src/components/GenuineCheckModal/index.js
  10. 5
      src/components/RequestAmount/index.js
  11. 5
      src/components/ThrowBlock.js
  12. 25
      src/components/TranslatedError.js
  13. 9
      src/components/base/FormattedVal/index.js
  14. 7
      src/components/base/Input/index.js
  15. 5
      src/components/modals/AddAccounts/index.js
  16. 6
      src/components/modals/AddAccounts/steps/02-step-connect-device.js
  17. 10
      src/components/modals/AddAccounts/steps/03-step-import.js
  18. 5
      src/components/modals/Receive/index.js
  19. 11
      src/components/modals/Send/04-step-confirmation.js
  20. 4
      src/components/modals/StepConnectDevice.js
  21. 13
      src/helpers/createCustomErrorClass.js
  22. 7
      src/helpers/getAddressForCurrency/btc.js
  23. 5
      src/helpers/libcore.js
  24. 12
      src/helpers/signTransactionForCurrency/ethereum.js
  25. 13
      static/i18n/en/errors.yml

7
src/api/Ethereum.js

@ -1,8 +1,11 @@
// @flow // @flow
import type { CryptoCurrency } from '@ledgerhq/live-common/lib/types' import type { CryptoCurrency } from '@ledgerhq/live-common/lib/types'
import createCustomErrorClass from 'helpers/createCustomErrorClass'
import network from './network' import network from './network'
import { blockchainBaseURL } from './Ledger' import { blockchainBaseURL } from './Ledger'
export const LedgerAPINotAvailable = createCustomErrorClass('LedgerAPINotAvailable')
export type Block = { height: number } // TODO more fields actually export type Block = { height: number } // TODO more fields actually
export type Tx = { export type Tx = {
hash: string, hash: string,
@ -42,7 +45,9 @@ export type API = {
export const apiForCurrency = (currency: CryptoCurrency): API => { export const apiForCurrency = (currency: CryptoCurrency): API => {
const baseURL = blockchainBaseURL(currency) const baseURL = blockchainBaseURL(currency)
if (!baseURL) { if (!baseURL) {
throw new Error(`ledger API not available for currency ${currency.id}`) throw new LedgerAPINotAvailable(`LedgerAPINotAvailable ${currency.id}`, {
currencyName: currency.name,
})
} }
return { return {
async getTransactions(address, blockHash) { async getTransactions(address, blockHash) {

5
src/api/Fees.js

@ -1,9 +1,12 @@
// @flow // @flow
import invariant from 'invariant' import invariant from 'invariant'
import type { Currency } from '@ledgerhq/live-common/lib/types' import type { Currency } from '@ledgerhq/live-common/lib/types'
import createCustomErrorClass from 'helpers/createCustomErrorClass'
import { blockchainBaseURL } from './Ledger' import { blockchainBaseURL } from './Ledger'
import network from './network' import network from './network'
const FeeEstimationFailed = createCustomErrorClass('FeeEstimationFailed')
export type Fees = { export type Fees = {
[_: string]: number, [_: string]: number,
} }
@ -15,5 +18,5 @@ export const getEstimatedFees = async (currency: Currency): Promise<Fees> => {
if (data) { if (data) {
return data return data
} }
throw new Error(`fee estimation failed. status=${status}`) throw new FeeEstimationFailed(`FeeEstimationFailed ${status}`, { httpStatus: status })
} }

14
src/api/Ledger.js

@ -1,9 +1,14 @@
// @flow // @flow
import type { Currency } from '@ledgerhq/live-common/lib/types' import type { Currency } from '@ledgerhq/live-common/lib/types'
import logger from 'logger' import logger from 'logger'
import createCustomErrorClass from 'helpers/createCustomErrorClass'
const BASE_URL = process.env.LEDGER_REST_API_BASE || 'https://api.ledgerwallet.com/' const BASE_URL = process.env.LEDGER_REST_API_BASE || 'https://api.ledgerwallet.com/'
export const LedgerAPIErrorWithMessage = createCustomErrorClass('LedgerAPIErrorWithMessage')
export const LedgerAPIError = createCustomErrorClass('LedgerAPIError')
export const NetworkDown = createCustomErrorClass('NetworkDown')
export const userFriendlyError = <A>(p: Promise<A>): Promise<A> => export const userFriendlyError = <A>(p: Promise<A>): Promise<A> =>
p.catch(error => { p.catch(error => {
if (error.response) { if (error.response) {
@ -24,16 +29,17 @@ export const userFriendlyError = <A>(p: Promise<A>): Promise<A> =>
logger.warn("can't parse server result", e) logger.warn("can't parse server result", e)
} }
} }
throw new Error(msg) throw new LedgerAPIErrorWithMessage(msg)
} }
} }
logger.log('Ledger API: HTTP status', error.response.status, 'data: ', error.response.data) const { status } = error.response
throw new Error('A problem occurred with Ledger API. Please try again later.') logger.log('Ledger API: HTTP status', status, 'data: ', error.response.data)
throw new LedgerAPIError(`LedgerAPIError ${status}`, { status })
} else if (error.request) { } else if (error.request) {
// The request was made but no response was received // The request was made but no response was received
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of // `error.request` is an instance of XMLHttpRequest in the browser and an instance of
// http.ClientRequest in node.js // http.ClientRequest in node.js
throw new Error('Your network is down. Please try again later.') throw new NetworkDown()
} }
throw error throw error
}) })

3
src/bridge/BridgeSyncContext.js

@ -3,6 +3,7 @@
// it handles automatically re-calling synchronize // it handles automatically re-calling synchronize
// this is an even high abstraction than the bridge // this is an even high abstraction than the bridge
import invariant from 'invariant'
import logger from 'logger' import logger from 'logger'
import shuffle from 'lodash/shuffle' import shuffle from 'lodash/shuffle'
import React, { Component } from 'react' import React, { Component } from 'react'
@ -65,7 +66,7 @@ class Provider extends Component<BridgeSyncProviderOwnProps, Sync> {
return return
} }
const account = this.props.accounts.find(a => a.id === accountId) const account = this.props.accounts.find(a => a.id === accountId)
if (!account) throw new Error('account not found') invariant(account, 'account not found')
const bridge = getBridgeForCurrency(account.currency) const bridge = getBridgeForCurrency(account.currency)

15
src/bridge/RippleJSBridge.js

@ -1,4 +1,5 @@
// @flow // @flow
import invariant from 'invariant'
import { Observable } from 'rxjs' import { Observable } from 'rxjs'
import React from 'react' import React from 'react'
import bs58check from 'ripple-bs58check' import bs58check from 'ripple-bs58check'
@ -298,9 +299,10 @@ const RippleJSBridge: WalletBridge<Transaction> = {
if (finished) return if (finished) return
const balance = parseAPIValue(info.xrpBalance) const balance = parseAPIValue(info.xrpBalance)
if (isNaN(balance) || !isFinite(balance)) { invariant(
throw new Error(`Ripple: invalid balance=${balance} for address ${address}`) !isNaN(balance) && isFinite(balance),
} `Ripple: invalid balance=${balance} for address ${address}`,
)
const transactions = await api.getTransactions(address, { const transactions = await api.getTransactions(address, {
minLedgerVersion, minLedgerVersion,
@ -375,9 +377,10 @@ const RippleJSBridge: WalletBridge<Transaction> = {
} }
const balance = parseAPIValue(info.xrpBalance) const balance = parseAPIValue(info.xrpBalance)
if (isNaN(balance) || !isFinite(balance)) { invariant(
throw new Error(`Ripple: invalid balance=${balance} for address ${freshAddress}`) !isNaN(balance) && isFinite(balance),
} `Ripple: invalid balance=${balance} for address ${freshAddress}`,
)
o.next(a => ({ ...a, balance })) o.next(a => ({ ...a, balance }))

5
src/commands/index.js

@ -1,5 +1,6 @@
// @flow // @flow
import invariant from 'invariant'
import type { Command } from 'helpers/ipc' import type { Command } from 'helpers/ipc'
import getAddress from 'commands/getAddress' import getAddress from 'commands/getAddress'
@ -53,9 +54,7 @@ const all: Array<Command<any, any>> = [
] ]
all.forEach(cmd => { all.forEach(cmd => {
if (all.some(c => c !== cmd && c.id === cmd.id)) { invariant(!all.some(c => c !== cmd && c.id === cmd.id), `duplicate command '${cmd.id}'`)
throw new Error(`duplicate command '${cmd.id}'`)
}
}) })
export default all export default all

7
src/components/DeviceConnect/index.js

@ -12,6 +12,7 @@ import noop from 'lodash/noop'
import Box from 'components/base/Box' import Box from 'components/base/Box'
import Spinner from 'components/base/Spinner' import Spinner from 'components/base/Spinner'
import CryptoCurrencyIcon from 'components/CryptoCurrencyIcon' import CryptoCurrencyIcon from 'components/CryptoCurrencyIcon'
import TranslatedError from 'components/TranslatedError'
import IconCheck from 'icons/Check' import IconCheck from 'icons/Check'
import IconExclamationCircle from 'icons/ExclamationCircle' import IconExclamationCircle from 'icons/ExclamationCircle'
@ -146,7 +147,7 @@ type Props = {
deviceSelected: ?Device, deviceSelected: ?Device,
onChangeDevice: Device => void, onChangeDevice: Device => void,
t: T, t: T,
errorMessage: ?string, error: ?Error,
} }
const emitChangeDevice = props => { const emitChangeDevice = props => {
@ -186,7 +187,7 @@ class DeviceConnect extends PureComponent<Props> {
genuineCheckStatus, genuineCheckStatus,
withGenuineCheck, withGenuineCheck,
appOpened, appOpened,
errorMessage, error,
currency, currency,
t, t,
onChangeDevice, onChangeDevice,
@ -306,7 +307,7 @@ class DeviceConnect extends PureComponent<Props> {
<Info hasErrors> <Info hasErrors>
<IconInfoCircle size={12} /> <IconInfoCircle size={12} />
<Box shrink selectable> <Box shrink selectable>
{String(errorMessage || '')} <TranslatedError error={error} />
</Box> </Box>
</Info> </Info>
) : null} ) : null}

29
src/components/EnsureDeviceApp/index.js

@ -13,9 +13,13 @@ import type { State as StoreState } from 'reducers/index'
import getAddress from 'commands/getAddress' import getAddress from 'commands/getAddress'
import { standardDerivation } from 'helpers/derivations' import { standardDerivation } from 'helpers/derivations'
import isDashboardOpen from 'commands/isDashboardOpen' import isDashboardOpen from 'commands/isDashboardOpen'
import createCustomErrorClass from 'helpers/createCustomErrorClass'
import { CHECK_APP_INTERVAL_WHEN_VALID, CHECK_APP_INTERVAL_WHEN_INVALID } from 'config/constants' import { CHECK_APP_INTERVAL_WHEN_VALID, CHECK_APP_INTERVAL_WHEN_INVALID } from 'config/constants'
export const WrongAppOpened = createCustomErrorClass('WrongAppOpened')
export const WrongDeviceForAccount = createCustomErrorClass('WrongDeviceForAccount')
type OwnProps = { type OwnProps = {
currency?: ?CryptoCurrency, currency?: ?CryptoCurrency,
deviceSelected: ?Device, deviceSelected: ?Device,
@ -31,7 +35,7 @@ type OwnProps = {
devices: Device[], devices: Device[],
deviceSelected: ?Device, deviceSelected: ?Device,
deviceStatus: DeviceStatus, deviceStatus: DeviceStatus,
errorMessage: ?string, error: ?Error,
}) => React$Element<*>, }) => React$Element<*>,
} }
@ -48,7 +52,7 @@ type GenuineCheckStatus = 'success' | 'fail' | 'progress'
type State = { type State = {
deviceStatus: DeviceStatus, deviceStatus: DeviceStatus,
appStatus: AppStatus, appStatus: AppStatus,
errorMessage: ?string, error: ?Error,
genuineCheckStatus: GenuineCheckStatus, genuineCheckStatus: GenuineCheckStatus,
} }
@ -62,7 +66,7 @@ class EnsureDeviceApp extends PureComponent<Props, State> {
state = { state = {
appStatus: 'progress', appStatus: 'progress',
deviceStatus: this.props.deviceSelected ? 'connected' : 'unconnected', deviceStatus: this.props.deviceSelected ? 'connected' : 'unconnected',
errorMessage: null, error: null,
genuineCheckStatus: 'progress', genuineCheckStatus: 'progress',
} }
@ -139,7 +143,7 @@ class EnsureDeviceApp extends PureComponent<Props, State> {
e.message === 'Cannot write to HID device') e.message === 'Cannot write to HID device')
) { ) {
logger.log(e) logger.log(e)
throw new Error(`You must open application ‘${cur.name}’ on the device`) throw new WrongAppOpened(`WrongAppOpened ${cur.id}`, { currencyName: cur.name })
} }
throw e throw e
}) })
@ -148,10 +152,13 @@ class EnsureDeviceApp extends PureComponent<Props, State> {
const { freshAddress } = account const { freshAddress } = account
if (account && freshAddress !== address) { if (account && freshAddress !== address) {
logger.warn({ freshAddress, address }) logger.warn({ freshAddress, address })
throw new Error(`You must use the device associated to the account ‘${account.name}`) throw new WrongDeviceForAccount(`WrongDeviceForAccount ${account.name}`, {
accountName: account.name,
})
} }
} }
} else { } else {
logger.warn('EnsureDeviceApp for using dashboard is DEPRECATED !!!')
// FIXME REMOVE THIS ! should use EnsureDashboard dedicated component. // FIXME REMOVE THIS ! should use EnsureDashboard dedicated component.
const isDashboard = isDashboardOpen.send({ devicePath: deviceSelected.path }).toPromise() const isDashboard = isDashboardOpen.send({ devicePath: deviceSelected.path }).toPromise()
@ -166,7 +173,7 @@ class EnsureDeviceApp extends PureComponent<Props, State> {
this.handleGenuineCheck() this.handleGenuineCheck()
} }
} catch (e) { } catch (e) {
this.handleStatusChange(this.state.deviceStatus, 'fail', e.message) this.handleStatusChange(this.state.deviceStatus, 'fail', e)
isSuccess = false isSuccess = false
} }
@ -182,12 +189,12 @@ class EnsureDeviceApp extends PureComponent<Props, State> {
_timeout: * _timeout: *
_unmounted = false _unmounted = false
handleStatusChange = (deviceStatus, appStatus, errorMessage = null) => { handleStatusChange = (deviceStatus, appStatus, error = null) => {
const { onStatusChange } = this.props const { onStatusChange } = this.props
clearTimeout(this._timeout) clearTimeout(this._timeout)
if (!this._unmounted) { if (!this._unmounted) {
this.setState({ deviceStatus, appStatus, errorMessage }) this.setState({ deviceStatus, appStatus, error })
onStatusChange && onStatusChange(deviceStatus, appStatus, errorMessage) onStatusChange && onStatusChange(deviceStatus, appStatus, error)
} }
} }
@ -202,7 +209,7 @@ class EnsureDeviceApp extends PureComponent<Props, State> {
render() { render() {
const { currency, account, devices, deviceSelected, render } = this.props const { currency, account, devices, deviceSelected, render } = this.props
const { appStatus, deviceStatus, genuineCheckStatus, errorMessage } = this.state const { appStatus, deviceStatus, genuineCheckStatus, error } = this.state
if (render) { if (render) {
// if cur is not provided, we assume we want to check if user is on // if cur is not provided, we assume we want to check if user is on
@ -216,7 +223,7 @@ class EnsureDeviceApp extends PureComponent<Props, State> {
deviceSelected: deviceStatus === 'connected' ? deviceSelected : null, deviceSelected: deviceStatus === 'connected' ? deviceSelected : null,
deviceStatus, deviceStatus,
genuineCheckStatus, genuineCheckStatus,
errorMessage, error,
}) })
} }

4
src/components/GenuineCheckModal/index.js

@ -45,7 +45,7 @@ class GenuineCheck extends PureComponent<Props, State> {
onStatusChange={status => { onStatusChange={status => {
logger.log(`status changed to ${status}`) logger.log(`status changed to ${status}`)
}} }}
render={({ appStatus, genuineCheckStatus, deviceSelected, errorMessage }) => ( render={({ appStatus, genuineCheckStatus, deviceSelected, error }) => (
<DeviceConnect <DeviceConnect
appOpened={ appOpened={
appStatus === 'success' ? 'success' : appStatus === 'fail' ? 'fail' : null appStatus === 'success' ? 'success' : appStatus === 'fail' ? 'fail' : null
@ -54,7 +54,7 @@ class GenuineCheck extends PureComponent<Props, State> {
genuineCheckStatus={genuineCheckStatus} genuineCheckStatus={genuineCheckStatus}
devices={reducedDevicesList} devices={reducedDevicesList}
deviceSelected={deviceSelected} deviceSelected={deviceSelected}
errorMessage={errorMessage} error={error}
/> />
)} )}
/> />

5
src/components/RequestAmount/index.js

@ -21,6 +21,9 @@ import InputCurrency from 'components/base/InputCurrency'
import Button from 'components/base/Button' import Button from 'components/base/Button'
import Box from 'components/base/Box' import Box from 'components/base/Box'
import type { State } from 'reducers' import type { State } from 'reducers'
import createCustomErrorClass from 'helpers/createCustomErrorClass'
const NotEnoughBalance = createCustomErrorClass('NotEnoughBalance')
const InputRight = styled(Box).attrs({ const InputRight = styled(Box).attrs({
ff: 'Rubik', ff: 'Rubik',
@ -145,7 +148,7 @@ export class RequestAmount extends PureComponent<Props> {
return ( return (
<Box horizontal grow shrink> <Box horizontal grow shrink>
<InputCurrency <InputCurrency
error={canBeSpent ? null : 'Not enough balance'} error={canBeSpent ? null : new NotEnoughBalance()}
containerProps={containerProps} containerProps={containerProps}
defaultUnit={account.unit} defaultUnit={account.unit}
value={value} value={value}

5
src/components/ThrowBlock.js

@ -12,6 +12,7 @@ import db from 'helpers/db'
import ExportLogsBtn from 'components/ExportLogsBtn' import ExportLogsBtn from 'components/ExportLogsBtn'
import Box from 'components/base/Box' import Box from 'components/base/Box'
import Button from 'components/base/Button' import Button from 'components/base/Button'
import TranslatedError from './TranslatedError'
type Props = { type Props = {
children: any, children: any,
@ -81,7 +82,9 @@ ${error.stack}
if (error) { if (error) {
return ( return (
<Container> <Container>
<Inner>{`Error: ${error.message}`}</Inner> <Inner>
<TranslatedError error={error} />
</Inner>
<Box horizontal flow={2}> <Box horizontal flow={2}>
<Button primary onClick={this.handleRestart}> <Button primary onClick={this.handleRestart}>
{'Restart app'} {'Restart app'}

25
src/components/TranslatedError.js

@ -0,0 +1,25 @@
// @flow
// Convention:
// - errors we throw on our app will use a different error.name per error type
// - an error can have parameters, to use them, just use field of the Error object, that's what we give to `t()`
// - returned value is intentially not styled (is universal). wrap this in whatever you need
import { PureComponent } from 'react'
import { translate } from 'react-i18next'
import type { T } from 'types/common'
type Props = {
error: ?Error,
t: T,
}
class TranslatedError extends PureComponent<Props> {
render() {
const { t, error } = this.props
if (!error) return null
if (typeof error === 'string') return error
return t(`errors:${error.name}`, error)
}
}
export default translate()(TranslatedError)

9
src/components/base/FormattedVal/index.js

@ -1,5 +1,6 @@
// @flow // @flow
import invariant from 'invariant'
import React from 'react' import React from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import { connect } from 'react-redux' import { connect } from 'react-redux'
@ -76,9 +77,7 @@ function FormattedVal(props: Props) {
} = props } = props
let { val } = props let { val } = props
if (isUndefined(val)) { invariant(!isUndefined(val), 'FormattedVal require a `val` prop. Received `undefined`')
throw new Error('FormattedVal require a `val` prop. Received `undefined`')
}
const isNegative = val < 0 const isNegative = val < 0
@ -88,9 +87,7 @@ function FormattedVal(props: Props) {
// FIXME move out the % feature of this component... totally unrelated to currency & annoying for flow type. // FIXME move out the % feature of this component... totally unrelated to currency & annoying for flow type.
text = `${alwaysShowSign ? (isNegative ? '- ' : '+ ') : ''}${isNegative ? val * -1 : val} %` text = `${alwaysShowSign ? (isNegative ? '- ' : '+ ') : ''}${isNegative ? val * -1 : val} %`
} else { } else {
if (!unit) { invariant(unit, 'FormattedVal require a `unit` prop. Received `undefined`')
throw new Error('FormattedVal require a `unit` prop. Received `undefined`')
}
if (withIcon && isNegative) { if (withIcon && isNegative) {
val *= -1 val *= -1

7
src/components/base/Input/index.js

@ -8,6 +8,7 @@ import noop from 'lodash/noop'
import fontFamily from 'styles/styled/fontFamily' import fontFamily from 'styles/styled/fontFamily'
import Box from 'components/base/Box' import Box from 'components/base/Box'
import TranslatedError from 'components/TranslatedError'
const Container = styled(Box).attrs({ const Container = styled(Box).attrs({
horizontal: true, horizontal: true,
@ -172,7 +173,11 @@ class Input extends PureComponent<Props, State> {
onChange={this.handleChange} onChange={this.handleChange}
onKeyDown={this.handleKeyDown} onKeyDown={this.handleKeyDown}
/> />
{error && typeof error === 'string' ? <ErrorDisplay>{error}</ErrorDisplay> : null} {error ? (
<ErrorDisplay>
<TranslatedError error={error} />
</ErrorDisplay>
) : null}
</Box> </Box>
{renderRight} {renderRight}
</Container> </Container>

5
src/components/modals/AddAccounts/index.js

@ -1,5 +1,6 @@
// @flow // @flow
import invariant from 'invariant'
import React, { PureComponent } from 'react' import React, { PureComponent } from 'react'
import { compose } from 'redux' import { compose } from 'redux'
import { connect } from 'react-redux' import { connect } from 'react-redux'
@ -173,9 +174,7 @@ class AddAccounts extends PureComponent<Props, State> {
const stepIndex = this.STEPS.findIndex(s => s.id === stepId) const stepIndex = this.STEPS.findIndex(s => s.id === stepId)
const step = this.STEPS[stepIndex] const step = this.STEPS[stepIndex]
if (!step) { invariant(step, `AddAccountsModal: step ${stepId} doesn't exists`)
throw new Error(`AddAccountsModal: step ${stepId} doesn't exists`)
}
const { component: StepComponent, footer: StepFooter, hideFooter, onBack } = step const { component: StepComponent, footer: StepFooter, hideFooter, onBack } = step

6
src/components/modals/AddAccounts/steps/02-step-connect-device.js

@ -1,5 +1,6 @@
// @flow // @flow
import invariant from 'invariant'
import React, { Fragment } from 'react' import React, { Fragment } from 'react'
import { Trans } from 'react-i18next' import { Trans } from 'react-i18next'
@ -11,9 +12,8 @@ import { CurrencyCircleIcon } from 'components/base/CurrencyBadge'
import type { StepProps } from '../index' import type { StepProps } from '../index'
function StepConnectDevice({ t, currency, currentDevice, setState }: StepProps) { function StepConnectDevice({ t, currency, currentDevice, setState }: StepProps) {
if (!currency) { invariant(currency, 'No currency given')
throw new Error('No currency given')
}
return ( return (
<Fragment> <Fragment>
<Box align="center" mb={6}> <Box align="center" mb={6}>

10
src/components/modals/AddAccounts/steps/03-step-import.js

@ -1,5 +1,6 @@
// @flow // @flow
import invariant from 'invariant'
import React, { PureComponent, Fragment } from 'react' import React, { PureComponent, Fragment } from 'react'
import type { Account } from '@ledgerhq/live-common/lib/types' import type { Account } from '@ledgerhq/live-common/lib/types'
import uniq from 'lodash/uniq' import uniq from 'lodash/uniq'
@ -30,13 +31,8 @@ class StepImport extends PureComponent<StepProps> {
startScanAccountsDevice() { startScanAccountsDevice() {
const { currency, currentDevice, setState } = this.props const { currency, currentDevice, setState } = this.props
try { try {
if (!currency) { invariant(currency, 'No currency to scan')
throw new Error('No currency to scan') invariant(currentDevice, 'No device')
}
if (!currentDevice) {
throw new Error('No device')
}
const bridge = getBridgeForCurrency(currency) const bridge = getBridgeForCurrency(currency)

5
src/components/modals/Receive/index.js

@ -18,6 +18,7 @@ import Breadcrumb from 'components/Breadcrumb'
import Button from 'components/base/Button' import Button from 'components/base/Button'
import Modal, { ModalBody, ModalTitle, ModalContent, ModalFooter } from 'components/base/Modal' import Modal, { ModalBody, ModalTitle, ModalContent, ModalFooter } from 'components/base/Modal'
import StepConnectDevice from 'components/modals/StepConnectDevice' import StepConnectDevice from 'components/modals/StepConnectDevice'
import { WrongDeviceForAccount } from 'components/EnsureDeviceApp'
import StepAccount from './01-step-account' import StepAccount from './01-step-account'
import StepConfirmAddress from './03-step-confirm-address' import StepConfirmAddress from './03-step-confirm-address'
@ -208,7 +209,9 @@ class ReceiveModal extends PureComponent<Props, State> {
.toPromise() .toPromise()
if (address !== account.freshAddress) { if (address !== account.freshAddress) {
throw new Error('Confirmed address is different') throw new WrongDeviceForAccount(`WrongDeviceForAccount ${account.name}`, {
accountName: account.name,
})
} }
this.setState({ addressVerified: true, stepIndex: 3 }) this.setState({ addressVerified: true, stepIndex: 3 })

11
src/components/modals/Send/04-step-confirmation.js

@ -2,6 +2,7 @@
import React from 'react' import React from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import type { Operation } from '@ledgerhq/live-common/lib/types' import type { Operation } from '@ledgerhq/live-common/lib/types'
import type { T } from 'types/common'
import Spinner from 'components/base/Spinner' import Spinner from 'components/base/Spinner'
import IconCheckCircle from 'icons/CheckCircle' import IconCheckCircle from 'icons/CheckCircle'
@ -9,9 +10,7 @@ import IconExclamationCircleThin from 'icons/ExclamationCircleThin'
import Box from 'components/base/Box' import Box from 'components/base/Box'
import { multiline } from 'styles/helpers' import { multiline } from 'styles/helpers'
import { colors } from 'styles/theme' import { colors } from 'styles/theme'
import { formatError } from 'helpers/errors' import TranslatedError from '../../TranslatedError'
import type { T } from 'types/common'
const Container = styled(Box).attrs({ const Container = styled(Box).attrs({
alignItems: 'center', alignItems: 'center',
@ -65,7 +64,11 @@ function StepConfirmation(props: Props) {
</span> </span>
<Title>{t(`${tPrefix}.title`)}</Title> <Title>{t(`${tPrefix}.title`)}</Title>
<Text style={{ userSelect: 'text' }}> <Text style={{ userSelect: 'text' }}>
{optimisticOperation ? multiline(t(`${tPrefix}.text`)) : error ? formatError(error) : null} {optimisticOperation ? (
multiline(t(`${tPrefix}.text`))
) : error ? (
<TranslatedError error={error} />
) : null}
</Text> </Text>
<Text style={{ userSelect: 'text' }}> <Text style={{ userSelect: 'text' }}>
{optimisticOperation ? optimisticOperation.hash : ''} {optimisticOperation ? optimisticOperation.hash : ''}

4
src/components/modals/StepConnectDevice.js

@ -28,14 +28,14 @@ const StepConnectDevice = ({
currency={currency} currency={currency}
deviceSelected={deviceSelected} deviceSelected={deviceSelected}
onStatusChange={onStatusChange} onStatusChange={onStatusChange}
render={({ currency, appStatus, devices, deviceSelected, errorMessage }) => ( render={({ currency, appStatus, devices, deviceSelected, error }) => (
<DeviceConnect <DeviceConnect
currency={currency} currency={currency}
appOpened={appStatus === 'success' ? 'success' : appStatus === 'fail' ? 'fail' : null} appOpened={appStatus === 'success' ? 'success' : appStatus === 'fail' ? 'fail' : null}
devices={devices} devices={devices}
deviceSelected={deviceSelected} deviceSelected={deviceSelected}
onChangeDevice={onChangeDevice} onChangeDevice={onChangeDevice}
errorMessage={errorMessage} error={error}
/> />
)} )}
/> />

13
src/helpers/createCustomErrorClass.js

@ -0,0 +1,13 @@
// @flow
export default (name: string) => {
const C = function CustomError(message?: string, fields?: Object) {
this.name = name
this.message = message || name
this.stack = new Error().stack
Object.assign(this, fields)
}
// $FlowFixMe
C.prototype = new Error()
return C
}

7
src/helpers/getAddressForCurrency/btc.js

@ -4,6 +4,9 @@ import type { CryptoCurrency } from '@ledgerhq/live-common/lib/types'
import Btc from '@ledgerhq/hw-app-btc' import Btc from '@ledgerhq/hw-app-btc'
import type Transport from '@ledgerhq/hw-transport' import type Transport from '@ledgerhq/hw-transport'
import getBitcoinLikeInfo from '../devices/getBitcoinLikeInfo' import getBitcoinLikeInfo from '../devices/getBitcoinLikeInfo'
import createCustomErrorClass from '../createCustomErrorClass'
const BtcUnmatchedApp = createCustomErrorClass('BtcUnmatchedApp')
export default async ( export default async (
transport: Transport<*>, transport: Transport<*>,
@ -24,7 +27,9 @@ export default async (
if (bitcoinLikeInfo) { if (bitcoinLikeInfo) {
const { P2SH, P2PKH } = await getBitcoinLikeInfo(transport) const { P2SH, P2PKH } = await getBitcoinLikeInfo(transport)
if (P2SH !== bitcoinLikeInfo.P2SH || P2PKH !== bitcoinLikeInfo.P2PKH) { if (P2SH !== bitcoinLikeInfo.P2SH || P2PKH !== bitcoinLikeInfo.P2PKH) {
throw new Error(`You must open application ‘${currency.name}’ on the device`) throw new BtcUnmatchedApp(`BtcUnmatchedApp ${currency.id}`, {
currencyName: currency.name,
})
} }
} }

5
src/helpers/libcore.js

@ -10,9 +10,12 @@ import type { NJSAccount, NJSOperation } from '@ledgerhq/ledger-core/src/ledgerc
import { isSegwitAccount } from 'helpers/bip32' import { isSegwitAccount } from 'helpers/bip32'
import * as accountIdHelper from 'helpers/accountId' import * as accountIdHelper from 'helpers/accountId'
import createCustomErrorClass from './createCustomErrorClass'
import { getAccountPlaceholderName, getNewAccountPlaceholderName } from './accountName' import { getAccountPlaceholderName, getNewAccountPlaceholderName } from './accountName'
const NoAddressesFound = createCustomErrorClass('NoAddressesFound')
type Props = { type Props = {
core: *, core: *,
devicePath: string, devicePath: string,
@ -248,7 +251,7 @@ async function buildAccountRaw({
})) }))
if (addresses.length === 0) { if (addresses.length === 0) {
throw new Error('no addresses found') throw new NoAddressesFound()
} }
const { str: freshAddress, path: freshAddressPath } = addresses[0] const { str: freshAddress, path: freshAddressPath } = addresses[0]

12
src/helpers/signTransactionForCurrency/ethereum.js

@ -1,4 +1,5 @@
// @flow // @flow
import invariant from 'invariant'
import Eth from '@ledgerhq/hw-app-eth' import Eth from '@ledgerhq/hw-app-eth'
import type Transport from '@ledgerhq/hw-transport' import type Transport from '@ledgerhq/hw-transport'
import EthereumTx from 'ethereumjs-tx' import EthereumTx from 'ethereumjs-tx'
@ -34,7 +35,7 @@ export default async (
// First, we need to create a partial tx and send to the device // First, we need to create a partial tx and send to the device
const chainId = getNetworkId(currencyId) const chainId = getNetworkId(currencyId)
if (!chainId) throw new Error(`chainId not found for currency=${currencyId}`) invariant(chainId, `chainId not found for currency=${currencyId}`)
const tx = new EthereumTx({ const tx = new EthereumTx({
nonce: t.nonce, nonce: t.nonce,
gasPrice: `0x${t.gasPrice.toString(16)}`, gasPrice: `0x${t.gasPrice.toString(16)}`,
@ -57,11 +58,10 @@ export default async (
tx.s = Buffer.from(result.s, 'hex') tx.s = Buffer.from(result.s, 'hex')
const signedChainId = Math.floor((tx.v[0] - 35) / 2) // EIP155: v should be chain_id * 2 + {35, 36} const signedChainId = Math.floor((tx.v[0] - 35) / 2) // EIP155: v should be chain_id * 2 + {35, 36}
const validChainId = chainId & 0xff // eslint-disable-line no-bitwise const validChainId = chainId & 0xff // eslint-disable-line no-bitwise
if (signedChainId !== validChainId) { invariant(
throw new Error( signedChainId === validChainId,
`Invalid chainId signature returned. Expected: ${chainId}, Got: ${signedChainId}`, `Invalid chainId signature returned. Expected: ${chainId}, Got: ${signedChainId}`,
) )
}
// Finally, we can send the transaction string to broadcast // Finally, we can send the transaction string to broadcast
return `0x${tx.serialize().toString('hex')}` return `0x${tx.serialize().toString('hex')}`

13
static/i18n/en/errors.yml

@ -0,0 +1,13 @@
RangeError: {{message}}
Error: {{message}}
LedgerAPIErrorWithMessage: {{message}}
TransportStatusError: {{message}}
FeeEstimationFailed: 'fee estimation failed (status: {{status}})'
NotEnoughBalance: 'Not enough balance'
BtcUnmatchedApp: 'You must open application ‘{{currencyName}}’ on the device'
WrongAppOpened: 'You must open application ‘{{currencyName}}’ on the device'
WrongDeviceForAccount: 'You must use the device associated to the account ‘{{accountName}}’'
LedgerAPINotAvailable: 'Ledger API is not available for currency {{currencyName}}'
LedgerAPIError: 'A problem occurred with Ledger API. Please try again later. (HTTP {{status}})'
NetworkDown: 'Your internet connection seems down. Please try again later.'
NoAddressesFound: 'No accounts found'
Loading…
Cancel
Save