From 5decfccd7f233190cd210a0c9c46698a4c59dbdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=ABck=20V=C3=A9zien?= Date: Tue, 23 Jan 2018 13:59:49 +0100 Subject: [PATCH] Add sync feature, change AppRegionDrag, add total balance --- flow-defs/globals.js | 2 + flow-defs/module.js | 2 + flow-defs/process.js | 3 + package.json | 9 +- src/actions/accounts.js | 23 +----- src/components/AccountPage.js | 21 +---- src/components/AppRegionDrag.js | 6 +- src/components/DashboardPage.js | 17 ++-- src/components/SideBar/index.js | 8 +- src/components/TopBar.js | 68 ++++++++++++--- src/components/modals/AddAccount.js | 18 ++-- src/helpers/btc.js | 111 ++++++++++++++++++++++++- src/internals/accounts/index.js | 1 + src/internals/accounts/sync.js | 26 ++++++ src/internals/index.js | 29 +++++++ src/internals/usb/index.js | 28 +------ src/internals/usb/wallet/accounts.js | 118 +++++++-------------------- src/internals/usb/wallet/index.js | 8 +- src/main/app.js | 8 +- src/main/bridge.js | 14 ++-- src/reducers/accounts.js | 12 +++ src/renderer/events.js | 23 ++++++ src/renderer/index.js | 7 +- webpack/internals.config.js | 5 +- yarn.lock | 10 +-- 25 files changed, 355 insertions(+), 222 deletions(-) create mode 100644 src/internals/accounts/index.js create mode 100644 src/internals/accounts/sync.js create mode 100644 src/internals/index.js diff --git a/flow-defs/globals.js b/flow-defs/globals.js index ee6bc5da..b37e6093 100644 --- a/flow-defs/globals.js +++ b/flow-defs/globals.js @@ -1,3 +1,5 @@ +/* eslint-disable */ + declare var __DEV__: boolean declare var __PROD__: boolean declare var __ENV__: string diff --git a/flow-defs/module.js b/flow-defs/module.js index dd0331a6..55df3acd 100644 --- a/flow-defs/module.js +++ b/flow-defs/module.js @@ -1,3 +1,5 @@ +/* eslint-disable */ + declare var module: { hot: { accept(path: string, callback: () => void): void, diff --git a/flow-defs/process.js b/flow-defs/process.js index 02e1b6a0..3a089aeb 100644 --- a/flow-defs/process.js +++ b/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, } diff --git a/package.json b/package.json index 7c6e070b..3fc889b6 100644 --- a/package.json +++ b/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", diff --git a/src/actions/accounts.js b/src/actions/accounts.js index e6cca59b..a0642bbb 100644 --- a/src/actions/accounts.js +++ b/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)) } diff --git a/src/components/AccountPage.js b/src/components/AccountPage.js index 9f5217a9..bfcc454c 100644 --- a/src/components/AccountPage.js +++ b/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 { render() { const { account, accountData } = this.props @@ -49,7 +36,7 @@ class AccountPage extends PureComponent { - {formatBTC(accountData.balance)} + {format(accountData.balance)} @@ -59,7 +46,7 @@ class AccountPage extends PureComponent { {accountData.transactions.map(tr => ( {'-'} - {formatBTC(tr.balance)} + {format(tr.balance)} ))} diff --git a/src/components/AppRegionDrag.js b/src/components/AppRegionDrag.js index 0bd42763..bb22212f 100644 --- a/src/components/AppRegionDrag.js +++ b/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; ` diff --git a/src/components/DashboardPage.js b/src/components/DashboardPage.js index 1544391a..0ead5e39 100644 --- a/src/components/DashboardPage.js +++ b/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 { render() { - const { currentDevice } = this.props - return currentDevice !== null ? ( - - Your current device: {currentDevice.path} - - ) : null + const { totalBalance } = this.props + return Your balance: {format(totalBalance)} } } diff --git a/src/components/SideBar/index.js b/src/components/SideBar/index.js index 125d30ed..07da6439 100644 --- a/src/components/SideBar/index.js +++ b/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 { {'Accounts'}
{Object.entries(accounts).map(([id, account]: [string, any]) => ( - + {account.name} ))} diff --git a/src/components/TopBar.js b/src/components/TopBar.js index 29e03298..a311425a 100644 --- a/src/components/TopBar.js +++ b/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 { 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 { } } + 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 { render() { const { devices, hasPassword } = this.props - const { changeDevice } = this.state + const { changeDevice, sync } = this.state return ( @@ -94,16 +140,16 @@ class TopBar extends PureComponent { ))} )} - - {hasPassword && } - + + + {sync.progress === true + ? 'sync...' + : sync.fail === true ? 'sync fail :(' : 'sync finish!'} + + + {hasPassword && } + + ) diff --git a/src/components/modals/AddAccount.js b/src/components/modals/AddAccount.js index e7e29c9e..9e9ef74a 100644 --- a/src/components/modals/AddAccount.js +++ b/src/components/modals/AddAccount.js @@ -151,6 +151,11 @@ class AddAccountModal extends PureComponent { } } + 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 { 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 { } } - 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', diff --git a/src/helpers/btc.js b/src/helpers/btc.js index 0eff1d1e..30269a07 100644 --- a/src/helpers/btc.js +++ b/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) { + 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) +} diff --git a/src/internals/accounts/index.js b/src/internals/accounts/index.js new file mode 100644 index 00000000..3a7b6968 --- /dev/null +++ b/src/internals/accounts/index.js @@ -0,0 +1 @@ +export sync from './sync' diff --git a/src/internals/accounts/sync.js b/src/internals/accounts/sync.js new file mode 100644 index 00000000..f5ccb044 --- /dev/null +++ b/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 }) => { + 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) + } + }, +}) diff --git a/src/internals/index.js b/src/internals/index.js new file mode 100644 index 00000000..67eabe1b --- /dev/null +++ b/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) +}) diff --git a/src/internals/usb/index.js b/src/internals/usb/index.js index e9a57995..582551f1 100644 --- a/src/internals/usb/index.js +++ b/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' diff --git a/src/internals/usb/wallet/accounts.js b/src/internals/usb/wallet/accounts.js index 802515b4..c6202284 100644 --- a/src/internals/usb/wallet/accounts.js +++ b/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) } diff --git a/src/internals/usb/wallet/index.js b/src/internals/usb/wallet/index.js index f465ce31..f00ab756 100644 --- a/src/internals/usb/wallet/index.js +++ b/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) } }, }) diff --git a/src/main/app.js b/src/main/app.js index 0403bd5f..e3651213 100644 --- a/src/main/app.js +++ b/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()) }) diff --git a/src/main/bridge.js b/src/main/bridge.js index 06cbdaf5..d1bb2883 100644 --- a/src/main/bridge.js +++ b/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: { diff --git a/src/reducers/accounts.js b/src/reducers/accounts.js index c2a3febe..17e2569c 100644 --- a/src/reducers/accounts.js +++ b/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 } diff --git a/src/renderer/events.js b/src/renderer/events.js index eea7325a..231e6357 100644 --- a/src/renderer/events.js +++ b/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) diff --git a/src/renderer/index.js b/src/renderer/index.js index b3ea74f3..7666e381 100644 --- a/src/renderer/index.js +++ b/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({Comp}, rootNode) diff --git a/webpack/internals.config.js b/webpack/internals.config.js index 9bcc0893..05f886b4 100644 --- a/webpack/internals.config.js +++ b/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, diff --git a/yarn.lock b/yarn.lock index 3e8a120f..c0de29e4 100644 --- a/yarn.lock +++ b/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"