Browse Source

Merge pull request #898 from LedgerHQ/develop

Prepare for rc.2
master
Gaëtan Renaudeau 7 years ago
committed by GitHub
parent
commit
bbfe82acec
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      src/bridge/EthereumJSBridge.js
  2. 29
      src/bridge/LibcoreBridge.js
  3. 10
      src/bridge/RippleJSBridge.js
  4. 2
      src/bridge/UnsupportedBridge.js
  5. 4
      src/bridge/makeMockBridge.js
  6. 2
      src/bridge/types.js
  7. 9
      src/components/RequestAmount/index.js
  8. 16
      src/components/modals/Send/fields/AmountField.js
  9. 5
      src/components/modals/Send/steps/01-step-amount.js
  10. 5
      src/components/modals/Send/steps/04-step-confirmation.js
  11. 7
      src/components/modals/UpdateFirmware/Installing.js
  12. 2
      src/components/modals/UpdateFirmware/steps/02-step-flash-mcu.js
  13. 7
      src/config/constants.js
  14. 2
      src/helpers/anonymizer.js
  15. 4
      src/helpers/apps/installApp.js
  16. 4
      src/helpers/apps/uninstallApp.js
  17. 7
      src/helpers/socket.js
  18. 4
      src/helpers/urls.js
  19. 2
      static/i18n/en/app.yml
  20. 28
      static/i18n/fr/app.yml
  21. 6
      static/i18n/fr/errors.yml

6
src/bridge/EthereumJSBridge.js

