Browse Source

Merge branch 'develop' of github.com:LedgerHQ/ledger-live-desktop into feature/test-e2e-password-lock

gre-patch-1
Arnaud97234 6 years ago
parent
commit
22066a9b91
No known key found for this signature in database GPG Key ID: C77229DB9ECCFE6
  1. 1
      .prettierignore
  2. 4
      package.json
  3. 6
      src/bridge/EthereumJSBridge.js
  4. 10
      src/bridge/LibcoreBridge.js
  5. 34
      src/bridge/RippleJSBridge.js
  6. 4
      src/bridge/UnsupportedBridge.js
  7. 8
      src/bridge/makeMockBridge.js
  8. 7
      src/bridge/types.js
  9. 2
      src/commands/index.js
  10. 18
      src/commands/killInternalProcess.js
  11. 19
      src/components/AdvancedOptions/RippleKind.js
  12. 5
      src/components/ExchangePage/index.js
  13. 62
      src/components/ManagerPage/AppsList.js
  14. 7
      src/components/ManagerPage/index.js
  15. 2
      src/components/Onboarding/steps/Analytics.js
  16. 8
      src/components/RequestAmount/index.js
  17. 1
      src/components/base/Modal/ConfirmModal.js
  18. 16
      src/components/modals/Send/fields/AmountField.js
  19. 10
      src/components/modals/Send/steps/01-step-amount.js
  20. 29
      src/helpers/countervalues.js
  21. 4
      src/helpers/reset.js
  22. 2
      src/reducers/settings.js
  23. 2
      static/i18n/en/app.json
  24. 32
      test-e2e/password-lock-check.spec.js
  25. 6
      yarn.lock

1
.prettierignore

@ -1 +1,2 @@
package.json package.json
test-e2e/sync/data

4
package.json

@ -18,7 +18,7 @@
"test": "jest src", "test": "jest src",
"test-e2e": "jest test-e2e", "test-e2e": "jest test-e2e",
"test-sync": "bash test-e2e/sync/launch.sh", "test-sync": "bash test-e2e/sync/launch.sh",
"prettier": "prettier --write \"{src,webpack,.storybook,test-e2e, test-e2e/sync/data}/**/*.{js,json}\"", "prettier": "prettier --write \"{src,webpack,.storybook,test-e2e}/**/*.{js,json}\"",
"ci": "yarn lint && yarn flow && yarn prettier && yarn test", "ci": "yarn lint && yarn flow && yarn prettier && yarn test",
"storybook": "NODE_ENV=development STORYBOOK_ENV=1 start-storybook -s ./static -p 4444", "storybook": "NODE_ENV=development STORYBOOK_ENV=1 start-storybook -s ./static -p 4444",
"publish-storybook": "bash ./scripts/legacy/publish-storybook.sh", "publish-storybook": "bash ./scripts/legacy/publish-storybook.sh",
@ -40,7 +40,7 @@
"@ledgerhq/hw-transport": "^4.13.0", "@ledgerhq/hw-transport": "^4.13.0",
"@ledgerhq/hw-transport-node-hid": "4.22.0", "@ledgerhq/hw-transport-node-hid": "4.22.0",
"@ledgerhq/ledger-core": "2.0.0-rc.7", "@ledgerhq/ledger-core": "2.0.0-rc.7",
"@ledgerhq/live-common": "^3.5.1", "@ledgerhq/live-common": "^3.7.1",
"animated": "^0.2.2", "animated": "^0.2.2",
"async": "^2.6.1", "async": "^2.6.1",
"axios": "^0.18.0", "axios": "^0.18.0",

6
src/bridge/EthereumJSBridge.js

