diff --git a/src/commands/installOsuFirmware.js b/src/commands/installOsuFirmware.js index fd990d54..f73a76d4 100644 --- a/src/commands/installOsuFirmware.js +++ b/src/commands/installOsuFirmware.js @@ -6,12 +6,12 @@ import { fromPromise } from 'rxjs/observable/fromPromise' import { withDevice } from 'helpers/deviceAccess' import installOsuFirmware from 'helpers/firmware/installOsuFirmware' -import type { LedgerScriptParams } from 'helpers/common' +import type { Firmware } from 'components/modals/UpdateFirmware' type Input = { devicePath: string, targetId: string | number, - firmware: LedgerScriptParams & { shouldUpdateMcu: boolean }, + firmware: Firmware, } type Result = * diff --git a/src/components/ManagerPage/FirmwareUpdate.js b/src/components/ManagerPage/FirmwareUpdate.js index f5a896ef..a10ebf6d 100644 --- a/src/components/ManagerPage/FirmwareUpdate.js +++ b/src/components/ManagerPage/FirmwareUpdate.js @@ -37,7 +37,6 @@ export type ModalStatus = 'closed' | 'disclaimer' | 'install' | 'error' | 'succe type Props = { t: T, - device: Device, deviceInfo: DeviceInfo, } @@ -80,18 +79,15 @@ class FirmwareUpdate extends PureComponent { } } - installOsuFirmware = async () => { + installOsuFirmware = async (device: Device) => { try { const { latestFirmware } = this.state - const { - deviceInfo, - device: { path: devicePath }, - } = this.props + const { deviceInfo } = this.props invariant(latestFirmware, 'did not find a new firmware or firmware is not set') this.setState({ modal: 'install' }) const { success } = await installOsuFirmware - .send({ devicePath, firmware: latestFirmware, targetId: deviceInfo.targetId }) + .send({ devicePath: device.path, firmware: latestFirmware, targetId: deviceInfo.targetId }) .toPromise() return success } catch (err) { @@ -100,9 +96,8 @@ class FirmwareUpdate extends PureComponent { } } - installFinalFirmware = async () => { + installFinalFirmware = async (device: Device) => { try { - const { device } = this.props const { success } = await installFinalFirmware.send({ devicePath: device.path }).toPromise() return success } catch (err) { @@ -111,8 +106,7 @@ class FirmwareUpdate extends PureComponent { } } - flashMCU = async () => { - const { device } = this.props + flashMCU = async (device: Device) => { await installMcu.send({ devicePath: device.path }).toPromise() } diff --git a/src/components/modals/UpdateFirmware/index.js b/src/components/modals/UpdateFirmware/index.js index a86b8bef..1208ad61 100644 --- a/src/components/modals/UpdateFirmware/index.js +++ b/src/components/modals/UpdateFirmware/index.js @@ -1,50 +1,43 @@ // @flow -import React, { PureComponent, Fragment } from 'react' +import React, { PureComponent } from 'react' import { translate } from 'react-i18next' -import type { T } from 'types/common' +import type { T, Device } from 'types/common' import Modal from 'components/base/Modal' import Stepper from 'components/base/Stepper' -import Button from 'components/base/Button' import SyncSkipUnderPriority from 'components/SyncSkipUnderPriority' import type { StepProps as DefaultStepProps, Step } from 'components/base/Stepper' 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 Firmware = LedgerScriptParams & { shouldUpdateMcu: boolean } + export type StepProps = DefaultStepProps & { - firmware: ?LedgerScriptParams, + firmware: Firmware, onCloseModal: () => void, - installOsuFirmware: () => void, - installFinalFirmware: () => void, - flashMcu: () => void, + installOsuFirmware: (device: Device) => void, + installFinalFirmware: (device: Device) => void, + flashMCU: (device: Device) => void, } type StepId = 'idCheck' | 'updateMCU' | 'finish' -function DebugFooter({ - transitionTo, - where, -}: { - where: string, - transitionTo: (where: string) => void, -}) { - return -} +const createSteps = ({ t, firmware }: { t: T, firmware: Firmware }): Array<*> => { + const updateStep = { + id: 'idCheck', + label: t('app:manager.modal.steps.idCheck'), + component: StepFullFirmwareInstall, + footer: null, + onBack: null, + hideFooter: true, + } -const createSteps = ({ - t, - firmware, -}: { - t: T, - firmware: LedgerScriptParams & { shouldUpdateMcu: boolean }, -}): Array<*> => { const finalStep = { id: 'finish', label: t('app:addAccounts.breadcrumb.finish'), @@ -54,58 +47,35 @@ const createSteps = ({ hideFooter: false, } + const mcuStep = { + id: 'updateMCU', + label: t('app:manager.modal.steps.updateMCU'), + component: StepFlashMcu, + footer: null, + onBack: null, + hideFooter: true, + } + + const steps = [updateStep] + 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, - ] + steps.push(mcuStep) } - return [ - { - id: 'idCheck', - label: t('app:manager.modal.steps.idCheck'), - component: StepFullFirmwareInstall, - footer: ({ firmware, ...props }: StepProps) => ( - - ), - onBack: null, - hideFooter: false, - }, - finalStep, - ] + steps.push(finalStep) + + return steps } type Props = { t: T, status: ModalStatus, onClose: () => void, - firmware: LedgerScriptParams & { shouldUpdateMcu: boolean }, - installOsuFirmware: () => void, - installFinalFirmware: () => void, - flashMcu: () => void, + firmware: Firmware, + installOsuFirmware: (device: Device) => void, + installFinalFirmware: (device: Device) => void, + flashMCU: (device: Device) => void, + stepId: StepId | string, } type State = { @@ -113,10 +83,14 @@ type State = { } class UpdateModal extends PureComponent { - state = { + static defaultProps = { stepId: 'idCheck', } + state = { + stepId: this.props.stepId, + } + STEPS = createSteps({ t: this.props.t, firmware: this.props.firmware }) handleStepChange = (step: Step) => this.setState({ stepId: step.id }) diff --git a/src/components/modals/UpdateFirmware/steps/01-step-install-full-firmware.js b/src/components/modals/UpdateFirmware/steps/01-step-install-full-firmware.js index d325d0ee..0a06407b 100644 --- a/src/components/modals/UpdateFirmware/steps/01-step-install-full-firmware.js +++ b/src/components/modals/UpdateFirmware/steps/01-step-install-full-firmware.js @@ -2,12 +2,21 @@ 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 getDeviceInfo from 'commands/getDeviceInfo' + +import { getCurrentDevice } from 'reducers/devices' +import { createCancelablePolling } from 'helpers/promise' import Box from 'components/base/Box' import Text from 'components/base/Text' import Progress from 'components/base/Progress' import DeviceConfirm from 'components/DeviceConfirm' +import type { Device } from 'types/common' import type { StepProps } from '../' const Container = styled(Box).attrs({ @@ -46,22 +55,74 @@ const Ellipsis = styled.span` width: 100%; ` -class StepFullFirmwareInstall extends PureComponent { +type Props = StepProps & { + device: Device, +} + +type State = { + installing: boolean, +} + +class StepFullFirmwareInstall extends PureComponent { + state = { + installing: false, + } + componentDidMount() { this.install() } + componentWillUnmount() { + if (this._unsubConnect) this._unsubConnect() + } + + ensureDevice = () => { + 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() + return { device, deviceInfo } + }) + this._unsubConnect = unsubscribe + return promise + } + install = async () => { - const { installOsuFirmware, installFinalFirmware, transitionTo } = this.props - const success = await installOsuFirmware() + const { installOsuFirmware, installFinalFirmware } = this.props + const { device, deviceInfo } = await this.ensureDevice() + if (deviceInfo.isOSU) { + this.setState({ installing: true }) + const finalSuccess = await installFinalFirmware(device) + if (finalSuccess) this.transitionTo() + } + + const success = await installOsuFirmware(device) if (success) { - const finalSuccess = await installFinalFirmware() + this.setState({ installing: true }) + if (this._unsubConnect) this._unsubConnect() + const { device: cleanDevice } = await this.ensureDevice() + const finalSuccess = await installFinalFirmware(cleanDevice) if (finalSuccess) { - transitionTo('finish') + this.transitionTo() } } } + transitionTo = () => { + const { firmware, transitionTo } = this.props + if (firmware.shouldUpdateMcu) { + transitionTo('updateMCU') + } else { + transitionTo('finish') + } + } + renderBody = () => { const { installing } = this.state const { firmware, t } = this.props @@ -91,6 +152,8 @@ class StepFullFirmwareInstall extends PureComponent { ) } + _unsubConnect: * + render() { const { t } = this.props return ( @@ -105,4 +168,8 @@ class StepFullFirmwareInstall extends PureComponent { } } -export default StepFullFirmwareInstall +const mapStateToProps = state => ({ + device: getCurrentDevice(state), +}) + +export default connect(mapStateToProps)(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 deleted file mode 100644 index e42cb954..00000000 --- a/src/components/modals/UpdateFirmware/steps/01-step-osu-installer.js +++ /dev/null @@ -1,85 +0,0 @@ -// @flow - -import React, { PureComponent } from 'react' -import styled from 'styled-components' - -import Box from 'components/base/Box' -import Text from 'components/base/Text' -import DeviceConfirm from 'components/DeviceConfirm' - -import type { StepProps } from '../' - -const Container = styled(Box).attrs({ - alignItems: 'center', - fontSize: 4, - color: 'dark', - px: 7, -})`` - -const Title = styled(Box).attrs({ - ff: 'Museo Sans|Regular', - fontSize: 5, - mb: 3, -})`` - -const Address = styled(Box).attrs({ - bg: p => (p.notValid ? 'transparent' : p.withQRCode ? 'white' : 'lightGrey'), - borderRadius: 1, - color: 'dark', - ff: 'Open Sans|SemiBold', - fontSize: 4, - mt: 2, - px: p => (p.notValid ? 0 : 4), - py: p => (p.notValid ? 0 : 3), -})` - border: ${p => (p.notValid ? 'none' : `1px dashed ${p.theme.colors.fog}`)}; - cursor: text; - user-select: text; - width: 325px; -` - -const Ellipsis = styled.span` - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - width: 100%; -` - -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')} - - - - {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 b4781936..c73aadfe 100644 --- a/src/components/modals/UpdateFirmware/steps/02-step-flash-mcu.js +++ b/src/components/modals/UpdateFirmware/steps/02-step-flash-mcu.js @@ -64,7 +64,8 @@ class StepFlashMcu extends PureComponent { } componentWillUnmount() { - this._unsubConnect() + if (this._unsubConnect) this._unsubConnect() + if (this._unsubDeviceInfo) this._unsubDeviceInfo() } waitForDeviceInBootloader = () => { @@ -86,14 +87,38 @@ class StepFlashMcu extends PureComponent { return promise } - install = async () => { + getDeviceInfo = () => { + 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() + return { device, deviceInfo } + }) + this._unsubDeviceInfo = unsubscribe + return promise + } + + flash = async () => { await this.waitForDeviceInBootloader() - const { flashMcu, installFinalFirmware, transitionTo } = this.props - this.setState({ installing: true }) - await flashMcu() - const finalSuccess = await installFinalFirmware() + const { flashMCU, device } = this.props + if (device) { + this.setState({ installing: true }) + await flashMCU(device) + } + } - if (finalSuccess) { + install = async () => { + const { transitionTo } = this.props + this.flash() + const deviceInfo = await this.getDeviceInfo() + if (deviceInfo.isBootloader) { + this.flash() + } else { transitionTo('finish') } } @@ -140,6 +165,7 @@ class StepFlashMcu extends PureComponent { } _unsubConnect: * + _unsubDeviceInfo: * render() { const { t } = this.props diff --git a/src/helpers/common.js b/src/helpers/common.js index e04c37d8..562709ca 100644 --- a/src/helpers/common.js +++ b/src/helpers/common.js @@ -51,6 +51,16 @@ export async function getFirmwareInfo(transport: Transport<*>) { 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 } } catch (err) { const error = new Error(err.message) diff --git a/src/helpers/firmware/installMcu.js b/src/helpers/firmware/installMcu.js index b07faea3..105ee7c8 100644 --- a/src/helpers/firmware/installMcu.js +++ b/src/helpers/firmware/installMcu.js @@ -9,7 +9,8 @@ import getDeviceInfo from 'helpers/devices/getDeviceInfo' type Result = Promise<*> export default async (transport: Transport<*>): Result => { - const { seVersion: version, targetId } = await getDeviceInfo(transport) + const deviceInfo = await getDeviceInfo(transport) + const { seVersion: version, targetId } = deviceInfo const nextVersion = await getNextMCU(version) const params = { targetId, diff --git a/src/helpers/firmware/installOsuFirmware.js b/src/helpers/firmware/installOsuFirmware.js index 2795f951..895845c7 100644 --- a/src/helpers/firmware/installOsuFirmware.js +++ b/src/helpers/firmware/installOsuFirmware.js @@ -4,7 +4,7 @@ import type Transport from '@ledgerhq/hw-transport' import { WS_INSTALL } from 'helpers/urls' import { createDeviceSocket } from 'helpers/socket' -import type { LedgerScriptParams } from 'helpers/common' +import type { Firmware } from 'components/modals/UpdateFirmware' import { createCustomErrorClass } from '../errors' @@ -30,7 +30,7 @@ type Result = Promise<{ success: boolean, error?: any }> export default async ( transport: Transport<*>, targetId: string | number, - firmware: LedgerScriptParams & { shouldUpdateMcu: boolean }, + firmware: Firmware, ): Result => { try { const params = {