Gaëtan Renaudeau
6 years ago
committed by
GitHub
120 changed files with 1145 additions and 2114 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
|
|||
|
|||
import { createCommand, Command } from 'helpers/ipc' |
|||
import { fromPromise } from 'rxjs/observable/fromPromise' |
|||
import type { DeviceInfo, OsuFirmware } from 'helpers/types' |
|||
import { from } from 'rxjs' |
|||
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', data => |
|||
fromPromise(getLatestFirmwareForDevice(data)), |
|||
const cmd: Command<DeviceInfo, Result> = createCommand('getLatestFirmwareForDevice', deviceInfo => |
|||
from(manager.getLatestFirmwareForDevice(deviceInfo)), |
|||
) |
|||
|
|||
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
|
|||
|
|||
import { createCommand, Command } from 'helpers/ipc' |
|||
import { fromPromise } from 'rxjs/observable/fromPromise' |
|||
|
|||
import { withDevice } from 'helpers/deviceAccess' |
|||
import installApp from 'helpers/apps/installApp' |
|||
|
|||
import type { ApplicationVersion } from 'helpers/types' |
|||
import installApp from '@ledgerhq/live-common/lib/hw/installApp' |
|||
import { withDevice } from '@ledgerhq/live-common/lib/hw/deviceAccess' |
|||
import type { ApplicationVersion } from '@ledgerhq/live-common/lib/types/manager' |
|||
|
|||
type Input = { |
|||
app: ApplicationVersion, |
|||
devicePath: string, |
|||
targetId: string | number, |
|||
app: ApplicationVersion, |
|||
} |
|||
|
|||
type Result = void |
|||
type Result = { progress: number } |
|||
|
|||
const cmd: Command<Input, Result> = createCommand( |
|||
'installApp', |
|||
({ devicePath, targetId, ...app }) => |
|||
fromPromise(withDevice(devicePath)(transport => installApp(transport, targetId, app))), |
|||
const cmd: Command<Input, Result> = createCommand('installApp', ({ devicePath, targetId, app }) => |
|||
withDevice(devicePath)(transport => installApp(transport, targetId, app)), |
|||
) |
|||
|
|||
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,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 |
|||
} |
@ -1,25 +0,0 @@ |
|||
// @flow
|
|||
import network from 'api/network' |
|||
|
|||
import { GET_NEXT_MCU } from 'helpers/urls' |
|||
import type { OsuFirmware } from 'helpers/types' |
|||
import { LatestMCUInstalledError } from 'config/errors' |
|||
|
|||
type NetworkResponse = { data: OsuFirmware | 'default' } |
|||
|
|||
export default async (bootloaderVersion: string): Promise<*> => { |
|||
const { data }: NetworkResponse = await network({ |
|||
method: 'POST', |
|||
url: GET_NEXT_MCU, |
|||
data: { |
|||
bootloader_version: bootloaderVersion, |
|||
}, |
|||
}) |
|||
|
|||
// FIXME: nextVersion will not be able to "default" when
|
|||
// Error handling is standardize on the API side
|
|||
if (data === 'default' || !data.name) { |
|||
throw new LatestMCUInstalledError('there is no next mcu version to install') |
|||
} |
|||
return data |
|||
} |
@ -1,51 +0,0 @@ |
|||
// @flow
|
|||
import type Transport from '@ledgerhq/hw-transport' |
|||
import type { DeviceInfo, DeviceVersion, OsuFirmware, FinalFirmware } from 'helpers/types' |
|||
|
|||
import { WS_INSTALL } from 'helpers/urls' |
|||
import { createDeviceSocket } from 'helpers/socket' |
|||
import getDeviceVersion from 'helpers/devices/getDeviceVersion' |
|||
import getOsuFirmware from 'helpers/devices/getOsuFirmware' |
|||
import getDeviceInfo from 'helpers/devices/getDeviceInfo' |
|||
import { ManagerDeviceLockedError } from 'config/errors' |
|||
|
|||
import getFinalFirmwareById from './getFinalFirmwareById' |
|||
|
|||
function remapSocketError(promise) { |
|||
return promise.catch((e: Error) => { |
|||
switch (true) { |
|||
case e.message.endsWith('6982'): |
|||
throw new ManagerDeviceLockedError() |
|||
default: |
|||
throw e |
|||
} |
|||
}) |
|||
} |
|||
|
|||
type Result = Promise<{ success: boolean }> |
|||
|
|||
export default async (transport: Transport<*>): Result => { |
|||
try { |
|||
const deviceInfo: DeviceInfo = await getDeviceInfo(transport) |
|||
const device: DeviceVersion = await getDeviceVersion(deviceInfo.targetId, deviceInfo.providerId) |
|||
const firmware: OsuFirmware = await getOsuFirmware({ |
|||
deviceId: device.id, |
|||
version: deviceInfo.fullVersion, |
|||
provider: deviceInfo.providerId, |
|||
}) |
|||
const { next_se_firmware_final_version } = firmware |
|||
const nextFirmware: FinalFirmware = await getFinalFirmwareById(next_se_firmware_final_version) |
|||
|
|||
const params = { |
|||
targetId: deviceInfo.targetId, |
|||
...nextFirmware, |
|||
firmwareKey: nextFirmware.firmware_key, |
|||
} |
|||
|
|||
const url = WS_INSTALL(params) |
|||
await remapSocketError(createDeviceSocket(transport, url).toPromise()) |
|||
return { success: true } |
|||
} catch (error) { |
|||
throw error |
|||
} |
|||
} |
@ -1,34 +0,0 @@ |
|||
// @flow
|
|||
import type Transport from '@ledgerhq/hw-transport' |
|||
|
|||
import { WS_MCU } from 'helpers/urls' |
|||
import { createDeviceSocket } from 'helpers/socket' |
|||
import getNextMCU from 'helpers/firmware/getNextMCU' |
|||
import getDeviceInfo from 'helpers/devices/getDeviceInfo' |
|||
import { ManagerDeviceLockedError } from 'config/errors' |
|||
|
|||
import type { DeviceInfo } from 'helpers/types' |
|||
|
|||
function remapSocketError(promise) { |
|||
return promise.catch((e: Error) => { |
|||
switch (true) { |
|||
case e.message.endsWith('6982'): |
|||
throw new ManagerDeviceLockedError() |
|||
default: |
|||
throw e |
|||
} |
|||
}) |
|||
} |
|||
|
|||
type Result = Promise<void> |
|||
|
|||
export default async (transport: Transport<*>): Result => { |
|||
const { seVersion: version, targetId }: DeviceInfo = await getDeviceInfo(transport) |
|||
const nextVersion = await getNextMCU(version) |
|||
const params = { |
|||
targetId, |
|||
version: nextVersion.name, |
|||
} |
|||
const url = WS_MCU(params) |
|||
await remapSocketError(createDeviceSocket(transport, url).toPromise()) |
|||
} |
@ -1,50 +0,0 @@ |
|||
// @flow
|
|||
import type Transport from '@ledgerhq/hw-transport' |
|||
|
|||
import { WS_INSTALL } from 'helpers/urls' |
|||
import { createDeviceSocket } from 'helpers/socket' |
|||
|
|||
import type { Firmware } from 'components/modals/UpdateFirmware' |
|||
|
|||
import { |
|||
ManagerNotEnoughSpaceError, |
|||
ManagerDeviceLockedError, |
|||
UserRefusedFirmwareUpdate, |
|||
} from 'config/errors' |
|||
|
|||
function remapError(promise) { |
|||
return promise.catch((e: Error) => { |
|||
switch (true) { |
|||
case e.message.endsWith('6985'): |
|||
throw new UserRefusedFirmwareUpdate() |
|||
case e.message.endsWith('6982'): |
|||
throw new ManagerDeviceLockedError() |
|||
case e.message.endsWith('6a84') || e.message.endsWith('6a85'): |
|||
throw new ManagerNotEnoughSpaceError() |
|||
default: |
|||
throw e |
|||
} |
|||
}) |
|||
} |
|||
|
|||
type Result = Promise<{ success: boolean }> |
|||
|
|||
export default async ( |
|||
transport: Transport<*>, |
|||
targetId: string | number, |
|||
firmware: Firmware, |
|||
): Result => { |
|||
try { |
|||
const params = { |
|||
targetId, |
|||
...firmware, |
|||
firmwareKey: firmware.firmware_key, |
|||
} |
|||
delete params.shouldFlashMcu |
|||
const url = WS_INSTALL(params) |
|||
await remapError(createDeviceSocket(transport, url).toPromise()) |
|||
return { success: true } |
|||
} catch (error) { |
|||
throw error |
|||
} |
|||
} |
@ -1,44 +0,0 @@ |
|||
// @flow
|
|||
|
|||
import type { CryptoCurrency } from '@ledgerhq/live-common/lib/types' |
|||
import Btc from '@ledgerhq/hw-app-btc' |
|||
import type Transport from '@ledgerhq/hw-transport' |
|||
import { BtcUnmatchedApp, UpdateYourApp } from 'config/errors' |
|||
import getBitcoinLikeInfo from '../devices/getBitcoinLikeInfo' |
|||
|
|||
const oldP2SH = { |
|||
digibyte: 5, |
|||
} |
|||
|
|||
export default async ( |
|||
transport: Transport<*>, |
|||
currency: CryptoCurrency, |
|||
path: string, |
|||
{ |
|||
segwit = true, |
|||
verify = false, |
|||
}: { |
|||
segwit?: boolean, |
|||
verify?: boolean, |
|||
}, |
|||
) => { |
|||
const btc = new Btc(transport) |
|||
const { bitcoinAddress, publicKey } = await btc.getWalletPublicKey(path, verify, segwit) |
|||
|
|||
const { bitcoinLikeInfo } = currency |
|||
if (bitcoinLikeInfo) { |
|||
const { P2SH, P2PKH } = await getBitcoinLikeInfo(transport) |
|||
if (P2SH !== bitcoinLikeInfo.P2SH || P2PKH !== bitcoinLikeInfo.P2PKH) { |
|||
if ( |
|||
currency.id in oldP2SH && |
|||
P2SH === oldP2SH[currency.id] && |
|||
P2PKH === bitcoinLikeInfo.P2PKH |
|||
) { |
|||
throw new UpdateYourApp(`UpdateYourApp ${currency.id}`, currency) |
|||
} |
|||
throw new BtcUnmatchedApp(`BtcUnmatchedApp ${currency.id}`, currency) |
|||
} |
|||
} |
|||
|
|||
return { address: bitcoinAddress, path, publicKey } |
|||
} |
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue