Gaëtan Renaudeau
7 years ago
8 changed files with 152 additions and 157 deletions
@ -0,0 +1,128 @@ |
|||||
|
// @flow
|
||||
|
|
||||
|
import invariant from 'invariant' |
||||
|
import logger from 'logger' |
||||
|
import Websocket from 'ws' |
||||
|
import type Transport from '@ledgerhq/hw-transport' |
||||
|
import { Observable } from 'rxjs' |
||||
|
import createCustomErrorClass from './createCustomErrorClass' |
||||
|
|
||||
|
const WebsocketConnectionError = createCustomErrorClass('WebsocketConnectionError') |
||||
|
const WebsocketConnectionFailed = createCustomErrorClass('WebsocketConnectionFailed') |
||||
|
const DeviceSocketFail = createCustomErrorClass('DeviceSocketFail') |
||||
|
const DeviceSocketNoBulkStatus = createCustomErrorClass('DeviceSocketNoBulkStatus') |
||||
|
const DeviceSocketNoHandler = createCustomErrorClass('DeviceSocketNoHandler') |
||||
|
|
||||
|
/** |
||||
|
* use Ledger WebSocket API to exchange data with the device |
||||
|
* Returns an Observable of the final result |
||||
|
*/ |
||||
|
export const createDeviceSocket = (transport: Transport<*>, url: string) => |
||||
|
Observable.create(o => { |
||||
|
let ws |
||||
|
let lastMessage: ?string |
||||
|
|
||||
|
try { |
||||
|
ws = new Websocket(url) |
||||
|
} catch (err) { |
||||
|
o.error(new WebsocketConnectionFailed(err.message)) |
||||
|
return () => {} |
||||
|
} |
||||
|
invariant(ws, 'websocket is available') |
||||
|
|
||||
|
ws.on('open', () => { |
||||
|
logger.websocket('OPENED', url) |
||||
|
}) |
||||
|
|
||||
|
ws.on('error', e => { |
||||
|
logger.websocket('ERROR', e) |
||||
|
o.error(new WebsocketConnectionError(e.message)) |
||||
|
}) |
||||
|
|
||||
|
ws.on('close', () => { |
||||
|
logger.websocket('CLOSE') |
||||
|
o.next(lastMessage || '') |
||||
|
o.complete() |
||||
|
}) |
||||
|
|
||||
|
const send = (nonce, response, data) => { |
||||
|
const msg = { |
||||
|
nonce, |
||||
|
response, |
||||
|
data, |
||||
|
} |
||||
|
logger.websocket('SEND', msg) |
||||
|
const strMsg = JSON.stringify(msg) |
||||
|
ws.send(strMsg) |
||||
|
} |
||||
|
|
||||
|
const handlers = { |
||||
|
exchange: async input => { |
||||
|
const { data, nonce } = input |
||||
|
const r: Buffer = await transport.exchange(Buffer.from(data, 'hex')) |
||||
|
const status = r.slice(r.length - 2) |
||||
|
const buffer = r.slice(0, r.length - 2) |
||||
|
const strStatus = status.toString('hex') |
||||
|
send(nonce, strStatus === '9000' ? 'success' : 'error', buffer.toString('hex')) |
||||
|
}, |
||||
|
|
||||
|
bulk: async input => { |
||||
|
const { data, nonce } = input |
||||
|
|
||||
|
// Execute all apdus and collect last status
|
||||
|
let lastStatus = null |
||||
|
for (const apdu of data) { |
||||
|
const r: Buffer = await transport.exchange(Buffer.from(apdu, 'hex')) |
||||
|
lastStatus = r.slice(r.length - 2) |
||||
|
} |
||||
|
if (!lastStatus) { |
||||
|
throw new DeviceSocketNoBulkStatus() |
||||
|
} |
||||
|
|
||||
|
const strStatus = lastStatus.toString('hex') |
||||
|
|
||||
|
send( |
||||
|
nonce, |
||||
|
strStatus === '9000' ? 'success' : 'error', |
||||
|
strStatus === '9000' ? '' : strStatus, |
||||
|
) |
||||
|
}, |
||||
|
|
||||
|
success: msg => { |
||||
|
lastMessage = msg.data || msg.result |
||||
|
ws.close() |
||||
|
}, |
||||
|
|
||||
|
error: msg => { |
||||
|
logger.websocket('ERROR', msg.data) |
||||
|
throw new DeviceSocketFail(msg.data) |
||||
|
}, |
||||
|
} |
||||
|
|
||||
|
const stackMessage = async rawMsg => { |
||||
|
try { |
||||
|
const msg = JSON.parse(rawMsg) |
||||
|
if (!(msg.query in handlers)) { |
||||
|
throw new DeviceSocketNoHandler(`Cannot handle msg of type ${msg.query}`, { |
||||
|
query: msg.query, |
||||
|
}) |
||||
|
} |
||||
|
logger.websocket('RECEIVE', msg) |
||||
|
await handlers[msg.query](msg) |
||||
|
} catch (err) { |
||||
|
logger.websocket('ERROR', err.toString()) |
||||
|
o.error(err) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
ws.on('message', async rawMsg => { |
||||
|
stackMessage(rawMsg) |
||||
|
}) |
||||
|
|
||||
|
return () => { |
||||
|
if (ws.readyState === 1) { |
||||
|
lastMessage = null |
||||
|
ws.close() |
||||
|
} |
||||
|
} |
||||
|
}) |
Loading…
Reference in new issue