Browse Source

add "Command" for more robust type checked com over ipc

master
Gaëtan Renaudeau 7 years ago
parent
commit
05f6850e73
  1. 3
      package.json
  2. 189
      src/bridge/EthereumJSBridge.js
  3. 55
      src/components/DeviceCheckAddress.js
  4. 46
      src/components/EnsureDeviceApp/index.js
  5. 184
      src/components/ReceiveBox.js
  6. 118
      src/helpers/ipc.js
  7. 39
      src/internals/accounts/helpers.js
  8. 34
      src/internals/accounts/index.js
  9. 36
      src/internals/devices/ensureDeviceApp.js
  10. 53
      src/internals/devices/getAddress.js
  11. 15
      src/internals/devices/index.js
  12. 48
      src/internals/devices/signTransaction.js
  13. 25
      src/internals/index.js
  14. 42
      yarn.lock

3
package.json

@ -82,6 +82,8 @@
"redux-actions": "^2.3.0",
"redux-thunk": "^2.2.0",
"reselect": "^3.0.1",
"rxjs": "^6.2.0",
"rxjs-compat": "^6.2.0",
"smooth-scrollbar": "^8.2.7",
"source-map": "0.7.2",
"source-map-support": "^0.5.4",
@ -89,6 +91,7 @@
"styled-system": "^2.2.1",
"tippy.js": "^2.5.2",
"uncontrollable": "^6.0.0",
"uuid": "^3.2.1",
"ws": "^5.1.1",
"zxcvbn": "^4.4.2"
},

189
src/bridge/EthereumJSBridge.js

