Browse Source

Merge branch 'develop' into usb-detect

master
Gaëtan Renaudeau 7 years ago
parent
commit
298ce15f33
  1. 1
      README.md
  2. 103
      src/components/DashboardPage/AccountCard.js
  3. 33
      src/components/DashboardPage/AccountCard/Header.js
  4. 94
      src/components/DashboardPage/AccountCard/index.js
  5. 66
      src/components/DashboardPage/AccountCardList.js
  6. 33
      src/components/DashboardPage/AccountCardListHeader.js
  7. 64
      src/components/DashboardPage/AccountCardPlaceholder.js
  8. 31
      src/components/DashboardPage/CurrentGreetings.js
  9. 22
      src/components/DashboardPage/SummaryDesc.js
  10. 186
      src/components/DashboardPage/index.js
  11. 26
      src/components/base/Button/index.js
  12. 3
      src/config/constants.js
  13. 1
      src/config/errors.js
  14. 62
      src/helpers/libcore.js
  15. 9
      src/helpers/linking.js
  16. 18
      src/helpers/promise.js
  17. 6
      src/renderer/init.js
  18. 6
      static/i18n/en/app.json
  19. 4
      static/i18n/en/errors.json

1
README.md

@ -80,7 +80,6 @@ DEBUG_ACTION=1
DEBUG_TAB_KEY=1
DEBUG_LIBCORE=1
DEBUG_WS=1
LEDGER_RESET_ALL=1
LEDGER_DEBUG_ALL_LANGS=1
SKIP_GENUINE=1
SKIP_ONBOARDING=1

103
src/components/DashboardPage/AccountCard.js

@ -1,103 +0,0 @@
// @flow
import React, { PureComponent } from 'react'
import styled from 'styled-components'
import type { Account, Currency } from '@ledgerhq/live-common/lib/types'
import Chart from 'components/base/Chart'
import Bar from 'components/base/Bar'
import Box, { Card } from 'components/base/Box'
import CalculateBalance from 'components/CalculateBalance'
import FormattedVal from 'components/base/FormattedVal'
import Ellipsis from 'components/base/Ellipsis'
import CryptoCurrencyIcon from 'components/CryptoCurrencyIcon'
import DeltaChange from '../DeltaChange'
const Wrapper = styled(Card).attrs({
p: 4,
flex: 1,
})`
cursor: ${p => (p.onClick ? 'pointer' : 'default')};
`
class AccountCard extends PureComponent<{
counterValue: Currency,
account: Account,
onClick?: Account => void,
daysCount: number,
}> {
render() {
const { counterValue, account, onClick, daysCount, ...props } = this.props
return (
<Wrapper onClick={onClick ? () => onClick(account) : null} {...props}>
<Box flow={4}>
<Box horizontal ff="Open Sans|SemiBold" flow={3} alignItems="center">
<Box
alignItems="center"
justifyContent="center"
style={{ color: account.currency.color }}
>
<CryptoCurrencyIcon currency={account.currency} size={20} />
</Box>
<Box grow>
<Box style={{ textTransform: 'uppercase' }} fontSize={0} color="graphite">
{account.currency.name}
</Box>
<Ellipsis fontSize={4} color="dark">
{account.name}
</Ellipsis>
</Box>
</Box>
<Bar size={1} color="fog" />
<Box justifyContent="center">
<FormattedVal
alwaysShowSign={false}
color="dark"
unit={account.unit}
showCode
val={account.balance}
/>
</Box>
</Box>
<CalculateBalance counterValue={counterValue} accounts={[account]} daysCount={daysCount}>
{({ isAvailable, balanceHistory, balanceStart, balanceEnd }) => (
<Box flow={4}>
<Box flow={2} horizontal>
<Box justifyContent="center">
{isAvailable ? (
<FormattedVal
animateTicker
unit={counterValue.units[0]}
val={balanceEnd}
alwaysShowSign={false}
showCode
fontSize={3}
color="graphite"
/>
) : null}
</Box>
<Box grow justifyContent="center">
{isAvailable && !balanceStart.isZero() ? (
<DeltaChange from={balanceStart} to={balanceEnd} alwaysShowSign fontSize={3} />
) : null}
</Box>
</Box>
<Chart
data={balanceHistory}
color={account.currency.color}
height={52}
hideAxis
isInteractive={false}
id={`account-chart-${account.id}`}
unit={account.unit}
/>
</Box>
)}
</CalculateBalance>
</Wrapper>
)
}
}
export default AccountCard

