Browse Source

Merge pull request #704 from LedgerHQ/develop

Prepare for beta.3
master
Gaëtan Renaudeau 7 years ago
committed by GitHub
parent
commit
d498b466ed
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      .npmrc
  2. 1
      .nvmrc
  3. 4
      electron-builder.yml
  4. 4
      package.json
  5. 12
      scripts/compile.sh
  6. 3
      scripts/dist-dir.sh
  7. 2
      scripts/start.sh
  8. 2
      src/api/Ethereum.js
  9. 2
      src/api/Fees.js
  10. 2
      src/api/network.js
  11. 3
      src/commands/getCurrentFirmware.js
  12. 10
      src/commands/getDeviceInfo.js
  13. 12
      src/commands/getIsGenuine.js
  14. 8
      src/commands/getLatestFirmwareForDevice.js
  15. 8
      src/commands/installFinalFirmware.js
  16. 2
      src/commands/libcoreGetFees.js
  17. 2
      src/commands/libcoreHardReset.js
  18. 8
      src/commands/listApps.js
  19. 2
      src/components/AccountPage/index.js
  20. 2
      src/components/AppError.js
  21. 6
      src/components/BalanceSummary/BalanceInfos.js
  22. 79
      src/components/ConfettiParty/Confetti.js
  23. 39
      src/components/ConfettiParty/index.js
  24. 6
      src/components/EnsureDeviceApp.js
  25. 23
      src/components/ExportLogsBtn.js
  26. 9
      src/components/ManagerPage/AppsList.js
  27. 5
      src/components/ManagerPage/Dashboard.js
  28. 12
      src/components/ManagerPage/FirmwareFinalUpdate.js
  29. 23
      src/components/ManagerPage/FirmwareUpdate.js
  30. 9
      src/components/ManagerPage/FlashMcu.js
  31. 10
      src/components/ManagerPage/index.js
  32. 22
      src/components/Onboarding/helperComponents.js
  33. 14
      src/components/Onboarding/steps/Finish.js
  34. 57
      src/components/Onboarding/steps/GenuineCheck/GenuineCheckErrorPage.js
  35. 67
      src/components/Onboarding/steps/GenuineCheck/GenuineCheckUnavailable.js
  36. 151
      src/components/Onboarding/steps/GenuineCheck/index.js
  37. 9
      src/components/Onboarding/steps/SelectDevice.js
  38. 4
      src/components/Onboarding/steps/SelectPIN/SelectPINblue.js
  39. 4
      src/components/Onboarding/steps/SelectPIN/SelectPINnano.js
  40. 4
      src/components/Onboarding/steps/SelectPIN/SelectPINrestoreBlue.js
  41. 4
      src/components/Onboarding/steps/SelectPIN/SelectPINrestoreNano.js
  42. 4
      src/components/Onboarding/steps/Start.js
  43. 4
      src/components/Onboarding/steps/WriteSeed/WriteSeedBlue.js
  44. 4
      src/components/Onboarding/steps/WriteSeed/WriteSeedNano.js
  45. 4
      src/components/Onboarding/steps/WriteSeed/WriteSeedRestore.js
  46. 72
      src/components/RenderError.js
  47. 2
      src/components/RequestAmount/index.js
  48. 8
      src/components/SelectAccount/index.js
  49. 2
      src/components/SettingsPage/sections/Currencies.js
  50. 2
      src/components/SettingsPage/sections/Profile.js
  51. 78
      src/components/TopBar/ActivityIndicator.js
  52. 7
      src/components/Workflow/EnsureDashboard.js
  53. 17
      src/components/Workflow/EnsureGenuine.js
  54. 14
      src/components/Workflow/index.js
  55. 2
      src/components/base/Box/Box.js
  56. 2
      src/components/base/SideBar/SideBarListItem.js
  57. 81
      src/components/base/Stepper/index.js
  58. 50
      src/components/base/Stepper/stories.js
  59. 208
      src/components/modals/AddAccounts/index.js
  60. 15
      src/components/modals/AddAccounts/steps/01-step-choose-currency.js
  61. 6
      src/components/modals/AddAccounts/steps/02-step-connect-device.js
  62. 81
      src/components/modals/AddAccounts/steps/03-step-import.js
  63. 9
      src/components/modals/OperationDetails.js
  64. 2
      src/components/modals/Send/index.js
  65. 7
      src/config/constants.js
  66. 2
      src/helpers/apps/installApp.js
  67. 11
      src/helpers/apps/listApps.js
  68. 2
      src/helpers/apps/uninstallApp.js
  69. 22
      src/helpers/common.js
  70. 14
      src/helpers/createCustomErrorClass.js
  71. 8
      src/helpers/devices/getCurrentFirmware.js
  72. 70
      src/helpers/devices/getDeviceInfo.js
  73. 3
      src/helpers/devices/getDeviceVersion.js
  74. 18
      src/helpers/devices/getIsGenuine.js
  75. 20
      src/helpers/devices/getLatestFirmwareForDevice.js
  76. 2
      src/helpers/devices/getNextMCU.js
  77. 2
      src/helpers/devices/getOsuFirmware.js
  78. 4
      src/helpers/devices/isDashboardOpen.js
  79. 79
      src/helpers/errors.js
  80. 14
      src/helpers/firmware/installFinalFirmware.js
  81. 2
      src/helpers/getAddressForCurrency/btc.js
  82. 3
      src/helpers/init-libcore.js
  83. 7
      src/helpers/ipc.js
  84. 9
      src/helpers/libcore.js
  85. 4
      src/helpers/promise.js
  86. 4
      src/helpers/socket.js
  87. 5
      src/helpers/urls.js
  88. 5
      src/icons/Home.js
  89. 0
      src/icons/SensitiveOperationShield.js
  90. 2
      src/icons/TriangleWarning.js
  91. 79
      src/icons/illustrations/GetStartedLogo.js
  92. 32
      src/icons/illustrations/LedgerBlue.js
  93. 47
      src/icons/illustrations/LedgerBlueError.js
  94. 47
      src/icons/illustrations/LedgerBlueSelectPIN.js
  95. 44
      src/icons/illustrations/LedgerNano.js
  96. 78
      src/icons/illustrations/LedgerNanoError.js
  97. 60
      src/icons/illustrations/LedgerNanoSelectPIN.js
  98. 63
      src/icons/illustrations/SetPassword.js
  99. 40
      src/icons/illustrations/WriteSeed.js
  100. 7
      src/internals/index.js

3
.npmrc

@ -0,0 +1,3 @@
npm_config_target=1.8.7
npm_config_disturl=https://atom.io/download/electron
npm_config_runtime=electron

1
.nvmrc

