Browse Source

Add sync feature, change AppRegionDrag, add total balance

master
Loëck Vézien 7 years ago
parent
commit
5decfccd7f
No known key found for this signature in database GPG Key ID: CBCDCE384E853AC4
  1. 2
      flow-defs/globals.js
  2. 2
      flow-defs/module.js
  3. 3
      flow-defs/process.js
  4. 9
      package.json
  5. 23
      src/actions/accounts.js
  6. 21
      src/components/AccountPage.js
  7. 6
      src/components/AppRegionDrag.js
  8. 17
      src/components/DashboardPage.js
  9. 8
      src/components/SideBar/index.js
  10. 68
      src/components/TopBar.js
  11. 18
      src/components/modals/AddAccount.js
  12. 111
      src/helpers/btc.js
  13. 1
      src/internals/accounts/index.js
  14. 26
      src/internals/accounts/sync.js
  15. 29
      src/internals/index.js
  16. 28
      src/internals/usb/index.js
  17. 118
      src/internals/usb/wallet/accounts.js
  18. 8
      src/internals/usb/wallet/index.js
  19. 8
      src/main/app.js
  20. 14
      src/main/bridge.js
  21. 12
      src/reducers/accounts.js
  22. 23
      src/renderer/events.js
  23. 7
      src/renderer/index.js
  24. 5
      webpack/internals.config.js
  25. 10
      yarn.lock

2
flow-defs/globals.js

@ -1,3 +1,5 @@
/* eslint-disable */
declare var __DEV__: boolean
declare var __PROD__: boolean
declare var __ENV__: string

2
flow-defs/module.js

