Browse Source

Merge branch 'master' into listenDevices-command

master
Gaëtan Renaudeau 7 years ago
committed by GitHub
parent
commit
9b12c967d2
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 19
      src/commands/getMemInfo.js
  2. 2
      src/commands/installApp.js
  3. 28
      src/commands/installFinalFirmware.js
  4. 28
      src/commands/installMcu.js
  5. 28
      src/commands/installOsuFirmware.js
  6. 25
      src/commands/uninstallApp.js
  7. 4
      src/components/ManagerPage/AppsList.js
  8. 49
      src/components/ManagerPage/index.js
  9. 20
      src/helpers/apps/uninstallApp.js
  10. 11
      src/helpers/devices/getMemInfo.js
  11. 24
      src/helpers/firmware/installFinalFirmware.js
  12. 9
      src/helpers/firmware/installMcu.js
  13. 8
      src/internals/devices/index.js
  14. 13
      src/internals/manager/constants.js
  15. 12
      src/internals/manager/getMemInfos.js
  16. 237
      src/internals/manager/helpers.js
  17. 5
      src/internals/manager/index.js

19
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<Input, Result> = createCommand('devices', 'getMemInfo', ({ devicePath }) =>
fromPromise(withDevice(devicePath)(transport => getMemInfo(transport))),
)
export default cmd

2
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'

28
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<Input, Result> = createCommand(
'devices',
'installFinalFirmware',
({ devicePath, firmware }) =>
fromPromise(withDevice(devicePath)(transport => installFinalFirmware(transport, firmware))),
)
export default cmd

28
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<Input, Result> = createCommand('devices', 'installMcu', () =>
fromPromise(installMcu()),
)
export default cmd

28
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<Input, Result> = createCommand(
'devices',
'installOsuFirmware',
({ devicePath, firmware }) =>
fromPromise(withDevice(devicePath)(transport => installOsuFirmware(transport, firmware))),
)
export default cmd

25
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<Input, Result> = createCommand(
'devices',
'uninstallApp',
({ devicePath, ...rest }) =>
fromPromise(withDevice(devicePath)(transport => uninstallApp(transport, rest))),
)
export default cmd

4
src/components/ManagerPage/AppsList.js

@ -107,8 +107,8 @@ class AppsList extends PureComponent<Props, State> {
name={c.name}
version={`Version ${c.version}`}
icon={ICONS_FALLBACK[c.icon] || c.icon}
onInstall={() => {}}
onUninstall={() => {}}
onInstall={this.handleInstallApp(c)}
onUninstall={this.handleUninstallApp()}
/>
))}
<Modal

49
src/components/ManagerPage/index.js

@ -5,57 +5,22 @@ import { translate } from 'react-i18next'
import type { T } from 'types/common'
// import Pills from 'components/base/Pills'
import AppsList from './AppsList'
// import DeviceInfos from './DeviceInfos'
// import FirmwareUpdate from './FirmwareUpdate'
import FirmwareUpdate from './FirmwareUpdate'
import EnsureDevice from './EnsureDevice'
import EnsureDashboard from './EnsureDashboard'
import EnsureGenuine from './EnsureGenuine'
const TABS = [{ key: 'apps', value: 'apps' }, { key: 'device', value: 'device' }]
type Props = {
t: T,
}
type State = {
// currentTab: 'apps' | 'device',
}
type State = {}
class ManagerPage extends Component<Props, State> {
// 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 (
<Fragment>
@ -64,26 +29,20 @@ class ManagerPage extends Component<Props, State> {
<EnsureDashboard device={device}>
{deviceInfo => (
<Fragment>
{/* <Pills
items={this.createTabs(device, nbDevices)}
activeKey={currentTab}
onChange={this.handleTabChange}
mb={6}
/> */}
{deviceInfo.mcu && <span>bootloader mode</span>}
{deviceInfo.final && <span>osu mode</span>}
{!deviceInfo.mcu &&
!deviceInfo.final && (
<EnsureGenuine device={device} t={t}>
{/* <FirmwareUpdate
<FirmwareUpdate
infos={{
targetId: deviceInfo.targetId,
version: deviceInfo.version,
}}
device={device}
t={t}
/> */}
/>
<AppsList device={device} />
</EnsureGenuine>
)}

20
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<void> {
return createSocketDialog(transport, '/update/install', appParams)
}

11
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<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' })
}

24
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
}
}

9
src/helpers/firmware/installMcu.js

@ -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))

8
src/internals/devices/index.js

@ -9,6 +9,10 @@ import getIsGenuine from 'commands/getIsGenuine'
import getLatestFirmwareForDevice from 'commands/getLatestFirmwareForDevice'
import installApp from 'commands/installApp'
import listenDevices from 'commands/listenDevices'
import uninstallApp from 'commands/uninstallApp'
import installOsuFirmware from 'commands/installOsuFirmware'
import installFinalFirmware from 'commands/installFinalFirmware'
import installMcu from 'commands/installMcu'
export const commands: Array<Command<any, any>> = [
getAddress,
@ -19,4 +23,8 @@ export const commands: Array<Command<any, any>> = [
getLatestFirmwareForDevice,
installApp,
listenDevices,
uninstallApp,
installOsuFirmware,
installFinalFirmware,
installMcu,
]

13
src/internals/manager/constants.js

@ -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],
}

12
src/internals/manager/getMemInfos.js

@ -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)

237
src/internals/manager/helpers.js

@ -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)
}
}

5
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<Command<any, any>> = [listApps]
export const commands: Array<Command<any, any>> = [listApps, getMemInfo]

Loading…
Cancel
Save