@ -0,0 +1 @@
lts/*

4
electron-builder.yml

@ -28,6 +28,10 @@ linux:
win:
artifactName: ${name}-${version}-${os}-${arch}.${ext}
certificateSubjectName: Ledger SAS
certificateSha1: 7dd9acb2ef0402883c65901ebbafd06e5293d391
signingHashAlgorithms:
- sha256
target:
- target: nsis
arch:

4
package.json

@ -8,7 +8,7 @@
"license": "MIT",
"scripts": {
"compile": "bash ./scripts/compile.sh",
"dist:dir": "yarn dist --dir -c.compression=store -c.mac.identity=null",
"dist:dir": "bash ./scripts/dist-dir.sh",
"dist": "bash ./scripts/dist.sh",
"test": "jest",
"flow": "flow",
@ -43,6 +43,7 @@
"@ledgerhq/hw-transport-node-hid": "^4.13.0",
"@ledgerhq/ledger-core": "2.0.0-rc.3",
"@ledgerhq/live-common": "2.31.0",
"animated": "^0.2.2",
"async": "^2.6.1",
"axios": "^0.18.0",
"babel-runtime": "^6.26.0",
@ -156,6 +157,7 @@
"prettier": "^1.13.5",
"react-hot-loader": "^4.3.2",
"react-test-renderer": "^16.4.1",
"uglifyjs-webpack-plugin": "^1.2.6",
"webpack": "^4.6.0",
"webpack-bundle-analyzer": "^2.11.1",
"webpack-cli": "^2.0.14",

12
scripts/compile.sh

@ -2,11 +2,11 @@
set -e
export GIT_REVISION=`git rev-parse HEAD`
export SENTRY_URL=https://db8f5b9b021048d4a401f045371701cb@sentry.io/274561
GIT_REVISION=`git rev-parse HEAD`
SENTRY_URL=https://db8f5b9b021048d4a401f045371701cb@sentry.io/274561
NODE_ENV=production
rm -rf ./node_modules/.cache dist
yarn
rm -rf dist &&
NODE_ENV=production yarn run webpack-cli --mode production --config webpack/internals.config.js &&
NODE_ENV=production yarn run electron-webpack
JOBS=max yarn
yarn run webpack-cli --mode production --config webpack/internals.config.js
yarn run electron-webpack

3
scripts/dist-dir.sh

@ -0,0 +1,3 @@
#/bin/bash
yarn compile && DEBUG=electron-builder electron-builder --dir -c.compression=store -c.mac.identity=null

2
scripts/start.sh

@ -1,5 +1,5 @@
#/bin/bash
concurrently --raw \
concurrently --raw --kill-others \
"cross-env NODE_ENV=development webpack-cli --mode development --watch --config webpack/internals.config.js" \
"cross-env NODE_ENV=development electron-webpack dev"

2
src/api/Ethereum.js

@ -1,6 +1,6 @@
// @flow
import type { CryptoCurrency } from '@ledgerhq/live-common/lib/types'
import createCustomErrorClass from 'helpers/createCustomErrorClass'
import { createCustomErrorClass } from 'helpers/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 createCustomErrorClass from 'helpers/createCustomErrorClass'
import { createCustomErrorClass } from 'helpers/errors'
import { blockchainBaseURL } from './Ledger'
import network from './network'

2
src/api/network.js

@ -3,7 +3,7 @@ import axios from 'axios'
import { GET_CALLS_RETRY, GET_CALLS_TIMEOUT } from 'config/constants'
import { retry } from 'helpers/promise'
import logger from 'logger'
import createCustomErrorClass from 'helpers/createCustomErrorClass'
import { createCustomErrorClass } from 'helpers/errors'
export const LedgerAPIErrorWithMessage = createCustomErrorClass('LedgerAPIErrorWithMessage')
export const LedgerAPIError = createCustomErrorClass('LedgerAPIError')

3
src/commands/getCurrentFirmware.js

@ -7,7 +7,8 @@ import getCurrentFirmware from 'helpers/devices/getCurrentFirmware'
type Input = {
deviceId: string | number,
version: string,
fullVersion: string,
provider: number,
}
type Result = *

10
src/commands/getDeviceInfo.js

@ -5,19 +5,13 @@ import { fromPromise } from 'rxjs/observable/fromPromise'
import { withDevice } from 'helpers/deviceAccess'
import getDeviceInfo from 'helpers/devices/getDeviceInfo'
import type { DeviceInfo } from 'helpers/devices/getDeviceInfo'
type Input = {
devicePath: string,
}
type Result = {
targetId: number | string,
version: string,
final: boolean,
mcu: boolean,
}
const cmd: Command<Input, Result> = createCommand('getDeviceInfo', ({ devicePath }) =>
const cmd: Command<Input, DeviceInfo> = createCommand('getDeviceInfo', ({ devicePath }) =>
fromPromise(withDevice(devicePath)(transport => getDeviceInfo(transport))),
)

12
src/commands/getIsGenuine.js

@ -2,23 +2,19 @@
import { createCommand, Command } from 'helpers/ipc'
import { fromPromise } from 'rxjs/observable/fromPromise'
import type { DeviceInfo } from 'helpers/devices/getDeviceInfo'
import getIsGenuine from 'helpers/devices/getIsGenuine'
import { withDevice } from 'helpers/deviceAccess'
type Input = {
devicePath: string,
targetId: string | number,
version: string,
deviceInfo: DeviceInfo,
}
type Result = string
const cmd: Command<Input, Result> = createCommand(
'getIsGenuine',
({ devicePath, targetId, version }) =>
fromPromise(
withDevice(devicePath)(transport => getIsGenuine(transport, { targetId, version })),
),
const cmd: Command<Input, Result> = createCommand('getIsGenuine', ({ devicePath, deviceInfo }) =>
fromPromise(withDevice(devicePath)(transport => getIsGenuine(transport, deviceInfo))),
)
export default cmd

8
src/commands/getLatestFirmwareForDevice.js

@ -2,17 +2,13 @@
import { createCommand, Command } from 'helpers/ipc'
import { fromPromise } from 'rxjs/observable/fromPromise'
import type { DeviceInfo } from 'helpers/devices/getDeviceInfo'
import getLatestFirmwareForDevice from '../helpers/devices/getLatestFirmwareForDevice'
type Input = {
targetId: string | number,
version: string,
}
type Result = *
const cmd: Command<Input, Result> = createCommand('getLatestFirmwareForDevice', data =>
const cmd: Command<DeviceInfo, Result> = createCommand('getLatestFirmwareForDevice', data =>
fromPromise(getLatestFirmwareForDevice(data)),
)

8
src/commands/installFinalFirmware.js

@ -3,13 +3,13 @@
import { createCommand, Command } from 'helpers/ipc'
import { fromPromise } from 'rxjs/observable/fromPromise'
import { withDevice } from 'helpers/deviceAccess'
import type { DeviceInfo } from 'helpers/devices/getDeviceInfo'
import installFinalFirmware from 'helpers/firmware/installFinalFirmware'
type Input = {
devicePath: string,
targetId: string | number,
version: string,
deviceInfo: DeviceInfo,
}
type Result = {
@ -18,8 +18,8 @@ type Result = {
const cmd: Command<Input, Result> = createCommand(
'installFinalFirmware',
({ devicePath, ...rest }) =>
fromPromise(withDevice(devicePath)(transport => installFinalFirmware(transport, { ...rest }))),
({ devicePath, deviceInfo }) =>
fromPromise(withDevice(devicePath)(transport => installFinalFirmware(transport, deviceInfo))),
)
export default cmd

2
src/commands/libcoreGetFees.js

@ -5,7 +5,7 @@ import withLibcore from 'helpers/withLibcore'
import { createCommand, Command } from 'helpers/ipc'
import * as accountIdHelper from 'helpers/accountId'
import { isValidAddress } from 'helpers/libcore'
import createCustomErrorClass from 'helpers/createCustomErrorClass'
import { createCustomErrorClass } from 'helpers/errors'
const InvalidAddress = createCustomErrorClass('InvalidAddress')

2
src/commands/libcoreHardReset.js

@ -3,7 +3,7 @@
import { createCommand } from 'helpers/ipc'
import { fromPromise } from 'rxjs/observable/fromPromise'
import withLibcore from 'helpers/withLibcore'
import createCustomErrorClass from 'helpers/createCustomErrorClass'
import { createCustomErrorClass } from 'helpers/errors'
const HardResetFail = createCustomErrorClass('HardResetFail')

8
src/commands/listApps.js

@ -7,13 +7,15 @@ import listApps from 'helpers/apps/listApps'
type Input = {
targetId: string | number,
version: string,
fullVersion: string,
provider: number,
}
type Result = *
const cmd: Command<Input, Result> = createCommand('listApps', ({ targetId, version }) =>
fromPromise(listApps(targetId, version)),
const cmd: Command<Input, Result> = createCommand(
'listApps',
({ targetId, fullVersion, provider }) => fromPromise(listApps(targetId, fullVersion, provider)),
)
export default cmd

2
src/components/AccountPage/index.js

@ -150,12 +150,14 @@ class AccountPage extends PureComponent<Props> {
<Box flow={4} mb={2}>
<Box horizontal>
<BalanceTotal
showCryptoEvenIfNotAvailable
isAvailable={isAvailable}
totalBalance={account.balance}
unit={account.unit}
>
<FormattedVal
animateTicker
disableRounding
alwaysShowSign={false}
color="warmGrey"
unit={counterValue.units[0]}

2
src/components/AppError.js

@ -13,7 +13,7 @@ import RenderError from './RenderError'
const App = ({ language, error }: { error: Error, language: string }) => (
<I18nextProvider i18n={i18n} initialLanguage={language}>
<ThemeProvider theme={theme}>
<RenderError disableExport error={error}>
<RenderError withoutAppData error={error}>
<TriggerAppReady />
</RenderError>
</ThemeProvider>

6
src/components/BalanceSummary/BalanceInfos.js

@ -32,6 +32,7 @@ type BalanceTotalProps = {
unit: Unit,
isAvailable: boolean,
totalBalance: number,
showCryptoEvenIfNotAvailable?: boolean,
}
type Props = {
@ -90,10 +91,10 @@ export function BalanceSinceDiff(props: Props) {
}
export function BalanceTotal(props: BalanceTotalProps) {
const { unit, totalBalance, isAvailable, children } = props
const { unit, totalBalance, isAvailable, showCryptoEvenIfNotAvailable, children } = props
return (
<Box grow {...props}>
{!isAvailable ? (
{!isAvailable && !showCryptoEvenIfNotAvailable ? (
<PlaceholderLine width={150} />
) : (
<FormattedVal
@ -101,6 +102,7 @@ export function BalanceTotal(props: BalanceTotalProps) {
color="dark"
unit={unit}
fontSize={8}
disableRounding
showCode
val={totalBalance}
/>

79
src/components/ConfettiParty/Confetti.js

@ -0,0 +1,79 @@
import React, { PureComponent } from 'react'
import Animated from 'animated/lib/targets/react-dom'
class Confetti extends PureComponent<
{
shape: string,
initialXPercent: number,
initialYPercent: number,
initialRotation: number,
initialScale: number,
duration: number,
rotations: number,
delta: [number, number],
},
{
value: *,
},
> {
state = {
progress: new Animated.Value(0),
}
componentDidMount() {
const { duration } = this.props
Animated.timing(this.state.progress, {
toValue: 1,
duration,
}).start()
}
render() {
const {
initialXPercent,
initialYPercent,
initialScale,
initialRotation,
shape,
rotations,
delta,
} = this.props
const { progress } = this.state
const rotate = progress.interpolate({
inputRange: [0, 1],
outputRange: ['0deg', `${rotations * 360}deg`],
})
const translateX = progress.interpolate({
inputRange: [0, 1],
outputRange: [0, delta[0]],
})
const translateY = progress.interpolate({
inputRange: [0, 1],
outputRange: [0, delta[1]],
})
const opacity = progress.interpolate({
inputRange: [0.6, 1],
outputRange: [1, 0],
clamp: true,
})
return (
<Animated.img
src={shape}
style={{
position: 'absolute',
left: `${(100 * initialXPercent).toFixed(0)}%`,
top: `${(100 * initialYPercent).toFixed(0)}%`,
opacity,
transformOrigin: 'center center',
transform: [
{ translateX },
{ translateY },
{ scale: initialScale },
{ rotate: `${initialRotation}deg` },
{ rotate },
],
}}
/>
)
}
}
export default Confetti

39
src/components/ConfettiParty/index.js

@ -0,0 +1,39 @@
import React, { PureComponent } from 'react'
import { i } from 'helpers/staticPath'
import Confetti from './Confetti'
const shapes = [
i('confetti-shapes/1.svg'),
i('confetti-shapes/2.svg'),
i('confetti-shapes/3.svg'),
i('confetti-shapes/4.svg'),
]
class ConfettiParty extends PureComponent<{}> {
state = {
confettis: Array(64)
.fill(null)
.map((_, i) => ({
id: i,
shape: shapes[Math.floor(shapes.length * Math.random())],
initialRotation: 360 * Math.random(),
initialYPercent: -0.2 + 0.1 * Math.random(),
initialXPercent: 0.2 + 0.6 * Math.random(),
initialScale: 1,
rotations: 4 + 4 * Math.random(),
delta: [(Math.random() - 0.5) * 600, 300 + 300 * Math.random()],
duration: 6000 + 5000 * Math.random(),
})),
}
render() {
const { confettis } = this.state
return (
<div style={{ position: 'relative', width: '100%', height: '100%' }}>
{confettis.map(c => <Confetti key={c.id} {...c} />)}
</div>
)
}
}
export default ConfettiParty

6
src/components/EnsureDeviceApp/index.js → src/components/EnsureDeviceApp.js

@ -13,7 +13,7 @@ import type { State as StoreState } from 'reducers/index'
import getAddress from 'commands/getAddress'
import { standardDerivation } from 'helpers/derivations'
import isDashboardOpen from 'commands/isDashboardOpen'
import createCustomErrorClass from 'helpers/createCustomErrorClass'
import { createCustomErrorClass } from 'helpers/errors'
import { CHECK_APP_INTERVAL_WHEN_VALID, CHECK_APP_INTERVAL_WHEN_INVALID } from 'config/constants'
@ -36,7 +36,7 @@ type OwnProps = {
deviceSelected: ?Device,
deviceStatus: DeviceStatus,
error: ?Error,
}) => React$Element<*>,
}) => React$Node,
}
type Props = OwnProps & {
@ -159,7 +159,7 @@ class EnsureDeviceApp extends PureComponent<Props, State> {
}
} else {
logger.warn('EnsureDeviceApp for using dashboard is DEPRECATED !!!')
// FIXME REMOVE THIS ! should use EnsureDashboard dedicated component.
// TODO: FIXME REMOVE THIS ! should use EnsureDashboard dedicated component.
const isDashboard = isDashboardOpen.send({ devicePath: deviceSelected.path }).toPromise()
if (!isDashboard) {

23
src/components/ExportLogsBtn.js

@ -19,15 +19,24 @@ const mapStateToProps = createStructuredSelector({
class ExportLogsBtn extends Component<{
t: *,
settings: *,
accounts: *,
settings: ?*,
accounts: ?*,
hookToShortcut?: boolean,
}> {
handleExportLogs = () => {
const { accounts, settings } = this.props
const logs = logger.exportLogs()
const resourceUsage = webFrame.getResourceUsage()
const report = { resourceUsage, logs, accounts, settings, date: new Date() }
const report = {
resourceUsage,
logs,
accounts,
settings,
date: new Date(),
release: __APP_VERSION__,
git_commit: __GIT_REVISION__,
environment: __DEV__ ? 'development' : 'production',
}
console.log(report) // eslint-disable-line no-console
const reportJSON = JSON.stringify(report)
const path = remote.dialog.showSaveDialog({
@ -69,4 +78,10 @@ class ExportLogsBtn extends Component<{
}
}
export default translate()(connect(mapStateToProps)(ExportLogsBtn))
const WithAppData = connect(mapStateToProps)(ExportLogsBtn)
const WithoutAppData = ExportLogsBtn
const ExportLogsBtnDispatcher = ({ withAppData, ...rest }: *) =>
withAppData ? <WithAppData {...rest} /> : <WithoutAppData {...rest} />
export default translate()(ExportLogsBtnDispatcher)

9
src/components/ManagerPage/AppsList.js

@ -48,7 +48,8 @@ type Props = {
device: Device,
targetId: string | number,
t: T,
version: string,
fullVersion: string,
provider: number,
}
type State = {
@ -82,8 +83,8 @@ class AppsList extends PureComponent<Props, State> {
async fetchAppList() {
try {
const { targetId, version } = this.props
const appsList = await listApps.send({ targetId, version }).toPromise()
const { targetId, fullVersion, provider } = this.props
const appsList = await listApps.send({ targetId, fullVersion, provider }).toPromise()
if (!this._unmounted) {
this.setState({ appsList, status: 'idle', appsLoaded: true })
}
@ -116,7 +117,7 @@ class AppsList extends PureComponent<Props, State> {
} = this.props
const data = { app, devicePath, targetId }
await uninstallApp.send(data).toPromise()
this.setState({ status: 'success', app: '' })
this.setState({ status: 'success' })
} catch (err) {
this.setState({ status: 'error', error: err, app: '', mode: 'home' })
}

5
src/components/ManagerPage/Dashboard.js

@ -34,10 +34,7 @@ const Dashboard = ({ device, deviceInfo, t }: Props) => (
</Text>
</Box>
<Box mt={5}>
<FirmwareUpdate
infos={{ targetId: deviceInfo.targetId, version: deviceInfo.version }}
device={device}
/>
<FirmwareUpdate deviceInfo={deviceInfo} device={device} />
</Box>
<Box mt={5}>
<AppsList device={device} targetId={deviceInfo.targetId} version={deviceInfo.version} />

12
src/components/ManagerPage/FirmwareFinalUpdate.js

@ -3,6 +3,7 @@
import React, { PureComponent } from 'react'
import { translate } from 'react-i18next'
import logger from 'logger'
import type { DeviceInfo } from 'helpers/devices/getDeviceInfo'
import type { Device, T } from 'types/common'
@ -11,15 +12,10 @@ import installFinalFirmware from 'commands/installFinalFirmware'
import Box, { Card } from 'components/base/Box'
// import Button from 'components/base/Button'
type DeviceInfos = {
targetId: number,
version: string,
}
type Props = {
t: T,
device: Device,
infos: DeviceInfos,
deviceInfo: DeviceInfo,
}
type State = {}
@ -35,9 +31,9 @@ class FirmwareFinalUpdate extends PureComponent<Props, State> {
installFinalFirmware = async () => {
try {
const { device, infos } = this.props
const { device, deviceInfo } = this.props
const { success } = await installFinalFirmware
.send({ devicePath: device.path, targetId: infos.targetId, version: infos.version })
.send({ devicePath: device.path, deviceInfo })
.toPromise()
if (success) {
this.setState()

23
src/components/ManagerPage/FirmwareUpdate.js

@ -14,6 +14,7 @@ import type { LedgerScriptParams } from 'helpers/common'
import getLatestFirmwareForDevice from 'commands/getLatestFirmwareForDevice'
import installOsuFirmware from 'commands/installOsuFirmware'
import type { DeviceInfo } from 'helpers/devices/getDeviceInfo'
import Box, { Card } from 'components/base/Box'
import Text from 'components/base/Text'
@ -32,17 +33,12 @@ let CACHED_LATEST_FIRMWARE = null
export const getCleanVersion = (input: string): string =>
input.endsWith('-osu') ? input.replace('-osu', '') : input
type DeviceInfos = {
targetId: number | string,
version: string,
}
type ModalStatus = 'closed' | 'disclaimer' | 'installing' | 'error' | 'success'
type Props = {
t: T,
device: Device,
infos: DeviceInfos,
deviceInfo: DeviceInfo,
}
type State = {
@ -73,12 +69,9 @@ class FirmwareUpdate extends PureComponent<Props, State> {
_unmounting = false
fetchLatestFirmware = async () => {
const { infos } = this.props
const { deviceInfo } = this.props
const latestFirmware =
CACHED_LATEST_FIRMWARE ||
(await getLatestFirmwareForDevice
.send({ targetId: infos.targetId, version: infos.version })
.toPromise())
CACHED_LATEST_FIRMWARE || (await getLatestFirmwareForDevice.send(deviceInfo).toPromise())
if (
!isEmpty(latestFirmware) &&
!isEqual(this.state.latestFirmware, latestFirmware) &&
@ -92,14 +85,14 @@ class FirmwareUpdate extends PureComponent<Props, State> {
installFirmware = async () => {
try {
const { latestFirmware } = this.state
const { infos } = this.props
const { deviceInfo } = this.props
invariant(latestFirmware, 'did not find a new firmware or firmware is not set')
const {
device: { path: devicePath },
} = this.props
this.setState({ modal: 'installing' })
const { success } = await installOsuFirmware
.send({ devicePath, firmware: latestFirmware, targetId: infos.targetId })
.send({ devicePath, firmware: latestFirmware, targetId: deviceInfo.targetId })
.toPromise()
if (success) {
this.fetchLatestFirmware()
@ -150,7 +143,7 @@ class FirmwareUpdate extends PureComponent<Props, State> {
}
render() {
const { infos, t } = this.props
const { deviceInfo, t } = this.props
const { latestFirmware, modal } = this.state
return (
@ -170,7 +163,7 @@ class FirmwareUpdate extends PureComponent<Props, State> {
</Box>
<Text ff="Open Sans|SemiBold" fontSize={2}>
{t('app:manager.firmware.installed', {
version: infos.version,
version: deviceInfo.fullVersion,
})}
</Text>
</Box>

9
src/components/ManagerPage/FlashMcu.js

@ -4,12 +4,7 @@ import React, { PureComponent } from 'react'
import type { Device } from 'types/common'
import installMcu from 'commands/installMcu'
type DeviceInfo = {
targetId: number | string,
version: string,
final: boolean,
mcu: boolean,
}
import type { DeviceInfo } from 'helpers/devices/getDeviceInfo'
type Props = {
device: Device,
@ -35,7 +30,7 @@ class FlashMcu extends PureComponent<Props, State> {
.send({
devicePath: device.path,
targetId: deviceInfo.targetId,
version: deviceInfo.version,
version: deviceInfo.seVersion,
})
.toPromise()
this.setState({ flashing: false })

10
src/components/ManagerPage/index.js

@ -4,19 +4,13 @@
import React, { PureComponent } from 'react'
import type { Device } from 'types/common'
import type { DeviceInfo } from 'helpers/devices/getDeviceInfo'
import Workflow from 'components/Workflow'
import WorkflowWithIcon from 'components/Workflow/WorkflowWithIcon'
import Dashboard from './Dashboard'
import FlashMcu from './FlashMcu'
type DeviceInfo = {
targetId: number | string,
version: string,
final: boolean,
mcu: boolean,
}
type Error = {
message: string,
stack: string,
@ -27,7 +21,7 @@ class ManagerPage extends PureComponent<*, *> {
return (
<Workflow
renderFinalUpdate={(device: Device, deviceInfo: DeviceInfo) => (
<p>UPDATE FINAL FIRMARE (TEMPLATE + ACTION WIP) {deviceInfo.final}</p>
<p>UPDATE FINAL FIRMARE (TEMPLATE + ACTION WIP) {deviceInfo.isOSU}</p>
)}
renderMcuUpdate={(device: Device, deviceInfo: DeviceInfo) => (
<FlashMcu device={device} deviceInfo={deviceInfo} />

22
src/components/Onboarding/helperComponents.js

@ -5,7 +5,7 @@ import { radii } from 'styles/theme'
import Box from 'components/base/Box'
import GrowScroll from 'components/base/GrowScroll'
import IconSensitiveOperationShield from 'icons/illustrations/SensitiveOperationShield'
import IconSensitiveOperationShield from 'icons/SensitiveOperationShield'
// GENERAL
export const Title = styled(Box).attrs({
@ -113,3 +113,23 @@ const DisclaimerBoxIconContainer = styled(Box).attrs({
top: 0;
right: 0;
`
// GENUINE CHECK
export const GenuineCheckCardWrapper = styled(Box).attrs({
horizontal: true,
p: 5,
borderRadius: '4px',
justify: 'space-between',
})`
width: 580px;
height: 74px;
transition: all ease-in-out 0.2s;
color: ${p => (p.isDisabled ? p.theme.colors.grey : p.theme.colors.black)};
border: ${p => `1px ${p.isDisabled ? 'dashed' : 'solid'} ${p.theme.colors.fog}`};
pointer-events: ${p => (p.isDisabled ? 'none' : 'auto')};
background-color: ${p => (p.isDisabled ? p.theme.colors.lightGrey : p.theme.colors.white)};
opacity: ${p => (p.isDisabled ? 0.7 : 1)};
&:hover {
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.05);
}
`

14
src/components/Onboarding/steps/Finish.js

@ -6,6 +6,7 @@ import styled from 'styled-components'
import Box from 'components/base/Box'
import Button from 'components/base/Button'
import ConfettiParty from 'components/ConfettiParty'
import IconCheckCircle from 'icons/CheckCircle'
import IconSocialTwitter from 'icons/Twitter'
@ -15,6 +16,16 @@ import IconSocialGithub from 'icons/Github'
import type { StepProps } from '..'
import { Title, Description } from '../helperComponents'
const ConfettiLayer = styled.div`
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -1;
pointer-events: none;
`
const socialMedia = [
{
key: 'twitter',
@ -40,6 +51,9 @@ export default (props: StepProps) => {
const { finish, t } = props
return (
<Box sticky justifyContent="center">
<ConfettiLayer>
<ConfettiParty />
</ConfettiLayer>
<Box alignItems="center">
<Box color="positiveGreen">
<IconCheckCircle size={44} />

57
src/components/Onboarding/steps/GenuineCheck/GenuineCheckErrorPage.js

@ -0,0 +1,57 @@
// @flow
import React, { Fragment } from 'react'
import { i } from 'helpers/staticPath'
import type { T } from 'types/common'
import Box from 'components/base/Box'
import Button from 'components/base/Button'
import { Title, Description, OnboardingFooterWrapper } from '../../helperComponents'
export function GenuineCheckErrorPage({
redoGenuineCheck,
contactSupport,
isLedgerNano,
t,
}: {
redoGenuineCheck: () => void,
contactSupport: () => void,
isLedgerNano: boolean | null,
t: T,
}) {
return (
<Box sticky pt={50}>
<Box grow alignItems="center" justifyContent="center">
{isLedgerNano ? (
<Fragment>
<Title>{t('onboarding:genuineCheck.errorPage.ledgerNano.title')}</Title>
<Description>{t('onboarding:genuineCheck.errorPage.ledgerNano.desc')}</Description>
<Box mt={5} mr={7}>
<img alt="" src={i('nano-error-onb.svg')} />
</Box>
</Fragment>
) : (
<Fragment>
<Title>{t('onboarding:genuineCheck.errorPage.ledgerBlue.title')}</Title>
<Description pb={5}>
{t('onboarding:genuineCheck.errorPage.ledgerBlue.desc')}
</Description>
<Box alignItems="center">
<img alt="" src={i('blue-error-onb.svg')} />
</Box>
</Fragment>
)}
</Box>
<OnboardingFooterWrapper>
<Button padded outlineGrey onClick={() => redoGenuineCheck()}>
{t('app:common.back')}
</Button>
<Button padded danger onClick={() => contactSupport()} ml="auto">
{t('onboarding:genuineCheck.buttons.contactSupport')}
</Button>
</OnboardingFooterWrapper>
</Box>
)
}

67
src/components/Onboarding/steps/GenuineCheck/GenuineCheckUnavailable.js

@ -0,0 +1,67 @@
// @flow
import React from 'react'
import { colors } from 'styles/theme'
import type { T } from 'types/common'
import type { OnboardingState } from 'reducers/onboarding'
import FakeLink from 'components/base/FakeLink'
import IconCross from 'icons/Cross'
import Box from 'components/base/Box'
import Button from 'components/base/Button'
import TranslatedError from 'components/TranslatedError'
import { OnboardingFooterWrapper } from '../../helperComponents'
export function GenuineCheckUnavailableFooter({
prevStep,
nextStep,
t,
}: {
prevStep: () => void,
nextStep: () => void,
t: T,
}) {
return (
<OnboardingFooterWrapper>
<Button padded outlineGrey onClick={() => prevStep()}>
{t('app:common.back')}
</Button>
<Box horizontal ml="auto">
<Button padded disabled={false} onClick={() => nextStep()} mx={2}>
{t('app:common.skipThisStep')}
</Button>
<Button padded onClick={nextStep} disabled primary>
{t('app:common.continue')}
</Button>
</Box>
</OnboardingFooterWrapper>
)
}
export function GenuineCheckUnavailableMessage({
handleOpenGenuineCheckModal,
onboarding,
t,
}: {
handleOpenGenuineCheckModal: () => void,
t: T,
onboarding: OnboardingState,
}) {
return (
<Box align="center" flow={1} color={colors.alertRed}>
<FakeLink ff="Open Sans|Regular" fontSize={4} underline onClick={handleOpenGenuineCheckModal}>
{t('app:common.retry')}
</FakeLink>
<Box horizontal justify="center">
<Box justifyContent="center">
<IconCross size={12} />
</Box>
<Box ff="Open Sans|Regular" style={{ maxWidth: 150 }} fontSize={2} ml={1}>
<TranslatedError error={onboarding.genuine.genuineCheckUnavailable} />
</Box>
</Box>
</Box>
)
}

151
src/components/Onboarding/steps/GenuineCheck.js → src/components/Onboarding/steps/GenuineCheck/index.js

@ -1,26 +1,19 @@
// @flow
import React, { PureComponent, Fragment } from 'react'
import React, { PureComponent } from 'react'
import { shell } from 'electron'
import { connect } from 'react-redux'
import styled from 'styled-components'
import { colors } from 'styles/theme'
import type { T } from 'types/common'
import { updateGenuineCheck } from 'reducers/onboarding'
import Box from 'components/base/Box'
import FakeLink from 'components/base/FakeLink'
import Button from 'components/base/Button'
import RadioGroup from 'components/base/RadioGroup'
import GenuineCheckModal from 'components/GenuineCheckModal'
import TranslatedError from 'components/TranslatedError'
import IconLedgerNanoError from 'icons/illustrations/LedgerNanoError'
import IconLedgerBlueError from 'icons/illustrations/LedgerBlueError'
import IconCheck from 'icons/Check'
import IconCross from 'icons/Cross'
import {
Title,
@ -28,11 +21,17 @@ import {
IconOptionRow,
FixedTopContainer,
StepContainerInner,
OnboardingFooterWrapper,
} from '../helperComponents'
GenuineCheckCardWrapper,
} from '../../helperComponents'
import { GenuineCheckErrorPage } from './GenuineCheckErrorPage'
import {
GenuineCheckUnavailableFooter,
GenuineCheckUnavailableMessage,
} from './GenuineCheckUnavailable'
import OnboardingFooter from '../../OnboardingFooter'
import type { StepProps } from '..'
import OnboardingFooter from '../OnboardingFooter'
import type { StepProps } from '../..'
const mapDispatchToProps = { updateGenuineCheck }
@ -144,7 +143,7 @@ class GenuineCheck extends PureComponent<StepProps, State> {
}
renderGenuineFail = () => (
<GenuineCheckFail
<GenuineCheckErrorPage
redoGenuineCheck={this.redoGenuineCheck}
contactSupport={this.contactSupport}
t={this.props.t}
@ -174,7 +173,7 @@ class GenuineCheck extends PureComponent<StepProps, State> {
</Description>
)}
<Box mt={5}>
<CardWrapper>
<GenuineCheckCardWrapper>
<Box justify="center">
<Box horizontal>
<IconOptionRow>{'1.'}</IconOptionRow>
@ -188,10 +187,10 @@ class GenuineCheck extends PureComponent<StepProps, State> {
onChange={item => this.handleButtonPass(item, 'pinStepPass')}
/>
</Box>
</CardWrapper>
</GenuineCheckCardWrapper>
</Box>
<Box mt={3}>
<CardWrapper isDisabled={!genuine.pinStepPass}>
<GenuineCheckCardWrapper isDisabled={!genuine.pinStepPass}>
<Box justify="center">
<Box horizontal>
<IconOptionRow color={!genuine.pinStepPass ? 'grey' : 'wallet'}>
@ -209,10 +208,10 @@ class GenuineCheck extends PureComponent<StepProps, State> {
/>
)}
</Box>
</CardWrapper>
</GenuineCheckCardWrapper>
</Box>
<Box mt={3}>
<CardWrapper isDisabled={!genuine.recoveryStepPass}>
<GenuineCheckCardWrapper isDisabled={!genuine.recoveryStepPass}>
<Box justify="center">
<Box horizontal>
<IconOptionRow color={!genuine.recoveryStepPass ? 'grey' : 'wallet'}>
@ -226,29 +225,16 @@ class GenuineCheck extends PureComponent<StepProps, State> {
{genuine.isDeviceGenuine ? (
<Box horizontal align="center" flow={1} color={colors.wallet}>
<IconCheck size={16} />
<GenuineSuccessText>
<Box ff="Open Sans|SemiBold" fontSize={4}>
{t('onboarding:genuineCheck.isGenuinePassed')}
</GenuineSuccessText>
</Box>
) : genuine.genuineCheckUnavailable ? (
<Box align="center" flow={1} color={colors.alertRed}>
<FakeLink
ff="Open Sans|Regular"
fontSize={4}
underline
onClick={this.handleOpenGenuineCheckModal}
>
{t('app:common.retry')}
</FakeLink>
<Box horizontal justify="center">
<Box justifyContent="center">
<IconCross size={12} />
</Box>
<Box ff="Open Sans|Regular" fontSize={2} ml={1}>
<TranslatedError error={genuine.genuineCheckUnavailable} />
</Box>
</Box>
</Box>
) : genuine.genuineCheckUnavailable ? (
<GenuineCheckUnavailableMessage
handleOpenGenuineCheckModal={this.handleOpenGenuineCheckModal}
onboarding={onboarding}
t={t}
/>
) : (
<Button
primary
@ -260,28 +246,13 @@ class GenuineCheck extends PureComponent<StepProps, State> {
)}
</Box>
)}
</CardWrapper>
</GenuineCheckCardWrapper>
</Box>
</StepContainerInner>
{genuine.genuineCheckUnavailable ? (
<OnboardingFooterWrapper>
<Button padded outlineGrey onClick={() => prevStep()}>
{t('app:common.back')}
</Button>
<Box horizontal ml="auto">
<Button padded disabled={false} onClick={() => nextStep()} mx={2}>
{t('app:common.skipThisStep')}
</Button>
<Button padded onClick={nextStep} disabled primary>
{t('app:common.continue')}
</Button>
</Box>
</OnboardingFooterWrapper>
<GenuineCheckUnavailableFooter nextStep={nextStep} prevStep={prevStep} t={t} />
) : (
<OnboardingFooter
horizontal
align="center"
flow={2}
t={t}
nextStep={nextStep}
prevStep={prevStep}
@ -306,79 +277,9 @@ export default connect(
mapDispatchToProps,
)(GenuineCheck)
// TODO extract to a separate file
export function GenuineCheckFail({
redoGenuineCheck,
contactSupport,
isLedgerNano,
t,
}: {
redoGenuineCheck: () => void,
contactSupport: () => void,
isLedgerNano: boolean | null,
t: T,
}) {
return (
<Box sticky pt={50}>
<Box grow alignItems="center" justifyContent="center">
{isLedgerNano ? (
<Fragment>
<Title>{t('onboarding:genuineCheck.errorPage.ledgerNano.title')}</Title>
<Description>{t('onboarding:genuineCheck.errorPage.ledgerNano.desc')}</Description>
<Box style={{ width: 550 }} mt={5} ml={100}>
<IconLedgerNanoError />
</Box>
</Fragment>
) : (
<Fragment>
<Title>{t('onboarding:genuineCheck.errorPage.ledgerBlue.title')}</Title>
<Description pb={5}>
{t('onboarding:genuineCheck.errorPage.ledgerBlue.desc')}
</Description>
<Box alignItems="center">
<IconLedgerBlueError />
</Box>
</Fragment>
)}
</Box>
<OnboardingFooterWrapper>
<Button padded outlineGrey onClick={() => redoGenuineCheck()}>
{t('app:common.back')}
</Button>
<Button padded danger onClick={() => contactSupport()} ml="auto">
{t('onboarding:genuineCheck.buttons.contactSupport')}
</Button>
</OnboardingFooterWrapper>
</Box>
)
}
export const GenuineSuccessText = styled(Box).attrs({
ff: 'Open Sans|SemiBold',
fontSize: 4,
})``
export const CardTitle = styled(Box).attrs({
ff: 'Open Sans|SemiBold',
fontSize: 4,
textAlign: 'left',
pl: 2,
})``
const CardWrapper = styled(Box).attrs({
horizontal: true,
p: 5,
borderRadius: '4px',
justify: 'space-between',
})`
width: 580px;
height: 74px;
transition: all ease-in-out 0.2s;
color: ${p => (p.isDisabled ? p.theme.colors.grey : p.theme.colors.black)};
border: ${p => `1px ${p.isDisabled ? 'dashed' : 'solid'} ${p.theme.colors.fog}`};
pointer-events: ${p => (p.isDisabled ? 'none' : 'auto')};
background-color: ${p => (p.isDisabled ? p.theme.colors.lightGrey : p.theme.colors.white)};
opacity: ${p => (p.isDisabled ? 0.7 : 1)};
&:hover {
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.05);
}
`

9
src/components/Onboarding/steps/SelectDevice.js

@ -3,14 +3,15 @@
import React, { PureComponent } from 'react'
import styled from 'styled-components'
import { connect } from 'react-redux'
import { i } from 'helpers/staticPath'
import { rgba } from 'styles/helpers'
import { isLedgerNano } from 'reducers/onboarding'
import Box from 'components/base/Box'
import IconCheckCirle from 'icons/Check'
import IconLedgerNano from 'icons/illustrations/LedgerNano'
import IconLedgerBlue from 'icons/illustrations/LedgerBlue'
import { Title, Inner, FixedTopContainer, StepContainerInner } from '../helperComponents'
import OnboardingFooter from '../OnboardingFooter'
@ -47,7 +48,7 @@ class SelectDevice extends PureComponent<StepProps, {}> {
>
{onboarding.isLedgerNano && <DeviceSelected />}
<DeviceIcon>
<IconLedgerNano />
<img alt="" src={i('ledger-nano-onb.svg')} />
</DeviceIcon>
<BlockTitle>{t('onboarding:selectDevice.ledgerNanoCard.title')}</BlockTitle>
</DeviceContainer>
@ -57,7 +58,7 @@ class SelectDevice extends PureComponent<StepProps, {}> {
>
{!onboarding.isLedgerNano && onboarding.isLedgerNano !== null && <DeviceSelected />}
<DeviceIcon>
<IconLedgerBlue />
<img alt="" src={i('ledger-blue-onb.svg')} />
</DeviceIcon>
<BlockTitle>{t('onboarding:selectDevice.ledgerBlueCard.title')}</BlockTitle>
</DeviceContainer>

4
src/components/Onboarding/steps/SelectPIN/SelectPINblue.js

@ -2,11 +2,11 @@
import React, { PureComponent } from 'react'
import { translate } from 'react-i18next'
import { colors } from 'styles/theme'
import { i } from 'helpers/staticPath'
import Box from 'components/base/Box'
import type { T } from 'types/common'
import IconLedgerBlueSelectPIN from 'icons/illustrations/LedgerBlueSelectPIN'
import IconChevronRight from 'icons/ChevronRight'
@ -60,7 +60,7 @@ class SelectPIN extends PureComponent<Props, *> {
<Box align="center">
<Inner style={{ width: 550 }}>
<Box style={{ width: 180, justifyContent: 'center', alignItems: 'center' }}>
<IconLedgerBlueSelectPIN />
<img alt="" src={i('select-pin-blue-onb.svg')} />
</Box>
<Box>
<Box shrink grow flow={4}>

4
src/components/Onboarding/steps/SelectPIN/SelectPINnano.js

@ -2,11 +2,11 @@
import React, { PureComponent } from 'react'
import { translate } from 'react-i18next'
import { colors } from 'styles/theme'
import { i } from 'helpers/staticPath'
import Box from 'components/base/Box'
import type { T } from 'types/common'
import IconLedgerNanoSelectPIN from 'icons/illustrations/LedgerNanoSelectPIN'
import IconChevronRight from 'icons/ChevronRight'
@ -63,7 +63,7 @@ class SelectPINnano extends PureComponent<Props, *> {
return (
<Box align="center" mt={3}>
<Inner style={{ width: 700 }}>
<IconLedgerNanoSelectPIN />
<img alt="" src={i('select-pin-nano-onb.svg')} />
<Box shrink grow flow={4} style={{ marginLeft: 40 }}>
{stepsLedgerNano.map(step => <OptionRow key={step.key} step={step} />)}
</Box>

4
src/components/Onboarding/steps/SelectPIN/SelectPINrestoreBlue.js

@ -2,11 +2,11 @@
import React, { PureComponent } from 'react'
import { translate } from 'react-i18next'
import { colors } from 'styles/theme'
import { i } from 'helpers/staticPath'
import Box from 'components/base/Box'
import type { T } from 'types/common'
import IconLedgerBlueSelectPIN from 'icons/illustrations/LedgerBlueSelectPIN'
import IconChevronRight from 'icons/ChevronRight'
@ -60,7 +60,7 @@ class SelectPINrestoreBlue extends PureComponent<Props, *> {
<Box align="center">
<Inner style={{ width: 550 }}>
<Box style={{ width: 180, justifyContent: 'center', alignItems: 'center' }}>
<IconLedgerBlueSelectPIN />
<img alt="" src={i('select-pin-blue-onb.svg')} />
</Box>
<Box>
<Box shrink grow flow={4}>

4
src/components/Onboarding/steps/SelectPIN/SelectPINrestoreNano.js

@ -2,11 +2,11 @@
import React, { PureComponent } from 'react'
import { translate } from 'react-i18next'
import { colors } from 'styles/theme'
import { i } from 'helpers/staticPath'
import Box from 'components/base/Box'
import type { T } from 'types/common'
import IconLedgerNanoSelectPIN from 'icons/illustrations/LedgerNanoSelectPIN'
import IconChevronRight from 'icons/ChevronRight'
@ -63,7 +63,7 @@ class SelectPINrestoreNano extends PureComponent<Props, *> {
return (
<Box align="center" mt={3}>
<Inner style={{ width: 700 }}>
<IconLedgerNanoSelectPIN />
<img alt="" src={i('select-pin-nano-onb.svg')} />
<Box shrink grow flow={4} style={{ marginLeft: 40 }}>
{stepsLedgerNano.map(step => <OptionRow key={step.key} step={step} />)}
</Box>

4
src/components/Onboarding/steps/Start.js

@ -1,11 +1,11 @@
// @flow
import React from 'react'
import { i } from 'helpers/staticPath'
import Box from 'components/base/Box'
import Button from 'components/base/Button'
import IconGetStarted from 'icons/illustrations/GetStartedLogo'
import type { StepProps } from '..'
import { Title } from '../helperComponents'
@ -14,7 +14,7 @@ export default (props: StepProps) => {
return (
<Box sticky justifyContent="center">
<Box alignItems="center">
<IconGetStarted />
<img alt="" src={i('get-started-onb.svg')} />
<Box my={4}>
<Title>{t('onboarding:start.title')}</Title>
</Box>

4
src/components/Onboarding/steps/WriteSeed/WriteSeedBlue.js

@ -2,11 +2,11 @@
import React, { PureComponent, Fragment } from 'react'
import { translate } from 'react-i18next'
import { colors } from 'styles/theme'
import { i } from 'helpers/staticPath'
import type { T } from 'types/common'
import Box from 'components/base/Box'
import IconWriteSeed from 'icons/illustrations/WriteSeed'
import IconChevronRight from 'icons/ChevronRight'
import {
@ -75,7 +75,7 @@ class WriteSeedBlue extends PureComponent<Props, *> {
<Box align="center">
<Inner style={{ width: 760 }}>
<Box style={{ width: 260, justifyContent: 'center', alignItems: 'center' }}>
<IconWriteSeed />
<img alt="" src={i('write-seed-onb.svg')} />
</Box>
<Box shrink flow={2} m={0}>
{steps.map(step => <OptionRow key={step.key} step={step} />)}

4
src/components/Onboarding/steps/WriteSeed/WriteSeedNano.js

@ -2,11 +2,11 @@
import React, { PureComponent, Fragment } from 'react'
import { translate } from 'react-i18next'
import { colors } from 'styles/theme'
import { i } from 'helpers/staticPath'
import type { T } from 'types/common'
import Box from 'components/base/Box'
import IconWriteSeed from 'icons/illustrations/WriteSeed'
import IconChevronRight from 'icons/ChevronRight'
import {
@ -75,7 +75,7 @@ class WriteSeedNano extends PureComponent<Props, *> {
<Box align="center" mt={3}>
<Inner style={{ width: 700 }}>
<Box style={{ width: 300 }} justifyContent="center" alignItems="center">
<IconWriteSeed />
<img alt="" src={i('write-seed-onb.svg')} />
</Box>
<Box shrink grow flow={4}>

4
src/components/Onboarding/steps/WriteSeed/WriteSeedRestore.js

@ -2,11 +2,11 @@
import React, { PureComponent, Fragment } from 'react'
import { translate } from 'react-i18next'
import { colors } from 'styles/theme'
import { i } from 'helpers/staticPath'
import Box from 'components/base/Box'
import type { T } from 'types/common'
import IconWriteSeed from 'icons/illustrations/WriteSeed'
import type { OnboardingState } from 'reducers/onboarding'
import IconChevronRight from 'icons/ChevronRight'
@ -100,7 +100,7 @@ class WriteSeedRestore extends PureComponent<Props, *> {
<Box align="center">
<Inner style={{ width: 760 }}>
<Box style={{ width: 260, justifyContent: 'center', alignItems: 'center' }}>
<IconWriteSeed />
<img alt="" src={i('write-seed-onb.svg')} />
</Box>
{onboarding.isLedgerNano ? (
<Box shrink flow={2} m={0}>

72
src/components/RenderError.js

@ -1,6 +1,7 @@
// @flow
import React, { PureComponent } from 'react'
import styled from 'styled-components'
import { shell, remote } from 'electron'
import qs from 'querystring'
import { translate } from 'react-i18next'
@ -14,19 +15,35 @@ import ExportLogsBtn from 'components/ExportLogsBtn'
import Box from 'components/base/Box'
import Space from 'components/base/Space'
import Button from 'components/base/Button'
import ConfirmModal from 'components/base/Modal/ConfirmModal'
import IconTriangleWarning from 'icons/TriangleWarning'
import { IconWrapperCircle } from './SettingsPage/sections/Profile'
type Props = {
error: Error,
t: T,
disableExport?: boolean,
withoutAppData?: boolean,
children?: *,
}
class RenderError extends PureComponent<Props, { isHardResetting: boolean }> {
class RenderError extends PureComponent<
Props,
{ isHardResetting: boolean, isHardResetModalOpened: boolean },
> {
state = {
isHardResetting: false,
isHardResetModalOpened: false,
}
handleOpenHardResetModal = () => this.setState({ isHardResetModalOpened: true })
handleCloseHardResetModal = () => this.setState({ isHardResetModalOpened: false })
hardResetIconRender = () => (
<IconWrapperCircle color="alertRed">
<IconTriangleWarning width={23} height={21} />
</IconWrapperCircle>
)
handleCreateIssue = () => {
const { error } = this.props
if (!error) {
@ -59,8 +76,8 @@ ${error.stack}
}
render() {
const { error, t, disableExport, children } = this.props
const { isHardResetting } = this.state
const { error, t, withoutAppData, children } = this.props
const { isHardResetting, isHardResetModalOpened } = this.state
return (
<Box align="center" grow>
<Space of={100} />
@ -84,19 +101,28 @@ ${error.stack}
<Button primary onClick={this.handleRestart}>
{t('app:crash.restart')}
</Button>
{!disableExport ? <ExportLogsBtn /> : null}
<ExportLogsBtn withoutAppData={withoutAppData} />
<Button primary onClick={this.handleCreateIssue}>
{t('app:crash.createTicket')}
</Button>
<Button danger onClick={this.handleHardReset} isLoading={isHardResetting}>
<Button danger onClick={this.handleOpenHardResetModal}>
{t('app:crash.reset')}
</Button>
</Box>
<ConfirmModal
isDanger
isLoading={isHardResetting}
isOpened={isHardResetModalOpened}
onClose={this.handleCloseHardResetModal}
onReject={this.handleCloseHardResetModal}
onConfirm={this.handleHardReset}
title={t('app:settings.hardResetModal.title')}
desc={t('app:settings.hardResetModal.desc')}
renderIcon={this.hardResetIconRender}
/>
<Box my={6}>
<ErrContainer>
<strong>{String(error)}</strong>
<div>{error.stack || 'no stacktrace'}</div>
</ErrContainer>
<ErrContainer>{`${String(error)}
${error.stack || 'no stacktrace'}`}</ErrContainer>
</Box>
<pre
style={{
@ -115,21 +141,15 @@ ${error.stack}
}
}
const ErrContainer = ({ children }: { children: any }) => (
<pre
style={{
margin: 'auto',
maxWidth: '80vw',
overflow: 'auto',
fontSize: 10,
fontFamily: 'monospace',
cursor: 'text',
userSelect: 'text',
opacity: 0.3,
}}
>
{children}
</pre>
)
const ErrContainer = styled.pre`
margin: auto;
max-width: 80vw;
overflow: auto;
font-size: 10px;
font-family: monospace;
cursor: text;
user-select: text;
opacity: 0.3;
`
export default translate()(RenderError)

2
src/components/RequestAmount/index.js

@ -21,7 +21,7 @@ import InputCurrency from 'components/base/InputCurrency'
import Button from 'components/base/Button'
import Box from 'components/base/Box'
import type { State } from 'reducers'
import createCustomErrorClass from 'helpers/createCustomErrorClass'
import { createCustomErrorClass } from 'helpers/errors'
const NotEnoughBalance = createCustomErrorClass('NotEnoughBalance')

8
src/components/SelectAccount/index.js

@ -43,7 +43,13 @@ const renderOption = a => {
</Text>
</Box>
<Box>
<FormattedVal color="grey" val={account.balance} unit={account.unit} showCode />
<FormattedVal
color="grey"
val={account.balance}
unit={account.unit}
showCode
disableRounding
/>
</Box>
</Box>
)

2
src/components/SettingsPage/sections/Currencies.js

@ -112,7 +112,7 @@ class TabCurrencies extends PureComponent<Props, State> {
<Body>
{currency !== intermediaryCurrency ? (
<Row
title={t('app:settings.display.exchange', {
title={t('app:settings.currencies.exchange', {
ticker: `${currency.ticker}${intermediaryCurrency.ticker}`,
})}
desc={t('app:settings.currencies.exchangeDesc')}

2
src/components/SettingsPage/sections/Profile.js

@ -263,7 +263,7 @@ export default connect(
)(TabProfile)
// TODO: need a helper file for common styles across the app
const IconWrapperCircle = styled(Box).attrs({})`
export const IconWrapperCircle = styled(Box).attrs({})`
width: 50px;
height: 50px;
border-radius: 50%;

78
src/components/TopBar/ActivityIndicator.js

@ -10,6 +10,7 @@ import type { T } from 'types/common'
import type { AsyncState } from 'reducers/bridgeSync'
import { globalSyncStateSelector } from 'reducers/bridgeSync'
import { isUpToDateSelector } from 'reducers/accounts'
import { BridgeSyncConsumer } from 'bridge/BridgeSyncContext'
import CounterValues from 'helpers/countervalues'
@ -24,78 +25,45 @@ import ItemContainer from './ItemContainer'
const mapStateToProps = createStructuredSelector({
globalSyncState: globalSyncStateSelector,
isUpToDate: isUpToDateSelector,
})
type Props = {
// FIXME: eslint should see that it is used in static method
isGlobalSyncStatePending: boolean, // eslint-disable-line react/no-unused-prop-types
error: ?Error,
isPending: boolean,
isError: boolean,
isUpToDate: boolean,
t: T,
cvPoll: *,
setSyncBehavior: *,
}
type State = {
hasClicked: boolean,
isGlobalSyncStatePending: boolean,
isFirstSync: boolean,
}
class ActivityIndicatorInner extends PureComponent<Props, State> {
state = {
hasClicked: false,
isFirstSync: true,
// FIXME: eslint should see that it is used in static method
isGlobalSyncStatePending: false, // eslint-disable-line react/no-unused-state
}
static getDerivedStateFromProps(nextProps: Props, prevState: State) {
const nextState = {
...prevState,
isGlobalSyncStatePending: nextProps.isGlobalSyncStatePending,
}
if (prevState.isGlobalSyncStatePending && !nextProps.isGlobalSyncStatePending) {
nextState.isFirstSync = false
nextState.hasClicked = false
}
return nextState
}
class ActivityIndicatorInner extends PureComponent<Props> {
onClick = () => {
this.props.cvPoll()
this.props.setSyncBehavior({ type: 'SYNC_ALL_ACCOUNTS', priority: 5 })
}
handleRefresh = () => {
this.setState({ hasClicked: true })
this.onClick()
}
render() {
const { isPending, isError, error, t } = this.props
const { hasClicked, isFirstSync } = this.state
const isDisabled = isError || (isPending && (isFirstSync || hasClicked))
const isRotating = isPending && (hasClicked || isFirstSync)
const { isUpToDate, isPending, isError, error, t } = this.props
const isDisabled = isError || isPending
const isRotating = isPending
const content = (
<ItemContainer disabled={isDisabled} onClick={isDisabled ? undefined : this.handleRefresh}>
<ItemContainer disabled={isDisabled} onClick={isDisabled ? undefined : this.onClick}>
<Rotating
size={16}
isRotating={isRotating}
color={isError ? 'alertRed' : isRotating ? 'grey' : 'positiveGreen'}
color={isError ? 'alertRed' : isRotating ? 'grey' : isUpToDate ? 'positiveGreen' : 'grey'}
>
{isError ? (
<IconExclamationCircle size={16} />
) : isRotating ? (
<IconLoader size={16} />
) : (
) : isUpToDate ? (
<IconCheckCircle size={16} />
) : (
<IconExclamationCircle size={16} />
)}
</Rotating>
<Box
@ -115,19 +83,21 @@ class ActivityIndicatorInner extends PureComponent<Props, State> {
ml={2}
cursor="pointer"
style={{ textDecoration: 'underline', pointerEvents: 'all' }}
onClick={this.handleRefresh}
onClick={this.onClick}
>
{t('app:common.sync.refresh')}
</Box>
</Fragment>
) : (
) : isUpToDate ? (
t('app:common.sync.upToDate')
) : (
t('app:common.sync.outdated')
)}
</Box>
</ItemContainer>
)
if (error) {
if (isError && error) {
return (
<Tooltip
tooltipBg="alertRed"
@ -146,7 +116,15 @@ class ActivityIndicatorInner extends PureComponent<Props, State> {
}
}
const ActivityIndicator = ({ globalSyncState, t }: { globalSyncState: AsyncState, t: T }) => (
const ActivityIndicator = ({
globalSyncState,
t,
isUpToDate,
}: {
globalSyncState: AsyncState,
t: T,
isUpToDate: boolean,
}) => (
<BridgeSyncConsumer>
{setSyncBehavior => (
<CounterValues.PollingConsumer>
@ -156,9 +134,9 @@ const ActivityIndicator = ({ globalSyncState, t }: { globalSyncState: AsyncState
return (
<ActivityIndicatorInner
t={t}
isUpToDate={isUpToDate}
isPending={isPending}
isGlobalSyncStatePending={globalSyncState.pending}
isError={!!isError}
isError={!!isError && !isUpToDate} // we only show error if it's not up to date. this hide a bit error that happen from time to time
error={isError ? globalSyncState.error : null}
cvPoll={cvPolling.poll}
setSyncBehavior={setSyncBehavior}

7
src/components/Workflow/EnsureDashboard.js

@ -7,12 +7,7 @@ import type { Device } from 'types/common'
import getDeviceInfo from 'commands/getDeviceInfo'
type DeviceInfo = {
targetId: number | string,
version: string,
final: boolean,
mcu: boolean,
}
import type { DeviceInfo } from 'helpers/devices/getDeviceInfo'
type Error = {
message: string,

17
src/components/Workflow/EnsureGenuine.js

@ -5,6 +5,7 @@ import isEqual from 'lodash/isEqual'
import { GENUINE_TIMEOUT } from 'config/constants'
import type { Device } from 'types/common'
import type { DeviceInfo } from 'helpers/devices/getDeviceInfo'
import getIsGenuine from 'commands/getIsGenuine'
@ -13,14 +14,9 @@ type Error = {
stack: string,
}
type DeviceInfos = {
targetId: number | string,
version: string,
}
type Props = {
device: ?Device,
infos: ?DeviceInfos,
deviceInfo: ?DeviceInfo,
children: (isGenuine: ?boolean, error: ?Error) => *,
}
@ -56,12 +52,15 @@ class EnsureGenuine extends PureComponent<Props, State> {
_unmounting = false
async checkIsGenuine() {
const { device, infos } = this.props
if (device && infos && !this._checking) {
const { device, deviceInfo } = this.props
if (device && deviceInfo && !this._checking) {
this._checking = true
try {
const res = await getIsGenuine
.send({ devicePath: device.path, targetId: infos.targetId, version: infos.version })
.send({
devicePath: device.path,
deviceInfo,
})
.pipe(timeout(GENUINE_TIMEOUT))
.toPromise()
if (this._unmounting) return

14
src/components/Workflow/index.js

@ -1,6 +1,7 @@
// @flow
import React, { PureComponent } from 'react'
import type { DeviceInfo } from 'helpers/devices/getDeviceInfo'
import type { Node } from 'react'
import type { Device } from 'types/common'
@ -9,13 +10,6 @@ import EnsureDevice from './EnsureDevice'
import EnsureDashboard from './EnsureDashboard'
import EnsureGenuine from './EnsureGenuine'
type DeviceInfo = {
targetId: number | string,
version: string,
final: boolean,
mcu: boolean,
}
type Error = {
message: string,
stack: string,
@ -55,16 +49,16 @@ class Workflow extends PureComponent<Props, State> {
{(device: Device) => (
<EnsureDashboard device={device}>
{(deviceInfo: ?DeviceInfo, dashboardError: ?Error) => {
if (deviceInfo && deviceInfo.mcu && renderMcuUpdate) {
if (deviceInfo && deviceInfo.isBootloader && renderMcuUpdate) {
return renderMcuUpdate(device, deviceInfo)
}
if (deviceInfo && deviceInfo.final && renderFinalUpdate) {
if (deviceInfo && deviceInfo.isOSU && renderFinalUpdate) {
return renderFinalUpdate(device, deviceInfo)
}
return (
<EnsureGenuine device={device} infos={deviceInfo}>
<EnsureGenuine device={device} deviceInfo={deviceInfo}>
{(isGenuine: ?boolean, genuineError: ?Error) => {
if (dashboardError || genuineError) {
return renderError

2
src/components/base/Box/Box.js

@ -17,6 +17,7 @@ import {
import fontFamily from 'styles/styled/fontFamily'
export const styledTextAlign = style({ prop: 'textAlign', cssProperty: 'textAlign' })
export const styledOverflow = style({ prop: 'overflow', cssProperty: 'overflow' })
export const styledCursor = style({ prop: 'cursor', cssProperty: 'cursor' })
export const styledTextTransform = style({ prop: 'textTransform', cssProperty: 'textTransform' })
@ -34,6 +35,7 @@ export default styled.div`
${styledTextAlign};
${styledCursor};
${styledTextTransform};
${styledOverflow};
display: flex;
flex-shrink: ${p => (p.noShrink === true ? '0' : p.shrink === true ? '1' : '')};

2
src/components/base/SideBar/SideBarListItem.js

@ -6,7 +6,7 @@ import styled from 'styled-components'
import Box, { Tabbable } from 'components/base/Box'
export type Props = {
label: string | (Props => React$Element<any>),
label: string | (Props => React$Node),
desc?: Props => any, // TODO: type should be more precise, but, eh ¯\_(ツ)_/¯
icon?: any, // TODO: type should be more precise, but, eh ¯\_(ツ)_/¯
disabled?: boolean,

81
src/components/base/Stepper/index.js

@ -0,0 +1,81 @@
// @flow
import React, { PureComponent } from 'react'
import invariant from 'invariant'
import { translate } from 'react-i18next'
import type { T } from 'types/common'
import { ModalContent, ModalTitle, ModalFooter, ModalBody } from 'components/base/Modal'
import Breadcrumb from 'components/Breadcrumb'
type Props = {
t: T,
title: string,
initialStepId: string,
onClose: void => void,
steps: Step[],
children: any,
}
export type Step = {
id: string,
label: string,
component: StepProps => React$Node,
footer: StepProps => React$Node,
preventClose?: boolean,
onBack?: StepProps => void,
}
type State = {
stepId: string,
}
export type StepProps = {
t: T,
transitionTo: string => void,
}
class Stepper extends PureComponent<Props, State> {
state = {
stepId: this.props.initialStepId,
}
transitionTo = stepId => this.setState({ stepId })
render() {
const { t, steps, title, onClose, children, ...props } = this.props
const { stepId } = this.state
const stepIndex = steps.findIndex(s => s.id === stepId)
const step = steps[stepIndex]
invariant(step, `Stepper: step ${stepId} doesn't exists`)
const { component: StepComponent, footer: StepFooter, onBack, preventClose } = step
const stepProps: StepProps = {
t,
transitionTo: this.transitionTo,
...props,
}
return (
<ModalBody onClose={preventClose ? undefined : onClose}>
<ModalTitle onBack={onBack ? () => onBack(stepProps) : undefined}>{title}</ModalTitle>
<ModalContent>
<Breadcrumb mb={6} currentStep={stepIndex} items={steps} />
<StepComponent {...stepProps} />
{children}
</ModalContent>
{StepFooter && (
<ModalFooter horizontal align="center" justify="flex-end">
<StepFooter {...stepProps} />
</ModalFooter>
)}
</ModalBody>
)
}
}
export default translate()(Stepper)

50
src/components/base/Stepper/stories.js

@ -0,0 +1,50 @@
// @flow
import React from 'react'
import { storiesOf } from '@storybook/react'
import { action } from '@storybook/addon-actions'
import Stepper from 'components/base/Stepper'
import Button from 'components/base/Button'
import type { StepProps, Step } from 'components/base/Stepper'
const stories = storiesOf('Components/base', module)
const steps: Step[] = [
{
id: 'first',
label: 'first step',
component: () => <div>first step</div>,
footer: ({ transitionTo }: StepProps) => (
<div>
<Button primary onClick={() => transitionTo('second')}>
Click to go next
</Button>
</div>
),
},
{
id: 'second',
label: 'second step',
preventClose: true,
onBack: ({ transitionTo }: StepProps) => transitionTo('first'),
component: () => <div>second step (you cant close on this one)</div>,
footer: ({ transitionTo }: StepProps) => (
<div>
<Button primary onClick={() => transitionTo('first')}>
Click to go prev
</Button>
</div>
),
},
]
stories.add('Stepper', () => (
<Stepper
onClose={action('onClose')}
title="Stepper component"
steps={steps}
initialStepId="first"
/>
))

208
src/components/modals/AddAccounts/index.js

@ -1,6 +1,5 @@
// @flow
import invariant from 'invariant'
import React, { PureComponent } from 'react'
import { compose } from 'redux'
import { connect } from 'react-redux'
@ -13,59 +12,66 @@ import type { Currency, Account } from '@ledgerhq/live-common/lib/types'
import { MODAL_ADD_ACCOUNTS } from 'config/constants'
import type { T, Device } from 'types/common'
import type { StepProps as DefaultStepProps, Step } from 'components/base/Stepper'
import { idleCallback } from 'helpers/promise'
import { getCurrentDevice } from 'reducers/devices'
import { accountsSelector } from 'reducers/accounts'
import { addAccount } from 'actions/accounts'
import { closeModal } from 'reducers/modals'
import Modal, { ModalContent, ModalTitle, ModalFooter, ModalBody } from 'components/base/Modal'
import Box from 'components/base/Box'
import Breadcrumb from 'components/Breadcrumb'
import Modal from 'components/base/Modal'
import Stepper from 'components/base/Stepper'
import StepChooseCurrency, { StepChooseCurrencyFooter } from './steps/01-step-choose-currency'
import StepConnectDevice, { StepConnectDeviceFooter } from './steps/02-step-connect-device'
import StepImport, { StepImportFooter } from './steps/03-step-import'
import StepFinish from './steps/04-step-finish'
const createSteps = ({ t }: { t: T }) => [
{
id: 'chooseCurrency',
label: t('app:addAccounts.breadcrumb.informations'),
component: StepChooseCurrency,
footer: StepChooseCurrencyFooter,
onBack: null,
hideFooter: false,
},
{
id: 'connectDevice',
label: t('app:addAccounts.breadcrumb.connectDevice'),
component: StepConnectDevice,
footer: StepConnectDeviceFooter,
onBack: ({ transitionTo }: StepProps) => transitionTo('chooseCurrency'),
hideFooter: false,
},
{
id: 'import',
label: t('app:addAccounts.breadcrumb.import'),
component: StepImport,
footer: StepImportFooter,
onBack: ({ transitionTo }: StepProps) => transitionTo('chooseCurrency'),
hideFooter: false,
},
{
id: 'finish',
label: t('app:addAccounts.breadcrumb.finish'),
component: StepFinish,
footer: null,
onBack: null,
hideFooter: true,
},
]
const createSteps = ({ t }: { t: T }) => {
const onBack = ({ transitionTo, resetScanState }: StepProps) => {
resetScanState()
transitionTo('chooseCurrency')
}
return [
{
id: 'chooseCurrency',
label: t('app:addAccounts.breadcrumb.informations'),
component: StepChooseCurrency,
footer: StepChooseCurrencyFooter,
onBack: null,
hideFooter: false,
},
{
id: 'connectDevice',
label: t('app:addAccounts.breadcrumb.connectDevice'),
component: StepConnectDevice,
footer: StepConnectDeviceFooter,
onBack,
hideFooter: false,
},
{
id: 'import',
label: t('app:addAccounts.breadcrumb.import'),
component: StepImport,
footer: StepImportFooter,
onBack,
hideFooter: false,
},
{
id: 'finish',
label: t('app:addAccounts.breadcrumb.finish'),
component: StepFinish,
footer: null,
onBack: null,
hideFooter: true,
},
]
}
type Props = {
t: T,
currentDevice: ?Device,
device: ?Device,
existingAccounts: Account[],
closeModal: string => void,
addAccount: Account => void,
@ -75,37 +81,39 @@ type StepId = 'chooseCurrency' | 'connectDevice' | 'import' | 'finish'
type ScanStatus = 'idle' | 'scanning' | 'error' | 'finished'
type State = {
stepId: StepId,
// TODO: I'm sure there will be always StepId and ScanStatus given,
// but I struggle making flow understand it. So I put string as fallback
stepId: StepId | string,
scanStatus: ScanStatus | string,
isAppOpened: boolean,
currency: ?Currency,
// scan process
scannedAccounts: Account[],
checkedAccountsIds: string[],
scanStatus: ScanStatus,
err: ?Error,
}
export type StepProps = {
export type StepProps = DefaultStepProps & {
t: T,
currency: ?Currency,
currentDevice: ?Device,
device: ?Device,
isAppOpened: boolean,
transitionTo: StepId => void,
setState: any => void,
onClickAdd: void => Promise<void>,
onCloseModal: void => void,
// scan process
scannedAccounts: Account[],
existingAccounts: Account[],
checkedAccountsIds: string[],
scanStatus: ScanStatus,
err: ?Error,
onClickAdd: void => Promise<void>,
onCloseModal: void => void,
resetScanState: void => void,
setCurrency: (?Currency) => void,
setAppOpened: boolean => void,
setScanStatus: (ScanStatus, ?Error) => string,
setScannedAccounts: ({ scannedAccounts?: Account[], checkedAccountsIds?: string[] }) => void,
}
const mapStateToProps = createStructuredSelector({
currentDevice: getCurrentDevice,
device: getCurrentDevice,
existingAccounts: accountsSelector,
})
@ -126,18 +134,7 @@ const INITIAL_STATE = {
class AddAccounts extends PureComponent<Props, State> {
state = INITIAL_STATE
STEPS = createSteps({
t: this.props.t,
})
transitionTo = stepId => {
const { currency } = this.state
let nextState = { stepId }
if (stepId === 'chooseCurrency') {
nextState = { ...INITIAL_STATE, currency }
}
this.setState(nextState)
}
STEPS = createSteps({ t: this.props.t })
handleClickAdd = async () => {
const { addAccount } = this.props
@ -151,16 +148,43 @@ class AddAccounts extends PureComponent<Props, State> {
await idleCallback()
addAccount(accountsToAdd[i])
}
this.transitionTo('finish')
}
handleCloseModal = () => {
const { closeModal } = this.props
closeModal(MODAL_ADD_ACCOUNTS)
handleCloseModal = () => this.props.closeModal(MODAL_ADD_ACCOUNTS)
handleStepChange = (step: Step) => this.setState({ stepId: step.id })
handleSetCurrency = (currency: ?Currency) => this.setState({ currency })
handleSetScanStatus = (scanStatus: string, err: ?Error = null) => {
this.setState({ scanStatus, err })
}
handleSetScannedAccounts = ({
checkedAccountsIds,
scannedAccounts,
}: {
checkedAccountsIds: string[],
scannedAccounts: Account[],
}) => {
this.setState({
...(checkedAccountsIds ? { checkedAccountsIds } : {}),
...(scannedAccounts ? { scannedAccounts } : {}),
})
}
handleResetScanState = () => {
this.setState({
scanStatus: 'idle',
err: null,
scannedAccounts: [],
checkedAccountsIds: [],
})
}
handleSetAppOpened = (isAppOpened: boolean) => this.setState({ isAppOpened })
render() {
const { t, currentDevice, existingAccounts } = this.props
const { t, device, existingAccounts } = this.props
const {
stepId,
currency,
@ -171,17 +195,9 @@ class AddAccounts extends PureComponent<Props, State> {
err,
} = this.state
const stepIndex = this.STEPS.findIndex(s => s.id === stepId)
const step = this.STEPS[stepIndex]
invariant(step, `AddAccountsModal: step ${stepId} doesn't exists`)
const { component: StepComponent, footer: StepFooter, hideFooter, onBack } = step
const stepProps: StepProps = {
t,
const addtionnalProps = {
currency,
currentDevice,
device,
existingAccounts,
scannedAccounts,
checkedAccountsIds,
@ -190,8 +206,11 @@ class AddAccounts extends PureComponent<Props, State> {
isAppOpened,
onClickAdd: this.handleClickAdd,
onCloseModal: this.handleCloseModal,
transitionTo: this.transitionTo,
setState: (...args) => this.setState(...args),
setScanStatus: this.handleSetScanStatus,
setCurrency: this.handleSetCurrency,
setScannedAccounts: this.handleSetScannedAccounts,
resetScanState: this.handleResetScanState,
setAppOpened: this.handleSetAppOpened,
}
return (
@ -200,21 +219,16 @@ class AddAccounts extends PureComponent<Props, State> {
refocusWhenChange={stepId}
onHide={() => this.setState({ ...INITIAL_STATE })}
render={({ onClose }) => (
<ModalBody onClose={onClose}>
<Stepper
title={t('app:addAccounts.title')}
initialStepId="chooseCurrency"
onStepChange={this.handleStepChange}
onClose={onClose}
steps={this.STEPS}
{...addtionnalProps}
>
<SyncSkipUnderPriority priority={100} />
<ModalTitle onBack={onBack ? () => onBack(stepProps) : void 0}>
{t('app:addAccounts.title')}
</ModalTitle>
<ModalContent>
<Breadcrumb mb={6} currentStep={stepIndex} items={this.STEPS} />
<StepComponent {...stepProps} />
</ModalContent>
{!hideFooter && (
<ModalFooter horizontal align="center" justify="flex-end" style={{ height: 80 }}>
{StepFooter ? <StepFooter {...stepProps} /> : <Box />}
</ModalFooter>
)}
</ModalBody>
</Stepper>
)}
/>
)
@ -228,7 +242,3 @@ export default compose(
),
translate(),
)(AddAccounts)
function idleCallback() {
return new Promise(resolve => window.requestIdleCallback(resolve))
}

15
src/components/modals/AddAccounts/steps/01-step-choose-currency.js

@ -1,7 +1,6 @@
// @flow
import React, { Fragment } from 'react'
import isArray from 'lodash/isArray'
import SelectCurrency from 'components/SelectCurrency'
import Button from 'components/base/Button'
@ -9,18 +8,8 @@ import CurrencyBadge from 'components/base/CurrencyBadge'
import type { StepProps } from '../index'
function StepChooseCurrency({ currency, setState }: StepProps) {
return (
<SelectCurrency
autoFocus
onChange={currency => {
setState({
currency: isArray(currency) && currency.length === 0 ? null : currency,
})
}}
value={currency}
/>
)
function StepChooseCurrency({ currency, setCurrency }: StepProps) {
return <SelectCurrency autoFocus onChange={setCurrency} value={currency} />
}
export function StepChooseCurrencyFooter({ transitionTo, currency, t }: StepProps) {

6
src/components/modals/AddAccounts/steps/02-step-connect-device.js

@ -11,7 +11,7 @@ import { CurrencyCircleIcon } from 'components/base/CurrencyBadge'
import type { StepProps } from '../index'
function StepConnectDevice({ t, currency, currentDevice, setState }: StepProps) {
function StepConnectDevice({ t, currency, device, setAppOpened }: StepProps) {
invariant(currency, 'No currency given')
return (
@ -30,11 +30,11 @@ function StepConnectDevice({ t, currency, currentDevice, setState }: StepProps)
</Box>
<ConnectDevice
t={t}
deviceSelected={currentDevice}
deviceSelected={device}
currency={currency}
onStatusChange={(deviceStatus, appStatus) => {
if (appStatus === 'success') {
setState({ isAppOpened: true })
setAppOpened(true)
}
}}
/>

81
src/components/modals/AddAccounts/steps/03-step-import.js

@ -18,19 +18,23 @@ import type { StepProps } from '../index'
class StepImport extends PureComponent<StepProps> {
componentDidMount() {
this.props.setState({ scanStatus: 'scanning' })
this.props.setScanStatus('scanning')
}
componentDidUpdate(prevProps: StepProps) {
// handle case when we click on stop sync
if (prevProps.scanStatus !== 'finished' && this.props.scanStatus === 'finished') {
this.unsub()
}
const didStartScan = prevProps.scanStatus !== 'scanning' && this.props.scanStatus === 'scanning'
const didFinishScan =
prevProps.scanStatus !== 'finished' && this.props.scanStatus === 'finished'
// handle case when we click on retry sync
if (prevProps.scanStatus !== 'scanning' && this.props.scanStatus === 'scanning') {
if (didStartScan) {
this.startScanAccountsDevice()
}
// handle case when we click on stop sync
if (didFinishScan) {
this.unsub()
}
}
componentWillUnmount() {
@ -63,15 +67,15 @@ class StepImport extends PureComponent<StepProps> {
startScanAccountsDevice() {
this.unsub()
const { currency, currentDevice, setState } = this.props
const { currency, device, setScanStatus, setScannedAccounts } = this.props
try {
invariant(currency, 'No currency to scan')
invariant(currentDevice, 'No device')
invariant(device, 'No device')
const bridge = getBridgeForCurrency(currency)
// TODO: use the real device
const devicePath = currentDevice.path
const devicePath = device.path
this.scanSubscription = bridge.scanAccountsOnDevice(currency, devicePath).subscribe({
next: account => {
@ -80,7 +84,7 @@ class StepImport extends PureComponent<StepProps> {
const hasAlreadyBeenImported = !!existingAccounts.find(a => account.id === a.id)
const isNewAccount = account.operations.length === 0
if (!hasAlreadyBeenScanned) {
setState({
setScannedAccounts({
scannedAccounts: [...scannedAccounts, this.translateName(account)],
checkedAccountsIds:
!hasAlreadyBeenImported && !isNewAccount
@ -89,43 +93,33 @@ class StepImport extends PureComponent<StepProps> {
})
}
},
complete: () => setState({ scanStatus: 'finished' }),
error: err => setState({ scanStatus: 'error', err }),
complete: () => setScanStatus('finished'),
error: err => setScanStatus('error', err),
})
} catch (err) {
setState({ scanStatus: 'error', err })
setScanStatus('error', err)
}
}
handleRetry = () => {
this.unsub()
this.handleResetState()
this.props.resetScanState()
this.startScanAccountsDevice()
}
handleResetState = () => {
const { setState } = this.props
setState({
scanStatus: 'idle',
err: null,
scannedAccounts: [],
checkedAccountsIds: [],
})
}
handleToggleAccount = (account: Account) => {
const { checkedAccountsIds, setState } = this.props
const { checkedAccountsIds, setScannedAccounts } = this.props
const isChecked = checkedAccountsIds.find(id => id === account.id) !== undefined
if (isChecked) {
setState({ checkedAccountsIds: checkedAccountsIds.filter(id => id !== account.id) })
setScannedAccounts({ checkedAccountsIds: checkedAccountsIds.filter(id => id !== account.id) })
} else {
setState({ checkedAccountsIds: [...checkedAccountsIds, account.id] })
setScannedAccounts({ checkedAccountsIds: [...checkedAccountsIds, account.id] })
}
}
handleUpdateAccount = (updatedAccount: Account) => {
const { scannedAccounts, setState } = this.props
setState({
const { scannedAccounts, setScannedAccounts } = this.props
setScannedAccounts({
scannedAccounts: scannedAccounts.map(account => {
if (account.id !== updatedAccount.id) {
return account
@ -136,19 +130,26 @@ class StepImport extends PureComponent<StepProps> {
}
handleSelectAll = () => {
const { scannedAccounts, setState } = this.props
setState({
const { scannedAccounts, setScannedAccounts } = this.props
setScannedAccounts({
checkedAccountsIds: scannedAccounts.filter(a => a.operations.length > 0).map(a => a.id),
})
}
handleUnselectAll = () => this.props.setState({ checkedAccountsIds: [] })
handleUnselectAll = () => this.props.setScannedAccounts({ checkedAccountsIds: [] })
renderError() {
const { err, t } = this.props
invariant(err, 'Trying to render inexisting error')
return (
<Box style={{ height: 200 }} align="center" justify="center" color="alertRed">
<Box
style={{ height: 200 }}
px={5}
textAlign="center"
align="center"
justify="center"
color="alertRed"
>
<IconExclamationCircleThin size={43} />
<Box mt={4}>{t('app:addAccounts.somethingWentWrong')}</Box>
<Box mt={4}>
@ -236,7 +237,8 @@ class StepImport extends PureComponent<StepProps> {
export default StepImport
export const StepImportFooter = ({
setState,
transitionTo,
setScanStatus,
scanStatus,
onClickAdd,
onCloseModal,
@ -274,18 +276,23 @@ export const StepImportFooter = ({
: t('app:common.close')
const willClose = !willCreateAccount && !willAddAccounts
const onClick = willClose ? onCloseModal : onClickAdd
const onClick = willClose
? onCloseModal
: async () => {
await onClickAdd()
transitionTo('finish')
}
return (
<Fragment>
{currency && <CurrencyBadge mr="auto" currency={currency} />}
{scanStatus === 'error' && (
<Button mr={2} onClick={() => setState({ scanStatus: 'scanning', err: null })}>
<Button mr={2} onClick={() => setScanStatus('scanning')}>
{t('app:common.retry')}
</Button>
)}
{scanStatus === 'scanning' && (
<Button mr={2} onClick={() => setState({ scanStatus: 'finished' })}>
<Button mr={2} onClick={() => setScanStatus('finished')}>
{t('app:common.stop')}
</Button>
)}

9
src/components/modals/OperationDetails.js

@ -126,7 +126,14 @@ const OperationDetails = connect(mapStateToProps)((props: Props) => {
/>
<Box my={4} alignItems="center">
<Box>
<FormattedVal unit={unit} alwaysShowSign showCode val={amount} fontSize={7} />
<FormattedVal
unit={unit}
alwaysShowSign
showCode
val={amount}
fontSize={7}
disableRounding
/>
</Box>
<Box mt={1}>
<CounterValue

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

@ -15,7 +15,7 @@ import { getBridgeForCurrency } from 'bridge'
import { accountsSelector } from 'reducers/accounts'
import { updateAccountWithUpdater } from 'actions/accounts'
import createCustomErrorClass from 'helpers/createCustomErrorClass'
import { createCustomErrorClass } from 'helpers/errors'
import { MODAL_SEND } from 'config/constants'
import Modal, { ModalBody, ModalContent, ModalTitle } from 'components/base/Modal'

7
src/config/constants.js

@ -12,6 +12,11 @@ const boolFromEnv = (key: string): boolean => {
const stringFromEnv = (key: string, def: string): string => process.env[key] || def
// Size
export const MIN_HEIGHT = intFromEnv('LEDGER_MIN_HEIGHT', 768)
export const MIN_WIDTH = intFromEnv('LEDGER_MIN_WIDTH', 1024)
// time and delays...
export const GET_CALLS_TIMEOUT = intFromEnv('GET_CALLS_TIMEOUT', 30 * 1000)
@ -23,6 +28,7 @@ export const SYNC_BOOT_DELAY = 2 * 1000
export const SYNC_ALL_INTERVAL = 120 * 1000
export const GENUINE_TIMEOUT = intFromEnv('GENUINE_TIMEOUT', 120 * 1000)
export const SYNC_TIMEOUT = intFromEnv('SYNC_TIMEOUT', 30 * 1000)
export const OUTDATED_CONSIDERED_DELAY = intFromEnv('OUTDATED_CONSIDERED_DELAY', 5 * 60 * 1000)
export const CHECK_APP_INTERVAL_WHEN_INVALID = 600
export const CHECK_APP_INTERVAL_WHEN_VALID = 1200
@ -60,6 +66,7 @@ export const DEBUG_ACTION = boolFromEnv('DEBUG_ACTION')
export const DEBUG_TAB_KEY = boolFromEnv('DEBUG_TAB_KEY')
export const DEBUG_LIBCORE = boolFromEnv('DEBUG_LIBCORE')
export const DEBUG_WS = boolFromEnv('DEBUG_WS')
export const DEBUG_SYNC = boolFromEnv('DEBUG_SYNC')
export const LEDGER_RESET_ALL = boolFromEnv('LEDGER_RESET_ALL')
export const LEDGER_DEBUG_ALL_LANGS = boolFromEnv('LEDGER_DEBUG_ALL_LANGS')
export const SKIP_GENUINE = boolFromEnv('SKIP_GENUINE')

2
src/helpers/apps/installApp.js

@ -7,7 +7,7 @@ import { createDeviceSocket } from 'helpers/socket'
import type { LedgerScriptParams } from 'helpers/common'
import createCustomErrorClass from '../createCustomErrorClass'
import { createCustomErrorClass } from '../errors'
const ManagerUnexpectedError = createCustomErrorClass('ManagerUnexpected')
const ManagerNotEnoughSpaceError = createCustomErrorClass('ManagerNotEnoughSpace')

11
src/helpers/apps/listApps.js

@ -5,11 +5,14 @@ import { APPLICATIONS_BY_DEVICE } from 'helpers/urls'
import getDeviceVersion from 'helpers/devices/getDeviceVersion'
import getCurrentFirmware from 'helpers/devices/getCurrentFirmware'
export default async (targetId: string | number, version: string) => {
export default async (targetId: string | number, fullVersion: string, provider: number) => {
try {
const provider = 1
const deviceData = await getDeviceVersion(targetId)
const firmwareData = await getCurrentFirmware({ deviceId: deviceData.id, version })
const deviceData = await getDeviceVersion(targetId, provider)
const firmwareData = await getCurrentFirmware({
deviceId: deviceData.id,
fullVersion,
provider,
})
const params = {
provider,
current_se_firmware_final_version: firmwareData.id,

2
src/helpers/apps/uninstallApp.js

@ -6,7 +6,7 @@ import { BASE_SOCKET_URL_SECURE } from 'config/constants'
import { createDeviceSocket } from 'helpers/socket'
import type { LedgerScriptParams } from 'helpers/common'
import createCustomErrorClass from '../createCustomErrorClass'
import { createCustomErrorClass } from '../errors'
const ManagerUnexpectedError = createCustomErrorClass('ManagerUnexpectedError')
const ManagerDeviceLockedError = createCustomErrorClass('ManagerDeviceLocked')

22
src/helpers/common.js

@ -32,9 +32,25 @@ export async function getFirmwareInfo(transport: Transport<*>) {
const data = byteArray.slice(0, byteArray.length - 2)
const targetIdStr = Buffer.from(data.slice(0, 4))
const targetId = targetIdStr.readUIntBE(0, 4)
const versionLength = data[4]
const version = Buffer.from(data.slice(5, 5 + versionLength)).toString()
return { targetId, version }
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()
return { targetId, seVersion, flags, mcuVersion }
} catch (err) {
const error = new Error(err.message)
error.stack = err.stack

14
src/helpers/createCustomErrorClass.js

@ -1,14 +0,0 @@
// @flow
export default (name: string): Class<any> => {
const C = function CustomError(message?: string, fields?: Object) {
this.name = name
this.message = message || name
this.stack = new Error().stack
Object.assign(this, fields)
}
// $FlowFixMe
C.prototype = new Error()
// $FlowFixMe we can't easily type a subset of Error for now...
return C
}

8
src/helpers/devices/getCurrentFirmware.js

@ -4,21 +4,21 @@ import network from 'api/network'
import { GET_CURRENT_FIRMWARE } from 'helpers/urls'
type Input = {
version: string,
fullVersion: string,
deviceId: string | number,
provider: number,
}
let error
export default async (input: Input): Promise<*> => {
try {
const provider = 1
const { data } = await network({
method: 'POST',
url: GET_CURRENT_FIRMWARE,
data: {
device_version: input.deviceId,
version_name: input.version,
provider,
version_name: input.fullVersion,
provider: input.provider,
},
})
return data

70
src/helpers/devices/getDeviceInfo.js

@ -4,23 +4,65 @@ import type Transport from '@ledgerhq/hw-transport'
import { getFirmwareInfo } from 'helpers/common'
type Result = {
export type DeviceInfo = {
targetId: string | number,
version: string,
mcu: boolean,
final: boolean,
seVersion: string,
isBootloader: boolean,
flags: string,
mcuVersion: string,
isOSU: boolean,
providerName: string,
providerId: number,
fullVersion: string,
}
export default async (transport: Transport<*>): Promise<Result> => {
try {
const { targetId, version } = await getFirmwareInfo(transport)
const finalReady = version.endsWith('-osu')
const mcuReady = targetId === 0x01000001
// prettier-ignore
const DETECT_CLUBCOIN = [
[0xe0, 0x04, 0x00, 0x00, Buffer.from([0x31, 0x10, 0x00, 0x02])],
[0xe0, 0x50, 0x00, 0x00, Buffer.from([0xe4, 0x6c, 0x4c, 0x71, 0x8b, 0xc8, 0x7f, 0xb7])],
[0xe0, 0x51, 0x80, 0x00, Buffer.from([0x41, 0x04, 0xc9, 0x8c, 0xa0, 0x99, 0x53, 0x47, 0x2b, 0x36, 0x06, 0x1e, 0x0e, 0x40, 0xc9, 0x3d, 0x50, 0x52, 0x34, 0x09, 0x0e, 0xfd, 0x74, 0xf1, 0xd7, 0xa2, 0x93, 0xe8, 0x28, 0x15, 0x9a, 0x97, 0x71, 0x1b, 0x33, 0xd1, 0x8a, 0xfc, 0x17, 0xad, 0x15, 0x6e, 0xae, 0xd9, 0x9c, 0xf4, 0x3b, 0x20, 0xe1, 0x5d, 0x64, 0xaf, 0x39, 0xa5, 0x51, 0x3b, 0x4e, 0x3c, 0x5f, 0x43, 0x17, 0xe6, 0x42, 0x70, 0x2f, 0x05, 0x47, 0x30, 0x45, 0x02, 0x21, 0x00, 0xf1, 0xd2, 0xb8, 0x34, 0x99, 0x4a, 0x0c, 0x1f, 0x25, 0xea, 0x20, 0xcf, 0x33, 0xe3, 0x2b, 0xd0, 0x6b, 0xcf, 0x7c, 0x42, 0x4a, 0x02, 0xee, 0xe8, 0xf6, 0x96, 0x99, 0x20, 0xe1, 0xe8, 0xc2, 0xb3, 0x02, 0x20, 0x63, 0x2d, 0x19, 0xbd, 0x30, 0xab, 0x20, 0x76, 0x18, 0x78, 0x78, 0xae, 0xaa, 0x0f, 0x4d, 0x48, 0x04, 0x01, 0x32, 0x79, 0xd0, 0x16, 0xde, 0xca, 0x66, 0x93, 0xf3, 0x7b, 0x4e, 0x50, 0x7f, 0x43])],
]
return { targetId, version, final: finalReady, mcu: mcuReady }
} catch (err) {
const error = Error(err.message)
error.stack = err.stack
throw error
const PROVIDERS = {
'': 1,
das: 2,
club: 3,
shitcoins: 4,
}
export default async (transport: Transport<*>): Promise<DeviceInfo> => {
const res = await getFirmwareInfo(transport)
let { seVersion } = res
const { targetId, mcuVersion, flags } = res
if (seVersion === '1.2') {
try {
for (let i = 0; i < DETECT_CLUBCOIN.length; i++) {
const instructions = DETECT_CLUBCOIN[i]
await transport.send(...instructions)
}
seVersion = '1.2.0-club'
} catch (e) {
seVersion = '1.2.0'
}
}
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 = 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,
}
}

3
src/helpers/devices/getDeviceVersion.js

@ -2,8 +2,7 @@
import { GET_DEVICE_VERSION } from 'helpers/urls'
import network from 'api/network'
export default async (targetId: string | number): Promise<*> => {
const provider = 1
export default async (targetId: string | number, provider: number): Promise<*> => {
const { data } = await network({
method: 'POST',
url: GET_DEVICE_VERSION,

18
src/helpers/devices/getIsGenuine.js

@ -2,21 +2,21 @@
import type Transport from '@ledgerhq/hw-transport'
import { SKIP_GENUINE } from 'config/constants'
import { WS_GENUINE } from 'helpers/urls'
import type { DeviceInfo } from 'helpers/devices/getDeviceInfo'
import { createDeviceSocket } from 'helpers/socket'
import getCurrentFirmware from './getCurrentFirmware'
import getDeviceVersion from './getDeviceVersion'
export default async (
transport: Transport<*>,
app: { targetId: string | number, version: string },
): Promise<string> => {
const { targetId, version } = app
const device = await getDeviceVersion(app.targetId)
const firmware = await getCurrentFirmware({ deviceId: device.id, version })
export default async (transport: Transport<*>, deviceInfo: DeviceInfo): Promise<string> => {
const deviceVersion = await getDeviceVersion(deviceInfo.targetId, deviceInfo.providerId)
const firmware = await getCurrentFirmware({
deviceId: deviceVersion.id,
fullVersion: deviceInfo.fullVersion,
provider: deviceInfo.providerId,
})
const params = {
targetId,
version,
targetId: deviceInfo.targetId,
perso: firmware.perso,
}
const url = WS_GENUINE(params)

20
src/helpers/devices/getLatestFirmwareForDevice.js

@ -1,24 +1,22 @@
// @flow
import network from 'api/network'
import { GET_LATEST_FIRMWARE } from 'helpers/urls'
import type { DeviceInfo } from 'helpers/devices/getDeviceInfo'
import getCurrentFirmware from './getCurrentFirmware'
import getDeviceVersion from './getDeviceVersion'
type Input = {
version: string,
targetId: string | number,
}
export default async (input: Input) => {
export default async (deviceInfo: DeviceInfo) => {
try {
const provider = 1
const { targetId, version } = input
// Get device infos from targetId
const deviceVersion = await getDeviceVersion(targetId)
const deviceVersion = await getDeviceVersion(deviceInfo.targetId, deviceInfo.providerId)
// Get firmware infos with firmware name and device version
const seFirmwareVersion = await getCurrentFirmware({ version, deviceId: deviceVersion.id })
const seFirmwareVersion = await getCurrentFirmware({
fullVersion: deviceInfo.fullVersion,
deviceId: deviceVersion.id,
provider: deviceInfo.providerId,
})
// Fetch next possible firmware
const { data } = await network({
@ -27,7 +25,7 @@ export default async (input: Input) => {
data: {
current_se_firmware_final_version: seFirmwareVersion.id,
device_version: deviceVersion.id,
provider,
provider: deviceInfo.providerId,
},
})

2
src/helpers/devices/getNextMCU.js

@ -2,7 +2,7 @@
import network from 'api/network'
import { GET_NEXT_MCU } from 'helpers/urls'
import createCustomErrorClass from 'helpers/createCustomErrorClass'
import { createCustomErrorClass } from 'helpers/errors'
const LatestMCUInstalledError = createCustomErrorClass('LatestMCUInstalledError')

2
src/helpers/devices/getOsuFirmware.js

@ -15,7 +15,7 @@ export default async (input: Input): Promise<*> => {
url: GET_CURRENT_OSU,
data: {
device_version: input.deviceId,
version_name: input.version,
version_name: `${input.version}-osu`,
provider,
},
})

4
src/helpers/devices/isDashboardOpen.js

@ -8,8 +8,8 @@ type Result = boolean
export default async (transport: Transport<*>): Promise<Result> => {
try {
const { targetId, version } = await getFirmwareInfo(transport)
if (targetId && version) {
const { targetId, seVersion } = await getFirmwareInfo(transport)
if (targetId && seVersion) {
return true
}

79
src/helpers/errors.js

@ -1,3 +1,80 @@
// @flow
/* eslint-disable no-continue */
export const formatError = (e: Error) => e.message
const errorClasses = {}
export const createCustomErrorClass = (name: string): Class<any> => {
const C = function CustomError(message?: string, fields?: Object) {
this.name = name
this.message = message || name
this.stack = new Error().stack
Object.assign(this, fields)
}
// $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) {
const constructor = (typeof object.name === 'string' && errorClasses[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
}

14
src/helpers/firmware/installFinalFirmware.js

@ -1,5 +1,6 @@
// @flow
import type Transport from '@ledgerhq/hw-transport'
import type { DeviceInfo } from 'helpers/devices/getDeviceInfo'
import { WS_INSTALL } from 'helpers/urls'
import { createDeviceSocket } from 'helpers/socket'
@ -7,22 +8,17 @@ import getDeviceVersion from 'helpers/devices/getDeviceVersion'
import getOsuFirmware from 'helpers/devices/getOsuFirmware'
import getFinalFirmwareById from './getFinalFirmwareById'
type Input = {
targetId: number | string,
version: string,
}
type Result = *
export default async (transport: Transport<*>, app: Input): Result => {
export default async (transport: Transport<*>, deviceInfo: DeviceInfo): Result => {
try {
const { targetId, version } = app
const device = await getDeviceVersion(targetId)
const firmware = await getOsuFirmware({ deviceId: device.id, version })
const device = await getDeviceVersion(deviceInfo.targetId, deviceInfo.providerId)
const firmware = await getOsuFirmware({ deviceId: device.id, version: deviceInfo.fullVersion })
const { next_se_firmware_final_version } = firmware
const nextFirmware = await getFinalFirmwareById(next_se_firmware_final_version)
const params = {
targetId,
targetId: deviceInfo.targetId,
...nextFirmware,
firmwareKey: nextFirmware.firmware_key,
}

2
src/helpers/getAddressForCurrency/btc.js

@ -4,7 +4,7 @@ import type { CryptoCurrency } from '@ledgerhq/live-common/lib/types'
import Btc from '@ledgerhq/hw-app-btc'
import type Transport from '@ledgerhq/hw-transport'
import getBitcoinLikeInfo from '../devices/getBitcoinLikeInfo'
import createCustomErrorClass from '../createCustomErrorClass'
import { createCustomErrorClass } from '../errors'
const BtcUnmatchedApp = createCustomErrorClass('BtcUnmatchedApp')

3
src/helpers/init-libcore.js

@ -3,6 +3,7 @@
import logger from 'logger'
import invariant from 'invariant'
import network from 'api/network'
import { serializeError } from './errors'
const lib = require('@ledgerhq/ledger-core')
@ -96,7 +97,7 @@ const NJSHttpClient = new lib.NJSHttpClient({
r.complete(urlConnection, null)
} catch (err) {
const urlConnection = createHttpConnection(res, err.message)
r.complete(urlConnection, { code: 0, message: err.message })
r.complete(urlConnection, { code: 0, message: JSON.stringify(serializeError(err)) })
}
},
})

7
src/helpers/ipc.js

@ -2,6 +2,7 @@
import logger from 'logger'
import { Observable } from 'rxjs'
import uuidv4 from 'uuid/v4'
import { deserializeError } from './errors'
export function createCommand<In, A>(id: string, impl: In => Observable<A>): Command<In, A> {
return new Command(id, impl)
@ -60,10 +61,12 @@ function ipcRendererSendCommand<In, A>(id: string, data: In): Observable<A> {
ipcRenderer.removeListener('command-event', handleCommandEvent)
break
case 'cmd.ERROR':
o.error(msg.data)
case 'cmd.ERROR': {
const error = deserializeError(msg.data)
o.error(error)
ipcRenderer.removeListener('command-event', handleCommandEvent)
break
}
default:
}

9
src/helpers/libcore.js

@ -11,8 +11,7 @@ import type { NJSAccount, NJSOperation } from '@ledgerhq/ledger-core/src/ledgerc
import { isSegwitAccount } from 'helpers/bip32'
import * as accountIdHelper from 'helpers/accountId'
import createCustomErrorClass from './createCustomErrorClass'
import { createCustomErrorClass, deserializeError } from './errors'
import { getAccountPlaceholderName, getNewAccountPlaceholderName } from './accountName'
const NoAddressesFound = createCustomErrorClass('NoAddressesFound')
@ -158,7 +157,11 @@ const coreSyncAccount = (core, account) =>
(payload && payload.getString('EV_SYNC_ERROR_MESSAGE')) ||
'Sync failed'
).replace(' (EC_PRIV_KEY_INVALID_FORMAT)', '')
reject(new Error(message))
try {
reject(deserializeError(JSON.parse(message)))
} catch (e) {
reject(message)
}
return
}
if (

4
src/helpers/promise.js

@ -27,3 +27,7 @@ export function retry<A>(f: () => Promise<A>, options?: $Shape<typeof defaults>)
})
}
}
export function idleCallback() {
return new Promise(resolve => window.requestIdleCallback(resolve))
}