@ -420,15 +420,13 @@ const EthereumBridge: WalletBridge<Transaction> = {
getTransactionRecipient: (a, t) => t.recipient, getTransactionRecipient: (a, t) => t.recipient,
isValidTransaction: (a, t) => (!t.amount.isZero() && t.recipient && true) || false,
EditFees, EditFees,
EditAdvancedOptions, EditAdvancedOptions,
checkCanBeSpent: (a, t) => checkValidTransaction: (a, t) =>
t.amount.isLessThanOrEqualTo(a.balance) t.amount.isLessThanOrEqualTo(a.balance)
? Promise.resolve() ? Promise.resolve(true)
: Promise.reject(new NotEnoughBalance()), : Promise.reject(new NotEnoughBalance()),
getTotalSpent: (a, t) => getTotalSpent: (a, t) =>

10
src/bridge/LibcoreBridge.js

@ -94,11 +94,11 @@ const getFees = async (a, transaction) => {
return promise return promise
} }
const checkCanBeSpent = (a, t) => const checkValidTransaction = (a, t) =>
!t.amount !t.amount
? Promise.resolve() ? Promise.resolve(true)
: getFees(a, t) : getFees(a, t)
.then(() => {}) .then(() => true)
.catch(e => { .catch(e => {
if (e.code === NOT_ENOUGH_FUNDS) { if (e.code === NOT_ENOUGH_FUNDS) {
throw new NotEnoughBalance() throw new NotEnoughBalance()
@ -191,9 +191,7 @@ const LibcoreBridge: WalletBridge<Transaction> = {
// EditAdvancedOptions, // EditAdvancedOptions,
isValidTransaction: (a, t) => (!t.amount.isZero() && t.recipient && true) || false, checkValidTransaction,
checkCanBeSpent,
getTotalSpent: (a, t) => getTotalSpent: (a, t) =>
t.amount.isZero() t.amount.isZero()

34
src/bridge/RippleJSBridge.js

@ -463,10 +463,30 @@ const RippleJSBridge: WalletBridge<Transaction> = {
getTransactionAmount: (a, t) => t.amount, getTransactionAmount: (a, t) => t.amount,
editTransactionRecipient: (account, t, recipient) => ({ editTransactionRecipient: (account, t, recipient) => {
...t, const parts = recipient.split('?')
recipient, const params = new URLSearchParams(parts[1])
}), recipient = parts[0]
// Extract parameters we may need
for (const [key, value] of params.entries()) {
switch (key) {
case 'dt':
t.tag = parseInt(value, 10) || 0
break
case 'amount':
t.amount = parseAPIValue(value || '0')
break
default:
// do nothing
}
}
return {
...t,
recipient,
}
},
EditFees, EditFees,
@ -474,9 +494,7 @@ const RippleJSBridge: WalletBridge<Transaction> = {
getTransactionRecipient: (a, t) => t.recipient, getTransactionRecipient: (a, t) => t.recipient,
isValidTransaction: (a, t) => (!t.amount.isZero() && t.recipient && true) || false, checkValidTransaction: async (a, t) => {
checkCanBeSpent: async (a, t) => {
const r = await getServerInfo(a.endpointConfig) const r = await getServerInfo(a.endpointConfig)
if ( if (
t.amount t.amount
@ -484,7 +502,7 @@ const RippleJSBridge: WalletBridge<Transaction> = {
.plus(parseAPIValue(r.validatedLedger.reserveBaseXRP)) .plus(parseAPIValue(r.validatedLedger.reserveBaseXRP))
.isLessThanOrEqualTo(a.balance) .isLessThanOrEqualTo(a.balance)
) { ) {
return return true
} }
throw new NotEnoughBalance() throw new NotEnoughBalance()
}, },

4
src/bridge/UnsupportedBridge.js

@ -27,13 +27,11 @@ const UnsupportedBridge: WalletBridge<*> = {
getTransactionAmount: () => BigNumber(0), getTransactionAmount: () => BigNumber(0),
isValidTransaction: () => false,
editTransactionRecipient: () => null, editTransactionRecipient: () => null,
getTransactionRecipient: () => '', getTransactionRecipient: () => '',
checkCanBeSpent: () => Promise.resolve(), checkValidTransaction: () => Promise.resolve(false),
getTotalSpent: () => Promise.resolve(BigNumber(0)), getTotalSpent: () => Promise.resolve(BigNumber(0)),

8
src/bridge/makeMockBridge.js

@ -18,7 +18,7 @@ const defaultOpts = {
scanAccountDeviceSuccessRate: 0.8, scanAccountDeviceSuccessRate: 0.8,
transactionsSizeTarget: 100, transactionsSizeTarget: 100,
extraInitialTransactionProps: () => null, extraInitialTransactionProps: () => null,
checkCanBeSpent: () => Promise.resolve(), checkValidTransaction: () => Promise.resolve(),
getTotalSpent: (a, t) => Promise.resolve(t.amount), getTotalSpent: (a, t) => Promise.resolve(t.amount),
getMaxAmount: a => Promise.resolve(a.balance), getMaxAmount: a => Promise.resolve(a.balance),
} }
@ -36,7 +36,7 @@ function makeMockBridge(opts?: Opts): WalletBridge<*> {
extraInitialTransactionProps, extraInitialTransactionProps,
getTotalSpent, getTotalSpent,
getMaxAmount, getMaxAmount,
checkCanBeSpent, checkValidTransaction,
} = { } = {
...defaultOpts, ...defaultOpts,
...opts, ...opts,
@ -155,9 +155,7 @@ function makeMockBridge(opts?: Opts): WalletBridge<*> {
EditAdvancedOptions, EditAdvancedOptions,
isValidTransaction: (a, t) => (t.amount > 0 && t.recipient && true) || false, checkValidTransaction,
checkCanBeSpent,
getTotalSpent, getTotalSpent,

7
src/bridge/types.js

@ -76,15 +76,16 @@ export interface WalletBridge<Transaction> {
getTransactionRecipient(account: Account, transaction: Transaction): string; getTransactionRecipient(account: Account, transaction: Transaction): string;
isValidTransaction(account: Account, transaction: Transaction): boolean;
// render the whole Fees section of the form // render the whole Fees section of the form
EditFees?: *; // React$ComponentType<EditProps<Transaction>>; EditFees?: *; // React$ComponentType<EditProps<Transaction>>;
// render the whole advanced part of the form // render the whole advanced part of the form
EditAdvancedOptions?: *; // React$ComponentType<EditProps<Transaction>>; EditAdvancedOptions?: *; // React$ComponentType<EditProps<Transaction>>;
checkCanBeSpent(account: Account, transaction: Transaction): Promise<void>; // validate the transaction and all currency specific validations here, we can return false
// to disable the button without throwing an error if we are handling the error on a different
// input or throw an error that will highlight the issue on the amount field
checkValidTransaction(account: Account, transaction: Transaction): Promise<boolean>;
getTotalSpent(account: Account, transaction: Transaction): Promise<BigNumber>; getTotalSpent(account: Account, transaction: Transaction): Promise<BigNumber>;

2
src/commands/index.js

@ -15,6 +15,7 @@ import installFinalFirmware from 'commands/installFinalFirmware'
import installMcu from 'commands/installMcu' import installMcu from 'commands/installMcu'
import installOsuFirmware from 'commands/installOsuFirmware' import installOsuFirmware from 'commands/installOsuFirmware'
import isDashboardOpen from 'commands/isDashboardOpen' import isDashboardOpen from 'commands/isDashboardOpen'
import killInternalProcess from 'commands/killInternalProcess'
import libcoreGetFees from 'commands/libcoreGetFees' import libcoreGetFees from 'commands/libcoreGetFees'
import libcoreGetVersion from 'commands/libcoreGetVersion' import libcoreGetVersion from 'commands/libcoreGetVersion'
import libcoreScanAccounts from 'commands/libcoreScanAccounts' import libcoreScanAccounts from 'commands/libcoreScanAccounts'
@ -47,6 +48,7 @@ const all: Array<Command<any, any>> = [
installMcu, installMcu,
installOsuFirmware, installOsuFirmware,
isDashboardOpen, isDashboardOpen,
killInternalProcess,
libcoreGetFees, libcoreGetFees,
libcoreGetVersion, libcoreGetVersion,
libcoreScanAccounts, libcoreScanAccounts,

18
src/commands/killInternalProcess.js

@ -0,0 +1,18 @@
// @flow
import { createCommand, Command } from 'helpers/ipc'
import { of } from 'rxjs'
type Input = void
type Result = boolean
const cmd: Command<Input, Result> = createCommand('killInternalProcess', () => {
setTimeout(() => {
// we assume commands are run on the internal process
// special exit code for better identification
process.exit(42)
})
return of(true)
})
export default cmd

19
src/components/AdvancedOptions/RippleKind.js

@ -6,7 +6,6 @@ import { translate } from 'react-i18next'
import Box from 'components/base/Box' import Box from 'components/base/Box'
import Input from 'components/base/Input' import Input from 'components/base/Input'
import Label from 'components/base/Label' import Label from 'components/base/Label'
import Spoiler from 'components/base/Spoiler'
type Props = { type Props = {
tag: ?number, tag: ?number,
@ -31,18 +30,14 @@ class RippleKind extends Component<Props> {
render() { render() {
const { tag, t } = this.props const { tag, t } = this.props
return ( return (
<Spoiler title={t('app:send.steps.amount.advancedOptions')}> <Box vertical flow={5}>
<Box horizontal align="center" flow={5}> <Box grow>
<Box style={{ width: 200 }}> <Label>
<Label> <span>{t('app:send.steps.amount.rippleTag')}</span>
<span>{t('app:send.steps.amount.rippleTag')}</span> </Label>
</Label> <Input value={String(tag || '')} onChange={this.onChange} />
</Box>
<Box grow>
<Input value={String(tag || '')} onChange={this.onChange} />
</Box>
</Box> </Box>
</Spoiler> </Box>
) )
} }
} }

5
src/components/ExchangePage/index.js

@ -2,6 +2,7 @@
import React, { PureComponent } from 'react' import React, { PureComponent } from 'react'
import { translate } from 'react-i18next' import { translate } from 'react-i18next'
import shuffle from 'lodash/shuffle'
import type { T } from 'types/common' import type { T } from 'types/common'
import { urls } from 'config/urls' import { urls } from 'config/urls'
@ -21,7 +22,7 @@ type Props = {
t: T, t: T,
} }
const cards = [ const cards = shuffle([
{ {
key: 'coinhouse', key: 'coinhouse',
id: 'coinhouse', id: 'coinhouse',
@ -70,7 +71,7 @@ const cards = [
url: urls.genesis, url: urls.genesis,
logo: <img src={i('logos/exchanges/genesis.svg')} alt="Genesis" width={150} />, logo: <img src={i('logos/exchanges/genesis.svg')} alt="Genesis" width={150} />,
}, },
] ])
class ExchangePage extends PureComponent<Props> { class ExchangePage extends PureComponent<Props> {
render() { render() {

62
src/components/ManagerPage/AppsList.js

@ -7,15 +7,13 @@ import { translate } from 'react-i18next'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import { compose } from 'redux' import { compose } from 'redux'
import type { Device, T } from 'types/common' import type { Device, T } from 'types/common'
import type { Application, ApplicationVersion, DeviceInfo } from 'helpers/types' import type { ApplicationVersion, DeviceInfo } from 'helpers/types'
import { getFullListSortedCryptoCurrencies } from 'helpers/countervalues'
import { developerModeSelector } from 'reducers/settings' import { developerModeSelector } from 'reducers/settings'
import listApps from 'commands/listApps' import listApps from 'commands/listApps'
import listAppVersions from 'commands/listAppVersions' import listAppVersions from 'commands/listAppVersions'
import installApp from 'commands/installApp' import installApp from 'commands/installApp'
import uninstallApp from 'commands/uninstallApp' import uninstallApp from 'commands/uninstallApp'
import Box from 'components/base/Box' import Box from 'components/base/Box'
import Space from 'components/base/Space' import Space from 'components/base/Space'
import Modal, { ModalBody, ModalFooter, ModalTitle, ModalContent } from 'components/base/Modal' import Modal, { ModalBody, ModalFooter, ModalTitle, ModalContent } from 'components/base/Modal'
@ -26,13 +24,11 @@ import Spinner from 'components/base/Spinner'
import Button from 'components/base/Button' import Button from 'components/base/Button'
import TranslatedError from 'components/TranslatedError' import TranslatedError from 'components/TranslatedError'
import TrackPage from 'analytics/TrackPage' import TrackPage from 'analytics/TrackPage'
import IconInfoCircle from 'icons/InfoCircle' import IconInfoCircle from 'icons/InfoCircle'
import ExclamationCircleThin from 'icons/ExclamationCircleThin' import ExclamationCircleThin from 'icons/ExclamationCircleThin'
import Update from 'icons/Update' import Update from 'icons/Update'
import Trash from 'icons/Trash' import Trash from 'icons/Trash'
import CheckCircle from 'icons/CheckCircle' import CheckCircle from 'icons/CheckCircle'
import { FreezeDeviceChangeEvents } from './HookDeviceChange' import { FreezeDeviceChangeEvents } from './HookDeviceChange'
import ManagerApp, { Container as FakeManagerAppContainer } from './ManagerApp' import ManagerApp, { Container as FakeManagerAppContainer } from './ManagerApp'
import AppSearchBar from './AppSearchBar' import AppSearchBar from './AppSearchBar'
@ -102,29 +98,53 @@ class AppsList extends PureComponent<Props, State> {
_unmounted = false _unmounted = false
filterAppVersions = (applicationsList, compatibleAppVersionsList) => { prepareAppList = ({ applicationsList, compatibleAppVersionsList, sortedCryptoCurrencies }) => {
if (!this.props.isDevMode) { const filtered = this.props.isDevMode
return compatibleAppVersionsList.filter(version => { ? compatibleAppVersionsList.slice(0)
const app = applicationsList.find(e => e.id === version.app) : compatibleAppVersionsList.filter(version => {
if (app) { const app = applicationsList.find(e => e.id === version.app)
return app.category !== 2 if (app) {
} return app.category !== 2
}
return false return false
}) })
}
return compatibleAppVersionsList const sortedCryptoApps = []
// sort by crypto first
sortedCryptoCurrencies.forEach(crypto => {
const app = filtered.find(
item => item.name.toLowerCase() === crypto.managerAppName.toLowerCase(),
)
if (app) {
filtered.splice(filtered.indexOf(app), 1)
sortedCryptoApps.push(app)
}
})
return sortedCryptoApps.concat(filtered)
} }
async fetchAppList() { async fetchAppList() {
try { try {
const { deviceInfo } = this.props const { deviceInfo } = this.props
const applicationsList: Array<Application> = await listApps.send().toPromise()
const compatibleAppVersionsList = await listAppVersions.send(deviceInfo).toPromise() const [
const filteredAppVersionsList = this.filterAppVersions(
applicationsList, applicationsList,
compatibleAppVersionsList, compatibleAppVersionsList,
) sortedCryptoCurrencies,
] = await Promise.all([
listApps.send().toPromise(),
listAppVersions.send(deviceInfo).toPromise(),
getFullListSortedCryptoCurrencies(),
])
const filteredAppVersionsList = this.prepareAppList({
applicationsList,
compatibleAppVersionsList,
sortedCryptoCurrencies,
})
if (!this._unmounted) { if (!this._unmounted) {
this.setState({ this.setState({

7
src/components/ManagerPage/index.js

@ -4,12 +4,11 @@ import React, { PureComponent, Fragment } from 'react'
import invariant from 'invariant' import invariant from 'invariant'
import { openURL } from 'helpers/linking' import { openURL } from 'helpers/linking'
import { urls } from 'config/urls' import { urls } from 'config/urls'
import type { Device } from 'types/common' import type { Device } from 'types/common'
import type { DeviceInfo } from 'helpers/types' import type { DeviceInfo } from 'helpers/types'
import { getFullListSortedCryptoCurrencies } from 'helpers/countervalues'
import Dashboard from './Dashboard' import Dashboard from './Dashboard'
import ManagerGenuineCheck from './ManagerGenuineCheck' import ManagerGenuineCheck from './ManagerGenuineCheck'
import HookDeviceChange from './HookDeviceChange' import HookDeviceChange from './HookDeviceChange'
@ -30,6 +29,10 @@ const INITIAL_STATE = {
class ManagerPage extends PureComponent<Props, State> { class ManagerPage extends PureComponent<Props, State> {
state = INITIAL_STATE state = INITIAL_STATE
componentDidMount() {
getFullListSortedCryptoCurrencies() // start fetching the crypto currencies ordering
}
// prettier-ignore // prettier-ignore
handleSuccessGenuine = ({ device, deviceInfo }: { device: Device, deviceInfo: DeviceInfo }) => { // eslint-disable-line react/no-unused-prop-types handleSuccessGenuine = ({ device, deviceInfo }: { device: Device, deviceInfo: DeviceInfo }) => { // eslint-disable-line react/no-unused-prop-types
this.setState({ isGenuine: true, device, deviceInfo }) this.setState({ isGenuine: true, device, deviceInfo })

2
src/components/Onboarding/steps/Analytics.js

@ -26,7 +26,7 @@ type State = {
} }
const INITIAL_STATE = { const INITIAL_STATE = {
analyticsToggle: false, analyticsToggle: true,
sentryLogsToggle: true, sentryLogsToggle: true,
} }

8
src/components/RequestAmount/index.js

@ -48,7 +48,7 @@ type OwnProps = {
// left value (always the one which is returned) // left value (always the one which is returned)
value: BigNumber, value: BigNumber,
canBeSpentError: ?Error, validTransactionError: ?Error,
// max left value // max left value
max: BigNumber, max: BigNumber,
@ -113,7 +113,7 @@ const mapStateToProps = (state: State, props: OwnProps) => {
export class RequestAmount extends PureComponent<Props> { export class RequestAmount extends PureComponent<Props> {
static defaultProps = { static defaultProps = {
max: BigNumber(Infinity), max: BigNumber(Infinity),
canBeSpent: true, validTransaction: true,
withMax: true, withMax: true,
} }
@ -139,14 +139,14 @@ export class RequestAmount extends PureComponent<Props> {
renderInputs(containerProps: Object) { renderInputs(containerProps: Object) {
// TODO move this inlined into render() for less spaghetti // TODO move this inlined into render() for less spaghetti
const { value, account, rightCurrency, getCounterValue, canBeSpentError } = this.props const { value, account, rightCurrency, getCounterValue, validTransactionError } = this.props
const right = getCounterValue(value) || BigNumber(0) const right = getCounterValue(value) || BigNumber(0)
const rightUnit = rightCurrency.units[0] const rightUnit = rightCurrency.units[0]
// FIXME: no way InputCurrency pure can work here. inlined InputRight (should be static func?), inline containerProps object.. // FIXME: no way InputCurrency pure can work here. inlined InputRight (should be static func?), inline containerProps object..
return ( return (
<Box horizontal grow shrink> <Box horizontal grow shrink>
<InputCurrency <InputCurrency
error={canBeSpentError} error={validTransactionError}
containerProps={containerProps} containerProps={containerProps}
defaultUnit={account.unit} defaultUnit={account.unit}
value={value} value={value}

1
src/components/base/Modal/ConfirmModal.js

@ -79,6 +79,7 @@ class ConfirmModal extends PureComponent<Props> {
primary={!isDanger} primary={!isDanger}
danger={isDanger} danger={isDanger}
isLoading={isLoading} isLoading={isLoading}
disabled={isLoading}
> >
{realConfirmText} {realConfirmText}
</Button> </Button>

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 Label from 'components/base/Label'
import RequestAmount from 'components/RequestAmount' import RequestAmount from 'components/RequestAmount'
class AmountField extends Component<*, { canBeSpentError: ?Error }> { class AmountField extends Component<*, { validTransactionError: ?Error }> {
state = { state = {
canBeSpentError: null, validTransactionError: null,
} }
componentDidMount() { componentDidMount() {
this.resync() this.resync()
@ -27,11 +27,11 @@ class AmountField extends Component<*, { canBeSpentError: ?Error }> {
const { account, bridge, transaction } = this.props const { account, bridge, transaction } = this.props
const syncId = ++this.syncId const syncId = ++this.syncId
try { try {
await bridge.checkCanBeSpent(account, transaction) await bridge.checkValidTransaction(account, transaction)
if (this.syncId !== syncId) return if (this.syncId !== syncId) return
this.setState({ canBeSpentError: null }) this.setState({ validTransactionError: null })
} catch (canBeSpentError) { } catch (validTransactionError) {
this.setState({ canBeSpentError }) this.setState({ validTransactionError })
} }
} }
@ -42,14 +42,14 @@ class AmountField extends Component<*, { canBeSpentError: ?Error }> {
render() { render() {
const { bridge, account, transaction, t } = this.props const { bridge, account, transaction, t } = this.props
const { canBeSpentError } = this.state const { validTransactionError } = this.state
return ( return (
<Box flow={1}> <Box flow={1}>
<Label>{t('app:send.steps.amount.amount')}</Label> <Label>{t('app:send.steps.amount.amount')}</Label>
<RequestAmount <RequestAmount
withMax={false} withMax={false}
account={account} account={account}
canBeSpentError={canBeSpentError} validTransactionError={validTransactionError}
onChange={this.onChange} onChange={this.onChange}
value={bridge.getTransactionAmount(account, transaction)} value={bridge.getTransactionAmount(account, transaction)}
/> />

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

@ -134,11 +134,13 @@ export class StepAmountFooter extends PureComponent<
bridge.getTransactionRecipient(account, transaction), bridge.getTransactionRecipient(account, transaction),
) )
if (syncId !== this.syncId) return if (syncId !== this.syncId) return
const canBeSpent = await bridge const isValidTransaction = await bridge
.checkCanBeSpent(account, transaction) .checkValidTransaction(account, transaction)
.then(() => true, () => false) .then(result => result, () => false)
if (syncId !== this.syncId) return if (syncId !== this.syncId) return
const canNext = isRecipientValid && canBeSpent && totalSpent.gt(0) const canNext =
!transaction.amount.isZero() && isRecipientValid && isValidTransaction && totalSpent.gt(0)
this.setState({ totalSpent, canNext, isSyncing: false }) this.setState({ totalSpent, canNext, isSyncing: false })
} catch (err) { } catch (err) {
logger.critical(err) logger.critical(err)

29
src/helpers/countervalues.js

@ -12,6 +12,8 @@ import {
intermediaryCurrency, intermediaryCurrency,
} from 'reducers/settings' } from 'reducers/settings'
import logger from 'logger' import logger from 'logger'
import { listCryptoCurrencies } from '@ledgerhq/live-common/lib/helpers/currencies'
import type { CryptoCurrency } from '@ledgerhq/live-common/lib/types'
const pairsSelector = createSelector( const pairsSelector = createSelector(
currenciesSelector, currenciesSelector,
@ -52,6 +54,7 @@ const addExtraPollingHooks = (schedulePoll, cancelPoll) => {
} }
} }
// TODO we should be able to pass-in our network() function
const CounterValues = createCounterValues({ const CounterValues = createCounterValues({
log: (...args) => logger.log('CounterValues:', ...args), log: (...args) => logger.log('CounterValues:', ...args),
getAPIBaseURL: () => LEDGER_COUNTERVALUES_API, getAPIBaseURL: () => LEDGER_COUNTERVALUES_API,
@ -61,4 +64,30 @@ const CounterValues = createCounterValues({
addExtraPollingHooks, addExtraPollingHooks,
}) })
let sortCache
export const getFullListSortedCryptoCurrencies: () => Promise<CryptoCurrency[]> = () => {
if (!sortCache) {
sortCache = CounterValues.fetchTickersByMarketcap().then(
tickers => {
const list = listCryptoCurrencies().slice(0)
const prependList = []
tickers.forEach(ticker => {
const item = list.find(c => c.ticker === ticker)
if (item) {
list.splice(list.indexOf(item), 1)
prependList.push(item)
}
})
return prependList.concat(list)
},
() => {
sortCache = null // reset the cache for the next time it comes here to "try again"
return listCryptoCurrencies() // fallback on default sort
},
)
}
return sortCache
}
export default CounterValues export default CounterValues

4
src/helpers/reset.js

@ -6,8 +6,10 @@ import resolveUserDataDirectory from 'helpers/resolveUserDataDirectory'
import { disable as disableDBMiddleware } from 'middlewares/db' import { disable as disableDBMiddleware } from 'middlewares/db'
import db from 'helpers/db' import db from 'helpers/db'
import { delay } from 'helpers/promise' import { delay } from 'helpers/promise'
import killInternalProcess from 'commands/killInternalProcess'
function resetLibcoreDatabase() { async function resetLibcoreDatabase() {
await killInternalProcess.send().toPromise()
const dbpath = path.resolve(resolveUserDataDirectory(), 'sqlite/') const dbpath = path.resolve(resolveUserDataDirectory(), 'sqlite/')
rimraf.sync(dbpath, { glob: false }) rimraf.sync(dbpath, { glob: false })
} }

2
src/reducers/settings.js

@ -70,7 +70,7 @@ const INITIAL_STATE: SettingsState = {
currenciesSettings: {}, currenciesSettings: {},
developerMode: !!process.env.__DEV__, developerMode: !!process.env.__DEV__,
loaded: false, loaded: false,
shareAnalytics: false, shareAnalytics: true,
sentryLogs: true, sentryLogs: true,
lastUsedVersion: __APP_VERSION__, lastUsedVersion: __APP_VERSION__,
} }

2
static/i18n/en/app.json

@ -167,7 +167,7 @@
"coinmama": "Coinmama is a financial service that makes it fast, safe and fun to buy digital assets, anywhere in the world.", "coinmama": "Coinmama is a financial service that makes it fast, safe and fun to buy digital assets, anywhere in the world.",
"simplex": "Simplex is a EU licensed financial institution, providing a fraudless credit card payment solution.", "simplex": "Simplex is a EU licensed financial institution, providing a fraudless credit card payment solution.",
"paybis": "it is safe and easy to Buy Bitcoin with credit card from PayBis. Service operates in US, Canada, Germany, Russia and Saudi Arabia.", "paybis": "it is safe and easy to Buy Bitcoin with credit card from PayBis. Service operates in US, Canada, Germany, Russia and Saudi Arabia.",
"luno": "Luno makes it safe and easy to buy, store and learn about digital currencies like Bitcoin and Ethreum", "luno": "Luno makes it safe and easy to buy, store and learn about cryptocurrencies like Bitcoin and Ethereum",
"shapeshift": "ShapeShift is an online marketplace where users can buy and sell digital assets. It is a fast and secure way for the world to buy and sell digital assets, with no lengthy signup process, no counterparty risk, and no friction.", "shapeshift": "ShapeShift is an online marketplace where users can buy and sell digital assets. It is a fast and secure way for the world to buy and sell digital assets, with no lengthy signup process, no counterparty risk, and no friction.",
"genesis": "Genesis is an institutional trading firm offering liquidity and borrow for digital currencies, including bitcoin, bitcoin cash, ethereum, ethereum classic, litecoin, and XRP." "genesis": "Genesis is an institutional trading firm offering liquidity and borrow for digital currencies, including bitcoin, bitcoin cash, ethereum, ethereum classic, litecoin, and XRP."
}, },

32
test-e2e/password-lock-check.spec.js

@ -2,30 +2,32 @@ import { Application } from 'spectron'
import { waitForDisappear, waitForExpectedText } from './helpers' import { waitForDisappear, waitForExpectedText } from './helpers'
const os = require('os') const os = require('os')
const path = require('path')
const fs = require('fs')
const appVersion = require('../package.json') const appVersion = require('../package.json')
let app let app
const TIMEOUT = 50 * 1000 const TIMEOUT = 50 * 1000
let app_path let appPath
let configPath let configPath
const platform = os.platform() const platform = os.platform()
if (platform === 'darwin') { if (platform === 'darwin') {
app_path = `./dist/mac/Ledger Live.app/Contents/MacOS/Ledger Live` appPath = `./dist/mac/Ledger Live.app/Contents/MacOS/Ledger Live`
configPath = `~/Library/Application Support/Ledger Live/` configPath = `${os.homedir()}/Library/Application Support/Ledger Live/`
} else if (platform === 'win32') { } else if (platform === 'win32') {
app_path = `.\\dist\\win-unpacked\\Ledger Live.exe` appPath = `.\\dist\\win-unpacked\\Ledger Live.exe`
configPath = '%AppData\\Roaming\\Ledger Live' configPath = '%AppData\\Roaming\\Ledger Live'
} else { } else {
app_path = `./dist/ledger-live-desktop-${appVersion.version}-linux-x86_64.AppImage` appPath = `./dist/ledger-live-desktop-${appVersion.version}-linux-x86_64.AppImage`
configPath = '$HOME/apps/ledger-live-desktop-$ledgerLiveVersion-linux-x86_64.AppImage' configPath = '$HOME/apps/ledger-live-desktop-$ledgerLiveVersion-linux-x86_64.AppImage'
} }
describe('Application launch', () => { describe('Application launch', () => {
beforeEach(async () => { beforeEach(async () => {
app = new Application({ app = new Application({
path: app_path, path: appPath,
env: { env: {
SKIP_ONBOARDING: '1', SKIP_ONBOARDING: '1',
}, },
@ -49,7 +51,7 @@ describe('Application launch', () => {
// Verify Account summary text // Verify Account summary text
// Count user's accounts // Count user's accounts
const userAccountsList = await app.client.elements('[data-e2e=dashboard_AccountCardWrapper]') const userAccountsList = await app.client.elements('[data-e2e=dashboard_AccountCardWrapper]')
const userAccountsCount = await Object.keys(userAccountsList.value).length const userAccountsCount = Object.keys(userAccountsList.value).length
// Check account number // Check account number
const accountSummary = await app.client.getText('[data-e2e=dashboard_accountsSummaryDesc]') const accountSummary = await app.client.getText('[data-e2e=dashboard_accountsSummaryDesc]')
const accountSummaryMessage = `Here's the summary of your ${userAccountsCount} accounts` const accountSummaryMessage = `Here's the summary of your ${userAccountsCount} accounts`
@ -66,18 +68,24 @@ describe('Application launch', () => {
await app.client.setValue('[data-e2e=setPassword_ConfirmPassword]', 5) await app.client.setValue('[data-e2e=setPassword_ConfirmPassword]', 5)
await app.client.keys('Enter') await app.client.keys('Enter')
await waitForExpectedText(app, '[data-e2e=settings_title]', 'Settings') await waitForExpectedText(app, '[data-e2e=settings_title]', 'Settings')
await app.client.pause(2000)
// Verify in app.json that accounts data are encrypted // Verify in app.json that accounts data are encrypted
const tmpAppJSONPath = `${configPath} + "app.json"` const tmpAppJSONPath = path.resolve(configPath, 'app.json')
const accountsOperations = '"operations": [{' const LockedfileContent = fs.readFileSync(tmpAppJSONPath, 'utf-8')
expect(tmpAppJSONPath).not.toContain(accountsOperations) const accountsOperations = '"operations":[{'
await expect(LockedfileContent).not.toContain(accountsOperations)
// Desable password lock // Disable password lock
await app.client.click('[data-e2e=passwordLock_button]') await app.client.click('[data-e2e=passwordLock_button]')
await waitForExpectedText(app, '[data-e2e=modal_title]', 'Disable password lock') await waitForExpectedText(app, '[data-e2e=modal_title]', 'Disable password lock')
await app.client.setValue('#password', 5) await app.client.setValue('#password', 5)
await app.client.pause(500)
await app.client.keys('Enter') await app.client.keys('Enter')
await waitForExpectedText(app, '[data-e2e=settings_title]', 'Settings') await waitForExpectedText(app, '[data-e2e=settings_title]', 'Settings')
await app.client.pause(3000)
const UnlockedfileContent = fs.readFileSync(tmpAppJSONPath, 'utf-8')
// Verify in app.json that accounts data are not encrypted
await expect(UnlockedfileContent).toContain(accountsOperations)
await app.client.pause(1000) await app.client.pause(1000)
}, },
TIMEOUT, TIMEOUT,

6
yarn.lock

@ -1549,9 +1549,9 @@
npm "^5.7.1" npm "^5.7.1"
prebuild-install "^2.2.2" prebuild-install "^2.2.2"
"@ledgerhq/live-common@^3.5.1": "@ledgerhq/live-common@^3.7.1":
version "3.5.1" version "3.7.1"
resolved "https://registry.yarnpkg.com/@ledgerhq/live-common/-/live-common-3.5.1.tgz#dab3eb061f361999a9e04ef564808831faac61ea" resolved "https://registry.yarnpkg.com/@ledgerhq/live-common/-/live-common-3.7.1.tgz#5ce1895920d2eae6c454c2c72612dc9afd11adec"
dependencies: dependencies:
axios "^0.18.0" axios "^0.18.0"
bignumber.js "^7.2.1" bignumber.js "^7.2.1"

Loading…
Cancel
Save