Browse Source

Merge pull request #1718 from LedgerHQ/develop

Prepare for 1.2.8
master
Gaëtan Renaudeau 6 years ago
committed by GitHub
parent
commit
58f1498f74
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      .flowconfig
  2. 19
      package.json
  3. 2
      src/api/Ethereum.js
  4. 2
      src/api/Fees.js
  5. 6
      src/api/network.js
  6. 2
      src/bridge/EthereumJSBridge.js
  7. 2
      src/bridge/LibcoreBridge.js
  8. 11
      src/bridge/RippleJSBridge.js
  9. BIN
      src/commands/.DS_Store
  10. 12
      src/commands/debugAppInfosForCurrency.js
  11. 17
      src/commands/firmwareMain.js
  12. 18
      src/commands/firmwarePrepare.js
  13. 14
      src/commands/firmwareRepair.js
  14. 25
      src/commands/getAddress.js
  15. 21
      src/commands/getCurrentFirmware.js
  16. 11
      src/commands/getDeviceInfo.js
  17. 16
      src/commands/getIsGenuine.js
  18. 13
      src/commands/getLatestFirmwareForDevice.js
  19. 19
      src/commands/getMemInfo.js
  20. 26
      src/commands/index.js
  21. 20
      src/commands/installApp.js
  22. 21
      src/commands/installFinalFirmware.js
  23. 19
      src/commands/installMcu.js
  24. 27
      src/commands/installOsuFirmware.js
  25. 19
      src/commands/isDashboardOpen.js
  26. 5
      src/commands/libcoreGetFees.js
  27. 45
      src/commands/libcoreSignAndBroadcast.js
  28. 15
      src/commands/listAppVersions.js
  29. 15
      src/commands/listApps.js
  30. 17
      src/commands/listCategories.js
  31. 15
      src/commands/shouldFlashMcu.js
  32. 10
      src/commands/signTransaction.js
  33. 15
      src/commands/testApdu.js
  34. 18
      src/commands/uninstallApp.js
  35. 1
      src/components/App.js
  36. 6
      src/components/EnsureDeviceApp.js
  37. 13
      src/components/ExportLogsBtn.js
  38. 4
      src/components/FeesField/BitcoinKind.js
  39. 2
      src/components/FeesField/EthereumKind.js
  40. 2
      src/components/FeesField/RippleKind.js
  41. 27
      src/components/GenuineCheck.js
  42. 171
      src/components/HSMStatusBanner.js
  43. 2
      src/components/IsUnlocked.js
  44. 2
      src/components/ManagerPage/AppSearchBar.js
  45. 116
      src/components/ManagerPage/AppsList.js
  46. 2
      src/components/ManagerPage/Dashboard.js
  47. 85
      src/components/ManagerPage/FirmwareUpdate.js
  48. 13
      src/components/ManagerPage/UpdateFirmwareButton.js
  49. 2
      src/components/ManagerPage/index.js
  50. 64
      src/components/ProgressBar/index.js
  51. 16
      src/components/ProgressBar/stories.js
  52. 98
      src/components/ProgressCircle/index.js
  53. 16
      src/components/ProgressCircle/stories.js
  54. 2
      src/components/SettingsPage/DisablePasswordModal.js
  55. 2
      src/components/SettingsPage/PasswordForm.js
  56. 2
      src/components/SettingsPage/PasswordModal.js
  57. 103
      src/components/SettingsPage/RepairDeviceButton.js
  58. 10
      src/components/SettingsPage/sections/Help.js
  59. 4
      src/components/base/Modal/ConfirmModal.js
  60. 203
      src/components/base/Modal/RepairModal.js
  61. 1
      src/components/base/Modal/index.js
  62. 2
      src/components/layout/Default.js
  63. 2
      src/components/modals/AccountSettingRenderBody.js
  64. 12
      src/components/modals/OperationDetails.js
  65. 19
      src/components/modals/Receive/steps/04-step-receive-funds.js
  66. 4
      src/components/modals/Send/fields/RecipientField.js
  67. 2
      src/components/modals/Send/index.js
  68. 34
      src/components/modals/UpdateFirmware/Disclaimer.js
  69. 10
      src/components/modals/UpdateFirmware/Installing.js
  70. 35
      src/components/modals/UpdateFirmware/index.js
  71. 115
      src/components/modals/UpdateFirmware/steps/01-step-install-full-firmware.js
  72. 117
      src/components/modals/UpdateFirmware/steps/02-step-flash-mcu.js
  73. 7
      src/config/constants.js
  74. 53
      src/config/errors.js
  75. 52
      src/helpers/apps/installApp.js
  76. 30
      src/helpers/apps/listAppVersions.js
  77. 10
      src/helpers/apps/listApps.js
  78. 10
      src/helpers/apps/listCategories.js
  79. 41
      src/helpers/apps/uninstallApp.js
  80. 5
      src/helpers/db/db.spec.js
  81. 6
      src/helpers/db/index.js
  82. 9
      src/helpers/debugAppInfosForCurrency/btc.js
  83. 10
      src/helpers/debugAppInfosForCurrency/ethereum.js
  84. 29
      src/helpers/debugAppInfosForCurrency/index.js
  85. 10
      src/helpers/debugAppInfosForCurrency/ripple.js
  86. 64
      src/helpers/deviceAccess.js
  87. 21
      src/helpers/devices/getBitcoinLikeInfo.js
  88. 24
      src/helpers/devices/getCurrentFirmware.js
  89. 42
      src/helpers/devices/getDeviceInfo.js
  90. 17
      src/helpers/devices/getDeviceVersion.js
  91. 32
      src/helpers/devices/getIsGenuine.js
  92. 76
      src/helpers/devices/getLatestFirmwareForDevice.js
  93. 10
      src/helpers/devices/getMemInfo.js
  94. 23
      src/helpers/devices/getOsuFirmware.js
  95. 16
      src/helpers/devices/isDashboardOpen.js
  96. 49
      src/helpers/devices/shouldFlashMcu.js
  97. 96
      src/helpers/errors.js
  98. 8
      src/helpers/firmware/getFinalFirmwareById.js
  99. 51
      src/helpers/firmware/getFirmwareInfo.js
  100. 13
      src/helpers/firmware/getMcus.js

1
.flowconfig

@ -5,6 +5,7 @@
[untyped]
.*/node_modules/react-select
<PROJECT_ROOT>/node_modules/qrloop/lib/Buffer.js*
[include]

19
package.json

@ -35,13 +35,13 @@
}
},
"dependencies": {
"@ledgerhq/hw-app-btc": "^v4.30.0",
"@ledgerhq/hw-app-eth": "^4.24.0",
"@ledgerhq/hw-app-xrp": "^4.25.0",
"@ledgerhq/hw-transport": "^4.24.0",
"@ledgerhq/hw-transport-node-hid": "4.24.0",
"@ledgerhq/ledger-core": "2.0.0-rc.12",
"@ledgerhq/live-common": "4.6.0",
"@ledgerhq/hw-app-btc": "^4.32.0",
"@ledgerhq/hw-app-eth": "^4.32.0",
"@ledgerhq/hw-app-xrp": "^4.32.0",
"@ledgerhq/hw-transport": "^4.32.0",
"@ledgerhq/hw-transport-node-hid": "^4.32.0",
"@ledgerhq/ledger-core": "2.0.0-rc.14",
"@ledgerhq/live-common": "4.8.0-beta.23",
"animated": "^0.2.2",
"async": "^2.6.1",
"axios": "^0.18.0",
@ -109,7 +109,6 @@
"uncontrollable": "^6.0.0",
"uuid": "^3.2.1",
"winston": "^3.0.0",
"winston-daily-rotate-file": "^3.2.3",
"winston-transport": "^4.2.0",
"write-file-atomic": "^2.3.0",
"ws": "^5.1.1",
@ -186,8 +185,8 @@
"yaml-loader": "^0.5.0"
},
"engines": {
"node": ">=8.9.0 <=8.12.0",
"node": ">=8.9.0 <=8.14.0",
"yarn": "^1.10.1"
},
"private": true
}
}

2
src/api/Ethereum.js

@ -1,7 +1,7 @@
// @flow
import type { CryptoCurrency } from '@ledgerhq/live-common/lib/types'
import { BigNumber } from 'bignumber.js'
import { LedgerAPINotAvailable } from 'config/errors'
import { LedgerAPINotAvailable } from '@ledgerhq/live-common/lib/errors'
import network from './network'
import { blockchainBaseURL } from './Ledger'

2
src/api/Fees.js

@ -2,7 +2,7 @@
import invariant from 'invariant'
import LRU from 'lru-cache'
import type { Currency } from '@ledgerhq/live-common/lib/types'
import { FeeEstimationFailed } from 'config/errors'
import { FeeEstimationFailed } from '@ledgerhq/live-common/lib/errors'
import { blockchainBaseURL } from './Ledger'
import network from './network'

6
src/api/network.js

@ -3,7 +3,11 @@ import axios from 'axios'
import { GET_CALLS_RETRY, GET_CALLS_TIMEOUT } from 'config/constants'
import { retry } from 'helpers/promise'
import logger from 'logger'
import { LedgerAPIErrorWithMessage, LedgerAPIError, NetworkDown } from 'config/errors'
import {
LedgerAPIErrorWithMessage,
LedgerAPIError,
NetworkDown,
} from '@ledgerhq/live-common/lib/errors'
import anonymizer from 'helpers/anonymizer'
const userFriendlyError = <A>(p: Promise<A>, { url, method, startTime, ...rest }): Promise<A> =>

2
src/bridge/EthereumJSBridge.js

