Gaëtan Renaudeau
6 years ago
committed by
GitHub
138 changed files with 2525 additions and 3219 deletions
Binary file not shown.
@ -0,0 +1,17 @@ |
|||||
|
// @flow
|
||||
|
|
||||
|
import main from '@ledgerhq/live-common/lib/hw/firmwareUpdate-main' |
||||
|
import type { FirmwareUpdateContext } from '@ledgerhq/live-common/lib/types/manager' |
||||
|
import { createCommand, Command } from 'helpers/ipc' |
||||
|
|
||||
|
type Input = FirmwareUpdateContext |
||||
|
|
||||
|
type Result = { progress: number, installing: ?string } |
||||
|
|
||||
|
const cmd: Command<Input, Result> = createCommand( |
||||
|
'firmwareMain', |
||||
|
firmware => main('', firmware), |
||||
|
// devicePath='' HACK to not depend on a devicePath because it's dynamic
|
||||
|
) |
||||
|
|
||||
|
export default cmd |
@ -0,0 +1,18 @@ |
|||||
|
// @flow
|
||||
|
|
||||
|
import prepare from '@ledgerhq/live-common/lib/hw/firmwareUpdate-prepare' |
||||
|
import type { FirmwareUpdateContext } from '@ledgerhq/live-common/lib/types/manager' |
||||
|
import { createCommand, Command } from 'helpers/ipc' |
||||
|
|
||||
|
type Input = { |
||||
|
devicePath: string, |
||||
|
firmware: FirmwareUpdateContext, |
||||
|
} |
||||
|
|
||||
|
type Result = { progress: number } |
||||
|
|
||||
|
const cmd: Command<Input, Result> = createCommand('firmwarePrepare', ({ devicePath, firmware }) => |
||||
|
prepare(devicePath, firmware), |
||||
|
) |
||||
|
|
||||
|
export default cmd |
@ -0,0 +1,14 @@ |
|||||
|
// @flow
|
||||
|
|
||||
|
import repair from '@ledgerhq/live-common/lib/hw/firmwareUpdate-repair' |
||||
|
import { createCommand, Command } from 'helpers/ipc' |
||||
|
|
||||
|
type Input = void |
||||
|
type Result = { progress: number } |
||||
|
|
||||
|
const cmd: Command<Input, Result> = createCommand( |
||||
|
'firmwareRepair', |
||||
|
() => repair(''), // devicePath='' HACK to not depend on a devicePath because it's dynamic
|
||||
|
) |
||||
|
|
||||
|
export default cmd |
@ -1,21 +0,0 @@ |
|||||
// @flow
|
|
||||
|
|
||||
import { createCommand, Command } from 'helpers/ipc' |
|
||||
import { fromPromise } from 'rxjs/observable/fromPromise' |
|
||||
|
|
||||
import getCurrentFirmware from 'helpers/devices/getCurrentFirmware' |
|
||||
import type { FinalFirmware } from 'helpers/types' |
|
||||
|
|
||||
type Input = { |
|
||||
deviceId: string | number, |
|
||||
fullVersion: string, |
|
||||
provider: number, |
|
||||
} |
|
||||
|
|
||||
type Result = FinalFirmware |
|
||||
|
|
||||
const cmd: Command<Input, Result> = createCommand('getCurrentFirmware', data => |
|
||||
fromPromise(getCurrentFirmware(data)), |
|
||||
) |
|
||||
|
|
||||
export default cmd |
|
@ -1,15 +1,14 @@ |
|||||
// @flow
|
// @flow
|
||||
|
|
||||
import { createCommand, Command } from 'helpers/ipc' |
import { createCommand, Command } from 'helpers/ipc' |
||||
import { fromPromise } from 'rxjs/observable/fromPromise' |
import { from } from 'rxjs' |
||||
import type { DeviceInfo, OsuFirmware } from 'helpers/types' |
import type { DeviceInfo, FirmwareUpdateContext } from '@ledgerhq/live-common/lib/types/manager' |
||||
|
import manager from '@ledgerhq/live-common/lib/manager' |
||||
|
|
||||
import getLatestFirmwareForDevice from '../helpers/devices/getLatestFirmwareForDevice' |
type Result = ?FirmwareUpdateContext |
||||
|
|
||||
type Result = ?(OsuFirmware & { shouldFlashMcu: boolean }) |
const cmd: Command<DeviceInfo, Result> = createCommand('getLatestFirmwareForDevice', deviceInfo => |
||||
|
from(manager.getLatestFirmwareForDevice(deviceInfo)), |
||||
const cmd: Command<DeviceInfo, Result> = createCommand('getLatestFirmwareForDevice', data => |
|
||||
fromPromise(getLatestFirmwareForDevice(data)), |
|
||||
) |
) |
||||
|
|
||||
export default cmd |
export default cmd |
||||
|
@ -1,19 +0,0 @@ |
|||||
// @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('getMemInfo', ({ devicePath }) => |
|
||||
fromPromise(withDevice(devicePath)(transport => getMemInfo(transport))), |
|
||||
) |
|
||||
|
|
||||
export default cmd |
|
@ -1,25 +1,19 @@ |
|||||
// @flow
|
// @flow
|
||||
|
|
||||
import { createCommand, Command } from 'helpers/ipc' |
import { createCommand, Command } from 'helpers/ipc' |
||||
import { fromPromise } from 'rxjs/observable/fromPromise' |
import installApp from '@ledgerhq/live-common/lib/hw/installApp' |
||||
|
import { withDevice } from '@ledgerhq/live-common/lib/hw/deviceAccess' |
||||
import { withDevice } from 'helpers/deviceAccess' |
import type { ApplicationVersion } from '@ledgerhq/live-common/lib/types/manager' |
||||
import installApp from 'helpers/apps/installApp' |
|
||||
|
|
||||
import type { ApplicationVersion } from 'helpers/types' |
|
||||
|
|
||||
type Input = { |
type Input = { |
||||
app: ApplicationVersion, |
|
||||
devicePath: string, |
devicePath: string, |
||||
targetId: string | number, |
targetId: string | number, |
||||
|
app: ApplicationVersion, |
||||
} |
} |
||||
|
|
||||
type Result = void |
type Result = { progress: number } |
||||
|
|
||||
const cmd: Command<Input, Result> = createCommand( |
const cmd: Command<Input, Result> = createCommand('installApp', ({ devicePath, targetId, app }) => |
||||
'installApp', |
withDevice(devicePath)(transport => installApp(transport, targetId, app)), |
||||
({ devicePath, targetId, ...app }) => |
|
||||
fromPromise(withDevice(devicePath)(transport => installApp(transport, targetId, app))), |
|
||||
) |
) |
||||
|
|
||||
export default cmd |
export default cmd |
||||
|
@ -1,21 +0,0 @@ |
|||||
// @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, |
|
||||
} |
|
||||
|
|
||||
type Result = { |
|
||||
success: boolean, |
|
||||
} |
|
||||
|
|
||||
const cmd: Command<Input, Result> = createCommand('installFinalFirmware', ({ devicePath }) => |
|
||||
fromPromise(withDevice(devicePath)(transport => installFinalFirmware(transport))), |
|
||||
) |
|
||||
|
|
||||
export default cmd |
|
@ -1,19 +0,0 @@ |
|||||
// @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, |
|
||||
} |
|
||||
|
|
||||
type Result = void |
|
||||
|
|
||||
const cmd: Command<Input, Result> = createCommand('installMcu', ({ devicePath }) => |
|
||||
fromPromise(withDevice(devicePath)(transport => installMcu(transport))), |
|
||||
) |
|
||||
|
|
||||
export default cmd |
|
@ -1,27 +0,0 @@ |
|||||
// @flow
|
|
||||
|
|
||||
import { createCommand, Command } from 'helpers/ipc' |
|
||||
import { fromPromise } from 'rxjs/observable/fromPromise' |
|
||||
|
|
||||
import { withDevice } from 'helpers/deviceAccess' |
|
||||
import installOsuFirmware from 'helpers/firmware/installOsuFirmware' |
|
||||
|
|
||||
import type { Firmware } from 'components/modals/UpdateFirmware' |
|
||||
|
|
||||
type Input = { |
|
||||
devicePath: string, |
|
||||
targetId: string | number, |
|
||||
firmware: Firmware, |
|
||||
} |
|
||||
|
|
||||
type Result = { success: boolean } |
|
||||
|
|
||||
const cmd: Command<Input, Result> = createCommand( |
|
||||
'installOsuFirmware', |
|
||||
({ devicePath, firmware, targetId }) => |
|
||||
fromPromise( |
|
||||
withDevice(devicePath)(transport => installOsuFirmware(transport, targetId, firmware)), |
|
||||
), |
|
||||
) |
|
||||
|
|
||||
export default cmd |
|
@ -1,19 +0,0 @@ |
|||||
// @flow
|
|
||||
|
|
||||
import { createCommand, Command } from 'helpers/ipc' |
|
||||
import { fromPromise } from 'rxjs/observable/fromPromise' |
|
||||
import { withDevice } from 'helpers/deviceAccess' |
|
||||
|
|
||||
import isDashboardOpen from '../helpers/devices/isDashboardOpen' |
|
||||
|
|
||||
type Input = { |
|
||||
devicePath: string, |
|
||||
} |
|
||||
|
|
||||
type Result = boolean |
|
||||
|
|
||||
const cmd: Command<Input, Result> = createCommand('isDashboardOpen', ({ devicePath }) => |
|
||||
fromPromise(withDevice(devicePath)(transport => isDashboardOpen(transport))), |
|
||||
) |
|
||||
|
|
||||
export default cmd |
|
@ -1,15 +0,0 @@ |
|||||
// @flow
|
|
||||
|
|
||||
import { createCommand, Command } from 'helpers/ipc' |
|
||||
import { fromPromise } from 'rxjs/observable/fromPromise' |
|
||||
import type { DeviceInfo, ApplicationVersion } from 'helpers/types' |
|
||||
|
|
||||
import listAppVersions from 'helpers/apps/listAppVersions' |
|
||||
|
|
||||
type Result = Array<ApplicationVersion> |
|
||||
|
|
||||
const cmd: Command<DeviceInfo, Result> = createCommand('listAppVersions', deviceInfo => |
|
||||
fromPromise(listAppVersions(deviceInfo)), |
|
||||
) |
|
||||
|
|
||||
export default cmd |
|
@ -1,15 +0,0 @@ |
|||||
// @flow
|
|
||||
|
|
||||
import { createCommand, Command } from 'helpers/ipc' |
|
||||
import { fromPromise } from 'rxjs/observable/fromPromise' |
|
||||
|
|
||||
import listApps from 'helpers/apps/listApps' |
|
||||
import type { Application } from 'helpers/types' |
|
||||
|
|
||||
type Input = void |
|
||||
|
|
||||
type Result = Array<Application> |
|
||||
|
|
||||
const cmd: Command<Input, Result> = createCommand('listApps', () => fromPromise(listApps())) |
|
||||
|
|
||||
export default cmd |
|
@ -1,17 +0,0 @@ |
|||||
// @flow
|
|
||||
|
|
||||
import { createCommand, Command } from 'helpers/ipc' |
|
||||
import { fromPromise } from 'rxjs/observable/fromPromise' |
|
||||
|
|
||||
import listCategories from 'helpers/apps/listCategories' |
|
||||
import type { Category } from 'helpers/types' |
|
||||
|
|
||||
type Input = void |
|
||||
|
|
||||
type Result = Array<Category> |
|
||||
|
|
||||
const cmd: Command<Input, Result> = createCommand('listCategories', () => |
|
||||
fromPromise(listCategories()), |
|
||||
) |
|
||||
|
|
||||
export default cmd |
|
@ -1,15 +0,0 @@ |
|||||
// @flow
|
|
||||
|
|
||||
import { createCommand, Command } from 'helpers/ipc' |
|
||||
import { fromPromise } from 'rxjs/observable/fromPromise' |
|
||||
import shouldFlashMcu from 'helpers/devices/shouldFlashMcu' |
|
||||
|
|
||||
import type { DeviceInfo } from 'helpers/types' |
|
||||
|
|
||||
type Result = boolean |
|
||||
|
|
||||
const cmd: Command<DeviceInfo, Result> = createCommand('shouldFlashMcu', data => |
|
||||
fromPromise(shouldFlashMcu(data)), |
|
||||
) |
|
||||
|
|
||||
export default cmd |
|
@ -0,0 +1,171 @@ |
|||||
|
// @flow
|
||||
|
|
||||
|
import React, { PureComponent } from 'react' |
||||
|
import { warnings } from '@ledgerhq/live-common/lib/api/socket' |
||||
|
import { translate } from 'react-i18next' |
||||
|
import styled from 'styled-components' |
||||
|
|
||||
|
import { colors } from 'styles/theme' |
||||
|
import uniqueId from 'lodash/uniqueId' |
||||
|
import { openURL } from 'helpers/linking' |
||||
|
import IconCross from 'icons/Cross' |
||||
|
import IconExclamationCircle from 'icons/ExclamationCircle' |
||||
|
import IconChevronRight from 'icons/ChevronRight' |
||||
|
|
||||
|
import Box from 'components/base/Box' |
||||
|
import { SHOW_MOCK_HSMWARNINGS } from '../config/constants' |
||||
|
import { urls } from '../config/urls' |
||||
|
|
||||
|
const CloseIconContainer = styled.div` |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
right: 0; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
padding: 10px; |
||||
|
border-bottom-left-radius: 4px; |
||||
|
` |
||||
|
|
||||
|
const CloseIcon = (props: *) => ( |
||||
|
<CloseIconContainer {...props}> |
||||
|
<IconCross size={16} color="white" /> |
||||
|
</CloseIconContainer> |
||||
|
) |
||||
|
|
||||
|
type Props = { |
||||
|
t: *, |
||||
|
} |
||||
|
|
||||
|
type State = { |
||||
|
pendingMessages: HSMStatus[], |
||||
|
} |
||||
|
|
||||
|
type HSMStatus = { |
||||
|
id: string, |
||||
|
message: string, |
||||
|
} |
||||
|
|
||||
|
class HSMStatusBanner extends PureComponent<Props, State> { |
||||
|
state = { |
||||
|
pendingMessages: SHOW_MOCK_HSMWARNINGS |
||||
|
? [ |
||||
|
{ |
||||
|
id: 'mock1', |
||||
|
message: 'Lorem Ipsum dolor sit amet #1', |
||||
|
}, |
||||
|
] |
||||
|
: [], |
||||
|
} |
||||
|
|
||||
|
componentDidMount() { |
||||
|
this.warningSub = warnings.subscribe({ |
||||
|
next: message => { |
||||
|
this.setState(prevState => ({ |
||||
|
pendingMessages: [...prevState.pendingMessages, { id: uniqueId(), message }], |
||||
|
})) |
||||
|
}, |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
componentWillUnmount() { |
||||
|
if (this.warningSub) { |
||||
|
this.warningSub.unsubscribe() |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
warningSub = null |
||||
|
|
||||
|
dismiss = dismissedItem => |
||||
|
this.setState(prevState => ({ |
||||
|
pendingMessages: prevState.pendingMessages.filter(item => item.id !== dismissedItem.id), |
||||
|
})) |
||||
|
|
||||
|
render() { |
||||
|
const { t } = this.props |
||||
|
const { pendingMessages } = this.state |
||||
|
|
||||
|
if (!pendingMessages.length) return null |
||||
|
const item = pendingMessages[0] |
||||
|
|
||||
|
return ( |
||||
|
<Box flow={2} style={styles.container}> |
||||
|
<BannerItem key={item.id} t={t} item={item} onItemDismiss={this.dismiss} /> |
||||
|
</Box> |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
class BannerItem extends PureComponent<{ |
||||
|
item: HSMStatus, |
||||
|
onItemDismiss: HSMStatus => void, |
||||
|
t: *, |
||||
|
}> { |
||||
|
onLinkClick = () => openURL(urls.contactSupport) |
||||
|
dismiss = () => this.props.onItemDismiss(this.props.item) |
||||
|
|
||||
|
render() { |
||||
|
const { item, t } = this.props |
||||
|
return ( |
||||
|
<Box relative key={item.id} style={styles.banner}> |
||||
|
<CloseIcon onClick={this.dismiss} /> |
||||
|
<Box horizontal flow={2}> |
||||
|
<IconExclamationCircle size={16} color="white" /> |
||||
|
<Box shrink ff="Open Sans|SemiBold" style={styles.message}> |
||||
|
{item.message} |
||||
|
</Box> |
||||
|
</Box> |
||||
|
<BannerItemLink t={t} onClick={this.onLinkClick} /> |
||||
|
</Box> |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const UnderlinedLink = styled.span` |
||||
|
border-bottom: 1px solid transparent; |
||||
|
&:hover { |
||||
|
border-bottom-color: white; |
||||
|
} |
||||
|
` |
||||
|
|
||||
|
const BannerItemLink = ({ t, onClick }: { t: *, onClick: void => * }) => ( |
||||
|
<Box |
||||
|
mt={2} |
||||
|
ml={4} |
||||
|
flow={1} |
||||
|
horizontal |
||||
|
align="center" |
||||
|
cursor="pointer" |
||||
|
onClick={onClick} |
||||
|
color="white" |
||||
|
> |
||||
|
<IconChevronRight size={16} color="white" /> |
||||
|
<UnderlinedLink>{t('common.learnMore')}</UnderlinedLink> |
||||
|
</Box> |
||||
|
) |
||||
|
|
||||
|
const styles = { |
||||
|
container: { |
||||
|
position: 'fixed', |
||||
|
left: 32, |
||||
|
bottom: 32, |
||||
|
zIndex: 100, |
||||
|
}, |
||||
|
banner: { |
||||
|
background: colors.orange, |
||||
|
overflow: 'hidden', |
||||
|
borderRadius: 4, |
||||
|
fontSize: 13, |
||||
|
paddingTop: 17, |
||||
|
padding: 15, |
||||
|
color: 'white', |
||||
|
fontWeight: 'bold', |
||||
|
paddingRight: 30, |
||||
|
width: 350, |
||||
|
}, |
||||
|
message: { |
||||
|
marginTop: -3, |
||||
|
}, |
||||
|
} |
||||
|
|
||||
|
export default translate()(HSMStatusBanner) |
@ -0,0 +1,64 @@ |
|||||
|
// @flow
|
||||
|
|
||||
|
import React, { PureComponent } from 'react' |
||||
|
import styled, { css, keyframes } from 'styled-components' |
||||
|
|
||||
|
import { colors } from 'styles/theme' |
||||
|
|
||||
|
const animIndeterminate = keyframes` |
||||
|
0% { |
||||
|
transform: scaleX(0) translate3d(0, 0, 0); |
||||
|
} |
||||
|
50% { |
||||
|
transform: scaleX(1) translate3d(100%, 0, 0); |
||||
|
} |
||||
|
100% { |
||||
|
transform: scaleX(0) translate3d(0, 0, 0); |
||||
|
} |
||||
|
` |
||||
|
|
||||
|
const Outer = styled.div` |
||||
|
background-color: ${colors.fog}; |
||||
|
border-radius: 3px; |
||||
|
overflow: hidden; |
||||
|
height: 5px; |
||||
|
width: ${p => p.width}px; |
||||
|
position: relative; |
||||
|
` |
||||
|
|
||||
|
const Inner = styled.div` |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
right: 0; |
||||
|
bottom: 0; |
||||
|
background-color: ${colors.wallet}; |
||||
|
transform-origin: center left; |
||||
|
|
||||
|
${p => |
||||
|
p.progress === 0 |
||||
|
? css` |
||||
|
animation: ${animIndeterminate} 2s cubic-bezier(0.61, 0.01, 0.39, 1.03) infinite; |
||||
|
` |
||||
|
: css` |
||||
|
transform: scaleX(${p => p.progress}); |
||||
|
`};
|
||||
|
` |
||||
|
|
||||
|
type Props = { |
||||
|
progress: number, |
||||
|
width: number, |
||||
|
} |
||||
|
|
||||
|
class ProgressBar extends PureComponent<Props> { |
||||
|
render() { |
||||
|
const { progress, width } = this.props |
||||
|
return ( |
||||
|
<Outer width={width}> |
||||
|
<Inner progress={progress} /> |
||||
|
</Outer> |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export default ProgressBar |
@ -0,0 +1,16 @@ |
|||||
|
// @flow
|
||||
|
|
||||
|
import React from 'react' |
||||
|
import { storiesOf } from '@storybook/react' |
||||
|
import { number } from '@storybook/addon-knobs' |
||||
|
|
||||
|
import ProgressBar from 'components/ProgressBar' |
||||
|
|
||||
|
const stories = storiesOf('Components', module) |
||||
|
|
||||
|
stories.add('ProgressBar', () => ( |
||||
|
<ProgressBar |
||||
|
progress={number('progress', 0, { min: 0, max: 1, step: 0.05 })} |
||||
|
width={number('width', 200, { min: 50, max: 500, step: 10 })} |
||||
|
/> |
||||
|
)) |
@ -0,0 +1,98 @@ |
|||||
|
// @flow
|
||||
|
|
||||
|
import React, { PureComponent } from 'react' |
||||
|
import styled, { css, keyframes } from 'styled-components' |
||||
|
|
||||
|
import { colors } from 'styles/theme' |
||||
|
|
||||
|
import Text from 'components/base/Text' |
||||
|
|
||||
|
const STROKE_WIDTH = 5 |
||||
|
|
||||
|
type Props = { |
||||
|
progress: number, |
||||
|
size: number, |
||||
|
} |
||||
|
|
||||
|
const animIndeterminate = keyframes` |
||||
|
0% { |
||||
|
} |
||||
|
50% { |
||||
|
} |
||||
|
100% { |
||||
|
} |
||||
|
` |
||||
|
|
||||
|
const InnerCircle = styled.circle` |
||||
|
transform-origin: 50% 50%; |
||||
|
${p => |
||||
|
p.progress === 0 |
||||
|
? css` |
||||
|
animation: ${animIndeterminate} 3s cubic-bezier(0.61, 0.01, 0.39, 1.03) infinite; |
||||
|
` |
||||
|
: css` |
||||
|
transition: stroke-dashoffset 0.35s; |
||||
|
transform: rotate(-90deg); |
||||
|
`};
|
||||
|
` |
||||
|
|
||||
|
const Container = styled.div` |
||||
|
position: relative; |
||||
|
width: ${p => p.size}px; |
||||
|
height: ${p => p.size}px; |
||||
|
` |
||||
|
|
||||
|
const TextContainer = styled.div` |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
right: 0; |
||||
|
bottom: 0; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
` |
||||
|
|
||||
|
class ProgressCircle extends PureComponent<Props> { |
||||
|
render() { |
||||
|
const { size, progress } = this.props |
||||
|
const radius = size / 2 |
||||
|
const normalizedRadius = radius - STROKE_WIDTH / 2 |
||||
|
const circumference = normalizedRadius * 2 * Math.PI |
||||
|
const strokeDashoffset = circumference - progress * circumference |
||||
|
|
||||
|
return ( |
||||
|
<Container size={size}> |
||||
|
<TextContainer> |
||||
|
<Text ff="Rubik|Regular" color="graphite" fontSize={5}> |
||||
|
{`${Math.round(progress * 100)}%`} |
||||
|
</Text> |
||||
|
</TextContainer> |
||||
|
<svg height={size} width={size}> |
||||
|
<circle |
||||
|
stroke={colors.fog} |
||||
|
fill="transparent" |
||||
|
strokeWidth={STROKE_WIDTH} |
||||
|
style={{ strokeDashoffset }} |
||||
|
r={normalizedRadius} |
||||
|
cx={radius} |
||||
|
cy={radius} |
||||
|
/> |
||||
|
<InnerCircle |
||||
|
progress={progress} |
||||
|
stroke={colors.wallet} |
||||
|
fill="transparent" |
||||
|
strokeWidth={STROKE_WIDTH} |
||||
|
strokeDasharray={`${circumference} ${circumference}`} |
||||
|
style={{ strokeDashoffset }} |
||||
|
r={normalizedRadius} |
||||
|
cx={radius} |
||||
|
cy={radius} |
||||
|
/> |
||||
|
</svg> |
||||
|
</Container> |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export default ProgressCircle |
@ -0,0 +1,16 @@ |
|||||
|
// @flow
|
||||
|
|
||||
|
import React from 'react' |
||||
|
import { storiesOf } from '@storybook/react' |
||||
|
import { number } from '@storybook/addon-knobs' |
||||
|
|
||||
|
import ProgressCircle from 'components/ProgressCircle' |
||||
|
|
||||
|
const stories = storiesOf('Components', module) |
||||
|
|
||||
|
stories.add('ProgressCircle', () => ( |
||||
|
<ProgressCircle |
||||
|
progress={number('progress', 0, { min: 0, max: 1, step: 0.01 })} |
||||
|
size={number('width', 150, { min: 50, max: 500, step: 10 })} |
||||
|
/> |
||||
|
)) |
@ -0,0 +1,103 @@ |
|||||
|
// @flow
|
||||
|
|
||||
|
import React, { Fragment, PureComponent } from 'react' |
||||
|
import { compose } from 'redux' |
||||
|
import { connect } from 'react-redux' |
||||
|
import { withRouter } from 'react-router' |
||||
|
import { translate } from 'react-i18next' |
||||
|
import { push } from 'react-router-redux' |
||||
|
|
||||
|
import type { T } from 'types/common' |
||||
|
import firmwareRepair from 'commands/firmwareRepair' |
||||
|
import Button from 'components/base/Button' |
||||
|
import { RepairModal } from 'components/base/Modal' |
||||
|
|
||||
|
type Props = { |
||||
|
t: T, |
||||
|
push: string => void, |
||||
|
} |
||||
|
|
||||
|
type State = { |
||||
|
opened: boolean, |
||||
|
isLoading: boolean, |
||||
|
error: ?Error, |
||||
|
progress: number, |
||||
|
} |
||||
|
|
||||
|
class RepairDeviceButton extends PureComponent<Props, State> { |
||||
|
state = { |
||||
|
opened: false, |
||||
|
isLoading: false, |
||||
|
error: null, |
||||
|
progress: 0, |
||||
|
} |
||||
|
|
||||
|
open = () => this.setState({ opened: true, error: null }) |
||||
|
|
||||
|
sub: * |
||||
|
|
||||
|
close = () => { |
||||
|
if (this.sub) this.sub.unsubscribe() |
||||
|
this.setState({ opened: false, isLoading: false, error: null, progress: 0 }) |
||||
|
} |
||||
|
|
||||
|
action = () => { |
||||
|
if (this.state.isLoading) return |
||||
|
const { push } = this.props |
||||
|
this.setState({ isLoading: true }) |
||||
|
this.sub = firmwareRepair.send().subscribe({ |
||||
|
next: patch => { |
||||
|
this.setState(patch) |
||||
|
}, |
||||
|
error: error => { |
||||
|
this.setState({ error, isLoading: false, progress: 0 }) |
||||
|
}, |
||||
|
complete: () => { |
||||
|
this.setState({ opened: false, isLoading: false, progress: 0 }, () => { |
||||
|
push('/manager') |
||||
|
}) |
||||
|
}, |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
render() { |
||||
|
const { t } = this.props |
||||
|
const { opened, isLoading, error, progress } = this.state |
||||
|
|
||||
|
return ( |
||||
|
<Fragment> |
||||
|
<Button small primary onClick={this.open} event="RepairDeviceButton"> |
||||
|
{t('settings.repairDevice.button')} |
||||
|
</Button> |
||||
|
|
||||
|
<RepairModal |
||||
|
cancellable |
||||
|
analyticsName="RepairDevice" |
||||
|
isOpened={opened} |
||||
|
onClose={this.close} |
||||
|
onReject={this.close} |
||||
|
onConfirm={this.action} |
||||
|
isLoading={isLoading} |
||||
|
title={t('settings.repairDevice.title')} |
||||
|
desc={t('settings.repairDevice.desc')} |
||||
|
confirmText={t('settings.repairDevice.button')} |
||||
|
progress={progress} |
||||
|
error={error} |
||||
|
/> |
||||
|
</Fragment> |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const mapDispatchToProps = { |
||||
|
push, |
||||
|
} |
||||
|
|
||||
|
export default compose( |
||||
|
translate(), |
||||
|
withRouter, |
||||
|
connect( |
||||
|
null, |
||||
|
mapDispatchToProps, |
||||
|
), |
||||
|
)(RepairDeviceButton) |
@ -0,0 +1,203 @@ |
|||||
|
// @flow
|
||||
|
|
||||
|
import React, { PureComponent } from 'react' |
||||
|
import { translate } from 'react-i18next' |
||||
|
import styled from 'styled-components' |
||||
|
|
||||
|
import type { T } from 'types/common' |
||||
|
|
||||
|
import { i } from 'helpers/staticPath' |
||||
|
import TrackPage from 'analytics/TrackPage' |
||||
|
import Button from 'components/base/Button' |
||||
|
import Box from 'components/base/Box' |
||||
|
import Text from 'components/base/Text' |
||||
|
import ProgressCircle from 'components/ProgressCircle' |
||||
|
import TranslatedError from 'components/TranslatedError' |
||||
|
import ExclamationCircleThin from 'icons/ExclamationCircleThin' |
||||
|
|
||||
|
import { Modal, ModalContent, ModalBody, ModalTitle, ModalFooter } from './index' |
||||
|
|
||||
|
const Container = styled(Box).attrs({ |
||||
|
alignItems: 'center', |
||||
|
fontSize: 4, |
||||
|
color: 'dark', |
||||
|
})`` |
||||
|
|
||||
|
const Bullet = styled.span` |
||||
|
font-weight: 600; |
||||
|
color: #142533; |
||||
|
` |
||||
|
|
||||
|
const Separator = styled(Box).attrs({ |
||||
|
color: 'fog', |
||||
|
})` |
||||
|
height: 1px; |
||||
|
width: 100%; |
||||
|
background-color: currentColor; |
||||
|
` |
||||
|
|
||||
|
const DisclaimerStep = ({ desc }: { desc?: string }) => ( |
||||
|
<ModalContent> |
||||
|
{desc ? ( |
||||
|
<Box ff="Open Sans" color="smoke" fontSize={4} textAlign="center" mb={2}> |
||||
|
{desc} |
||||
|
</Box> |
||||
|
) : null} |
||||
|
</ModalContent> |
||||
|
) |
||||
|
|
||||
|
const FlashStep = ({ progress, t }: { progress: number, t: * }) => |
||||
|
progress === 0 ? ( |
||||
|
<ModalContent> |
||||
|
<Box mx={7}> |
||||
|
<Text ff="Open Sans|Regular" align="center" color="smoke"> |
||||
|
<Bullet>{'1.'}</Bullet> |
||||
|
{t('manager.modal.mcuFirst')} |
||||
|
</Text> |
||||
|
<img |
||||
|
src={i('logos/unplugDevice.png')} |
||||
|
style={{ width: '100%', maxWidth: 368, marginTop: 30 }} |
||||
|
alt={t('manager.modal.mcuFirst')} |
||||
|
/> |
||||
|
</Box> |
||||
|
<Separator my={6} /> |
||||
|
<Box mx={7}> |
||||
|
<Text ff="Open Sans|Regular" align="center" color="smoke"> |
||||
|
<Bullet>{'2.'}</Bullet> |
||||
|
{t('manager.modal.mcuSecond')} |
||||
|
</Text> |
||||
|
<img |
||||
|
src={i('logos/bootloaderMode.png')} |
||||
|
style={{ width: '100%', maxWidth: 368, marginTop: 30 }} |
||||
|
alt={t('manager.modal.mcuFirst')} |
||||
|
/> |
||||
|
</Box> |
||||
|
</ModalContent> |
||||
|
) : ( |
||||
|
<ModalContent> |
||||
|
<Box mx={7} align="center"> |
||||
|
<ProgressCircle size={64} progress={progress} /> |
||||
|
</Box> |
||||
|
<Box mx={7} mt={3} mb={2} ff="Museo Sans|Regular" color="dark" textAlign="center"> |
||||
|
{t(`manager.modal.steps.flash`)} |
||||
|
</Box> |
||||
|
<Box mx={7} mt={2} mb={2}> |
||||
|
<Text ff="Open Sans|Regular" align="center" color="graphite" fontSize={4}> |
||||
|
{t('manager.modal.mcuPin')} |
||||
|
</Text> |
||||
|
</Box> |
||||
|
</ModalContent> |
||||
|
) |
||||
|
|
||||
|
const ErrorStep = ({ error }: { error: Error }) => ( |
||||
|
<ModalContent> |
||||
|
<Container> |
||||
|
<Box color="alertRed"> |
||||
|
<ExclamationCircleThin size={44} /> |
||||
|
</Box> |
||||
|
<Box |
||||
|
color="dark" |
||||
|
mt={4} |
||||
|
fontSize={6} |
||||
|
ff="Museo Sans|Regular" |
||||
|
textAlign="center" |
||||
|
style={{ maxWidth: 350 }} |
||||
|
> |
||||
|
<TranslatedError error={error} field="title" /> |
||||
|
</Box> |
||||
|
<Box |
||||
|
color="graphite" |
||||
|
mt={4} |
||||
|
fontSize={6} |
||||
|
ff="Open Sans" |
||||
|
textAlign="center" |
||||
|
style={{ maxWidth: 350 }} |
||||
|
> |
||||
|
<TranslatedError error={error} field="description" /> |
||||
|
</Box> |
||||
|
</Container> |
||||
|
</ModalContent> |
||||
|
) |
||||
|
|
||||
|
type Props = { |
||||
|
isOpened: boolean, |
||||
|
isDanger: boolean, |
||||
|
title: string, |
||||
|
subTitle?: string, |
||||
|
desc: string, |
||||
|
renderIcon?: Function, |
||||
|
confirmText?: string, |
||||
|
cancelText?: string, |
||||
|
onReject: Function, |
||||
|
onConfirm: Function, |
||||
|
t: T, |
||||
|
isLoading?: boolean, |
||||
|
analyticsName: string, |
||||
|
cancellable?: boolean, |
||||
|
progress: number, |
||||
|
error?: Error, |
||||
|
} |
||||
|
|
||||
|
class RepairModal extends PureComponent<Props> { |
||||
|
render() { |
||||
|
const { |
||||
|
cancellable, |
||||
|
isOpened, |
||||
|
title, |
||||
|
desc, |
||||
|
confirmText, |
||||
|
isDanger, |
||||
|
onReject, |
||||
|
onConfirm, |
||||
|
isLoading, |
||||
|
renderIcon, |
||||
|
t, |
||||
|
analyticsName, |
||||
|
progress, |
||||
|
error, |
||||
|
...props |
||||
|
} = this.props |
||||
|
|
||||
|
const realConfirmText = confirmText || t('common.confirm') |
||||
|
|
||||
|
return ( |
||||
|
<Modal |
||||
|
isOpened={isOpened} |
||||
|
preventBackdropClick={isLoading} |
||||
|
{...props} |
||||
|
render={({ onClose }) => ( |
||||
|
<ModalBody onClose={!cancellable && isLoading ? undefined : onClose}> |
||||
|
<TrackPage category="Modal" name={analyticsName} /> |
||||
|
<ModalTitle>{title}</ModalTitle> |
||||
|
{error ? ( |
||||
|
<ErrorStep error={error} /> |
||||
|
) : isLoading ? ( |
||||
|
<FlashStep t={t} progress={progress} /> |
||||
|
) : ( |
||||
|
<DisclaimerStep desc={desc} /> |
||||
|
)} |
||||
|
|
||||
|
{!isLoading ? ( |
||||
|
<ModalFooter horizontal align="center" justify="flex-end" flow={2}> |
||||
|
<Button onClick={onReject}>{t(`common.${error ? 'close' : 'cancel'}`)}</Button> |
||||
|
{error ? null : ( |
||||
|
<Button |
||||
|
onClick={onConfirm} |
||||
|
primary={!isDanger} |
||||
|
danger={isDanger} |
||||
|
isLoading={isLoading} |
||||
|
disabled={isLoading} |
||||
|
> |
||||
|
{realConfirmText} |
||||
|
</Button> |
||||
|
)} |
||||
|
</ModalFooter> |
||||
|
) : null} |
||||
|
</ModalBody> |
||||
|
)} |
||||
|
/> |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export default translate()(RepairModal) |
@ -1,53 +0,0 @@ |
|||||
// @flow
|
|
||||
|
|
||||
// TODO we need to start porting all custom errors here.
|
|
||||
|
|
||||
import { createCustomErrorClass } from 'helpers/errors' |
|
||||
|
|
||||
export const AccountNameRequiredError = createCustomErrorClass('AccountNameRequired') |
|
||||
export const BtcUnmatchedApp = createCustomErrorClass('BtcUnmatchedApp') |
|
||||
export const CantOpenDevice = createCustomErrorClass('CantOpenDevice') |
|
||||
export const DeviceAppVerifyNotSupported = createCustomErrorClass('DeviceAppVerifyNotSupported') |
|
||||
export const DeviceGenuineSocketEarlyClose = createCustomErrorClass('DeviceGenuineSocketEarlyClose') |
|
||||
export const DeviceNotGenuineError = createCustomErrorClass('DeviceNotGenuine') |
|
||||
export const DeviceSocketFail = createCustomErrorClass('DeviceSocketFail') |
|
||||
export const DeviceSocketNoBulkStatus = createCustomErrorClass('DeviceSocketNoBulkStatus') |
|
||||
export const DeviceSocketNoHandler = createCustomErrorClass('DeviceSocketNoHandler') |
|
||||
export const DisconnectedDevice = createCustomErrorClass('DisconnectedDevice') |
|
||||
export const EnpointConfigError = createCustomErrorClass('EnpointConfig') |
|
||||
export const FeeEstimationFailed = createCustomErrorClass('FeeEstimationFailed') |
|
||||
export const HardResetFail = createCustomErrorClass('HardResetFail') |
|
||||
export const InvalidAddress = createCustomErrorClass('InvalidAddress') |
|
||||
export const LatestMCUInstalledError = createCustomErrorClass('LatestMCUInstalledError') |
|
||||
export const LedgerAPIError = createCustomErrorClass('LedgerAPIError') |
|
||||
export const LedgerAPIErrorWithMessage = createCustomErrorClass('LedgerAPIErrorWithMessage') |
|
||||
export const LedgerAPINotAvailable = createCustomErrorClass('LedgerAPINotAvailable') |
|
||||
export const ManagerAppAlreadyInstalledError = createCustomErrorClass('ManagerAppAlreadyInstalled') |
|
||||
export const ManagerAppRelyOnBTCError = createCustomErrorClass('ManagerAppRelyOnBTC') |
|
||||
export const ManagerDeviceLockedError = createCustomErrorClass('ManagerDeviceLocked') |
|
||||
export const ManagerNotEnoughSpaceError = createCustomErrorClass('ManagerNotEnoughSpace') |
|
||||
export const ManagerUninstallBTCDep = createCustomErrorClass('ManagerUninstallBTCDep') |
|
||||
export const NetworkDown = createCustomErrorClass('NetworkDown') |
|
||||
export const NoAddressesFound = createCustomErrorClass('NoAddressesFound') |
|
||||
export const NotEnoughBalance = createCustomErrorClass('NotEnoughBalance') |
|
||||
export const NotEnoughBalanceBecauseDestinationNotCreated = createCustomErrorClass( |
|
||||
'NotEnoughBalanceBecauseDestinationNotCreated', |
|
||||
) |
|
||||
export const PasswordsDontMatchError = createCustomErrorClass('PasswordsDontMatch') |
|
||||
export const PasswordIncorrectError = createCustomErrorClass('PasswordIncorrect') |
|
||||
export const TimeoutTagged = createCustomErrorClass('TimeoutTagged') |
|
||||
export const UpdateYourApp = createCustomErrorClass('UpdateYourApp') |
|
||||
export const UserRefusedAddress = createCustomErrorClass('UserRefusedAddress') |
|
||||
export const UserRefusedFirmwareUpdate = createCustomErrorClass('UserRefusedFirmwareUpdate') |
|
||||
export const UserRefusedOnDevice = createCustomErrorClass('UserRefusedOnDevice') // TODO rename because it's just for transaction refusal
|
|
||||
export const WebsocketConnectionError = createCustomErrorClass('WebsocketConnectionError') |
|
||||
export const WebsocketConnectionFailed = createCustomErrorClass('WebsocketConnectionFailed') |
|
||||
export const WrongDeviceForAccount = createCustomErrorClass('WrongDeviceForAccount') |
|
||||
export const ETHAddressNonEIP = createCustomErrorClass('ETHAddressNonEIP') |
|
||||
export const CantScanQRCode = createCustomErrorClass('CantScanQRCode') |
|
||||
export const FeeNotLoaded = createCustomErrorClass('FeeNotLoaded') |
|
||||
|
|
||||
// db stuff, no need to translate
|
|
||||
export const NoDBPathGiven = createCustomErrorClass('NoDBPathGiven') |
|
||||
export const DBWrongPassword = createCustomErrorClass('DBWrongPassword') |
|
||||
export const DBNotReset = createCustomErrorClass('DBNotReset') |
|
@ -1,52 +0,0 @@ |
|||||
// @flow
|
|
||||
import type Transport from '@ledgerhq/hw-transport' |
|
||||
|
|
||||
import { createDeviceSocket } from 'helpers/socket' |
|
||||
|
|
||||
import type { ApplicationVersion } from 'helpers/types' |
|
||||
import { WS_INSTALL } from 'helpers/urls' |
|
||||
|
|
||||
import { |
|
||||
ManagerNotEnoughSpaceError, |
|
||||
ManagerDeviceLockedError, |
|
||||
ManagerAppAlreadyInstalledError, |
|
||||
ManagerAppRelyOnBTCError, |
|
||||
} from 'config/errors' |
|
||||
|
|
||||
function remapError(promise) { |
|
||||
return promise.catch((e: Error) => { |
|
||||
switch (true) { |
|
||||
case e.message.endsWith('6982'): |
|
||||
throw new ManagerDeviceLockedError() |
|
||||
case e.message.endsWith('6a84') || e.message.endsWith('6a85'): |
|
||||
throw new ManagerNotEnoughSpaceError() |
|
||||
case e.message.endsWith('6a80') || e.message.endsWith('6a81'): |
|
||||
throw new ManagerAppAlreadyInstalledError() |
|
||||
case e.message.endsWith('6a83'): |
|
||||
throw new ManagerAppRelyOnBTCError() |
|
||||
default: |
|
||||
throw e |
|
||||
} |
|
||||
}) |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Install an app on the device |
|
||||
*/ |
|
||||
export default async function installApp( |
|
||||
transport: Transport<*>, |
|
||||
targetId: string | number, |
|
||||
{ app }: { app: ApplicationVersion }, |
|
||||
): Promise<void> { |
|
||||
const params = { |
|
||||
targetId, |
|
||||
perso: app.perso, |
|
||||
deleteKey: app.delete_key, |
|
||||
firmware: app.firmware, |
|
||||
firmwareKey: app.firmware_key, |
|
||||
hash: app.hash, |
|
||||
} |
|
||||
|
|
||||
const url = WS_INSTALL(params) |
|
||||
await remapError(createDeviceSocket(transport, url).toPromise()) |
|
||||
} |
|
@ -1,30 +0,0 @@ |
|||||
// @flow
|
|
||||
import network from 'api/network' |
|
||||
import type { DeviceInfo, DeviceVersion, FinalFirmware, ApplicationVersion } from 'helpers/types' |
|
||||
|
|
||||
import { APPLICATIONS_BY_DEVICE } from 'helpers/urls' |
|
||||
import getDeviceVersion from 'helpers/devices/getDeviceVersion' |
|
||||
import getCurrentFirmware from 'helpers/devices/getCurrentFirmware' |
|
||||
|
|
||||
type NetworkResponse = { data: { application_versions: Array<ApplicationVersion> } } |
|
||||
|
|
||||
export default async (deviceInfo: DeviceInfo): Promise<Array<ApplicationVersion>> => { |
|
||||
const deviceData: DeviceVersion = await getDeviceVersion( |
|
||||
deviceInfo.targetId, |
|
||||
deviceInfo.providerId, |
|
||||
) |
|
||||
const firmwareData: FinalFirmware = await getCurrentFirmware({ |
|
||||
deviceId: deviceData.id, |
|
||||
fullVersion: deviceInfo.fullVersion, |
|
||||
provider: deviceInfo.providerId, |
|
||||
}) |
|
||||
const params = { |
|
||||
provider: deviceInfo.providerId, |
|
||||
current_se_firmware_final_version: firmwareData.id, |
|
||||
device_version: deviceData.id, |
|
||||
} |
|
||||
const { |
|
||||
data: { application_versions }, |
|
||||
}: NetworkResponse = await network({ method: 'POST', url: APPLICATIONS_BY_DEVICE, data: params }) |
|
||||
return application_versions.length > 0 ? application_versions : [] |
|
||||
} |
|
@ -1,10 +0,0 @@ |
|||||
// @flow
|
|
||||
import network from 'api/network' |
|
||||
|
|
||||
import { GET_APPLICATIONS } from 'helpers/urls' |
|
||||
import type { Application } from 'helpers/types' |
|
||||
|
|
||||
export default async (): Promise<Array<Application>> => { |
|
||||
const { data } = await network({ method: 'GET', url: GET_APPLICATIONS }) |
|
||||
return data.length > 0 ? data : [] |
|
||||
} |
|
@ -1,10 +0,0 @@ |
|||||
// @flow
|
|
||||
import network from 'api/network' |
|
||||
|
|
||||
import { GET_CATEGORIES } from 'helpers/urls' |
|
||||
import type { Category } from 'helpers/types' |
|
||||
|
|
||||
export default async (): Promise<Array<Category>> => { |
|
||||
const { data }: { data: Array<Category> } = await network({ method: 'GET', url: GET_CATEGORIES }) |
|
||||
return data.length > 0 ? data : [] |
|
||||
} |
|
@ -1,41 +0,0 @@ |
|||||
// @flow
|
|
||||
import type Transport from '@ledgerhq/hw-transport' |
|
||||
|
|
||||
import { createDeviceSocket } from 'helpers/socket' |
|
||||
|
|
||||
import type { ApplicationVersion } from 'helpers/types' |
|
||||
import { ManagerDeviceLockedError, ManagerUninstallBTCDep } from 'config/errors' |
|
||||
import { WS_INSTALL } from 'helpers/urls' |
|
||||
|
|
||||
function remapError(promise) { |
|
||||
return promise.catch((e: Error) => { |
|
||||
switch (true) { |
|
||||
case e.message.endsWith('6982'): |
|
||||
throw new ManagerDeviceLockedError() |
|
||||
case e.message.endsWith('6a83'): |
|
||||
throw new ManagerUninstallBTCDep() |
|
||||
default: |
|
||||
throw e |
|
||||
} |
|
||||
}) |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Install an app on the device |
|
||||
*/ |
|
||||
export default async function uninstallApp( |
|
||||
transport: Transport<*>, |
|
||||
targetId: string | number, |
|
||||
{ app }: { app: ApplicationVersion }, |
|
||||
): Promise<void> { |
|
||||
const params = { |
|
||||
targetId, |
|
||||
perso: app.perso, |
|
||||
deleteKey: app.delete_key, |
|
||||
firmware: app.delete, |
|
||||
firmwareKey: app.delete_key, |
|
||||
hash: app.hash, |
|
||||
} |
|
||||
const url = WS_INSTALL(params) |
|
||||
await remapError(createDeviceSocket(transport, url).toPromise()) |
|
||||
} |
|
@ -1,9 +0,0 @@ |
|||||
// @flow
|
|
||||
|
|
||||
import type Transport from '@ledgerhq/hw-transport' |
|
||||
|
|
||||
export default async (transport: Transport<*>) => { |
|
||||
const r = await transport.send(0xe0, 0xc4, 0, 0) |
|
||||
const version = `${r[2]}.${r[3]}.${r[4]}` |
|
||||
return { version } |
|
||||
} |
|
@ -1,10 +0,0 @@ |
|||||
// @flow
|
|
||||
|
|
||||
import Eth from '@ledgerhq/hw-app-eth' |
|
||||
import type Transport from '@ledgerhq/hw-transport' |
|
||||
|
|
||||
export default async (transport: Transport<*>) => { |
|
||||
const eth = new Eth(transport) |
|
||||
const { version } = await eth.getAppConfiguration() |
|
||||
return { version } |
|
||||
} |
|
@ -1,29 +0,0 @@ |
|||||
// @flow
|
|
||||
|
|
||||
import type { CryptoCurrency } from '@ledgerhq/live-common/lib/types' |
|
||||
import invariant from 'invariant' |
|
||||
import type Transport from '@ledgerhq/hw-transport' |
|
||||
import bitcoin from './btc' |
|
||||
import ethereum from './ethereum' |
|
||||
import ripple from './ripple' |
|
||||
|
|
||||
type Resolver = ( |
|
||||
transport: Transport<*>, |
|
||||
currency: CryptoCurrency, |
|
||||
) => Promise<{ |
|
||||
version?: string, |
|
||||
}> |
|
||||
|
|
||||
const perFamily: { [_: string]: * } = { |
|
||||
bitcoin, |
|
||||
ethereum, |
|
||||
ripple, |
|
||||
} |
|
||||
|
|
||||
const proxy: Resolver = (transport, currency) => { |
|
||||
const getAddress = perFamily[currency.family] |
|
||||
invariant(getAddress, `getAddress not implemented for ${currency.id}`) |
|
||||
return getAddress(transport) |
|
||||
} |
|
||||
|
|
||||
export default proxy |
|
@ -1,10 +0,0 @@ |
|||||
// @flow
|
|
||||
|
|
||||
import Xrp from '@ledgerhq/hw-app-xrp' |
|
||||
import type Transport from '@ledgerhq/hw-transport' |
|
||||
|
|
||||
export default async (transport: Transport<*>) => { |
|
||||
const xrp = new Xrp(transport) |
|
||||
const { version } = await xrp.getAppConfiguration() |
|
||||
return { version } |
|
||||
} |
|
@ -1,64 +0,0 @@ |
|||||
// @flow
|
|
||||
import logger from 'logger' |
|
||||
import throttle from 'lodash/throttle' |
|
||||
import type Transport from '@ledgerhq/hw-transport' |
|
||||
import TransportNodeHid from '@ledgerhq/hw-transport-node-hid' |
|
||||
import { DisconnectedDevice, CantOpenDevice } from 'config/errors' |
|
||||
import { retry } from './promise' |
|
||||
|
|
||||
// all open to device must use openDevice so we can prevent race conditions
|
|
||||
// and guarantee we do one device access at a time. It also will handle the .close()
|
|
||||
// NOTE optim: in the future we can debounce the close & reuse the same transport instance.
|
|
||||
|
|
||||
type WithDevice = (devicePath: string) => <T>(job: (Transport<*>) => Promise<*>) => Promise<T> |
|
||||
|
|
||||
const mapError = e => { |
|
||||
if (e && e.message && e.message.indexOf('cannot open device with path') >= 0) { |
|
||||
throw new CantOpenDevice(e.message) |
|
||||
} |
|
||||
if (e && e.message && e.message.indexOf('HID') >= 0) { |
|
||||
throw new DisconnectedDevice(e.message) |
|
||||
} |
|
||||
throw e |
|
||||
} |
|
||||
|
|
||||
let queue = Promise.resolve() |
|
||||
|
|
||||
let busy = false |
|
||||
|
|
||||
TransportNodeHid.setListenDevicesPollingSkip(() => busy) |
|
||||
|
|
||||
const refreshBusyUIState = throttle(() => { |
|
||||
if (process.env.CLI) return |
|
||||
process.send({ |
|
||||
type: 'setDeviceBusy', |
|
||||
busy, |
|
||||
}) |
|
||||
}, 100) |
|
||||
|
|
||||
export const withDevice: WithDevice = devicePath => job => { |
|
||||
const p = queue.then(async () => { |
|
||||
busy = true |
|
||||
refreshBusyUIState() |
|
||||
try { |
|
||||
// $FlowFixMe not sure what's wrong
|
|
||||
const t = await retry(() => TransportNodeHid.open(devicePath), { maxRetry: 2 }).catch( |
|
||||
mapError, |
|
||||
) |
|
||||
t.setDebugMode(logger.apdu) |
|
||||
try { |
|
||||
const res = await job(t).catch(mapError) |
|
||||
return res |
|
||||
} finally { |
|
||||
await t.close() |
|
||||
} |
|
||||
} finally { |
|
||||
busy = false |
|
||||
refreshBusyUIState() |
|
||||
} |
|
||||
}) |
|
||||
|
|
||||
queue = p.catch(() => null) |
|
||||
|
|
||||
return p |
|
||||
} |
|
@ -1,21 +0,0 @@ |
|||||
// @flow
|
|
||||
|
|
||||
import type Transport from '@ledgerhq/hw-transport' |
|
||||
|
|
||||
const getBitcoinLikeInfo = ( |
|
||||
transport: Transport<any>, |
|
||||
): Promise<{ |
|
||||
P2PKH: number, |
|
||||
P2SH: number, |
|
||||
message: Buffer, |
|
||||
short: Buffer, |
|
||||
}> => |
|
||||
transport.send(0xe0, 0x16, 0x00, 0x00).then(res => { |
|
||||
const P2PKH = res.readUInt16BE(0) |
|
||||
const P2SH = res.readUInt16BE(2) |
|
||||
const message = res.slice(5, res.readUInt8(4)) |
|
||||
const short = res.slice(5 + message.length + 1, res.readUInt8(5 + message.length)) |
|
||||
return { P2PKH, P2SH, message, short } |
|
||||
}) |
|
||||
|
|
||||
export default getBitcoinLikeInfo |
|
@ -1,24 +0,0 @@ |
|||||
// @flow
|
|
||||
import network from 'api/network' |
|
||||
|
|
||||
import { GET_CURRENT_FIRMWARE } from 'helpers/urls' |
|
||||
import type { FinalFirmware } from 'helpers/types' |
|
||||
|
|
||||
type Input = { |
|
||||
fullVersion: string, |
|
||||
deviceId: string | number, |
|
||||
provider: number, |
|
||||
} |
|
||||
|
|
||||
export default async (input: Input): Promise<FinalFirmware> => { |
|
||||
const { data }: { data: FinalFirmware } = await network({ |
|
||||
method: 'POST', |
|
||||
url: GET_CURRENT_FIRMWARE, |
|
||||
data: { |
|
||||
device_version: input.deviceId, |
|
||||
version_name: input.fullVersion, |
|
||||
provider: input.provider, |
|
||||
}, |
|
||||
}) |
|
||||
return data |
|
||||
} |
|
@ -1,42 +0,0 @@ |
|||||
// @flow
|
|
||||
|
|
||||
import type Transport from '@ledgerhq/hw-transport' |
|
||||
|
|
||||
import getFirmwareInfo from 'helpers/firmware/getFirmwareInfo' |
|
||||
import { FORCE_PROVIDER } from 'config/constants' |
|
||||
|
|
||||
import type { DeviceInfo } from 'helpers/types' |
|
||||
|
|
||||
const PROVIDERS = { |
|
||||
'': 1, |
|
||||
das: 2, |
|
||||
club: 3, |
|
||||
shitcoins: 4, |
|
||||
ee: 5, |
|
||||
} |
|
||||
|
|
||||
export default async (transport: Transport<*>): Promise<DeviceInfo> => { |
|
||||
const res = await getFirmwareInfo(transport) |
|
||||
const { seVersion } = res |
|
||||
const { targetId, mcuVersion, flags } = res |
|
||||
const parsedVersion = |
|
||||
seVersion.match(/([0-9]+.[0-9])+(.[0-9]+)?((?!-osu)-([a-z]+))?(-osu)?/) || [] |
|
||||
const isOSU = typeof parsedVersion[5] !== 'undefined' |
|
||||
const providerName = parsedVersion[4] || '' |
|
||||
const providerId = FORCE_PROVIDER || PROVIDERS[providerName] |
|
||||
const isBootloader = targetId === 0x01000001 |
|
||||
const majMin = parsedVersion[1] |
|
||||
const patch = parsedVersion[2] || '.0' |
|
||||
const fullVersion = `${majMin}${patch}${providerName ? `-${providerName}` : ''}` |
|
||||
return { |
|
||||
targetId, |
|
||||
seVersion: majMin + patch, |
|
||||
isOSU, |
|
||||
mcuVersion, |
|
||||
isBootloader, |
|
||||
providerName, |
|
||||
providerId, |
|
||||
flags, |
|
||||
fullVersion, |
|
||||
} |
|
||||
} |
|
@ -1,17 +0,0 @@ |
|||||
// @flow
|
|
||||
import { GET_DEVICE_VERSION } from 'helpers/urls' |
|
||||
import network from 'api/network' |
|
||||
|
|
||||
import type { DeviceVersion } from 'helpers/types' |
|
||||
|
|
||||
export default async (targetId: string | number, provider: number): Promise<DeviceVersion> => { |
|
||||
const { data }: { data: DeviceVersion } = await network({ |
|
||||
method: 'POST', |
|
||||
url: GET_DEVICE_VERSION, |
|
||||
data: { |
|
||||
provider, |
|
||||
target_id: targetId, |
|
||||
}, |
|
||||
}) |
|
||||
return data |
|
||||
} |
|
@ -1,32 +0,0 @@ |
|||||
// @flow
|
|
||||
import type Transport from '@ledgerhq/hw-transport' |
|
||||
import { SKIP_GENUINE } from 'config/constants' |
|
||||
import { WS_GENUINE } from 'helpers/urls' |
|
||||
import type { DeviceInfo, FinalFirmware, DeviceVersion } from 'helpers/types' |
|
||||
|
|
||||
import { createDeviceSocket } from 'helpers/socket' |
|
||||
import getCurrentFirmware from './getCurrentFirmware' |
|
||||
import getDeviceVersion from './getDeviceVersion' |
|
||||
|
|
||||
export default async (transport: Transport<*>, deviceInfo: DeviceInfo): Promise<string> => { |
|
||||
const deviceVersion: DeviceVersion = await getDeviceVersion( |
|
||||
deviceInfo.targetId, |
|
||||
deviceInfo.providerId, |
|
||||
) |
|
||||
|
|
||||
const firmware: FinalFirmware = await getCurrentFirmware({ |
|
||||
deviceId: deviceVersion.id, |
|
||||
fullVersion: deviceInfo.fullVersion, |
|
||||
provider: deviceInfo.providerId, |
|
||||
}) |
|
||||
|
|
||||
const params = { |
|
||||
targetId: deviceInfo.targetId, |
|
||||
perso: firmware.perso, |
|
||||
} |
|
||||
|
|
||||
const url = WS_GENUINE(params) |
|
||||
return SKIP_GENUINE |
|
||||
? new Promise(resolve => setTimeout(() => resolve('0000'), 1000)) |
|
||||
: createDeviceSocket(transport, url).toPromise() |
|
||||
} |
|
@ -1,76 +0,0 @@ |
|||||
// @flow
|
|
||||
import network from 'api/network' |
|
||||
import { GET_LATEST_FIRMWARE } from 'helpers/urls' |
|
||||
import type { |
|
||||
DeviceInfo, |
|
||||
DeviceVersion, |
|
||||
FinalFirmware, |
|
||||
OsuFirmware, |
|
||||
McuVersion, |
|
||||
} from 'helpers/types' |
|
||||
|
|
||||
import getFinalFirmwareById from 'helpers/firmware/getFinalFirmwareById' |
|
||||
import getMcus from 'helpers/firmware/getMcus' |
|
||||
|
|
||||
import getCurrentFirmware from './getCurrentFirmware' |
|
||||
import getDeviceVersion from './getDeviceVersion' |
|
||||
|
|
||||
type NetworkResponse = { |
|
||||
data: { |
|
||||
result: string, |
|
||||
se_firmware_osu_version: OsuFirmware, |
|
||||
}, |
|
||||
} |
|
||||
|
|
||||
type Result = ?(OsuFirmware & { shouldFlashMcu: boolean }) |
|
||||
|
|
||||
export default async (deviceInfo: DeviceInfo): Promise<Result> => { |
|
||||
// Get device infos from targetId
|
|
||||
const deviceVersion: DeviceVersion = await getDeviceVersion( |
|
||||
deviceInfo.targetId, |
|
||||
deviceInfo.providerId, |
|
||||
) |
|
||||
|
|
||||
// Get firmware infos with firmware name and device version
|
|
||||
const seFirmwareVersion: FinalFirmware = await getCurrentFirmware({ |
|
||||
fullVersion: deviceInfo.fullVersion, |
|
||||
deviceId: deviceVersion.id, |
|
||||
provider: deviceInfo.providerId, |
|
||||
}) |
|
||||
|
|
||||
// Fetch next possible firmware
|
|
||||
const { data }: NetworkResponse = await network({ |
|
||||
method: 'POST', |
|
||||
url: GET_LATEST_FIRMWARE, |
|
||||
data: { |
|
||||
current_se_firmware_final_version: seFirmwareVersion.id, |
|
||||
device_version: deviceVersion.id, |
|
||||
provider: deviceInfo.providerId, |
|
||||
}, |
|
||||
}) |
|
||||
|
|
||||
if (data.result === 'null') { |
|
||||
return null |
|
||||
} |
|
||||
|
|
||||
const { se_firmware_osu_version } = data |
|
||||
const { next_se_firmware_final_version } = se_firmware_osu_version |
|
||||
const seFirmwareFinalVersion: FinalFirmware = await getFinalFirmwareById( |
|
||||
next_se_firmware_final_version, |
|
||||
) |
|
||||
|
|
||||
const mcus: Array<McuVersion> = await getMcus() |
|
||||
|
|
||||
const currentMcuVersionId: Array<number> = mcus |
|
||||
.filter(mcu => mcu.name === deviceInfo.mcuVersion) |
|
||||
.map(mcu => mcu.id) |
|
||||
|
|
||||
if (!seFirmwareFinalVersion.mcu_versions.includes(...currentMcuVersionId)) { |
|
||||
return { |
|
||||
...se_firmware_osu_version, |
|
||||
shouldFlashMcu: true, |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return { ...se_firmware_osu_version, shouldFlashMcu: false } |
|
||||
} |
|
@ -1,10 +0,0 @@ |
|||||
// @flow
|
|
||||
|
|
||||
import type Transport from '@ledgerhq/hw-transport' |
|
||||
|
|
||||
import getFirmwareInfo from 'helpers/firmware/getFirmwareInfo' |
|
||||
|
|
||||
export default async function getMemInfos(transport: Transport<*>): Promise<Object> { |
|
||||
const { targetId } = await getFirmwareInfo(transport) // eslint-disable-line
|
|
||||
return new Promise(resolve => setTimeout(() => resolve({}), 1000)) |
|
||||
} |
|
@ -1,23 +0,0 @@ |
|||||
// @flow
|
|
||||
import network from 'api/network' |
|
||||
|
|
||||
import { GET_CURRENT_OSU } from 'helpers/urls' |
|
||||
|
|
||||
type Input = { |
|
||||
version: string, |
|
||||
deviceId: string | number, |
|
||||
provider: number, |
|
||||
} |
|
||||
|
|
||||
export default async (input: Input): Promise<*> => { |
|
||||
const { data } = await network({ |
|
||||
method: 'POST', |
|
||||
url: GET_CURRENT_OSU, |
|
||||
data: { |
|
||||
device_version: input.deviceId, |
|
||||
version_name: `${input.version}-osu`, |
|
||||
provider: input.provider, |
|
||||
}, |
|
||||
}) |
|
||||
return data |
|
||||
} |
|
@ -1,16 +0,0 @@ |
|||||
// @flow
|
|
||||
|
|
||||
import type Transport from '@ledgerhq/hw-transport' |
|
||||
|
|
||||
import getFirmwareInfo from 'helpers/firmware/getFirmwareInfo' |
|
||||
|
|
||||
type Result = boolean |
|
||||
|
|
||||
export default async (transport: Transport<*>): Promise<Result> => { |
|
||||
const { targetId, seVersion } = await getFirmwareInfo(transport) |
|
||||
if (targetId && seVersion) { |
|
||||
return true |
|
||||
} |
|
||||
|
|
||||
return false |
|
||||
} |
|
@ -1,49 +0,0 @@ |
|||||
// @flow
|
|
||||
import network from 'api/network' |
|
||||
import { GET_LATEST_FIRMWARE } from 'helpers/urls' |
|
||||
import type { DeviceInfo } from 'helpers/types' |
|
||||
|
|
||||
import getFinalFirmwareById from 'helpers/firmware/getFinalFirmwareById' |
|
||||
import getMcus from 'helpers/firmware/getMcus' |
|
||||
|
|
||||
import getOsuFirmware from './getOsuFirmware' |
|
||||
import getDeviceVersion from './getDeviceVersion' |
|
||||
|
|
||||
export default async (deviceInfo: DeviceInfo): Promise<boolean> => { |
|
||||
// Get device infos from targetId
|
|
||||
const deviceVersion = await getDeviceVersion(deviceInfo.targetId, deviceInfo.providerId) |
|
||||
|
|
||||
// Get firmware infos with firmware name and device version
|
|
||||
const seFirmwareVersion = await getOsuFirmware({ |
|
||||
version: deviceInfo.fullVersion, |
|
||||
deviceId: deviceVersion.id, |
|
||||
provider: deviceInfo.providerId, |
|
||||
}) |
|
||||
|
|
||||
// Fetch next possible firmware
|
|
||||
const { data } = await network({ |
|
||||
method: 'POST', |
|
||||
url: GET_LATEST_FIRMWARE, |
|
||||
data: { |
|
||||
current_se_firmware_final_version: seFirmwareVersion.id, |
|
||||
device_version: deviceVersion.id, |
|
||||
provider: deviceInfo.providerId, |
|
||||
}, |
|
||||
}) |
|
||||
|
|
||||
if (data.result === 'null') { |
|
||||
return false |
|
||||
} |
|
||||
|
|
||||
const { se_firmware_osu_version } = data |
|
||||
const { next_se_firmware_final_version } = se_firmware_osu_version |
|
||||
const seFirmwareFinalVersion = await getFinalFirmwareById(next_se_firmware_final_version) |
|
||||
|
|
||||
const mcus = await getMcus() |
|
||||
|
|
||||
const currentMcuVersionId = mcus |
|
||||
.filter(mcu => mcu.name === deviceInfo.mcuVersion) |
|
||||
.map(mcu => mcu.id) |
|
||||
|
|
||||
return !seFirmwareFinalVersion.mcu_versions.includes(...currentMcuVersionId) |
|
||||
} |
|
@ -1,96 +0,0 @@ |
|||||
// @flow
|
|
||||
/* eslint-disable no-continue */ |
|
||||
|
|
||||
// TODO we need to centralize the error in one place. so all are recorded
|
|
||||
const errorClasses = {} |
|
||||
|
|
||||
export const createCustomErrorClass = (name: string): Class<any> => { |
|
||||
const C = function CustomError(message?: string, fields?: Object) { |
|
||||
Object.assign(this, fields) |
|
||||
this.name = name |
|
||||
this.message = message || name |
|
||||
this.stack = new Error().stack |
|
||||
} |
|
||||
// $FlowFixMe
|
|
||||
C.prototype = new Error() |
|
||||
|
|
||||
errorClasses[name] = C |
|
||||
// $FlowFixMe we can't easily type a subset of Error for now...
|
|
||||
return C |
|
||||
} |
|
||||
|
|
||||
// inspired from https://github.com/programble/errio/blob/master/index.js
|
|
||||
export const deserializeError = (object: mixed): Error => { |
|
||||
if (typeof object === 'object' && object) { |
|
||||
try { |
|
||||
// $FlowFixMe FIXME HACK
|
|
||||
const msg = JSON.parse(object.message) |
|
||||
if (msg.message && msg.name) { |
|
||||
object = msg |
|
||||
} |
|
||||
} catch (e) { |
|
||||
// nothing
|
|
||||
} |
|
||||
const constructor = |
|
||||
object.name === 'Error' |
|
||||
? Error |
|
||||
: typeof object.name === 'string' |
|
||||
? errorClasses[object.name] || createCustomErrorClass(object.name) |
|
||||
: Error |
|
||||
|
|
||||
const error = Object.create(constructor.prototype) |
|
||||
for (const prop in object) { |
|
||||
if (object.hasOwnProperty(prop)) { |
|
||||
error[prop] = object[prop] |
|
||||
} |
|
||||
} |
|
||||
if (!error.stack && Error.captureStackTrace) { |
|
||||
Error.captureStackTrace(error, deserializeError) |
|
||||
} |
|
||||
return error |
|
||||
} |
|
||||
return new Error(String(object)) |
|
||||
} |
|
||||
|
|
||||
// inspired from https://github.com/sindresorhus/serialize-error/blob/master/index.js
|
|
||||
export const serializeError = (value: mixed) => { |
|
||||
if (!value) return value |
|
||||
if (typeof value === 'object') { |
|
||||
return destroyCircular(value, []) |
|
||||
} |
|
||||
if (typeof value === 'function') { |
|
||||
return `[Function: ${value.name || 'anonymous'}]` |
|
||||
} |
|
||||
return value |
|
||||
} |
|
||||
|
|
||||
// https://www.npmjs.com/package/destroy-circular
|
|
||||
function destroyCircular(from: Object, seen) { |
|
||||
const to = {} |
|
||||
seen.push(from) |
|
||||
for (const key of Object.keys(from)) { |
|
||||
const value = from[key] |
|
||||
if (typeof value === 'function') { |
|
||||
continue |
|
||||
} |
|
||||
if (!value || typeof value !== 'object') { |
|
||||
to[key] = value |
|
||||
continue |
|
||||
} |
|
||||
if (seen.indexOf(from[key]) === -1) { |
|
||||
to[key] = destroyCircular(from[key], seen.slice(0)) |
|
||||
continue |
|
||||
} |
|
||||
to[key] = '[Circular]' |
|
||||
} |
|
||||
if (typeof from.name === 'string') { |
|
||||
to.name = from.name |
|
||||
} |
|
||||
if (typeof from.message === 'string') { |
|
||||
to.message = from.message |
|
||||
} |
|
||||
if (typeof from.stack === 'string') { |
|
||||
to.stack = from.stack |
|
||||
} |
|
||||
return to |
|
||||
} |
|
@ -1,8 +0,0 @@ |
|||||
// @flow
|
|
||||
import network from 'api/network' |
|
||||
import { GET_FINAL_FIRMWARE } from 'helpers/urls' |
|
||||
|
|
||||
export default async (id: number) => { |
|
||||
const { data } = await network({ method: 'GET', url: `${GET_FINAL_FIRMWARE}/${id}` }) |
|
||||
return data |
|
||||
} |
|
@ -1,51 +0,0 @@ |
|||||
// @flow
|
|
||||
|
|
||||
import type Transport from '@ledgerhq/hw-transport' |
|
||||
import type { FirmwareInfo } from 'helpers/types' |
|
||||
|
|
||||
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], |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Retrieve targetId and firmware version from device |
|
||||
*/ |
|
||||
export default async function getFirmwareInfo(transport: Transport<*>): Promise<FirmwareInfo> { |
|
||||
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 seVersionLength = data[4] |
|
||||
const seVersion = Buffer.from(data.slice(5, 5 + seVersionLength)).toString() |
|
||||
const flagsLength = data[5 + seVersionLength] |
|
||||
const flags = Buffer.from( |
|
||||
data.slice(5 + seVersionLength + 1, 5 + seVersionLength + 1 + flagsLength), |
|
||||
).toString() |
|
||||
|
|
||||
const mcuVersionLength = data[5 + seVersionLength + 1 + flagsLength] |
|
||||
let mcuVersion = Buffer.from( |
|
||||
data.slice( |
|
||||
7 + seVersionLength + flagsLength, |
|
||||
7 + seVersionLength + flagsLength + mcuVersionLength, |
|
||||
), |
|
||||
) |
|
||||
if (mcuVersion[mcuVersion.length - 1] === 0) { |
|
||||
mcuVersion = mcuVersion.slice(0, mcuVersion.length - 1) |
|
||||
} |
|
||||
mcuVersion = mcuVersion.toString() |
|
||||
|
|
||||
if (!seVersionLength) { |
|
||||
return { |
|
||||
targetId, |
|
||||
seVersion: '0.0.0', |
|
||||
flags: '', |
|
||||
mcuVersion: '', |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return { targetId, seVersion, flags, mcuVersion } |
|
||||
} |
|
@ -1,13 +0,0 @@ |
|||||
// @flow
|
|
||||
import network from 'api/network' |
|
||||
|
|
||||
import { GET_MCUS } from 'helpers/urls' |
|
||||
|
|
||||
export default async (): Promise<*> => { |
|
||||
const { data } = await network({ |
|
||||
method: 'GET', |
|
||||
url: GET_MCUS, |
|
||||
}) |
|
||||
|
|
||||
return data |
|
||||
} |
|
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue