From de9e76a70e0702cba2c45256938f36cfbce77183 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Renaudeau?= Date: Mon, 18 Jun 2018 15:35:18 +0200 Subject: [PATCH 01/22] Improve socket implementation --- src/commands/getIsGenuine.js | 2 +- src/helpers/apps/installApp.js | 2 +- src/helpers/apps/uninstallApp.js | 2 +- src/helpers/common.js | 160 ++-------------------------- src/helpers/devices/getIsGenuine.js | 2 +- src/helpers/socket.js | 128 ++++++++++++++++++++++ src/logger.js | 8 ++ static/i18n/en/errors.yml | 5 + 8 files changed, 152 insertions(+), 157 deletions(-) create mode 100644 src/helpers/socket.js diff --git a/src/commands/getIsGenuine.js b/src/commands/getIsGenuine.js index 8b9cfa2e47..c0b66b8bce 100644 --- a/src/commands/getIsGenuine.js +++ b/src/commands/getIsGenuine.js @@ -6,7 +6,7 @@ import { fromPromise } from 'rxjs/observable/fromPromise' import getIsGenuine from 'helpers/devices/getIsGenuine' import { withDevice } from 'helpers/deviceAccess' -type Input = * +type Input = * // FIXME ! type Result = string const cmd: Command = createCommand('getIsGenuine', ({ devicePath, targetId }) => diff --git a/src/helpers/apps/installApp.js b/src/helpers/apps/installApp.js index 2cdd04a303..1998cfb5fb 100644 --- a/src/helpers/apps/installApp.js +++ b/src/helpers/apps/installApp.js @@ -11,6 +11,6 @@ import type { LedgerScriptParams } from 'helpers/common' export default async function installApp( transport: Transport<*>, { appParams }: { appParams: LedgerScriptParams }, -): Promise { +): Promise<*> { return createSocketDialog(transport, '/install', appParams) } diff --git a/src/helpers/apps/uninstallApp.js b/src/helpers/apps/uninstallApp.js index c9f4fc4441..c67c029cd5 100644 --- a/src/helpers/apps/uninstallApp.js +++ b/src/helpers/apps/uninstallApp.js @@ -11,7 +11,7 @@ import type { LedgerScriptParams } from 'helpers/common' export default async function uninstallApp( transport: Transport<*>, { appParams }: { appParams: LedgerScriptParams }, -): Promise { +): Promise<*> { const params = { ...appParams, firmware: appParams.delete, diff --git a/src/helpers/common.js b/src/helpers/common.js index 7d96e48b08..a5a28c8c26 100644 --- a/src/helpers/common.js +++ b/src/helpers/common.js @@ -1,24 +1,13 @@ // @flow -import chalk from 'chalk' -import Websocket from 'ws' +// FIXME remove this file! 'helpers/common.js' RLY? :P + import qs from 'qs' import type Transport from '@ledgerhq/hw-transport' +import { createDeviceSocket } from './socket' import { BASE_SOCKET_URL, APDUS, MANAGER_API_URL } from './constants' -type WebsocketType = { - send: (string, any) => void, - on: (string, Function) => void, -} - -type Message = { - nonce: number, - query?: string, - response?: string, - data: any, -} - export type LedgerScriptParams = { firmware?: string, firmwareKey?: string, @@ -35,59 +24,6 @@ export async function getMemInfos(transport: Transport<*>): Promise { return createSocketDialog(transport, '/get-mem-infos', { targetId, perso: 'perso_11' }) } -/** - * Send data through ws - */ -function socketSend(ws: WebsocketType, msg: Message) { - logWS('SEND', msg) - const strMsg = JSON.stringify(msg) - ws.send(strMsg) -} - -/** - * Exchange data on transport - */ -export async function exchange( - ws: WebsocketType, - transport: Transport<*>, - msg: Message, -): Promise { - const { data, nonce } = msg - const r: Buffer = await transport.exchange(Buffer.from(data, 'hex')) - const status = r.slice(r.length - 2) - const buffer = r.slice(0, r.length - 2) - const strStatus = status.toString('hex') - socketSend(ws, { - nonce, - response: strStatus === '9000' ? 'success' : 'error', - data: buffer.toString('hex'), - }) -} - -/** - * Bulk update on transport - */ -export async function bulk(ws: WebsocketType, transport: Transport<*>, msg: Message) { - const { data, nonce } = msg - - // Execute all apdus and collect last status - let lastStatus = null - for (const apdu of data) { - const r: Buffer = await transport.exchange(Buffer.from(apdu, 'hex')) - lastStatus = r.slice(r.length - 2) - } - if (!lastStatus) { - throw new Error('No status collected from bulk') - } - - const strStatus = lastStatus.toString('hex') - socketSend(ws, { - nonce, - response: strStatus === '9000' ? 'success' : 'error', - data: strStatus === '9000' ? '' : strStatus, - }) -} - /** * Open socket connection with firmware api, and init a dialog * with the device @@ -97,56 +33,10 @@ export async function createSocketDialog( endpoint: string, params: LedgerScriptParams, managerUrl: boolean = false, -) { - return new Promise(async (resolve, reject) => { - try { - let lastData - const url = `${managerUrl ? MANAGER_API_URL : BASE_SOCKET_URL}${endpoint}?${qs.stringify( - params, - )}` - - log('WS CONNECTING', url) - const ws: WebsocketType = new Websocket(url) - - ws.on('open', () => log('WS CONNECTED')) - - ws.on('close', () => { - log('WS CLOSED') - resolve(lastData) - }) - - ws.on('message', async rawMsg => { - const handlers = { - exchange: msg => exchange(ws, transport, msg), - bulk: msg => bulk(ws, transport, msg), - success: msg => { - if (msg.data) { - lastData = msg.data - } else if (msg.result) { - lastData = msg.result - } - }, - error: msg => { - log('WS ERROR', ':(') - throw new Error(msg.data) - }, - } - try { - const msg = JSON.parse(rawMsg) - if (!(msg.query in handlers)) { - throw new Error(`Cannot handle msg of type ${msg.query}`) - } - logWS('RECEIVE', msg) - await handlers[msg.query](msg) - } catch (err) { - log('ERROR', err.toString()) - reject(err) - } - }) - } catch (err) { - reject(err) - } - }) +): Promise { + console.warn('DEPRECATED createSocketDialog: use createDeviceSocket') // eslint-disable-line + const url = `${managerUrl ? MANAGER_API_URL : BASE_SOCKET_URL}${endpoint}?${qs.stringify(params)}` + return createDeviceSocket(transport, url).toPromise() } /** @@ -169,42 +59,6 @@ export async function getFirmwareInfo(transport: Transport<*>) { } } -/** - * Debug helper - */ -export function log(namespace: string, str: string = '', color?: string) { - namespace = namespace.padEnd(15) - // $FlowFixMe - const coloredNamespace = color ? chalk[color](namespace) : namespace - if (__DEV__) { - console.log(`${chalk.bold(`> ${coloredNamespace}`)} ${str}`) // eslint-disable-line no-console - } -} - -/** - * Log a socket send/receive - */ -export function logWS(type: string, msg: Message) { - const arrow = type === 'SEND' ? '↑' : '↓' - const namespace = `${arrow} WS ${type}` - const color = type === 'SEND' ? 'blue' : 'red' - if (msg.nonce) { - let d = '' - if (msg.query === 'exchange') { - d = msg.data.length > 100 ? `${msg.data.substr(0, 97)}...` : msg.data - } else if (msg.query === 'bulk') { - d = `[bulk x ${msg.data.length}]` - } - log( - namespace, - `${String(msg.nonce).padEnd(2)} ${(msg.response || msg.query || '').padEnd(10)} ${d}`, - color, - ) - } else { - log(namespace, JSON.stringify(msg), color) - } -} - /** * Helpers to build OSU and Final firmware params */ diff --git a/src/helpers/devices/getIsGenuine.js b/src/helpers/devices/getIsGenuine.js index 13bfdffc61..9af44aba6f 100644 --- a/src/helpers/devices/getIsGenuine.js +++ b/src/helpers/devices/getIsGenuine.js @@ -5,7 +5,7 @@ import { createSocketDialog } from 'helpers/common' export default async ( transport: Transport<*>, { targetId }: { targetId: string | number }, -): Promise<*> => +): Promise => process.env.SKIP_GENUINE > 0 ? new Promise(resolve => setTimeout(() => resolve('0000'), 1000)) : createSocketDialog(transport, '/genuine', { targetId }, true) diff --git a/src/helpers/socket.js b/src/helpers/socket.js new file mode 100644 index 0000000000..1204b04274 --- /dev/null +++ b/src/helpers/socket.js @@ -0,0 +1,128 @@ +// @flow + +import invariant from 'invariant' +import logger from 'logger' +import Websocket from 'ws' +import type Transport from '@ledgerhq/hw-transport' +import { Observable } from 'rxjs' +import createCustomErrorClass from './createCustomErrorClass' + +const WebsocketConnectionError = createCustomErrorClass('WebsocketConnectionError') +const WebsocketConnectionFailed = createCustomErrorClass('WebsocketConnectionFailed') +const DeviceSocketFail = createCustomErrorClass('DeviceSocketFail') +const DeviceSocketNoBulkStatus = createCustomErrorClass('DeviceSocketNoBulkStatus') +const DeviceSocketNoHandler = createCustomErrorClass('DeviceSocketNoHandler') + +/** + * use Ledger WebSocket API to exchange data with the device + * Returns an Observable of the final result + */ +export const createDeviceSocket = (transport: Transport<*>, url: string) => + Observable.create(o => { + let ws + let lastMessage: ?string + + try { + ws = new Websocket(url) + } catch (err) { + o.error(new WebsocketConnectionFailed(err.message)) + return () => {} + } + invariant(ws, 'websocket is available') + + ws.on('open', () => { + logger.websocket('OPENED', url) + }) + + ws.on('error', e => { + logger.websocket('ERROR', e) + o.error(new WebsocketConnectionError(e.message)) + }) + + ws.on('close', () => { + logger.websocket('CLOSE') + o.next(lastMessage || '') + o.complete() + }) + + const send = (nonce, response, data) => { + const msg = { + nonce, + response, + data, + } + logger.websocket('SEND', msg) + const strMsg = JSON.stringify(msg) + ws.send(strMsg) + } + + const handlers = { + exchange: async input => { + const { data, nonce } = input + const r: Buffer = await transport.exchange(Buffer.from(data, 'hex')) + const status = r.slice(r.length - 2) + const buffer = r.slice(0, r.length - 2) + const strStatus = status.toString('hex') + send(nonce, strStatus === '9000' ? 'success' : 'error', buffer.toString('hex')) + }, + + bulk: async input => { + const { data, nonce } = input + + // Execute all apdus and collect last status + let lastStatus = null + for (const apdu of data) { + const r: Buffer = await transport.exchange(Buffer.from(apdu, 'hex')) + lastStatus = r.slice(r.length - 2) + } + if (!lastStatus) { + throw new DeviceSocketNoBulkStatus() + } + + const strStatus = lastStatus.toString('hex') + + send( + nonce, + strStatus === '9000' ? 'success' : 'error', + strStatus === '9000' ? '' : strStatus, + ) + }, + + success: msg => { + lastMessage = msg.data || msg.result + ws.close() + }, + + error: msg => { + logger.websocket('ERROR', msg.data) + throw new DeviceSocketFail(msg.data) + }, + } + + const stackMessage = async rawMsg => { + try { + const msg = JSON.parse(rawMsg) + if (!(msg.query in handlers)) { + throw new DeviceSocketNoHandler(`Cannot handle msg of type ${msg.query}`, { + query: msg.query, + }) + } + logger.websocket('RECEIVE', msg) + await handlers[msg.query](msg) + } catch (err) { + logger.websocket('ERROR', err.toString()) + o.error(err) + } + } + + ws.on('message', async rawMsg => { + stackMessage(rawMsg) + }) + + return () => { + if (ws.readyState === 1) { + lastMessage = null + ws.close() + } + } + }) diff --git a/src/logger.js b/src/logger.js index e9f47e1adc..b8fe6ca844 100644 --- a/src/logger.js +++ b/src/logger.js @@ -52,6 +52,7 @@ const logDb = !__DEV__ || process.env.DEBUG_DB const logRedux = !__DEV__ || process.env.DEBUG_ACTION const logTabkey = !__DEV__ || process.env.DEBUG_TAB_KEY const logLibcore = !__DEV__ || process.env.DEBUG_LIBCORE +const logWS = !__DEV__ || process.env.DEBUG_WS export default { onCmd: (type: string, id: string, spentTime: number, data?: any) => { @@ -104,6 +105,13 @@ export default { addLog('keydown', msg) }, + websocket: (type: string, msg: *) => { + if (logWS) { + console.log(`~ ${type}:`, msg) + } + addLog('ws', `~ ${type}`, msg) + }, + libcore: (level: string, msg: string) => { if (logLibcore) { console.log(`πŸ›  ${level}: ${msg}`) diff --git a/static/i18n/en/errors.yml b/static/i18n/en/errors.yml index 7b6596f8ff..456fc91a6d 100644 --- a/static/i18n/en/errors.yml +++ b/static/i18n/en/errors.yml @@ -12,3 +12,8 @@ LedgerAPIError: 'A problem occurred with Ledger API. Please try again later. (HT NetworkDown: 'Your internet connection seems down. Please try again later.' NoAddressesFound: 'No accounts found' UserRefusedOnDevice: Transaction have been aborted +WebsocketConnectionError: An error occurred with the socket connection +WebsocketConnectionFailed: Failed to establish a socket connection +DeviceSocketFail: Device socket failure +DeviceSocketNoBulkStatus: Device socket failure (bulk) +DeviceSocketNoHandler: Device socket failure (handler {{query}}) From 898f755de3f87264fb3a7d96cb25fd6d01554d0c Mon Sep 17 00:00:00 2001 From: Anastasia Poupeney Date: Mon, 18 Jun 2018 13:17:51 +0200 Subject: [PATCH 02/22] trim and remove extra spaces in the account name input --- src/components/modals/AccountSettingRenderBody.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/components/modals/AccountSettingRenderBody.js b/src/components/modals/AccountSettingRenderBody.js index 8e02f6b0e1..510613d2f3 100644 --- a/src/components/modals/AccountSettingRenderBody.js +++ b/src/components/modals/AccountSettingRenderBody.js @@ -92,9 +92,14 @@ class HelperComp extends PureComponent { const { updateAccount, setDataModal } = this.props const { accountName, accountUnit } = this.state + const sanitizedAccountName = accountName ? accountName.replace(/\s+/g, ' ').trim() : null - if (accountName !== '') { - account = { ...account, unit: accountUnit || account.unit } + if (account.name || sanitizedAccountName) { + account = { + ...account, + unit: accountUnit || account.unit, + name: sanitizedAccountName || account.name, + } updateAccount(account) setDataModal(MODAL_SETTINGS_ACCOUNT, { account }) onClose() From e0f11108a7393c38127aa324cdfb2be538db6f43 Mon Sep 17 00:00:00 2001 From: Anastasia Poupeney Date: Mon, 18 Jun 2018 16:28:57 +0200 Subject: [PATCH 03/22] readMe and account name sanitize and trim when editing --- README.md | 108 ++++++++++++++++++++++------------- static/docs/architecture.jpg | Bin 0 -> 128055 bytes static/docs/ledgerLogo.png | Bin 0 -> 10426 bytes 3 files changed, 69 insertions(+), 39 deletions(-) create mode 100644 static/docs/architecture.jpg create mode 100644 static/docs/ledgerLogo.png diff --git a/README.md b/README.md index bcf05541d3..da1f0afda0 100644 --- a/README.md +++ b/README.md @@ -5,81 +5,111 @@ :warning: Disclaimer: this project is under active development. Use at your own risks. -## Installation + -#### Requirements +> Ledger Live Desktop is a new generation Ledger Wallet application build with React, Redux and Electron to run natively on the web. The main goal of the app is to provide our users with a single wallet for all crypto currencies supported by our devices. To learn more check out [Ledger](https://www.ledgerwallet.com/?utm_source=redirection&utm_medium=variable) -Project has been tested with +## Architecture -- [NodeJS](https://nodejs.org) v9.3.0 -- [Yarn](https://yarnpkg.com) v1.3.0 +From one side Ledger Desktop app connected to the Blockchain via the in-house written C++ library - LibCore and from the other it communicates to the Ledger Hardware Device to securely sign all transactions. + +