@ -25,7 +25,7 @@ import { apiForCurrency } from 'api/Ethereum'
import type { Tx } from 'api/Ethereum'
import getAddressCommand from 'commands/getAddress'
import signTransactionCommand from 'commands/signTransaction'
import { NotEnoughBalance, FeeNotLoaded, ETHAddressNonEIP } from 'config/errors'
import { NotEnoughBalance, FeeNotLoaded, ETHAddressNonEIP } from '@ledgerhq/live-common/lib/errors'
import type { EditProps, WalletBridge } from './types'
type Transaction = {

2
src/bridge/LibcoreBridge.js

@ -11,7 +11,7 @@ import libcoreSyncAccount from 'commands/libcoreSyncAccount'
import libcoreSignAndBroadcast from 'commands/libcoreSignAndBroadcast'
import libcoreGetFees, { extractGetFeesInputFromAccount } from 'commands/libcoreGetFees'
import libcoreValidAddress from 'commands/libcoreValidAddress'
import { NotEnoughBalance, FeeNotLoaded } from 'config/errors'
import { NotEnoughBalance, FeeNotLoaded } from '@ledgerhq/live-common/lib/errors'
import type { WalletBridge, EditProps } from './types'
const NOT_ENOUGH_FUNDS = 52

11
src/bridge/RippleJSBridge.js

@ -32,7 +32,7 @@ import {
NotEnoughBalance,
FeeNotLoaded,
NotEnoughBalanceBecauseDestinationNotCreated,
} from 'config/errors'
} from '@ledgerhq/live-common/lib/errors'
import type { WalletBridge, EditProps } from './types'
type Transaction = {
@ -123,6 +123,10 @@ async function signAndBroadcast({ a, t, deviceId, isCancelled, onSigned, onOpera
a.pendingOperations.length,
extra: {},
}
if (t.tag) {
op.extra.tag = t.tag
}
onOperationBroadcasted(op)
}
} finally {
@ -164,6 +168,7 @@ type Tx = {
currency: string,
value: string,
},
tag?: string,
},
paths: string,
},
@ -235,6 +240,10 @@ const txToOperation = (account: Account) => ({
transactionSequenceNumber: sequence,
extra: {},
}
if (destination.tag) {
op.extra.tag = destination.tag
}
return op
}

BIN
src/commands/.DS_Store

Binary file not shown.

12
src/commands/debugAppInfosForCurrency.js