@ -13,8 +13,11 @@ import { getDerivations } from 'helpers/derivations'
import getAddressCommand from 'commands/getAddress'
import signTransactionCommand from 'commands/signTransaction'
import { getAccountPlaceholderName, getNewAccountPlaceholderName } from 'helpers/accountName'
import { createCustomErrorClass } from 'helpers/errors'
import type { EditProps, WalletBridge } from './types'
const NotEnoughBalance = createCustomErrorClass('NotEnoughBalance')
// TODO in future it would be neat to support eip55
type Transaction = {
@ -363,7 +366,8 @@ const EthereumBridge: WalletBridge<Transaction> = {
EditAdvancedOptions,
canBeSpent: (a, t) => Promise.resolve(t.amount <= a.balance),
checkCanBeSpent: (a, t) =>
t.amount <= a.balance ? Promise.resolve() : Promise.reject(new NotEnoughBalance()),
getTotalSpent: (a, t) => Promise.resolve(t.amount + t.gasPrice * t.gasLimit),
getMaxAmount: (a, t) => Promise.resolve(a.balance - t.gasPrice * t.gasLimit),

29
src/bridge/LibcoreBridge.js

@ -11,8 +11,12 @@ import libcoreSyncAccount from 'commands/libcoreSyncAccount'
import libcoreSignAndBroadcast from 'commands/libcoreSignAndBroadcast'
import libcoreGetFees from 'commands/libcoreGetFees'
import libcoreValidAddress from 'commands/libcoreValidAddress'
import { createCustomErrorClass } from 'helpers/errors'
import type { WalletBridge, EditProps } from './types'
const NOT_ENOUGH_FUNDS = 52
const NotEnoughBalance = createCustomErrorClass('NotEnoughBalance')
const notImplemented = new Error('LibcoreBridge: not implemented')
type Transaction = {
@ -65,10 +69,13 @@ const isRecipientValid = (currency, recipient) => {
const feesLRU = LRU({ max: 100 })
const getFeesKey = (a, t) =>
`${a.id}_${a.blockHeight || 0}_${t.amount}_${t.recipient}_${t.feePerByte}`
const getFees = async (a, transaction) => {
const isValid = await isRecipientValid(a.currency, transaction.recipient)
if (!isValid) return null
const key = `${a.id}_${transaction.amount}_${transaction.recipient}_${transaction.feePerByte}`
const key = getFeesKey(a, transaction)
let promise = feesLRU.get(key)
if (promise) return promise
promise = libcoreGetFees
@ -79,6 +86,19 @@ const getFees = async (a, transaction) => {
return promise
}
const checkCanBeSpent = (a, t) =>
!t.amount
? Promise.resolve()
: getFees(a, t)
.then(() => {})
.catch(e => {
if (e.code === NOT_ENOUGH_FUNDS) {
throw new NotEnoughBalance()
}
feesLRU.del(getFeesKey(a, t))
throw e
})
const LibcoreBridge: WalletBridge<Transaction> = {
scanAccountsOnDevice(currency, devicePath) {
return libcoreScanAccounts
@ -173,12 +193,7 @@ const LibcoreBridge: WalletBridge<Transaction> = {
isValidTransaction: (a, t) => (t.amount > 0 && t.recipient && true) || false,
canBeSpent: (a, t) =>
!t.amount
? Promise.resolve(true)
: getFees(a, t)
.then(() => true)
.catch(() => false),
checkCanBeSpent,
getTotalSpent: (a, t) =>
!t.amount

10
src/bridge/RippleJSBridge.js

@ -19,8 +19,11 @@ import {
import FeesRippleKind from 'components/FeesField/RippleKind'
import AdvancedOptionsRippleKind from 'components/AdvancedOptions/RippleKind'
import { getAccountPlaceholderName, getNewAccountPlaceholderName } from 'helpers/accountName'
import { createCustomErrorClass } from 'helpers/errors'
import type { WalletBridge, EditProps } from './types'
const NotEnoughBalance = createCustomErrorClass('NotEnoughBalance')
type Transaction = {
amount: number,
recipient: string,
@ -461,9 +464,12 @@ const RippleJSBridge: WalletBridge<Transaction> = {
isValidTransaction: (a, t) => (t.amount > 0 && t.recipient && true) || false,
canBeSpent: async (a, t) => {
checkCanBeSpent: async (a, t) => {
const r = await getServerInfo(a.endpointConfig)
return t.amount + t.fee + parseAPIValue(r.validatedLedger.reserveBaseXRP) <= a.balance
if (t.amount + t.fee + parseAPIValue(r.validatedLedger.reserveBaseXRP) <= a.balance) {
return
}
throw new NotEnoughBalance()
},
getTotalSpent: (a, t) => Promise.resolve(t.amount + t.fee),

2
src/bridge/UnsupportedBridge.js

@ -31,7 +31,7 @@ const UnsupportedBridge: WalletBridge<*> = {
getTransactionRecipient: () => '',
canBeSpent: () => Promise.resolve(false),
checkCanBeSpent: () => Promise.resolve(),
getTotalSpent: () => Promise.resolve(0),

4
src/bridge/makeMockBridge.js

@ -32,7 +32,7 @@ function makeMockBridge(opts?: Opts): WalletBridge<*> {
extraInitialTransactionProps,
getTotalSpent,
getMaxAmount,
canBeSpent,
checkCanBeSpent,
} = {
...defaultOpts,
...opts,
@ -145,7 +145,7 @@ function makeMockBridge(opts?: Opts): WalletBridge<*> {
isValidTransaction: (a, t) => (t.amount > 0 && t.recipient && true) || false,
canBeSpent,
checkCanBeSpent,
getTotalSpent,

2
src/bridge/types.js

@ -82,7 +82,7 @@ export interface WalletBridge<Transaction> {
// render the whole advanced part of the form
EditAdvancedOptions?: *; // React$ComponentType<EditProps<Transaction>>;
canBeSpent(account: Account, transaction: Transaction): Promise<boolean>;
checkCanBeSpent(account: Account, transaction: Transaction): Promise<void>;
getTotalSpent(account: Account, transaction: Transaction): Promise<number>;

9
src/components/RequestAmount/index.js

@ -21,9 +21,6 @@ import InputCurrency from 'components/base/InputCurrency'
import Button from 'components/base/Button'
import Box from 'components/base/Box'
import type { State } from 'reducers'
import { createCustomErrorClass } from 'helpers/errors'
const NotEnoughBalance = createCustomErrorClass('NotEnoughBalance')
const InputRight = styled(Box).attrs({
ff: 'Rubik',
@ -50,7 +47,7 @@ type OwnProps = {
// left value (always the one which is returned)
value: number,
canBeSpent: boolean,
canBeSpentError: ?Error,
// max left value
max: number,
@ -141,14 +138,14 @@ export class RequestAmount extends PureComponent<Props> {
renderInputs(containerProps: Object) {
// TODO move this inlined into render() for less spaghetti
const { value, account, rightCurrency, getCounterValue, canBeSpent } = this.props
const { value, account, rightCurrency, getCounterValue, canBeSpentError } = this.props
const right = getCounterValue(value) || 0
const rightUnit = rightCurrency.units[0]
// FIXME: no way InputCurrency pure can work here. inlined InputRight (should be static func?), inline containerProps object..
return (
<Box horizontal grow shrink>
<InputCurrency
error={canBeSpent ? null : new NotEnoughBalance()}
error={canBeSpentError}
containerProps={containerProps}
defaultUnit={account.unit}
value={value}

16
src/components/modals/Send/fields/AmountField.js

@ -4,9 +4,9 @@ import Box from 'components/base/Box'
import Label from 'components/base/Label'
import RequestAmount from 'components/RequestAmount'
class AmountField extends Component<*, { canBeSpent: boolean }> {
class AmountField extends Component<*, { canBeSpentError: ?Error }> {
state = {
canBeSpent: true,
canBeSpentError: null,
}
componentDidMount() {
this.resync()
@ -26,9 +26,13 @@ class AmountField extends Component<*, { canBeSpent: boolean }> {
async resync() {
const { account, bridge, transaction } = this.props
const syncId = ++this.syncId
const canBeSpent = await bridge.canBeSpent(account, transaction)
try {
await bridge.checkCanBeSpent(account, transaction)
if (this.syncId !== syncId) return
this.setState({ canBeSpent })
this.setState({ canBeSpentError: null })
} catch (canBeSpentError) {
this.setState({ canBeSpentError })
}
}
onChange = (amount: number) => {
@ -38,14 +42,14 @@ class AmountField extends Component<*, { canBeSpent: boolean }> {
render() {
const { bridge, account, transaction, t } = this.props
const { canBeSpent } = this.state
const { canBeSpentError } = this.state
return (
<Box flow={1}>
<Label>{t('app:send.steps.amount.amount')}</Label>
<RequestAmount
withMax={false}
account={account}
canBeSpent={canBeSpent}
canBeSpentError={canBeSpentError}
onChange={this.onChange}
value={bridge.getTransactionAmount(account, transaction)}
/>

5
src/components/modals/Send/steps/01-step-amount.js

@ -58,6 +58,7 @@ export default ({
bridge &&
transaction && (
<AmountField
key={account.id}
account={account}
bridge={bridge}
transaction={transaction}
@ -134,7 +135,9 @@ export class StepAmountFooter extends PureComponent<
try {
const totalSpent = await bridge.getTotalSpent(account, transaction)
if (syncId !== this.syncId) return
const canBeSpent = await bridge.canBeSpent(account, transaction)
const canBeSpent = await bridge
.checkCanBeSpent(account, transaction)
.then(() => true, () => false)
if (syncId !== this.syncId) return
this.setState({ totalSpent, canBeSpent, isSyncing: false })
} catch (err) {

5
src/components/modals/Send/steps/04-step-confirmation.js

@ -2,7 +2,6 @@
import React, { Fragment } from 'react'
import styled from 'styled-components'
import { getAccountOperationExplorer } from '@ledgerhq/live-common/lib/explorers'
import { MODAL_OPERATION_DETAILS } from 'config/constants'
import { colors } from 'styles/theme'
@ -82,12 +81,9 @@ export function StepConfirmationFooter({
openModal,
closeModal,
}: StepProps<*>) {
const url =
optimisticOperation && account && getAccountOperationExplorer(account, optimisticOperation)
return (
<Fragment>
{optimisticOperation ? (
url ? (
<Button
ml={2}
event="Send Flow Step 4 View OpD Clicked"
@ -104,7 +100,6 @@ export function StepConfirmationFooter({
>
{t('app:send.steps.confirmation.success.cta')}
</Button>
) : null
) : error ? (
<Button
ml={2}

7
src/components/modals/UpdateFirmware/Installing.js

@ -18,11 +18,16 @@ function Installing({ t }: Props) {
<Box mx={7} align="center">
<Spinner color="fog" size={44} />
</Box>
<Box mx={7} mt={4} mb={7}>
<Box mx={7} mt={4} mb={2}>
<Text ff="Museo Sans|Regular" align="center" color="dark" fontSize={6}>
{t('app:manager.modal.installing')}
</Text>
</Box>
<Box mx={7} mt={4} mb={7}>
<Text ff="Open Sans|Regular" align="center" color="graphite" fontSize={4}>
{t('app:manager.modal.mcuPin')}
</Text>
</Box>
</Fragment>
)
}

2
src/components/modals/UpdateFirmware/steps/02-step-flash-mcu.js

@ -127,6 +127,8 @@ class StepFlashMcu extends PureComponent<Props, State> {
} else if (deviceInfo.isOSU) {
await installFinalFirmware(device)
transitionTo('finish')
} else {
transitionTo('finish')
}
} catch (error) {
setError(error)

7
src/config/constants.js

@ -53,11 +53,8 @@ export const MANAGER_API_BASE = stringFromEnv(
'MANAGER_API_BASE',
'https://beta.manager.live.ledger.fr/api',
)
export const BASE_SOCKET_URL = stringFromEnv('BASE_SOCKET_URL', 'ws://api.ledgerwallet.com/update')
export const BASE_SOCKET_URL_SECURE = stringFromEnv(
'BASE_SOCKET_URL',
'wss://api.ledgerwallet.com/update',
)
export const BASE_SOCKET_URL = stringFromEnv('BASE_SOCKET_URL', 'wss://api.ledgerwallet.com/update')
// Flags

2
src/helpers/anonymizer.js

@ -51,7 +51,7 @@ export default {
.replace(/\/addresses\/[^/]+/g, '/addresses/<HIDDEN>')
.replace(/blockHash=[^&]+/g, 'blockHash=<HIDDEN>'),
appURI: (uri: string): string => uri.replace(/account\/[^/]/g, 'account/<HIDDEN>'),
appURI: (uri: string): string => uri.replace(/account\/[^/]+/g, 'account/<HIDDEN>'),
filepath: filepathReplace,

4
src/helpers/apps/installApp.js

@ -2,7 +2,7 @@
import qs from 'qs'
import type Transport from '@ledgerhq/hw-transport'
import { BASE_SOCKET_URL_SECURE } from 'config/constants'
import { BASE_SOCKET_URL } from 'config/constants'
import { createDeviceSocket } from 'helpers/socket'
import type { LedgerScriptParams } from 'helpers/common'
@ -44,6 +44,6 @@ export default async function installApp(
...app,
firmwareKey: app.firmware_key,
}
const url = `${BASE_SOCKET_URL_SECURE}/install?${qs.stringify(params)}`
const url = `${BASE_SOCKET_URL}/install?${qs.stringify(params)}`
return remapError(createDeviceSocket(transport, url).toPromise())
}

4
src/helpers/apps/uninstallApp.js

@ -2,7 +2,7 @@
import qs from 'qs'
import type Transport from '@ledgerhq/hw-transport'
import { BASE_SOCKET_URL_SECURE } from 'config/constants'
import { BASE_SOCKET_URL } from 'config/constants'
import { createDeviceSocket } from 'helpers/socket'
import type { LedgerScriptParams } from 'helpers/common'
@ -38,6 +38,6 @@ export default async function uninstallApp(
firmware: app.delete,
firmwareKey: app.delete_key,
}
const url = `${BASE_SOCKET_URL_SECURE}/install?${qs.stringify(params)}`
const url = `${BASE_SOCKET_URL}/install?${qs.stringify(params)}`
return remapError(createDeviceSocket(transport, url).toPromise())
}

7
src/helpers/socket.js

@ -25,7 +25,7 @@ export const createDeviceSocket = (transport: Transport<*>, url: string) =>
try {
ws = new Websocket(url)
} catch (err) {
o.error(new WebsocketConnectionFailed(err.message))
o.error(new WebsocketConnectionFailed(err.message, { url }))
return () => {}
}
invariant(ws, 'websocket is available')
@ -36,7 +36,7 @@ export const createDeviceSocket = (transport: Transport<*>, url: string) =>
ws.on('error', e => {
logger.websocket('ERROR', e)
o.error(new WebsocketConnectionError(e.message))
o.error(new WebsocketConnectionError(e.message, { url }))
})
ws.on('close', () => {
@ -98,7 +98,7 @@ export const createDeviceSocket = (transport: Transport<*>, url: string) =>
error: msg => {
logger.websocket('ERROR', msg.data)
throw new DeviceSocketFail(msg.data)
throw new DeviceSocketFail(msg.data, { url })
},
}
@ -108,6 +108,7 @@ export const createDeviceSocket = (transport: Transport<*>, url: string) =>
if (!(msg.query in handlers)) {
throw new DeviceSocketNoHandler(`Cannot handle msg of type ${msg.query}`, {
query: msg.query,
url,
})
}
logger.websocket('RECEIVE', msg)

4
src/helpers/urls.js

@ -1,7 +1,7 @@
// @flow
import qs from 'qs'
import { MANAGER_API_BASE, BASE_SOCKET_URL_SECURE } from 'config/constants'
import { MANAGER_API_BASE, BASE_SOCKET_URL } from 'config/constants'
import type { LedgerScriptParams } from 'helpers/common'
const urlBuilder = (base: string) => (endpoint: string): string => `${base}/${endpoint}`
@ -9,7 +9,7 @@ const urlBuilder = (base: string) => (endpoint: string): string => `${base}/${en
const managerUrlbuilder = urlBuilder(MANAGER_API_BASE)
const wsURLBuilder = (endpoint: string) => (params?: Object) =>
`${BASE_SOCKET_URL_SECURE}/${endpoint}${params ? `?${qs.stringify(params)}` : ''}`
`${BASE_SOCKET_URL}/${endpoint}${params ? `?${qs.stringify(params)}` : ''}`
// const wsURLBuilderProxy = (endpoint: string) => (params?: Object) =>
// `ws://manager.ledger.fr:3501/${endpoint}${params ? `?${qs.stringify(params)}` : ''}`

2
static/i18n/en/app.yml

@ -245,7 +245,7 @@ manager:
subtitle: Install or uninstall apps on your device
device:
title: Connect your device
desc: 'Follow the steps below to use the device Manager'
desc: 'Follow the steps below to open the Manager'
cta: Connect my device
errors:
noDevice: No device is connected (TEMPLATE NEEDED)

28
static/i18n/fr/app.yml

@ -217,32 +217,32 @@ manager:
installed: 'Firmware version {{version}}'
titleNano: Ledger Nano S
titleBlue: Ledger Blue
update: Update firmware
continue: Continue update
update: Update
continue: Continue
latest: 'Firmware version {{version}} is available'
disclaimerTitle: 'You are about to install the latest <1><0>firmware {{version}}</0></1>'
disclaimerAppDelete: Please note that all the apps installed on your device will be deleted.
disclaimerAppReinstall: You will be able to re-install your apps after the firmware update
disclaimerTitle: 'You are about to install <1><0>firmware version {{version}}</0></1>'
disclaimerAppDelete: Apps installed on your device have to re-installed after the update.
disclaimerAppReinstall: "This does not affect your crypto assets in the blockchain."
modal:
steps:
idCheck: Identifier check
updateMCU: Update MCU
idCheck: Identifier
updateMCU: MCU update
confirm: Confirmation
installing: Firmware updating...
confirmIdentifier: Confirm identifier
confirmIdentifierText: Please confirm identifier on your Device. Be sure the identifier is the same as below
confirmIdentifier: Verify the identifier
confirmIdentifierText: Verify that the identifier on your device is the same as the identifier below. Press the right button to confirm.
identifier: Identifier
mcuTitle: Updating MCU
mcuFirst: Unplug your device from your computer
mcuSecond: Press and hold left button and plug your device until the processing screen appears
mcuFirst: Disconnect the USB cable from your device
mcuSecond: Press the left button and hold it while you reconnect the USB cable until the processing screen appears
mcuPin: If asked on device, please enter your pin code to finish the process
successTitle: Firmware has been updated with success
successText: You can now re-install your applications on your device
successTitle: Firmware updated
successText: You may re-install the apps on your device
title: Manager
subtitle: Install or uninstall apps on your device
device:
title: Connect your device
desc: 'Follow the steps below to use the device Manager'
desc: 'Follow the steps below to open the Manager'
cta: Connect my device
errors:
noDevice: No device is connected (TEMPLATE NEEDED)

6
static/i18n/fr/errors.yml

@ -23,6 +23,12 @@ DisconnectedDevice:
Error:
title: '{{message}}'
description: Something went wrong. Please retry or contact us.
"Invariant Violation":
title: '{{message}}'
description: Something went wrong. Please retry or contact us.
InternalError:
title: '{{message}}'
description: Something went wrong. Please retry or contact us.
TypeError:
title: '{{message}}'
description: Something went wrong. Please retry or contact us.

Loading…
Cancel
Save