33
src/components/DashboardPage/AccountCard/Header.js

@ -0,0 +1,33 @@
// @flow
import React, { PureComponent } from 'react'
import type { CryptoCurrency } from '@ledgerhq/live-common/lib/types'
import Box from 'components/base/Box'
import Ellipsis from 'components/base/Ellipsis'
import CryptoCurrencyIcon from 'components/CryptoCurrencyIcon'
class AccountCardHeader extends PureComponent<{
currency: CryptoCurrency,
accountName: string,
}> {
render() {
const { currency, accountName } = this.props
return (
<Box horizontal ff="Open Sans|SemiBold" flow={3} alignItems="center">
<Box alignItems="center" justifyContent="center" style={{ color: currency.color }}>
<CryptoCurrencyIcon currency={currency} size={20} />
</Box>
<Box grow>
<Box style={{ textTransform: 'uppercase' }} fontSize={0} color="graphite">
{currency.name}
</Box>
<Ellipsis fontSize={4} color="dark">
{accountName}
</Ellipsis>
</Box>
</Box>
)
}
}
export default AccountCardHeader

94
src/components/DashboardPage/AccountCard/index.js

@ -0,0 +1,94 @@
// @flow
import React, { PureComponent } from 'react'
import styled from 'styled-components'
import type { Account, Currency } from '@ledgerhq/live-common/lib/types'
import Chart from 'components/base/Chart'
import Bar from 'components/base/Bar'
import Box, { Card } from 'components/base/Box'
import CalculateBalance from 'components/CalculateBalance'
import FormattedVal from 'components/base/FormattedVal'
import DeltaChange from 'components/DeltaChange'
import AccountCardHeader from './Header'
const Wrapper = styled(Card).attrs({
p: 4,
flex: 1,
})`
cursor: ${p => (p.onClick ? 'pointer' : 'default')};
`
class AccountCard extends PureComponent<{
counterValue: Currency,
account: Account,
onClick: Account => void,
daysCount: number,
}> {
renderBody = ({ isAvailable, balanceHistory, balanceStart, balanceEnd }: *) => {
const { counterValue, account } = this.props
return (
<Box flow={4}>
<Box flow={2} horizontal>
<Box justifyContent="center">
{isAvailable ? (
<FormattedVal
animateTicker
unit={counterValue.units[0]}
val={balanceEnd}
alwaysShowSign={false}
showCode
fontSize={3}
color="graphite"
/>
) : null}
</Box>
<Box grow justifyContent="center">
{isAvailable && !balanceStart.isZero() ? (
<DeltaChange from={balanceStart} to={balanceEnd} alwaysShowSign fontSize={3} />
) : null}
</Box>
</Box>
<Chart
data={balanceHistory}
color={account.currency.color}
height={52}
hideAxis
isInteractive={false}
id={`account-chart-${account.id}`}
unit={account.unit}
/>
</Box>
)
}
onClick = () => {
const { account, onClick } = this.props
onClick(account)
}
render() {
const { counterValue, account, onClick, daysCount, ...props } = this.props
return (
<Wrapper onClick={this.onClick} {...props}>
<Box flow={4}>
<AccountCardHeader accountName={account.name} currency={account.currency} />
<Bar size={1} color="fog" />
<Box justifyContent="center">
<FormattedVal
alwaysShowSign={false}
color="dark"
unit={account.unit}
showCode
val={account.balance}
/>
</Box>
</Box>
<CalculateBalance counterValue={counterValue} accounts={[account]} daysCount={daysCount}>
{this.renderBody}
</CalculateBalance>
</Wrapper>
)
}
}
export default AccountCard

66
src/components/DashboardPage/AccountCardList.js

