diff --git a/src/components/ManagerPage/AppsList.js b/src/components/ManagerPage/AppsList.js index 344f9d2b..ba343b78 100644 --- a/src/components/ManagerPage/AppsList.js +++ b/src/components/ManagerPage/AppsList.js @@ -1,7 +1,7 @@ // @flow /* eslint-disable react/jsx-no-literals */ // FIXME -import React, { PureComponent } from 'react' +import React, { PureComponent, Fragment } from 'react' import styled from 'styled-components' import { translate } from 'react-i18next' @@ -13,14 +13,16 @@ import installApp from 'commands/installApp' import uninstallApp from 'commands/uninstallApp' import Box from 'components/base/Box' -import Modal, { ModalBody } from 'components/base/Modal' +import Modal, { ModalBody, ModalFooter, ModalTitle, ModalContent } from 'components/base/Modal' import Tooltip from 'components/base/Tooltip' import Text from 'components/base/Text' import Progress from 'components/base/Progress' import Spinner from 'components/base/Spinner' import Button from 'components/base/Button' +import TranslatedError from 'components/TranslatedError' import ExclamationCircle from 'icons/ExclamationCircle' +import ExclamationCircleThin from 'icons/ExclamationCircleThin' import Update from 'icons/Update' import Trash from 'icons/Trash' import CheckCircle from 'icons/CheckCircle' @@ -89,7 +91,7 @@ class AppsList extends PureComponent { this.setState({ appsList, status: 'idle', appsLoaded: true }) } } catch (err) { - this.setState({ status: 'error', error: err.message }) + this.setState({ status: 'error', error: err }) } } @@ -119,7 +121,7 @@ class AppsList extends PureComponent { await uninstallApp.send(data).toPromise() this.setState({ status: 'success', app: '' }) } catch (err) { - this.setState({ status: 'error', error: err.message, app: '', mode: 'home' }) + this.setState({ status: 'error', error: err, app: '', mode: 'home' }) } } @@ -132,42 +134,80 @@ class AppsList extends PureComponent { ( - + {status === 'busy' || status === 'idle' ? ( - - {mode === 'installing' ? : } - - {t(`app:manager.apps.${mode}`, { app })} - - - - - + + + {mode === 'installing' ? ( + + + + ) : ( + + + + )} + + + + {t(`app:manager.apps.${mode}`, { app })} + + + + + + ) : status === 'error' ? ( - -
{'error happened'}
- {error} - -
+ + + + + + + + + + + + + ) : status === 'success' ? ( - - - - - - {t( - `app:manager.apps.${ - mode === 'installing' ? 'installSuccess' : 'uninstallSuccess' - }`, - { app }, - )} - - - + + + + + + + {t( + `app:manager.apps.${ + mode === 'installing' ? 'installSuccess' : 'uninstallSuccess' + }`, + { app }, + )} + + + + + + ) : null}
)} diff --git a/src/components/ManagerPage/Dashboard.js b/src/components/ManagerPage/Dashboard.js index 981a48d3..5b4eb971 100644 --- a/src/components/ManagerPage/Dashboard.js +++ b/src/components/ManagerPage/Dashboard.js @@ -2,6 +2,7 @@ import React from 'react' import { translate } from 'react-i18next' +import { EXPERIMENTAL_FIRMWARE_UPDATE } from 'config/constants' import type { T, Device } from 'types/common' import Box from 'components/base/Box' @@ -34,13 +35,15 @@ const Dashboard = ({ device, deviceInfo, t }: Props) => ( - + {EXPERIMENTAL_FIRMWARE_UPDATE ? ( + + ) : null} diff --git a/src/components/modals/ReleaseNotes.js b/src/components/modals/ReleaseNotes.js index 24f9114a..2d4950e3 100644 --- a/src/components/modals/ReleaseNotes.js +++ b/src/components/modals/ReleaseNotes.js @@ -3,7 +3,7 @@ import React, { PureComponent } from 'react' import { translate } from 'react-i18next' import ReactMarkdown from 'react-markdown' import styled from 'styled-components' -import axios from 'axios' +import network from 'api/network' import { MODAL_RELEASES_NOTES } from 'config/constants' import Modal, { ModalBody, ModalTitle, ModalContent, ModalFooter } from 'components/base/Modal' @@ -160,8 +160,10 @@ class ReleaseNotes extends PureComponent { if (!this.loading) { this.loading = true - axios - .get(`https://api.github.com/repos/LedgerHQ/ledger-live-desktop/releases/tags/v${version}`) + network({ + method: 'GET', + url: `https://api.github.com/repos/LedgerHQ/ledger-live-desktop/releases/tags/v${version}`, + }) .then(response => { const { body } = response.data diff --git a/src/config/constants.js b/src/config/constants.js index 802feb7a..ebca3946 100644 --- a/src/config/constants.js +++ b/src/config/constants.js @@ -67,6 +67,7 @@ export const SHOW_LEGACY_NEW_ACCOUNT = boolFromEnv('SHOW_LEGACY_NEW_ACCOUNT') export const HIGHLIGHT_I18N = boolFromEnv('HIGHLIGHT_I18N') export const DISABLE_ACTIVITY_INDICATORS = boolFromEnv('DISABLE_ACTIVITY_INDICATORS') export const EXPERIMENTAL_CENTER_MODAL = boolFromEnv('EXPERIMENTAL_CENTER_MODAL') +export const EXPERIMENTAL_FIRMWARE_UPDATE = boolFromEnv('EXPERIMENTAL_FIRMWARE_UPDATE') // Other constants diff --git a/src/helpers/apps/installApp.js b/src/helpers/apps/installApp.js index 2183d639..96f1af75 100644 --- a/src/helpers/apps/installApp.js +++ b/src/helpers/apps/installApp.js @@ -7,6 +7,19 @@ import { createDeviceSocket } from 'helpers/socket' import type { LedgerScriptParams } from 'helpers/common' +import createCustomErrorClass from '../createCustomErrorClass' + +const CannotInstall = createCustomErrorClass('CannotInstall') + +function remapError(promise) { + return promise.catch((e: Error) => { + if (e.message.endsWith('6982')) { + throw new CannotInstall() + } + throw e + }) +} + /** * Install an app on the device */ @@ -21,5 +34,5 @@ export default async function installApp( firmwareKey: app.firmware_key, } const url = `${BASE_SOCKET_URL_SECURE}/install?${qs.stringify(params)}` - return createDeviceSocket(transport, url).toPromise() + return remapError(createDeviceSocket(transport, url).toPromise()) } diff --git a/src/helpers/apps/listApps.js b/src/helpers/apps/listApps.js index 21f9ec03..2e7449fe 100644 --- a/src/helpers/apps/listApps.js +++ b/src/helpers/apps/listApps.js @@ -1,5 +1,5 @@ // @flow -import axios from 'axios' +import network from 'api/network' import { APPLICATIONS_BY_DEVICE } from 'helpers/urls' import getDeviceVersion from 'helpers/devices/getDeviceVersion' @@ -17,7 +17,7 @@ export default async (targetId: string | number, version: string) => { } const { data: { application_versions }, - } = await axios.post(APPLICATIONS_BY_DEVICE, params) + } = await network({ method: 'POST', url: APPLICATIONS_BY_DEVICE, data: params }) return application_versions.length > 0 ? application_versions : [] } catch (err) { const error = Error(err.message) diff --git a/src/helpers/apps/uninstallApp.js b/src/helpers/apps/uninstallApp.js index 570e361c..6ead5304 100644 --- a/src/helpers/apps/uninstallApp.js +++ b/src/helpers/apps/uninstallApp.js @@ -6,6 +6,18 @@ import { BASE_SOCKET_URL_SECURE } from 'config/constants' import { createDeviceSocket } from 'helpers/socket' import type { LedgerScriptParams } from 'helpers/common' +import createCustomErrorClass from '../createCustomErrorClass' + +const CannotUninstall = createCustomErrorClass('CannotUninstall') + +function remapError(promise) { + return promise.catch((e: Error) => { + if (e.message.endsWith('6a83')) { + throw new CannotUninstall() + } + throw e + }) +} /** * Install an app on the device @@ -22,5 +34,5 @@ export default async function uninstallApp( firmwareKey: app.delete_key, } const url = `${BASE_SOCKET_URL_SECURE}/install?${qs.stringify(params)}` - return createDeviceSocket(transport, url).toPromise() + return remapError(createDeviceSocket(transport, url).toPromise()) } diff --git a/src/helpers/devices/getCurrentFirmware.js b/src/helpers/devices/getCurrentFirmware.js index 41808bbb..d9f4c32f 100644 --- a/src/helpers/devices/getCurrentFirmware.js +++ b/src/helpers/devices/getCurrentFirmware.js @@ -1,5 +1,5 @@ // @flow -import axios from 'axios' +import network from 'api/network' import { GET_CURRENT_FIRMWARE } from 'helpers/urls' @@ -12,10 +12,14 @@ let error export default async (input: Input): Promise<*> => { try { const provider = 1 - const { data } = await axios.post(GET_CURRENT_FIRMWARE, { - device_version: input.deviceId, - version_name: input.version, - provider, + const { data } = await network({ + method: 'POST', + url: GET_CURRENT_FIRMWARE, + data: { + device_version: input.deviceId, + version_name: input.version, + provider, + }, }) return data } catch (err) { diff --git a/src/helpers/devices/getDeviceVersion.js b/src/helpers/devices/getDeviceVersion.js index 87cd2b6d..a66a8421 100644 --- a/src/helpers/devices/getDeviceVersion.js +++ b/src/helpers/devices/getDeviceVersion.js @@ -1,19 +1,16 @@ // @flow -import axios from 'axios' - import { GET_DEVICE_VERSION } from 'helpers/urls' +import network from 'api/network' export default async (targetId: string | number): Promise<*> => { - try { - const provider = 1 - const { data } = await axios.post(GET_DEVICE_VERSION, { + const provider = 1 + const { data } = await network({ + method: 'POST', + url: GET_DEVICE_VERSION, + data: { provider, target_id: targetId, - }) - return data - } catch (err) { - const error = Error(err.message) - error.stack = err.stack - throw err - } + }, + }) + return data } diff --git a/src/helpers/devices/getLatestFirmwareForDevice.js b/src/helpers/devices/getLatestFirmwareForDevice.js index dc705a80..71fa3598 100644 --- a/src/helpers/devices/getLatestFirmwareForDevice.js +++ b/src/helpers/devices/getLatestFirmwareForDevice.js @@ -1,5 +1,5 @@ // @flow -import axios from 'axios' +import network from 'api/network' import { GET_LATEST_FIRMWARE } from 'helpers/urls' import getCurrentFirmware from './getCurrentFirmware' @@ -21,10 +21,14 @@ export default async (input: Input) => { const seFirmwareVersion = await getCurrentFirmware({ version, deviceId: deviceVersion.id }) // Fetch next possible firmware - const { data } = await axios.post(GET_LATEST_FIRMWARE, { - current_se_firmware_final_version: seFirmwareVersion.id, - device_version: deviceVersion.id, - provider, + const { data } = await network({ + method: 'POST', + url: GET_LATEST_FIRMWARE, + data: { + current_se_firmware_final_version: seFirmwareVersion.id, + device_version: deviceVersion.id, + provider, + }, }) if (data.result === 'null') { diff --git a/src/helpers/devices/getNextMCU.js b/src/helpers/devices/getNextMCU.js index fc246abe..9023d27b 100644 --- a/src/helpers/devices/getNextMCU.js +++ b/src/helpers/devices/getNextMCU.js @@ -1,5 +1,5 @@ // @flow -import axios from 'axios' +import network from 'api/network' import { GET_NEXT_MCU } from 'helpers/urls' import createCustomErrorClass from 'helpers/createCustomErrorClass' @@ -8,8 +8,12 @@ const LatestMCUInstalledError = createCustomErrorClass('LatestMCUInstalledError' export default async (bootloaderVersion: string): Promise<*> => { try { - const { data } = await axios.post(GET_NEXT_MCU, { - bootloader_version: bootloaderVersion, + const { data } = await network({ + method: 'POST', + url: GET_NEXT_MCU, + data: { + bootloader_version: bootloaderVersion, + }, }) // FIXME: nextVersion will not be able to "default" when Error diff --git a/src/helpers/devices/getOsuFirmware.js b/src/helpers/devices/getOsuFirmware.js index 8ee45576..eaf8344f 100644 --- a/src/helpers/devices/getOsuFirmware.js +++ b/src/helpers/devices/getOsuFirmware.js @@ -1,5 +1,5 @@ // @flow -import axios from 'axios' +import network from 'api/network' import { GET_CURRENT_OSU } from 'helpers/urls' @@ -8,19 +8,16 @@ type Input = { deviceId: string | number, } -let error export default async (input: Input): Promise<*> => { - try { - const provider = 1 - const { data } = await axios.post(GET_CURRENT_OSU, { + const provider = 1 + const { data } = await network({ + method: 'POST', + url: GET_CURRENT_OSU, + data: { device_version: input.deviceId, version_name: input.version, provider, - }) - return data - } catch (err) { - error = Error(err.message) - error.stack = err.stack - throw error - } + }, + }) + return data } diff --git a/src/helpers/firmware/getFinalFirmwareById.js b/src/helpers/firmware/getFinalFirmwareById.js index ec73461f..d3296a9c 100644 --- a/src/helpers/firmware/getFinalFirmwareById.js +++ b/src/helpers/firmware/getFinalFirmwareById.js @@ -1,15 +1,8 @@ // @flow -import axios from 'axios' - +import network from 'api/network' import { GET_FINAL_FIRMWARE } from 'helpers/urls' export default async (id: number) => { - try { - const { data } = await axios.get(`${GET_FINAL_FIRMWARE}/${id}`) - return data - } catch (err) { - const error = Error(err.message) - error.stack = err.stack - throw err - } + const { data } = await network({ method: 'GET', url: `${GET_FINAL_FIRMWARE}/${id}` }) + return data } diff --git a/src/helpers/firmware/installFinalFirmware.js b/src/helpers/firmware/installFinalFirmware.js index 365a37d6..f2c3c885 100644 --- a/src/helpers/firmware/installFinalFirmware.js +++ b/src/helpers/firmware/installFinalFirmware.js @@ -30,9 +30,7 @@ export default async (transport: Transport<*>, app: Input): Result => { const url = WS_INSTALL(params) await createDeviceSocket(transport, url).toPromise() return { success: true } - } catch (err) { - const error = Error(err.message) - error.stack = err.stack + } catch (error) { const result = { success: false, error } throw result } diff --git a/src/helpers/firmware/installMcu.js b/src/helpers/firmware/installMcu.js index 0c0ceb62..44ae9c37 100644 --- a/src/helpers/firmware/installMcu.js +++ b/src/helpers/firmware/installMcu.js @@ -13,7 +13,6 @@ export default async ( ): Result => { const { version } = args const nextVersion = await getNextMCU(version) - const params = { targetId: args.targetId, version: nextVersion.name, diff --git a/src/helpers/firmware/installOsuFirmware.js b/src/helpers/firmware/installOsuFirmware.js index 5c8b95ff..5995b7ae 100644 --- a/src/helpers/firmware/installOsuFirmware.js +++ b/src/helpers/firmware/installOsuFirmware.js @@ -22,9 +22,7 @@ export default async ( const url = WS_INSTALL(params) await createDeviceSocket(transport, url).toPromise() return { success: true } - } catch (err) { - const error = Error(err.message) - error.stack = err.stack + } catch (error) { const result = { success: false, error } throw result } diff --git a/src/icons/Trash.js b/src/icons/Trash.js index a6630967..992d7349 100644 --- a/src/icons/Trash.js +++ b/src/icons/Trash.js @@ -5,6 +5,7 @@ import React from 'react' const path = ( diff --git a/static/i18n/en/errors.yml b/static/i18n/en/errors.yml index 768cd711..9e24e1f5 100644 --- a/static/i18n/en/errors.yml +++ b/static/i18n/en/errors.yml @@ -16,8 +16,10 @@ NoAddressesFound: 'No accounts found' UserRefusedOnDevice: Transaction have been aborted WebsocketConnectionError: An error occurred with the socket connection WebsocketConnectionFailed: Failed to establish a socket connection -DeviceSocketFail: Device socket failure +DeviceSocketFail: 'Device socket failure ({{message}})' DeviceSocketNoBulkStatus: Device socket failure (bulk) DeviceSocketNoHandler: Device socket failure (handler {{query}}) LatestMCUInstalledError: The latest MCU is already installed on the Device HardResetFail: Hard reset failure +CannotUninstall: Cannot uninstall app +CannotInstall: Cannot install app