Browse Source

Merge pull request #53 from loeck/master

Update dependencies, fix sync Accounts, few clean
master
Meriadec Pillet 7 years ago
committed by GitHub
parent
commit
7039d170ea
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      flow-defs/process.js
  2. 16
      package.json
  3. 14
      src/components/DashboardPage.js
  4. 2
      src/components/SideBar/Item.js
  5. 32
      src/components/SideBar/index.js
  6. 9
      src/components/UpdateNotifier.js
  7. 2
      src/components/Wrapper.js
  8. 4
      src/components/base/Modal/index.js
  9. 91
      src/components/modals/AddAccount.js
  10. 1
      src/components/modals/Receive.js
  11. 1
      src/components/modals/Send.js
  12. 2
      src/constants.js
  13. 38
      src/helpers/btc.js
  14. 16
      src/i18n/en/translation.yml
  15. 16
      src/i18n/fr/translation.yml
  16. 7
      src/internals/index.js
  17. 4
      src/internals/usb/devices.js
  18. 9
      src/internals/usb/wallet/accounts.js
  19. 2
      src/main/app.js
  20. 8
      src/main/bridge.js
  21. 2
      src/main/index.js
  22. 28
      src/reducers/accounts.js
  23. 7
      src/reducers/settings.js
  24. 28
      src/renderer/events.js
  25. 55
      yarn.lock

1
flow-defs/process.js

@ -5,6 +5,7 @@ declare var process: {
on(event: string, args: any): void, on(event: string, args: any): void,
nextTick(callback: Function): void, nextTick(callback: Function): void,
setMaxListeners(any): void, setMaxListeners(any): void,
removeListener(string, Function): void,
title: string, title: string,
env: Object, env: Object,
} }

16
package.json

@ -39,16 +39,16 @@
}, },
"dependencies": { "dependencies": {
"@ledgerhq/common": "2.0.5", "@ledgerhq/common": "2.0.5",
"@ledgerhq/hw-app-btc": "^2.0.5", "@ledgerhq/hw-app-btc": "^2.1.0",
"@ledgerhq/hw-app-eth": "^2.0.5", "@ledgerhq/hw-app-eth": "^2.1.0",
"@ledgerhq/hw-transport": "^2.0.5", "@ledgerhq/hw-transport": "^2.1.0",
"@ledgerhq/hw-transport-node-hid": "^2.0.6", "@ledgerhq/hw-transport-node-hid": "^2.1.0",
"axios": "^0.17.1", "axios": "^0.17.1",
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",
"bitcoinjs-lib": "^3.3.2", "bitcoinjs-lib": "^3.3.2",
"blockchain.info": "^2.11.0", "blockchain.info": "^2.11.0",
"bs58check": "^2.1.1", "bs58check": "^2.1.1",
"color": "^2.0.1", "color": "^3.0.0",
"cross-env": "^5.1.3", "cross-env": "^5.1.3",
"downshift": "^1.25.0", "downshift": "^1.25.0",
"electron-store": "^1.3.0", "electron-store": "^1.3.0",
@ -65,7 +65,7 @@
"raven-js": "^3.22.1", "raven-js": "^3.22.1",
"react": "^16.2.0", "react": "^16.2.0",
"react-dom": "^16.2.0", "react-dom": "^16.2.0",
"react-i18next": "^7.3.1", "react-i18next": "^7.3.2",
"react-mortal": "^3.0.1", "react-mortal": "^3.0.1",
"react-motion": "^0.5.2", "react-motion": "^0.5.2",
"react-redux": "^5.0.6", "react-redux": "^5.0.6",
@ -105,11 +105,11 @@
"eslint-config-airbnb": "^16.1.0", "eslint-config-airbnb": "^16.1.0",
"eslint-config-prettier": "^2.9.0", "eslint-config-prettier": "^2.9.0",
"eslint-import-resolver-babel-module": "^4.0.0", "eslint-import-resolver-babel-module": "^4.0.0",
"eslint-plugin-flowtype": "^2.41.1", "eslint-plugin-flowtype": "^2.42.0",
"eslint-plugin-import": "^2.8.0", "eslint-plugin-import": "^2.8.0",
"eslint-plugin-jsx-a11y": "^6.0.3", "eslint-plugin-jsx-a11y": "^6.0.3",
"eslint-plugin-react": "^7.5.1", "eslint-plugin-react": "^7.5.1",
"flow-bin": "^0.63.1", "flow-bin": "^0.64.0",
"flow-typed": "^2.2.3", "flow-typed": "^2.2.3",
"husky": "^0.14.3", "husky": "^0.14.3",
"lint-staged": "^6.0.0", "lint-staged": "^6.0.0",

14
src/components/DashboardPage.js

@ -1,12 +1,14 @@
// @flow // @flow
import React, { PureComponent } from 'react' import React, { PureComponent } from 'react'
import { compose } from 'redux'
import { translate } from 'react-i18next'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import chunk from 'lodash/chunk' import chunk from 'lodash/chunk'
import { push } from 'react-router-redux' import { push } from 'react-router-redux'
import type { MapStateToProps } from 'react-redux' import type { MapStateToProps } from 'react-redux'
import type { Accounts } from 'types/common' import type { Accounts, T } from 'types/common'
import { format } from 'helpers/btc' import { format } from 'helpers/btc'
@ -29,6 +31,7 @@ const mapDispatchToProps = {
} }
type Props = { type Props = {
t: T,
accounts: Accounts, accounts: Accounts,
push: Function, push: Function,
openModal: Function, openModal: Function,
@ -53,7 +56,7 @@ class DashboardPage extends PureComponent<Props, State> {
handleChangeTab = tab => this.setState({ tab }) handleChangeTab = tab => this.setState({ tab })
render() { render() {
const { totalBalance, openModal, push, accounts } = this.props const { t, totalBalance, openModal, push, accounts } = this.props
const { tab } = this.state const { tab } = this.state
const totalAccounts = Object.keys(accounts).length const totalAccounts = Object.keys(accounts).length
@ -123,7 +126,7 @@ class DashboardPage extends PureComponent<Props, State> {
style={{ borderStyle: 'dashed', cursor: 'pointer' }} style={{ borderStyle: 'dashed', cursor: 'pointer' }}
onClick={() => openModal('add-account')} onClick={() => openModal('add-account')}
> >
{'+ Add account'} {`+ ${t('addAccount.title')}`}
</Box> </Box>
) : ( ) : (
<Card <Card
@ -132,7 +135,8 @@ class DashboardPage extends PureComponent<Props, State> {
style={{ cursor: 'pointer', height: 200 }} style={{ cursor: 'pointer', height: 200 }}
onClick={() => push(`/account/${key}`)} onClick={() => push(`/account/${key}`)}
> >
{accounts[key].name} <div>{accounts[key].name}</div>
<div>{accounts[key].data && format(accounts[key].data.balance)}</div>
</Card> </Card>
), ),
)} )}
@ -144,4 +148,4 @@ class DashboardPage extends PureComponent<Props, State> {
} }
} }
export default connect(mapStateToProps, mapDispatchToProps)(DashboardPage) export default compose(connect(mapStateToProps, mapDispatchToProps), translate())(DashboardPage)