@ -0,0 +1,66 @@
// @flow
import React, { Component } from 'react'
import type { Account, Currency } from '@ledgerhq/live-common/lib/types'
import Box from 'components/base/Box'
import AccountCard from './AccountCard'
import AccountCardListHeader from './AccountCardListHeader'
import AccountCardPlaceholder from './AccountCardPlaceholder'
type Props = {
accounts: Account[],
onAccountClick: Account => void,
counterValue: Currency,
daysCount: number,
}
class AccountCardList extends Component<Props> {
render() {
const { accounts, counterValue, daysCount, onAccountClick } = this.props
return (
<Box flow={4}>
<AccountCardListHeader accountsLength={accounts.length} />
<Box
horizontal
flexWrap="wrap"
justifyContent="flex-start"
alignItems="center"
style={{ margin: '0 -16px' }}
>
{accounts
.map(account => ({
key: account.id,
account,
}))
.concat(
Array(3 - (accounts.length % 3))
.fill(null)
.map((_, i) => ({
key: `placeholder_${i}`,
withPlaceholder: i === 0,
})),
)
.map(item => (
<Box key={item.key} flex="33%" p={16}>
{item.account ? (
<AccountCard
key={item.account.id}
counterValue={counterValue}
account={item.account}
daysCount={daysCount}
onClick={onAccountClick}
/>
) : item.withPlaceholder ? (
<AccountCardPlaceholder />
) : null}
</Box>
))}
</Box>
</Box>
)
}
}
export default AccountCardList

33
src/components/DashboardPage/AccountCardListHeader.js

@ -0,0 +1,33 @@
// @flow
import React, { PureComponent } from 'react'
import { translate } from 'react-i18next'
import type { T } from 'types/common'
import Box from 'components/base/Box'
import Text from 'components/base/Text'
import AccountsOrder from './AccountsOrder'
type Props = {
t: T,
accountsLength: number,
}
class AccountCardListHeader extends PureComponent<Props> {
render() {
const { accountsLength, t } = this.props
return (
<Box horizontal alignItems="flex-end">
<Text color="dark" ff="Museo Sans" fontSize={6}>
{t('app:dashboard.accounts.title', { count: accountsLength })}
</Text>
<Box ml="auto" horizontal flow={1}>
<AccountsOrder />
</Box>
</Box>
)
}
}
export default translate()(AccountCardListHeader)

64
src/components/DashboardPage/AccountCardPlaceholder.js

@ -0,0 +1,64 @@
// @flow
import React, { PureComponent } from 'react'
import { connect } from 'react-redux'
import { translate } from 'react-i18next'
import styled from 'styled-components'
import { openModal } from 'reducers/modals'
import { MODAL_ADD_ACCOUNTS } from 'config/constants'
import type { T } from 'types/common'
import { i } from 'helpers/staticPath'
import Box from 'components/base/Box'
import Button from 'components/base/Button'
const Wrapper = styled(Box).attrs({
p: 4,
flex: 1,
alignItems: 'center',
})`
border: 1px dashed ${p => p.theme.colors.fog};
border-radius: 4px;
height: 215px;
`
class AccountCardPlaceholder extends PureComponent<{
t: T,
openModal: string => void,
}> {
onAddAccounts = () => this.props.openModal(MODAL_ADD_ACCOUNTS)
render() {
const { t } = this.props
return (
<Wrapper>
<Box mt={2}>
<img alt="" src={i('empty-account-tile.svg')} />
</Box>
<Box
ff="Open Sans"
fontSize={3}
color="grey"
pb={2}
mt={3}
textAlign="center"
style={{ maxWidth: 150 }}
>
{t('app:dashboard.emptyAccountTile.desc')}
</Box>
<Button primary onClick={this.onAddAccounts}>
{t('app:dashboard.emptyAccountTile.createAccount')}
</Button>
</Wrapper>
)
}
}
export default translate()(
connect(
null,
{
openModal,
},
)(AccountCardPlaceholder),
)

31
src/components/DashboardPage/CurrentGreetings.js

@ -0,0 +1,31 @@
// @flow
import React, { PureComponent } from 'react'
import { translate } from 'react-i18next'
import type { T } from 'types/common'
import Text from 'components/base/Text'
const getCurrentGreetings = () => {
const localTimeHour = new Date().getHours()
const afternoon_breakpoint = 12
const evening_breakpoint = 17
if (localTimeHour >= afternoon_breakpoint && localTimeHour < evening_breakpoint) {
return 'app:dashboard.greeting.afternoon'
} else if (localTimeHour >= evening_breakpoint) {
return 'app:dashboard.greeting.evening'
}
return 'app:dashboard.greeting.morning'
}
class CurrentGettings extends PureComponent<{ t: T }> {
render() {
const { t } = this.props
return (
<Text color="dark" ff="Museo Sans" fontSize={7}>
{t(getCurrentGreetings())}
</Text>
)
}
}
export default translate()(CurrentGettings)

