diff --git a/src/components/ManagerPage/FirmwareFinalUpdate.js b/src/components/ManagerPage/FirmwareFinalUpdate.js index f1498929..19e9b76f 100644 --- a/src/components/ManagerPage/FirmwareFinalUpdate.js +++ b/src/components/ManagerPage/FirmwareFinalUpdate.js @@ -10,7 +10,7 @@ import type { Device, T } from 'types/common' import installFinalFirmware from 'commands/installFinalFirmware' import Box, { Card } from 'components/base/Box' -// import Button from 'components/base/Button' +import Button from 'components/base/Button' type Props = { t: T, @@ -33,7 +33,10 @@ class FirmwareFinalUpdate extends PureComponent { try { const { device, deviceInfo } = this.props const { success } = await installFinalFirmware - .send({ devicePath: device.path, deviceInfo }) + .send({ + devicePath: device.path, + deviceInfo, + }) .toPromise() if (success) { this.setState() @@ -52,7 +55,9 @@ class FirmwareFinalUpdate extends PureComponent { {t('app:manager.firmware.update')} - + + + ) diff --git a/src/components/ManagerPage/FirmwareUpdate.js b/src/components/ManagerPage/FirmwareUpdate.js index d00fdaa6..60afcc15 100644 --- a/src/components/ManagerPage/FirmwareUpdate.js +++ b/src/components/ManagerPage/FirmwareUpdate.js @@ -1,8 +1,8 @@ // @flow /* eslint-disable react/jsx-no-literals */ // FIXME -import React, { PureComponent, Fragment } from 'react' -import { translate, Trans } from 'react-i18next' +import React, { PureComponent } from 'react' +import { translate } from 'react-i18next' import isEqual from 'lodash/isEqual' import isEmpty from 'lodash/isEmpty' import invariant from 'invariant' @@ -14,14 +14,13 @@ import type { LedgerScriptParams } from 'helpers/common' import getLatestFirmwareForDevice from 'commands/getLatestFirmwareForDevice' import installOsuFirmware from 'commands/installOsuFirmware' +import DisclaimerModal from 'components/modals/UpdateFirmware/Disclaimer' +import UpdateModal from 'components/modals/UpdateFirmware' import type { DeviceInfo } from 'helpers/devices/getDeviceInfo' import Tooltip from 'components/base/Tooltip' import Box, { Card } from 'components/base/Box' import Text from 'components/base/Text' -import Modal, { ModalBody, ModalFooter, ModalTitle, ModalContent } from 'components/base/Modal' -import Button from 'components/base/Button' -// import Progress from 'components/base/Progress' import NanoS from 'icons/device/NanoS' import CheckFull from 'icons/CheckFull' @@ -29,12 +28,10 @@ import CheckFull from 'icons/CheckFull' import { PreventDeviceChangeRecheck } from 'components/EnsureDevice' import UpdateFirmwareButton from './UpdateFirmwareButton' -let CACHED_LATEST_FIRMWARE = null - export const getCleanVersion = (input: string): string => input.endsWith('-osu') ? input.replace('-osu', '') : input -type ModalStatus = 'closed' | 'disclaimer' | 'installing' | 'error' | 'success' +export type ModalStatus = 'closed' | 'disclaimer' | 'install' | 'error' | 'success' type Props = { t: T, @@ -58,7 +55,7 @@ class FirmwareUpdate extends PureComponent { } componentDidUpdate() { - if (!CACHED_LATEST_FIRMWARE || isEmpty(this.state.latestFirmware)) { + if (isEmpty(this.state.latestFirmware)) { this.fetchLatestFirmware() } } @@ -71,14 +68,12 @@ class FirmwareUpdate extends PureComponent { fetchLatestFirmware = async () => { const { deviceInfo } = this.props - const latestFirmware = - CACHED_LATEST_FIRMWARE || (await getLatestFirmwareForDevice.send(deviceInfo).toPromise()) + const latestFirmware = await getLatestFirmwareForDevice.send(deviceInfo).toPromise() if ( !isEmpty(latestFirmware) && !isEqual(this.state.latestFirmware, latestFirmware) && !this._unmounting ) { - CACHED_LATEST_FIRMWARE = latestFirmware this.setState({ latestFirmware }) } } @@ -91,7 +86,7 @@ class FirmwareUpdate extends PureComponent { const { device: { path: devicePath }, } = this.props - this.setState({ modal: 'installing' }) + this.setState({ modal: 'install' }) const { success } = await installOsuFirmware .send({ devicePath, firmware: latestFirmware, targetId: deviceInfo.targetId }) .toPromise() @@ -105,43 +100,8 @@ class FirmwareUpdate extends PureComponent { handleCloseModal = () => this.setState({ modal: 'closed' }) - handleInstallModal = () => this.setState({ modal: 'disclaimer' }) - - renderModal = () => { - const { t } = this.props - const { modal, latestFirmware } = this.state - return ( - ( - - - {t('app:manager.firmware.update')} - - - - You are about to install the latest - - {`firmware ${latestFirmware ? getCleanVersion(latestFirmware.name) : ''}`} - - - - - {t('app:manager.firmware.disclaimerAppDelete')} - {t('app:manager.firmware.disclaimerAppReinstall')} - - - - - - - - )} - /> - ) - } + handleDisclaimerModal = () => this.setState({ modal: 'disclaimer' }) + handleInstallModal = () => this.setState({ modal: 'install' }) render() { const { deviceInfo, t } = this.props @@ -172,11 +132,17 @@ class FirmwareUpdate extends PureComponent { {modal !== 'closed' ? : null} - {this.renderModal()} + + ) } diff --git a/src/components/ManagerPage/UpdateFirmwareButton.js b/src/components/ManagerPage/UpdateFirmwareButton.js index ebeb884c..cdfead05 100644 --- a/src/components/ManagerPage/UpdateFirmwareButton.js +++ b/src/components/ManagerPage/UpdateFirmwareButton.js @@ -4,7 +4,7 @@ import { translate } from 'react-i18next' import type { T } from 'types/common' -import { EXPERIMENTAL_FIRMWARE_UPDATE } from 'config/constants' +// import { EXPERIMENTAL_FIRMWARE_UPDATE } from 'config/constants' import Button from 'components/base/Button' import Text from 'components/base/Text' @@ -27,7 +27,7 @@ const UpdateFirmwareButton = ({ t, firmware, installFirmware }: Props) => {t('app:manager.firmware.latest', { version: getCleanVersion(firmware.name) })} - diff --git a/src/components/ManagerPage/index.js b/src/components/ManagerPage/index.js index 56035d87..e0b3f76e 100644 --- a/src/components/ManagerPage/index.js +++ b/src/components/ManagerPage/index.js @@ -7,7 +7,6 @@ import type { Device } from 'types/common' import type { DeviceInfo } from 'helpers/devices/getDeviceInfo' import Dashboard from './Dashboard' -// import FlashMcu from './FlashMcu' import ManagerGenuineCheck from './ManagerGenuineCheck' diff --git a/src/components/modals/UpdateFirmware/Disclaimer.js b/src/components/modals/UpdateFirmware/Disclaimer.js new file mode 100644 index 00000000..379c8928 --- /dev/null +++ b/src/components/modals/UpdateFirmware/Disclaimer.js @@ -0,0 +1,80 @@ +// @flow +/* eslint react/jsx-no-literals: 0 */ + +import React, { PureComponent, Fragment } from 'react' +import { translate, Trans } from 'react-i18next' + +import type { T } from 'types/common' + +import Modal, { ModalBody, ModalFooter, ModalTitle, ModalContent } from 'components/base/Modal' +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 type { ModalStatus } from 'components/ManagerPage/FirmwareUpdate' + +type FirmwareInfos = { + name: string, + notes: string, +} + +type Props = { + t: T, + status: ModalStatus, + firmware: FirmwareInfos, + goToNextStep: () => void, + onClose: () => void, +} + +type State = * + +class DisclaimerModal extends PureComponent { + render(): React$Node { + const { status, firmware, onClose, t, goToNextStep } = this.props + return ( + ( + + + {t('app:manager.firmware.update')} + + + + You are about to install the latest + + {`firmware ${firmware ? getCleanVersion(firmware.name) : ''}`} + + + + + {t('app:manager.firmware.disclaimerAppDelete')} + {t('app:manager.firmware.disclaimerAppReinstall')} + + + + + + {firmware.notes} + + + + + + + + + + )} + /> + ) + } +} + +export default translate()(DisclaimerModal) diff --git a/src/components/modals/UpdateFirmware/index.js b/src/components/modals/UpdateFirmware/index.js new file mode 100644 index 00000000..abb71c8b --- /dev/null +++ b/src/components/modals/UpdateFirmware/index.js @@ -0,0 +1,122 @@ +// @flow +import React, { PureComponent, Fragment } from 'react' +import { translate } from 'react-i18next' + +import type { T } 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 StepFlashMcu from './steps/02-step-flash-mcu' +import StepConfirmation, { StepConfirmFooter } from './steps/03-step-confirmation' + +export type StepProps = DefaultStepProps & { + firmware: ?LedgerScriptParams, + onCloseModal: () => void, +} + +type StepId = 'idCheck' | 'updateMCU' | 'finish' + +// FIXME: Debugging for now to move between steps +// Remove when plugged to firmware update +function DebugFooter({ + transitionTo, + where, +}: { + where: string, + transitionTo: (where: string) => void, +}) { + return +} + +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, + }, + { + id: 'finish', + label: t('app:addAccounts.breadcrumb.finish'), + component: StepConfirmation, + footer: StepConfirmFooter, + onBack: null, + hideFooter: false, + }, +] + +type Props = { + t: T, + status: ModalStatus, + onClose: () => void, + firmware: ?LedgerScriptParams, +} + +type State = { + stepId: StepId | string, +} + +class UpdateModal extends PureComponent { + state = { + stepId: 'idCheck', + } + + STEPS = createSteps({ t: this.props.t }) + + handleStepChange = (step: Step) => this.setState({ stepId: step.id }) + + render(): React$Node { + const { status, t, firmware, onClose } = this.props + const { stepId } = this.state + + const additionalProps = { + firmware, + onCloseModal: onClose, + } + + return ( + ( + + + + )} + /> + ) + } +} + +export default translate()(UpdateModal) 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 new file mode 100644 index 00000000..333ac961 --- /dev/null +++ b/src/components/modals/UpdateFirmware/steps/01-step-install-full-firmware.js @@ -0,0 +1,72 @@ +// @flow + +import React 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 '../' + +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%; +` + +// TODO: Change to class component and add osu firmware install +function StepFullFirmwareInstall({ t, firmware }: StepProps) { + return ( + + {t('app:manager.modal.confirmIdentifier')} + + {t('app:manager.modal.confirmIdentifierText')} + + + + {t('app:manager.modal.identifier')} + +
+ {firmware && firmware.hash} +
+
+ + + +
+ ) +} + +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 new file mode 100644 index 00000000..5aa12aa2 --- /dev/null +++ b/src/components/modals/UpdateFirmware/steps/01-step-osu-installer.js @@ -0,0 +1,72 @@ +// @flow + +import React 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 '../' + +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%; +` + +// 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')} + +
+ {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 new file mode 100644 index 00000000..3991634c --- /dev/null +++ b/src/components/modals/UpdateFirmware/steps/02-step-flash-mcu.js @@ -0,0 +1,70 @@ +// @flow + +import React from 'react' +import styled from 'styled-components' + +import { i } from 'helpers/staticPath' + +import Box from 'components/base/Box' +import Text from 'components/base/Text' + +import type { StepProps } from '../' + +const Container = styled(Box).attrs({ + alignItems: 'center', + fontSize: 4, + color: 'dark', +})`` + +const Title = styled(Box).attrs({ + ff: 'Museo Sans|Regular', + fontSize: 5, + mb: 3, +})`` + +const Bullet = styled.span` + font-weight: 600; + color: #142533; +` + +const Separator = styled(Box).attrs({ + color: 'fog', +})` + height: 1px; + width: 100%; + 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')} + + {t('app:manager.modal.mcuFirst')} + + + + + {'2.'} + {t('app:manager.modal.mcuSecond')} + + {t('app:manager.modal.mcuFirst')} + + + ) +} + +export default StepFlashMcu diff --git a/src/components/modals/UpdateFirmware/steps/03-step-confirmation.js b/src/components/modals/UpdateFirmware/steps/03-step-confirmation.js new file mode 100644 index 00000000..553c0c5a --- /dev/null +++ b/src/components/modals/UpdateFirmware/steps/03-step-confirmation.js @@ -0,0 +1,52 @@ +// @flow + +import React 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 CheckCircle from 'icons/CheckCircle' + +import type { StepProps } from '../' + +const Container = styled(Box).attrs({ + alignItems: 'center', + fontSize: 4, + color: 'dark', +})`` + +const Title = styled(Box).attrs({ + fontFamily: 'Museo Sans', + fontSize: 6, + color: 'dark', +})` + font-weight: 500; +` + +function StepConfirmation({ t }: StepProps) { + return ( + + + + + {t('app:manager.modal.successTitle')} + + + {t('app:manager.modal.successText')} + + + + + ) +} + +export function StepConfirmFooter({ t, onCloseModal }: StepProps) { + return ( + + ) +} + +export default StepConfirmation diff --git a/src/helpers/common.js b/src/helpers/common.js index 0015c91d..e04c37d8 100644 --- a/src/helpers/common.js +++ b/src/helpers/common.js @@ -20,6 +20,7 @@ export type LedgerScriptParams = { version: string, icon: string, app?: number, + hash?: string, } /** diff --git a/src/helpers/socket.js b/src/helpers/socket.js index 9c35ecba..18d98795 100644 --- a/src/helpers/socket.js +++ b/src/helpers/socket.js @@ -74,6 +74,7 @@ export const createDeviceSocket = (transport: Transport<*>, url: string) => for (const apdu of data) { const r: Buffer = await transport.exchange(Buffer.from(apdu, 'hex')) lastStatus = r.slice(r.length - 2) + if (lastStatus.toString('hex') !== '9000') break } diff --git a/static/i18n/en/app.yml b/static/i18n/en/app.yml index 4158726b..e85caab8 100644 --- a/static/i18n/en/app.yml +++ b/static/i18n/en/app.yml @@ -204,12 +204,24 @@ manager: firmware: installed: 'Firmware version {{version}}' update: Update firmware - updateTitle: Firmware update continue: Continue update latest: 'Firmware version {{version}} is available' disclaimerTitle: 'You are about to install the latest <1><0>firmware {{version}}' disclaimerAppDelete: Please note that all the apps installed on your device will be deleted. disclaimerAppReinstall: You will be able to re-install your apps after the firmware update + modal: + steps: + idCheck: Identifier check + updateMCU: Update MCU + confirm: Confirmation + confirmIdentifier: Confirm identifier + confirmIdentifierText: Please confirm identifier on your Device. Be sure the identifier is the same as below + identifier: Identifier + mcuTitle: Updating MCU + mcuFirst: Unplug your device from your computer + mcuSecond: Press and hold left button and plug your device until the processing screen appears + successTitle: Firmware has been updated with success + successText: You can now re-install your applications on your device title: Manager subtitle: Install or uninstall apps on your device device: diff --git a/static/images/logos/unplugDevice.png b/static/images/logos/unplugDevice.png new file mode 100755 index 00000000..41aac779 Binary files /dev/null and b/static/images/logos/unplugDevice.png differ