From b5aeada08b02c3fe1abcc6fec68e91df7ee25d8a Mon Sep 17 00:00:00 2001 From: "Valentin D. Pinkman" Date: Mon, 28 May 2018 15:18:51 +0200 Subject: [PATCH] Refactor jobs into commands --- src/commands/getMemInfo.js | 19 ++ src/commands/installApp.js | 2 +- src/commands/installFinalFirmware.js | 28 +++ src/commands/installMcu.js | 28 +++ src/commands/installOsuFirmware.js | 28 +++ src/commands/uninstallApp.js | 25 ++ src/components/ManagerPage/AppsList.js | 4 +- src/components/ManagerPage/index.js | 49 +--- src/helpers/apps/uninstallApp.js | 20 +- src/helpers/devices/getMemInfo.js | 11 + src/helpers/firmware/installFinalFirmware.js | 24 +- src/helpers/firmware/installMcu.js | 9 +- src/internals/devices/index.js | 8 + src/internals/manager/constants.js | 13 - src/internals/manager/getMemInfos.js | 12 - src/internals/manager/helpers.js | 237 ------------------- src/internals/manager/index.js | 5 +- 17 files changed, 189 insertions(+), 333 deletions(-) create mode 100644 src/commands/getMemInfo.js create mode 100644 src/commands/installFinalFirmware.js create mode 100644 src/commands/installMcu.js create mode 100644 src/commands/installOsuFirmware.js create mode 100644 src/commands/uninstallApp.js create mode 100644 src/helpers/devices/getMemInfo.js delete mode 100644 src/internals/manager/constants.js delete mode 100644 src/internals/manager/getMemInfos.js delete mode 100644 src/internals/manager/helpers.js diff --git a/src/commands/getMemInfo.js b/src/commands/getMemInfo.js new file mode 100644 index 00000000..8b175181 --- /dev/null +++ b/src/commands/getMemInfo.js @@ -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 = createCommand('devices', 'getMemInfo', ({ devicePath }) => + fromPromise(withDevice(devicePath)(transport => getMemInfo(transport))), +) + +export default cmd diff --git a/src/commands/installApp.js b/src/commands/installApp.js index d9f6d530..792a5661 100644 --- a/src/commands/installApp.js +++ b/src/commands/installApp.js @@ -2,8 +2,8 @@ import { createCommand, Command } from 'helpers/ipc' import { fromPromise } from 'rxjs/observable/fromPromise' -import { withDevice } from 'helpers/deviceAccess' +import { withDevice } from 'helpers/deviceAccess' import installApp from 'helpers/apps/installApp' import type { LedgerScriptParams } from 'helpers/common' diff --git a/src/commands/installFinalFirmware.js b/src/commands/installFinalFirmware.js new file mode 100644 index 00000000..775dd743 --- /dev/null +++ b/src/commands/installFinalFirmware.js @@ -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 = createCommand( + 'devices', + 'installFinalFirmware', + ({ devicePath, firmware }) => + fromPromise(withDevice(devicePath)(transport => installFinalFirmware(transport, firmware))), +) + +export default cmd diff --git a/src/commands/installMcu.js b/src/commands/installMcu.js new file mode 100644 index 00000000..b1fa57ed --- /dev/null +++ b/src/commands/installMcu.js @@ -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 = createCommand('devices', 'installMcu', () => + fromPromise(installMcu()), +) + +export default cmd diff --git a/src/commands/installOsuFirmware.js b/src/commands/installOsuFirmware.js new file mode 100644 index 00000000..f5110b13 --- /dev/null +++ b/src/commands/installOsuFirmware.js @@ -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 = createCommand( + 'devices', + 'installOsuFirmware', + ({ devicePath, firmware }) => + fromPromise(withDevice(devicePath)(transport => installOsuFirmware(transport, firmware))), +) + +export default cmd diff --git a/src/commands/uninstallApp.js b/src/commands/uninstallApp.js new file mode 100644 index 00000000..a07a2e8f --- /dev/null +++ b/src/commands/uninstallApp.js @@ -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 = createCommand( + 'devices', + 'uninstallApp', + ({ devicePath, ...rest }) => + fromPromise(withDevice(devicePath)(transport => uninstallApp(transport, rest))), +) + +export default cmd diff --git a/src/components/ManagerPage/AppsList.js b/src/components/ManagerPage/AppsList.js index 19581ff0..66d8dd88 100644 --- a/src/components/ManagerPage/AppsList.js +++ b/src/components/ManagerPage/AppsList.js @@ -107,8 +107,8 @@ class AppsList extends PureComponent { name={c.name} version={`Version ${c.version}`} icon={ICONS_FALLBACK[c.icon] || c.icon} - onInstall={() => {}} - onUninstall={() => {}} + onInstall={this.handleInstallApp(c)} + onUninstall={this.handleUninstallApp()} /> ))} { - // state = { - // currentTab: 'apps', - // } - - // componentWillReceiveProps(nextProps) { - // const { device } = this.props - // const { currentTab } = this.state - // if (device && !nextProps.device && currentTab === 'device') { - // this.setState({ currentTab: 'apps' }) - // } - // } - - // handleTabChange = t => this.setState({ currentTab: t.value }) - - createTabs = (device, nbDevices) => { - const { t } = this.props - return TABS.map(i => { - let label = t(`manager:tabs.${i.key}`) - if (i.key === 'device') { - if (!device) { - return null - } - label += ` (${nbDevices})` - } - return { ...i, label } - }).filter(Boolean) - } - render() { const { t } = this.props - // const { currentTab } = this.state return ( @@ -64,26 +29,20 @@ class ManagerPage extends Component { {deviceInfo => ( - {/* */} {deviceInfo.mcu && bootloader mode} {deviceInfo.final && osu mode} {!deviceInfo.mcu && !deviceInfo.final && ( - {/* */} + /> )} diff --git a/src/helpers/apps/uninstallApp.js b/src/helpers/apps/uninstallApp.js index 5fa45564..99a4175a 100644 --- a/src/helpers/apps/uninstallApp.js +++ b/src/helpers/apps/uninstallApp.js @@ -1,12 +1,16 @@ // @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, { -// action: uninstallApp, -// successResponse: 'manager.appUninstalled', -// errorResponse: 'manager.appUninstallError', -// })(data) +/** + * Install an app on the device + */ +export default async function uninstallApp( + transport: Transport<*>, + { appParams }: { appParams: LedgerScriptParams }, +): Promise { + return createSocketDialog(transport, '/update/install', appParams) +} diff --git a/src/helpers/devices/getMemInfo.js b/src/helpers/devices/getMemInfo.js new file mode 100644 index 00000000..5e5ad62e --- /dev/null +++ b/src/helpers/devices/getMemInfo.js @@ -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 { + 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' }) +} diff --git a/src/helpers/firmware/installFinalFirmware.js b/src/helpers/firmware/installFinalFirmware.js index 6e77ecdd..af3410ac 100644 --- a/src/helpers/firmware/installFinalFirmware.js +++ b/src/helpers/firmware/installFinalFirmware.js @@ -1,24 +1,26 @@ // @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' -type DataType = { - devicePath: string, +type Input = { 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 { - const transport = await CommNodeHid.open(data.devicePath) - const finalData = buildFinalParams(data.firmware) - await createSocketDialog(transport, '/update/install', finalData) - send('device.finalFirmwareInstallSuccess', { success: true }) + const osuData = buildOsuParams(data.firmware) + await createSocketDialog(transport, '/update/install', osuData) + return { success: true } } catch (err) { - send('device.finalFirmwareInstallError', { success: false }) + const error = Error(err.message) + error.stack = err.stack + const result = { success: false, error } + throw result } } diff --git a/src/helpers/firmware/installMcu.js b/src/helpers/firmware/installMcu.js index 0f8b7d99..e44f4d47 100644 --- a/src/helpers/firmware/installMcu.js +++ b/src/helpers/firmware/installMcu.js @@ -1 +1,8 @@ -// flow +// @flow + +type Result = Promise + +// TODO: IMPLEMENTATION FOR FLASHING FIRMWARE +// GETTING APDUS FROM SERVER +// SEND THE APDUS TO DEVICE +export default async (): Result => new Promise(resolve => resolve(true)) diff --git a/src/internals/devices/index.js b/src/internals/devices/index.js index ac971950..be28396f 100644 --- a/src/internals/devices/index.js +++ b/src/internals/devices/index.js @@ -8,6 +8,10 @@ import getFirmwareInfo from 'commands/getFirmwareInfo' import getIsGenuine from 'commands/getIsGenuine' import getLatestFirmwareForDevice from 'commands/getLatestFirmwareForDevice' import installApp from 'commands/installApp' +import uninstallApp from 'commands/uninstallApp' +import installOsuFirmware from 'commands/installOsuFirmware' +import installFinalFirmware from 'commands/installFinalFirmware' +import installMcu from 'commands/installMcu' import listen from './listen' // TODO port these to commands @@ -21,4 +25,8 @@ export const commands: Array> = [ getIsGenuine, getLatestFirmwareForDevice, installApp, + uninstallApp, + installOsuFirmware, + installFinalFirmware, + installMcu, ] diff --git a/src/internals/manager/constants.js b/src/internals/manager/constants.js deleted file mode 100644 index 3ca86b53..00000000 --- a/src/internals/manager/constants.js +++ /dev/null @@ -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], -} diff --git a/src/internals/manager/getMemInfos.js b/src/internals/manager/getMemInfos.js deleted file mode 100644 index 34845888..00000000 --- a/src/internals/manager/getMemInfos.js +++ /dev/null @@ -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) diff --git a/src/internals/manager/helpers.js b/src/internals/manager/helpers.js deleted file mode 100644 index 2a41210d..00000000 --- a/src/internals/manager/helpers.js +++ /dev/null @@ -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, - successResponse: string, - errorResponse: string, - }, -) { - console.log('DEPRECATED: createTransportHandler use withDevice and commands/*') - return async function transportHandler({ - devicePath, - ...params - }: { - devicePath: string, - }): Promise { - 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 { - 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 { - 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) - } -} diff --git a/src/internals/manager/index.js b/src/internals/manager/index.js index 3b2c0349..7f7a94af 100644 --- a/src/internals/manager/index.js +++ b/src/internals/manager/index.js @@ -2,6 +2,7 @@ import type { Command } from 'helpers/ipc' import listApps from 'commands/listApps' +import getMemInfo from 'commands/getMemInfo' /** * Manager @@ -19,6 +20,4 @@ import listApps from 'commands/listApps' * */ -export { default as getMemInfos } from './getMemInfos' - -export const commands: Array> = [listApps] +export const commands: Array> = [listApps, getMemInfo]