+ +

+ +## Setup + +### Requirements + +- [NodeJS](https://nodejs.org) LTS +- [Yarn](https://yarnpkg.com) LTS - [Python](https://www.python.org/) v2.7.10 (used by [node-gyp](https://github.com/nodejs/node-gyp) to build native addons) - You will also need a C++ compiler -#### Optional +### Optional + +- In the application we use `Museo Sans` font. To include it in the app, you need to have a zip file `museosans.zip` which you should extract and place inside the `static/fonts/museosans` directory -- `Museo Sans` font - for Ledger guys, [follow that link](https://drive.google.com/drive/folders/14R6kGFtx53DuqTyIOjnT7BGogzeyMSzN), download `museosans.zip` and extract it inside the `static/fonts/museosans` directory +## Install -#### Setup +1. Clone or fork the repo -1. Install dependencies +```bash +git clone git@github.com:LedgerHQ/ledger-live-desktop.git +``` + +2. Install dependencies ```bash yarn ``` -2. Create `.env` file +## Run + +Launch the app ```bash -# ENV VARIABLES -# ------------- +yarn start +``` -# Where errors will be tracked (you may not want to edit this line) -# SENTRY_URL= +## Build -# OPTIONAL ENV VARIABLES -# ---------------------- +```bash +# Build & package the whole app +# Creates a .dmg for Mac, .exe installer for Windows, or .AppImage for Linux +# Output files will be created in dist/ folder +yarn dist +``` -# API base url, fallback to our API if not set -API_BASE_URL=http://... +**Note:** Use `yarn dist:dir` to speed up the process: it will skip the packaging step. Handy for debugging builds. You can also use `BUNDLE_ANALYZER=1 yarn dist:dir` to generate [webpack-bundle-analyzer](https://github.com/webpack-contrib/webpack-bundle-analyzer) report. -# Setup device debug mode -DEBUG_DEVICE=0 +--- -# Developer tools position (used only in dev) -# can be one of: right, bottom, undocked, detach -DEV_TOOLS_MODE=bottom +## Config (optional helpers) -# Filter debug output -DEBUG=lwd*,-lwd:syncb +### Create a .env file -# hide the dev window -HIDE_DEV_WINDOW=0 +```bash +SENTRY_URL=... # Edit this line if you want to send errors to your sentry account + +API_BASE_URL=http://... # API base url, fallback to our API if not set + +DEBUG_DEVICE=0 # Setup device debug mode + +DEV_TOOLS_MODE=bottom # Developer tools position (used only in dev). Options: right, bottom, undocked, detach + +DEBUG=lwd*,-lwd:syncb # Filter debug output + +HIDE_DEV_WINDOW=0 # hide the dev window + +SKIP_ONBOARDING=1 # To skip the onboarding ``` -#### Development commands +### Launch storybook ```bash -# Launch the app -yarn start - -# Launch the storybook yarn storybook +``` -# Code quality checks +### Run code quality checks + +```bash yarn lint # launch eslint yarn prettier # launch prettier yarn flow # launch flow yarn test # launch unit tests ``` -#### Building from source +### Programmaically reset hard the app + +Stop the app and to clean accounts, settings, etc, run ```bash -# Build & package the whole app -# Creates a .dmg for Mac, .exe installer for Windows, or .AppImage for Linux -# Output files will be created in dist/ folder -yarn dist +rm -rf ~/Library/Application\ Support/Electron/ ``` -**Note:** Use `yarn dist:dir` to speed up the process: it will skip the packaging step. Handy for debugging builds. You can also use `BUNDLE_ANALYZER=1 yarn dist:dir` to generate [webpack-bundle-analyzer](https://github.com/webpack-contrib/webpack-bundle-analyzer) report. +## Additional Info on tools used in the app + +- Sentry - error-tracking software, [learn more](https://sentry.io/welcome/) +- Storybook - UI development environment, [learn more](https://storybook.js.org/) +- U2F - We use a custom transport encapsulation to pass instructions to the hardware device with U2F protocol. [Learn more about U2F](https://en.wikipedia.org/wiki/Universal_2nd_Factor) diff --git a/static/docs/architecture.jpg b/static/docs/architecture.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8046a9e4063dbb7197ba972bf2fc6322d09da9a7 GIT binary patch literal 128055 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%0AB7w5!Q*liQ9c^i82LRgC9fF00jL)2 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�XBtO9vpRXSKn$r;@Z5jXm$@UxMb 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 literal 0 HcmV?d00001 diff --git a/static/docs/ledgerLogo.png b/static/docs/ledgerLogo.png new file mode 100644 index 0000000000000000000000000000000000000000..2a45816b8fec7c414f51f2bdf57caba2c0e873b1 GIT binary patch literal 10426 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 VCa<0iG$Rses_WdZQnh{lzW^AKhdlrQ literal 0 HcmV?d00001 From 1c10bf403c495393139db461063569e33df0bbdf Mon Sep 17 00:00:00 2001 From: Anastasia Poupeney Date: Mon, 18 Jun 2018 17:07:38 +0200 Subject: [PATCH 04/22] minor typo fix --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index da1f0afda0..410034fcb6 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ :warning: Disclaimer: this project is under active development. Use at your own risks. - + > Ledger Live Desktop is a new generation Ledger Wallet application build with React, Redux and Electron to run natively on the web. The main goal of the app is to provide our users with a single wallet for all crypto currencies supported by our devices. To learn more check out [Ledger](https://www.ledgerwallet.com/?utm_source=redirection&utm_medium=variable) @@ -14,7 +14,7 @@ From one side Ledger Desktop app connected to the Blockchain via the in-house written C++ library - LibCore and from the other it communicates to the Ledger Hardware Device to securely sign all transactions.