@ -1,10 +1,10 @@
// @flow
import { getCryptoCurrencyById } from '@ledgerhq/live-common/lib/currencies'
import debugAppInfosForCurrency from '@ledgerhq/live-common/lib/hw/debugAppInfosForCurrency'
import { createCommand, Command } from 'helpers/ipc'
import { fromPromise } from 'rxjs/observable/fromPromise'
import { withDevice } from 'helpers/deviceAccess'
import debugAppInfosForCurrency from 'helpers/debugAppInfosForCurrency'
import { from } from 'rxjs'
import { withDevice } from '@ledgerhq/live-common/lib/hw/deviceAccess'
type Input = {
currencyId: string,
@ -18,10 +18,8 @@ type Result = {
const cmd: Command<Input, Result> = createCommand(
'debugAppInfosForCurrency',
({ currencyId, devicePath }) =>
fromPromise(
withDevice(devicePath)(transport =>
debugAppInfosForCurrency(transport, getCryptoCurrencyById(currencyId)),
),
withDevice(devicePath)(transport =>
from(debugAppInfosForCurrency(transport, getCryptoCurrencyById(currencyId))),
),
)

17
src/commands/firmwareMain.js

@ -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

18
src/commands/firmwarePrepare.js

@ -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

14
src/commands/firmwareRepair.js

@ -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

25
src/commands/getAddress.js

@ -2,18 +2,15 @@
import { getCryptoCurrencyById } from '@ledgerhq/live-common/lib/currencies'
import { createCommand, Command } from 'helpers/ipc'
import { fromPromise } from 'rxjs/observable/fromPromise'
import { withDevice } from 'helpers/deviceAccess'
import getAddressForCurrency from 'helpers/getAddressForCurrency'
import { DeviceAppVerifyNotSupported, UserRefusedAddress } from 'config/errors'
import { from } from 'rxjs'
import { withDevice } from '@ledgerhq/live-common/lib/hw/deviceAccess'
import getAddress from '@ledgerhq/live-common/lib/hw/getAddress'
type Input = {
currencyId: string,
devicePath: string,
path: string,
verify?: boolean,
segwit?: boolean,
}
type Result = {
@ -25,20 +22,8 @@ type Result = {
const cmd: Command<Input, Result> = createCommand(
'getAddress',
({ currencyId, devicePath, path, ...options }) =>
fromPromise(
withDevice(devicePath)(transport =>
getAddressForCurrency(transport, getCryptoCurrencyById(currencyId), path, options),
).catch(e => {
if (e && e.name === 'TransportStatusError') {
if (e.statusCode === 0x6b00 && options.verify) {
throw new DeviceAppVerifyNotSupported()
}
if (e.statusCode === 0x6985) {
throw new UserRefusedAddress()
}
}
throw e
}),
withDevice(devicePath)(transport =>
from(getAddress(transport, getCryptoCurrencyById(currencyId), path, options.verify)),
),
)

21
src/commands/getCurrentFirmware.js

@ -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

11
src/commands/getDeviceInfo.js

@ -1,11 +1,10 @@
// @flow
import { createCommand, Command } from 'helpers/ipc'
import { fromPromise } from 'rxjs/observable/fromPromise'
import { withDevice } from 'helpers/deviceAccess'
import getDeviceInfo from 'helpers/devices/getDeviceInfo'
import type { DeviceInfo } from 'helpers/types'
import { from } from 'rxjs'
import { withDevice } from '@ledgerhq/live-common/lib/hw/deviceAccess'
import getDeviceInfo from '@ledgerhq/live-common/lib/hw/getDeviceInfo'
import type { DeviceInfo } from '@ledgerhq/live-common/lib/types/manager'
type Input = {
devicePath: string,
@ -14,7 +13,7 @@ type Input = {
type Result = DeviceInfo
const cmd: Command<Input, Result> = createCommand('getDeviceInfo', ({ devicePath }) =>
fromPromise(withDevice(devicePath)(transport => getDeviceInfo(transport))),
withDevice(devicePath)(transport => from(getDeviceInfo(transport))),
)
export default cmd

16
src/commands/getIsGenuine.js

@ -1,11 +1,12 @@
// @flow
import { createCommand, Command } from 'helpers/ipc'
import { fromPromise } from 'rxjs/observable/fromPromise'
import type { DeviceInfo } from 'helpers/types'
import getIsGenuine from 'helpers/devices/getIsGenuine'
import { withDevice } from 'helpers/deviceAccess'
import { of } from 'rxjs'
import { delay } from 'rxjs/operators'
import genuineCheck from '@ledgerhq/live-common/lib/hw/genuineCheck'
import { withDevice } from '@ledgerhq/live-common/lib/hw/deviceAccess'
import type { DeviceInfo } from '@ledgerhq/live-common/lib/types/manager'
import { SKIP_GENUINE } from 'config/constants'
type Input = {
devicePath: string,
@ -14,7 +15,10 @@ type Input = {
type Result = string
const cmd: Command<Input, Result> = createCommand('getIsGenuine', ({ devicePath, deviceInfo }) =>
fromPromise(withDevice(devicePath)(transport => getIsGenuine(transport, deviceInfo))),
withDevice(devicePath)(
transport =>
SKIP_GENUINE ? of('0000').pipe(delay(1000)) : genuineCheck(transport, deviceInfo),
),
)
export default cmd

13
src/commands/getLatestFirmwareForDevice.js

@ -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

19
src/commands/getMemInfo.js

@ -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

26
src/commands/index.js

@ -4,17 +4,14 @@ import invariant from 'invariant'
import type { Command } from 'helpers/ipc'
import debugAppInfosForCurrency from 'commands/debugAppInfosForCurrency'
import firmwarePrepare from 'commands/firmwarePrepare'
import firmwareMain from 'commands/firmwareMain'
import firmwareRepair from 'commands/firmwareRepair'
import getAddress from 'commands/getAddress'
import getDeviceInfo from 'commands/getDeviceInfo'
import getCurrentFirmware from 'commands/getCurrentFirmware'
import getIsGenuine from 'commands/getIsGenuine'
import getLatestFirmwareForDevice from 'commands/getLatestFirmwareForDevice'
import getMemInfo from 'commands/getMemInfo'
import installApp from 'commands/installApp'
import installFinalFirmware from 'commands/installFinalFirmware'
import installMcu from 'commands/installMcu'
import installOsuFirmware from 'commands/installOsuFirmware'
import isDashboardOpen from 'commands/isDashboardOpen'
import killInternalProcess from 'commands/killInternalProcess'
import libcoreGetFees from 'commands/libcoreGetFees'
import libcoreGetVersion from 'commands/libcoreGetVersion'
@ -23,12 +20,8 @@ import libcoreScanFromXPUB from 'commands/libcoreScanFromXPUB'
import libcoreSignAndBroadcast from 'commands/libcoreSignAndBroadcast'
import libcoreSyncAccount from 'commands/libcoreSyncAccount'
import libcoreValidAddress from 'commands/libcoreValidAddress'
import listApps from 'commands/listApps'
import listAppVersions from 'commands/listAppVersions'
import listCategories from 'commands/listCategories'
import listenDevices from 'commands/listenDevices'
import ping from 'commands/ping'
import shouldFlashMcu from 'commands/shouldFlashMcu'
import signTransaction from 'commands/signTransaction'
import testApdu from 'commands/testApdu'
import testCrash from 'commands/testCrash'
@ -37,17 +30,14 @@ import uninstallApp from 'commands/uninstallApp'
const all: Array<Command<any, any>> = [
debugAppInfosForCurrency,
firmwarePrepare,
firmwareMain,
firmwareRepair,
getAddress,
getDeviceInfo,
getCurrentFirmware,
getIsGenuine,
getLatestFirmwareForDevice,
getMemInfo,
installApp,
installFinalFirmware,
installMcu,
installOsuFirmware,
isDashboardOpen,
killInternalProcess,
libcoreGetFees,
libcoreGetVersion,
@ -56,12 +46,8 @@ const all: Array<Command<any, any>> = [
libcoreSignAndBroadcast,
libcoreSyncAccount,
libcoreValidAddress,
listApps,
listAppVersions,
listCategories,
listenDevices,
ping,
shouldFlashMcu,
signTransaction,
testApdu,
testCrash,

20
src/commands/installApp.js

@ -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

21
src/commands/installFinalFirmware.js

@ -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

19
src/commands/installMcu.js

@ -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

27
src/commands/installOsuFirmware.js

@ -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

19
src/commands/isDashboardOpen.js

@ -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

5
src/commands/libcoreGetFees.js

@ -13,7 +13,7 @@ import {
bigNumberToLibcoreAmount,
getOrCreateWallet,
} from 'helpers/libcore'
import { InvalidAddress } from 'config/errors'
import { InvalidAddress } from '@ledgerhq/live-common/lib/errors'
type BitcoinLikeTransaction = {
// TODO we rename this Transaction concept into transactionInput
@ -75,7 +75,8 @@ const cmd: Command<Input, Result> = createCommand(
njsWalletCurrency,
BigNumber(transaction.feePerByte),
)
const transactionBuilder = bitcoinLikeAccount.buildTransaction()
const isPartial = true
const transactionBuilder = bitcoinLikeAccount.buildTransaction(isPartial)
if (!isValidAddress(core, njsWalletCurrency, transaction.recipient)) {
// FIXME this is a bug in libcore. later it will probably check this and we can remove this check
throw new InvalidAddress()

45
src/commands/libcoreSignAndBroadcast.js

@ -4,7 +4,7 @@ import logger from 'logger'
import { BigNumber } from 'bignumber.js'
import { StatusCodes } from '@ledgerhq/hw-transport'
import Btc from '@ledgerhq/hw-app-btc'
import { Observable } from 'rxjs'
import { Observable, from } from 'rxjs'
import { isSegwitDerivationMode } from '@ledgerhq/live-common/lib/derivation'
import { getCryptoCurrencyById } from '@ledgerhq/live-common/lib/currencies'
import type { OperationRaw, DerivationMode, CryptoCurrency } from '@ledgerhq/live-common/lib/types'
@ -14,11 +14,11 @@ import {
bigNumberToLibcoreAmount,
getOrCreateWallet,
} from 'helpers/libcore'
import { UpdateYourApp } from 'config/errors'
import { UpdateYourApp } from '@ledgerhq/live-common/lib/errors'
import withLibcore from 'helpers/withLibcore'
import { createCommand, Command } from 'helpers/ipc'
import { withDevice } from 'helpers/deviceAccess'
import { withDevice } from '@ledgerhq/live-common/lib/hw/deviceAccess'
type BitcoinLikeTransaction = {
amount: string,
@ -227,7 +227,8 @@ export async function doSignAndBroadcast({
const njsWalletCurrency = njsWallet.getCurrency()
const amount = bigNumberToLibcoreAmount(core, njsWalletCurrency, BigNumber(transaction.amount))
const fees = bigNumberToLibcoreAmount(core, njsWalletCurrency, BigNumber(transaction.feePerByte))
const transactionBuilder = bitcoinLikeAccount.buildTransaction()
const isPartial = false
const transactionBuilder = bitcoinLikeAccount.buildTransaction(isPartial)
// TODO: check if is valid address. if not, it will fail silently on invalid
@ -245,22 +246,26 @@ export async function doSignAndBroadcast({
const hasTimestamp = !!njsWalletCurrency.bitcoinLikeNetworkParameters.UsesTimestampedTransaction
// TODO: const timestampDelay = njsWalletCurrency.bitcoinLikeNetworkParameters.TimestampDelay
const signedTransaction = await withDevice(deviceId)(async transport =>
signTransaction({
hwApp: new Btc(transport),
currency,
blockHeight,
transaction: builded,
sigHashType: parseInt(sigHashType, 16),
hasTimestamp,
derivationMode,
}),
).catch(e => {
if (e && e.statusCode === StatusCodes.INCORRECT_P1_P2) {
throw new UpdateYourApp(`UpdateYourApp ${currency.id}`, currency)
}
throw e
})
const signedTransaction = await withDevice(deviceId)(transport =>
from(
signTransaction({
hwApp: new Btc(transport),
currency,
blockHeight,
transaction: builded,
sigHashType: parseInt(sigHashType, 16),
hasTimestamp,
derivationMode,
}),
),
)
.toPromise()
.catch(e => {
if (e && e.statusCode === StatusCodes.INCORRECT_P1_P2) {
throw new UpdateYourApp(`UpdateYourApp ${currency.id}`, currency)
}
throw e
})
if (!signedTransaction || isCancelled() || !njsAccount) return
onSigned()

15
src/commands/listAppVersions.js

@ -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

15
src/commands/listApps.js

@ -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

17
src/commands/listCategories.js

@ -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

15
src/commands/shouldFlashMcu.js

@ -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

10
src/commands/signTransaction.js

@ -1,8 +1,8 @@
// @flow
import { createCommand, Command } from 'helpers/ipc'
import { fromPromise } from 'rxjs/observable/fromPromise'
import { withDevice } from 'helpers/deviceAccess'
import { from } from 'rxjs'
import { withDevice } from '@ledgerhq/live-common/lib/hw/deviceAccess'
import signTransactionForCurrency from 'helpers/signTransactionForCurrency'
type Input = {
@ -17,10 +17,8 @@ type Result = string
const cmd: Command<Input, Result> = createCommand(
'signTransaction',
({ currencyId, devicePath, path, transaction }) =>
fromPromise(
withDevice(devicePath)(transport =>
signTransactionForCurrency(currencyId)(transport, currencyId, path, transaction),
),
withDevice(devicePath)(transport =>
from(signTransactionForCurrency(currencyId)(transport, currencyId, path, transaction)),
),
)

15
src/commands/testApdu.js

@ -3,8 +3,8 @@
// This is a test example for dev testing purpose.
import { createCommand, Command } from 'helpers/ipc'
import { fromPromise } from 'rxjs/observable/fromPromise'
import { withDevice } from 'helpers/deviceAccess'
import { from } from 'rxjs'
import { withDevice } from '@ledgerhq/live-common/lib/hw/deviceAccess'
type Input = {
devicePath: string,
@ -15,11 +15,12 @@ type Result = {
}
const cmd: Command<Input, Result> = createCommand('testApdu', ({ apduHex, devicePath }) =>
fromPromise(
withDevice(devicePath)(async transport => {
const res = await transport.exchange(Buffer.from(apduHex, 'hex'))
return { responseHex: res.toString('hex') }
}),
withDevice(devicePath)(transport =>
from(
transport
.exchange(Buffer.from(apduHex, 'hex'))
.then(res => ({ responseHex: res.toString('hex') })),
),
),
)

18
src/commands/uninstallApp.js

@ -1,12 +1,8 @@
// @flow
import { createCommand, Command } from 'helpers/ipc'
import { fromPromise } from 'rxjs/observable/fromPromise'
import { withDevice } from 'helpers/deviceAccess'
import uninstallApp from 'helpers/apps/uninstallApp'
import type { ApplicationVersion } from 'helpers/types'
import { withDevice } from '@ledgerhq/live-common/lib/hw/deviceAccess'
import uninstallApp from '@ledgerhq/live-common/lib/hw/uninstallApp'
import type { ApplicationVersion } from '@ledgerhq/live-common/lib/types/manager'
type Input = {
app: ApplicationVersion,
@ -14,12 +10,10 @@ type Input = {
targetId: string | number,
}
type Result = void
type Result = *
const cmd: Command<Input, Result> = createCommand(
'uninstallApp',
({ devicePath, targetId, ...app }) =>
fromPromise(withDevice(devicePath)(transport => uninstallApp(transport, targetId, app))),
const cmd: Command<Input, Result> = createCommand('uninstallApp', ({ devicePath, targetId, app }) =>
withDevice(devicePath)(transport => uninstallApp(transport, targetId, app)),
)
export default cmd

1
src/components/App.js

@ -45,4 +45,5 @@ const App = ({
</Provider>
)
// $FlowFixMe
export default hot(module)(App)

6
src/components/EnsureDeviceApp.js

@ -23,7 +23,11 @@ import IconUsb from 'icons/Usb'
import type { Device } from 'types/common'
import { WrongDeviceForAccount, CantOpenDevice, UpdateYourApp } from 'config/errors'
import {
WrongDeviceForAccount,
CantOpenDevice,
UpdateYourApp,
} from '@ledgerhq/live-common/lib/errors'
import { getCurrentDevice } from 'reducers/devices'
const usbIcon = <IconUsb size={16} />

13
src/components/ExportLogsBtn.js

@ -2,12 +2,20 @@
import logger from 'logger'
import moment from 'moment'
import fs from 'fs'
import { webFrame, remote } from 'electron'
import { ipcRenderer, webFrame, remote } from 'electron'
import React, { Component } from 'react'
import { translate } from 'react-i18next'
import KeyHandler from 'react-key-handler'
import Button from './base/Button'
const queryLogs = () =>
new Promise(success => {
ipcRenderer.once('logs', (event: any, { logs }) => {
success(logs)
})
ipcRenderer.send('queryLogs')
})
function writeToFile(file, data) {
return new Promise((resolve, reject) => {
fs.writeFile(file, data, error => {
@ -33,7 +41,6 @@ class ExportLogsBtn extends Component<{
environment: __DEV__ ? 'development' : 'production',
userAgent: window.navigator.userAgent,
})
const date = new Date() // we don't want all the logs that happen after the Export was pressed ^^
const path = remote.dialog.showSaveDialog({
title: 'Export logs',
defaultPath: `ledgerlive-export-${moment().format(
@ -47,7 +54,7 @@ class ExportLogsBtn extends Component<{
],
})
if (path) {
const logs = await logger.queryAllLogs(date)
const logs = await queryLogs()
const json = JSON.stringify(logs)
await writeToFile(path, json)
}

4
src/components/FeesField/BitcoinKind.js

@ -8,7 +8,7 @@ import { translate } from 'react-i18next'
import type { T } from 'types/common'
import { FeeNotLoaded } from 'config/errors'
import { FeeNotLoaded } from '@ledgerhq/live-common/lib/errors'
import InputCurrency from 'components/base/InputCurrency'
import Select from 'components/base/Select'
import type { Fees } from 'api/Fees'
@ -89,7 +89,7 @@ class FeesField extends Component<OwnProps, State> {
}
items.push(!feePerByte && !error ? notLoadedItem : customItem)
const selectedItem =
!feePerByte && prevState.selectedItem.feePerByte.eq(feePerByte)
feePerByte && prevState.selectedItem.feePerByte.eq(feePerByte)
? prevState.selectedItem
: items.find(f => f.feePerByte.eq(feePerByte)) || items[items.length - 1]
return { items, selectedItem }

2
src/components/FeesField/EthereumKind.js

@ -4,7 +4,7 @@ import React, { Component } from 'react'
import { BigNumber } from 'bignumber.js'
import type { Account } from '@ledgerhq/live-common/lib/types'
import { FeeNotLoaded } from 'config/errors'
import { FeeNotLoaded } from '@ledgerhq/live-common/lib/errors'
import InputCurrency from 'components/base/InputCurrency'
import type { Fees } from 'api/Fees'
import WithFeesAPI from '../WithFeesAPI'

2
src/components/FeesField/RippleKind.js

@ -4,7 +4,7 @@ import React, { Component } from 'react'
import type { BigNumber } from 'bignumber.js'
import type { Account } from '@ledgerhq/live-common/lib/types'
import { apiForEndpointConfig, parseAPIValue } from 'api/Ripple'
import { FeeNotLoaded } from 'config/errors'
import { FeeNotLoaded } from '@ledgerhq/live-common/lib/errors'
import InputCurrency from 'components/base/InputCurrency'
import GenericContainer from './GenericContainer'

27
src/components/GenuineCheck.js

@ -9,12 +9,18 @@ import { delay, createCancelablePolling } from 'helpers/promise'
import logger from 'logger'
import type { T, Device } from 'types/common'
import type { DeviceInfo } from 'helpers/types'
import manager from '@ledgerhq/live-common/lib/manager'
import type { DeviceInfo } from '@ledgerhq/live-common/lib/types/manager'
import { GENUINE_TIMEOUT, DEVICE_INFOS_TIMEOUT, GENUINE_CACHE_DELAY } from 'config/constants'
import { getCurrentDevice } from 'reducers/devices'
import { CantOpenDevice, DeviceNotGenuineError, DeviceGenuineSocketEarlyClose } from 'config/errors'
import {
CantOpenDevice,
DeviceNotGenuineError,
DeviceGenuineSocketEarlyClose,
UnexpectedBootloader,
} from '@ledgerhq/live-common/lib/errors'
import getDeviceInfo from 'commands/getDeviceInfo'
import getIsGenuine from 'commands/getIsGenuine'
@ -77,11 +83,26 @@ class GenuineCheck extends PureComponent<Props> {
device: Device,
deviceInfo: DeviceInfo,
}) => {
if (deviceInfo.isOSU || deviceInfo.isBootloader) {
if (deviceInfo.isBootloader) {
logger.log('device is in bootloader mode')
throw new UnexpectedBootloader()
}
if (deviceInfo.isOSU) {
logger.log('device is in update mode. skipping genuine')
return true
}
// Preload things in parallel
Promise.all([
// Step dashboard, we preload the applist before entering manager while we're still doing the genuine check
manager.getAppsList(deviceInfo),
// we also preload as much info as possible in case of a MCU
manager.getLatestFirmwareForDevice(deviceInfo),
]).catch(e => {
logger.warn(e)
})
if (genuineDevices.has(device)) {
logger.log("genuine was already checked. don't check again")
await delay(GENUINE_CACHE_DELAY)

171
src/components/HSMStatusBanner.js

@ -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)

2
src/components/IsUnlocked.js

@ -17,7 +17,7 @@ import { hardReset } from 'helpers/reset'
import { fetchAccounts } from 'actions/accounts'
import { isLocked, unlock } from 'reducers/application'
import { PasswordIncorrectError } from 'config/errors'
import { PasswordIncorrectError } from '@ledgerhq/live-common/lib/errors'
import Box from 'components/base/Box'
import InputPassword from 'components/base/InputPassword'

2
src/components/ManagerPage/AppSearchBar.js

@ -3,7 +3,7 @@
import React, { PureComponent, Fragment } from 'react'
import styled from 'styled-components'
import type { ApplicationVersion } from 'helpers/types'
import type { ApplicationVersion } from '@ledgerhq/live-common/lib/types/manager'
import Box from 'components/base/Box'
import Space from 'components/base/Space'

116
src/components/ManagerPage/AppsList.js

@ -6,12 +6,12 @@ import styled from 'styled-components'
import { translate } from 'react-i18next'
import { connect } from 'react-redux'
import { compose } from 'redux'
import type { Device, T } from 'types/common'
import type { ApplicationVersion, DeviceInfo } from 'helpers/types'
import type { ApplicationVersion, DeviceInfo } from '@ledgerhq/live-common/lib/types/manager'
import manager from '@ledgerhq/live-common/lib/manager'
import { getFullListSortedCryptoCurrencies } from 'helpers/countervalues'
import { developerModeSelector } from 'reducers/settings'
import listApps from 'commands/listApps'
import listAppVersions from 'commands/listAppVersions'
import installApp from 'commands/installApp'
import uninstallApp from 'commands/uninstallApp'
import Box from 'components/base/Box'
@ -19,7 +19,7 @@ import Space from 'components/base/Space'
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 ProgressBar from 'components/ProgressBar'
import Spinner from 'components/base/Spinner'
import Button from 'components/base/Button'
import TranslatedError from 'components/TranslatedError'
@ -65,6 +65,7 @@ type State = {
appsLoaded: boolean,
app: string,
mode: Mode,
progress: number,
}
const oldAppsInstallDisabled = ['ZenCash', 'Ripple']
@ -86,6 +87,7 @@ class AppsList extends PureComponent<Props, State> {
appsLoaded: false,
app: '',
mode: 'home',
progress: 0,
}
componentDidMount() {
@ -98,53 +100,15 @@ class AppsList extends PureComponent<Props, State> {
_unmounted = false
prepareAppList = ({ applicationsList, compatibleAppVersionsList, sortedCryptoCurrencies }) => {
const filtered = this.props.isDevMode
? compatibleAppVersionsList.slice(0)
: compatibleAppVersionsList.filter(version => {
const app = applicationsList.find(e => e.id === version.app)
if (app) {
return app.category !== 2
}
return false
})
const sortedCryptoApps = []
// sort by crypto first
sortedCryptoCurrencies.forEach(crypto => {
const app = filtered.find(
item => item.name.toLowerCase() === crypto.managerAppName.toLowerCase(),
)
if (app) {
filtered.splice(filtered.indexOf(app), 1)
sortedCryptoApps.push(app)
}
})
return sortedCryptoApps.concat(filtered)
}
async fetchAppList() {
try {
const { deviceInfo } = this.props
const [
applicationsList,
compatibleAppVersionsList,
sortedCryptoCurrencies,
] = await Promise.all([
listApps.send().toPromise(),
listAppVersions.send(deviceInfo).toPromise(),
getFullListSortedCryptoCurrencies(),
])
const { deviceInfo, isDevMode } = this.props
const filteredAppVersionsList = this.prepareAppList({
applicationsList,
compatibleAppVersionsList,
sortedCryptoCurrencies,
})
try {
const filteredAppVersionsList = await manager.getAppsList(
deviceInfo,
isDevMode,
getFullListSortedCryptoCurrencies,
)
if (!this._unmounted) {
this.setState({
@ -158,41 +122,37 @@ class AppsList extends PureComponent<Props, State> {
}
}
handleInstallApp = (app: ApplicationVersion) => async () => {
this.setState({ status: 'busy', app: app.name, mode: 'installing' })
try {
const {
device: { path: devicePath },
deviceInfo,
} = this.props
const data = { app, devicePath, targetId: deviceInfo.targetId }
await installApp.send(data).toPromise()
this.setState({ status: 'success' })
} catch (err) {
this.setState({ status: 'error', error: err, mode: 'home' })
}
sub: *
runAppScript = (app: ApplicationVersion, mode: *, cmd: *) => {
this.setState({ status: 'busy', app: app.name, mode, progress: 0 })
const {
device: { path: devicePath },
deviceInfo: { targetId },
} = this.props
this.sub = cmd.send({ app, devicePath, targetId }).subscribe({
next: patch => {
this.setState(patch)
},
complete: () => {
this.setState({ status: 'success' })
},
error: error => {
this.setState({ status: 'error', error, app: '', mode: 'home' })
},
})
}
handleUninstallApp = (app: ApplicationVersion) => async () => {
this.setState({ status: 'busy', app: app.name, mode: 'uninstalling' })
try {
const {
device: { path: devicePath },
deviceInfo,
} = this.props
const data = { app, devicePath, targetId: deviceInfo.targetId }
await uninstallApp.send(data).toPromise()
this.setState({ status: 'success' })
} catch (err) {
this.setState({ status: 'error', error: err, app: '', mode: 'home' })
}
}
handleInstallApp = (app: ApplicationVersion) => () =>
this.runAppScript(app, 'installing', installApp)
handleUninstallApp = (app: ApplicationVersion) => () =>
this.runAppScript(app, 'uninstalling', uninstallApp)
handleCloseModal = () => this.setState({ status: 'idle', mode: 'home' })
renderModal = () => {
const { t } = this.props
const { app, status, error, mode } = this.state
const { app, status, error, mode, progress } = this.state
return (
<Modal
isOpened={status !== 'idle' && status !== 'loading'}
@ -217,7 +177,7 @@ class AppsList extends PureComponent<Props, State> {
{t(`manager.apps.${mode}`, { app })}
</Text>
<Box mt={6}>
<Progress style={{ width: '100%' }} infinite />
<ProgressBar width={150} progress={progress} />
</Box>
</ModalContent>
</Fragment>

2
src/components/ManagerPage/Dashboard.js

@ -4,7 +4,7 @@ import { translate } from 'react-i18next'
import styled from 'styled-components'
import type { T, Device } from 'types/common'
import type { DeviceInfo } from 'helpers/types'
import type { DeviceInfo } from '@ledgerhq/live-common/lib/types/manager'
import Box from 'components/base/Box'
import Text from 'components/base/Text'

85
src/components/ManagerPage/FirmwareUpdate.js

@ -3,20 +3,13 @@
import React, { PureComponent, Fragment } from 'react'
import { translate } from 'react-i18next'
import isEqual from 'lodash/isEqual'
import isEmpty from 'lodash/isEmpty'
import invariant from 'invariant'
import type { Device, T } from 'types/common'
import type { DeviceInfo, OsuFirmware } from 'helpers/types'
import type { DeviceInfo, FirmwareUpdateContext } from '@ledgerhq/live-common/lib/types/manager'
import type { StepId } from 'components/modals/UpdateFirmware'
import getLatestFirmwareForDevice from 'commands/getLatestFirmwareForDevice'
import shouldFlashMcu from 'commands/shouldFlashMcu'
import installOsuFirmware from 'commands/installOsuFirmware'
import installFinalFirmware from 'commands/installFinalFirmware'
import installMcu from 'commands/installMcu'
import DisclaimerModal from 'components/modals/UpdateFirmware/Disclaimer'
import UpdateModal from 'components/modals/UpdateFirmware'
@ -42,32 +35,34 @@ type Props = {
}
type State = {
latestFirmware: ?OsuFirmware & ?{ shouldFlashMcu: boolean },
firmware: ?FirmwareUpdateContext,
modal: ModalStatus,
stepId: ?StepId,
shouldFlash: boolean,
ready: boolean,
}
const intializeState = ({ deviceInfo }): State => ({
latestFirmware: null,
firmware: null,
modal: 'closed',
stepId: deviceInfo.isBootloader ? 'updateMCU' : 'idCheck',
shouldFlash: false,
ready: false,
})
class FirmwareUpdate extends PureComponent<Props, State> {
state = intializeState(this.props)
componentDidMount() {
async componentDidMount() {
const { deviceInfo } = this.props
if (!deviceInfo.isOSU && !deviceInfo.isBootloader) {
this.fetchLatestFirmware()
} else if (deviceInfo.isOSU) {
this.shouldFlashMcu()
} else if (deviceInfo.isBootloader) {
this.handleInstallModal('updateMCU', true)
const firmware = await getLatestFirmwareForDevice.send(deviceInfo).toPromise()
if (firmware && !this._unmounting) {
/* eslint-disable */
this.setState({
firmware,
ready: true,
modal: deviceInfo.isOSU ? 'install' : 'closed',
stepId: deviceInfo.isOSU ? 'updateMCU' : 'idCheck',
})
/* eslint-enable */
}
}
@ -77,55 +72,15 @@ class FirmwareUpdate extends PureComponent<Props, State> {
_unmounting = false
fetchLatestFirmware = async () => {
const { deviceInfo } = this.props
const latestFirmware = await getLatestFirmwareForDevice.send(deviceInfo).toPromise()
if (
!isEmpty(latestFirmware) &&
!isEqual(this.state.latestFirmware, latestFirmware) &&
!this._unmounting
) {
this.setState({ latestFirmware, ready: true })
}
}
shouldFlashMcu = async () => {
const { deviceInfo } = this.props
const shouldFlash = await shouldFlashMcu.send(deviceInfo).toPromise()
if (!this._unmounting) {
this.setState({ shouldFlash, modal: 'install', stepId: 'idCheck', ready: true })
}
}
installOsuFirmware = async (device: Device) => {
const { latestFirmware } = this.state
const { deviceInfo } = this.props
invariant(latestFirmware, 'did not find a new firmware or firmware is not set')
this.setState({ modal: 'install' })
const result = await installOsuFirmware
.send({ devicePath: device.path, firmware: latestFirmware, targetId: deviceInfo.targetId })
.toPromise()
return result
}
installFinalFirmware = (device: Device) =>
installFinalFirmware.send({ devicePath: device.path }).toPromise()
flashMCU = async (device: Device) => installMcu.send({ devicePath: device.path }).toPromise()
handleCloseModal = () => this.setState({ modal: 'closed' })
handleDisclaimerModal = () => this.setState({ modal: 'disclaimer' })
handleInstallModal = (stepId: StepId = 'idCheck', shouldFlash?: boolean) =>
this.setState({ modal: 'install', stepId, shouldFlash, ready: true })
handleDisclaimerNext = () => this.setState({ modal: 'install' })
render() {
const { deviceInfo, t, device } = this.props
const { latestFirmware, modal, stepId, shouldFlash, ready } = this.state
const { firmware, modal, stepId, ready } = this.state
return (
<Card p={4}>
<Box horizontal align="center" flow={2}>
@ -151,12 +106,12 @@ class FirmwareUpdate extends PureComponent<Props, State> {
})}
</Text>
</Box>
<UpdateFirmwareButton firmware={latestFirmware} onClick={this.handleDisclaimerModal} />
<UpdateFirmwareButton firmware={firmware} onClick={this.handleDisclaimerModal} />
</Box>
{ready ? (
<Fragment>
<DisclaimerModal
firmware={latestFirmware}
firmware={firmware}
status={modal}
goToNextStep={this.handleDisclaimerNext}
onClose={this.handleCloseModal}
@ -165,11 +120,7 @@ class FirmwareUpdate extends PureComponent<Props, State> {
status={modal}
stepId={stepId}
onClose={this.handleCloseModal}
firmware={latestFirmware}
shouldFlashMcu={shouldFlash}
installOsuFirmware={this.installOsuFirmware}
installFinalFirmware={this.installFinalFirmware}
flashMCU={this.flashMCU}
firmware={firmware}
/>
</Fragment>
) : null}

13
src/components/ManagerPage/UpdateFirmwareButton.js

@ -3,19 +3,14 @@ import React, { Fragment } from 'react'
import { translate } from 'react-i18next'
import type { T } from 'types/common'
import type { OsuFirmware, FinalFirmware } from '@ledgerhq/live-common/lib/types/manager'
import Button from 'components/base/Button'
import Text from 'components/base/Text'
import { getCleanVersion } from 'components/ManagerPage/FirmwareUpdate'
type FirmwareInfos = {
name: string,
notes: string,
}
type Props = {
t: T,
firmware: ?FirmwareInfos,
firmware: ?{ osu: OsuFirmware, finalFirmware: FinalFirmware },
onClick: () => void,
}
@ -23,14 +18,14 @@ const UpdateFirmwareButton = ({ t, firmware, onClick }: Props) =>
firmware ? (
<Fragment>
<Text ff="Open Sans|Regular" fontSize={4} style={{ marginLeft: 'auto', marginRight: 15 }}>
{t('manager.firmware.latest', { version: getCleanVersion(firmware.name) })}
{t('manager.firmware.latest', { version: getCleanVersion(firmware.osu.name) })}
</Text>
<Button
primary
onClick={onClick}
event={'Manager Firmware Update Click'}
eventProperties={{
firmwareName: firmware.name,
firmwareName: firmware.osu.name,
}}
>
{t('manager.firmware.update')}

2
src/components/ManagerPage/index.js

@ -5,7 +5,7 @@ import invariant from 'invariant'
import { openURL } from 'helpers/linking'
import { urls } from 'config/urls'
import type { Device } from 'types/common'
import type { DeviceInfo } from 'helpers/types'
import type { DeviceInfo } from '@ledgerhq/live-common/lib/types/manager'
import { getFullListSortedCryptoCurrencies } from 'helpers/countervalues'
import Dashboard from './Dashboard'

64
src/components/ProgressBar/index.js

@ -0,0 +1,64 @@
// @flow
import React, { PureComponent } from 'react'
import styled, { css, keyframes } from 'styled-components'
import { colors } from 'styles/theme'
const animIndeterminate = keyframes`
0% {
transform: scaleX(0) translate3d(0, 0, 0);
}
50% {
transform: scaleX(1) translate3d(100%, 0, 0);
}
100% {
transform: scaleX(0) translate3d(0, 0, 0);
}
`
const Outer = styled.div`
background-color: ${colors.fog};
border-radius: 3px;
overflow: hidden;
height: 5px;
width: ${p => p.width}px;
position: relative;
`
const Inner = styled.div`
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: ${colors.wallet};
transform-origin: center left;
${p =>
p.progress === 0
? css`
animation: ${animIndeterminate} 2s cubic-bezier(0.61, 0.01, 0.39, 1.03) infinite;
`
: css`
transform: scaleX(${p => p.progress});
`};
`
type Props = {
progress: number,
width: number,
}
class ProgressBar extends PureComponent<Props> {
render() {
const { progress, width } = this.props
return (
<Outer width={width}>
<Inner progress={progress} />
</Outer>
)
}
}
export default ProgressBar

16
src/components/ProgressBar/stories.js

@ -0,0 +1,16 @@
// @flow
import React from 'react'
import { storiesOf } from '@storybook/react'
import { number } from '@storybook/addon-knobs'
import ProgressBar from 'components/ProgressBar'
const stories = storiesOf('Components', module)
stories.add('ProgressBar', () => (
<ProgressBar
progress={number('progress', 0, { min: 0, max: 1, step: 0.05 })}
width={number('width', 200, { min: 50, max: 500, step: 10 })}
/>
))

98
src/components/ProgressCircle/index.js

@ -0,0 +1,98 @@
// @flow
import React, { PureComponent } from 'react'
import styled, { css, keyframes } from 'styled-components'
import { colors } from 'styles/theme'
import Text from 'components/base/Text'
const STROKE_WIDTH = 5
type Props = {
progress: number,
size: number,
}
const animIndeterminate = keyframes`
0% {
}
50% {
}
100% {
}
`
const InnerCircle = styled.circle`
transform-origin: 50% 50%;
${p =>
p.progress === 0
? css`
animation: ${animIndeterminate} 3s cubic-bezier(0.61, 0.01, 0.39, 1.03) infinite;
`
: css`
transition: stroke-dashoffset 0.35s;
transform: rotate(-90deg);
`};
`
const Container = styled.div`
position: relative;
width: ${p => p.size}px;
height: ${p => p.size}px;
`
const TextContainer = styled.div`
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
align-items: center;
justify-content: center;
`
class ProgressCircle extends PureComponent<Props> {
render() {
const { size, progress } = this.props
const radius = size / 2
const normalizedRadius = radius - STROKE_WIDTH / 2
const circumference = normalizedRadius * 2 * Math.PI
const strokeDashoffset = circumference - progress * circumference
return (
<Container size={size}>
<TextContainer>
<Text ff="Rubik|Regular" color="graphite" fontSize={5}>
{`${Math.round(progress * 100)}%`}
</Text>
</TextContainer>
<svg height={size} width={size}>
<circle
stroke={colors.fog}
fill="transparent"
strokeWidth={STROKE_WIDTH}
style={{ strokeDashoffset }}
r={normalizedRadius}
cx={radius}
cy={radius}
/>
<InnerCircle
progress={progress}
stroke={colors.wallet}
fill="transparent"
strokeWidth={STROKE_WIDTH}
strokeDasharray={`${circumference} ${circumference}`}
style={{ strokeDashoffset }}
r={normalizedRadius}
cx={radius}
cy={radius}
/>
</svg>
</Container>
)
}
}
export default ProgressCircle

16
src/components/ProgressCircle/stories.js

@ -0,0 +1,16 @@
// @flow
import React from 'react'
import { storiesOf } from '@storybook/react'
import { number } from '@storybook/addon-knobs'
import ProgressCircle from 'components/ProgressCircle'
const stories = storiesOf('Components', module)
stories.add('ProgressCircle', () => (
<ProgressCircle
progress={number('progress', 0, { min: 0, max: 1, step: 0.01 })}
size={number('width', 150, { min: 50, max: 500, step: 10 })}
/>
))

2
src/components/SettingsPage/DisablePasswordModal.js

@ -1,7 +1,7 @@
// @flow
import React, { PureComponent } from 'react'
import { PasswordIncorrectError } from 'config/errors'
import { PasswordIncorrectError } from '@ledgerhq/live-common/lib/errors'
import db from 'helpers/db'
import Box from 'components/base/Box'

2
src/components/SettingsPage/PasswordForm.js

@ -6,7 +6,7 @@ import Box from 'components/base/Box'
import InputPassword from 'components/base/InputPassword'
import Label from 'components/base/Label'
import { PasswordsDontMatchError } from 'config/errors'
import { PasswordsDontMatchError } from '@ledgerhq/live-common/lib/errors'
import type { T } from 'types/common'

2
src/components/SettingsPage/PasswordModal.js

@ -5,7 +5,7 @@ import React, { PureComponent } from 'react'
import type { T } from 'types/common'
import db from 'helpers/db'
import { PasswordIncorrectError } from 'config/errors'
import { PasswordIncorrectError } from '@ledgerhq/live-common/lib/errors'
import Box from 'components/base/Box'
import Button from 'components/base/Button'
import { Modal, ModalContent, ModalBody, ModalTitle, ModalFooter } from 'components/base/Modal'

103
src/components/SettingsPage/RepairDeviceButton.js

@ -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)

10
src/components/SettingsPage/sections/Help.js

@ -5,13 +5,13 @@ import { translate } from 'react-i18next'
import type { T } from 'types/common'
import TrackPage from 'analytics/TrackPage'
import IconHelp from 'icons/Help'
import { resolveLogsDirectory } from 'helpers/log'
import { urls } from 'config/urls'
import ExportLogsBtn from 'components/ExportLogsBtn'
import OpenUserDataDirectoryBtn from 'components/OpenUserDataDirectoryBtn'
import CleanButton from '../CleanButton'
import ResetButton from '../ResetButton'
import RepairDeviceButton from '../RepairDeviceButton'
import AboutRowItem from '../AboutRowItem'
import LaunchOnboardingBtn from '../LaunchOnboardingBtn'
@ -52,10 +52,7 @@ class SectionHelp extends PureComponent<Props> {
>
<CleanButton />
</Row>
<Row
title={t('settings.exportLogs.title')}
desc={t('settings.exportLogs.desc', { logsDirectory: resolveLogsDirectory() })}
>
<Row title={t('settings.exportLogs.title')} desc={t('settings.exportLogs.desc')}>
<ExportLogsBtn />
</Row>
<Row
@ -76,6 +73,9 @@ class SectionHelp extends PureComponent<Props> {
>
<ResetButton />
</Row>
<Row title={t('settings.repairDevice.title')} desc={t('settings.repairDevice.desc')}>
<RepairDeviceButton />
</Row>
</Body>
</Section>
)

4
src/components/base/Modal/ConfirmModal.js

@ -25,11 +25,13 @@ type Props = {
t: T,
isLoading?: boolean,
analyticsName: string,
cancellable?: boolean,
}
class ConfirmModal extends PureComponent<Props> {
render() {
const {
cancellable,
isOpened,
title,
subTitle,
@ -54,7 +56,7 @@ class ConfirmModal extends PureComponent<Props> {
preventBackdropClick={isLoading}
{...props}
render={({ onClose }) => (
<ModalBody onClose={isLoading ? undefined : onClose}>
<ModalBody onClose={!cancellable && isLoading ? undefined : onClose}>
<TrackPage category="Modal" name={analyticsName} />
<ModalTitle>{title}</ModalTitle>
<ModalContent>

203
src/components/base/Modal/RepairModal.js

@ -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
src/components/base/Modal/index.js

@ -22,6 +22,7 @@ import GrowScroll from 'components/base/GrowScroll'
export { default as ModalBody } from './ModalBody'
export { default as ConfirmModal } from './ConfirmModal'
export { default as RepairModal } from './RepairModal'
export { default as ModalTitle } from './ModalTitle'
const springConfig = {

2
src/components/layout/Default.js

@ -35,6 +35,7 @@ import SideBar from 'components/MainSideBar'
import TopBar from 'components/TopBar'
import SyncBackground from 'components/SyncBackground'
import SyncContinuouslyPendingOperations from '../SyncContinouslyPendingOperations'
import HSMStatusBanner from '../HSMStatusBanner'
const Main = styled(GrowScroll).attrs({
px: 6,
@ -106,6 +107,7 @@ class Default extends Component<Props> {
<SideBar />
<Box shrink grow bg="lightGrey" color="grey" overflow="hidden" relative>
<HSMStatusBanner />
<TopBar />
<Main innerRef={n => (this._scrollContainer = n)} tabIndex={-1}>
<Route path="/" exact component={DashboardPage} />

2
src/components/modals/AccountSettingRenderBody.js

@ -17,7 +17,7 @@ import { setDataModal } from 'reducers/modals'
import { getBridgeForCurrency } from 'bridge'
import { AccountNameRequiredError, EnpointConfigError } from 'config/errors'
import { AccountNameRequiredError, EnpointConfigError } from '@ledgerhq/live-common/lib/errors'
import TrackPage from 'analytics/TrackPage'
import Spoiler from 'components/base/Spoiler'

12
src/components/modals/OperationDetails.js

@ -3,7 +3,7 @@
import React, { Fragment, Component } from 'react'
import { connect } from 'react-redux'
import { openURL } from 'helpers/linking'
import { translate } from 'react-i18next'
import { Trans, translate } from 'react-i18next'
import styled from 'styled-components'
import moment from 'moment'
import { getOperationAmountNumber } from '@ledgerhq/live-common/lib/operation'
@ -114,7 +114,7 @@ type Props = {
const OperationDetails = connect(mapStateToProps)((props: Props) => {
const { t, onClose, operation, account, currencySettings, marketIndicator } = props
if (!operation || !account || !currencySettings) return null
const { hash, date, senders, type, fee, recipients } = operation
const { extra, hash, date, senders, type, fee, recipients } = operation
const { name, unit, currency } = account
const amount = getOperationAmountNumber(operation)
@ -226,6 +226,14 @@ const OperationDetails = connect(mapStateToProps)((props: Props) => {
<OpDetailsTitle>{t('operationDetails.to')}</OpDetailsTitle>
<DataList lines={recipients} t={t} />
</Box>
{Object.entries(extra).map(([key, value]) => (
<Box key={key}>
<OpDetailsTitle>
<Trans i18nKey={`operationDetails.extra.${key}`} defaults={key} />
</OpDetailsTitle>
<OpDetailsData>{value}</OpDetailsData>
</Box>
))}
</Box>
</GrowScroll>
<GradientBox />

19
src/components/modals/Receive/steps/04-step-receive-funds.js

@ -5,10 +5,9 @@ import React, { PureComponent } from 'react'
import TrackPage from 'analytics/TrackPage'
import getAddress from 'commands/getAddress'
import { isSegwitDerivationMode } from '@ledgerhq/live-common/lib/derivation'
import Box from 'components/base/Box'
import CurrentAddressForAccount from 'components/CurrentAddressForAccount'
import { DisconnectedDevice, WrongDeviceForAccount } from 'config/errors'
import { DisconnectedDevice, WrongDeviceForAccount } from '@ledgerhq/live-common/lib/errors'
import type { StepProps } from '..'
@ -25,14 +24,14 @@ export default class StepReceiveFunds extends PureComponent<StepProps> {
if (!device || !account) {
throw new DisconnectedDevice()
}
const params = {
currencyId: account.currency.id,
devicePath: device.path,
path: account.freshAddressPath,
segwit: isSegwitDerivationMode(account.derivationMode),
verify: true,
}
const { address } = await getAddress.send(params).toPromise()
const { address } = await getAddress
.send({
currencyId: account.currency.id,
devicePath: device.path,
path: account.freshAddressPath,
verify: true,
})
.toPromise()
if (address !== account.freshAddress) {
throw new WrongDeviceForAccount(`WrongDeviceForAccount ${account.name}`, {

4
src/components/modals/Send/fields/RecipientField.js

@ -9,8 +9,8 @@ import Box from 'components/base/Box'
import LabelWithExternalIcon from 'components/base/LabelWithExternalIcon'
import RecipientAddress from 'components/RecipientAddress'
import { track } from 'analytics/segment'
import { createCustomErrorClass } from 'helpers/errors'
import { CantScanQRCode } from 'config/errors'
import { createCustomErrorClass } from '@ledgerhq/live-common/lib/errors/helpers'
import { CantScanQRCode } from '@ledgerhq/live-common/lib/errors'
type Props<Transaction> = {
t: T,

2
src/components/modals/Send/index.js

@ -20,7 +20,7 @@ import type { StepProps as DefaultStepProps } from 'components/base/Stepper'
import { getCurrentDevice } from 'reducers/devices'
import { accountsSelector } from 'reducers/accounts'
import { closeModal, openModal } from 'reducers/modals'
import { DisconnectedDevice, UserRefusedOnDevice } from 'config/errors'
import { DisconnectedDevice, UserRefusedOnDevice } from '@ledgerhq/live-common/lib/errors'
import Modal from 'components/base/Modal'
import Stepper from 'components/base/Stepper'

34
src/components/modals/UpdateFirmware/Disclaimer.js

@ -3,7 +3,7 @@
import React, { PureComponent, Fragment } from 'react'
import { translate, Trans } from 'react-i18next'
import type { OsuFirmware, FinalFirmware } from '@ledgerhq/live-common/lib/types/manager'
import type { T } from 'types/common'
import Modal, { ModalBody, ModalFooter, ModalTitle, ModalContent } from 'components/base/Modal'
@ -18,15 +18,13 @@ import type { ModalStatus } from 'components/ManagerPage/FirmwareUpdate'
import { getCleanVersion } from 'components/ManagerPage/FirmwareUpdate'
type FirmwareInfos = {
name: string,
notes: string,
}
type Props = {
t: T,
status: ModalStatus,
firmware: FirmwareInfos,
firmware: {
osu: ?OsuFirmware,
final: ?FinalFirmware,
},
goToNextStep: () => void,
onClose: () => void,
}
@ -50,7 +48,9 @@ class DisclaimerModal extends PureComponent<Props, State> {
<Trans i18nKey="manager.firmware.disclaimerTitle">
You are about to install
<Text ff="Open Sans|SemiBold" color="dark">
{`firmware version ${firmware ? getCleanVersion(firmware.name) : ''}`}
{`firmware version ${
firmware && firmware.osu ? getCleanVersion(firmware.osu.name) : ''
}`}
</Text>
</Trans>
</Text>
@ -59,14 +59,16 @@ class DisclaimerModal extends PureComponent<Props, State> {
{t('manager.firmware.disclaimerAppReinstall')}
</Text>
</ModalContent>
<ModalContent relative pb={0} style={{ height: 250, width: '100%' }}>
<GrowScroll pb={5}>
<Notes>
<Markdown>{firmware.notes}</Markdown>
</Notes>
</GrowScroll>
<GradientBox />
</ModalContent>
{firmware && firmware.osu ? (
<ModalContent relative pb={0} style={{ height: 250, width: '100%' }}>
<GrowScroll pb={5}>
<Notes>
<Markdown>{firmware.osu.notes}</Markdown>
</Notes>
</GrowScroll>
<GradientBox />
</ModalContent>
) : null}
<ModalFooter horizontal justifyContent="flex-end" style={{ width: '100%' }}>
<Button primary onClick={goToNextStep}>
{t('common.continue')}

10
src/components/modals/UpdateFirmware/Installing.js

@ -4,23 +4,25 @@ import { translate } from 'react-i18next'
import Box from 'components/base/Box'
import Text from 'components/base/Text'
import Spinner from 'components/base/Spinner'
import ProgressCircle from 'components/ProgressCircle'
import type { T } from 'types/common'
type Props = {
t: T,
progress: number,
installing: string,
}
function Installing({ t }: Props) {
function Installing({ t, progress, installing }: Props) {
return (
<Fragment>
<Box mx={7} align="center">
<Spinner color="fog" size={44} />
<ProgressCircle size={64} progress={progress} />
</Box>
<Box mx={7} mt={4} mb={2}>
<Text ff="Museo Sans|Regular" align="center" color="dark" fontSize={6}>
{t('manager.modal.installing')}
{t(`manager.modal.steps.${installing}`)}
</Text>
</Box>
<Box mx={7} mt={4} mb={7}>

35
src/components/modals/UpdateFirmware/index.js

@ -2,22 +2,22 @@
import React, { PureComponent } from 'react'
import { translate } from 'react-i18next'
import type { T, Device } from 'types/common'
import type { T } from 'types/common'
import Modal from 'components/base/Modal'
import Stepper from 'components/base/Stepper'
import SyncSkipUnderPriority from 'components/SyncSkipUnderPriority'
import type { FirmwareUpdateContext } from '@ledgerhq/live-common/lib/types/manager'
import type { StepProps as DefaultStepProps, Step } from 'components/base/Stepper'
import type { ModalStatus } from 'components/ManagerPage/FirmwareUpdate'
import type { OsuFirmware } from 'helpers/types'
import { FreezeDeviceChangeEvents } from '../../ManagerPage/HookDeviceChange'
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'
const createSteps = ({ t, shouldFlashMcu }: { t: T, shouldFlashMcu: boolean }): Array<*> => {
const createSteps = ({ t }: { t: T }): Array<*> => {
const updateStep = {
id: 'idCheck',
label: t('manager.modal.identifier'),
@ -45,26 +45,12 @@ const createSteps = ({ t, shouldFlashMcu }: { t: T, shouldFlashMcu: boolean }):
hideFooter: true,
}
const steps = [updateStep]
if (shouldFlashMcu) {
steps.push(mcuStep)
}
steps.push(finalStep)
return steps
return [updateStep, mcuStep, finalStep]
}
export type Firmware = OsuFirmware & { shouldFlashMcu: boolean }
export type StepProps = DefaultStepProps & {
firmware: Firmware,
firmware: FirmwareUpdateContext,
onCloseModal: () => void,
installOsuFirmware: (device: Device) => void,
installFinalFirmware: (device: Device) => void,
flashMCU: (device: Device) => void,
shouldFlashMcu: boolean,
error: ?Error,
setError: Error => void,
}
@ -75,11 +61,7 @@ type Props = {
t: T,
status: ModalStatus,
onClose: () => void,
firmware: Firmware,
shouldFlashMcu: boolean,
installOsuFirmware: (device: Device) => void,
installFinalFirmware: (device: Device) => void,
flashMCU: (device: Device) => void,
firmware: FirmwareUpdateContext,
stepId: StepId | string,
}
@ -98,9 +80,6 @@ class UpdateModal extends PureComponent<Props, State> {
STEPS = createSteps({
t: this.props.t,
shouldFlashMcu: this.props.firmware
? this.props.firmware.shouldFlashMcu
: this.props.shouldFlashMcu,
})
setError = (e: Error) => this.setState({ error: e })
@ -114,10 +93,10 @@ class UpdateModal extends PureComponent<Props, State> {
const { stepId, error, nonce } = this.state
const additionalProps = {
firmware,
error,
onCloseModal: onClose,
setError: this.setError,
firmware,
...props,
}

115
src/components/modals/UpdateFirmware/steps/01-step-install-full-firmware.js

@ -3,24 +3,17 @@
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 firmwarePrepare from 'commands/firmwarePrepare'
import { getCurrentDevice } from 'reducers/devices'
import { createCancelablePolling, delay } from 'helpers/promise'
import TrackPage from 'analytics/TrackPage'
import Box from 'components/base/Box'
import Text from 'components/base/Text'
import ProgressBar from 'components/ProgressBar'
import DeviceConfirm from 'components/DeviceConfirm'
import type { Device } from 'types/common'
import type { StepProps } from '../'
import Installing from '../Installing'
const Container = styled(Box).attrs({
alignItems: 'center',
fontSize: 4,
@ -55,79 +48,44 @@ type Props = StepProps & {
device: Device,
}
type State = {
installing: boolean,
}
class StepFullFirmwareInstall extends PureComponent<Props, State> {
class StepFullFirmwareInstall extends PureComponent<Props, { progress: number }> {
state = {
installing: false,
progress: 0,
}
componentDidMount() {
this.install()
}
const { firmware, device, transitionTo, setError } = this.props
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,
firmware,
shouldFlashMcu,
transitionTo,
setError,
} = this.props
const { device, deviceInfo } = await this.ensureDevice()
if (deviceInfo.isBootloader) {
transitionTo('updateMCU')
if (!firmware.osu) {
transitionTo('finish')
return
}
try {
if (deviceInfo.isOSU) {
this.setState({ installing: true })
await installFinalFirmware(device)
transitionTo('finish')
} else {
await installOsuFirmware(device)
this.setState({ installing: true })
if (this._unsubConnect) this._unsubConnect()
if ((firmware && firmware.shouldFlashMcu) || shouldFlashMcu) {
delay(1000)
this.sub = firmwarePrepare
.send({
devicePath: device.path,
firmware,
})
.subscribe({
next: patch => {
this.setState(patch)
},
complete: () => {
transitionTo('updateMCU')
} else {
const { device: freshDevice } = await this.ensureDevice()
await installFinalFirmware(freshDevice)
},
error: error => {
setError(error)
transitionTo('finish')
}
}
} catch (error) {
setError(error)
transitionTo('finish')
}
},
})
}
componentWillUnmount() {
if (this.sub) this.sub.unsubscribe()
}
sub: *
formatHashName = (hash: string): string => {
if (!hash) {
return ''
@ -138,21 +96,19 @@ class StepFullFirmwareInstall extends PureComponent<Props, State> {
}
renderBody = () => {
const { installing } = this.state
const { t, firmware } = this.props
return installing ? (
<Installing />
) : (
return (
<Fragment>
<Text ff="Open Sans|Regular" align="center" color="smoke">
{t('manager.modal.confirmIdentifierText')}
</Text>
<Box mx={7} mt={5}>
<Box mx={7} my={5}>
<Text ff="Open Sans|SemiBold" align="center" color="smoke">
{t('manager.modal.identifier')}
</Text>
<Address>{firmware && this.formatHashName(firmware.hash)}</Address>
<Address>{firmware.osu && this.formatHashName(firmware.osu.hash)}</Address>
</Box>
<ProgressBar progress={this.state.progress} width={200} />
<Box mt={5}>
<DeviceConfirm />
</Box>
@ -160,14 +116,11 @@ class StepFullFirmwareInstall extends PureComponent<Props, State> {
)
}
_unsubConnect: *
render() {
const { installing } = this.state
const { t } = this.props
return (
<Container>
<Title>{installing ? '' : t('manager.modal.confirmIdentifier')}</Title>
<Title>{t('manager.modal.confirmIdentifier')}</Title>
<TrackPage category="Manager" name="InstallFirmware" />
{this.renderBody()}
</Container>

117
src/components/modals/UpdateFirmware/steps/02-step-flash-mcu.js

@ -2,21 +2,14 @@
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 firmwareMain from 'commands/firmwareMain'
import TrackPage from 'analytics/TrackPage'
import Box from 'components/base/Box'
import Text from 'components/base/Text'
import type { Device } from 'types/common'
import type { StepProps } from '../'
import Installing from '../Installing'
@ -46,107 +39,48 @@ const Separator = styled(Box).attrs({
background-color: currentColor;
`
const mapStateToProps = state => ({
device: getCurrentDevice(state),
})
type Props = StepProps & { device?: Device }
type Props = StepProps
type State = {
installing: boolean,
installing: ?string,
progress: number,
}
class StepFlashMcu extends PureComponent<Props, State> {
state = {
installing: false,
installing: null,
progress: 0,
}
componentDidMount() {
this.install()
}
componentWillUnmount() {
if (this._unsubConnect) this._unsubConnect()
if (this._unsubDeviceInfo) this._unsubDeviceInfo()
}
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
}
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
}
flash = async () => {
await this.waitForDeviceInBootloader()
const { flashMCU, device } = this.props
if (device) {
this.setState({ installing: true })
await flashMCU(device)
}
}
install = async () => {
const { transitionTo, installFinalFirmware, setError } = this.props
const { deviceInfo, device } = await this.getDeviceInfo()
const { firmware, transitionTo, setError } = this.props
try {
if (deviceInfo.isBootloader) {
await this.flash()
this.install()
} else if (deviceInfo.isOSU) {
await installFinalFirmware(device)
this.sub = firmwareMain.send(firmware).subscribe({
next: patch => {
this.setState(patch)
},
complete: () => {
transitionTo('finish')
} else {
},
error: error => {
setError(error)
transitionTo('finish')
}
} catch (error) {
setError(error)
transitionTo('finish')
}
},
})
}
firstFlash = async () => {
await this.flash()
this.install()
componentWillUnmount() {
if (this.sub) this.sub.unsubscribe()
}
sub: *
renderBody = () => {
const { installing } = this.state
const { installing, progress } = this.state
const { t } = this.props
return installing ? (
<Installing />
<Installing installing={installing} progress={progress} />
) : (
<Fragment>
<Box mx={7}>
@ -176,9 +110,6 @@ class StepFlashMcu extends PureComponent<Props, State> {
)
}
_unsubConnect: *
_unsubDeviceInfo: *
render() {
const { t } = this.props
const { installing } = this.state
@ -192,4 +123,4 @@ class StepFlashMcu extends PureComponent<Props, State> {
}
}
export default connect(mapStateToProps)(StepFlashMcu)
export default StepFlashMcu

7
src/config/constants.js

@ -58,12 +58,6 @@ export const LEDGER_REST_API_BASE = stringFromEnv(
'LEDGER_REST_API_BASE',
'https://explorers.api.live.ledger.com',
)
export const MANAGER_API_BASE = stringFromEnv(
'MANAGER_API_BASE',
'https://manager.api.live.ledger.com/api',
)
export const BASE_SOCKET_URL = stringFromEnv('BASE_SOCKET_URL', 'wss://api.ledgerwallet.com/update')
// Provider
export const FORCE_PROVIDER = intFromEnv('FORCE_PROVIDER', 0)
@ -84,6 +78,7 @@ export const LEDGER_DEBUG_ALL_LANGS = boolFromEnv('LEDGER_DEBUG_ALL_LANGS')
export const SKIP_GENUINE = boolFromEnv('SKIP_GENUINE')
export const SKIP_ONBOARDING = boolFromEnv('SKIP_ONBOARDING')
export const SHOW_LEGACY_NEW_ACCOUNT = boolFromEnv('SHOW_LEGACY_NEW_ACCOUNT')
export const SHOW_MOCK_HSMWARNINGS = boolFromEnv('SHOW_MOCK_HSMWARNINGS')
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')

53
src/config/errors.js

@ -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')

52
src/helpers/apps/installApp.js

@ -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())
}

30
src/helpers/apps/listAppVersions.js

@ -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 : []
}

10
src/helpers/apps/listApps.js

@ -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 : []
}

10
src/helpers/apps/listCategories.js

@ -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 : []
}

41
src/helpers/apps/uninstallApp.js

@ -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())
}

5
src/helpers/db/db.spec.js

@ -1,15 +1,12 @@
import os from 'os'
import path from 'path'
import fs from 'fs'
import rimrafModule from 'rimraf'
import db from 'helpers/db'
import { promisify } from 'helpers/promise'
import { fsReadFile, fsWriteFile, fsMkdir } from 'helpers/fs'
const rimraf = promisify(rimrafModule)
const fsReadFile = promisify(fs.readFile)
const fsWriteFile = promisify(fs.writeFile)
const fsMkdir = promisify(fs.mkdir)
const accountsTransform = {
get: accounts => accounts.map(account => ({ ...account, balance: Number(account.balance) })),

6
src/helpers/db/index.js

@ -1,6 +1,5 @@
// @flow
import fs from 'fs'
import path from 'path'
import crypto from 'crypto'
import cloneDeep from 'lodash/cloneDeep'
@ -9,17 +8,16 @@ import get from 'lodash/get'
import set from 'lodash/set'
import logger from 'logger'
import { fsReadFile, fsUnlink } from 'helpers/fs'
import { promisify, debounce } from 'helpers/promise'
import { NoDBPathGiven, DBWrongPassword } from 'config/errors'
import { NoDBPathGiven, DBWrongPassword } from '@ledgerhq/live-common/lib/errors'
type Transform = {
get: any => any,
set: any => any,
}
const fsReadFile = promisify(fs.readFile)
const fsUnlink = promisify(fs.unlink)
const writeFileAtomic = promisify(writeFileAtomicModule)
const ALGORITHM = 'aes-256-cbc'

9
src/helpers/debugAppInfosForCurrency/btc.js

@ -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 }
}

10
src/helpers/debugAppInfosForCurrency/ethereum.js

@ -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 }
}

29
src/helpers/debugAppInfosForCurrency/index.js

@ -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

10
src/helpers/debugAppInfosForCurrency/ripple.js

@ -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 }
}

64
src/helpers/deviceAccess.js

@ -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
}

21
src/helpers/devices/getBitcoinLikeInfo.js

@ -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

24
src/helpers/devices/getCurrentFirmware.js

@ -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
}

42
src/helpers/devices/getDeviceInfo.js

@ -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,
}
}

17
src/helpers/devices/getDeviceVersion.js

@ -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
}

32
src/helpers/devices/getIsGenuine.js

@ -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()
}

76
src/helpers/devices/getLatestFirmwareForDevice.js

@ -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 }
}

10
src/helpers/devices/getMemInfo.js

@ -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))
}

23
src/helpers/devices/getOsuFirmware.js

@ -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
}

16
src/helpers/devices/isDashboardOpen.js

@ -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
}

49
src/helpers/devices/shouldFlashMcu.js

@ -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)
}

96
src/helpers/errors.js

@ -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
}

8
src/helpers/firmware/getFinalFirmwareById.js

@ -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
}

51
src/helpers/firmware/getFirmwareInfo.js

@ -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 }
}

13
src/helpers/firmware/getMcus.js

@ -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
}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save