Browse Source

Fetch installable apps from api instead of currency list

master
meriadec 7 years ago
parent
commit
1512ec6b23
No known key found for this signature in database GPG Key ID: 1D2FC2305E2CB399
  1. 11
      src/components/ManagerPage/ManagerApp.js
  2. 59
      src/components/ManagerPage/index.js
  3. 11
      src/internals/usb/manager/constants.js
  4. 45
      src/internals/usb/manager/helpers.js
  5. 10
      src/internals/usb/manager/index.js
  6. 2
      src/renderer/events.js

11
src/components/ManagerPage/ManagerApp.js

@ -2,9 +2,6 @@
import React from 'react' import React from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import { getIconByCoinType } from '@ledgerhq/currencies/react'
import type { Currency } from '@ledgerhq/currencies'
import Box, { Tabbable } from 'components/base/Box' import Box, { Tabbable } from 'components/base/Box'
import Text from 'components/base/Text' import Text from 'components/base/Text'
@ -24,18 +21,16 @@ const ActionBtn = styled(Tabbable).attrs({
})`` })``
type Props = { type Props = {
currency: Currency, name: string,
onInstall: Function, onInstall: Function,
onUninstall: Function, onUninstall: Function,
} }
export default function ManagerApp(props: Props) { export default function ManagerApp(props: Props) {
const { currency, onInstall, onUninstall } = props const { name, onInstall, onUninstall } = props
const Icon = getIconByCoinType(currency.coinType)
return ( return (
<Container flow={3}> <Container flow={3}>
{Icon && <Icon size={24} />} <Text ff="Museo Sans|Bold">{name}</Text>
<Text>{currency.name}</Text>
<Box horizontal flow={2}> <Box horizontal flow={2}>
<ActionBtn onClick={onInstall}>{'Install'}</ActionBtn> <ActionBtn onClick={onInstall}>{'Install'}</ActionBtn>
<ActionBtn onClick={onUninstall}>{'Remove'}</ActionBtn> <ActionBtn onClick={onUninstall}>{'Remove'}</ActionBtn>

59
src/components/ManagerPage/index.js

@ -5,9 +5,7 @@ import { connect } from 'react-redux'
import styled from 'styled-components' import styled from 'styled-components'
import { translate } from 'react-i18next' import { translate } from 'react-i18next'
import { compose } from 'redux' import { compose } from 'redux'
import { listCurrencies } from '@ledgerhq/currencies'
import type { Currency } from '@ledgerhq/currencies'
import type { Device } from 'types/common' import type { Device } from 'types/common'
import { runJob } from 'renderer/events' import { runJob } from 'renderer/events'
@ -19,8 +17,6 @@ import Modal, { ModalBody } from 'components/base/Modal'
import ManagerApp from './ManagerApp' import ManagerApp from './ManagerApp'
const CURRENCIES = listCurrencies()
const List = styled(Box).attrs({ const List = styled(Box).attrs({
horizontal: true, horizontal: true,
m: -1, m: -1,
@ -36,25 +32,54 @@ type Props = {
device: Device, device: Device,
} }
type Status = 'idle' | 'busy' | 'success' | 'error' type Status = 'loading' | 'idle' | 'busy' | 'success' | 'error'
type LedgerApp = {
name: string,
app: Object,
}
type State = { type State = {
status: Status, status: Status,
error: string | null, error: string | null,
appsList: LedgerApp[],
} }
class ManagerPage extends PureComponent<Props, State> { class ManagerPage extends PureComponent<Props, State> {
state = { state = {
status: 'idle', status: 'loading',
error: null, error: null,
appsList: [],
}
componentDidMount() {
this.fetchList()
}
componentWillUnmount() {
this._unmounted = true
}
_unmounted = false
async fetchList() {
const appsList = await runJob({
channel: 'usb',
job: 'manager.listApps',
successResponse: 'manager.listAppsSuccess',
errorResponse: 'manager.listAppsError',
})
if (!this._unmounted) {
this.setState({ appsList, status: 'idle' })
}
} }
createDeviceJobHandler = options => (currency: Currency) => async () => { createDeviceJobHandler = options => ({ app: appParams }) => async () => {
this.setState({ status: 'busy' }) this.setState({ status: 'busy' })
try { try {
const { job, successResponse, errorResponse } = options const { job, successResponse, errorResponse } = options
const { device: { path: devicePath } } = this.props const { device: { path: devicePath } } = this.props
const data = { appName: currency.name.toLowerCase(), devicePath } const data = { appParams, devicePath }
await runJob({ channel: 'usb', job, successResponse, errorResponse, data }) await runJob({ channel: 'usb', job, successResponse, errorResponse, data })
this.setState({ status: 'success' }) this.setState({ status: 'success' })
} catch (err) { } catch (err) {
@ -78,10 +103,10 @@ class ManagerPage extends PureComponent<Props, State> {
renderList = () => ( renderList = () => (
<List> <List>
{CURRENCIES.map(c => ( {this.state.appsList.map(c => (
<ManagerApp <ManagerApp
key={c.coinType} key={c.name}
currency={c} name={c.name}
onInstall={this.handleInstall(c)} onInstall={this.handleInstall(c)}
onUninstall={this.handleUninstall(c)} onUninstall={this.handleUninstall(c)}
/> />
@ -100,9 +125,17 @@ class ManagerPage extends PureComponent<Props, State> {
<Box fontSize={8}>{'Connect your device'}</Box> <Box fontSize={8}>{'Connect your device'}</Box>
</Box> </Box>
)} )}
{deviceStatus === 'connected' && this.renderList()} {deviceStatus === 'connected' && (
<Box>
{status === 'loading' ? (
<Box ff="Museo Sans|Bold">{'Loading app list...'}</Box>
) : (
this.renderList()
)}
</Box>
)}
<Modal <Modal
isOpened={status !== 'idle'} isOpened={status !== 'idle' && status !== 'loading'}
render={() => ( render={() => (
<ModalBody p={6} align="center" justify="center" style={{ height: 300 }}> <ModalBody p={6} align="center" justify="center" style={{ height: 300 }}>
{status === 'busy' ? ( {status === 'busy' ? (

11
src/internals/usb/manager/constants.js

@ -1,17 +1,6 @@
// Socket endpoint // Socket endpoint
export const BASE_SOCKET_URL = 'ws://api.ledgerwallet.com/update/install' export const BASE_SOCKET_URL = 'ws://api.ledgerwallet.com/update/install'
// Apparently params we need to add to websocket requests
//
// see https://github.com/LedgerHQ/ledger-manager-chrome
// > controllers/manager/ApplyUpdateController.scala
//
// @TODO: Get rid of them.
export const DEFAULT_SOCKET_PARAMS = {
perso: 'perso_11',
hash: '0000000000000000000000000000000000000000000000000000000000000000',
}
// List of APDUS // List of APDUS
export const APDUS = { export const APDUS = {
GET_FIRMWARE: [0xe0, 0x01, 0x00, 0x00], GET_FIRMWARE: [0xe0, 0x01, 0x00, 0x00],

45
src/internals/usb/manager/helpers.js

@ -8,7 +8,7 @@ import noop from 'lodash/noop'
import type Transport from '@ledgerhq/hw-transport' import type Transport from '@ledgerhq/hw-transport'
import type { IPCSend } from 'types/electron' import type { IPCSend } from 'types/electron'
import { BASE_SOCKET_URL, DEFAULT_SOCKET_PARAMS, APDUS } from './constants' import { BASE_SOCKET_URL, APDUS } from './constants'
type WebsocketType = { type WebsocketType = {
send: (string, any) => void, send: (string, any) => void,
@ -22,6 +22,13 @@ type Message = {
data: any, data: any,
} }
type LedgerAppParams = {
firmware: string,
firmwareKey: string,
delete: string,
deleteKey: string,
}
/** /**
* Generate handler which create transport with given * Generate handler which create transport with given
* `devicePath` then call action with it * `devicePath` then call action with it
@ -63,15 +70,9 @@ export function createTransportHandler(
*/ */
export async function installApp( export async function installApp(
transport: Transport<*>, transport: Transport<*>,
{ appName }: { appName: string }, { appParams }: { appParams: LedgerAppParams },
): Promise<void> { ): Promise<void> {
log('INSTALL', `Request to install ${appName} app`) return createSocketDialog(transport, appParams)
return createSocketDialog(transport, ({ version }) => ({
firmware: `nanos/${version}/${appName}/app_latest`,
firmwareKey: `nanos/${version}/${appName}/app_latest_key`,
delete: `nanos/${version}/${appName}/app_del`,
deleteKey: `nanos/${version}/${appName}/app_del_key`,
}))
} }
/** /**
@ -79,15 +80,13 @@ export async function installApp(
*/ */
export async function uninstallApp( export async function uninstallApp(
transport: Transport<*>, transport: Transport<*>,
{ appName }: { appName: string }, { appParams }: { appParams: LedgerAppParams },
): Promise<void> { ): Promise<void> {
log('INSTALL', `Request to uninstall ${appName} app`) return createSocketDialog(transport, {
return createSocketDialog(transport, ({ version }) => ({ ...appParams,
firmware: `nanos/${version}/${appName}/app_del`, firmware: appParams.delete,
firmwareKey: `nanos/${version}/${appName}/app_del_key`, firmwareKey: appParams.deleteKey,
delete: `nanos/${version}/${appName}/app_del`, })
deleteKey: `nanos/${version}/${appName}/app_del_key`,
}))
} }
/** /**
@ -143,16 +142,10 @@ async function bulk(ws: WebsocketType, transport: Transport<*>, msg: Message) {
* Open socket connection with firmware api, and init a dialog * Open socket connection with firmware api, and init a dialog
* with the device * with the device
*/ */
function createSocketDialog(transport: Transport<*>, buildParams: Function) { function createSocketDialog(transport: Transport<*>, appParams: LedgerAppParams) {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
try { try {
const { targetId, version } = await getFirmwareInfo(transport) const url = `${BASE_SOCKET_URL}?${qs.stringify(appParams)}`
const fullParams = qs.stringify({
targetId,
...DEFAULT_SOCKET_PARAMS,
...buildParams({ targetId, version }),
})
const url = `${BASE_SOCKET_URL}?${fullParams}`
log('WS CONNECTING', url) log('WS CONNECTING', url)
const ws: WebsocketType = new Websocket(url) const ws: WebsocketType = new Websocket(url)
@ -195,7 +188,7 @@ function createSocketDialog(transport: Transport<*>, buildParams: Function) {
/** /**
* Retrieve targetId and firmware version from device * Retrieve targetId and firmware version from device
*/ */
async function getFirmwareInfo(transport: Transport<*>) { export async function getFirmwareInfo(transport: Transport<*>) {
const res = await transport.send(...APDUS.GET_FIRMWARE) const res = await transport.send(...APDUS.GET_FIRMWARE)
const byteArray = [...res] const byteArray = [...res]
const data = byteArray.slice(0, byteArray.length - 2) const data = byteArray.slice(0, byteArray.length - 2)

10
src/internals/usb/manager/index.js

@ -17,6 +17,7 @@
*/ */
import type { IPCSend } from 'types/electron' import type { IPCSend } from 'types/electron'
import axios from 'axios'
import { createTransportHandler, installApp, uninstallApp } from './helpers' import { createTransportHandler, installApp, uninstallApp } from './helpers'
export default (send: IPCSend) => ({ export default (send: IPCSend) => ({
@ -31,4 +32,13 @@ export default (send: IPCSend) => ({
successResponse: 'device.appUninstalled', successResponse: 'device.appUninstalled',
errorResponse: 'device.appUninstallError', errorResponse: 'device.appUninstallError',
}), }),
listApps: async () => {
try {
const { data } = await axios.get('https://api.ledgerwallet.com/update/applications')
send('manager.listAppsSuccess', data['nanos-1.4'])
} catch (err) {
send('manager.listAppsError', { message: err.message, stack: err.stack })
}
},
}) })

2
src/renderer/events.js

@ -56,7 +56,7 @@ export function runJob({
job: string, job: string,
successResponse: string, successResponse: string,
errorResponse: string, errorResponse: string,
data: any, data?: any,
}): Promise<void> { }): Promise<void> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
ipcRenderer.send(channel, { type: job, data }) ipcRenderer.send(channel, { type: job, data })

Loading…
Cancel
Save