2
src/components/SideBar/Item.js

@ -53,7 +53,7 @@ type Props = {
linkTo?: string | null, linkTo?: string | null,
modal?: string | null, modal?: string | null,
desc?: string | null, desc?: string | null,
icon?: Element<*> | null, icon?: string | null,
location: Location, location: Location,
isModalOpened: boolean, isModalOpened: boolean,
push: Function, push: Function,

32
src/components/SideBar/index.js

@ -1,11 +1,13 @@
// @flow // @flow
import React, { PureComponent } from 'react' import React, { PureComponent } from 'react'
import { compose } from 'redux'
import { translate } from 'react-i18next'
import styled from 'styled-components' import styled from 'styled-components'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import type { MapStateToProps } from 'react-redux' import type { MapStateToProps } from 'react-redux'
import type { Accounts } from 'types/common' import type { Accounts, T } from 'types/common'
import { openModal } from 'reducers/modals' import { openModal } from 'reducers/modals'
import { getAccounts } from 'reducers/accounts' import { getAccounts } from 'reducers/accounts'
@ -45,6 +47,7 @@ const BtnAddAccount = styled(Box).attrs({
` `
type Props = { type Props = {
t: T,
accounts: Accounts, accounts: Accounts,
openModal: Function, openModal: Function,
} }
@ -59,30 +62,30 @@ const mapDispatchToProps = {
class SideBar extends PureComponent<Props> { class SideBar extends PureComponent<Props> {
render() { render() {
const { accounts, openModal } = this.props const { t, accounts, openModal } = this.props
return ( return (
<Container bg="night"> <Container bg="night">
<GrowScroll flow={4} py={4}> <GrowScroll flow={4} py={4}>
<Box flow={2}> <Box flow={2}>
<CapsSubtitle>{'Menu'}</CapsSubtitle> <CapsSubtitle>{t('sidebar.menu')}</CapsSubtitle>
<div> <div>
<Item icon="bar-chart" linkTo="/"> <Item icon="bar-chart" linkTo="/">
{'Dashboard'} {t('dashboard.title')}
</Item> </Item>
<Item icon="upload" modal="send"> <Item icon="upload" modal="send">
{'Send'} {t('send.title')}
</Item> </Item>
<Item icon="download" modal="receive"> <Item icon="download" modal="receive">
{'Receive'} {t('receive.title')}
</Item> </Item>
<Item icon="cog" linkTo="/settings"> <Item icon="cog" linkTo="/settings">
{'Settings'} {t('settings.title')}
</Item> </Item>
</div> </div>
</Box> </Box>
<Box flow={2}> <Box flow={2}>
<CapsSubtitle>{'Accounts'}</CapsSubtitle> <CapsSubtitle>{t('sidebar.accounts')}</CapsSubtitle>
<div> <div>
{Object.entries(accounts).map(([id, account]: [string, any]) => ( {Object.entries(accounts).map(([id, account]: [string, any]) => (
<Item linkTo={`/account/${id}`} desc={format(account.data.balance)} key={id}> <Item linkTo={`/account/${id}`} desc={format(account.data.balance)} key={id}>
@ -91,13 +94,18 @@ class SideBar extends PureComponent<Props> {
))} ))}
</div> </div>
</Box> </Box>
<BtnAddAccount onClick={() => openModal('add-account')}>{'Add account'}</BtnAddAccount> <BtnAddAccount onClick={() => openModal('add-account')}>
{t('addAccount.title')}
</BtnAddAccount>
</GrowScroll> </GrowScroll>
</Container> </Container>
) )
} }
} }
export default connect(mapStateToProps, mapDispatchToProps, null, { export default compose(
pure: false, connect(mapStateToProps, mapDispatchToProps, null, {
})(SideBar) pure: false,
}),
translate(),
)(SideBar)

9
src/components/UpdateNotifier.js

@ -7,7 +7,7 @@ import type { MapStateToProps } from 'react-redux'
import styled from 'styled-components' import styled from 'styled-components'
import { getUpdateStatus, getUpdateData } from 'reducers/update' import { getUpdateStatus, getUpdateData } from 'reducers/update'
import { sendEvent } from 'renderer/events' import { sendEvent, checkUpdates } from 'renderer/events'
import type { State } from 'reducers' import type { State } from 'reducers'
import type { UpdateStatus } from 'reducers/update' import type { UpdateStatus } from 'reducers/update'
@ -39,6 +39,12 @@ const Container = styled(Box).attrs({
` `
class UpdateNotifier extends PureComponent<Props> { class UpdateNotifier extends PureComponent<Props> {
componentWillReceiveProps(nextProps) {
if (['idle', 'unavailable', 'error'].includes(nextProps.updateStatus)) {
checkUpdates()
}
}
renderStatus() { renderStatus() {
const { updateStatus } = this.props const { updateStatus } = this.props
switch (updateStatus) { switch (updateStatus) {
@ -68,6 +74,7 @@ class UpdateNotifier extends PureComponent<Props> {
render() { render() {
const { updateStatus } = this.props const { updateStatus } = this.props
const isToggled = updateStatus === 'downloaded' const isToggled = updateStatus === 'downloaded'
return ( return (
<Motion <Motion

2
src/components/Wrapper.js

@ -36,7 +36,7 @@ class Wrapper extends Component<{}> {
<Box shrink grow bg="cream" color="grey"> <Box shrink grow bg="cream" color="grey">
<TopBar /> <TopBar />
<Box grow relative> <Box grow relative>
<UpdateNotifier /> {__PROD__ && <UpdateNotifier />}
<GrowScroll p={4}> <GrowScroll p={4}>
<Route path="/" exact component={DashboardPage} /> <Route path="/" exact component={DashboardPage} />
<Route path="/settings" component={SettingsPage} /> <Route path="/settings" component={SettingsPage} />

4
src/components/base/Modal/index.js

@ -9,8 +9,6 @@ import Mortal from 'react-mortal'
import styled from 'styled-components' import styled from 'styled-components'
import noop from 'lodash/noop' import noop from 'lodash/noop'
import type { Element } from 'react'
import { rgba } from 'styles/helpers' import { rgba } from 'styles/helpers'
import { closeModal, isModalOpened } from 'reducers/modals' import { closeModal, isModalOpened } from 'reducers/modals'
@ -139,7 +137,7 @@ export const ModalBody = ({
onClose, onClose,
...props ...props
}: { }: {
children: Element<any> | string, children: any,
onClose?: Function, onClose?: Function,
}) => ( }) => (
<Body> <Body>

91
src/components/modals/AddAccount.js

@ -8,7 +8,7 @@ import type { MapStateToProps } from 'react-redux'
import type { Accounts, Device } from 'types/common' import type { Accounts, Device } from 'types/common'
import { closeModal } from 'reducers/modals' import { closeModal } from 'reducers/modals'
import { getAccounts } from 'reducers/accounts' import { canCreateAccount, getAccounts } from 'reducers/accounts'
import { getCurrentDevice } from 'reducers/devices' import { getCurrentDevice } from 'reducers/devices'
import { sendEvent } from 'renderer/events' import { sendEvent } from 'renderer/events'
@ -46,14 +46,18 @@ const Steps = {
</Box> </Box>
<Box horizontal justify="flex-end"> <Box horizontal justify="flex-end">
<Button primary type="submit"> <Button primary type="submit">
Create account Add account
</Button> </Button>
</Box> </Box>
</Box> </Box>
</form> </form>
), ),
connectDevice: () => <div>Connect your Ledger</div>, connectDevice: (props: Object) => (
startWallet: (props: Object) => <div>Select {props.wallet.toUpperCase()} App on your Ledger</div>, <div>
<div>Connect your Ledger: {props.connected ? 'ok' : 'ko'}</div>
<div>Start {props.wallet.toUpperCase()} App on your Ledger: ko</div>
</div>
),
inProgress: (props: Object) => ( inProgress: (props: Object) => (
<div> <div>
In progress. In progress.
@ -65,20 +69,38 @@ const Steps = {
</div> </div>
), ),
listAccounts: (props: Object) => { listAccounts: (props: Object) => {
const accounts = Object.entries(props.accounts) const accounts = []
let newAccount = null
Object.entries(props.accounts).forEach(([, account]: [string, any]) => {
const hasTransactions = account.transactions.length > 0
if (hasTransactions) {
accounts.push(account)
} else {
newAccount = account
}
})
return ( return (
<div> <div>
{accounts.length > 0 {accounts.map(account => (
? accounts.map(([index, account]: [string, any]) => ( <div key={account.id} style={{ marginBottom: 10 }}>
<div key={index}> <div>Balance: {account.balance}</div>
<div>Balance: {account.balance}</div> <div>Transactions: {account.transactions.length}</div>
<div>Transactions: {account.transactions.length}</div> <div>
<div> <Button onClick={props.onAddAccount(account)}>Import</Button>
<Button onClick={props.onAddAccount(index)}>Import</Button> </div>
</div> </div>
</div> ))}
)) {props.canCreateAccount && newAccount !== null ? (
: 'No accounts'} <div>
<Button onClick={props.onAddAccount(newAccount)}>Create new account</Button>
</div>
) : (
<div>You cannot create new account</div>
)}
</div> </div>
) )
}, },
@ -89,13 +111,14 @@ type InputValue = {
wallet: string, wallet: string,
} }
type Step = 'createAccount' | 'connectDevice' | 'inProgress' | 'startWallet' | 'listAccounts' type Step = 'createAccount' | 'connectDevice' | 'inProgress' | 'listAccounts'
type Props = { type Props = {
accounts: Accounts,
addAccount: Function, addAccount: Function,
canCreateAccount: boolean,
closeModal: Function, closeModal: Function,
currentDevice: Device | null, currentDevice: Device | null,
accounts: Accounts,
} }
type State = { type State = {
inputValue: InputValue, inputValue: InputValue,
@ -106,6 +129,7 @@ type State = {
const mapStateToProps: MapStateToProps<*, *, *> = state => ({ const mapStateToProps: MapStateToProps<*, *, *> = state => ({
accounts: getAccounts(state), accounts: getAccounts(state),
canCreateAccount: canCreateAccount(state),
currentDevice: getCurrentDevice(state), currentDevice: getCurrentDevice(state),
}) })
@ -133,21 +157,11 @@ class AddAccountModal extends PureComponent<Props, State> {
ipcRenderer.on('msg', this.handleWalletRequest) ipcRenderer.on('msg', this.handleWalletRequest)
} }
componentWillReceiveProps(nextProps) {
const { currentDevice } = nextProps
if (this.props.currentDevice === null && this.state.step !== 'createAccount') {
this.setState({
step: currentDevice !== null ? 'startWallet' : 'connectDevice',
})
}
}
componentDidUpdate() { componentDidUpdate() {
const { step } = this.state const { step } = this.state
const { currentDevice } = this.props const { currentDevice } = this.props
if (step === 'startWallet' && currentDevice !== null) { if (step === 'connectDevice' && currentDevice !== null) {
this.getWalletInfos() this.getWalletInfos()
} else { } else {
clearTimeout(this._timeout) clearTimeout(this._timeout)
@ -160,8 +174,8 @@ class AddAccountModal extends PureComponent<Props, State> {
} }
getWalletInfos() { getWalletInfos() {
const { inputValue } = this.state
const { currentDevice, accounts } = this.props const { currentDevice, accounts } = this.props
const { inputValue } = this.state
if (currentDevice === null) { if (currentDevice === null) {
return return
@ -175,6 +189,7 @@ class AddAccountModal extends PureComponent<Props, State> {
} }
getStepProps() { getStepProps() {
const { currentDevice, canCreateAccount } = this.props
const { inputValue, step, progress, accounts } = this.state const { inputValue, step, progress, accounts } = this.state
const props = (predicate, props) => (predicate ? props : {}) const props = (predicate, props) => (predicate ? props : {})
@ -185,7 +200,8 @@ class AddAccountModal extends PureComponent<Props, State> {
onSubmit: this.handleSubmit, onSubmit: this.handleSubmit,
onChangeInput: this.handleChangeInput, onChangeInput: this.handleChangeInput,
}), }),
...props(step === 'startWallet', { ...props(step === 'connectDevice', {
connected: currentDevice !== null,
wallet: inputValue.wallet, wallet: inputValue.wallet,
}), }),
...props(step === 'inProgress', { ...props(step === 'inProgress', {
@ -193,6 +209,7 @@ class AddAccountModal extends PureComponent<Props, State> {
}), }),
...props(step === 'listAccounts', { ...props(step === 'listAccounts', {
accounts, accounts,
canCreateAccount,
onAddAccount: this.handleAddAccount, onAddAccount: this.handleAddAccount,
}), }),
} }
@ -218,11 +235,11 @@ class AddAccountModal extends PureComponent<Props, State> {
} }
} }
handleAddAccount = index => () => { handleAddAccount = account => () => {
const { inputValue, accounts } = this.state const { inputValue } = this.state
const { addAccount, closeModal } = this.props const { addAccount, closeModal } = this.props
const { id, ...data } = accounts[index] const { id, ...data } = account
addAccount({ addAccount({
id, id,
@ -232,6 +249,7 @@ class AddAccountModal extends PureComponent<Props, State> {
}) })
closeModal('add-account') closeModal('add-account')
this.handleClose() this.handleClose()
} }
@ -246,7 +264,6 @@ class AddAccountModal extends PureComponent<Props, State> {
handleSubmit = (e: SyntheticEvent<HTMLFormElement>) => { handleSubmit = (e: SyntheticEvent<HTMLFormElement>) => {
e.preventDefault() e.preventDefault()
const { currentDevice } = this.props
const { inputValue } = this.state const { inputValue } = this.state
if (inputValue.accountName.trim() === '' || inputValue.wallet.trim() === '') { if (inputValue.accountName.trim() === '' || inputValue.wallet.trim() === '') {
@ -254,7 +271,7 @@ class AddAccountModal extends PureComponent<Props, State> {
} }
this.setState({ this.setState({
step: currentDevice === null ? 'connectDevice' : 'startWallet', step: 'connectDevice',
}) })
} }
@ -275,7 +292,7 @@ class AddAccountModal extends PureComponent<Props, State> {
return ( return (
<Modal <Modal
name="add-account" name="add-account"
preventBackdropClick preventBackdropClick={step !== 'createAccount'}
onClose={this.handleClose} onClose={this.handleClose}
render={({ onClose }) => ( render={({ onClose }) => (
<ModalBody onClose={onClose} flow={3}> <ModalBody onClose={onClose} flow={3}>

1
src/components/modals/Receive.js

@ -11,7 +11,6 @@ class ReceiveModal extends PureComponent<Props> {
return ( return (
<Modal <Modal
name="receive" name="receive"
preventBackdropClick
render={({ onClose }) => <ModalBody onClose={onClose}>receive modal</ModalBody>} render={({ onClose }) => <ModalBody onClose={onClose}>receive modal</ModalBody>}
/> />
) )

1
src/components/modals/Send.js

@ -149,7 +149,6 @@ class Send extends PureComponent<Props, State> {
return ( return (
<Modal <Modal
name="send" name="send"
preventBackdropClick
onClose={this.handleClose} onClose={this.handleClose}
render={({ onClose }) => ( render={({ onClose }) => (
<Fragment> <Fragment>

2
src/constants.js

@ -0,0 +1,2 @@
export const CHECK_UPDATE_TIMEOUT = 5e3
export const SYNC_ACCOUNT_TIMEOUT = 5e3

38
src/helpers/btc.js

@ -84,6 +84,7 @@ export async function getAccount({
const script = segwit ? parseInt(network.scriptHash, 10) : parseInt(network.pubKeyHash, 10) const script = segwit ? parseInt(network.scriptHash, 10) : parseInt(network.pubKeyHash, 10)
let transactions = [] let transactions = []
let allAddresses = []
let lastAddress = null let lastAddress = null
const pubKeyToSegwitAddress = (pubKey, scriptVersion) => { const pubKeyToSegwitAddress = (pubKey, scriptVersion) => {
@ -111,12 +112,15 @@ export async function getAccount({
}), }),
}) })
const getLastAddress = (addresses, lastTx) => { const getLastAddress = (addresses, txs) => {
const address = addresses const txsAddresses = [...txs.inputs.map(tx => tx.prev_out.addr), ...txs.out.map(tx => tx.addr)]
.filter(a => a.type === 'external') const lastAddress = addresses.reverse().find(a => txsAddresses.includes(a.address)) || {
.find(a => a.address === lastTx.addr) || { index: 0 } index: 0,
}
return getAddress({ type: 'external', index: address.index + 1 }) return {
index: lastAddress.index,
address: getAddress({ type: 'external', index: lastAddress.index + 1 }).address,
}
} }
const nextPath = (index = 0) => const nextPath = (index = 0) =>
@ -129,15 +133,16 @@ export async function getAccount({
), ),
).then(async results => { ).then(async results => {
const addresses = results.reduce((result, v) => [...result, ...v], []) const addresses = results.reduce((result, v) => [...result, ...v], [])
const listAddresses = addresses.map(a => a.address) const listAddresses = addresses.map(a => a.address)
allAddresses = [...allAddresses, ...listAddresses]
const { txs } = await getTransactions(listAddresses) const { txs } = await getTransactions(listAddresses)
const hasTransactions = txs.length > 0 const hasTransactions = txs.length > 0
transactions = [...transactions, ...txs.map(computeTransaction(listAddresses))] transactions = [...transactions, ...txs.map(computeTransaction(allAddresses))]
lastAddress = hasTransactions ? getLastAddress(addresses, txs[0].out[0]) : lastAddress lastAddress = hasTransactions ? getLastAddress(addresses, txs[0]) : lastAddress
if (hasTransactions) { if (hasTransactions) {
return nextPath(index + (gapLimit - 1)) return nextPath(index + (gapLimit - 1))
@ -154,10 +159,23 @@ export async function getAccount({
currentIndex: lastAddress.index, currentIndex: lastAddress.index,
address: lastAddress.address, address: lastAddress.address,
} }
: {}), : {
currentIndex: 0,
address: getAddress({ type: 'external', index: 0 }).address,
}),
} }
}) })
if (currentIndex > 0) {
for (let i = currentIndex; i--; ) {
allAddresses = [
...allAddresses,
getAddress({ type: 'internal', index: i }).address,
getAddress({ type: 'external', index: i }).address,
]
}
}
return nextPath(currentIndex) return nextPath(currentIndex)
} }

16
src/i18n/en/translation.yml

@ -9,6 +9,22 @@ language:
en: English en: English
fr: French fr: French
sidebar:
menu: Menu
accounts: Accounts
dashboard:
title: Dashboard
send:
title: Send
receive:
title: Receive
addAccount:
title: Add account
settings: settings:
title: Settings title: Settings

16
src/i18n/fr/translation.yml

@ -9,6 +9,22 @@ language:
en: Anglais en: Anglais
fr: Français fr: Français
sidebar:
menu: Menu
accounts: Comptes
dashboard:
title: Tableau de bord
send:
title: Envoyer
receive:
title: Recevoir
addAccount:
title: Ajouter un compte
settings: settings:
title: Réglages title: Réglages

7
src/internals/index.js

@ -16,12 +16,13 @@ const handlers = Object.keys(func).reduce((result, key) => {
return result return result
}, {}) }, {})
process.on('message', payload => { const onMessage = payload => {
const { type, data } = payload const { type, data } = payload
const handler = objectPath.get(handlers, type) const handler = objectPath.get(handlers, type)
if (!handler) { if (!handler) {
return return
} }
handler(data) handler(data)
}) }
process.on('message', onMessage)

4
src/internals/usb/devices.js

@ -1,8 +1,6 @@
// @flow
import CommNodeHid from '@ledgerhq/hw-transport-node-hid' import CommNodeHid from '@ledgerhq/hw-transport-node-hid'
export default (send: Function) => ({ export default send => ({
listen: () => { listen: () => {
CommNodeHid.listen({ CommNodeHid.listen({
next: e => { next: e => {

9
src/internals/usb/wallet/accounts.js

@ -122,12 +122,9 @@ export default async ({
const hasTransactions = account.transactions.length > 0 const hasTransactions = account.transactions.length > 0
// If the first account is empty we still add it accounts[currentAccount] = {
if (currentAccount === 0 || hasTransactions) { id: xpub58,
accounts[currentAccount] = { ...account,
id: xpub58,
...account,
}
} }
if (hasTransactions) { if (hasTransactions) {

2
src/main/app.js

@ -2,8 +2,6 @@
import { app, BrowserWindow } from 'electron' import { app, BrowserWindow } from 'electron'
process.setMaxListeners(100)
// necessary to prevent win from being garbage collected // necessary to prevent win from being garbage collected
let mainWindow let mainWindow

8
src/main/bridge.js

@ -24,8 +24,7 @@ function onForkChannel(forkType, callType) {
} }
} }
compute.send({ type, data }) const onMessage = payload => {
compute.on('message', payload => {
const { type, data, options = {} } = payload const { type, data, options = {} } = payload
if (callType === 'async') { if (callType === 'async') {
event.sender.send('msg', { type, data }) event.sender.send('msg', { type, data })
@ -36,7 +35,10 @@ function onForkChannel(forkType, callType) {
if (options.kill && compute) { if (options.kill && compute) {
kill() kill()
} }
}) }
compute.on('message', onMessage)
compute.send({ type, data })
process.on('exit', kill) process.on('exit', kill)
} }

2
src/main/index.js

@ -1,5 +1,7 @@
// @flow // @flow
process.setMaxListeners(0)
require('../globals') require('../globals')
require('./bridge') require('./bridge')
require('./app') require('./app')

28
src/reducers/accounts.js

@ -1,8 +1,11 @@
// @flow // @flow
import { handleActions } from 'redux-actions' import { handleActions } from 'redux-actions'
import every from 'lodash/every'
import get from 'lodash/get' import get from 'lodash/get'
import reduce from 'lodash/reduce' import reduce from 'lodash/reduce'
import uniqBy from 'lodash/uniqBy'
import type { State } from 'reducers' import type { State } from 'reducers'
import type { Account, Accounts, AccountData } from 'types/common' import type { Account, Accounts, AccountData } from 'types/common'
@ -12,11 +15,15 @@ export type AccountsState = Accounts
const state: AccountsState = {} const state: AccountsState = {}
function getAccount(account: Account) { function getAccount(account: Account) {
const transactions = get(account.data, 'transactions', [])
transactions.sort((a, b) => b.time - a.time)
return { return {
...account, ...account,
data: { data: {
...account.data, ...(account.data || {}),
transactions: get(account.data, 'transactions', []).sort((a, b) => b.time - a.time), transactions,
}, },
} }
} }
@ -34,16 +41,21 @@ const handlers: Object = {
const account = state[accountID] const account = state[accountID]
const { data: accountData } = account const { data: accountData } = account
const balance = get(accountData, 'balance', 0) const transactions = uniqBy(
const transactions = get(accountData, 'transactions', []) [...get(accountData, 'transactions', []), ...data.transactions],
tx => tx.hash,
)
const currentIndex = data.currentIndex ? data.currentIndex : get(accountData, 'currentIndex', 0) const currentIndex = data.currentIndex ? data.currentIndex : get(accountData, 'currentIndex', 0)
account.data = { account.data = {
...accountData, ...accountData,
...data, ...data,
balance: balance + data.balance, balance: transactions.reduce((result, v) => {
result += v.balance
return result
}, 0),
currentIndex, currentIndex,
transactions: [...transactions, ...data.transactions], transactions,
} }
return { return {
@ -81,4 +93,8 @@ export function getAccountData(state: State, id: string): AccountData | null {
return get(getAccountById(state, id), 'data', null) return get(getAccountById(state, id), 'data', null)
} }
export function canCreateAccount(state: State): boolean {
return every(getAccounts(state), a => a.data.transactions.length > 0)
}
export default handleActions(handlers, state) export default handleActions(handlers, state)

7
src/reducers/settings.js

@ -8,7 +8,12 @@ import type { Settings } from 'types/common'
export type SettingsState = Object export type SettingsState = Object
const state: SettingsState = {} const state: SettingsState = {
language: 'en',
password: {
state: false,
},
}
const handlers: Object = { const handlers: Object = {
SAVE_SETTINGS: (state: SettingsState, { payload: settings }: { payload: Settings }) => ({ SAVE_SETTINGS: (state: SettingsState, { payload: settings }: { payload: Settings }) => ({

28
src/renderer/events.js

@ -6,6 +6,8 @@ import get from 'lodash/get'
import type { Accounts } from 'types/common' import type { Accounts } from 'types/common'
import { CHECK_UPDATE_TIMEOUT, SYNC_ACCOUNT_TIMEOUT } from 'constants'
import { updateDevices, addDevice, removeDevice } from 'actions/devices' import { updateDevices, addDevice, removeDevice } from 'actions/devices'
import { syncAccount } from 'actions/accounts' import { syncAccount } from 'actions/accounts'
import { setUpdateStatus } from 'reducers/update' import { setUpdateStatus } from 'reducers/update'
@ -16,10 +18,6 @@ type MsgPayload = {
data: any, data: any,
} }
// wait a bit before launching update check
const CHECK_UPDATE_TIMEOUT = 3e3
const SYNC_ACCOUNT_TIMEOUT = 5e3
let syncAccounts = true let syncAccounts = true
let syncTimeout let syncTimeout
@ -39,11 +37,15 @@ export function sendSyncEvent(channel: string, msgType: string, data: any): any
export function startSyncAccounts(accounts: Accounts) { export function startSyncAccounts(accounts: Accounts) {
syncAccounts = true syncAccounts = true
sendEvent('accounts', 'sync.all', { sendEvent('accounts', 'sync.all', {
accounts: Object.entries(accounts).map(([id, account]: [string, any]) => ({ accounts: Object.entries(accounts).map(([id, account]: [string, any]) => {
id, const currentIndex = get(account, 'data.currentIndex', 0)
currentIndex: get(account, 'data.currentIndex', 0), return {
})), id,
currentIndex,
}
}),
}) })
} }
@ -52,13 +54,19 @@ export function stopSyncAccounts() {
clearTimeout(syncTimeout) clearTimeout(syncTimeout)
} }
export function checkUpdates() {
setTimeout(() => sendEvent('msg', 'updater.init'), CHECK_UPDATE_TIMEOUT)
}
export default ({ store, locked }: { store: Object, locked: boolean }) => { export default ({ store, locked }: { store: Object, locked: boolean }) => {
const handlers = { const handlers = {
accounts: { accounts: {
sync: { sync: {
success: accounts => { success: accounts => {
if (syncAccounts) { if (syncAccounts) {
accounts.forEach(account => store.dispatch(syncAccount(account))) accounts.forEach(
account => account.transactions.length > 0 && store.dispatch(syncAccount(account)),
)
syncTimeout = setTimeout(() => { syncTimeout = setTimeout(() => {
const newAccounts = getAccounts(store.getState()) const newAccounts = getAccounts(store.getState())
startSyncAccounts(newAccounts) startSyncAccounts(newAccounts)
@ -107,6 +115,6 @@ export default ({ store, locked }: { store: Object, locked: boolean }) => {
if (__PROD__) { if (__PROD__) {
// Start check of eventual updates // Start check of eventual updates
setTimeout(() => sendEvent('msg', 'updater.init'), CHECK_UPDATE_TIMEOUT) checkUpdates()
} }
} }

55
yarn.lock

@ -93,31 +93,30 @@
redux "^3.7.2" redux "^3.7.2"
redux-thunk "^2.2.0" redux-thunk "^2.2.0"
"@ledgerhq/hw-app-btc@^2.0.5": "@ledgerhq/hw-app-btc@^2.1.0":
version "2.0.5" version "2.1.0"
resolved "https://registry.yarnpkg.com/@ledgerhq/hw-app-btc/-/hw-app-btc-2.0.5.tgz#44dabd18c4dcc68127869d384b06f0601c4a7a0e" resolved "https://registry.yarnpkg.com/@ledgerhq/hw-app-btc/-/hw-app-btc-2.1.0.tgz#6f7354ac4838eda4d78252b4fe984cd034a317bf"
dependencies: dependencies:
"@ledgerhq/hw-transport" "^2.0.5" "@ledgerhq/hw-transport" "^2.1.0"
"@ledgerhq/hw-app-eth@^2.0.5": "@ledgerhq/hw-app-eth@^2.1.0":
version "2.0.5" version "2.1.0"
resolved "https://registry.yarnpkg.com/@ledgerhq/hw-app-eth/-/hw-app-eth-2.0.5.tgz#844d938d7985f498058e7bd5cd48bc86b510d5ec" resolved "https://registry.yarnpkg.com/@ledgerhq/hw-app-eth/-/hw-app-eth-2.1.0.tgz#66dfbe4b11be9e22ab5574db603cd70aa9b82bf0"
dependencies: dependencies:
"@ledgerhq/hw-transport" "^2.0.5" "@ledgerhq/hw-transport" "^2.1.0"
"@ledgerhq/hw-transport-node-hid@^2.0.6": "@ledgerhq/hw-transport-node-hid@^2.1.0":
version "2.0.6" version "2.1.0"
resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport-node-hid/-/hw-transport-node-hid-2.0.6.tgz#29e76b05156218ccd1fe4c78c887829ac9598f98" resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport-node-hid/-/hw-transport-node-hid-2.1.0.tgz#bcaeab27feb7d869f8a8d96cee1e4934f3c87040"
dependencies: dependencies:
"@ledgerhq/hw-transport" "^2.0.5" "@ledgerhq/hw-transport" "^2.1.0"
node-hid "^0.7.2" node-hid "^0.7.2"
"@ledgerhq/hw-transport@^2.0.5": "@ledgerhq/hw-transport@^2.1.0":
version "2.0.5" version "2.1.0"
resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport/-/hw-transport-2.0.5.tgz#5070a0e8dfb22f365b08dcf10fb03a8bf44fa5bf" resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport/-/hw-transport-2.1.0.tgz#7d8460a2ea8d5344796482458b6adb11c1cfc706"
dependencies: dependencies:
events "^1.1.1" events "^1.1.1"
invariant "^2.2.0"
"@storybook/addon-actions@^3.3.10": "@storybook/addon-actions@^3.3.10":
version "3.3.10" version "3.3.10"
@ -2411,9 +2410,9 @@ color@^0.11.0:
color-convert "^1.3.0" color-convert "^1.3.0"
color-string "^0.3.0" color-string "^0.3.0"
color@^2.0.1: color@^3.0.0:
version "2.0.1" version "3.0.0"
resolved "https://registry.yarnpkg.com/color/-/color-2.0.1.tgz#e4ed78a3c4603d0891eba5430b04b86314f4c839" resolved "https://registry.yarnpkg.com/color/-/color-3.0.0.tgz#d920b4328d534a3ac8295d68f7bd4ba6c427be9a"
dependencies: dependencies:
color-convert "^1.9.1" color-convert "^1.9.1"
color-string "^1.5.2" color-string "^1.5.2"
@ -3570,9 +3569,9 @@ eslint-module-utils@^2.1.1:
debug "^2.6.8" debug "^2.6.8"
pkg-dir "^1.0.0" pkg-dir "^1.0.0"
eslint-plugin-flowtype@^2.41.1: eslint-plugin-flowtype@^2.42.0:
version "2.41.1" version "2.42.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-flowtype/-/eslint-plugin-flowtype-2.41.1.tgz#0996e1ea1d501dfc945802453a304ae9e8098b78" resolved "https://registry.yarnpkg.com/eslint-plugin-flowtype/-/eslint-plugin-flowtype-2.42.0.tgz#7fcc98df4ed9482a22ac10ba4ca48d649c4c733a"
dependencies: dependencies:
lodash "^4.15.0" lodash "^4.15.0"
@ -4063,9 +4062,9 @@ flatten@^1.0.2:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782" resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782"
flow-bin@^0.63.1: flow-bin@^0.64.0:
version "0.63.1" version "0.64.0"
resolved "https://registry.yarnpkg.com/flow-bin/-/flow-bin-0.63.1.tgz#ab00067c197169a5fb5b4996c8f6927b06694828" resolved "https://registry.yarnpkg.com/flow-bin/-/flow-bin-0.64.0.tgz#ddd3fb3b183ab1ab35a5d5dec9caf5ebbcded167"
flow-typed@^2.2.3: flow-typed@^2.2.3:
version "2.2.3" version "2.2.3"
@ -7323,9 +7322,9 @@ react-html-attributes@^1.3.0:
dependencies: dependencies:
html-element-attributes "^1.0.0" html-element-attributes "^1.0.0"
react-i18next@^7.3.1: react-i18next@^7.3.2:
version "7.3.1" version "7.3.2"
resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-7.3.1.tgz#b0ca03db9cc4d4067b6481d09d902a5166d620a0" resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-7.3.2.tgz#5036dc0371808bd8afe0c9a4a738ebd39721e33b"
dependencies: dependencies:
hoist-non-react-statics "2.3.1" hoist-non-react-statics "2.3.1"
html-parse-stringify2 "2.0.1" html-parse-stringify2 "2.0.1"

Loading…
Cancel
Save