4
src/helpers/socket.js

@ -5,7 +5,7 @@ import logger from 'logger'
import Websocket from 'ws'
import type Transport from '@ledgerhq/hw-transport'
import { Observable } from 'rxjs'
import createCustomErrorClass from './createCustomErrorClass'
import { createCustomErrorClass } from './errors'
const WebsocketConnectionError = createCustomErrorClass('WebsocketConnectionError')
const WebsocketConnectionFailed = createCustomErrorClass('WebsocketConnectionFailed')
@ -74,7 +74,9 @@ export const createDeviceSocket = (transport: Transport<*>, url: string) =>
for (const apdu of data) {
const r: Buffer = await transport.exchange(Buffer.from(apdu, 'hex'))
lastStatus = r.slice(r.length - 2)
if (lastStatus.toString('hex') !== '9000') break
}
if (!lastStatus) {
throw new DeviceSocketNoBulkStatus()
}

5
src/helpers/urls.js

@ -23,7 +23,10 @@ export const GET_LATEST_FIRMWARE: string = managerUrlbuilder('get_latest_firmwar
export const GET_NEXT_MCU: string = managerUrlbuilder('mcu_versions_bootloader')
export const WS_INSTALL: (arg: LedgerScriptParams) => string = wsURLBuilder('install')
export const WS_GENUINE: (arg: { targetId: string | number }) => string = wsURLBuilder('genuine')
export const WS_GENUINE: (arg: {
targetId: string | number,
perso: string,
}) => string = wsURLBuilder('genuine')
export const WS_MCU: (arg: { targetId: string | number, version: string }) => string = wsURLBuilder(
'mcu',
)

5
src/icons/Home.js

@ -3,7 +3,10 @@
import React from 'react'
const path = (
<path d="m8.261 2.252a1.11 1.11 0 0 0-1.408 0l-6.772 5.545a0.224 0.224 0 0 0-0.03 0.313l0.563 0.69a0.224 0.224 0 0 0 0.314 0.03l0.408-0.333v5.502c0 0.245 0.2 0.445 0.445 0.445h4.667c0.122 0 0.222-0.1 0.222-0.222v-3.556h1.778v3.556c0 0.122 0.1 0.222 0.222 0.222h4.666c0.245 0 0.445-0.2 0.445-0.445v-5.505l0.408 0.333a0.224 0.224 0 0 0 0.314-0.03l0.564-0.69a0.227 0.227 0 0 0-0.036-0.31l-6.771-5.545zm4.184 10.858h-2.667v-3.555c0-0.122-0.1-0.222-0.222-0.222h-4c-0.122 0-0.222 0.1-0.222 0.222v3.555h-2.667v-5.708l4.747-3.889c0.08-0.066 0.2-0.066 0.28 0l4.748 3.89v5.707z" />
<path
fill="currentColor"
d="m8.261 2.252a1.11 1.11 0 0 0-1.408 0l-6.772 5.545a0.224 0.224 0 0 0-0.03 0.313l0.563 0.69a0.224 0.224 0 0 0 0.314 0.03l0.408-0.333v5.502c0 0.245 0.2 0.445 0.445 0.445h4.667c0.122 0 0.222-0.1 0.222-0.222v-3.556h1.778v3.556c0 0.122 0.1 0.222 0.222 0.222h4.666c0.245 0 0.445-0.2 0.445-0.445v-5.505l0.408 0.333a0.224 0.224 0 0 0 0.314-0.03l0.564-0.69a0.227 0.227 0 0 0-0.036-0.31l-6.771-5.545zm4.184 10.858h-2.667v-3.555c0-0.122-0.1-0.222-0.222-0.222h-4c-0.122 0-0.222 0.1-0.222 0.222v3.555h-2.667v-5.708l4.747-3.889c0.08-0.066 0.2-0.066 0.28 0l4.748 3.89v5.707z"
/>
)
export default ({ size, ...p }: { size: number }) => (

0
src/icons/illustrations/SensitiveOperationShield.js → src/icons/SensitiveOperationShield.js

2
src/icons/TriangleWarning.js

@ -10,7 +10,7 @@ const path = (
)
export default ({ height, width, ...p }: { height: number, width: number }) => (
<svg viewBox="0 0 19 17" height={height} width={width} {...p}>
<svg viewBox="0 0 17 17" height={height} width={width} {...p}>
{path}
</svg>
)

79
src/icons/illustrations/GetStartedLogo.js

@ -1,79 +0,0 @@
// @flow
import React from 'react'
export default () => (
<svg width="113" height="109">
<g fill="none" fillRule="evenodd">
<rect
width="1.44"
height="5.6"
y="16.6"
fill="#1D2028"
rx=".72"
transform="matrix(-1 0 0 1 1.44 0)"
/>
<rect
width="1.44"
height="5.6"
y="34.8"
fill="#1D2028"
rx=".72"
transform="matrix(-1 0 0 1 1.44 0)"
/>
<path
fill="#6490F1"
fillOpacity=".1"
stroke="#1D2028"
strokeWidth="2"
d="M16.592 12c.225 0 .408.183.408.408v95.184a.408.408 0 0 1-.408.408H2.128a.408.408 0 0 1-.408-.408V12.408c0-.225.183-.408.408-.408h14.464z"
/>
<rect
width="7.64"
height="27"
x="5.513"
y="18.522"
fill="#FFF"
stroke="#6490F1"
rx=".704"
transform="matrix(-1 0 0 1 18.665 0)"
/>
<path
fill="#FFF"
stroke="#1D2028"
strokeWidth="2"
d="M9.36 54A7.64 7.64 0 0 1 17 61.64v45.952a.408.408 0 0 1-.408.408H2.128a.408.408 0 0 1-.408-.408V61.64A7.64 7.64 0 0 1 9.36 54z"
/>
<ellipse
cx="9.36"
cy="61.4"
fill="#FFF"
stroke="#6490F1"
rx="3.82"
ry="3.7"
transform="matrix(-1 0 0 1 18.72 0)"
/>
<rect width="3.137" height="9.306" x="109.863" y="13.959" fill="#1D2028" rx="1.569" />
<rect
width="76.431"
height="106.571"
x="34"
y="1"
fill="#6490F1"
fillOpacity=".1"
stroke="#1D2027"
strokeWidth="2"
rx="5.44"
/>
<rect
width="52.333"
height="79.653"
x="46.043"
y="15.235"
fill="#FFF"
stroke="#6490F1"
rx="4.08"
/>
</g>
</svg>
)

32
src/icons/illustrations/LedgerBlue.js

@ -1,32 +0,0 @@
// @flow
import React from 'react'
export default () => (
<svg width="52" height="72">
<g fill="none" fillRule="evenodd">
<rect width="2.039" height="6.171" x="49.961" y="9.257" fill="#1D2028" rx="1.02" />
<rect
width="49.48"
height="70.5"
x=".75"
y=".75"
fill="#6490F1"
fillOpacity=".1"
stroke="#1D2027"
strokeWidth="1.5"
rx="3.2"
/>
<rect
width="34.167"
height="52.986"
x="8.403"
y="10.021"
fill="#FFF"
stroke="#6490F1"
strokeWidth=".5"
rx="2.4"
/>
</g>
</svg>
)

47
src/icons/illustrations/LedgerBlueError.js

@ -1,47 +0,0 @@
// @flow
import React from 'react'
export default () => (
<svg width="92" height="188">
<defs>
<linearGradient id="a" x1="50%" x2="50%" y1="0%" y2="100%">
<stop offset="0%" />
<stop offset="100%" stopColor="#FFF" />
</linearGradient>
</defs>
<g fill="none" fillRule="evenodd">
<path
stroke="#1D2027"
strokeWidth="2"
d="M37 120.644a1 1 0 0 0-1 1v26.225a5 5 0 0 0 5 5h8.486a5 5 0 0 0 5-5v-26.225a1 1 0 0 0-1-1H37z"
/>
<path stroke="#142533" strokeWidth="2" d="M42.208 153.253v10.929h6.436v-10.93h-6.436z" />
<path
stroke="#1D2027"
strokeWidth="2"
d="M39.713 120.577h11.255l-.082-6.977a1 1 0 0 0-1-.988h-9.267a1 1 0 0 0-.988 1.012l.082 6.953z"
/>
<path
fill="url(#a)"
d="M6.836 53.925h1.616v22.65H6.836v-22.65zm5.657 0h1.616v22.65h-1.616v-22.65z"
transform="translate(35 111)"
/>
<path
fill="#1D2028"
d="M88.556 17.556c0-1.105.671-2 1.5-2 .828 0 1.5.895 1.5 2v6c0 1.104-.672 2-1.5 2-.829 0-1.5-.896-1.5-2"
/>
<rect
width="88"
height="118.635"
x="1"
y="1"
fill="#FCEAEC"
stroke="#1D2027"
strokeWidth="2"
rx="5.44"
/>
<rect width="59" height="88.615" x="15.5" y="16.5" fill="#FFF" stroke="#EA2E49" rx="4.08" />
</g>
</svg>
)

47
src/icons/illustrations/LedgerBlueSelectPIN.js

@ -1,47 +0,0 @@
// @flow
import React from 'react'
export default () => (
<svg width="92" height="188">
<defs>
<linearGradient id="a" x1="50%" x2="50%" y1="0%" y2="100%">
<stop offset="0%" />
<stop offset="100%" stopColor="#FFF" />
</linearGradient>
</defs>
<g fill="none" fillRule="evenodd">
<path
stroke="#1D2027"
strokeWidth="2"
d="M37 120.644a1 1 0 0 0-1 1v26.225a5 5 0 0 0 5 5h8.486a5 5 0 0 0 5-5v-26.225a1 1 0 0 0-1-1H37z"
/>
<path stroke="#142533" strokeWidth="2" d="M42.208 153.253v10.929h6.436v-10.93h-6.436z" />
<path
stroke="#1D2027"
strokeWidth="2"
d="M39.713 120.577h11.255l-.082-6.977a1 1 0 0 0-1-.988h-9.267a1 1 0 0 0-.988 1.012l.082 6.953z"
/>
<path
fill="url(#a)"
d="M6.836 53.925h1.616v22.65H6.836v-22.65zm5.657 0h1.616v22.65h-1.616v-22.65z"
transform="translate(35 111)"
/>
<path
fill="#1D2028"
d="M88.556 17.556c0-1.105.671-2 1.5-2 .828 0 1.5.895 1.5 2v6c0 1.104-.672 2-1.5 2-.829 0-1.5-.896-1.5-2"
/>
<rect
width="88"
height="118.635"
x="1"
y="1"
fill="#EFF3FD"
stroke="#1D2027"
strokeWidth="2"
rx="5.44"
/>
<rect width="59" height="88.615" x="15.5" y="16.5" fill="#FFF" stroke="#6490F1" rx="4.08" />
</g>
</svg>
)

44
src/icons/illustrations/LedgerNano.js

@ -1,44 +0,0 @@
// @flow
import React from 'react'
export default () => (
<svg width="13" height="72">
<g fill="none" fillRule="evenodd">
<rect width="1.04" height="4.114" x="11.96" y="4.114" fill="#1D2028" rx=".52" />
<rect width="1.04" height="4.114" x="11.96" y="17.486" fill="#1D2028" rx=".52" />
<path
fill="#6490F1"
fillOpacity=".1"
stroke="#1D2028"
strokeWidth="1.5"
d="M1.6.75a.85.85 0 0 0-.85.85v68.8c0 .47.38.85.85.85h9.28c.47 0 .85-.38.85-.85V1.6a.85.85 0 0 0-.85-.85H1.6z"
/>
<rect
width="5.74"
height="20.071"
x="3.39"
y="5.409"
fill="#FFF"
stroke="#6490F1"
strokeWidth=".5"
rx=".8"
/>
<path
fill="#FFF"
stroke="#1D2028"
strokeWidth="1.5"
d="M6.24 31.607a5.49 5.49 0 0 0-5.49 5.49V70.4c0 .47.38.85.85.85h9.28c.47 0 .85-.38.85-.85V37.097a5.49 5.49 0 0 0-5.49-5.49z"
/>
<ellipse
cx="6.24"
cy="37.029"
fill="#FFF"
stroke="#6490F1"
strokeWidth=".5"
rx="2.87"
ry="2.836"
/>
</g>
</svg>
)

78
src/icons/illustrations/LedgerNanoError.js

@ -1,78 +0,0 @@
// @flow
import React from 'react'
export default () => (
<svg>
<defs>
<linearGradient id="a" x1="50%" x2="50%" y1="0%" y2="100%">
<stop offset="0%" />
<stop offset="100%" stopColor="#FFF" />
</linearGradient>
<rect id="b" width="41.711" height="238.384" rx="4" />
<path
id="c"
d="M5.773 5l2.541-2.541a.235.235 0 0 0 0-.332l-.441-.441a.235.235 0 0 0-.332 0L5 4.226l-2.541-2.54a.235.235 0 0 0-.332 0l-.441.441a.235.235 0 0 0 0 .332L4.226 5l-2.54 2.541a.235.235 0 0 0 0 .332l.441.441c.092.092.24.092.332 0L5 5.774l2.541 2.54c.092.092.24.092.332 0l.441-.441a.235.235 0 0 0 0-.332L5.774 5z"
/>
</defs>
<g fill="none" fillRule="evenodd">
<path
stroke="#1D2027"
strokeWidth="2"
d="M127.356 30a1 1 0 0 1-1 1H100.13a5 5 0 0 1-5-5v-8.486a5 5 0 0 1 5-5h26.225a1 1 0 0 1 1 1V30z"
/>
<path stroke="#142533" strokeWidth="2" d="M94.747 24.792H83.818v-6.436h10.93v6.436z" />
<path
stroke="#1D2027"
strokeWidth="2"
d="M127.423 27.287V16.032l6.977.082a1 1 0 0 1 .988 1v9.267a1 1 0 0 1-1.012.988l-6.953-.082z"
/>
<path
fill="url(#a)"
d="M6.836 53.925h1.616v82.65H6.836v-82.65zm5.657 0h1.616v82.65h-1.616v-82.65z"
transform="matrix(0 -1 -1 0 137 32)"
/>
<g transform="rotate(-90 85 -42)">
<rect width="4.492" height="17.12" x="38.336" y="15.505" fill="#142533" rx="2" />
<rect width="4.492" height="17.12" x="38.336" y="70.094" fill="#142533" rx="2" />
<use fill="#FFF" />
<rect
width="39.711"
height="236.384"
x="1"
y="1"
fill="#FCE0E4"
stroke="#142533"
strokeLinejoin="square"
strokeWidth="2"
rx="4"
/>
<rect
width="20.176"
height="61.019"
x="10.767"
y="21.173"
fill="#FFF"
stroke="#EA2E49"
rx="1.6"
/>
<path
fill="#FFF"
stroke="#142533"
strokeWidth="2"
d="M20.856 95.966C9.89 95.966 1 104.856 1 115.822v118.562a3 3 0 0 0 3 3h33.711a3 3 0 0 0 3-3V115.822c0-10.966-8.89-19.856-19.855-19.856z"
/>
<ellipse cx="21.016" cy="116.123" stroke="#EA2E49" rx="10.57" ry="10.644" />
<g transform="translate(16.066 26.884)">
<mask id="d" fill="#fff">
<use xlinkHref="#c" />
</mask>
<use fill="#000" fillRule="nonzero" xlinkHref="#c" />
<g fill="#EA2E49" mask="url(#d)">
<path d="M0 0h10v10H0z" />
</g>
</g>
</g>
</g>
</svg>
)

60
src/icons/illustrations/LedgerNanoSelectPIN.js

@ -1,60 +0,0 @@
// @flow
import React from 'react'
export default () => (
<svg width="260" height="129">
<defs>
<linearGradient id="a" x1="50%" x2="50%" y1="0%" y2="100%">
<stop offset="0%" />
<stop offset="100%" stopColor="#FFF" />
</linearGradient>
<path
id="b"
d="M91 0h34a4 4 0 0 1 4 4v108.144c0 11.519-9.337 20.856-20.856 20.856h-.288C96.337 133 87 123.663 87 112.144V4a4 4 0 0 1 4-4z"
/>
</defs>
<g fill="none" fillRule="evenodd">
<path
stroke="#1D2027"
strokeWidth="2"
d="M127.856 31.44a1 1 0 0 1-1 1H100.63a5 5 0 0 1-5-5v-8.486a5 5 0 0 1 5-5h26.225a1 1 0 0 1 1 1v16.485z"
/>
<path stroke="#142533" strokeWidth="2" d="M95.247 26.231H84.318v-6.435h10.93v6.435z" />
<path
stroke="#1D2027"
strokeWidth="2"
d="M127.923 28.726V17.471l6.977.083a1 1 0 0 1 .988 1v9.266a1 1 0 0 1-1.012.988l-6.953-.083z"
/>
<path
fill="url(#a)"
d="M6.836 53.925h1.616v82.65H6.836v-82.65zm5.657 0h1.616v82.65h-1.616v-82.65z"
transform="matrix(0 -1 -1 0 137.5 33.44)"
/>
<g transform="rotate(-90 128.59 1.975)">
<rect width="4.492" height="17.12" x="125.336" y="15.505" fill="#142533" rx="2" />
<rect width="4.492" height="17.12" x="125.336" y="70.094" fill="#142533" rx="2" />
<use fill="#FFF" xlinkHref="#b" />
<path
fill="#6490F1"
fillOpacity=".15"
stroke="#142533"
strokeLinejoin="square"
strokeWidth="2"
d="M91 1a3 3 0 0 0-3 3v108.144C88 123.11 96.89 132 107.856 132h.288C119.11 132 128 123.11 128 112.144V4a3 3 0 0 0-3-3H91z"
/>
<rect width="21" height="62" x="97.5" y="21.5" fill="#FFF" stroke="#6490F1" rx="1.6" />
<path
fill="#6490F1"
d="M105.5 35h5a.5.5 0 0 1 .5.5v34a.5.5 0 0 1-.5.5h-5a.5.5 0 0 1-.5-.5v-34a.5.5 0 0 1 .5-.5zm1.238 3.042l.774.512v.013l-.774.505.341.466.722-.577h.013l.243.899.551-.177-.328-.88.932.053v-.597l-.932.046.328-.873-.551-.17-.243.892h-.013l-.722-.584-.34.472zm0 3.908l.774.512v.013l-.774.505.341.466.722-.578h.013l.243.9.551-.178-.328-.88.932.053v-.597l-.932.046.328-.872-.551-.17-.243.891h-.013l-.722-.584-.34.473zm0 3.907l.774.512v.013l-.774.505.341.466.722-.577h.013l.243.899.551-.178-.328-.879.932.053v-.597l-.932.046.328-.873-.551-.17-.243.892h-.013l-.722-.584-.34.472zm0 3.908l.774.511v.014l-.774.505.341.466.722-.578h.013l.243.899.551-.177-.328-.88.932.053v-.597l-.932.046.328-.872-.551-.171-.243.892h-.013l-.722-.584-.34.473zm0 3.907l.774.512v.013l-.774.505.341.466.722-.577h.013l.243.898.551-.177-.328-.879.932.053v-.597l-.932.046.328-.873-.551-.17-.243.892h-.013l-.722-.584-.34.472zm0 3.908l.774.511v.013l-.774.506.341.465.722-.577h.013l.243.899.551-.177-.328-.88.932.053v-.597l-.932.046.328-.873-.551-.17-.243.892h-.013l-.722-.584-.34.473zm0 3.907l.774.512v.013l-.774.505.341.466.722-.578h.013l.243.9.551-.178-.328-.879.932.052v-.597l-.932.046.328-.872-.551-.17-.243.891h-.013l-.722-.583-.34.472zm0 3.907l.774.512v.013l-.774.505.341.466.722-.577h.013l.243.899.551-.177-.328-.88.932.053v-.597l-.932.046.328-.873-.551-.17-.243.892h-.013l-.722-.584-.34.472z"
/>
<path
fill="#FFF"
stroke="#142533"
strokeWidth="2"
d="M123.166 125.105c7.049-8.4 5.953-20.925-2.447-27.974l-90.824-76.21a3 3 0 0 0-4.227.37L4 47.115a3 3 0 0 0 .37 4.227l90.824 76.21c8.4 7.049 20.924 5.953 27.973-2.447z"
/>
<ellipse cx="108.016" cy="111.123" stroke="#6490F1" rx="10.57" ry="10.644" />
</g>
</g>
</svg>
)

63
src/icons/illustrations/SetPassword.js

@ -1,63 +0,0 @@
// @flow
import React from 'react'
export default () => (
<svg width="136" height="52">
<g fill="none" fillRule="evenodd">
<g fillRule="nonzero">
<g transform="translate(36 1)">
<ellipse cx="37.488" cy="45.422" fill="#000" rx="1.037" ry="1.036" />
<ellipse cx="33.5" cy="45.422" fill="#000" rx="1.037" ry="1.036" />
<ellipse cx="29.512" cy="45.422" fill="#000" rx="1.037" ry="1.036" />
<path
fill="#142533"
d="M65.405 40.242h-2.792V9.562c0-7.251-5.982-9.164-9.173-9.164H13.56c-3.191 0-9.173 1.913-9.173 9.165v30.68H1.595C.957 40.242.4 40.8.4 41.436c0 3.188 1.914 9.165 9.172 9.165H57.43c7.258 0 9.172-5.977 9.172-9.164 0-.638-.558-1.196-1.196-1.196zM6.78 9.562c0-6.534 6.062-6.773 6.78-6.773h39.88c.718 0 6.78.24 6.78 6.774v30.68H6.78V9.562zm50.649 38.649H9.57c-5.025 0-6.3-3.586-6.7-5.578h61.18c-.32 1.912-1.595 5.578-6.62 5.578z"
/>
</g>
<g fill="#6490F1">
<path d="M74.189 20.125c0-.16.08-.319.08-.478V16.38c0-1.833-1.524-3.427-3.448-3.427h-2.005c-1.844 0-3.448 1.514-3.448 3.427v3.267c0 .16 0 .319.08.478-1.763.16-3.127 1.594-3.127 3.347v6.773c0 1.833 1.524 3.427 3.448 3.427h8.26c1.844 0 3.447-1.514 3.447-3.427v-6.773c-.08-1.753-1.523-3.188-3.287-3.347zM67.854 16.3c0-.558.481-1.036 1.043-1.036H70.9c.561 0 1.043.478 1.043 1.036v3.267c0 .16-.08.319-.16.478h-3.69c-.08-.16-.16-.318-.16-.478l-.08-3.267zm7.136 14.025c0 .558-.48 1.036-1.042 1.036H65.77a1.058 1.058 0 0 1-1.042-1.036v-6.773c0-.558.481-1.036 1.042-1.036h8.26c.56 0 1.042.478 1.042 1.036l-.08 6.773z" />
<path d="M69.899 24.11c-.638 0-1.197.548-1.197 1.175v2.43c0 .627.559 1.176 1.197 1.176s1.196-.549 1.196-1.176v-2.43c0-.705-.558-1.176-1.196-1.176z" />
</g>
</g>
<g transform="translate(109 4)">
<path
fill="#CCC"
fillRule="nonzero"
d="M6.325 38.352l.614-.636c.254-.263.712-.233 1.023.067.31.3.356.757.102 1.02l-.614.636.643.62c.267.258.242.715-.055 1.022-.296.307-.753.348-1.019.09l-.643-.62-.614.635c-.254.264-.712.234-1.023-.066-.31-.3-.356-.757-.102-1.02l.614-.636-.643-.62c-.266-.258-.242-.715.055-1.022.296-.308.753-.348 1.02-.09l.642.62z"
/>
<ellipse cx="1.224" cy="24.349" stroke="#E2E2E2" strokeWidth=".8" rx="1.146" ry="1.133" />
<ellipse cx="21.21" cy="23.216" stroke="#E2E2E2" strokeWidth=".8" rx="1.564" ry="1.546" />
<path
fill="#EEE"
fillRule="nonzero"
d="M11.558 12.189l.917-.95c.281-.291.677-.366.883-.167.206.2.145.597-.136.888l-.917.95.96.928c.295.284.375.68.178.884-.197.204-.596.139-.89-.146l-.961-.927-.917.95c-.281.29-.677.365-.883.166-.206-.2-.145-.597.136-.888l.917-.95-.96-.928c-.295-.284-.375-.68-.178-.884.197-.204.596-.139.89.146l.961.928z"
/>
<path
fill="#CCC"
fillRule="nonzero"
d="M25.03 3.01l.71-.736c.251-.26.657-.277.905-.037.249.24.246.646-.005.907l-.71.734.744.718c.263.255.285.66.047.906-.237.246-.643.239-.907-.016l-.743-.718-.71.735c-.251.26-.657.277-.906.037-.248-.24-.246-.646.006-.906l.71-.735-.744-.718c-.263-.255-.285-.66-.047-.906.237-.246.643-.238.907.016l.743.718z"
/>
</g>
<g transform="translate(1 -1)">
<path
fill="#CCC"
fillRule="nonzero"
d="M20.486 48.016l.621-.643c.257-.267.72-.238 1.033.065.314.303.36.764.102 1.03l-.621.644.648.626c.269.26.243.722-.057 1.033-.3.31-.761.352-1.03.093l-.648-.627-.622.644c-.257.266-.72.237-1.033-.066-.313-.302-.359-.764-.102-1.03l.622-.643-.649-.627c-.268-.26-.243-.721.057-1.032.3-.311.762-.353 1.03-.093l.649.626z"
/>
<ellipse cx="3.155" cy="37.389" stroke="#CCC" strokeWidth=".8" rx="3.155" ry="3.13" />
<ellipse cx="18.932" cy="29.564" stroke="#E2E2E2" strokeWidth=".8" rx="1.578" ry="1.565" />
<path
fill="#EEE"
fillRule="nonzero"
d="M9.447 16.156l1.118-1.158c.343-.356.825-.447 1.075-.205.251.242.176.726-.167 1.081l-1.118 1.158 1.167 1.128c.358.346.454.828.214 1.076-.24.249-.725.17-1.084-.176l-1.167-1.127-1.118 1.158c-.343.355-.825.447-1.075.205-.251-.242-.176-.727.167-1.082l1.118-1.158L7.41 15.93c-.358-.346-.454-.828-.214-1.077.24-.248.725-.17 1.084.176l1.167 1.128z"
/>
<path
fill="#CCC"
fillRule="nonzero"
d="M22.47 2.013l.699-.724c.214-.222.515-.28.672-.128.156.151.11.454-.105.676l-.699.724.73.704c.224.216.283.518.133.673-.15.156-.453.106-.677-.11l-.73-.704-.698.723c-.215.222-.515.28-.672.128-.157-.15-.11-.454.104-.676l.7-.723-.73-.705c-.224-.216-.284-.517-.134-.673.15-.155.453-.106.677.11l.73.705z"
/>
</g>
</g>
</svg>
)

40
src/icons/illustrations/WriteSeed.js

@ -1,40 +0,0 @@
// @flow
import React from 'react'
export default () => (
<svg width="145" height="109">
<g fill="none" fillRule="evenodd" transform="translate(-1)">
<path
fill="#6490F1"
fillOpacity=".1"
stroke="#142533"
strokeWidth="1.8"
d="M29.105 104.014l19.87-84.34L4.123 35.999a2.7 2.7 0 0 0-1.573 3.566l26.556 64.449zm89.265 1.336L97.73 34.29l44.752 16.288a2.7 2.7 0 0 1 1.49 3.748L118.37 105.35z"
/>
<rect
width="88.2"
height="107.2"
x="29.9"
y=".9"
fill="#FFF"
stroke="#142533"
strokeWidth="1.8"
rx="3.6"
/>
<rect width="50" height="4" x="49" y="14" fill="#6490F1" rx="2" />
<path
fill="#6490F1"
d="M83.18 91.378h24.01a1.81 1.81 0 0 1 0 3.622H83.18a1.81 1.81 0 1 1 0-3.622zm-42.37 0h24.01a1.81 1.81 0 1 1 0 3.622H40.81a1.81 1.81 0 1 1 0-3.622zm42.37-12.675h24.01a1.81 1.81 0 0 1 0 3.621H83.18a1.81 1.81 0 1 1 0-3.621zm-42.37 0h24.01a1.81 1.81 0 1 1 0 3.621H40.81a1.81 1.81 0 1 1 0-3.621zm42.37-12.676h24.01a1.81 1.81 0 0 1 0 3.622H83.18a1.81 1.81 0 1 1 0-3.622zm-42.37 0h24.01a1.81 1.81 0 1 1 0 3.622H40.81a1.81 1.81 0 1 1 0-3.622zm42.37-12.676h24.01a1.81 1.81 0 0 1 0 3.622H83.18a1.81 1.81 0 1 1 0-3.622zm-42.37 0h24.01a1.81 1.81 0 1 1 0 3.622H40.81a1.81 1.81 0 1 1 0-3.622zm42.37-12.675h24.01a1.81 1.81 0 0 1 0 3.621H83.18a1.81 1.81 0 1 1 0-3.621zm-42.37 0h24.01a1.81 1.81 0 1 1 0 3.621H40.81a1.81 1.81 0 1 1 0-3.621zM83.18 28h24.01a1.81 1.81 0 0 1 0 3.622H83.18a1.81 1.81 0 1 1 0-3.622zm-42.37 0h24.01a1.81 1.81 0 1 1 0 3.622H40.81a1.81 1.81 0 1 1 0-3.622z"
opacity=".5"
/>
<path
fill="#FFF"
fillRule="nonzero"
stroke="#6490F1"
strokeWidth="2"
d="M85.623 56.047a17.987 17.987 0 0 1 6.194 13.596c0 9.92-8.032 17.99-17.909 17.99-9.876 0-17.908-8.07-17.908-17.99a17.986 17.986 0 0 1 6.194-13.596v-3.281C62.194 46.278 67.447 41 73.908 41c6.462 0 11.715 5.278 11.715 11.766v3.28zM73.809 73.193zm.003-.018v4.55a.1.1 0 0 0 .096.102.1.1 0 0 0 .097-.102v-4.55a3.149 3.149 0 0 1-.006-.054l-.088-.899.885-.179a4.505 4.505 0 0 0 3.596-4.42c0-2.493-2.008-4.51-4.484-4.51-2.475 0-4.483 2.018-4.483 4.51a4.505 4.505 0 0 0 3.596 4.42l.885.18-.088.898a3.149 3.149 0 0 1-.006.054zm.196.018a.1.1 0 0 0 0 .004v-.004zm0 .004v-.002zm-7.62-19.876a17.719 17.719 0 0 1 7.52-1.668c2.633 0 5.186.577 7.521 1.668v-.555c0-4.17-3.375-7.562-7.52-7.562-4.146 0-7.521 3.392-7.521 7.562v.555z"
/>
</g>
</svg>
)

7
src/internals/index.js

@ -5,6 +5,7 @@ import uuid from 'uuid/v4'
import { setImplementation } from 'api/network'
import sentry from 'sentry/node'
import { DEBUG_NETWORK } from 'config/constants'
import { serializeError } from 'helpers/errors'
require('../env')
@ -75,11 +76,7 @@ process.on('message', m => {
process.send({
type: 'cmd.ERROR',
requestId,
data: {
...error,
name: error && error.name,
message: error && error.message,
},
data: serializeError(error),
})
},
})

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

Loading…
Cancel
Save