22
src/components/DashboardPage/SummaryDesc.js

@ -0,0 +1,22 @@
// @flow
import React, { PureComponent } from 'react'
import { translate } from 'react-i18next'
import type { T } from 'types/common'
import Text from 'components/base/Text'
class SummaryDesc extends PureComponent<{
t: T,
totalAccounts: number,
}> {
render() {
const { totalAccounts, t } = this.props
return (
<Text color="grey" fontSize={5} ff="Museo Sans|Light">
{t('app:dashboard.summary', { count: totalAccounts })}
</Text>
)
}
}
export default translate()(SummaryDesc)

186
src/components/DashboardPage/index.js

@ -5,7 +5,6 @@ import uniq from 'lodash/uniq'
import { compose } from 'redux'
import { translate } from 'react-i18next'
import { connect } from 'react-redux'
import styled from 'styled-components'
import { push } from 'react-router-redux'
import { createStructuredSelector } from 'reselect'
import type { Account, Currency } from '@ledgerhq/live-common/lib/types'
@ -14,11 +13,8 @@ import type { T } from 'types/common'
import { colors } from 'styles/theme'
import { accountsSelector } from 'reducers/accounts'
import { openModal } from 'reducers/modals'
import { MODAL_ADD_ACCOUNTS } from 'config/constants'
import {
counterValueCurrencySelector,
localeSelector,
selectedTimeRangeSelector,
timeRangeDaysByKey,
} from 'reducers/settings'
@ -32,27 +28,23 @@ import UpdateNotifier from 'components/UpdateNotifier'
import BalanceInfos from 'components/BalanceSummary/BalanceInfos'
import BalanceSummary from 'components/BalanceSummary'
import Box from 'components/base/Box'
import { i } from 'helpers/staticPath'
import PillsDaysCount from 'components/PillsDaysCount'
import Text from 'components/base/Text'
import OperationsList from 'components/OperationsList'
import StickyBackToTop from 'components/StickyBackToTop'
import Button from 'components/base/Button'
import AccountCard from './AccountCard'
import AccountsOrder from './AccountsOrder'
import EmptyState from './EmptyState'
import CurrentGreetings from './CurrentGreetings'
import SummaryDesc from './SummaryDesc'
import AccountCardList from './AccountCardList'
const mapStateToProps = createStructuredSelector({
accounts: accountsSelector,
counterValue: counterValueCurrencySelector,
locale: localeSelector,
selectedTimeRange: selectedTimeRangeSelector,
})
const mapDispatchToProps = {
push,
saveSettings,
openModal,
}
type Props = {
@ -62,41 +54,33 @@ type Props = {
counterValue: Currency,
selectedTimeRange: TimeRange,
saveSettings: ({ selectedTimeRange: TimeRange }) => *,
openModal: string => void,
}
class DashboardPage extends PureComponent<Props> {
onAccountClick = account => this.props.push(`/account/${account.id}`)
handleGreeting = () => {
const localTimeHour = new Date().getHours()
const afternoon_breakpoint = 12
const evening_breakpoint = 17
if (localTimeHour >= afternoon_breakpoint && localTimeHour < evening_breakpoint) {
return 'app:dashboard.greeting.afternoon'
} else if (localTimeHour >= evening_breakpoint) {
return 'app:dashboard.greeting.evening'
}
return 'app:dashboard.greeting.morning'
}
handleChangeSelectedTime = item => {
this.props.saveSettings({ selectedTimeRange: item.key })
}
_cacheBalance = null
renderHeader = ({ isAvailable, totalBalance, selectedTimeRange, sinceBalance, refBalance }) => (
<BalanceInfos
t={this.props.t}
counterValue={this.props.counterValue}
isAvailable={isAvailable}
totalBalance={totalBalance}
since={selectedTimeRange}
sinceBalance={sinceBalance}
refBalance={refBalance}
/>
)
render() {
const { accounts, t, counterValue, selectedTimeRange, openModal } = this.props
const { accounts, t, counterValue, selectedTimeRange } = this.props
const daysCount = timeRangeDaysByKey[selectedTimeRange]
const timeFrame = this.handleGreeting()
const imagePath = i('empty-account-tile.svg')
const totalAccounts = accounts.length
const totalCurrencies = uniq(accounts.map(a => a.currency.id)).length
const totalOperations = accounts.reduce((sum, a) => sum + a.operations.length, 0)
const displayOperationsHelper = (account: Account) => account.operations.length > 0
const displayOperations = accounts.some(displayOperationsHelper)
return (
<Fragment>
@ -113,12 +97,8 @@ class DashboardPage extends PureComponent<Props> {
<Fragment>
<Box horizontal alignItems="flex-end">
<Box grow>
<Text color="dark" ff="Museo Sans" fontSize={7}>
{t(timeFrame)}
</Text>
<Text color="grey" fontSize={5} ff="Museo Sans|Light">
{t('app:dashboard.summary', { count: totalAccounts })}
</Text>
<CurrentGreetings />
<SummaryDesc totalAccounts={totalAccounts} />
</Box>
<Box>
<PillsDaysCount
@ -127,105 +107,33 @@ class DashboardPage extends PureComponent<Props> {
/>
</Box>
</Box>
<Fragment>
<BalanceSummary
counterValue={counterValue}
chartId="dashboard-chart"
chartColor={colors.wallet}
<BalanceSummary
counterValue={counterValue}
chartId="dashboard-chart"
chartColor={colors.wallet}
accounts={accounts}
selectedTimeRange={selectedTimeRange}
daysCount={daysCount}
renderHeader={this.renderHeader}
/>
<AccountCardList
onAccountClick={this.onAccountClick}
accounts={accounts}
daysCount={daysCount}
counterValue={counterValue}
/>
{totalOperations > 0 && (
<OperationsList
onAccountClick={this.onAccountClick}
accounts={accounts}
selectedTimeRange={selectedTimeRange}
daysCount={daysCount}
renderHeader={({
isAvailable,
totalBalance,
selectedTimeRange,
sinceBalance,
refBalance,
}) => (
<BalanceInfos
t={t}
counterValue={counterValue}
isAvailable={isAvailable}
totalBalance={totalBalance}
since={selectedTimeRange}
sinceBalance={sinceBalance}
refBalance={refBalance}
/>
)}
title={t('app:dashboard.recentActivity')}
withAccount
/>
<Box flow={4}>
<Box horizontal alignItems="flex-end">
<Text color="dark" ff="Museo Sans" fontSize={6}>
{t('app:dashboard.accounts.title', { count: accounts.length })}
</Text>
<Box ml="auto" horizontal flow={1}>
<AccountsOrder />
</Box>
</Box>
<Box
horizontal
flexWrap="wrap"
justifyContent="flex-start"
alignItems="center"
style={{ margin: '0 -16px' }}
>
{accounts
.concat(
Array(3 - (accounts.length % 3))
.fill(null)
.map((_, i) => i === 0),
)
.map((account, i) => (
<Box
key={typeof account === 'object' ? account.id : `placeholder_${i}`}
flex="33%"
p={16}
>
{account ? (
typeof account === 'object' ? (
<AccountCard
key={account.id}
counterValue={counterValue}
account={account}
daysCount={daysCount}
onClick={this.onAccountClick}
/>
) : (
<Wrapper>
<Box mt={2}>
<img alt="" src={imagePath} />
</Box>
<Box
ff="Open Sans"
fontSize={3}
color="grey"
pb={2}
mt={3}
textAlign="center"
style={{ maxWidth: 150 }}
>
{t('app:dashboard.emptyAccountTile.desc')}
</Box>
<Button primary onClick={() => openModal(MODAL_ADD_ACCOUNTS)}>
{t('app:dashboard.emptyAccountTile.createAccount')}
</Button>
</Wrapper>
)
) : null}
</Box>
))}
</Box>
</Box>
{displayOperations && (
<OperationsList
onAccountClick={this.onAccountClick}
accounts={accounts}
title={t('app:dashboard.recentActivity')}
withAccount
/>
)}
<StickyBackToTop />
</Fragment>
)}
<StickyBackToTop />
</Fragment>
) : (
<EmptyState />
@ -243,13 +151,3 @@ export default compose(
),
translate(),
)(DashboardPage)
const Wrapper = styled(Box).attrs({
p: 4,
flex: 1,
alignItems: 'center',
})`
border: 1px dashed ${p => p.theme.colors.fog};
border-radius: 4px;
height: 215px;
`

26
src/components/base/Button/index.js

@ -93,16 +93,22 @@ const buttonStyles: { [_: string]: Style } = {
background: ${rgba(c, 0.1)};
`
},
active: p => `
color: ${darken(
p.outlineColor ? p.theme.colors[p.outlineColor] || p.outlineColor : p.theme.colors.wallet,
0.1,
)};
border-color: ${darken(
p.outlineColor ? p.theme.colors[p.outlineColor] || p.outlineColor : p.theme.colors.wallet,
0.1,
)};
`,
active: p => {
const c = p.outlineColor
? p.theme.colors[p.outlineColor] || p.outlineColor
: p.theme.colors.wallet
return `
background: ${rgba(c, 0.15)};
color: ${darken(
p.outlineColor ? p.theme.colors[p.outlineColor] || p.outlineColor : p.theme.colors.wallet,
0.1,
)};
border-color: ${darken(
p.outlineColor ? p.theme.colors[p.outlineColor] || p.outlineColor : p.theme.colors.wallet,
0.1,
)};
`
},
},
outlineGrey: {
default: p => `

3
src/config/constants.js

@ -45,7 +45,7 @@ export const SYNC_ALL_INTERVAL = 120 * 1000
export const SYNC_BOOT_DELAY = 2 * 1000
export const SYNC_PENDING_INTERVAL = 10 * 1000
export const SYNC_MAX_CONCURRENT = intFromEnv('LEDGER_SYNC_MAX_CONCURRENT', 1)
export const SYNC_TIMEOUT = intFromEnv('SYNC_TIMEOUT', 30 * 1000)
export const SYNC_TIMEOUT = intFromEnv('SYNC_TIMEOUT', 60 * 1000)
// Endpoints...
@ -79,7 +79,6 @@ export const DEBUG_TAB_KEY = boolFromEnv('DEBUG_TAB_KEY')
export const DEBUG_LIBCORE = boolFromEnv('DEBUG_LIBCORE')
export const DEBUG_WS = boolFromEnv('DEBUG_WS')
export const DEBUG_SYNC = boolFromEnv('DEBUG_SYNC')
export const LEDGER_RESET_ALL = boolFromEnv('LEDGER_RESET_ALL')
export const LEDGER_DEBUG_ALL_LANGS = boolFromEnv('LEDGER_DEBUG_ALL_LANGS')
export const SKIP_GENUINE = boolFromEnv('SKIP_GENUINE')
export const SKIP_ONBOARDING = boolFromEnv('SKIP_ONBOARDING')

1
src/config/errors.js

@ -12,6 +12,7 @@ export const UserRefusedAddress = createCustomErrorClass('UserRefusedAddress')
export const WrongDeviceForAccount = createCustomErrorClass('WrongDeviceForAccount')
export const DeviceNotGenuineError = createCustomErrorClass('DeviceNotGenuine')
export const DeviceGenuineSocketEarlyClose = createCustomErrorClass('DeviceGenuineSocketEarlyClose')
export const TimeoutTagged = createCustomErrorClass('TimeoutTagged')
// db stuff, no need to translate
export const NoDBPathGiven = createCustomErrorClass('NoDBPathGiven')

62
src/helpers/libcore.js

@ -16,6 +16,7 @@ import { isSegwitPath, isUnsplitPath } from 'helpers/bip32'
import * as accountIdHelper from 'helpers/accountId'
import { createCustomErrorClass, deserializeError } from './errors'
import { getAccountPlaceholderName, getNewAccountPlaceholderName } from './accountName'
import { timeoutTagged } from './promise'
const NoAddressesFound = createCustomErrorClass('NoAddressesFound')
@ -202,6 +203,7 @@ const coreSyncAccount = (core, account) =>
new Promise((resolve, reject) => {
const eventReceiver = createEventReceiver(core, e => {
const code = e.getCode()
logger.debug(`syncAccountEvent ${code}`, { type: 'libcore-sync' })
if (code === core.EVENT_CODE.UNDEFINED || code === core.EVENT_CODE.SYNCHRONIZATION_FAILED) {
const payload = e.getPayload()
const message = (
@ -270,7 +272,7 @@ async function scanNextAccount(props: {
const shouldSyncAccount = true // TODO: let's sync everytime. maybe in the future we can optimize.
if (shouldSyncAccount) {
await coreSyncAccount(core, njsAccount)
await timeoutTagged('coreSyncAccount', 30000, coreSyncAccount(core, njsAccount))
}
if (isUnsubscribed()) return []
@ -325,10 +327,10 @@ async function getOrCreateWallet(
): NJSWallet {
const pool = core.getPoolInstance()
try {
const wallet = await pool.getWallet(WALLET_IDENTIFIER)
const wallet = await timeoutTagged('getWallet', 5000, pool.getWallet(WALLET_IDENTIFIER))
return wallet
} catch (err) {
const currency = await pool.getCurrency(currencyId)
const currency = await timeoutTagged('getCurrency', 5000, pool.getCurrency(currencyId))
const splitConfig = isUnsplit ? SPLITTED_CURRENCIES[currencyId] || null : null
const coinType = splitConfig ? splitConfig.coinType : '<coin_type>'
const walletConfig = isSegwit
@ -342,9 +344,11 @@ async function getOrCreateWallet(
}
: undefined
const njsWalletConfig = createWalletConfig(core, walletConfig)
const wallet = await core
.getPoolInstance()
.createWallet(WALLET_IDENTIFIER, currency, njsWalletConfig)
const wallet = await timeoutTagged(
'createWallet',
10000,
core.getPoolInstance().createWallet(WALLET_IDENTIFIER, currency, njsWalletConfig),
)
return wallet
}
}
@ -368,21 +372,33 @@ async function buildAccountRaw({
core: *,
ops: NJSOperation[],
}): Promise<AccountRaw> {
const njsBalance = await njsAccount.getBalance()
const njsBalance = await timeoutTagged('getBalance', 10000, njsAccount.getBalance())
const balance = njsBalance.toLong()
const jsCurrency = getCryptoCurrencyById(currencyId)
const { derivations } = await wallet.getAccountCreationInfo(accountIndex)
const { derivations } = await timeoutTagged(
'getAccountCreationInfo',
10000,
wallet.getAccountCreationInfo(accountIndex),
)
const [walletPath, accountPath] = derivations
// retrieve xpub
const xpub = njsAccount.getRestoreKey()
// blockHeight
const { height: blockHeight } = await njsAccount.getLastBlock()
const { height: blockHeight } = await timeoutTagged(
'getLastBlock',
30000,
njsAccount.getLastBlock(),
)
// get a bunch of fresh addresses
const rawAddresses = await njsAccount.getFreshPublicAddresses()
const rawAddresses = await timeoutTagged(
'getFreshPublicAddresses',
10000,
njsAccount.getFreshPublicAddresses(),
)
const addresses = rawAddresses.map(njsAddress => ({
str: njsAddress.toString(),
@ -500,7 +516,11 @@ export async function syncAccount({
const isUnsplit = isUnsplitPath(freshAddressPath, SPLITTED_CURRENCIES[currencyId])
let njsWallet
try {
njsWallet = await core.getPoolInstance().getWallet(decodedAccountId.walletName)
njsWallet = await timeoutTagged(
'getWallet',
10000,
core.getPoolInstance().getWallet(decodedAccountId.walletName),
)
} catch (e) {
logger.warn(`Have to reimport the account... (${e})`)
njsWallet = await getOrCreateWallet(
@ -514,20 +534,28 @@ export async function syncAccount({
let njsAccount
try {
njsAccount = await njsWallet.getAccount(index)
njsAccount = await timeoutTagged('getAccount', 10000, njsWallet.getAccount(index))
} catch (e) {
logger.warn(`Have to recreate the account... (${e.message})`)
const extendedInfos = await njsWallet.getExtendedKeyAccountCreationInfo(index)
const extendedInfos = await timeoutTagged(
'getEKACI',
10000,
njsWallet.getExtendedKeyAccountCreationInfo(index),
)
extendedInfos.extendedKeys.push(decodedAccountId.xpub)
njsAccount = await njsWallet.newAccountWithExtendedKeyInfo(extendedInfos)
njsAccount = await timeoutTagged(
'newAWEKI',
10000,
njsWallet.newAccountWithExtendedKeyInfo(extendedInfos),
)
}
const unsub = await coreSyncAccount(core, njsAccount)
const unsub = await timeoutTagged('coreSyncAccount', 30000, coreSyncAccount(core, njsAccount))
unsub()
const query = njsAccount.queryOperations()
const ops = await query.complete().execute()
const njsBalance = await njsAccount.getBalance()
const ops = await timeoutTagged('ops', 30000, query.complete().execute())
const njsBalance = await timeoutTagged('getBalance', 10000, njsAccount.getBalance())
const syncedRawAccount = await buildAccountRaw({
njsAccount,

9
src/helpers/linking.js

@ -1,12 +1,17 @@
// @flow
import { shell } from 'electron'
import { track } from 'analytics/segment'
let shell
if (!process.env.STORYBOOK_ENV) {
const electron = require('electron')
shell = electron.shell
}
export const openURL = (
url: string,
customEventName: string = 'OpenURL',
extraParams: Object = {},
) => {
track(customEventName, { ...extraParams, url })
shell.openExternal(url)
shell && shell.openExternal(url)
}

18
src/helpers/promise.js

@ -2,6 +2,7 @@
// small utilities for Promises
import logger from 'logger'
import { TimeoutTagged } from 'config/errors'
export const delay = (ms: number): Promise<void> => new Promise(f => setTimeout(f, ms))
@ -65,6 +66,23 @@ export function createCancelablePolling(
return { unsubscribe, promise }
}
export const timeoutTagged = <T>(tag: string, delay: number, promise: Promise<T>): Promise<T> =>
new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new TimeoutTagged('timeout', { tag }))
}, delay)
promise.then(
r => {
clearTimeout(timeout)
resolve(r)
},
e => {
clearTimeout(timeout)
reject(e)
},
)
})
export const promisify = (fn: any) => (...args: any) =>
new Promise((resolve, reject) =>
fn(...args, (err: Error, res: any) => {

6
src/renderer/init.js

@ -12,7 +12,6 @@ import { runMigrations } from 'migrations'
import createStore from 'renderer/createStore'
import events from 'renderer/events'
import { LEDGER_RESET_ALL } from 'config/constants'
import { enableGlobalTab, disableGlobalTab, isGlobalTabEnabled } from 'config/global-tab'
import { fetchAccounts } from 'actions/accounts'
@ -25,7 +24,6 @@ import resolveUserDataDirectory from 'helpers/resolveUserDataDirectory'
import db from 'helpers/db'
import dbMiddleware from 'middlewares/db'
import CounterValues from 'helpers/countervalues'
import hardReset from 'helpers/hardReset'
import { decodeAccountsModel, encodeAccountsModel } from 'reducers/accounts'
@ -43,10 +41,6 @@ const TAB_KEY = 9
db.init(userDataDirectory)
async function init() {
if (LEDGER_RESET_ALL) {
await hardReset()
}
await runMigrations()
db.init(userDataDirectory)
db.registerTransform('app', 'accounts', { get: decodeAccountsModel, set: encodeAccountsModel })

6
static/i18n/en/app.json

@ -196,7 +196,7 @@
"successDescription_plural": "Your accounts have been created.",
"createNewAccount": {
"title": "Add a new account",
"noOperationOnLastAccount": "No transactions found on your last new account <1><0>{{accountName}}</0></1>. You can add a new account after you've started transacting on that account.",
"noOperationOnLastAccount": "There are no transactions on your last created <1><0>{{accountName}}</0></1> account. You must first receive crypto assets on that account before you can add a new one.",
"noAccountToCreate": "No <1><0>{{currencyName}}</0></1> account was found to create"
},
"cta": {
@ -399,7 +399,7 @@
},
"softResetModal": {
"title": "Clear cache",
"desc": "Clearing the Ledger Live cache forces network resynchronization"
"desc": "Clearing the Ledger Live cache forces network resynchronization. Your settings and accounts are not affected. The private keys to access your crypto assets in the blockchain remain secure on your Ledger device and on your Recovery sheet."
},
"removeAccountModal": {
"title": "Remove account",
@ -459,4 +459,4 @@
"desc_2": "Please beware that Ledger does not provide financial, tax, or legal advice. You should take such decisions on your own or consult with reliable experts.",
"cta": "Got it"
}
}
}

4
static/i18n/en/errors.json

@ -115,6 +115,10 @@
"title": "Oops, a time out occurred",
"description": "It took too long for the server to respond."
},
"TimeoutTagged": {
"title": "Oops, a time out occurred ({{tag}})",
"description": "It took too long for the server to respond."
},
"TransportError": {
"title": "Something went wrong. Please reconnect your device.",
"description": "{{message}}"

Loading…
Cancel
Save