- +

## Setup From 40724224b1814ab4fedb4307858edb0602ef8db2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Renaudeau?= Date: Mon, 18 Jun 2018 18:00:27 +0200 Subject: [PATCH 05/22] unify all constants --- src/api/Ledger.js | 5 +-- src/components/CalculateBalance.js | 5 +++ src/config/constants.js | 45 ++++++++++++++++++- src/config/languages.js | 3 +- src/helpers/apps/listApps.js | 4 +- src/helpers/common.js | 12 ++++- src/helpers/constants.js | 13 ------ src/helpers/deviceAccess.js | 3 +- src/helpers/devices/getFirmwareInfo.js | 13 +++--- src/helpers/devices/getIsGenuine.js | 3 +- .../devices/getLatestFirmwareForDevice.js | 6 +-- src/helpers/libcore.js | 3 +- src/internals/index.js | 3 +- src/logger.js | 21 ++++++--- src/reducers/onboarding.js | 3 +- src/renderer/i18n/instanciate.js | 3 +- src/renderer/init.js | 3 +- 17 files changed, 104 insertions(+), 44 deletions(-) delete mode 100644 src/helpers/constants.js diff --git a/src/api/Ledger.js b/src/api/Ledger.js index 2c84ab5da5..f84d0f7c58 100644 --- a/src/api/Ledger.js +++ b/src/api/Ledger.js @@ -1,7 +1,6 @@ // @flow import type { Currency } from '@ledgerhq/live-common/lib/types' - -const BASE_URL = process.env.LEDGER_REST_API_BASE || 'https://api.ledgerwallet.com/' +import { LEDGER_REST_API_BASE } from 'config/constants' export const blockchainBaseURL = ({ ledgerExplorerId }: Currency): ?string => - ledgerExplorerId ? `${BASE_URL}blockchain/v2/${ledgerExplorerId}` : null + ledgerExplorerId ? `${LEDGER_REST_API_BASE}blockchain/v2/${ledgerExplorerId}` : null diff --git a/src/components/CalculateBalance.js b/src/components/CalculateBalance.js index 709f7114f0..d94f0b6e00 100644 --- a/src/components/CalculateBalance.js +++ b/src/components/CalculateBalance.js @@ -79,7 +79,12 @@ const mapStateToProps = (state: State, props: OwnProps) => { } } +const hash = ({ balanceHistory, balanceEnd }) => `${balanceHistory.length}_${balanceEnd}` + class CalculateBalance extends Component { + shouldComponentUpdate(nextProps) { + return hash(nextProps) !== hash(this.props) + } render() { const { children } = this.props return children(this.props) diff --git a/src/config/constants.js b/src/config/constants.js index 343b4ed598..74ca72ef85 100644 --- a/src/config/constants.js +++ b/src/config/constants.js @@ -1,10 +1,18 @@ // @flow -const intFromEnv = (key: string, def: number) => { +const intFromEnv = (key: string, def: number): number => { const v = process.env[key] if (!isNaN(v)) return parseInt(v, 10) return def } +const boolFromEnv = (key: string): boolean => { + const v = process.env[key] + return (v && v !== '0' && v !== 'false') || false +} + +const stringFromEnv = (key: string, def: string): string => process.env[key] || def + +// time and delays... export const GET_CALLS_TIMEOUT = intFromEnv('GET_CALLS_TIMEOUT', 30 * 1000) export const GET_CALLS_RETRY = intFromEnv('GET_CALLS_RETRY', 2) @@ -19,6 +27,41 @@ export const CHECK_UPDATE_DELAY = 5e3 export const DEVICE_DISCONNECT_DEBOUNCE = intFromEnv('LEDGER_DEVICE_DISCONNECT_DEBOUNCE', 500) +// Endpoints... + +export const LEDGER_REST_API_BASE = stringFromEnv( + 'LEDGER_REST_API_BASE', + 'https://api.ledgerwallet.com/', +) +export const MANAGER_API_BASE = stringFromEnv( + 'MANAGER_API_BASE', + 'https://beta.manager.live.ledger.fr/api', +) +export const BASE_SOCKET_URL = stringFromEnv('BASE_SOCKET_URL', 'ws://api.ledgerwallet.com/update') +export const BASE_SOCKET_URL_SECURE = stringFromEnv( + 'BASE_SOCKET_URL', + 'wss://api.ledgerwallet.com/update', +) + +// Flags + +export const DEBUG_DEVICE = boolFromEnv('DEBUG_DEVICE') +export const DEBUG_NETWORK = boolFromEnv('DEBUG_NETWORK') +export const DEBUG_COMMANDS = boolFromEnv('DEBUG_COMMANDS') +export const DEBUG_DB = boolFromEnv('DEBUG_DB') +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 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') +export const SKIP_ONBOARDING = boolFromEnv('SKIP_ONBOARDING') +export const SHOW_LEGACY_NEW_ACCOUNT = boolFromEnv('SHOW_LEGACY_NEW_ACCOUNT') +export const HIGHLIGHT_I18N = boolFromEnv('HIGHLIGHT_I18N') + +// Other constants + export const MODAL_ADD_ACCOUNTS = 'MODAL_ADD_ACCOUNTS' export const MODAL_OPERATION_DETAILS = 'MODAL_OPERATION_DETAILS' export const MODAL_RECEIVE = 'MODAL_RECEIVE' diff --git a/src/config/languages.js b/src/config/languages.js index 96e3d3a973..29774a7fc3 100644 --- a/src/config/languages.js +++ b/src/config/languages.js @@ -1,6 +1,7 @@ // @flow +import { LEDGER_DEBUG_ALL_LANGS } from 'config/constants' const allLanguages = ['en', 'fr'] const prodStableLanguages = ['en'] -const languages = process.env.LEDGER_DEBUG_ALL_LANGS ? allLanguages : prodStableLanguages +const languages = LEDGER_DEBUG_ALL_LANGS ? allLanguages : prodStableLanguages export default languages diff --git a/src/helpers/apps/listApps.js b/src/helpers/apps/listApps.js index edd5436607..becd83732e 100644 --- a/src/helpers/apps/listApps.js +++ b/src/helpers/apps/listApps.js @@ -1,12 +1,12 @@ // @flow import axios from 'axios' -import { API_BASE_URL } from 'helpers/constants' +import { MANAGER_API_BASE } from 'config/constants' export default async (targetId: string | number) => { try { const { data: deviceData } = await axios.get( - `${API_BASE_URL}/device_versions_target_id/${targetId}`, + `${MANAGER_API_BASE}/device_versions_target_id/${targetId}`, ) const { data } = await axios.get('https://api.ledgerwallet.com/update/applications') diff --git a/src/helpers/common.js b/src/helpers/common.js index a5a28c8c26..34b34f094b 100644 --- a/src/helpers/common.js +++ b/src/helpers/common.js @@ -4,9 +4,15 @@ import qs from 'qs' import type Transport from '@ledgerhq/hw-transport' +import { BASE_SOCKET_URL, BASE_SOCKET_URL_SECURE } from 'config/constants' import { createDeviceSocket } from './socket' -import { BASE_SOCKET_URL, APDUS, MANAGER_API_URL } from './constants' +const APDUS = { + GET_FIRMWARE: [0xe0, 0x01, 0x00, 0x00], + // we dont have common call that works inside app & dashboard + // TODO: this should disappear. + GET_FIRMWARE_FALLBACK: [0xe0, 0xc4, 0x00, 0x00], +} export type LedgerScriptParams = { firmware?: string, @@ -35,7 +41,9 @@ export async function createSocketDialog( managerUrl: boolean = false, ): Promise { console.warn('DEPRECATED createSocketDialog: use createDeviceSocket') // eslint-disable-line - const url = `${managerUrl ? MANAGER_API_URL : BASE_SOCKET_URL}${endpoint}?${qs.stringify(params)}` + const url = `${managerUrl ? BASE_SOCKET_URL_SECURE : BASE_SOCKET_URL}${endpoint}?${qs.stringify( + params, + )}` return createDeviceSocket(transport, url).toPromise() } diff --git a/src/helpers/constants.js b/src/helpers/constants.js deleted file mode 100644 index 254ede2385..0000000000 --- a/src/helpers/constants.js +++ /dev/null @@ -1,13 +0,0 @@ -// Socket endpoint - -export const BASE_SOCKET_URL = 'ws://api.ledgerwallet.com/update' -export const MANAGER_API_URL = 'wss://api.ledgerwallet.com/update' -export const API_BASE_URL = process.env.API_BASE_URL || 'https://beta.manager.live.ledger.fr/api' - -// List of APDUS -export const APDUS = { - GET_FIRMWARE: [0xe0, 0x01, 0x00, 0x00], - // we dont have common call that works inside app & dashboard - // TODO: this should disappear. - GET_FIRMWARE_FALLBACK: [0xe0, 0xc4, 0x00, 0x00], -} diff --git a/src/helpers/deviceAccess.js b/src/helpers/deviceAccess.js index 7d857f4adc..34aa349931 100644 --- a/src/helpers/deviceAccess.js +++ b/src/helpers/deviceAccess.js @@ -2,6 +2,7 @@ import createSemaphore from 'semaphore' import type Transport from '@ledgerhq/hw-transport' import TransportNodeHid from '@ledgerhq/hw-transport-node-hid' +import { DEBUG_DEVICE } from 'config/constants' import { retry } from './promise' // all open to device must use openDevice so we can prevent race conditions @@ -20,7 +21,7 @@ export const withDevice: WithDevice = devicePath => { takeSemaphorePromise(sem, async () => { const t = await retry(() => TransportNodeHid.open(devicePath), { maxRetry: 1 }) - if (process.env.DEBUG_DEVICE > 0) t.setDebugMode(true) + if (DEBUG_DEVICE) t.setDebugMode(true) try { const res = await job(t) // $FlowFixMe diff --git a/src/helpers/devices/getFirmwareInfo.js b/src/helpers/devices/getFirmwareInfo.js index f0fd05faf7..389cbd1bd7 100644 --- a/src/helpers/devices/getFirmwareInfo.js +++ b/src/helpers/devices/getFirmwareInfo.js @@ -2,7 +2,7 @@ import axios from 'axios' import isEmpty from 'lodash/isEmpty' -import { API_BASE_URL } from 'helpers/constants' +import { MANAGER_API_BASE } from 'config/constants' type Input = { version: string, @@ -12,10 +12,13 @@ type Input = { let error export default async (data: Input) => { try { - const { data: seFirmwareVersion } = await axios.post(`${API_BASE_URL}/firmware_versions_name`, { - se_firmware_name: data.version, - target_id: data.targetId, - }) + const { data: seFirmwareVersion } = await axios.post( + `${MANAGER_API_BASE}/firmware_versions_name`, + { + se_firmware_name: data.version, + target_id: data.targetId, + }, + ) if (!isEmpty(seFirmwareVersion)) { return seFirmwareVersion diff --git a/src/helpers/devices/getIsGenuine.js b/src/helpers/devices/getIsGenuine.js index 9af44aba6f..ad82420fc5 100644 --- a/src/helpers/devices/getIsGenuine.js +++ b/src/helpers/devices/getIsGenuine.js @@ -1,11 +1,12 @@ // @flow import type Transport from '@ledgerhq/hw-transport' import { createSocketDialog } from 'helpers/common' +import { SKIP_GENUINE } from 'config/constants' export default async ( transport: Transport<*>, { targetId }: { targetId: string | number }, ): Promise => - process.env.SKIP_GENUINE > 0 + SKIP_GENUINE ? new Promise(resolve => setTimeout(() => resolve('0000'), 1000)) : createSocketDialog(transport, '/genuine', { targetId }, true) diff --git a/src/helpers/devices/getLatestFirmwareForDevice.js b/src/helpers/devices/getLatestFirmwareForDevice.js index 9b081681e8..f3f2ae1789 100644 --- a/src/helpers/devices/getLatestFirmwareForDevice.js +++ b/src/helpers/devices/getLatestFirmwareForDevice.js @@ -1,7 +1,7 @@ // @flow import axios from 'axios' import isEmpty from 'lodash/isEmpty' -import { API_BASE_URL } from 'helpers/constants' +import { MANAGER_API_BASE } from 'config/constants' import getFirmwareInfo from './getFirmwareInfo' @@ -17,11 +17,11 @@ export default async (data: Input) => { // Get device infos from targetId const { data: deviceVersion } = await axios.get( - `${API_BASE_URL}/device_versions_target_id/${data.targetId}`, + `${MANAGER_API_BASE}/device_versions_target_id/${data.targetId}`, ) // Fetch next possible firmware - const { data: serverData } = await axios.post(`${API_BASE_URL}/get_latest_firmware`, { + const { data: serverData } = await axios.post(`${MANAGER_API_BASE}/get_latest_firmware`, { current_se_firmware_version: seFirmwareVersion.id, device_version: deviceVersion.id, providers: [1], diff --git a/src/helpers/libcore.js b/src/helpers/libcore.js index b04049c358..3ab6f8757b 100644 --- a/src/helpers/libcore.js +++ b/src/helpers/libcore.js @@ -4,6 +4,7 @@ import logger from 'logger' import Btc from '@ledgerhq/hw-app-btc' import { withDevice } from 'helpers/deviceAccess' import { getCryptoCurrencyById } from '@ledgerhq/live-common/lib/helpers/currencies' +import { SHOW_LEGACY_NEW_ACCOUNT } from 'config/constants' import type { AccountRaw, OperationRaw, OperationType } from '@ledgerhq/live-common/lib/types' import type { NJSAccount, NJSOperation } from '@ledgerhq/ledger-core/src/ledgercore_doc' @@ -28,8 +29,6 @@ type Props = { onAccountScanned: AccountRaw => void, } -const { SHOW_LEGACY_NEW_ACCOUNT } = process.env - export function scanAccountsOnDevice(props: Props): Promise { const { devicePath, currencyId, onAccountScanned, core } = props const currency = getCryptoCurrencyById(currencyId) diff --git a/src/internals/index.js b/src/internals/index.js index 6c82d86fc1..c9d156bb22 100644 --- a/src/internals/index.js +++ b/src/internals/index.js @@ -4,6 +4,7 @@ import logger from 'logger' import uuid from 'uuid/v4' import { setImplementation } from 'api/network' import sentry from 'sentry/node' +import { DEBUG_NETWORK } from 'config/constants' require('../env') @@ -15,7 +16,7 @@ let sentryEnabled = process.env.INITIAL_SENTRY_ENABLED || false sentry(() => sentryEnabled, process.env.SENTRY_USER_ID) -if (process.env.DEBUG_NETWORK) { +if (DEBUG_NETWORK) { setImplementation(networkArg => { const id = uuid() return new Promise((resolve, reject) => { diff --git a/src/logger.js b/src/logger.js index b8fe6ca844..cc6cfc820a 100644 --- a/src/logger.js +++ b/src/logger.js @@ -9,6 +9,15 @@ * - for analytics in the future */ +import { + DEBUG_COMMANDS, + DEBUG_DB, + DEBUG_ACTION, + DEBUG_TAB_KEY, + DEBUG_LIBCORE, + DEBUG_WS, +} from 'config/constants' + const logs = [] const MAX_LOG_LENGTH = 500 @@ -47,12 +56,12 @@ const makeSerializableLog = (o: mixed) => { return String(o) } -const logCmds = !__DEV__ || process.env.DEBUG_COMMANDS -const logDb = !__DEV__ || process.env.DEBUG_DB -const logRedux = !__DEV__ || process.env.DEBUG_ACTION -const logTabkey = !__DEV__ || process.env.DEBUG_TAB_KEY -const logLibcore = !__DEV__ || process.env.DEBUG_LIBCORE -const logWS = !__DEV__ || process.env.DEBUG_WS +const logCmds = !__DEV__ || DEBUG_COMMANDS +const logDb = !__DEV__ || DEBUG_DB +const logRedux = !__DEV__ || DEBUG_ACTION +const logTabkey = !__DEV__ || DEBUG_TAB_KEY +const logLibcore = !__DEV__ || DEBUG_LIBCORE +const logWS = !__DEV__ || DEBUG_WS export default { onCmd: (type: string, id: string, spentTime: number, data?: any) => { diff --git a/src/reducers/onboarding.js b/src/reducers/onboarding.js index 2281655638..8fd43f1bb0 100644 --- a/src/reducers/onboarding.js +++ b/src/reducers/onboarding.js @@ -1,5 +1,6 @@ // @flow +import { SKIP_ONBOARDING } from 'config/constants' import { handleActions, createAction } from 'redux-actions' type Step = { @@ -29,7 +30,7 @@ export type OnboardingState = { const state: OnboardingState = { stepIndex: 0, - stepName: process.env.SKIP_ONBOARDING ? 'finish' : 'start', + stepName: SKIP_ONBOARDING ? 'finish' : 'start', genuine: { pinStepPass: false, recoveryStepPass: false, diff --git a/src/renderer/i18n/instanciate.js b/src/renderer/i18n/instanciate.js index f6e6213a4c..e4217dfe5c 100644 --- a/src/renderer/i18n/instanciate.js +++ b/src/renderer/i18n/instanciate.js @@ -1,3 +1,4 @@ +import { HIGHLIGHT_I18N } from 'config/constants' import i18n from 'i18next' const commonConfig = { @@ -29,7 +30,7 @@ export function createWithBackend(backend, backendOpts) { ...backendOpts, } - if (process.env.HIGHLIGHT_I18N) { + if (HIGHLIGHT_I18N) { config.postProcess = 'highlight' } diff --git a/src/renderer/init.js b/src/renderer/init.js index d902122dfd..0ad9407dde 100644 --- a/src/renderer/init.js +++ b/src/renderer/init.js @@ -11,6 +11,7 @@ import moment from 'moment' import createStore from 'renderer/createStore' import events from 'renderer/events' +import { LEDGER_RESET_ALL } from 'config/constants' import { enableGlobalTab, disableGlobalTab, isGlobalTabEnabled } from 'config/global-tab' import { fetchAccounts } from 'actions/accounts' @@ -34,7 +35,7 @@ const rootNode = document.getElementById('app') const TAB_KEY = 9 async function init() { - if (process.env.LEDGER_RESET_ALL) { + if (LEDGER_RESET_ALL) { await hardReset() } From 486e44aae7b1164037e825da29d84bb316fdeee7 Mon Sep 17 00:00:00 2001 From: meriadec Date: Mon, 18 Jun 2018 18:17:01 +0200 Subject: [PATCH 06/22] Prevent unmounted setState and remove listener on StickyBackToTop --- src/components/StickyBackToTop.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/StickyBackToTop.js b/src/components/StickyBackToTop.js index 9e10697803..dd2533b741 100644 --- a/src/components/StickyBackToTop.js +++ b/src/components/StickyBackToTop.js @@ -49,6 +49,7 @@ class StickyBackToTop extends PureComponent { const { scrollContainer } = this.props.getGrowScroll() if (scrollContainer) { const listener = () => { + if (this._unmounted) return const { scrollTop } = scrollContainer const visible = scrollTop > this.props.scrollThreshold this.setState(previous => { @@ -59,11 +60,12 @@ class StickyBackToTop extends PureComponent { }) } scrollContainer.addEventListener('scroll', listener) - this.releaseListener = () => scrollContainer.addEventListener('scroll', listener) + this.releaseListener = () => scrollContainer.removeEventListener('scroll', listener) } } componentWillUnmount() { + this._unmounted = true this.releaseListener() } From 65e85633bad9f660e8ef90e7745ff8c1800e905f Mon Sep 17 00:00:00 2001 From: meriadec Date: Mon, 18 Jun 2018 18:17:28 +0200 Subject: [PATCH 07/22] Add padding-bottom to ExchangePage --- src/components/ExchangePage/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ExchangePage/index.js b/src/components/ExchangePage/index.js index f3f9064d2f..f5c7f12937 100644 --- a/src/components/ExchangePage/index.js +++ b/src/components/ExchangePage/index.js @@ -41,7 +41,7 @@ class ExchangePage extends PureComponent { ] return ( - + {t('app:exchange.title')} From 8c099bda9e8639ebc5e958669ad1ab743f73f2ec Mon Sep 17 00:00:00 2001 From: Anastasia Poupeney Date: Mon, 18 Jun 2018 18:26:25 +0200 Subject: [PATCH 08/22] add ellipsis to the account name on operation row --- src/components/OperationsList/AccountCell.js | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/components/OperationsList/AccountCell.js b/src/components/OperationsList/AccountCell.js index 1612c9215b..3e2405f1cb 100644 --- a/src/components/OperationsList/AccountCell.js +++ b/src/components/OperationsList/AccountCell.js @@ -29,12 +29,22 @@ class AccountCell extends PureComponent { {Icon && } - - {accountName} - + {accountName} ) } } export default AccountCell + +const AccountNameEllipsis = styled(Box).attrs({ + ff: 'Open Sans|SemiBold', + fontSize: 3, + color: 'dark', + flexShrink: 1, +})` + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +` From 955557f411e495e39c9d579e57b1fc62cca8cfd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Renaudeau?= Date: Mon, 18 Jun 2018 18:28:20 +0200 Subject: [PATCH 09/22] README: update the env var list --- README.md | 46 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 410034fcb6..48b1ba3112 100644 --- a/README.md +++ b/README.md @@ -67,22 +67,40 @@ yarn dist ## Config (optional helpers) -### Create a .env file +### Environment variables -```bash -SENTRY_URL=... # Edit this line if you want to send errors to your sentry account - -API_BASE_URL=http://... # API base url, fallback to our API if not set - -DEBUG_DEVICE=0 # Setup device debug mode - -DEV_TOOLS_MODE=bottom # Developer tools position (used only in dev). Options: right, bottom, undocked, detach +(you can use a .env or export environment variables) -DEBUG=lwd*,-lwd:syncb # Filter debug output - -HIDE_DEV_WINDOW=0 # hide the dev window - -SKIP_ONBOARDING=1 # To skip the onboarding +```bash +DEV_TOOLS_MODE=bottom # devtools position Options: right, bottom, undocked, detach +HIDE_DEV_WINDOW=0 + +## flags for development purpose +DEBUG_DEVICE=1 +DEBUG_NETWORK=1 +DEBUG_COMMANDS=1 +DEBUG_DB=1 +DEBUG_ACTION=1 +DEBUG_TAB_KEY=1 +DEBUG_LIBCORE=1 +DEBUG_WS=1 +LEDGER_RESET_ALL=1 +LEDGER_DEBUG_ALL_LANGS=1 +SKIP_GENUINE=1 +SKIP_ONBOARDING=1 +SHOW_LEGACY_NEW_ACCOUNT=1 +HIGHLIGHT_I18N=1 + +## constants +GET_CALLS_TIMEOUT=30000 +GET_CALLS_RETRY=2 +SYNC_MAX_CONCURRENT=6 +SYNC_BOOT_DELAY=2000 +SYNC_ALL_INTERVAL=60000 +CHECK_APP_INTERVAL_WHEN_INVALID=600 +CHECK_APP_INTERVAL_WHEN_VALID=1200 +CHECK_UPDATE_DELAY=5000 +DEVICE_DISCONNECT_DEBOUNCE=500 ``` ### Launch storybook From 62f95aeb5253e2a25468a91bc6a4b7340c5dceb4 Mon Sep 17 00:00:00 2001 From: meriadec Date: Mon, 18 Jun 2018 18:33:55 +0200 Subject: [PATCH 10/22] Flow fix --- src/components/StickyBackToTop.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/StickyBackToTop.js b/src/components/StickyBackToTop.js index dd2533b741..b42c11e13a 100644 --- a/src/components/StickyBackToTop.js +++ b/src/components/StickyBackToTop.js @@ -69,6 +69,8 @@ class StickyBackToTop extends PureComponent { this.releaseListener() } + _unmounted = false + onClick = () => { const { scrollContainer } = this.props.getGrowScroll() if (scrollContainer) { From a715599d11231da8924787cc09567bb0728428a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Renaudeau?= Date: Mon, 18 Jun 2018 19:23:24 +0200 Subject: [PATCH 11/22] ridiculous fix --- src/components/base/GrowScroll/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/base/GrowScroll/index.js b/src/components/base/GrowScroll/index.js index a2686a07b3..979e1e25c7 100644 --- a/src/components/base/GrowScroll/index.js +++ b/src/components/base/GrowScroll/index.js @@ -41,7 +41,7 @@ class GrowScroll extends PureComponent { : { display: 'flex', flex: 1, - positoin: 'relative', + position: 'relative', }), } From 96bf196b916868f260a55177d52a1d343fd401a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Renaudeau?= Date: Mon, 18 Jun 2018 19:46:09 +0200 Subject: [PATCH 12/22] fix GrowScroll to blink scroll because of `auto` --- src/components/base/GrowScroll/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/base/GrowScroll/index.js b/src/components/base/GrowScroll/index.js index 979e1e25c7..4b8de4817e 100644 --- a/src/components/base/GrowScroll/index.js +++ b/src/components/base/GrowScroll/index.js @@ -46,7 +46,7 @@ class GrowScroll extends PureComponent { } const scrollContainerStyles = { - overflowY: 'auto', + overflowY: 'scroll', marginRight: `-80px`, paddingRight: `80px`, ...(maxHeight From b5520ab65aac4949309d8feb99583fc8bba8578d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Renaudeau?= Date: Mon, 18 Jun 2018 20:28:26 +0200 Subject: [PATCH 13/22] update README --- README.md | 40 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 48b1ba3112..e0a9745bb3 100644 --- a/README.md +++ b/README.md @@ -105,6 +105,8 @@ DEVICE_DISCONNECT_DEBOUNCE=500 ### Launch storybook +We use [storybook](https://storybook.js.org/) for UI development. + ```bash yarn storybook ``` @@ -126,8 +128,38 @@ Stop the app and to clean accounts, settings, etc, run rm -rf ~/Library/Application\ Support/Electron/ ``` -## Additional Info on tools used in the app +## File structure -- Sentry - error-tracking software, [learn more](https://sentry.io/welcome/) -- Storybook - UI development environment, [learn more](https://storybook.js.org/) -- U2F - We use a custom transport encapsulation to pass instructions to the hardware device with U2F protocol. [Learn more about U2F](https://en.wikipedia.org/wiki/Universal_2nd_Factor) +``` +. +β”œβ”€β”€ dist : output folder generate by the build +β”œβ”€β”€ scripts : commands (for building, releasing,...) +β”œβ”€β”€ src +β”‚Β Β  β”œβ”€β”€ internals : code that run on the 'internal' thread. +β”‚Β Β  β”œβ”€β”€ main : code that run on the 'main' thread. +β”‚Β Β  β”œβ”€β”€ renderer : code that run on the 'renderer' thread +β”‚Β Β  β”œβ”€β”€ components : all the React components +| └── modals : sub levels for the modals +β”‚Β Β  β”œβ”€β”€ api : related to HTTP APIs +β”‚Β Β  β”œβ”€β”€ bridge : an abstraction on top of blockchains apis (libcore / js impls) +β”‚Β Β  β”œβ”€β”€ commands : an abstraction to run code over the internal thread +β”‚Β Β  β”œβ”€β”€ icons : all the icons of our app, as React components. +β”‚Β Β  β”œβ”€β”€ config : contains the constants,... +β”‚Β Β  β”œβ”€β”€ helpers : generic folder for our business logic (might be reorganized in the future) +β”‚Β Β  β”œβ”€β”€ middlewares : redux middlewares +β”‚Β Β  β”œβ”€β”€ actions : redux actions +β”‚Β Β  β”œβ”€β”€ reducers : redux reducers +β”‚Β Β  β”œβ”€β”€ sentry : for our bug tracker +β”‚Β Β  β”œβ”€β”€ stories : for storybook +β”‚Β Β  β”œβ”€β”€ styles : theme +β”‚Β Β  β”œβ”€β”€ logger.js : abstraction for all our console.log s +β”‚Β Β  └── types : global flow types +β”œβ”€β”€ static +β”‚Β Β  β”œβ”€β”€ docs +β”‚Β Β  β”œβ”€β”€ fonts +β”‚Β Β  β”œβ”€β”€ i18n +β”‚Β Β  β”œβ”€β”€ images +β”‚Β Β  └── videos +β”œβ”€β”€ webpack : build configuration +└── yarn.lock +``` From 53ff157c9f9ad4bc1030f0c74e7e9104dc431ea7 Mon Sep 17 00:00:00 2001 From: Thibaut Date: Mon, 18 Jun 2018 21:17:36 +0200 Subject: [PATCH 14/22] Reload translations mapped to `ctrl + l` (#607) * Reload translations button added to dev internal tools * Revert changes (minus Debug modal layout fix) * Language reloading binded to `ctrl + l` * Use component lifecycle * Restore debug modal content height --- src/components/layout/Default.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/components/layout/Default.js b/src/components/layout/Default.js index a11652abf4..e7286fa07d 100644 --- a/src/components/layout/Default.js +++ b/src/components/layout/Default.js @@ -32,11 +32,15 @@ const Main = styled(GrowScroll).attrs({ type Props = { location: Location, + i18n: { + reloadResources: Function, + }, } class Default extends Component { componentDidMount() { window.requestAnimationFrame(() => (this._timeout = setTimeout(() => window.onAppReady(), 300))) + window.addEventListener('keydown', this.kbShortcut) } componentDidUpdate(prevProps) { @@ -54,6 +58,13 @@ class Default extends Component { componentWillUnmount() { clearTimeout(this._timeout) + window.removeEventListener('keydown', this.kbShortcut) // Prevents adding multiple listeners when hot reloading + } + + kbShortcut = event => { + if (event.ctrlKey && event.key === 'l') { + this.props.i18n.reloadResources() + } } _timeout = undefined From 7b2a883313a82128ef3aa269c0ae8d7d5abeffe1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Renaudeau?= Date: Tue, 19 Jun 2018 09:00:10 +0200 Subject: [PATCH 15/22] selectedDayRange is now a settings --- src/components/AccountPage/index.js | 43 +++++++++++------------ src/components/BalanceSummary/index.js | 10 +++--- src/components/DashboardPage/index.js | 44 +++++++++++------------- src/components/PillsDaysCount.js | 25 ++++++-------- src/components/base/Chart/refreshDraw.js | 4 +-- src/reducers/settings.js | 11 ++++++ 6 files changed, 69 insertions(+), 68 deletions(-) diff --git a/src/components/AccountPage/index.js b/src/components/AccountPage/index.js index 312c6d356b..5aadb53040 100644 --- a/src/components/AccountPage/index.js +++ b/src/components/AccountPage/index.js @@ -16,8 +16,15 @@ import type { T } from 'types/common' import { rgba } from 'styles/helpers' +import { saveSettings } from 'actions/settings' import { accountSelector } from 'reducers/accounts' -import { counterValueCurrencySelector, localeSelector } from 'reducers/settings' +import { + counterValueCurrencySelector, + localeSelector, + selectedTimeRangeSelector, + timeRangeDaysByKey, +} from 'reducers/settings' +import type { TimeRange } from 'reducers/settings' import { openModal } from 'reducers/modals' import IconAccountSettings from 'icons/AccountSettings' @@ -63,10 +70,12 @@ const mapStateToProps = (state, props) => ({ account: accountSelector(state, { accountId: props.match.params.id }), counterValue: counterValueCurrencySelector(state), settings: localeSelector(state), + selectedTimeRange: selectedTimeRangeSelector(state), }) const mapDispatchToProps = { openModal, + saveSettings, } type Props = { @@ -74,30 +83,20 @@ type Props = { t: T, account?: Account, openModal: Function, + saveSettings: ({ selectedTimeRange: TimeRange }) => *, + selectedTimeRange: TimeRange, } -type State = { - selectedTime: string, - daysCount: number, -} - -class AccountPage extends PureComponent { - state = { - selectedTime: 'month', - daysCount: 30, +class AccountPage extends PureComponent { + handleChangeSelectedTime = item => { + this.props.saveSettings({ selectedTimeRange: item.key }) } - handleChangeSelectedTime = item => - this.setState({ - selectedTime: item.key, - daysCount: item.value, - }) - _cacheBalance = null render() { - const { account, openModal, t, counterValue } = this.props - const { selectedTime, daysCount } = this.state + const { account, openModal, t, counterValue, selectedTimeRange } = this.props + const daysCount = timeRangeDaysByKey[selectedTimeRange] // Don't even throw if we jumped in wrong account route if (!account) { @@ -148,7 +147,7 @@ class AccountPage extends PureComponent { chartId={`account-chart-${account.id}`} counterValue={counterValue} daysCount={daysCount} - selectedTime={selectedTime} + selectedTimeRange={selectedTimeRange} renderHeader={({ totalBalance, sinceBalance, refBalance }) => ( @@ -165,7 +164,7 @@ class AccountPage extends PureComponent { @@ -177,7 +176,7 @@ class AccountPage extends PureComponent { totalBalance={totalBalance} sinceBalance={sinceBalance} refBalance={refBalance} - since={selectedTime} + since={selectedTimeRange} /> { totalBalance={totalBalance} sinceBalance={sinceBalance} refBalance={refBalance} - since={selectedTime} + since={selectedTimeRange} /> diff --git a/src/components/BalanceSummary/index.js b/src/components/BalanceSummary/index.js index 78d47a7c7a..9c04ec94b7 100644 --- a/src/components/BalanceSummary/index.js +++ b/src/components/BalanceSummary/index.js @@ -14,10 +14,10 @@ type Props = { chartColor: string, chartId: string, accounts: Account[], - selectedTime: string, + selectedTimeRange: string, daysCount: number, renderHeader?: ({ - selectedTime: *, + selectedTimeRange: *, totalBalance: number, sinceBalance: number, refBalance: number, @@ -31,7 +31,7 @@ const BalanceSummary = ({ counterValue, daysCount, renderHeader, - selectedTime, + selectedTimeRange, }: Props) => { const account = accounts.length === 1 ? accounts[0] : undefined return ( @@ -43,7 +43,7 @@ const BalanceSummary = ({ {renderHeader ? ( {renderHeader({ - selectedTime, + selectedTimeRange, // FIXME refactor these totalBalance: balanceEnd, sinceBalance: balanceStart, @@ -59,7 +59,7 @@ const BalanceSummary = ({ data={balanceHistory} height={200} currency={counterValue} - tickXScale={selectedTime} + tickXScale={selectedTimeRange} renderTickY={val => formatShort(counterValue.units[0], val)} renderTooltip={ isAvailable && !account diff --git a/src/components/DashboardPage/index.js b/src/components/DashboardPage/index.js index 6894b765e5..4267a42989 100644 --- a/src/components/DashboardPage/index.js +++ b/src/components/DashboardPage/index.js @@ -13,7 +13,13 @@ import type { T } from 'types/common' import { colors } from 'styles/theme' import { accountsSelector } from 'reducers/accounts' -import { counterValueCurrencySelector, localeSelector } from 'reducers/settings' +import { + counterValueCurrencySelector, + localeSelector, + selectedTimeRangeSelector, + timeRangeDaysByKey, +} from 'reducers/settings' +import type { TimeRange } from 'reducers/settings' import { reorderAccounts } from 'actions/accounts' import { saveSettings } from 'actions/settings' @@ -35,6 +41,7 @@ const mapStateToProps = createStructuredSelector({ accounts: accountsSelector, counterValue: counterValueCurrencySelector, locale: localeSelector, + selectedTimeRange: selectedTimeRangeSelector, }) const mapDispatchToProps = { @@ -48,20 +55,11 @@ type Props = { accounts: Account[], push: Function, counterValue: Currency, + selectedTimeRange: TimeRange, + saveSettings: ({ selectedTimeRange: TimeRange }) => *, } -type State = { - selectedTime: string, - daysCount: number, -} - -class DashboardPage extends PureComponent { - state = { - // save to user preference? - selectedTime: 'month', - daysCount: 30, - } - +class DashboardPage extends PureComponent { onAccountClick = account => this.props.push(`/account/${account.id}`) handleGreeting = () => { @@ -77,17 +75,15 @@ class DashboardPage extends PureComponent { return 'app:dashboard.greeting.morning' } - handleChangeSelectedTime = item => - this.setState({ - selectedTime: item.key, - daysCount: item.value, - }) + handleChangeSelectedTime = item => { + this.props.saveSettings({ selectedTimeRange: item.key }) + } _cacheBalance = null render() { - const { accounts, t, counterValue } = this.props - const { selectedTime, daysCount } = this.state + const { accounts, t, counterValue, selectedTimeRange } = this.props + const daysCount = timeRangeDaysByKey[selectedTimeRange] const timeFrame = this.handleGreeting() const totalAccounts = accounts.length @@ -111,7 +107,7 @@ class DashboardPage extends PureComponent { @@ -122,14 +118,14 @@ class DashboardPage extends PureComponent { chartId="dashboard-chart" chartColor={colors.wallet} accounts={accounts} - selectedTime={selectedTime} + selectedTimeRange={selectedTimeRange} daysCount={daysCount} - renderHeader={({ totalBalance, selectedTime, sinceBalance, refBalance }) => ( + renderHeader={({ totalBalance, selectedTimeRange, sinceBalance, refBalance }) => ( diff --git a/src/components/PillsDaysCount.js b/src/components/PillsDaysCount.js index 12b49f8901..5d28d47a08 100644 --- a/src/components/PillsDaysCount.js +++ b/src/components/PillsDaysCount.js @@ -2,33 +2,28 @@ import React, { PureComponent } from 'react' import { translate } from 'react-i18next' - import type { T } from 'types/common' - import Pills from 'components/base/Pills' +import { timeRangeDaysByKey } from 'reducers/settings' +import type { TimeRange } from 'reducers/settings' type Props = { - selectedTime: string, - onChange: ({ key: string, value: *, label: string }) => void, + selected: string, + onChange: ({ key: string, value: *, label: string }) => *, t: T, } -const itemsTimes = [ - { key: 'week', value: 7 }, - { key: 'month', value: 30 }, - { key: 'year', value: 365 }, -] - class PillsDaysCount extends PureComponent { render() { - const { selectedTime, onChange, t } = this.props + const { selected, onChange, t } = this.props return ( ({ - ...item, - label: t(`app:time.${item.key}`), + items={Object.keys(timeRangeDaysByKey).map((key: TimeRange) => ({ + key, + value: timeRangeDaysByKey[key], + label: t(`app:time.${key}`), }))} - activeKey={selectedTime} + activeKey={selected} onChange={onChange} /> ) diff --git a/src/components/base/Chart/refreshDraw.js b/src/components/base/Chart/refreshDraw.js index fa3d2e313c..dfe861bfa1 100644 --- a/src/components/base/Chart/refreshDraw.js +++ b/src/components/base/Chart/refreshDraw.js @@ -24,8 +24,8 @@ const RENDER_TICK_X = { default: 'MMM D', } -function getRenderTickX(selectedTime) { - return t => moment(t).format(RENDER_TICK_X[selectedTime] || RENDER_TICK_X.default) +function getRenderTickX(selectedTimeRange) { + return t => moment(t).format(RENDER_TICK_X[selectedTimeRange] || RENDER_TICK_X.default) } export default function refreshDraw({ ctx, props }: { ctx: CTX, props: Props }) { diff --git a/src/reducers/settings.js b/src/reducers/settings.js index eb6b8ec3dd..cf314c51b9 100644 --- a/src/reducers/settings.js +++ b/src/reducers/settings.js @@ -18,6 +18,14 @@ import type { State } from 'reducers' export const intermediaryCurrency = getCryptoCurrencyById('bitcoin') +export const timeRangeDaysByKey = { + week: 7, + month: 30, + year: 365, +} + +export type TimeRange = $Keys + export type SettingsState = { loaded: boolean, // is the settings loaded from db (it not we don't save them) hasCompletedOnboarding: boolean, @@ -29,6 +37,7 @@ export type SettingsState = { isEnabled: boolean, value: string, }, + selectedTimeRange: TimeRange, marketIndicator: 'eastern' | 'western', currenciesSettings: { [currencyId: string]: CurrencySettings, @@ -67,6 +76,7 @@ const INITIAL_STATE: SettingsState = { isEnabled: false, value: '', }, + selectedTimeRange: 'month', marketIndicator: 'western', currenciesSettings: {}, region, @@ -207,5 +217,6 @@ export const exchangeSettingsForAccountSelector: ESFAS = createSelector( export const marketIndicatorSelector = (state: State) => state.settings.marketIndicator export const sentryLogsBooleanSelector = (state: State) => state.settings.sentryLogs +export const selectedTimeRangeSelector = (state: State) => state.settings.selectedTimeRange export default handleActions(handlers, INITIAL_STATE) From 375398b4a4150ce699dbb80ff5a3d4585929cd64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Renaudeau?= Date: Tue, 19 Jun 2018 10:07:25 +0200 Subject: [PATCH 16/22] Progress on Onboarding Genuine Check --- src/components/GenuineCheckModal/index.js | 61 ++++++++++++++++--- .../Onboarding/steps/GenuineCheck.js | 26 +++++++- src/components/Workflow/index.js | 5 +- src/reducers/onboarding.js | 2 + 4 files changed, 77 insertions(+), 17 deletions(-) diff --git a/src/components/GenuineCheckModal/index.js b/src/components/GenuineCheckModal/index.js index 8b27eaa771..0005afed7c 100644 --- a/src/components/GenuineCheckModal/index.js +++ b/src/components/GenuineCheckModal/index.js @@ -1,6 +1,6 @@ // @flow -import React, { PureComponent } from 'react' +import React, { PureComponent, Fragment } from 'react' import { translate } from 'react-i18next' import type { T } from 'types/common' @@ -11,14 +11,47 @@ import WorkflowDefault from 'components/Workflow/WorkflowDefault' type Props = { t: T, - onGenuineCheck: (isGenuine: boolean) => void, + onGenuineCheckPass: () => void, + onGenuineCheckFailed: () => void, + onGenuineCheckUnavailable: Error => void, } type State = {} +class GenuineCheckStatus extends PureComponent<*> { + componentDidMount() { + this.sideEffect() + } + componentDidUpdate() { + this.sideEffect() + } + sideEffect() { + const { + isGenuine, + error, + onGenuineCheckPass, + onGenuineCheckFailed, + onGenuineCheckUnavailable, + } = this.props + if (isGenuine !== undefined) { + if (isGenuine) { + onGenuineCheckPass() + } else { + onGenuineCheckFailed() + } + } else if (error) { + onGenuineCheckUnavailable(error) + } + } + render() { + return null + } +} + +/* eslint-disable react/no-multi-comp */ class GenuineCheck extends PureComponent { renderBody = ({ onClose }) => { - const { t, onGenuineCheck } = this.props + const { t, onGenuineCheckPass, onGenuineCheckFailed, onGenuineCheckUnavailable } = this.props // TODO: use the real devices list. for now we force choosing only // the current device because we don't handle multi device in MVP @@ -28,14 +61,22 @@ class GenuineCheck extends PureComponent { {t('app:genuinecheck.modal.title')} onGenuineCheck(isGenuine)} renderDefault={(device, deviceInfo, isGenuine, errors) => ( - + + + + )} /> diff --git a/src/components/Onboarding/steps/GenuineCheck.js b/src/components/Onboarding/steps/GenuineCheck.js index 618222a6ba..eb03bd6389 100644 --- a/src/components/Onboarding/steps/GenuineCheck.js +++ b/src/components/Onboarding/steps/GenuineCheck.js @@ -90,10 +90,28 @@ class GenuineCheck extends PureComponent { handleCloseGenuineCheckModal = (cb?: Function) => this.setState(state => ({ ...state, isGenuineCheckModalOpened: false }), () => cb && cb()) - handleGenuineCheck = isGenuine => { + handleGenuineCheckPass = () => { this.handleCloseGenuineCheckModal(() => { this.props.updateGenuineCheck({ - isDeviceGenuine: isGenuine, + isDeviceGenuine: true, + genuineCheckUnavailable: null, + }) + }) + } + handleGenuineCheckFailed = () => { + this.handleCloseGenuineCheckModal(() => { + this.props.updateGenuineCheck({ + isDeviceGenuine: false, + genuineCheckUnavailable: null, + }) + }) + } + + handleGenuineCheckUnavailable = error => { + this.handleCloseGenuineCheckModal(() => { + this.props.updateGenuineCheck({ + isDeviceGenuine: false, + genuineCheckUnavailable: error, }) }) } @@ -219,7 +237,9 @@ class GenuineCheck extends PureComponent { ) diff --git a/src/components/Workflow/index.js b/src/components/Workflow/index.js index da3ad61a45..9c5edc0a23 100644 --- a/src/components/Workflow/index.js +++ b/src/components/Workflow/index.js @@ -34,11 +34,11 @@ type Props = { renderMcuUpdate?: (deviceInfo: DeviceInfo) => Node, renderFinalUpdate?: (deviceInfo: DeviceInfo) => Node, renderDashboard?: (device: Device, deviceInfo: DeviceInfo, isGenuine: boolean) => Node, - onGenuineCheck?: (isGenuine: boolean) => void, renderError?: (dashboardError: ?Error, genuineError: ?Error) => Node, } type State = {} +// In future, move to meri's approach; this code is way too much specific class Workflow extends PureComponent { render() { const { @@ -47,7 +47,6 @@ class Workflow extends PureComponent { renderMcuUpdate, renderError, renderDefault, - onGenuineCheck, } = this.props return ( @@ -74,8 +73,6 @@ class Workflow extends PureComponent { } if (isGenuine && deviceInfo && device && !dashboardError && !genuineError) { - if (onGenuineCheck) onGenuineCheck(isGenuine) - if (renderDashboard) return renderDashboard(device, deviceInfo, isGenuine) } diff --git a/src/reducers/onboarding.js b/src/reducers/onboarding.js index 8fd43f1bb0..10cb09683a 100644 --- a/src/reducers/onboarding.js +++ b/src/reducers/onboarding.js @@ -23,6 +23,7 @@ export type OnboardingState = { recoveryStepPass: boolean, isGenuineFail: boolean, isDeviceGenuine: boolean, + genuineCheckUnavailable: ?Error, }, isLedgerNano: boolean | null, flowType: string, @@ -36,6 +37,7 @@ const state: OnboardingState = { recoveryStepPass: false, isGenuineFail: false, isDeviceGenuine: false, + genuineCheckUnavailable: null, }, isLedgerNano: null, flowType: '', From 7d7917a3558ba155d05566eb21f5adc1fe37b995 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Renaudeau?= Date: Tue, 19 Jun 2018 10:35:14 +0200 Subject: [PATCH 17/22] StickBackToTop now will animate like it used to do --- package.json | 1 + src/components/StickyBackToTop.js | 5 ++++- yarn.lock | 4 ++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 73a0d7c96f..4361abff2e 100644 --- a/package.json +++ b/package.json @@ -96,6 +96,7 @@ "secp256k1": "3.3.1", "semaphore": "^1.1.0", "semver": "^5.5.0", + "smoothscroll-polyfill": "^0.4.3", "source-map": "0.7.3", "source-map-support": "^0.5.4", "styled-components": "^3.3.2", diff --git a/src/components/StickyBackToTop.js b/src/components/StickyBackToTop.js index b42c11e13a..e76b676029 100644 --- a/src/components/StickyBackToTop.js +++ b/src/components/StickyBackToTop.js @@ -5,6 +5,9 @@ import styled from 'styled-components' import Box from 'components/base/Box' import AngleUp from 'icons/AngleUp' import { GrowScrollContext } from './base/GrowScroll' +import smoothscroll from 'smoothscroll-polyfill' + +smoothscroll.polyfill() const Container = styled(Box)` position: fixed; @@ -75,7 +78,7 @@ class StickyBackToTop extends PureComponent { const { scrollContainer } = this.props.getGrowScroll() if (scrollContainer) { // $FlowFixMe seems to be missing in flow - scrollContainer.scrollTo(0, 0) + scrollContainer.scrollTo({ top: 0, behavior: 'smooth' }) } } diff --git a/yarn.lock b/yarn.lock index def68badd3..3c35cb9b5a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12808,6 +12808,10 @@ smart-buffer@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.0.1.tgz#07ea1ca8d4db24eb4cac86537d7d18995221ace3" +smoothscroll-polyfill@^0.4.3: + version "0.4.3" + resolved "https://registry.yarnpkg.com/smoothscroll-polyfill/-/smoothscroll-polyfill-0.4.3.tgz#94e5f2d604efcceb53f23ff0380d7ea7280d4bff" + snapdragon-node@^2.0.1: version "2.1.1" resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" From 3553f401e6fbae0888db42027cd7b606da8f4f56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Renaudeau?= Date: Tue, 19 Jun 2018 10:55:49 +0200 Subject: [PATCH 18/22] fix linter --- src/components/StickyBackToTop.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/StickyBackToTop.js b/src/components/StickyBackToTop.js index e76b676029..7dc77a21de 100644 --- a/src/components/StickyBackToTop.js +++ b/src/components/StickyBackToTop.js @@ -2,10 +2,10 @@ import React, { PureComponent } from 'react' import ReactDOM from 'react-dom' import styled from 'styled-components' +import smoothscroll from 'smoothscroll-polyfill' import Box from 'components/base/Box' import AngleUp from 'icons/AngleUp' import { GrowScrollContext } from './base/GrowScroll' -import smoothscroll from 'smoothscroll-polyfill' smoothscroll.polyfill() From f791f4ef38a5556b85bd603005708e52a10418a4 Mon Sep 17 00:00:00 2001 From: meriadec Date: Tue, 19 Jun 2018 11:43:23 +0200 Subject: [PATCH 19/22] Polishing onboarding genuine check states --- src/components/GenuineCheckModal/index.js | 5 +--- .../Onboarding/steps/GenuineCheck.js | 29 +++++++++++++++++-- src/components/Workflow/EnsureGenuine.js | 7 +++-- src/components/base/FakeLink.js | 7 +++-- static/i18n/en/onboarding.yml | 1 + 5 files changed, 37 insertions(+), 12 deletions(-) diff --git a/src/components/GenuineCheckModal/index.js b/src/components/GenuineCheckModal/index.js index 0005afed7c..8afe0b3b45 100644 --- a/src/components/GenuineCheckModal/index.js +++ b/src/components/GenuineCheckModal/index.js @@ -19,9 +19,6 @@ type Props = { type State = {} class GenuineCheckStatus extends PureComponent<*> { - componentDidMount() { - this.sideEffect() - } componentDidUpdate() { this.sideEffect() } @@ -33,7 +30,7 @@ class GenuineCheckStatus extends PureComponent<*> { onGenuineCheckFailed, onGenuineCheckUnavailable, } = this.props - if (isGenuine !== undefined) { + if (isGenuine !== null) { if (isGenuine) { onGenuineCheckPass() } else { diff --git a/src/components/Onboarding/steps/GenuineCheck.js b/src/components/Onboarding/steps/GenuineCheck.js index eb03bd6389..44bdd1f246 100644 --- a/src/components/Onboarding/steps/GenuineCheck.js +++ b/src/components/Onboarding/steps/GenuineCheck.js @@ -11,6 +11,7 @@ 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' @@ -18,6 +19,7 @@ import GenuineCheckModal from 'components/GenuineCheckModal' import IconLedgerNanoError from 'icons/illustrations/LedgerNanoError' import IconLedgerBlueError from 'icons/illustrations/LedgerBlueError' import IconCheck from 'icons/Check' +import IconCross from 'icons/Cross' import { Title, @@ -88,7 +90,15 @@ class GenuineCheck extends PureComponent { handleOpenGenuineCheckModal = () => this.setState({ isGenuineCheckModalOpened: true }) handleCloseGenuineCheckModal = (cb?: Function) => - this.setState(state => ({ ...state, isGenuineCheckModalOpened: false }), () => cb && cb()) + this.setState( + state => ({ ...state, isGenuineCheckModalOpened: false }), + () => { + // FIXME: meh + if (cb && typeof cb === 'function') { + cb() + } + }, + ) handleGenuineCheckPass = () => { this.handleCloseGenuineCheckModal(() => { @@ -101,6 +111,7 @@ class GenuineCheck extends PureComponent { handleGenuineCheckFailed = () => { this.handleCloseGenuineCheckModal(() => { this.props.updateGenuineCheck({ + isGenuineFail: true, isDeviceGenuine: false, genuineCheckUnavailable: null, }) @@ -211,6 +222,20 @@ class GenuineCheck extends PureComponent { {t('onboarding:genuineCheck.isGenuinePassed')} + ) : genuine.genuineCheckUnavailable ? ( + + + + {t('onboarding:genuineCheck.isGenuineUnavailable')} + + {t('app:common.retry')} + + + ) : (