{
- state = {
- flashing: false,
- }
-
- flashMCU = async () => {
- const { device, deviceInfo } = this.props
- const { flashing } = this.state
-
- if (!flashing) {
- this.setState({ flashing: true })
- await installMcu
- .send({
- devicePath: device.path,
- targetId: deviceInfo.targetId,
- version: deviceInfo.seVersion,
- })
- .toPromise()
- this.setState({ flashing: false })
- }
- }
-
- render() {
- return (
-
-
{'Flashing MCU'}
-
-
- )
- }
-}
-
-export default FlashMcu
diff --git a/src/components/ManagerPage/UpdateFirmwareButton.js b/src/components/ManagerPage/UpdateFirmwareButton.js
index cdfead05..e3a58aac 100644
--- a/src/components/ManagerPage/UpdateFirmwareButton.js
+++ b/src/components/ManagerPage/UpdateFirmwareButton.js
@@ -18,16 +18,16 @@ type FirmwareInfos = {
type Props = {
t: T,
firmware: ?FirmwareInfos,
- installFirmware: () => void,
+ onClick: () => void,
}
-const UpdateFirmwareButton = ({ t, firmware, installFirmware }: Props) =>
+const UpdateFirmwareButton = ({ t, firmware, onClick }: Props) =>
firmware ? (
{t('app:manager.firmware.latest', { version: getCleanVersion(firmware.name) })}
-
diff --git a/src/components/ManagerPage/index.js b/src/components/ManagerPage/index.js
index e0b3f76e..9eba78a4 100644
--- a/src/components/ManagerPage/index.js
+++ b/src/components/ManagerPage/index.js
@@ -40,10 +40,6 @@ class ManagerPage extends PureComponent {
invariant(device, 'Inexistant device considered genuine')
invariant(deviceInfo, 'Inexistant device infos for genuine device')
- // TODO
- // renderFinalUpdate
- // renderMcuUpdate
-
return
}
}
diff --git a/src/components/modals/ReleaseNotes.js b/src/components/modals/ReleaseNotes.js
index 2d4950e3..d80999d8 100644
--- a/src/components/modals/ReleaseNotes.js
+++ b/src/components/modals/ReleaseNotes.js
@@ -25,7 +25,7 @@ type State = {
markdown: ?string,
}
-const Notes = styled(Box).attrs({
+export const Notes = styled(Box).attrs({
ff: 'Open Sans',
fontSize: 4,
color: 'smoke',
diff --git a/src/components/modals/UpdateFirmware/Disclaimer.js b/src/components/modals/UpdateFirmware/Disclaimer.js
index 379c8928..bb462158 100644
--- a/src/components/modals/UpdateFirmware/Disclaimer.js
+++ b/src/components/modals/UpdateFirmware/Disclaimer.js
@@ -3,6 +3,7 @@
import React, { PureComponent, Fragment } from 'react'
import { translate, Trans } from 'react-i18next'
+import ReactMarkdown from 'react-markdown'
import type { T } from 'types/common'
@@ -11,11 +12,12 @@ import Text from 'components/base/Text'
import Button from 'components/base/Button'
import GrowScroll from 'components/base/GrowScroll'
import GradientBox from 'components/GradientBox'
-
-import { getCleanVersion } from 'components/ManagerPage/FirmwareUpdate'
+import { Notes } from 'components/modals/ReleaseNotes'
import type { ModalStatus } from 'components/ManagerPage/FirmwareUpdate'
+import { getCleanVersion } from 'components/ManagerPage/FirmwareUpdate'
+
type FirmwareInfos = {
name: string,
notes: string,
@@ -58,9 +60,9 @@ class DisclaimerModal extends PureComponent {
-
- {firmware.notes}
-
+
+ {firmware.notes}
+
diff --git a/src/components/modals/UpdateFirmware/index.js b/src/components/modals/UpdateFirmware/index.js
index abb71c8b..a86b8bef 100644
--- a/src/components/modals/UpdateFirmware/index.js
+++ b/src/components/modals/UpdateFirmware/index.js
@@ -14,18 +14,20 @@ import type { ModalStatus } from 'components/ManagerPage/FirmwareUpdate'
import type { LedgerScriptParams } from 'helpers/common'
import StepOSUInstaller from './steps/01-step-osu-installer'
+import StepFullFirmwareInstall from './steps/01-step-install-full-firmware'
import StepFlashMcu from './steps/02-step-flash-mcu'
import StepConfirmation, { StepConfirmFooter } from './steps/03-step-confirmation'
export type StepProps = DefaultStepProps & {
firmware: ?LedgerScriptParams,
onCloseModal: () => void,
+ installOsuFirmware: () => void,
+ installFinalFirmware: () => void,
+ flashMcu: () => void,
}
type StepId = 'idCheck' | 'updateMCU' | 'finish'
-// FIXME: Debugging for now to move between steps
-// Remove when plugged to firmware update
function DebugFooter({
transitionTo,
where,
@@ -36,45 +38,74 @@ function DebugFooter({
return transitionTo(where)}>{where}
}
-const createSteps = ({ t }: { t: T }) => [
- {
- id: 'idCheck',
- label: t('app:manager.modal.steps.idCheck'),
- component: StepOSUInstaller,
- footer: ({ firmware, ...props }: StepProps) => (
-
- ),
- onBack: null,
- hideFooter: false,
- },
- {
- id: 'updateMCU',
- label: t('app:manager.modal.steps.updateMCU'),
- component: StepFlashMcu,
- footer: ({ firmware, ...props }: StepProps) => (
-
-
-
-
- ),
- onBack: null,
- hideFooter: false,
- },
- {
+const createSteps = ({
+ t,
+ firmware,
+}: {
+ t: T,
+ firmware: LedgerScriptParams & { shouldUpdateMcu: boolean },
+}): Array<*> => {
+ const finalStep = {
id: 'finish',
label: t('app:addAccounts.breadcrumb.finish'),
component: StepConfirmation,
footer: StepConfirmFooter,
onBack: null,
hideFooter: false,
- },
-]
+ }
+
+ if (firmware.shouldUpdateMcu) {
+ return [
+ {
+ id: 'idCheck',
+ label: t('app:manager.modal.steps.idCheck'),
+ component: StepOSUInstaller,
+ footer: ({ firmware, ...props }: StepProps) => (
+
+ ),
+ onBack: null,
+ hideFooter: false,
+ },
+ {
+ id: 'updateMCU',
+ label: t('app:manager.modal.steps.updateMCU'),
+ component: StepFlashMcu,
+ footer: ({ firmware, ...props }: StepProps) => (
+
+
+
+
+ ),
+ onBack: null,
+ hideFooter: false,
+ },
+ finalStep,
+ ]
+ }
+
+ return [
+ {
+ id: 'idCheck',
+ label: t('app:manager.modal.steps.idCheck'),
+ component: StepFullFirmwareInstall,
+ footer: ({ firmware, ...props }: StepProps) => (
+
+ ),
+ onBack: null,
+ hideFooter: false,
+ },
+ finalStep,
+ ]
+}
type Props = {
t: T,
status: ModalStatus,
onClose: () => void,
- firmware: ?LedgerScriptParams,
+ firmware: LedgerScriptParams & { shouldUpdateMcu: boolean },
+ installOsuFirmware: () => void,
+ installFinalFirmware: () => void,
+ flashMcu: () => void,
}
type State = {
@@ -86,17 +117,18 @@ class UpdateModal extends PureComponent {
stepId: 'idCheck',
}
- STEPS = createSteps({ t: this.props.t })
+ STEPS = createSteps({ t: this.props.t, firmware: this.props.firmware })
handleStepChange = (step: Step) => this.setState({ stepId: step.id })
render(): React$Node {
- const { status, t, firmware, onClose } = this.props
+ const { status, t, firmware, onClose, ...props } = this.props
const { stepId } = this.state
const additionalProps = {
firmware,
onCloseModal: onClose,
+ ...props,
}
return (
@@ -104,6 +136,7 @@ class UpdateModal extends PureComponent {
onClose={onClose}
isOpened={status === 'install'}
refocusWhenChange={stepId}
+ preventBackdropClick={false}
render={() => (
- {t('app:manager.modal.confirmIdentifier')}
-
- {t('app:manager.modal.confirmIdentifierText')}
-
-
-
- {t('app:manager.modal.identifier')}
+class StepFullFirmwareInstall extends PureComponent {
+ componentDidMount() {
+ this.install()
+ }
+
+ install = async () => {
+ const { installOsuFirmware, installFinalFirmware, transitionTo } = this.props
+ const success = await installOsuFirmware()
+ if (success) {
+ const finalSuccess = await installFinalFirmware()
+ if (finalSuccess) {
+ transitionTo('finish')
+ }
+ }
+ }
+
+ renderBody = () => {
+ const { installing } = this.state
+ const { firmware, t } = this.props
+
+ if (installing) {
+ return (
+
+
+
+ )
+ }
+
+ return (
+
+
+
+ {t('app:manager.modal.identifier')}
+
+
+ {firmware && firmware.hash}
+
+
+
+
+
+
+ )
+ }
+
+ render() {
+ const { t } = this.props
+ return (
+
+ {t('app:manager.modal.confirmIdentifier')}
+
+ {t('app:manager.modal.confirmIdentifierText')}
-
- {firmware && firmware.hash}
-
-
-
-
-
-
- )
+ {this.renderBody()}
+
+ )
+ }
}
export default StepFullFirmwareInstall
diff --git a/src/components/modals/UpdateFirmware/steps/01-step-osu-installer.js b/src/components/modals/UpdateFirmware/steps/01-step-osu-installer.js
index 5aa12aa2..e42cb954 100644
--- a/src/components/modals/UpdateFirmware/steps/01-step-osu-installer.js
+++ b/src/components/modals/UpdateFirmware/steps/01-step-osu-installer.js
@@ -1,11 +1,10 @@
// @flow
-import React from 'react'
+import React, { PureComponent } from 'react'
import styled from 'styled-components'
import Box from 'components/base/Box'
import Text from 'components/base/Text'
-import Button from 'components/base/Button'
import DeviceConfirm from 'components/DeviceConfirm'
import type { StepProps } from '../'
@@ -46,27 +45,41 @@ const Ellipsis = styled.span`
width: 100%;
`
-// TODO: Change to class component and add osu firmware install
-function StepOSUInstaller({ t, firmware }: StepProps) {
- return (
-
- {t('app:manager.modal.confirmIdentifier')}
-
- {t('app:manager.modal.confirmIdentifierText')}
-
-
-
- {t('app:manager.modal.identifier')}
+class StepOSUInstaller extends PureComponent {
+ componentDidMount() {
+ this.install()
+ }
+
+ install = async () => {
+ const { installOsuFirmware, transitionTo } = this.props
+ const success = await installOsuFirmware()
+ if (success) {
+ transitionTo('updateMCU')
+ }
+ }
+
+ render() {
+ const { t, firmware } = this.props
+ return (
+
+ {t('app:manager.modal.confirmIdentifier')}
+
+ {t('app:manager.modal.confirmIdentifierText')}
-
- {firmware && firmware.hash}
-
-
-
-
-
-
- )
+
+
+ {t('app:manager.modal.identifier')}
+
+
+ {firmware && firmware.hash}
+
+
+
+
+
+
+ )
+ }
}
export default StepOSUInstaller
diff --git a/src/components/modals/UpdateFirmware/steps/02-step-flash-mcu.js b/src/components/modals/UpdateFirmware/steps/02-step-flash-mcu.js
index 3991634c..b4781936 100644
--- a/src/components/modals/UpdateFirmware/steps/02-step-flash-mcu.js
+++ b/src/components/modals/UpdateFirmware/steps/02-step-flash-mcu.js
@@ -1,12 +1,21 @@
// @flow
-import React from 'react'
+import React, { PureComponent, Fragment } from 'react'
import styled from 'styled-components'
+import { connect } from 'react-redux'
+import { timeout } from 'rxjs/operators/timeout'
+import { DEVICE_INFOS_TIMEOUT } from 'config/constants'
import { i } from 'helpers/staticPath'
+import { getCurrentDevice } from 'reducers/devices'
+import { createCancelablePolling } from 'helpers/promise'
+import getDeviceInfo from 'commands/getDeviceInfo'
import Box from 'components/base/Box'
import Text from 'components/base/Text'
+import Progress from 'components/base/Progress'
+
+import type { Device } from 'types/common'
import type { StepProps } from '../'
@@ -35,36 +44,112 @@ const Separator = styled(Box).attrs({
background-color: currentColor;
`
-// TODO: Change to class component and add flash mcu and final
-function StepFlashMcu({ t }: StepProps) {
- return (
-
- {t('app:manager.modal.mcuTitle')}
-
-
- {'1.'}
- {t('app:manager.modal.mcuFirst')}
-
-
-
-
-
-
- {'2.'}
- {t('app:manager.modal.mcuSecond')}
-
-
-
-
- )
+const mapStateToProps = state => ({
+ device: getCurrentDevice(state),
+})
+
+type Props = StepProps & { device?: Device }
+
+type State = {
+ installing: boolean,
+}
+
+class StepFlashMcu extends PureComponent {
+ state = {
+ installing: false,
+ }
+
+ componentDidMount() {
+ this.install()
+ }
+
+ componentWillUnmount() {
+ this._unsubConnect()
+ }
+
+ waitForDeviceInBootloader = () => {
+ const { unsubscribe, promise } = createCancelablePolling(async () => {
+ const { device } = this.props
+ if (!device) {
+ throw new Error('No device')
+ }
+ const deviceInfo = await getDeviceInfo
+ .send({ devicePath: device.path })
+ .pipe(timeout(DEVICE_INFOS_TIMEOUT))
+ .toPromise()
+ if (!deviceInfo.isBootloader) {
+ throw new Error('Device is not in bootloader')
+ }
+ return { device, deviceInfo }
+ })
+ this._unsubConnect = unsubscribe
+ return promise
+ }
+
+ install = async () => {
+ await this.waitForDeviceInBootloader()
+ const { flashMcu, installFinalFirmware, transitionTo } = this.props
+ this.setState({ installing: true })
+ await flashMcu()
+ const finalSuccess = await installFinalFirmware()
+
+ if (finalSuccess) {
+ transitionTo('finish')
+ }
+ }
+
+ renderBody = () => {
+ const { installing } = this.state
+ const { t } = this.props
+
+ if (installing) {
+ return (
+
+
+
+ )
+ }
+
+ return (
+
+
+
+ {'1.'}
+ {t('app:manager.modal.mcuFirst')}
+
+
+
+
+
+
+ {'2.'}
+ {t('app:manager.modal.mcuSecond')}
+
+
+
+
+ )
+ }
+
+ _unsubConnect: *
+
+ render() {
+ const { t } = this.props
+ return (
+
+ {t('app:manager.modal.mcuTitle')}
+ {this.renderBody()}
+
+ )
+ }
}
-export default StepFlashMcu
+export default connect(mapStateToProps)(StepFlashMcu)
diff --git a/src/helpers/devices/getLatestFirmwareForDevice.js b/src/helpers/devices/getLatestFirmwareForDevice.js
index 6a0a2624..3406b8af 100644
--- a/src/helpers/devices/getLatestFirmwareForDevice.js
+++ b/src/helpers/devices/getLatestFirmwareForDevice.js
@@ -3,6 +3,9 @@ import network from 'api/network'
import { GET_LATEST_FIRMWARE } from 'helpers/urls'
import type { DeviceInfo } from 'helpers/devices/getDeviceInfo'
+import getFinalFirmwareById from 'helpers/firmware/getFinalFirmwareById'
+import getMcus from 'helpers/firmware/getMcus'
+
import getCurrentFirmware from './getCurrentFirmware'
import getDeviceVersion from './getDeviceVersion'
@@ -34,7 +37,23 @@ export default async (deviceInfo: DeviceInfo) => {
}
const { se_firmware_osu_version } = data
- return se_firmware_osu_version
+ 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)
+
+ if (!seFirmwareFinalVersion.mcu_versions.includes(...currentMcuVersionId)) {
+ return {
+ ...se_firmware_osu_version,
+ shouldUpdateMcu: true,
+ }
+ }
+
+ return { ...se_firmware_osu_version, shouldUpdateMcu: false }
} catch (err) {
const error = Error(err.message)
error.stack = err.stack
diff --git a/src/helpers/firmware/getMcus.js b/src/helpers/firmware/getMcus.js
new file mode 100644
index 00000000..3f0b0399
--- /dev/null
+++ b/src/helpers/firmware/getMcus.js
@@ -0,0 +1,19 @@
+// @flow
+import network from 'api/network'
+
+import { GET_MCUS } from 'helpers/urls'
+
+export default async (): Promise<*> => {
+ try {
+ const { data } = await network({
+ method: 'GET',
+ url: GET_MCUS,
+ })
+
+ return data
+ } catch (err) {
+ const error = Error(err.message)
+ error.stack = err.stack
+ throw err
+ }
+}
diff --git a/src/helpers/devices/getNextMCU.js b/src/helpers/firmware/getNextMCU.js
similarity index 100%
rename from src/helpers/devices/getNextMCU.js
rename to src/helpers/firmware/getNextMCU.js
diff --git a/src/helpers/firmware/installFinalFirmware.js b/src/helpers/firmware/installFinalFirmware.js
index e4d5269b..6e47a9fc 100644
--- a/src/helpers/firmware/installFinalFirmware.js
+++ b/src/helpers/firmware/installFinalFirmware.js
@@ -6,12 +6,14 @@ 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 getFinalFirmwareById from './getFinalFirmwareById'
-type Result = *
+type Result = Promise<{ success: boolean, error?: string }>
-export default async (transport: Transport<*>, deviceInfo: DeviceInfo): Result => {
+export default async (transport: Transport<*>): Result => {
try {
+ const deviceInfo: DeviceInfo = await getDeviceInfo(transport)
const device = await getDeviceVersion(deviceInfo.targetId, deviceInfo.providerId)
const firmware = await getOsuFirmware({ deviceId: device.id, version: deviceInfo.fullVersion })
const { next_se_firmware_final_version } = firmware
diff --git a/src/helpers/firmware/installMcu.js b/src/helpers/firmware/installMcu.js
index 44ae9c37..b07faea3 100644
--- a/src/helpers/firmware/installMcu.js
+++ b/src/helpers/firmware/installMcu.js
@@ -3,18 +3,16 @@ import type Transport from '@ledgerhq/hw-transport'
import { WS_MCU } from 'helpers/urls'
import { createDeviceSocket } from 'helpers/socket'
-import getNextMCU from 'helpers/devices/getNextMCU'
+import getNextMCU from 'helpers/firmware/getNextMCU'
+import getDeviceInfo from 'helpers/devices/getDeviceInfo'
type Result = Promise<*>
-export default async (
- transport: Transport<*>,
- args: { targetId: string | number, version: string },
-): Result => {
- const { version } = args
+export default async (transport: Transport<*>): Result => {
+ const { seVersion: version, targetId } = await getDeviceInfo(transport)
const nextVersion = await getNextMCU(version)
const params = {
- targetId: args.targetId,
+ targetId,
version: nextVersion.name,
}
const url = WS_MCU(params)
diff --git a/src/helpers/firmware/installOsuFirmware.js b/src/helpers/firmware/installOsuFirmware.js
index 5995b7ae..2795f951 100644
--- a/src/helpers/firmware/installOsuFirmware.js
+++ b/src/helpers/firmware/installOsuFirmware.js
@@ -6,12 +6,31 @@ import { createDeviceSocket } from 'helpers/socket'
import type { LedgerScriptParams } from 'helpers/common'
+import { createCustomErrorClass } from '../errors'
+
+const ManagerUnexpectedError = createCustomErrorClass('ManagerUnexpected')
+const ManagerNotEnoughSpaceError = createCustomErrorClass('ManagerNotEnoughSpace')
+const ManagerDeviceLockedError = createCustomErrorClass('ManagerDeviceLocked')
+
+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()
+ default:
+ throw new ManagerUnexpectedError(e.message, { msg: e.message })
+ }
+ })
+}
+
type Result = Promise<{ success: boolean, error?: any }>
export default async (
transport: Transport<*>,
targetId: string | number,
- firmware: LedgerScriptParams,
+ firmware: LedgerScriptParams & { shouldUpdateMcu: boolean },
): Result => {
try {
const params = {
@@ -19,8 +38,9 @@ export default async (
...firmware,
firmwareKey: firmware.firmware_key,
}
+ delete params.shouldUpdateMcu
const url = WS_INSTALL(params)
- await createDeviceSocket(transport, url).toPromise()
+ await remapError(createDeviceSocket(transport, url).toPromise())
return { success: true }
} catch (error) {
const result = { success: false, error }
diff --git a/src/helpers/urls.js b/src/helpers/urls.js
index 6ff24a70..11bed849 100644
--- a/src/helpers/urls.js
+++ b/src/helpers/urls.js
@@ -21,6 +21,7 @@ export const GET_CURRENT_FIRMWARE: string = managerUrlbuilder('get_firmware_vers
export const GET_CURRENT_OSU: string = managerUrlbuilder('get_osu_version')
export const GET_LATEST_FIRMWARE: string = managerUrlbuilder('get_latest_firmware')
export const GET_NEXT_MCU: string = managerUrlbuilder('mcu_versions_bootloader')
+export const GET_MCUS: string = managerUrlbuilder('mcu_versions')
export const GET_CATEGORIES: string = managerUrlbuilder('categories')
export const GET_APPLICATIONS: string = managerUrlbuilder('applications')
diff --git a/static/images/logos/bootloaderMode.png b/static/images/logos/bootloaderMode.png
new file mode 100755
index 00000000..b6983a79
Binary files /dev/null and b/static/images/logos/bootloaderMode.png differ
diff --git a/static/images/logos/unplugDevice.png b/static/images/logos/unplugDevice.png
index 41aac779..38158a4a 100755
Binary files a/static/images/logos/unplugDevice.png and b/static/images/logos/unplugDevice.png differ