Meriadec Pillet
7 years ago
committed by
GitHub
17 changed files with 335 additions and 132 deletions
@ -1,20 +1,32 @@ |
|||
// @flow
|
|||
|
|||
import React, { PureComponent } from 'react' |
|||
import { compose } from 'redux' |
|||
import { connect } from 'react-redux' |
|||
import { translate } from 'react-i18next' |
|||
|
|||
import type { MapStateToProps } from 'react-redux' |
|||
import type { Device } from 'types/common' |
|||
|
|||
import { getCurrentDevice } from 'reducers/devices' |
|||
|
|||
import Box from 'components/base/Box' |
|||
|
|||
const mapStateToProps: MapStateToProps<*, *, *> = state => ({ |
|||
currentDevice: getCurrentDevice(state), |
|||
}) |
|||
|
|||
type Props = { |
|||
devices: Array<Object>, |
|||
t: (string, ?Object) => string, |
|||
currentDevice: Device | null, |
|||
} |
|||
|
|||
class Home extends PureComponent<Props> { |
|||
render() { |
|||
const { devices, t } = this.props |
|||
return <div>{t('common.connectedDevices', { count: devices.length })}</div> |
|||
const { currentDevice } = this.props |
|||
return currentDevice !== null ? ( |
|||
<Box style={{ wordBreak: 'break-word' }} p={20}> |
|||
Your current device: {currentDevice.path} |
|||
</Box> |
|||
) : null |
|||
} |
|||
} |
|||
|
|||
export default compose(connect(({ devices }: Props): Object => ({ devices })), translate())(Home) |
|||
export default connect(mapStateToProps)(Home) |
|||
|
@ -1,17 +1,124 @@ |
|||
// @flow
|
|||
|
|||
import React, { PureComponent } from 'react' |
|||
import React, { PureComponent, Fragment } from 'react' |
|||
import { connect } from 'react-redux' |
|||
|
|||
import type { MapStateToProps, MapDispatchToProps } from 'react-redux' |
|||
import type { Device, Devices } from 'types/common' |
|||
import type { deviceChooseType } from 'actions/devices' |
|||
|
|||
import { getDevices, getCurrentDevice } from 'reducers/devices' |
|||
|
|||
import { deviceChoose } from 'actions/devices' |
|||
|
|||
import Box from 'components/base/Box' |
|||
import Overlay from 'components/base/Overlay' |
|||
|
|||
const mapStateToProps: MapStateToProps<*, *, *> = state => ({ |
|||
devices: getDevices(state), |
|||
currentDevice: getCurrentDevice(state), |
|||
}) |
|||
|
|||
const mapDispatchToProps: MapDispatchToProps<*, *, *> = { |
|||
deviceChoose, |
|||
} |
|||
|
|||
type Props = { |
|||
devices: Devices, |
|||
currentDevice: Device, |
|||
deviceChoose: deviceChooseType, |
|||
} |
|||
|
|||
type State = { |
|||
changeDevice: boolean, |
|||
} |
|||
|
|||
const hasDevices = props => props.currentDevice === null && props.devices.length > 0 |
|||
|
|||
class TopBar extends PureComponent<Props, State> { |
|||
state = { |
|||
changeDevice: hasDevices(this.props), |
|||
} |
|||
|
|||
componentWillReceiveProps(nextProps) { |
|||
if (hasDevices(nextProps) && this.props.currentDevice !== null) { |
|||
this.setState({ |
|||
changeDevice: true, |
|||
}) |
|||
} |
|||
} |
|||
|
|||
handleChangeDevice = () => { |
|||
const { devices } = this.props |
|||
|
|||
if (devices.length > 0) { |
|||
this.setState({ |
|||
changeDevice: true, |
|||
}) |
|||
} |
|||
} |
|||
|
|||
handleSelectDevice = device => () => { |
|||
const { deviceChoose } = this.props |
|||
|
|||
deviceChoose(device) |
|||
|
|||
this.setState({ |
|||
changeDevice: false, |
|||
}) |
|||
} |
|||
|
|||
class TopBar extends PureComponent<{}> { |
|||
render() { |
|||
const { devices } = this.props |
|||
const { changeDevice } = this.state |
|||
|
|||
return ( |
|||
<Box bg="white" noShrink style={{ height: 60 }}> |
|||
{''} |
|||
</Box> |
|||
<Fragment> |
|||
{changeDevice && ( |
|||
<Overlay p={20}> |
|||
{devices.map(device => ( |
|||
<Box |
|||
key={device.path} |
|||
color="white" |
|||
bg="night" |
|||
onClick={this.handleSelectDevice(device)} |
|||
> |
|||
{device.path} |
|||
</Box> |
|||
))} |
|||
</Overlay> |
|||
)} |
|||
<Box bg="white" noShrink style={{ height: 60 }} justify="center" align="flex-end"> |
|||
<CountDevices count={devices.length} onChangeDevice={this.handleChangeDevice} /> |
|||
</Box> |
|||
</Fragment> |
|||
) |
|||
} |
|||
} |
|||
|
|||
export default TopBar |
|||
const CountDevices = ({ count, onChangeDevice } = { count: Number, onChangeDevice: Function }) => ( |
|||
<Box |
|||
color="night" |
|||
mr={20} |
|||
horizontal |
|||
flow={10} |
|||
onClick={onChangeDevice} |
|||
style={{ cursor: 'pointer' }} |
|||
> |
|||
<Box> |
|||
<DeviceIcon height={20} width={20} /> |
|||
</Box> |
|||
<Box>{count}</Box> |
|||
</Box> |
|||
) |
|||
|
|||
const DeviceIcon = props => ( |
|||
<svg {...props} viewBox="0 0 19.781 19.781"> |
|||
<path |
|||
d="M14.507 0L9.8 4.706a2.92 2.92 0 0 0-1.991.854l-6.89 6.889a2.93 2.93 0 0 0 0 4.143l2.33 2.33a2.925 2.925 0 0 0 4.141 0l6.89-6.891c.613-.612.895-1.43.851-2.232l4.589-4.588L14.507 0zm.386 8.792a2.927 2.927 0 0 0-.611-.902l-2.33-2.331a2.945 2.945 0 0 0-1.08-.682l3.637-3.636 3.968 3.969-3.584 3.582zm.693-5.381l-.949.949-1.26-1.26.949-.949 1.26 1.26zm1.881 1.882l-.949.949-1.26-1.26.948-.949 1.261 1.26z" |
|||
fill="currentColor" |
|||
/> |
|||
</svg> |
|||
) |
|||
|
|||
export default connect(mapStateToProps, mapDispatchToProps)(TopBar) |
|||
|
@ -1,34 +1,23 @@ |
|||
// @flow
|
|||
|
|||
import React, { Fragment } from 'react' |
|||
import { compose } from 'redux' |
|||
import { connect } from 'react-redux' |
|||
import React from 'react' |
|||
import { Route } from 'react-router' |
|||
import { translate } from 'react-i18next' |
|||
|
|||
import Box from 'components/base/Box' |
|||
import Overlay from 'components/base/Overlay' |
|||
|
|||
import Home from 'components/Home' |
|||
import SideBar from 'components/SideBar' |
|||
import TopBar from 'components/TopBar' |
|||
|
|||
const Wrapper = ({ devices, t }: { devices: Array<Object>, t: string => string }) => ( |
|||
<Fragment> |
|||
{devices.length === 0 ? ( |
|||
<Overlay align="center" justify="center"> |
|||
<Box color="white">{t('common.connectDevice')}</Box> |
|||
</Overlay> |
|||
) : ( |
|||
<Box grow horizontal> |
|||
<SideBar /> |
|||
<Box grow bg="cream"> |
|||
<TopBar /> |
|||
<Route path="/" component={Home} /> |
|||
</Box> |
|||
</Box> |
|||
)} |
|||
</Fragment> |
|||
const Wrapper = () => ( |
|||
<Box grow horizontal> |
|||
<SideBar /> |
|||
<Box grow bg="cream"> |
|||
<TopBar /> |
|||
<Route path="/" component={Home} /> |
|||
</Box> |
|||
</Box> |
|||
) |
|||
|
|||
export default compose(connect(({ devices }): Object => ({ devices })), translate())(Wrapper) |
|||
export default translate()(Wrapper) |
|||
|
@ -1,6 +1,6 @@ |
|||
common: |
|||
ok: Okay |
|||
cancel: Cancel |
|||
connectDevice: Please connect your device |
|||
connectedDevices: You have {{count}} device connected |
|||
connectedDevices_0: You don't have device connected |
|||
connectedDevices_plural: You have {{count}} devices connected |
|||
|
@ -0,0 +1,29 @@ |
|||
// @flow
|
|||
|
|||
import { fork } from 'child_process' |
|||
import { ipcMain } from 'electron' |
|||
import { resolve } from 'path' |
|||
|
|||
ipcMain.on('msg', (event: any, payload) => { |
|||
const { type, data } = payload |
|||
|
|||
const compute = fork('./usb', { |
|||
cwd: resolve(__dirname, './'), |
|||
}) |
|||
|
|||
const send = (msgType, data) => { |
|||
event.sender.send('msg', { |
|||
type: msgType, |
|||
data, |
|||
}) |
|||
} |
|||
|
|||
compute.send({ type, data }) |
|||
compute.on('message', payload => { |
|||
const { type, data, options = {} } = payload |
|||
send(type, data) |
|||
if (options.kill) { |
|||
compute.kill() |
|||
} |
|||
}) |
|||
}) |
@ -1,5 +1,5 @@ |
|||
// @flow
|
|||
|
|||
require('../globals') |
|||
require('./ledger') |
|||
require('./bridge') |
|||
require('./app') |
|||
|
@ -1,70 +0,0 @@ |
|||
// @flow
|
|||
|
|||
import { ipcMain } from 'electron' |
|||
import { isLedgerDevice } from 'ledgerco/lib/utils' |
|||
import ledgerco, { comm_node } from 'ledgerco' |
|||
import objectPath from 'object-path' |
|||
|
|||
import HID from 'ledger-node-js-hid' |
|||
|
|||
async function getWalletInfos(path: string, wallet: string) { |
|||
if (wallet === 'btc') { |
|||
const comm = new comm_node(new HID.HID(path), true, 0, false) |
|||
const btc = new ledgerco.btc(comm) |
|||
const walletInfos = await btc.getWalletPublicKey_async("44'/0'/0'/0") |
|||
return walletInfos |
|||
} |
|||
throw new Error('invalid wallet') |
|||
} |
|||
|
|||
let isListenDevices = false |
|||
|
|||
const handlers = { |
|||
devices: { |
|||
listen: send => { |
|||
if (isListenDevices) { |
|||
return |
|||
} |
|||
|
|||
isListenDevices = true |
|||
|
|||
HID.listenDevices.start() |
|||
|
|||
HID.listenDevices.events.on( |
|||
'add', |
|||
device => isLedgerDevice(device) && send('device.add', device), |
|||
) |
|||
HID.listenDevices.events.on( |
|||
'remove', |
|||
device => isLedgerDevice(device) && send('device.remove', device), |
|||
) |
|||
}, |
|||
all: send => send('devices.update', HID.devices().filter(isLedgerDevice)), |
|||
}, |
|||
requestWalletInfos: async (send, { path, wallet }) => { |
|||
try { |
|||
const publicKey = await getWalletInfos(path, wallet) |
|||
send('receiveWalletInfos', { path, publicKey }) |
|||
} catch (err) { |
|||
send('failWalletInfos', { path, err: err.stack }) |
|||
} |
|||
}, |
|||
} |
|||
|
|||
ipcMain.on('msg', (event: *, payload) => { |
|||
const { type, data } = payload |
|||
|
|||
const handler = objectPath.get(handlers, type) |
|||
if (!handler) { |
|||
return |
|||
} |
|||
|
|||
const send = (msgType: string, data: *) => { |
|||
event.sender.send('msg', { |
|||
type: msgType, |
|||
data, |
|||
}) |
|||
} |
|||
|
|||
handler(send, data) |
|||
}) |
@ -0,0 +1,66 @@ |
|||
process.title = 'ledger-wallet-desktop-usb' |
|||
|
|||
const HID = require('ledger-node-js-hid') |
|||
const objectPath = require('object-path') |
|||
const { isLedgerDevice } = require('ledgerco/lib/utils') |
|||
const ledgerco = require('ledgerco') |
|||
|
|||
function send(type, data, options = { kill: true }) { |
|||
process.send({ type, data, options }) |
|||
} |
|||
|
|||
async function getWalletInfos(path, wallet) { |
|||
if (wallet === 'btc') { |
|||
const comm = new ledgerco.comm_node(new HID.HID(path), true, 0, false) |
|||
const btc = new ledgerco.btc(comm) |
|||
const walletInfos = await btc.getWalletPublicKey_async("44'/0'/0'/0") |
|||
return walletInfos |
|||
} |
|||
throw new Error('invalid wallet') |
|||
} |
|||
|
|||
let isListenDevices = false |
|||
|
|||
const handlers = { |
|||
devices: { |
|||
listen: () => { |
|||
if (isListenDevices) { |
|||
return |
|||
} |
|||
|
|||
isListenDevices = true |
|||
|
|||
const handleChangeDevice = eventName => device => |
|||
isLedgerDevice(device) && send(eventName, device, { kill: false }) |
|||
|
|||
HID.listenDevices.start() |
|||
|
|||
HID.listenDevices.events.on('add', handleChangeDevice('device.add')) |
|||
HID.listenDevices.events.on('remove', handleChangeDevice('device.remove')) |
|||
}, |
|||
all: () => send('devices.update', HID.devices().filter(isLedgerDevice)), |
|||
}, |
|||
wallet: { |
|||
infos: { |
|||
request: async ({ path, wallet }) => { |
|||
try { |
|||
const publicKey = await getWalletInfos(path, wallet) |
|||
send('wallet.infos.success', { path, publicKey }) |
|||
} catch (err) { |
|||
send('wallet.infos.fail', { path, err: err.stack || err }) |
|||
} |
|||
}, |
|||
}, |
|||
}, |
|||
} |
|||
|
|||
process.on('message', payload => { |
|||
const { type, data } = payload |
|||
|
|||
const handler = objectPath.get(handlers, type) |
|||
if (!handler) { |
|||
return |
|||
} |
|||
|
|||
handler(data) |
|||
}) |
Loading…
Reference in new issue