@ -1,3 +1,5 @@
/* eslint-disable */
declare var module: {
hot: {
accept(path: string, callback: () => void): void,

3
flow-defs/process.js

@ -1,7 +1,10 @@
/* eslint-disable */
declare var process: {
send(args: any): void,
on(event: string, args: any): void,
nextTick(callback: Function): void,
setMaxListeners(any): void,
title: string,
env: Object,
}

9
package.json

@ -20,17 +20,16 @@
"storybook": "start-storybook -p 4444"
},
"lint-staged": {
"*.js": [
"eslint --fix",
"prettier --write",
"git add"
]
"*.js": ["eslint --fix", "prettier --write", "git add"]
},
"electronWebpack": {
"renderer": {
"webpackConfig": "./webpack/renderer.config.js"
}
},
"resolutions": {
"webpack-sources": "1.0.1"
},
"dependencies": {
"@ledgerhq/hw-app-btc": "^1.1.2-beta.068e2a14",
"@ledgerhq/hw-app-eth": "^1.1.2-beta.068e2a14",

23
src/actions/accounts.js

@ -7,10 +7,6 @@ import type { Dispatch } from 'redux'
import db from 'helpers/db'
import type { Account } from 'types/common'
import type { State } from 'reducers'
// import { getAccounts } from 'reducers/accounts'
// import { getAddressData } from 'helpers/btc'
export type AddAccount = Account => { type: string, payload: Account }
export const addAccount: AddAccount = payload => ({
@ -24,22 +20,9 @@ export const fetchAccounts: FetchAccounts = () => ({
payload: db('accounts'),
})
// const setAccountData = createAction('SET_ACCOUNT_DATA', (accountID, data) => ({ accountID, data }))
const setAccountData = createAction('SET_ACCOUNT_DATA', (accountID, data) => ({ accountID, data }))
export const syncAccount: Function = account => async (dispatch: Dispatch<*>) => {
// const { address } = account
// const addressData = await getAddressData(address)
// dispatch(setAccountData(account.id, addressData))
}
export const syncAccounts = () => async (dispatch: Dispatch<*>, getState: () => State) => {
// const state = getState()
// const accountsMap = getAccounts(state)
// const accounts = values(accountsMap)
//
// console.log(`syncing accounts...`)
//
// await Promise.all(accounts.map(account => dispatch(syncAccount(account))))
//
// console.log(`all accounts synced`)
const { id, ...data } = account
dispatch(setAccountData(id, data))
}

21
src/components/AccountPage.js

@ -2,11 +2,12 @@
import React, { PureComponent, Fragment } from 'react'
import { connect } from 'react-redux'
import { formatCurrencyUnit } from 'ledger-wallet-common/lib/data/currency'
import type { MapStateToProps } from 'react-redux'
import type { Account, AccountData } from 'types/common'
import { format } from 'helpers/btc'
import { getAccountById, getAccountData } from 'reducers/accounts'
import Box, { Card } from 'components/base/Box'
@ -22,20 +23,6 @@ const mapStateToProps: MapStateToProps<*, *, *> = (state, props) => ({
accountData: getAccountData(state, props.match.params.id),
})
function formatBTC(v) {
return formatCurrencyUnit(
{
name: 'bitcoin',
code: 'BTC',
symbol: 'b',
magnitude: 8,
},
v,
true,
true,
)
}
class AccountPage extends PureComponent<Props> {
render() {
const { account, accountData } = this.props
@ -49,7 +36,7 @@ class AccountPage extends PureComponent<Props> {
<Fragment>
<Box horizontal flow={3}>
<Box flex={1}>
<Card title="Balance">{formatBTC(accountData.balance)}</Card>
<Card title="Balance">{format(accountData.balance)}</Card>
</Box>
<Box flex={1}>
<Card title="Receive" />
@ -59,7 +46,7 @@ class AccountPage extends PureComponent<Props> {
{accountData.transactions.map(tr => (
<Box horizontal key={tr.hash}>
<Box grow>{'-'}</Box>
<Box>{formatBTC(tr.balance)}</Box>
<Box>{format(tr.balance)}</Box>
</Box>
))}
</Card>

6
src/components/AppRegionDrag.js

@ -4,10 +4,6 @@ import styled from 'styled-components'
export default styled.div`
-webkit-app-region: drag;
background: ${p => p.theme.colors.white};
height: 40px;
left: 0;
position: absolute;
right: 0;
top: 0;
z-index: -1;
`

17
src/components/DashboardPage.js

@ -4,28 +4,25 @@ import React, { PureComponent } from 'react'
import { connect } from 'react-redux'
import type { MapStateToProps } from 'react-redux'
import type { Device } from 'types/common'
import { getCurrentDevice } from 'reducers/devices'
import { format } from 'helpers/btc'
import { getTotalBalance } from 'reducers/accounts'
import Box from 'components/base/Box'
const mapStateToProps: MapStateToProps<*, *, *> = state => ({
currentDevice: getCurrentDevice(state),
totalBalance: getTotalBalance(state),
})
type Props = {
currentDevice: Device | null,
totalBalance: number,
}
class DashboardPage extends PureComponent<Props> {
render() {
const { currentDevice } = this.props
return currentDevice !== null ? (
<Box style={{ wordBreak: 'break-word' }} p={20}>
Your current device: {currentDevice.path}
</Box>
) : null
const { totalBalance } = this.props
return <Box p={20}>Your balance: {format(totalBalance)}</Box>
}
}

8
src/components/SideBar/index.js

@ -10,6 +10,7 @@ import type { Accounts } from 'types/common'
import { openModal } from 'reducers/modals'
import { getAccounts } from 'reducers/accounts'
import { format } from 'helpers/btc'
import { rgba } from 'styles/helpers'
import Box, { GrowScroll } from 'components/base/Box'
@ -29,7 +30,6 @@ const Container = styled(Box).attrs({
noShrink: true,
})`
background-color: ${p => rgba(p.theme.colors[p.bg], process.platform === 'darwin' ? 0.4 : 1)};
padding-top: 40px;
width: 250px;
`
@ -77,11 +77,7 @@ class SideBar extends PureComponent<Props> {
<CapsSubtitle>{'Accounts'}</CapsSubtitle>
<div>
{Object.entries(accounts).map(([id, account]: [string, any]) => (
<Item
linkTo={`/account/${id}`}
desc={`${account.type.toUpperCase()} ${account.data.balance}`}
key={id}
>
<Item linkTo={`/account/${id}`} desc={format(account.data.balance)} key={id}>
{account.name}
</Item>
))}

68
src/components/TopBar.js

@ -2,6 +2,7 @@
import React, { PureComponent, Fragment } from 'react'
import { connect } from 'react-redux'
import { ipcRenderer } from 'electron'
import type { MapStateToProps, MapDispatchToProps } from 'react-redux'
import type { Device, Devices } from 'types/common'
@ -35,6 +36,10 @@ type Props = {
}
type State = {
changeDevice: boolean,
sync: {
progress: null | boolean,
fail: boolean,
},
}
const hasDevices = props => props.currentDevice === null && props.devices.length > 0
@ -42,6 +47,14 @@ const hasDevices = props => props.currentDevice === null && props.devices.length
class TopBar extends PureComponent<Props, State> {
state = {
changeDevice: hasDevices(this.props),
sync: {
progress: null,
fail: false,
},
}
componentDidMount() {
ipcRenderer.on('msg', this.handleAccountSync)
}
componentWillReceiveProps(nextProps) {
@ -52,6 +65,39 @@ class TopBar extends PureComponent<Props, State> {
}
}
componentWillUnmount() {
ipcRenderer.removeListener('msg', this.handleAccountSync)
}
handleAccountSync = (e, { type }) => {
if (type === 'accounts.sync.progress') {
this.setState({
sync: {
progress: true,
fail: false,
},
})
}
if (type === 'accounts.sync.fail') {
this.setState({
sync: {
progress: null,
fail: true,
},
})
}
if (type === 'accounts.sync.success') {
this.setState({
sync: {
progress: false,
fail: false,
},
})
}
}
handleChangeDevice = () => {
const { devices } = this.props
@ -76,7 +122,7 @@ class TopBar extends PureComponent<Props, State> {
render() {
const { devices, hasPassword } = this.props
const { changeDevice } = this.state
const { changeDevice, sync } = this.state
return (
<Fragment>
@ -94,16 +140,16 @@ class TopBar extends PureComponent<Props, State> {
))}
</Overlay>
)}
<Box
bg="white"
noShrink
style={{ height: 60 }}
justify="flex-end"
align="center"
horizontal
>
{hasPassword && <LockApplication onLock={this.handleLock} />}
<CountDevices count={devices.length} onChangeDevice={this.handleChangeDevice} />
<Box bg="white" noShrink style={{ height: 60 }} align="center" horizontal>
<Box grow>
{sync.progress === true
? 'sync...'
: sync.fail === true ? 'sync fail :(' : 'sync finish!'}
</Box>
<Box justify="flex-end" horizontal>
{hasPassword && <LockApplication onLock={this.handleLock} />}
<CountDevices count={devices.length} onChangeDevice={this.handleChangeDevice} />
</Box>
</Box>
</Fragment>
)

18
src/components/modals/AddAccount.js

@ -151,6 +151,11 @@ class AddAccountModal extends PureComponent<Props, State> {
}
}
componentWillUnmount() {
ipcRenderer.removeListener('msg', this.handleWalletRequest)
clearTimeout(this._timeout)
}
getWalletInfos() {
const { inputValue } = this.state
const { currentDevice, accounts } = this.props
@ -159,7 +164,7 @@ class AddAccountModal extends PureComponent<Props, State> {
return
}
sendEvent('usb', 'wallet.request', {
sendEvent('usb', 'wallet.getAccounts', {
path: currentDevice.path,
wallet: inputValue.wallet,
currentAccounts: Object.keys(accounts),
@ -190,24 +195,19 @@ class AddAccountModal extends PureComponent<Props, State> {
}
}
componentWillUmount() {
ipcRenderer.removeListener('msg', this.handleWalletRequest)
clearTimeout(this._timeout)
}
handleWalletRequest = (e, { data, type }) => {
if (type === 'wallet.request.progress') {
if (type === 'wallet.getAccounts.progress') {
this.setState({
step: 'inProgress',
progress: data,
})
}
if (type === 'wallet.request.fail') {
if (type === 'wallet.getAccounts.fail') {
this._timeout = setTimeout(() => this.getWalletInfos(), 1e3)
}
if (type === 'wallet.request.success') {
if (type === 'wallet.getAccounts.success') {
this.setState({
accounts: data,
step: 'listAccounts',

111
src/helpers/btc.js

@ -1,5 +1,36 @@
export function computeTransaction(addresses) {
return transaction => {
// @flow
import axios from 'axios'
import bitcoin from 'bitcoinjs-lib'
import { formatCurrencyUnit } from 'ledger-wallet-common/lib/data/currency'
export function format(v: string | number, options: Object = { alwaysShowSign: true }) {
return formatCurrencyUnit(
{
name: 'bitcoin',
code: 'BTC',
symbol: 'b',
magnitude: 8,
},
Number(v),
options.alwaysShowSign,
true,
)
}
export const networks = [
{
...bitcoin.networks.bitcoin,
family: 1,
},
{
...bitcoin.networks.testnet,
family: 1,
},
]
export function computeTransaction(addresses: Array<*>) {
return (transaction: Object) => {
const outputVal = transaction.outputs
.filter(o => addresses.includes(o.address))
.reduce((acc, cur) => acc + cur.value, 0)
@ -13,3 +44,79 @@ export function computeTransaction(addresses) {
}
}
}
export function getTransactions(addresses: Array<string>) {
return axios.get(
`http://api.ledgerwallet.com/blockchain/v2/btc_testnet/addresses/${addresses.join(
',',
)}/transactions?noToken=true`,
)
}
export async function getAccount({
currentIndex = 0,
hdnode,
segwit,
network,
}: {
currentIndex?: number,
hdnode: Object,
segwit: boolean,
network: Object,
}) {
const script = segwit ? parseInt(network.scriptHash, 10) : parseInt(network.pubKeyHash, 10)
let transactions = []
const pubKeyToSegwitAddress = (pubKey, scriptVersion) => {
const script = [0x00, 0x14].concat(Array.from(bitcoin.crypto.hash160(pubKey)))
const hash160 = bitcoin.crypto.hash160(new Uint8Array(script))
return bitcoin.address.toBase58Check(hash160, scriptVersion)
}
const getPublicAddress = ({ hdnode, path, script, segwit }) => {
hdnode = hdnode.derivePath(path)
if (!segwit) {
return hdnode.getAddress().toString()
}
return pubKeyToSegwitAddress(hdnode.getPublicKeyBuffer(), script)
}
const nextPath = (index = 0) => {
const count = 20
const getAddress = path => getPublicAddress({ hdnode, path, script, segwit })
return Promise.all(
Array.from(Array(count).keys()).map(v =>
Promise.all([
getAddress(`0/${v + index}`), // external chain
getAddress(`1/${v + index}`), // internal chain
]),
),
).then(async results => {
const currentAddresses = results.reduce((result, v) => [...result, ...v], [])
const { data: { txs } } = await getTransactions(currentAddresses)
transactions = [...transactions, ...txs.map(computeTransaction(currentAddresses))]
if (txs.length > 0) {
return nextPath(index + (count - 1))
}
return {
balance: transactions.reduce((result, v) => {
result += v.balance
return result
}, 0),
transactions,
}
})
}
return nextPath(currentIndex)
}
export function getHDNode({ xpub58, network }: { xpub58: string, network: Object }) {
return bitcoin.HDNode.fromBase58(xpub58, network)
}

1
src/internals/accounts/index.js

@ -0,0 +1 @@
export sync from './sync'

26
src/internals/accounts/sync.js

@ -0,0 +1,26 @@
// @flow
import { getAccount, getHDNode, networks } from 'helpers/btc'
export default (send: Function) => ({
all: async ({ accounts }: { accounts: Array<Object> }) => {
const network = networks[1]
send('accounts.sync.progress', null, { kill: false })
const syncAccount = ({ id }) => {
const hdnode = getHDNode({ xpub58: id, network })
return getAccount({ hdnode, network, segwit: true }).then(account => ({
id,
...account,
}))
}
try {
const result = await Promise.all(accounts.map(syncAccount))
send('accounts.sync.success', result)
} catch (err) {
send('accounts.sync.fail', err.stack || err)
}
},
})

29
src/internals/index.js

@ -0,0 +1,29 @@
// @flow
import objectPath from 'object-path'
process.title = `ledger-wallet-desktop-${process.env.FORK_TYPE}`
process.setMaxListeners(Infinity)
function sendEvent(type: string, data: any, options: Object = { kill: true }) {
process.send({ type, data, options })
}
// $FlowFixMe
const func = require(`./${process.env.FORK_TYPE}`) // eslint-disable-line import/no-dynamic-require
const handlers = Object.keys(func).reduce((result, key) => {
result[key] = func[key](sendEvent)
return result
}, {})
process.on('message', payload => {
const { type, data } = payload
const handler = objectPath.get(handlers, type)
if (!handler) {
return
}
handler(data)
})

28
src/internals/usb/index.js

@ -1,26 +1,2 @@
// @flow
import objectPath from 'object-path'
import devices from './devices'
import wallet from './wallet'
process.title = 'ledger-wallet-desktop-usb'
function sendEvent(type: string, data: any, options: Object = { kill: true }) {
process.send({ type, data, options })
}
const handlers = {
devices: devices(sendEvent),
wallet: wallet(sendEvent),
}
process.on('message', payload => {
const { type, data } = payload
const handler = objectPath.get(handlers, type)
if (!handler) {
return
}
handler(data)
})
export devices from './devices'
export wallet from './wallet'

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

@ -1,22 +1,14 @@
// @flow
/* eslint-disable no-bitwise */
import axios from 'axios'
import bitcoin from 'bitcoinjs-lib'
import bs58check from 'bs58check'
import Btc from '@ledgerhq/hw-app-btc'
import { computeTransaction } from 'helpers/btc'
import { getAccount, getHDNode, networks } from 'helpers/btc'
export const networks = [
{
...bitcoin.networks.bitcoin,
family: 1,
},
{
...bitcoin.networks.testnet,
family: 1,
},
]
type Coin = 0 | 1
function getCompressPublicKey(publicKey) {
let compressedKeyIndex
@ -29,7 +21,7 @@ function getCompressPublicKey(publicKey) {
return result
}
function parseHexString(str) {
function parseHexString(str: any) {
const result = []
while (str.length >= 2) {
result.push(parseInt(str.substring(0, 2), 16))
@ -40,10 +32,10 @@ function parseHexString(str) {
function createXpub({ depth, fingerprint, childnum, chainCode, publicKey, network }) {
return [
network.toString(16).padStart(8, 0),
depth.toString(16).padStart(2, 0),
fingerprint.toString(16).padStart(8, 0),
childnum.toString(16).padStart(8, 0),
network.toString(16).padStart(8, '0'),
depth.toString(16).padStart(2, '0'),
fingerprint.toString(16).padStart(8, '0'),
childnum.toString(16).padStart(8, '0'),
chainCode,
publicKey,
].join('')
@ -55,77 +47,23 @@ function encodeBase58Check(vchIn) {
return bs58check.encode(Buffer.from(vchIn))
}
function getPath({ coin, account, segwit }) {
function getPath({ coin, account, segwit }: { coin: Coin, account?: any, segwit: boolean }) {
return `${segwit ? 49 : 44}'/${coin}'${account !== undefined ? `/${account}'` : ''}`
}
function pubKeyToSegwitAddress(pubKey, scriptVersion) {
const script = [0x00, 0x14].concat(Array.from(bitcoin.crypto.hash160(pubKey)))
const hash160 = bitcoin.crypto.hash160(new Uint8Array(script))
return bitcoin.address.toBase58Check(hash160, scriptVersion)
}
function getPublicAddress(hdnode, path, script, segwit) {
hdnode = hdnode.derivePath(path)
if (!segwit) {
return hdnode.getAddress().toString()
}
return pubKeyToSegwitAddress(hdnode.getPublicKeyBuffer(), script)
}
function getTransactions(addresses) {
return axios.get(
`http://api.ledgerwallet.com/blockchain/v2/btc_testnet/addresses/${addresses.join(
',',
)}/transactions?noToken=true`,
)
}
export async function getAccount({ hdnode, segwit, network }) {
const script = segwit ? parseInt(network.scriptHash, 10) : parseInt(network.pubKeyHash, 10)
let transactions = []
const nextPath = start => {
const count = 20
const getAddress = path => getPublicAddress(hdnode, path, script, segwit)
return Promise.all(
Array.from(Array(count).keys()).map(v =>
Promise.all([
getAddress(`0/${v + start}`), // external chain
getAddress(`1/${v + start}`), // internal chain
]),
),
).then(async results => {
const currentAddresses = results.reduce((result, v) => [...result, ...v], [])
const { data: { txs } } = await getTransactions(currentAddresses)
transactions = [...transactions, ...txs.map(computeTransaction(currentAddresses))]
if (txs.length > 0) {
return nextPath(start + (count - 1))
}
return {
balance: transactions.reduce((result, v) => {
result += v.balance
return result
}, 0),
transactions,
}
})
}
return nextPath(0)
}
export function getHDNode({ xpub58, network }) {
return bitcoin.HDNode.fromBase58(xpub58, network)
}
export default async ({ transport, currentAccounts, onProgress, coin = 1, segwit = true }) => {
export default async ({
transport,
currentAccounts,
onProgress,
coin = 1,
segwit = true,
}: {
transport: Object,
currentAccounts: Array<*>,
onProgress: Function,
coin?: Coin,
segwit?: boolean,
}) => {
const btc = new Btc(transport)
const network = networks[coin]
@ -171,7 +109,7 @@ export default async ({ transport, currentAccounts, onProgress, coin = 1, segwit
const xpub58 = await getXpub58ByAccount({ account: currentAccount, network })
if (currentAccounts.includes(xpub58)) {
return getAllAccounts(currentAccount + 1, accounts) // skip existing account
return getAllAccounts(currentAccount + 1, accounts) // Skip existing account
}
const hdnode = getHDNode({ xpub58, network })
@ -182,12 +120,18 @@ export default async ({ transport, currentAccounts, onProgress, coin = 1, segwit
transactions: transactions.length,
})
if (transactions.length > 0) {
const hasTransactions = transactions.length > 0
// If the first account is empty we still add it
if (currentAccount === 0 || hasTransactions) {
accounts[currentAccount] = {
id: xpub58,
balance,
transactions,
}
}
if (hasTransactions) {
return getAllAccounts(currentAccount + 1, accounts)
}

8
src/internals/usb/wallet/index.js

@ -15,7 +15,7 @@ async function getAllAccountsByWallet({ path, wallet, currentAccounts, onProgres
}
export default (sendEvent: Function) => ({
request: async ({
getAccounts: async ({
path,
wallet,
currentAccounts,
@ -29,11 +29,11 @@ export default (sendEvent: Function) => ({
path,
wallet,
currentAccounts,
onProgress: progress => sendEvent('wallet.request.progress', progress, { kill: false }),
onProgress: progress => sendEvent('wallet.getAccounts.progress', progress, { kill: false }),
})
sendEvent('wallet.request.success', data)
sendEvent('wallet.getAccounts.success', data)
} catch (err) {
sendEvent('wallet.request.fail', err.stack || err)
sendEvent('wallet.getAccounts.fail', err.stack || err)
}
},
})

8
src/main/app.js

@ -29,14 +29,10 @@ function createMainWindow() {
window.loadURL(url)
window.on('closed', () => {
window.on('close', () => {
mainWindow = null
})
ipcMain.on('renderer-ready', () => {
window.show()
})
window.webContents.on('devtools-opened', () => {
window.focus()
setImmediate(() => {
@ -76,4 +72,6 @@ app.on('ready', async () => {
}
mainWindow = createMainWindow()
ipcMain.on('renderer-ready', () => mainWindow && mainWindow.show())
})

14
src/main/bridge.js

@ -7,11 +7,15 @@ import { resolve } from 'path'
import setupAutoUpdater from './autoUpdate'
function onChannelUsb(callType) {
function onForkChannel(forkType, callType) {
return (event: any, payload) => {
const { type, data } = payload
const compute = fork(resolve(__dirname, `${__DEV__ ? '../../' : './'}dist/internals/usb`))
const compute = fork(resolve(__dirname, `${__DEV__ ? '../../' : './'}dist/internals`), [], {
env: {
FORK_TYPE: forkType,
},
})
compute.send({ type, data })
compute.on('message', payload => {
@ -31,9 +35,9 @@ function onChannelUsb(callType) {
}
}
// Forwards every usb message to usb process
ipcMain.on('usb', onChannelUsb('async'))
ipcMain.on('usb:sync', onChannelUsb('sync'))
// Forwards every `type` messages to another process
ipcMain.on('usb', onForkChannel('usb', 'async'))
ipcMain.on('accounts', onForkChannel('accounts', 'async'))
const handlers = {
updater: {

12
src/reducers/accounts.js

@ -2,6 +2,7 @@
import { handleActions } from 'redux-actions'
import get from 'lodash/get'
import reduce from 'lodash/reduce'
import type { State } from 'reducers'
import type { Account, Accounts, AccountData } from 'types/common'
@ -32,6 +33,17 @@ const handlers: Object = {
// Selectors
export function getTotalBalance(state: { accounts: AccountsState }) {
return reduce(
state.accounts,
(result, account) => {
result += account.data.balance
return result
},
0,
)
}
export function getAccounts(state: { accounts: AccountsState }) {
return state.accounts
}

23
src/renderer/events.js

@ -4,6 +4,7 @@ import { ipcRenderer } from 'electron'
import objectPath from 'object-path'
import { updateDevices, addDevice, removeDevice } from 'actions/devices'
import { syncAccount } from 'actions/accounts'
import { setUpdateStatus } from 'reducers/update'
type MsgPayload = {
@ -13,6 +14,7 @@ type MsgPayload = {
// wait a bit before launching update check
const CHECK_UPDATE_TIMEOUT = 3e3
const SYNC_ACCOUNT_TIMEOUT = 1e3
export function sendEvent(channel: string, msgType: string, data: any) {
ipcRenderer.send(channel, {
@ -28,8 +30,24 @@ export function sendSyncEvent(channel: string, msgType: string, data: any): any
})
}
function syncAccounts(accounts) {
sendEvent('accounts', 'sync.all', {
accounts: Object.entries(accounts).map(([id]: [string, any]) => ({
id,
})),
})
}
export default (store: Object) => {
const handlers = {
accounts: {
sync: {
success: accounts => {
accounts.forEach(account => store.dispatch(syncAccount(account)))
setTimeout(() => syncAccounts(store.getState().accounts), SYNC_ACCOUNT_TIMEOUT)
},
},
},
devices: {
update: devices => {
store.dispatch(updateDevices(devices))
@ -58,12 +76,17 @@ export default (store: Object) => {
handler(data)
})
const state = store.getState()
// First time, we get all devices
sendEvent('usb', 'devices.all')
// Start detection when we plug/unplug devices
sendEvent('usb', 'devices.listen')
// Start accounts sync
syncAccounts(state.accounts)
if (__PROD__) {
// Start check of eventual updates
setTimeout(() => sendEvent('msg', 'updater.init'), CHECK_UPDATE_TIMEOUT)

7
src/renderer/index.js

@ -8,7 +8,7 @@ import createHistory from 'history/createHashHistory'
import createStore from 'renderer/createStore'
import events from 'renderer/events'
import { fetchAccounts, syncAccounts } from 'actions/accounts'
import { fetchAccounts } from 'actions/accounts'
import { fetchSettings } from 'actions/settings'
import { isLocked } from 'reducers/application'
@ -20,17 +20,16 @@ const history = createHistory()
const store = createStore(history)
const rootNode = document.getElementById('app')
events(store)
store.dispatch(fetchSettings())
const state = store.getState() || {}
if (!isLocked(state)) {
store.dispatch(fetchAccounts())
store.dispatch(syncAccounts())
}
events(store)
function r(Comp) {
if (rootNode) {
render(<AppContainer>{Comp}</AppContainer>, rootNode)

5
webpack/internals.config.js

@ -20,7 +20,10 @@ module.exports = webpackMain().then(config => ({
devtool: config.devtool,
target: config.target,
entry: dirs(path.resolve(__dirname, '../src/internals')),
entry: {
...dirs(path.resolve(__dirname, '../src/internals')),
index: path.resolve(__dirname, '../src/internals/index'),
},
resolve: {
extensions: config.resolve.extensions,

10
yarn.lock

@ -8260,7 +8260,7 @@ source-map@0.5.6:
version "0.5.6"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412"
source-map@0.5.x, source-map@^0.5.3, source-map@^0.5.6, source-map@~0.5.0, source-map@~0.5.1:
source-map@0.5.x, source-map@^0.5.3, source-map@^0.5.6, source-map@~0.5.0, source-map@~0.5.1, source-map@~0.5.3:
version "0.5.7"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
@ -9210,12 +9210,12 @@ webpack-merge@^4.1.0:
dependencies:
lodash "^4.17.4"
webpack-sources@^1.0.1, webpack-sources@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.1.0.tgz#a101ebae59d6507354d71d8013950a3a8b7a5a54"
webpack-sources@1.0.1, webpack-sources@^1.0.1, webpack-sources@^1.1.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.0.1.tgz#c7356436a4d13123be2e2426a05d1dad9cbe65cf"
dependencies:
source-list-map "^2.0.0"
source-map "~0.6.1"
source-map "~0.5.3"
webpack@^3.10.0:
version "3.10.0"

Loading…
Cancel
Save