@ -1,12 +1,12 @@
// @flow
import React from 'react'
import { ipcRenderer } from 'electron'
import { sendEvent } from 'renderer/events'
import EthereumKind from 'components/FeesField/EthereumKind'
import type { Account, Operation } from '@ledgerhq/live-common/lib/types'
import { apiForCurrency } from 'api/Ethereum'
import type { Tx } from 'api/Ethereum'
import { makeBip44Path } from 'helpers/bip32path'
import getAddressCommand from 'internals/devices/getAddress'
import signTransactionCommand from 'internals/devices/signTransaction'
import type { EditProps, WalletBridge } from './types'
// TODO in future it would be neat to support eip55
@ -62,67 +62,19 @@ const paginateMoreTransactions = async (
return mergeOps(acc, txs.map(toAccountOperation(account)))
}
function signTransactionOnDevice(
a: Account,
t: Transaction,
deviceId: string,
nonce: string,
): Promise<string> {
const transaction = { ...t, nonce }
return new Promise((resolve, reject) => {
const unbind = () => {
ipcRenderer.removeListener('msg', handleMsgEvent)
}
function handleMsgEvent(e, { data, type }) {
if (type === 'devices.signTransaction.success') {
unbind()
resolve(data)
} else if (type === 'devices.signTransaction.fail') {
unbind()
reject(new Error('failed to get address'))
}
}
ipcRenderer.on('msg', handleMsgEvent)
sendEvent('devices', 'signTransaction', {
currencyId: a.currency.id,
devicePath: deviceId,
path: a.path,
transaction,
})
})
}
const EthereumBridge: WalletBridge<Transaction> = {
scanAccountsOnDevice(currency, deviceId, { next, complete, error }) {
let finished = false
const unbind = () => {
const unsubscribe = () => {
finished = true
ipcRenderer.removeListener('msg', handleMsgEvent)
}
const api = apiForCurrency(currency)
// FIXME: THIS IS SPAghetti, we need to move to a more robust approach to get an observable with a sendEvent
// in future ideally what we want is:
// return mergeMap(addressesObservable, address => fetchAccount(address))
let index = 0
let balanceZerosCount = 0
function pollNextAddress() {
sendEvent('devices', 'getAddress', {
currencyId: currency.id,
devicePath: deviceId,
path: makeBip44Path({
currency,
x: index,
}),
})
index++
}
let currentBlockPromise
function lazyCurrentBlock() {
if (!currentBlockPromise) {
@ -131,52 +83,23 @@ const EthereumBridge: WalletBridge<Transaction> = {
return currentBlockPromise
}
async function stepAddress({ address, path }) {
try {
const balance = await api.getAccountBalance(address)
if (finished) return
if (balance === 0) {
if (balanceZerosCount === 0) {
// first zero account will emit one account as opportunity to create a new account..
const currentBlock = await lazyCurrentBlock()
const accountId = `${currency.id}_${address}`
const account: Account = {
id: accountId,
xpub: '',
path,
walletPath: String(index),
name: 'New Account',
isSegwit: false,
address,
addresses: [address],
balance,
blockHeight: currentBlock.height,
archived: true,
index,
currency,
operations: [],
unit: currency.units[0],
lastSyncDate: new Date(),
}
next(account)
}
balanceZerosCount++
// NB we currently stop earlier. in future we shouldn't stop here, just continue & user will stop at the end!
// NB (what's the max tho?)
unbind()
complete()
} else {
async function stepAddress(
index,
{ address, path },
): { account?: Account, complete?: boolean } {
const balance = await api.getAccountBalance(address)
if (finished) return {}
if (balance === 0) {
if (balanceZerosCount === 0) {
// first zero account will emit one account as opportunity to create a new account..
const currentBlock = await lazyCurrentBlock()
if (finished) return
const { txs } = await api.getTransactions(address)
if (finished) return
const accountId = `${currency.id}_${address}`
const account: Account = {
id: accountId,
xpub: '',
path,
walletPath: String(index),
name: address.slice(32),
name: 'New Account',
isSegwit: false,
address,
addresses: [address],
@ -189,32 +112,69 @@ const EthereumBridge: WalletBridge<Transaction> = {
unit: currency.units[0],
lastSyncDate: new Date(),
}
account.operations = txs.map(toAccountOperation(account))
next(account)
pollNextAddress()
// NB we currently stop earlier. in future we shouldn't stop here, just continue & user will stop at the end!
// NB (what's the max tho?)
return { account, complete: true }
}
} catch (e) {
error(e)
balanceZerosCount++
return { complete: true }
}
}
function handleMsgEvent(e, { data, type }) {
if (type === 'devices.getAddress.success') {
stepAddress(data)
} else if (type === 'devices.getAddress.fail') {
error(new Error(data.message))
const currentBlock = await lazyCurrentBlock()
if (finished) return {}
const { txs } = await api.getTransactions(address)
if (finished) return {}
const accountId = `${currency.id}_${address}`
const account: Account = {
id: accountId,
xpub: '',
path,
walletPath: String(index),
name: address.slice(32),
isSegwit: false,
address,
addresses: [address],
balance,
blockHeight: currentBlock.height,
archived: true,
index,
currency,
operations: [],
unit: currency.units[0],
lastSyncDate: new Date(),
}
account.operations = txs.map(toAccountOperation(account))
return { account }
}
ipcRenderer.on('msg', handleMsgEvent)
async function main() {
try {
for (let index = 0; index < 255; index++) {
const res = await getAddressCommand
.send({
currencyId: currency.id,
devicePath: deviceId,
path: makeBip44Path({
currency,
x: index,
}),
})
.toPromise()
const r = await stepAddress(index, res)
if (r.account) next(r.account)
if (r.complete) {
complete()
break
}
}
} catch (e) {
error(e)
}
}
pollNextAddress()
main()
return {
unsubscribe() {
unbind()
},
}
return { unsubscribe }
},
synchronize({ address, blockHeight, currency }, { next, complete, error }) {
@ -297,9 +257,20 @@ const EthereumBridge: WalletBridge<Transaction> = {
signAndBroadcast: async (a, t, deviceId) => {
const api = apiForCurrency(a.currency)
const nonce = await api.getAccountNonce(a.address)
const transaction = await signTransactionOnDevice(a, t, deviceId, nonce)
const transaction = await signTransactionCommand
.send({
currencyId: a.currency.id,
devicePath: deviceId,
path: a.path,
transaction: { ...t, nonce },
})
.toPromise()
const result = await api.broadcastTransaction(transaction)
return result
},
}

55
src/components/DeviceCheckAddress.js

@ -1,16 +1,14 @@
// @flow
import { PureComponent } from 'react'
import { ipcRenderer } from 'electron'
import type { Account } from '@ledgerhq/live-common/lib/types'
import type { Device } from 'types/common'
import { sendEvent } from 'renderer/events'
import getAddress from 'internals/devices/getAddress'
type Props = {
onCheck: Function,
render: Function,
onCheck: boolean => void,
render: ({ isVerified?: ?boolean }) => *,
account: Account,
device: Device,
}
@ -26,39 +24,40 @@ class CheckAddress extends PureComponent<Props, State> {
componentDidMount() {
const { device, account } = this.props
ipcRenderer.on('msg', this.handleMsgEvent)
this.verifyAddress({ device, account })
}
componentWillUnmount() {
ipcRenderer.removeListener('msg', this.handleMsgEvent)
componentDidUnmount() {
if (this.sub) this.sub.unsubscribe()
}
handleMsgEvent = (e: any, { type }: { type: string }) => {
const { onCheck } = this.props
sub: *
if (type === 'accounts.verifyAddress.success') {
this.setState({
isVerified: true,
verifyAddress = ({ device, account }: { device: Device, account: Account }) => {
this.sub = getAddress
.send({
currencyId: account.currency.id,
devicePath: device.path,
path: account.path,
segwit: account.isSegwit,
verify: true,
})
onCheck(true)
}
if (type === 'accounts.verifyAddress.fail') {
this.setState({
isVerified: false,
.subscribe({
next: () => {
this.setState({
isVerified: true,
})
this.props.onCheck(true)
},
error: () => {
this.setState({
isVerified: false,
})
this.props.onCheck(false)
},
})
onCheck(false)
}
}
verifyAddress = ({ device, account }: { device: Device, account: Account }) =>
sendEvent('accounts', 'verifyAddress', {
pathDevice: device.path,
path: account.path,
segwit: account.path.startsWith("49'"), // TODO: store segwit info in account
})
render() {
const { render } = this.props
const { isVerified } = this.state

46
src/components/EnsureDeviceApp/index.js

@ -2,15 +2,14 @@
import invariant from 'invariant'
import { PureComponent } from 'react'
import { connect } from 'react-redux'
import { ipcRenderer } from 'electron'
import { makeBip44Path } from 'helpers/bip32path'
import type { Account, CryptoCurrency } from '@ledgerhq/live-common/lib/types'
import type { Device } from 'types/common'
import { sendEvent } from 'renderer/events'
import { getDevices } from 'reducers/devices'
import type { State as StoreState } from 'reducers/index'
import getAddress from 'internals/devices/getAddress'
type OwnProps = {
currency: ?CryptoCurrency,
@ -54,7 +53,6 @@ class EnsureDeviceApp extends PureComponent<Props, State> {
}
componentDidMount() {
ipcRenderer.on('msg', this.handleMsgEvent)
if (this.props.deviceSelected !== null) {
this.checkAppOpened()
}
@ -88,21 +86,21 @@ class EnsureDeviceApp extends PureComponent<Props, State> {
}
componentWillUnmount() {
ipcRenderer.removeListener('msg', this.handleMsgEvent)
clearTimeout(this._timeout)
}
checkAppOpened = () => {
checkAppOpened = async () => {
const { deviceSelected, account, currency } = this.props
if (!deviceSelected) {
return
}
let options = null
let options
if (account) {
options = {
devicePath: deviceSelected.path,
currencyId: account.currency.id,
path: account.path,
accountAddress: account.address,
@ -110,6 +108,7 @@ class EnsureDeviceApp extends PureComponent<Props, State> {
}
} else if (currency) {
options = {
devicePath: deviceSelected.path,
currencyId: currency.id,
path: makeBip44Path({ currency }),
}
@ -117,11 +116,17 @@ class EnsureDeviceApp extends PureComponent<Props, State> {
throw new Error('either currency or account is required')
}
// TODO just use getAddress!
sendEvent('devices', 'ensureDeviceApp', {
devicePath: deviceSelected.path,
...options,
})
try {
const { address } = await getAddress.send(options).toPromise()
if (account && account.address !== address) {
throw new Error('Account address is different than device address')
}
this.handleStatusChange(this.state.deviceStatus, 'success')
} catch (e) {
this.handleStatusChange(this.state.deviceStatus, 'fail', e.message)
}
this._timeout = setTimeout(this.checkAppOpened, 1e3)
}
_timeout: *
@ -133,25 +138,6 @@ class EnsureDeviceApp extends PureComponent<Props, State> {
onStatusChange && onStatusChange(deviceStatus, appStatus, errorMessage)
}
handleMsgEvent = (e, { type, data }) => {
const { deviceStatus } = this.state
const { deviceSelected } = this.props
if (!deviceSelected) {
return
}
if (type === 'devices.ensureDeviceApp.success' && deviceSelected.path === data.devicePath) {
this.handleStatusChange(deviceStatus, 'success')
this._timeout = setTimeout(this.checkAppOpened, 1e3)
}
if (type === 'devices.ensureDeviceApp.fail' && deviceSelected.path === data.devicePath) {
this.handleStatusChange(deviceStatus, 'fail', data.message)
this._timeout = setTimeout(this.checkAppOpened, 1e3)
}
}
render() {
const { currency, account, devices, deviceSelected, render } = this.props
const { appStatus, deviceStatus, errorMessage } = this.state

184
src/components/ReceiveBox.js

@ -1,184 +0,0 @@
// @flow
import React, { PureComponent } from 'react'
import { connect } from 'react-redux'
import styled from 'styled-components'
import { ipcRenderer } from 'electron'
import type { Account } from '@ledgerhq/live-common/lib/types'
import type { Device } from 'types/common'
import { getCurrentDevice } from 'reducers/devices'
import { sendEvent } from 'renderer/events'
import Box from 'components/base/Box'
import Button from 'components/base/Button'
import CopyToClipboard from 'components/base/CopyToClipboard'
import Print from 'components/base/Print'
import QRCode from 'components/base/QRCode'
import Text from 'components/base/Text'
export const AddressBox = styled(Box).attrs({
bg: 'lightGrey',
p: 2,
})`
border-radius: ${p => p.theme.radii[1]}px;
border: 1px solid ${p => p.theme.colors.fog};
cursor: text;
text-align: center;
user-select: text;
word-break: break-all;
`
const Action = styled(Box).attrs({
alignItems: 'center',
color: 'fog',
flex: 1,
flow: 1,
fontSize: 0,
})`
font-weight: bold;
text-align: center;
cursor: pointer;
text-transform: uppercase;
&:hover {
color: ${p => p.theme.colors.grey};
}
`
const mapStateToProps = state => ({
currentDevice: getCurrentDevice(state),
})
type Props = {
currentDevice: Device | null,
account: Account,
amount?: string,
}
type State = {
isVerified: null | boolean,
isDisplay: boolean,
}
const defaultState = {
isVerified: null,
isDisplay: false,
}
class ReceiveBox extends PureComponent<Props, State> {
static defaultProps = {
amount: undefined,
}
state = {
...defaultState,
}
componentDidMount() {
ipcRenderer.on('msg', this.handleMsgEvent)
}
componentWillReceiveProps(nextProps: Props) {
if (this.props.account !== nextProps.account) {
this.setState({
...defaultState,
})
}
}
componentWillUnmount() {
ipcRenderer.removeListener('msg', this.handleMsgEvent)
this.setState({
...defaultState,
})
}
handleMsgEvent = (e, { type }) => {
if (type === 'wallet.verifyAddress.success') {
this.setState({
isVerified: true,
})
}
if (type === 'wallet.verifyAddress.fail') {
this.setState({
isVerified: false,
})
}
}
handleVerifyAddress = () => {
const { currentDevice, account } = this.props
if (currentDevice !== null) {
sendEvent('usb', 'wallet.verifyAddress', {
pathDevice: currentDevice.path,
path: `${account.walletPath}${account.path}`,
})
this.setState({
isDisplay: true,
})
}
}
render() {
const { amount, account } = this.props
const { isVerified, isDisplay } = this.state
if (!isDisplay) {
return (
<Box grow alignItems="center" justifyContent="center">
<Button onClick={this.handleVerifyAddress}>Display address on device</Button>
</Box>
)
}
const { address } = account
return (
<Box flow={3}>
<Box>
isVerified:{' '}
{isVerified === null
? 'not yet...'
: isVerified === true
? 'ok!'
: '/!\\ contact support'}
</Box>
<Box alignItems="center">
<QRCode size={150} data={`bitcoin:${address}${amount ? `?amount=${amount}` : ''}`} />
</Box>
<Box alignItems="center" flow={2}>
<Text fontSize={1}>{'Current address'}</Text>
<AddressBox>{address}</AddressBox>
</Box>
<Box horizontal>
<CopyToClipboard
data={address}
render={copy => (
<Action onClick={copy}>
<span>{'Copy'}</span>
</Action>
)}
/>
<Print
data={{ address, amount }}
render={(print, isLoading) => (
<Action onClick={print}>
<span>{isLoading ? '...' : 'Print'}</span>
</Action>
)}
/>
<Action>
<span>{'Share'}</span>
</Action>
</Box>
</Box>
)
}
}
export default connect(mapStateToProps, null)(ReceiveBox)

118
src/helpers/ipc.js

@ -0,0 +1,118 @@
// @flow
import { ipcRenderer } from 'electron'
import { Observable } from 'rxjs'
import uuidv4 from 'uuid/v4'
type Msg<A> = {
type: string,
data?: A,
options?: *,
}
function send<A>(msg: Msg<A>) {
process.send(msg)
}
export class Command<In, A> {
channel: string
type: string
id: string
impl: In => Observable<A>
constructor(channel: string, type: string, impl: In => Observable<A>) {
this.channel = channel
this.type = type
this.id = `${channel}.${type}`
this.impl = impl
}
// ~~~ On exec side we can:
exec(data: In, requestId: string) {
return this.impl(data).subscribe({
next: (data: A) => {
send({
type: `NEXT_${requestId}`,
data,
})
},
complete: () => {
send({
type: `COMPLETE_${requestId}`,
options: { kill: true },
})
},
error: error => {
send({
type: `ERROR_${requestId}`,
data: {
name: error && error.name,
message: error && error.message,
},
options: { kill: true },
})
},
})
}
// ~~~ On renderer side we can:
/**
* Usage example:
* sub = send(data).subscribe({ next: ... })
* // or
* const res = await send(data).toPromise()
*/
send(data: In): Observable<A> {
return Observable.create(o => {
const { channel, type, id } = this
const requestId: string = uuidv4()
const unsubscribe = () => {
ipcRenderer.removeListener('msg', handleMsgEvent)
}
function handleMsgEvent(e, msg: Msg<A>) {
switch (msg.type) {
case `NEXT_${requestId}`:
if (msg.data) {
o.next(msg.data)
}
break
case `COMPLETE_${requestId}`:
o.complete()
unsubscribe()
break
case `ERROR_${requestId}`:
o.error(msg.data)
unsubscribe()
break
default:
}
}
ipcRenderer.on('msg', handleMsgEvent)
ipcRenderer.send(channel, {
type,
data: {
id,
data,
requestId,
},
})
return unsubscribe
})
}
}
export function createCommand<In, A>(
channel: string,
type: string,
impl: In => Observable<A>,
): Command<In, A> {
return new Command(channel, type, impl)
}

39
src/internals/accounts/helpers.js

@ -1,39 +0,0 @@
// @flow
/* eslint-disable no-bitwise */
import Btc from '@ledgerhq/hw-app-btc'
export async function getFreshReceiveAddress({
currencyId,
accountIndex,
}: {
currencyId: string,
accountIndex: number,
}) {
// TODO: investigate why importing it on file scope causes trouble
const core = require('init-ledger-core')()
const wallet = await core.getWallet(currencyId)
const account = await wallet.getAccount(accountIndex)
const addresses = await account.getFreshPublicAddresses()
if (!addresses.length) {
throw new Error('No fresh addresses')
}
return addresses[0]
}
export function verifyAddress({
transport,
path,
segwit = true,
}: {
transport: Object,
path: string,
segwit?: boolean,
}) {
console.warn('DEPRECATED use devices.getAddress with verify option')
const btc = new Btc(transport)
return btc.getWalletPublicKey(path, true, segwit)
}

34
src/internals/accounts/index.js

@ -1,11 +1,8 @@
// @flow
import CommNodeHid from '@ledgerhq/hw-transport-node-hid'
import type { IPCSend } from 'types/electron'
import scanAccountsOnDevice from './scanAccountsOnDevice'
import { verifyAddress, getFreshReceiveAddress } from './helpers'
import sync from './sync'
@ -36,37 +33,6 @@ export default {
send('accounts.scanAccountsOnDevice.fail', formatErr(err))
}
},
getFreshReceiveAddress: async (
send: IPCSend,
{
currencyId,
accountIndex,
}: {
currencyId: string,
accountIndex: number,
},
) => {
try {
const freshAddress = await getFreshReceiveAddress({ currencyId, accountIndex })
send('accounts.getFreshReceiveAddress.success', freshAddress)
} catch (err) {
send('accounts.getFreshReceiveAddress.fail', err)
}
},
verifyAddress: async (
send: IPCSend,
{ pathDevice, path }: { pathDevice: string, path: string },
) => {
const transport = await CommNodeHid.open(pathDevice)
try {
await verifyAddress({ transport, path })
send('accounts.verifyAddress.success')
} catch (err) {
send('accounts.verifyAddress.fail')
}
},
}
// TODO: move this to a helper

36
src/internals/devices/ensureDeviceApp.js

@ -1,36 +0,0 @@
// @flow
import invariant from 'invariant'
import CommNodeHid from '@ledgerhq/hw-transport-node-hid'
import type Transport from '@ledgerhq/hw-transport'
import type { IPCSend } from 'types/electron'
import getAddressForCurrency from './getAddressForCurrency'
export default async (
send: IPCSend,
{
currencyId,
devicePath,
path,
accountAddress,
...options
}: {
currencyId: string,
devicePath: string,
path: string,
accountAddress: ?string,
},
) => {
try {
invariant(currencyId, 'currencyId "%s" not defined', currencyId)
const transport: Transport<*> = await CommNodeHid.open(devicePath)
const resolver = getAddressForCurrency(currencyId)
const { address } = await resolver(transport, currencyId, path, options)
if (accountAddress && address !== accountAddress) {
throw new Error('Account address is different than device address')
}
send('devices.ensureDeviceApp.success', { devicePath })
} catch (err) {
send('devices.ensureDeviceApp.fail', { devicePath, message: err.message })
}
}

53
src/internals/devices/getAddress.js

@ -1,32 +1,33 @@
// @flow
import invariant from 'invariant'
import { createCommand, Command } from 'helpers/ipc'
import { fromPromise } from 'rxjs/observable/fromPromise'
import CommNodeHid from '@ledgerhq/hw-transport-node-hid'
import type Transport from '@ledgerhq/hw-transport'
import type { IPCSend } from 'types/electron'
import getAddressForCurrency from './getAddressForCurrency'
export default async (
send: IPCSend,
{
currencyId,
devicePath,
path,
...options
}: {
currencyId: string,
devicePath: string,
path: string,
verify?: boolean,
},
) => {
try {
invariant(currencyId, 'currencyId "%s" not defined', currencyId)
const transport: Transport<*> = await CommNodeHid.open(devicePath)
const resolver = getAddressForCurrency(currencyId)
const res = await resolver(transport, currencyId, path, options)
send('devices.getAddress.success', res)
} catch (err) {
send('devices.getAddress.fail', { message: err.message })
}
type Input = {
currencyId: string,
devicePath: string,
path: string,
verify?: boolean,
segwit?: boolean,
}
type Result = {
address: string,
path: string,
publicKey: string,
}
const cmd: Command<Input, Result> = createCommand(
'devices',
'getAddress',
({ currencyId, devicePath, path, ...options }) =>
fromPromise(
CommNodeHid.open(devicePath).then(transport =>
getAddressForCurrency(currencyId)(transport, currencyId, path, options),
),
),
)
export default cmd

15
src/internals/devices/index.js

@ -1,4 +1,11 @@
export listen from './listen'
export ensureDeviceApp from './ensureDeviceApp'
export getAddress from './getAddress'
export signTransaction from './signTransaction'
// @flow
import type { Command } from 'helpers/ipc'
import getAddress from './getAddress'
import listen from './listen'
import signTransaction from './signTransaction'
// TODO port these to commands
export { listen }
export const commands: Array<Command<any, any>> = [getAddress, signTransaction]

48
src/internals/devices/signTransaction.js

@ -1,32 +1,28 @@
// @flow
import invariant from 'invariant'
import { createCommand, Command } from 'helpers/ipc'
import { fromPromise } from 'rxjs/observable/fromPromise'
import CommNodeHid from '@ledgerhq/hw-transport-node-hid'
import type Transport from '@ledgerhq/hw-transport'
import type { IPCSend } from 'types/electron'
import signTransactionForCurrency from './signTransactionForCurrency'
export default async (
send: IPCSend,
{
currencyId,
devicePath,
path,
transaction,
}: {
currencyId: string,
devicePath: string,
path: string,
transaction: *,
},
) => {
try {
invariant(currencyId, 'currencyId "%s" not defined', currencyId)
const transport: Transport<*> = await CommNodeHid.open(devicePath)
const signer = signTransactionForCurrency(currencyId)
const res = await signer(transport, currencyId, path, transaction)
send('devices.signTransaction.success', res)
} catch (err) {
send('devices.signTransaction.fail')
}
type Input = {
currencyId: string,
devicePath: string,
path: string,
transaction: *,
}
type Result = string
const cmd: Command<Input, Result> = createCommand(
'devices',
'signTransaction',
({ currencyId, devicePath, path, transaction }) =>
fromPromise(
CommNodeHid.open(devicePath).then(transport =>
signTransactionForCurrency(currencyId)(transport, currencyId, path, transaction),
),
),
)
export default cmd

25
src/internals/index.js

@ -24,13 +24,26 @@ if (handlers.default) {
}
process.on('message', payload => {
const { type, data } = payload
const handler = objectPath.get(handlers, type)
if (!handler) {
console.warn(`No handler found for ${type}`)
return
console.log(payload)
if (payload.data && payload.data.requestId) {
const { data, requestId, id } = payload.data
// this is the new type of "command" payload!
const cmd = (handlers.commands || []).find(cmd => cmd.id === id)
if (!cmd) {
console.warn(`command ${id} not found`)
} else {
cmd.exec(data, requestId)
}
} else {
// this will be deprecated!
const { type, data } = payload
const handler = objectPath.get(handlers, type)
if (!handler) {
console.warn(`No handler found for ${type}`)
return
}
handler(sendEvent, data)
}
handler(sendEvent, data)
})
if (__DEV__ || DEV_TOOLS) {

42
yarn.lock

@ -5072,7 +5072,7 @@ debug@3.1.0, debug@^3.0.0, debug@^3.1.0:
dependencies:
ms "2.0.0"
debuglog@*, debuglog@^1.0.1:
debuglog@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492"
@ -7494,7 +7494,7 @@ import-local@^1.0.0:
pkg-dir "^2.0.0"
resolve-cwd "^2.0.0"
imurmurhash@*, imurmurhash@^0.1.4:
imurmurhash@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
@ -8808,10 +8808,6 @@ lodash-es@^4.17.4, lodash-es@^4.17.5, lodash-es@^4.2.1:
version "4.17.10"
resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.10.tgz#62cd7104cdf5dd87f235a837f0ede0e8e5117e05"
lodash._baseindexof@*:
version "3.1.0"
resolved "https://registry.yarnpkg.com/lodash._baseindexof/-/lodash._baseindexof-3.1.0.tgz#fe52b53a1c6761e42618d654e4a25789ed61822c"
lodash._baseuniq@~4.6.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/lodash._baseuniq/-/lodash._baseuniq-4.6.0.tgz#0ebb44e456814af7905c6212fa2c9b2d51b841e8"
@ -8819,25 +8815,11 @@ lodash._baseuniq@~4.6.0:
lodash._createset "~4.0.0"
lodash._root "~3.0.0"
lodash._bindcallback@*:
version "3.0.1"
resolved "https://registry.yarnpkg.com/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz#e531c27644cf8b57a99e17ed95b35c748789392e"
lodash._cacheindexof@*:
version "3.0.2"
resolved "https://registry.yarnpkg.com/lodash._cacheindexof/-/lodash._cacheindexof-3.0.2.tgz#3dc69ac82498d2ee5e3ce56091bafd2adc7bde92"
lodash._createcache@*:
version "3.1.2"
resolved "https://registry.yarnpkg.com/lodash._createcache/-/lodash._createcache-3.1.2.tgz#56d6a064017625e79ebca6b8018e17440bdcf093"
dependencies:
lodash._getnative "^3.0.0"
lodash._createset@~4.0.0:
version "4.0.3"
resolved "https://registry.yarnpkg.com/lodash._createset/-/lodash._createset-4.0.3.tgz#0f4659fbb09d75194fa9e2b88a6644d363c9fe26"
lodash._getnative@*, lodash._getnative@^3.0.0:
lodash._getnative@^3.0.0:
version "3.9.1"
resolved "https://registry.yarnpkg.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5"
@ -8897,10 +8879,6 @@ lodash.pick@^4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3"
lodash.restparam@*:
version "3.6.1"
resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805"
lodash.some@^4.6.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/lodash.some/-/lodash.some-4.6.0.tgz#1bb9f314ef6b8baded13b549169b2a945eb68e4d"
@ -11594,7 +11572,7 @@ readable-stream@~2.1.5:
string_decoder "~0.10.x"
util-deprecate "~1.0.1"
readdir-scoped-modules@*, readdir-scoped-modules@^1.0.0:
readdir-scoped-modules@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/readdir-scoped-modules/-/readdir-scoped-modules-1.0.2.tgz#9fafa37d286be5d92cbaebdee030dc9b5f406747"
dependencies:
@ -12073,12 +12051,22 @@ rx@2.3.24:
version "2.3.24"
resolved "https://registry.yarnpkg.com/rx/-/rx-2.3.24.tgz#14f950a4217d7e35daa71bbcbe58eff68ea4b2b7"
rxjs-compat@^6.2.0:
version "6.2.0"
resolved "https://registry.yarnpkg.com/rxjs-compat/-/rxjs-compat-6.2.0.tgz#2eb49cc6ac20d0d7057c6887d1895beaab0966f9"
rxjs@^5.1.1, rxjs@^5.4.2, rxjs@^5.5.2:
version "5.5.10"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.5.10.tgz#fde02d7a614f6c8683d0d1957827f492e09db045"
dependencies:
symbol-observable "1.0.1"
rxjs@^6.2.0:
version "6.2.0"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.2.0.tgz#e024d0e180b72756a83c2aaea8f25423751ba978"
dependencies:
tslib "^1.9.0"
safe-buffer@5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853"
@ -13201,7 +13189,7 @@ tryer@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/tryer/-/tryer-1.0.0.tgz#027b69fa823225e551cace3ef03b11f6ab37c1d7"
tslib@^1.7.1:
tslib@^1.7.1, tslib@^1.9.0:
version "1.9.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.1.tgz#a5d1f0532a49221c87755cfcc89ca37197242ba7"

Loading…
Cancel
Save