Gaëtan Renaudeau
7 years ago
committed by
GitHub
17 changed files with 189 additions and 333 deletions
@ -0,0 +1,19 @@ |
|||||
|
// @flow
|
||||
|
|
||||
|
import { createCommand, Command } from 'helpers/ipc' |
||||
|
import { fromPromise } from 'rxjs/observable/fromPromise' |
||||
|
|
||||
|
import { withDevice } from 'helpers/deviceAccess' |
||||
|
import getMemInfo from 'helpers/devices/getMemInfo' |
||||
|
|
||||
|
type Input = { |
||||
|
devicePath: string, |
||||
|
} |
||||
|
|
||||
|
type Result = * |
||||
|
|
||||
|
const cmd: Command<Input, Result> = createCommand('devices', 'getMemInfo', ({ devicePath }) => |
||||
|
fromPromise(withDevice(devicePath)(transport => getMemInfo(transport))), |
||||
|
) |
||||
|
|
||||
|
export default cmd |
@ -0,0 +1,28 @@ |
|||||
|
// @flow
|
||||
|
|
||||
|
import { createCommand, Command } from 'helpers/ipc' |
||||
|
import { fromPromise } from 'rxjs/observable/fromPromise' |
||||
|
import { withDevice } from 'helpers/deviceAccess' |
||||
|
|
||||
|
import installFinalFirmware from 'helpers/firmware/installFinalFirmware' |
||||
|
|
||||
|
type Input = { |
||||
|
devicePath: string, |
||||
|
firmware: Object, |
||||
|
} |
||||
|
|
||||
|
type Result = { |
||||
|
targetId: number | string, |
||||
|
version: string, |
||||
|
final: boolean, |
||||
|
mcu: boolean, |
||||
|
} |
||||
|
|
||||
|
const cmd: Command<Input, Result> = createCommand( |
||||
|
'devices', |
||||
|
'installFinalFirmware', |
||||
|
({ devicePath, firmware }) => |
||||
|
fromPromise(withDevice(devicePath)(transport => installFinalFirmware(transport, firmware))), |
||||
|
) |
||||
|
|
||||
|
export default cmd |
@ -0,0 +1,28 @@ |
|||||
|
// @flow
|
||||
|
|
||||
|
import { createCommand, Command } from 'helpers/ipc' |
||||
|
import { fromPromise } from 'rxjs/observable/fromPromise' |
||||
|
|
||||
|
// import { withDevice } from 'helpers/deviceAccess'
|
||||
|
import installMcu from 'helpers/firmware/installMcu' |
||||
|
|
||||
|
// type Input = {
|
||||
|
// devicePath: string,
|
||||
|
// firmware: Object,
|
||||
|
// }
|
||||
|
|
||||
|
// type Result = {
|
||||
|
// targetId: number | string,
|
||||
|
// version: string,
|
||||
|
// final: boolean,
|
||||
|
// mcu: boolean,
|
||||
|
// }
|
||||
|
|
||||
|
type Input = * |
||||
|
type Result = * |
||||
|
|
||||
|
const cmd: Command<Input, Result> = createCommand('devices', 'installMcu', () => |
||||
|
fromPromise(installMcu()), |
||||
|
) |
||||
|
|
||||
|
export default cmd |
@ -0,0 +1,28 @@ |
|||||
|
// @flow
|
||||
|
|
||||
|
import { createCommand, Command } from 'helpers/ipc' |
||||
|
import { fromPromise } from 'rxjs/observable/fromPromise' |
||||
|
import { withDevice } from 'helpers/deviceAccess' |
||||
|
|
||||
|
import installOsuFirmware from 'helpers/firmware/installOsuFirmware' |
||||
|
|
||||
|
type Input = { |
||||
|
devicePath: string, |
||||
|
firmware: Object, |
||||
|
} |
||||
|
|
||||
|
type Result = { |
||||
|
targetId: number | string, |
||||
|
version: string, |
||||
|
final: boolean, |
||||
|
mcu: boolean, |
||||
|
} |
||||
|
|
||||
|
const cmd: Command<Input, Result> = createCommand( |
||||
|
'devices', |
||||
|
'installOsuFirmware', |
||||
|
({ devicePath, firmware }) => |
||||
|
fromPromise(withDevice(devicePath)(transport => installOsuFirmware(transport, firmware))), |
||||
|
) |
||||
|
|
||||
|
export default cmd |
@ -0,0 +1,25 @@ |
|||||
|
// @flow
|
||||
|
|
||||
|
import { createCommand, Command } from 'helpers/ipc' |
||||
|
import { fromPromise } from 'rxjs/observable/fromPromise' |
||||
|
import { withDevice } from 'helpers/deviceAccess' |
||||
|
|
||||
|
import uninstallApp from 'helpers/apps/uninstallApp' |
||||
|
|
||||
|
import type { LedgerScriptParams } from 'helpers/common' |
||||
|
|
||||
|
type Input = { |
||||
|
appParams: LedgerScriptParams, |
||||
|
devicePath: string, |
||||
|
} |
||||
|
|
||||
|
type Result = * |
||||
|
|
||||
|
const cmd: Command<Input, Result> = createCommand( |
||||
|
'devices', |
||||
|
'uninstallApp', |
||||
|
({ devicePath, ...rest }) => |
||||
|
fromPromise(withDevice(devicePath)(transport => uninstallApp(transport, rest))), |
||||
|
) |
||||
|
|
||||
|
export default cmd |
@ -1,12 +1,16 @@ |
|||||
// @flow
|
// @flow
|
||||
|
|
||||
// import type { IPCSend } from 'types/electron'
|
import type Transport from '@ledgerhq/hw-transport' |
||||
|
|
||||
// import { createTransportHandler, uninstallApp } from 'helpers/common'
|
import { createSocketDialog } from 'helpers/common' |
||||
|
import type { LedgerScriptParams } from 'helpers/common' |
||||
|
|
||||
// export default (send: IPCSend, data: any) =>
|
/** |
||||
// createTransportHandler(send, {
|
* Install an app on the device |
||||
// action: uninstallApp,
|
*/ |
||||
// successResponse: 'manager.appUninstalled',
|
export default async function uninstallApp( |
||||
// errorResponse: 'manager.appUninstallError',
|
transport: Transport<*>, |
||||
// })(data)
|
{ appParams }: { appParams: LedgerScriptParams }, |
||||
|
): Promise<void> { |
||||
|
return createSocketDialog(transport, '/update/install', appParams) |
||||
|
} |
||||
|
@ -0,0 +1,11 @@ |
|||||
|
// @flow
|
||||
|
|
||||
|
import type Transport from '@ledgerhq/hw-transport' |
||||
|
|
||||
|
import { getFirmwareInfo, createSocketDialog } from 'helpers/common' |
||||
|
|
||||
|
export default async function getMemInfos(transport: Transport<*>): Promise<Object> { |
||||
|
const { targetId } = await getFirmwareInfo(transport) |
||||
|
// Dont ask me about this `perso_11`: I don't know. But we need it.
|
||||
|
return createSocketDialog(transport, '/get-mem-infos', { targetId, perso: 'perso_11' }) |
||||
|
} |
@ -1,24 +1,26 @@ |
|||||
// @flow
|
// @flow
|
||||
|
|
||||
import CommNodeHid from '@ledgerhq/hw-transport-node-hid' |
import type Transport from '@ledgerhq/hw-transport' |
||||
|
|
||||
import type { IPCSend } from 'types/electron' |
|
||||
import { createSocketDialog, buildParamsFromFirmware } from 'helpers/common' |
import { createSocketDialog, buildParamsFromFirmware } from 'helpers/common' |
||||
|
|
||||
type DataType = { |
type Input = { |
||||
devicePath: string, |
|
||||
firmware: Object, |
firmware: Object, |
||||
} |
} |
||||
|
|
||||
const buildFinalParams = buildParamsFromFirmware('final') |
type Result = * |
||||
|
|
||||
export default async (send: IPCSend, data: DataType) => { |
const buildOsuParams = buildParamsFromFirmware('final') |
||||
|
|
||||
|
export default async (transport: Transport<*>, data: Input): Result => { |
||||
try { |
try { |
||||
const transport = await CommNodeHid.open(data.devicePath) |
const osuData = buildOsuParams(data.firmware) |
||||
const finalData = buildFinalParams(data.firmware) |
await createSocketDialog(transport, '/update/install', osuData) |
||||
await createSocketDialog(transport, '/update/install', finalData) |
return { success: true } |
||||
send('device.finalFirmwareInstallSuccess', { success: true }) |
|
||||
} catch (err) { |
} catch (err) { |
||||
send('device.finalFirmwareInstallError', { success: false }) |
const error = Error(err.message) |
||||
|
error.stack = err.stack |
||||
|
const result = { success: false, error } |
||||
|
throw result |
||||
} |
} |
||||
} |
} |
||||
|
@ -1 +1,8 @@ |
|||||
// flow
|
// @flow
|
||||
|
|
||||
|
type Result = Promise<boolean> |
||||
|
|
||||
|
// TODO: IMPLEMENTATION FOR FLASHING FIRMWARE
|
||||
|
// GETTING APDUS FROM SERVER
|
||||
|
// SEND THE APDUS TO DEVICE
|
||||
|
export default async (): Result => new Promise(resolve => resolve(true)) |
||||
|
@ -1,13 +0,0 @@ |
|||||
// Socket endpoint
|
|
||||
|
|
||||
export const BASE_SOCKET_URL = 'ws://api.ledgerwallet.com' |
|
||||
// If you want to test locally with https://github.com/LedgerHQ/ledger-update-python-api
|
|
||||
// export const BASE_SOCKET_URL = 'ws://localhost:3001/update'
|
|
||||
|
|
||||
// List of APDUS
|
|
||||
export const APDUS = { |
|
||||
GET_FIRMWARE: [0xe0, 0x01, 0x00, 0x00], |
|
||||
// we dont have common call that works inside app & dashboard
|
|
||||
// TODO: this should disappear.
|
|
||||
GET_FIRMWARE_FALLBACK: [0xe0, 0xc4, 0x00, 0x00], |
|
||||
} |
|
@ -1,12 +0,0 @@ |
|||||
// @flow
|
|
||||
|
|
||||
import type { IPCSend } from 'types/electron' |
|
||||
|
|
||||
import { createTransportHandler, getMemInfos } from './helpers' |
|
||||
|
|
||||
export default (send: IPCSend, data: any) => |
|
||||
createTransportHandler(send, { |
|
||||
action: getMemInfos, |
|
||||
successResponse: 'manager.getMemInfosSuccess', |
|
||||
errorResponse: 'manager.getMemInfosError', |
|
||||
})(data) |
|
@ -1,237 +0,0 @@ |
|||||
// @flow
|
|
||||
|
|
||||
import { withDevice } from 'helpers/deviceAccess' |
|
||||
import chalk from 'chalk' |
|
||||
import Websocket from 'ws' |
|
||||
import qs from 'qs' |
|
||||
import type Transport from '@ledgerhq/hw-transport' |
|
||||
|
|
||||
import type { IPCSend } from 'types/electron' |
|
||||
import { BASE_SOCKET_URL, APDUS } from './constants' |
|
||||
|
|
||||
// TODO: REMOVE FILE WHEN REFACTO IS OVER
|
|
||||
|
|
||||
type WebsocketType = { |
|
||||
send: (string, any) => void, |
|
||||
on: (string, Function) => void, |
|
||||
} |
|
||||
|
|
||||
type Message = { |
|
||||
nonce: number, |
|
||||
query?: string, |
|
||||
response?: string, |
|
||||
data: any, |
|
||||
} |
|
||||
|
|
||||
type LedgerScriptParams = { |
|
||||
firmware?: string, |
|
||||
firmwareKey?: string, |
|
||||
delete?: string, |
|
||||
deleteKey?: string, |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Generate handler which create transport with given |
|
||||
* `devicePath` then call action with it |
|
||||
*/ |
|
||||
export function createTransportHandler( |
|
||||
send: IPCSend, |
|
||||
{ |
|
||||
action, |
|
||||
successResponse, |
|
||||
errorResponse, |
|
||||
}: { |
|
||||
action: (Transport<*>, ...any) => Promise<any>, |
|
||||
successResponse: string, |
|
||||
errorResponse: string, |
|
||||
}, |
|
||||
) { |
|
||||
console.log('DEPRECATED: createTransportHandler use withDevice and commands/*') |
|
||||
return async function transportHandler({ |
|
||||
devicePath, |
|
||||
...params |
|
||||
}: { |
|
||||
devicePath: string, |
|
||||
}): Promise<void> { |
|
||||
try { |
|
||||
const data = await withDevice(devicePath)(transport => action(transport, params)) |
|
||||
send(successResponse, data) |
|
||||
} catch (err) { |
|
||||
if (!err) { |
|
||||
send(errorResponse, { message: 'Unknown error...' }) |
|
||||
} |
|
||||
send(errorResponse, { message: err.message, stack: err.stack }) |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
export async function getMemInfos(transport: Transport<*>): Promise<Object> { |
|
||||
const { targetId } = await getFirmwareInfo(transport) |
|
||||
// Dont ask me about this `perso_11`: I don't know. But we need it.
|
|
||||
return createSocketDialog(transport, '/get-mem-infos', { targetId, perso: 'perso_11' }) |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Send data through ws |
|
||||
*/ |
|
||||
function socketSend(ws: WebsocketType, msg: Message) { |
|
||||
logWS('SEND', msg) |
|
||||
const strMsg = JSON.stringify(msg) |
|
||||
ws.send(strMsg) |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Exchange data on transport |
|
||||
*/ |
|
||||
export async function exchange( |
|
||||
ws: WebsocketType, |
|
||||
transport: Transport<*>, |
|
||||
msg: Message, |
|
||||
): Promise<void> { |
|
||||
const { data, nonce } = msg |
|
||||
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') |
|
||||
socketSend(ws, { |
|
||||
nonce, |
|
||||
response: strStatus === '9000' ? 'success' : 'error', |
|
||||
data: buffer.toString('hex'), |
|
||||
}) |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Bulk update on transport |
|
||||
*/ |
|
||||
export async function bulk(ws: WebsocketType, transport: Transport<*>, msg: Message) { |
|
||||
const { data, nonce } = msg |
|
||||
|
|
||||
// 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 Error('No status collected from bulk') |
|
||||
} |
|
||||
|
|
||||
const strStatus = lastStatus.toString('hex') |
|
||||
socketSend(ws, { |
|
||||
nonce, |
|
||||
response: strStatus === '9000' ? 'success' : 'error', |
|
||||
data: strStatus === '9000' ? '' : strStatus, |
|
||||
}) |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Open socket connection with firmware api, and init a dialog |
|
||||
* with the device |
|
||||
*/ |
|
||||
export async function createSocketDialog( |
|
||||
transport: Transport<*>, |
|
||||
endpoint: string, |
|
||||
params: LedgerScriptParams, |
|
||||
) { |
|
||||
return new Promise(async (resolve, reject) => { |
|
||||
try { |
|
||||
let lastData |
|
||||
const url = `${BASE_SOCKET_URL}${endpoint}?${qs.stringify(params)}` |
|
||||
|
|
||||
log('WS CONNECTING', url) |
|
||||
const ws: WebsocketType = new Websocket(url) |
|
||||
|
|
||||
ws.on('open', () => log('WS CONNECTED')) |
|
||||
|
|
||||
ws.on('close', () => { |
|
||||
log('WS CLOSED') |
|
||||
resolve(lastData) |
|
||||
}) |
|
||||
|
|
||||
ws.on('message', async rawMsg => { |
|
||||
const handlers = { |
|
||||
exchange: msg => exchange(ws, transport, msg), |
|
||||
bulk: msg => bulk(ws, transport, msg), |
|
||||
success: msg => { |
|
||||
if (msg.data) { |
|
||||
lastData = msg.data |
|
||||
} |
|
||||
}, |
|
||||
error: msg => { |
|
||||
log('WS ERROR', ':(') |
|
||||
throw new Error(msg.data) |
|
||||
}, |
|
||||
} |
|
||||
try { |
|
||||
const msg = JSON.parse(rawMsg) |
|
||||
if (!(msg.query in handlers)) { |
|
||||
throw new Error(`Cannot handle msg of type ${msg.query}`) |
|
||||
} |
|
||||
logWS('RECEIVE', msg) |
|
||||
await handlers[msg.query](msg) |
|
||||
} catch (err) { |
|
||||
log('ERROR', err.toString()) |
|
||||
reject(err) |
|
||||
} |
|
||||
}) |
|
||||
} catch (err) { |
|
||||
reject(err) |
|
||||
} |
|
||||
}) |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Retrieve targetId and firmware version from device |
|
||||
*/ |
|
||||
export async function getFirmwareInfo(transport: Transport<*>) { |
|
||||
try { |
|
||||
const res = await transport.send(...APDUS.GET_FIRMWARE) |
|
||||
const byteArray = [...res] |
|
||||
const data = byteArray.slice(0, byteArray.length - 2) |
|
||||
const targetIdStr = Buffer.from(data.slice(0, 4)) |
|
||||
const targetId = targetIdStr.readUIntBE(0, 4) |
|
||||
const versionLength = data[4] |
|
||||
const version = Buffer.from(data.slice(5, 5 + versionLength)).toString() |
|
||||
return { targetId, version } |
|
||||
} catch (err) { |
|
||||
const error = new Error(err.message) |
|
||||
error.stack = err.stack |
|
||||
throw error |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Debug helper |
|
||||
*/ |
|
||||
export function log(namespace: string, str: string = '', color?: string) { |
|
||||
namespace = namespace.padEnd(15) |
|
||||
// $FlowFixMe
|
|
||||
const coloredNamespace = color ? chalk[color](namespace) : namespace |
|
||||
if (__DEV__) { |
|
||||
console.log(`${chalk.bold(`> ${coloredNamespace}`)} ${str}`) // eslint-disable-line no-console
|
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Log a socket send/receive |
|
||||
*/ |
|
||||
export function logWS(type: string, msg: Message) { |
|
||||
const arrow = type === 'SEND' ? '↑' : '↓' |
|
||||
const namespace = `${arrow} WS ${type}` |
|
||||
const color = type === 'SEND' ? 'blue' : 'red' |
|
||||
if (msg.nonce) { |
|
||||
let d = '' |
|
||||
if (msg.query === 'exchange') { |
|
||||
d = msg.data.length > 100 ? `${msg.data.substr(0, 97)}...` : msg.data |
|
||||
} else if (msg.query === 'bulk') { |
|
||||
d = `[bulk x ${msg.data.length}]` |
|
||||
} |
|
||||
log( |
|
||||
namespace, |
|
||||
`${String(msg.nonce).padEnd(2)} ${(msg.response || msg.query || '').padEnd(10)} ${d}`, |
|
||||
color, |
|
||||
) |
|
||||
} else { |
|
||||
log(namespace, JSON.stringify(msg), color) |
|
||||
} |
|
||||
} |
|
Loading…
Reference in new issue