Meriadec Pillet
7 years ago
committed by
GitHub
17 changed files with 335 additions and 132 deletions
@ -1,20 +1,32 @@ |
|||||
// @flow
|
// @flow
|
||||
|
|
||||
import React, { PureComponent } from 'react' |
import React, { PureComponent } from 'react' |
||||
import { compose } from 'redux' |
|
||||
import { connect } from 'react-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 = { |
type Props = { |
||||
devices: Array<Object>, |
currentDevice: Device | null, |
||||
t: (string, ?Object) => string, |
|
||||
} |
} |
||||
|
|
||||
class Home extends PureComponent<Props> { |
class Home extends PureComponent<Props> { |
||||
render() { |
render() { |
||||
const { devices, t } = this.props |
const { currentDevice } = this.props |
||||
return <div>{t('common.connectedDevices', { count: devices.length })}</div> |
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
|
// @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 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() { |
render() { |
||||
|
const { devices } = this.props |
||||
|
const { changeDevice } = this.state |
||||
|
|
||||
return ( |
return ( |
||||
<Box bg="white" noShrink style={{ height: 60 }}> |
<Fragment> |
||||
{''} |
{changeDevice && ( |
||||
</Box> |
<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
|
// @flow
|
||||
|
|
||||
import React, { Fragment } from 'react' |
import React from 'react' |
||||
import { compose } from 'redux' |
|
||||
import { connect } from 'react-redux' |
|
||||
import { Route } from 'react-router' |
import { Route } from 'react-router' |
||||
import { translate } from 'react-i18next' |
import { translate } from 'react-i18next' |
||||
|
|
||||
import Box from 'components/base/Box' |
import Box from 'components/base/Box' |
||||
import Overlay from 'components/base/Overlay' |
|
||||
|
|
||||
import Home from 'components/Home' |
import Home from 'components/Home' |
||||
import SideBar from 'components/SideBar' |
import SideBar from 'components/SideBar' |
||||
import TopBar from 'components/TopBar' |
import TopBar from 'components/TopBar' |
||||
|
|
||||
const Wrapper = ({ devices, t }: { devices: Array<Object>, t: string => string }) => ( |
const Wrapper = () => ( |
||||
<Fragment> |
<Box grow horizontal> |
||||
{devices.length === 0 ? ( |
<SideBar /> |
||||
<Overlay align="center" justify="center"> |
<Box grow bg="cream"> |
||||
<Box color="white">{t('common.connectDevice')}</Box> |
<TopBar /> |
||||
</Overlay> |
<Route path="/" component={Home} /> |
||||
) : ( |
</Box> |
||||
<Box grow horizontal> |
</Box> |
||||
<SideBar /> |
|
||||
<Box grow bg="cream"> |
|
||||
<TopBar /> |
|
||||
<Route path="/" component={Home} /> |
|
||||
</Box> |
|
||||
</Box> |
|
||||
)} |
|
||||
</Fragment> |
|
||||
) |
) |
||||
|
|
||||
export default compose(connect(({ devices }): Object => ({ devices })), translate())(Wrapper) |
export default translate()(Wrapper) |
||||
|
@ -1,6 +1,6 @@ |
|||||
common: |
common: |
||||
ok: Okay |
ok: Okay |
||||
cancel: Cancel |
cancel: Cancel |
||||
connectDevice: Please connect your device |
|
||||
connectedDevices: You have {{count}} device connected |
connectedDevices: You have {{count}} device connected |
||||
|
connectedDevices_0: You don't have device connected |
||||
connectedDevices_plural: You have {{count}} devices 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
|
// @flow
|
||||
|
|
||||
require('../globals') |
require('../globals') |
||||
require('./ledger') |
require('./bridge') |
||||
require('./app') |
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