diff --git a/package.json b/package.json index 3fa406db..67328fc4 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "productName": "Ledger Live", "description": "Ledger Live - Desktop", "repository": "https://github.com/LedgerHQ/ledger-live-desktop", - "version": "0.1.0-alpha.10", + "version": "0.1.0-alpha.11", "author": "Ledger", "license": "MIT", "scripts": { diff --git a/scripts/release.sh b/scripts/release.sh index 5aae7d22..8f919bbb 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -21,5 +21,7 @@ fi # TODO check if local git HEAD is EXACTLY our remote master HEAD export SENTRY_URL=https://db8f5b9b021048d4a401f045371701cb@sentry.io/274561 +rm -rf ./node_modules/.cache +yarn yarn compile build diff --git a/src/bridge/LibcoreBridge.js b/src/bridge/LibcoreBridge.js index 45321d7e..67f71138 100644 --- a/src/bridge/LibcoreBridge.js +++ b/src/bridge/LibcoreBridge.js @@ -48,7 +48,7 @@ const EditAdvancedOptions = ({ onChange, value }: EditProps) => ( const recipientValidLRU = LRU({ max: 100 }) -const isRecipientValid = (currency, recipient): Promise => { +const isRecipientValid = (currency, recipient) => { const key = `${currency.id}_${recipient}` let promise = recipientValidLRU.get(key) if (promise) return promise @@ -172,14 +172,18 @@ const LibcoreBridge: WalletBridge = { isValidTransaction: (a, t) => (t.amount > 0 && t.recipient && true) || false, canBeSpent: (a, t) => - getFees(a, t) - .then(fees => fees !== null) - .catch(() => false), + !t.amount + ? Promise.resolve(true) + : getFees(a, t) + .then(() => true) + .catch(() => false), getTotalSpent: (a, t) => - getFees(a, t) - .then(totalFees => t.amount + (totalFees || 0)) - .catch(() => 0), + !t.amount + ? Promise.resolve(0) + : getFees(a, t) + .then(totalFees => t.amount + (totalFees || 0)) + .catch(() => 0), getMaxAmount: (a, t) => getFees(a, t) diff --git a/src/commands/getIsGenuine.js b/src/commands/getIsGenuine.js index 25f94d13..8b9cfa2e 100644 --- a/src/commands/getIsGenuine.js +++ b/src/commands/getIsGenuine.js @@ -4,10 +4,13 @@ import { createCommand, Command } from 'helpers/ipc' import { fromPromise } from 'rxjs/observable/fromPromise' import getIsGenuine from 'helpers/devices/getIsGenuine' +import { withDevice } from 'helpers/deviceAccess' type Input = * -type Result = boolean +type Result = string -const cmd: Command = createCommand('getIsGenuine', () => fromPromise(getIsGenuine())) +const cmd: Command = createCommand('getIsGenuine', ({ devicePath, targetId }) => + fromPromise(withDevice(devicePath)(transport => getIsGenuine(transport, { targetId }))), +) export default cmd diff --git a/src/components/DashboardPage/EmptyState.js b/src/components/DashboardPage/EmptyState.js index e71476e8..723c785b 100644 --- a/src/components/DashboardPage/EmptyState.js +++ b/src/components/DashboardPage/EmptyState.js @@ -48,6 +48,9 @@ class EmptyState extends PureComponent { {t('app:emptyState.dashboard.title')} {t('app:emptyState.dashboard.desc')} + - diff --git a/src/components/DeviceConfirm/index.js b/src/components/DeviceConfirm/index.js index a9c88790..6e9a4e8b 100644 --- a/src/components/DeviceConfirm/index.js +++ b/src/components/DeviceConfirm/index.js @@ -41,7 +41,7 @@ const WrapperIcon = styled(Box)` } ` -const Check = ({ error }: { error: * }) => ( +const Check = ({ error }: { error?: boolean }) => ( {error ? : } @@ -74,7 +74,7 @@ const PushButton = styled(Box)` ` type Props = { - error: *, + error?: boolean, } const SVG = ( diff --git a/src/components/IsUnlocked.js b/src/components/IsUnlocked.js index 136b1066..c0cb10e3 100644 --- a/src/components/IsUnlocked.js +++ b/src/components/IsUnlocked.js @@ -4,22 +4,26 @@ import bcrypt from 'bcryptjs' import React, { Component } from 'react' import { connect } from 'react-redux' import { compose } from 'redux' +import { remote } from 'electron' import styled from 'styled-components' import { translate } from 'react-i18next' import type { SettingsState as Settings } from 'reducers/settings' import type { T } from 'types/common' import IconLockScreen from 'icons/LockScreen' - +import IconTriangleWarning from 'icons/TriangleWarning' import get from 'lodash/get' import { setEncryptionKey } from 'helpers/db' +import hardReset from 'helpers/hardReset' import { fetchAccounts } from 'actions/accounts' import { isLocked, unlock } from 'reducers/application' import Box from 'components/base/Box' import InputPassword from 'components/base/InputPassword' +import Button from './base/Button/index' +import ConfirmModal from './base/Modal/ConfirmModal' type InputValue = { password: string, @@ -36,6 +40,8 @@ type Props = { type State = { inputValue: InputValue, incorrectPassword: boolean, + isHardResetting: boolean, + isHardResetModalOpened: boolean, } const mapStateToProps = state => ({ @@ -53,6 +59,8 @@ const defaultState = { password: '', }, incorrectPassword: false, + isHardResetting: false, + isHardResetModalOpened: false, } export const PageTitle = styled(Box).attrs({ @@ -96,6 +104,7 @@ class IsUnlocked extends Component { ...prev.inputValue, [key]: value, }, + incorrectPassword: false, })) handleSubmit = async (e: SyntheticEvent) => { @@ -117,8 +126,25 @@ class IsUnlocked extends Component { } } + handleOpenHardResetModal = () => this.setState({ isHardResetModalOpened: true }) + handleCloseHardResetModal = () => this.setState({ isHardResetModalOpened: false }) + + handleHardReset = async () => { + this.setState({ isHardResetting: true }) + try { + await hardReset() + remote.getCurrentWindow().webContents.reloadIgnoringCache() + } catch (err) { + this.setState({ isHardResetting: false }) + } + } + hardResetIconRender = () => ( + + + + ) render() { - const { inputValue, incorrectPassword } = this.state + const { inputValue, incorrectPassword, isHardResetting, isHardResetModalOpened } = this.state const { isLocked, t } = this.props if (isLocked) { @@ -143,8 +169,22 @@ class IsUnlocked extends Component { error={incorrectPassword && t('app:password.errorMessageIncorrectPassword')} /> + + ) } @@ -164,3 +204,12 @@ export default compose( ), translate(), )(IsUnlocked) + +const IconWrapperCircle = styled(Box).attrs({})` + width: 50px; + height: 50px; + border-radius: 50%; + background: #ea2e4919; + text-align: -webkit-center; + justify-content: center; +` diff --git a/src/components/ManagerPage/EnsureDashboard.js b/src/components/ManagerPage/EnsureDashboard.js index 96c9e4ca..5d29f2e3 100644 --- a/src/components/ManagerPage/EnsureDashboard.js +++ b/src/components/ManagerPage/EnsureDashboard.js @@ -42,6 +42,7 @@ class EnsureDashboard extends PureComponent { componentDidMount() { this.checkForDashboard() + this._interval = setInterval(this.checkForDashboard, 1000) } componentDidUpdate() { @@ -50,12 +51,14 @@ class EnsureDashboard extends PureComponent { componentWillUnmount() { this._unmounting = true + clearInterval(this._interval) } _checking = false _unmounting = false + _interval: * - async checkForDashboard() { + checkForDashboard = async () => { const { device } = this.props if (device && !this._checking) { this._checking = true diff --git a/src/components/ManagerPage/EnsureGenuine.js b/src/components/ManagerPage/EnsureGenuine.js index 2de56aeb..1391e163 100644 --- a/src/components/ManagerPage/EnsureGenuine.js +++ b/src/components/ManagerPage/EnsureGenuine.js @@ -12,8 +12,14 @@ type Error = { stack: string, } +type DeviceInfos = { + targetId: number | string, + version: string, +} + type Props = { device: ?Device, + infos: ?DeviceInfos, children: (isGenuine: ?boolean, error: ?Error) => Node, } @@ -49,12 +55,15 @@ class EnsureGenuine extends PureComponent { _unmounting = false async checkIsGenuine() { - const { device } = this.props - if (device && !this._checking) { + const { device, infos } = this.props + if (device && infos && !this._checking) { this._checking = true try { - const isGenuine = await getIsGenuine.send().toPromise() - if (!this.state.genuine || this.state.error) { + const res = await getIsGenuine + .send({ devicePath: device.path, targetId: infos.targetId }) + .toPromise() + const isGenuine = res === '0000' + if ((!this.state.genuine || this.state.error) && isGenuine) { !this._unmounting && this.setState({ genuine: isGenuine, error: null }) } } catch (err) { diff --git a/src/components/ManagerPage/Workflow.js b/src/components/ManagerPage/Workflow.js index 1b42771f..e52f701a 100644 --- a/src/components/ManagerPage/Workflow.js +++ b/src/components/ManagerPage/Workflow.js @@ -52,7 +52,7 @@ class Workflow extends PureComponent { {(device: Device) => ( {(deviceInfo: ?DeviceInfo, dashboardError: ?Error) => ( - + {(isGenuine: ?boolean, genuineError: ?Error) => { if (dashboardError || genuineError) { return renderError diff --git a/src/components/Onboarding/steps/Analytics.js b/src/components/Onboarding/steps/Analytics.js index 40969dc8..d92720bc 100644 --- a/src/components/Onboarding/steps/Analytics.js +++ b/src/components/Onboarding/steps/Analytics.js @@ -19,7 +19,7 @@ type State = { } const INITIAL_STATE = { - analyticsToggle: true, + analyticsToggle: false, sentryLogsToggle: true, } diff --git a/src/components/Onboarding/steps/GenuineCheck.js b/src/components/Onboarding/steps/GenuineCheck.js index bff77501..c4f97654 100644 --- a/src/components/Onboarding/steps/GenuineCheck.js +++ b/src/components/Onboarding/steps/GenuineCheck.js @@ -129,7 +129,12 @@ class GenuineCheck extends PureComponent { {t('onboarding:genuineCheck.title')} - {t('onboarding:genuineCheck.desc')} + {onboarding.isLedgerNano ? ( + {t('onboarding:genuineCheck.descNano')} + ) : ( + {t('onboarding:genuineCheck.descBlue')} + )} + diff --git a/src/components/SettingsPage/sections/Profile.js b/src/components/SettingsPage/sections/Profile.js index d3557f09..703b1e99 100644 --- a/src/components/SettingsPage/sections/Profile.js +++ b/src/components/SettingsPage/sections/Profile.js @@ -2,6 +2,7 @@ import React, { PureComponent } from 'react' import { connect } from 'react-redux' +import styled from 'styled-components' import { remote } from 'electron' import bcrypt from 'bcryptjs' @@ -19,6 +20,7 @@ import CheckBox from 'components/base/CheckBox' import Box from 'components/base/Box' import Button from 'components/base/Button' import { ConfirmModal } from 'components/base/Modal' +import IconTriangleWarning from 'icons/TriangleWarning' import IconUser from 'icons/User' import PasswordModal from '../PasswordModal' import DisablePasswordModal from '../DisablePasswordModal' @@ -125,6 +127,12 @@ class TabProfile extends PureComponent { }) } + hardResetIconRender = () => ( + + + + ) + render() { const { t, settings, saveSettings } = this.props const { @@ -223,8 +231,8 @@ class TabProfile extends PureComponent { onReject={this.handleCloseHardResetModal} onConfirm={this.handleHardReset} title={t('app:settings.hardResetModal.title')} - subTitle={t('app:settings.hardResetModal.subTitle')} desc={t('app:settings.hardResetModal.desc')} + renderIcon={this.hardResetIconRender} /> { render() { const { isPending, isError, t } = this.props const { hasClicked, isFirstSync } = this.state - const isDisabled = isFirstSync || hasClicked || isError + const isDisabled = isError || (isPending && (isFirstSync || hasClicked)) const isRotating = isPending && (hasClicked || isFirstSync) return ( diff --git a/src/components/base/InputCurrency/index.js b/src/components/base/InputCurrency/index.js index 205d9067..c32377fc 100644 --- a/src/components/base/InputCurrency/index.js +++ b/src/components/base/InputCurrency/index.js @@ -6,7 +6,6 @@ import styled from 'styled-components' import { formatCurrencyUnit } from '@ledgerhq/live-common/lib/helpers/currencies' import noop from 'lodash/noop' -import isNaN from 'lodash/isNaN' import Box from 'components/base/Box' import Input from 'components/base/Input' @@ -14,8 +13,41 @@ import Select from 'components/base/LegacySelect' import type { Unit } from '@ledgerhq/live-common/lib/types' -function parseValue(value) { - return value.toString().replace(/,/g, '.') +// TODO move this back to live common +const numbers = '0123456789' +const sanitizeValueString = ( + unit: Unit, + valueString: string, +): { + display: string, + value: string, +} => { + let display = '' + let value = '' + let decimals = -1 + for (let i = 0; i < valueString.length; i++) { + const c = valueString[i] + if (numbers.indexOf(c) !== -1) { + if (decimals >= 0) { + decimals++ + if (decimals > unit.magnitude) break + value += c + display += c + } else if (value !== '0') { + value += c + display += c + } + } else if (decimals === -1 && (c === ',' || c === '.')) { + if (i === 0) display = '0' + decimals = 0 + display += '.' + } + } + for (let i = Math.max(0, decimals); i < unit.magnitude; ++i) { + value += '0' + } + if (!value) value = '0' + return { display, value } } function format(unit: Unit, value: number, { isFocused, showAllDigits, subMagnitude }) { @@ -85,9 +117,10 @@ class InputCurrency extends PureComponent { componentWillReceiveProps(nextProps: Props) { const { value, showAllDigits, unit } = this.props const needsToBeReformatted = - value !== nextProps.value || - showAllDigits !== nextProps.showAllDigits || - unit !== nextProps.unit + !this.state.isFocused && + (value !== nextProps.value || + showAllDigits !== nextProps.showAllDigits || + unit !== nextProps.unit) if (needsToBeReformatted) { const { isFocused } = this.state this.setState({ @@ -104,28 +137,13 @@ class InputCurrency extends PureComponent { } handleChange = (v: string) => { - v = parseValue(v) - - // allow to type directly `.` in input to have `0.` - if (v.startsWith('.')) { - v = `0${v}` - } - - // forbid multiple 0 at start - if (v === '' || v.startsWith('00')) { - const { onChange, unit } = this.props - onChange(0, unit) - this.setState({ displayValue: '' }) - return - } - - // Check if value is valid Number - if (isNaN(Number(v))) { - return + const { onChange, unit, value } = this.props + const r = sanitizeValueString(unit, v) + const satoshiValue = parseInt(r.value, 10) + if (value !== satoshiValue) { + onChange(satoshiValue, unit) } - - this.emitOnChange(v) - this.setState({ displayValue: v || '' }) + this.setState({ displayValue: r.display }) } handleBlur = () => { @@ -149,16 +167,6 @@ class InputCurrency extends PureComponent { }) } - emitOnChange = (v: string) => { - const { onChange, unit } = this.props - const { displayValue } = this.state - - if (displayValue.toString() !== v.toString()) { - const satoshiValue = Number(v) * 10 ** unit.magnitude - onChange(satoshiValue, unit) - } - } - renderItem = item => item.code renderSelected = item => {item.code} diff --git a/src/components/base/Modal/ConfirmModal.js b/src/components/base/Modal/ConfirmModal.js index 750f9b3f..c4c29f7e 100644 --- a/src/components/base/Modal/ConfirmModal.js +++ b/src/components/base/Modal/ConfirmModal.js @@ -14,8 +14,9 @@ type Props = { isOpened: boolean, isDanger: boolean, title: string, - subTitle: string, + subTitle?: string, desc: string, + renderIcon?: Function, confirmText?: string, cancelText?: string, onReject: Function, @@ -37,6 +38,7 @@ class ConfirmModal extends PureComponent { onReject, onConfirm, isLoading, + renderIcon, t, ...props } = this.props @@ -57,6 +59,11 @@ class ConfirmModal extends PureComponent { {subTitle} )} + {renderIcon && ( + + {renderIcon()} + + )} {desc} diff --git a/src/components/modals/AccountSettingRenderBody.js b/src/components/modals/AccountSettingRenderBody.js index 60968fd2..95bb68fa 100644 --- a/src/components/modals/AccountSettingRenderBody.js +++ b/src/components/modals/AccountSettingRenderBody.js @@ -151,6 +151,7 @@ class HelperComp extends PureComponent { } onFocus={e => this.handleFocus(e, 'accountName')} diff --git a/src/components/modals/AddAccounts/steps/03-step-import.js b/src/components/modals/AddAccounts/steps/03-step-import.js index 779d8242..4573e6a8 100644 --- a/src/components/modals/AddAccounts/steps/03-step-import.js +++ b/src/components/modals/AddAccounts/steps/03-step-import.js @@ -160,7 +160,9 @@ class StepImport extends PureComponent { count: importableAccounts.length, }) - const importableAccountsEmpty = t('app:addAccounts.noAccountToImport', { currencyName: currency ? ` ${currency.name}}` : ''}) + const importableAccountsEmpty = t('app:addAccounts.noAccountToImport', { + currencyName: currency ? ` ${currency.name}}` : '', + }) return ( diff --git a/src/components/modals/Receive/index.js b/src/components/modals/Receive/index.js index 836e2602..01d8c5e3 100644 --- a/src/components/modals/Receive/index.js +++ b/src/components/modals/Receive/index.js @@ -198,7 +198,7 @@ class ReceiveModal extends PureComponent { }) } else { this.setState({ - account: accounts[0] + account: accounts[0], }) } } diff --git a/src/components/modals/Send/03-step-verification.js b/src/components/modals/Send/03-step-verification.js index a6df0e6d..1148b617 100644 --- a/src/components/modals/Send/03-step-verification.js +++ b/src/components/modals/Send/03-step-verification.js @@ -27,14 +27,13 @@ const Info = styled(Box).attrs({ ` type Props = { - hasError: boolean, t: T, } -export default ({ t, hasError }: Props) => ( +export default ({ t }: Props) => ( {multiline(t('app:send.steps.verification.warning'))} {t('app:send.steps.verification.body')} - + ) diff --git a/src/components/modals/Send/index.js b/src/components/modals/Send/index.js index 62fdf864..57106659 100644 --- a/src/components/modals/Send/index.js +++ b/src/components/modals/Send/index.js @@ -15,6 +15,7 @@ import { getBridgeForCurrency } from 'bridge' import { accountsSelector } from 'reducers/accounts' import { updateAccountWithUpdater } from 'actions/accounts' +import createCustomErrorClass from 'helpers/createCustomErrorClass' import { MODAL_SEND } from 'config/constants' import Modal, { ModalBody, ModalContent, ModalTitle } from 'components/base/Modal' @@ -32,6 +33,8 @@ import StepAmount from './01-step-amount' import StepVerification from './03-step-verification' import StepConfirmation from './04-step-confirmation' +export const UserRefusedOnDevice = createCustomErrorClass('UserRefusedOnDevice') + type Props = { updateAccountWithUpdater: (string, (Account) => Account) => void, accounts: Account[], @@ -226,14 +229,11 @@ class SendModal extends Component> { }) } - onOperationError = (error: Error) => { - // $FlowFixMe - if (error.statusCode === 0x6985) { - // User denied on device - this.setState({ error }) - } else { - this.setState({ error, stepIndex: 3 }) - } + onOperationError = (error: *) => { + this.setState({ + error: error.statusCode === 0x6985 ? new UserRefusedOnDevice() : error, + stepIndex: 3, + }) } onChangeAccount = account => { diff --git a/src/config/constants.js b/src/config/constants.js index 4cbd1cf2..343b4ed5 100644 --- a/src/config/constants.js +++ b/src/config/constants.js @@ -9,7 +9,7 @@ const intFromEnv = (key: string, def: number) => { export const GET_CALLS_TIMEOUT = intFromEnv('GET_CALLS_TIMEOUT', 30 * 1000) export const GET_CALLS_RETRY = intFromEnv('GET_CALLS_RETRY', 2) -export const SYNC_MAX_CONCURRENT = intFromEnv('LEDGER_SYNC_MAX_CONCURRENT', 2) +export const SYNC_MAX_CONCURRENT = intFromEnv('LEDGER_SYNC_MAX_CONCURRENT', 6) export const SYNC_BOOT_DELAY = 2 * 1000 export const SYNC_ALL_INTERVAL = 60 * 1000 diff --git a/src/helpers/apps/installApp.js b/src/helpers/apps/installApp.js index 465dc18f..2cdd04a3 100644 --- a/src/helpers/apps/installApp.js +++ b/src/helpers/apps/installApp.js @@ -12,5 +12,5 @@ export default async function installApp( transport: Transport<*>, { appParams }: { appParams: LedgerScriptParams }, ): Promise { - return createSocketDialog(transport, '/update/install', appParams) + return createSocketDialog(transport, '/install', appParams) } diff --git a/src/helpers/apps/uninstallApp.js b/src/helpers/apps/uninstallApp.js index af3c23b2..c9f4fc44 100644 --- a/src/helpers/apps/uninstallApp.js +++ b/src/helpers/apps/uninstallApp.js @@ -17,5 +17,5 @@ export default async function uninstallApp( firmware: appParams.delete, firmwareKey: appParams.deleteKey, } - return createSocketDialog(transport, '/update/install', params) + return createSocketDialog(transport, '/install', params) } diff --git a/src/helpers/common.js b/src/helpers/common.js index 52ebbe06..7d96e48b 100644 --- a/src/helpers/common.js +++ b/src/helpers/common.js @@ -5,7 +5,7 @@ import Websocket from 'ws' import qs from 'qs' import type Transport from '@ledgerhq/hw-transport' -import { BASE_SOCKET_URL, APDUS } from './constants' +import { BASE_SOCKET_URL, APDUS, MANAGER_API_URL } from './constants' type WebsocketType = { send: (string, any) => void, @@ -24,34 +24,11 @@ export type LedgerScriptParams = { firmwareKey?: string, delete?: string, deleteKey?: string, + targetId?: string | number, } type FirmwareUpdateType = 'osu' | 'final' -// /** -// * Install an app on the device -// */ -// export async function installApp( -// transport: Transport<*>, -// { appParams }: { appParams: LedgerScriptParams }, -// ): Promise { -// return createSocketDialog(transport, '/update/install', appParams) -// } - -/** - * Uninstall an app on the device - */ -export async function uninstallApp( - transport: Transport<*>, - { appParams }: { appParams: LedgerScriptParams }, -): Promise { - return createSocketDialog(transport, '/update/install', { - ...appParams, - firmware: appParams.delete, - firmwareKey: appParams.deleteKey, - }) -} - export async function getMemInfos(transport: Transport<*>): Promise { const { targetId } = await getFirmwareInfo(transport) // Dont ask me about this `perso_11`: I don't know. But we need it. @@ -119,11 +96,14 @@ export async function createSocketDialog( transport: Transport<*>, endpoint: string, params: LedgerScriptParams, + managerUrl: boolean = false, ) { return new Promise(async (resolve, reject) => { try { let lastData - const url = `${BASE_SOCKET_URL}${endpoint}?${qs.stringify(params)}` + const url = `${managerUrl ? MANAGER_API_URL : BASE_SOCKET_URL}${endpoint}?${qs.stringify( + params, + )}` log('WS CONNECTING', url) const ws: WebsocketType = new Websocket(url) @@ -142,6 +122,8 @@ export async function createSocketDialog( success: msg => { if (msg.data) { lastData = msg.data + } else if (msg.result) { + lastData = msg.result } }, error: msg => { diff --git a/src/helpers/constants.js b/src/helpers/constants.js index becafa8f..254ede23 100644 --- a/src/helpers/constants.js +++ b/src/helpers/constants.js @@ -1,10 +1,8 @@ // Socket endpoint -export const BASE_SOCKET_URL = 'ws://api.ledgerwallet.com' -export const BASE_SOCKET_URL_TEMP = 'ws://manager.ledger.fr:3500' +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' -// If you want to test locally with https://github.com/LedgerHQ/ledger-update-python-api -// export const BASE_SOCKET_URL = 'ws://localhost:3001/update' // List of APDUS export const APDUS = { diff --git a/src/helpers/createCustomErrorClass.js b/src/helpers/createCustomErrorClass.js index 78738ef5..698dc61e 100644 --- a/src/helpers/createCustomErrorClass.js +++ b/src/helpers/createCustomErrorClass.js @@ -1,6 +1,6 @@ // @flow -export default (name: string) => { +export default (name: string): Class => { const C = function CustomError(message?: string, fields?: Object) { this.name = name this.message = message || name @@ -9,5 +9,6 @@ export default (name: string) => { } // $FlowFixMe C.prototype = new Error() + // $FlowFixMe we can't easily type a subset of Error for now... return C } diff --git a/src/helpers/deviceAccess.js b/src/helpers/deviceAccess.js index f2210b64..7d857f4a 100644 --- a/src/helpers/deviceAccess.js +++ b/src/helpers/deviceAccess.js @@ -19,7 +19,8 @@ export const withDevice: WithDevice = devicePath => { return job => takeSemaphorePromise(sem, async () => { const t = await retry(() => TransportNodeHid.open(devicePath), { maxRetry: 1 }) - if (process.env.DEBUG_DEVICE) t.setDebugMode(true) + + if (process.env.DEBUG_DEVICE > 0) t.setDebugMode(true) try { const res = await job(t) // $FlowFixMe diff --git a/src/helpers/devices/getIsGenuine.js b/src/helpers/devices/getIsGenuine.js index 0c546e26..13bfdffc 100644 --- a/src/helpers/devices/getIsGenuine.js +++ b/src/helpers/devices/getIsGenuine.js @@ -1,6 +1,11 @@ // @flow +import type Transport from '@ledgerhq/hw-transport' +import { createSocketDialog } from 'helpers/common' -// import type Transport from '@ledgerhq/hw-transport' - -export default async (/* transport: Transport<*> */) => - new Promise(resolve => setTimeout(() => resolve(true), 1000)) +export default async ( + transport: Transport<*>, + { targetId }: { targetId: string | number }, +): Promise<*> => + process.env.SKIP_GENUINE > 0 + ? new Promise(resolve => setTimeout(() => resolve('0000'), 1000)) + : createSocketDialog(transport, '/genuine', { targetId }, true) diff --git a/src/helpers/firmware/installFinalFirmware.js b/src/helpers/firmware/installFinalFirmware.js index 3c46e27d..43a57435 100644 --- a/src/helpers/firmware/installFinalFirmware.js +++ b/src/helpers/firmware/installFinalFirmware.js @@ -12,7 +12,7 @@ const buildOsuParams = buildParamsFromFirmware('final') export default async (transport: Transport<*>, firmware: Input): Result => { try { const osuData = buildOsuParams(firmware) - await createSocketDialog(transport, '/update/install', osuData) + await createSocketDialog(transport, '/install', osuData) return { success: true } } catch (err) { const error = Error(err.message) diff --git a/src/helpers/firmware/installOsuFirmware.js b/src/helpers/firmware/installOsuFirmware.js index 5a53fdc0..c213cb58 100644 --- a/src/helpers/firmware/installOsuFirmware.js +++ b/src/helpers/firmware/installOsuFirmware.js @@ -13,7 +13,7 @@ const buildOsuParams = buildParamsFromFirmware('osu') export default async (transport: Transport<*>, firmware: Input): Result => { try { const osuData = buildOsuParams(firmware) - await createSocketDialog(transport, '/update/install', osuData) + await createSocketDialog(transport, '/install', osuData) return { success: true } } catch (err) { const error = Error(err.message) diff --git a/src/helpers/libcore.js b/src/helpers/libcore.js index 0cc0232d..5607fda8 100644 --- a/src/helpers/libcore.js +++ b/src/helpers/libcore.js @@ -312,7 +312,7 @@ function buildOperationRaw({ const bitcoinLikeTransaction = bitcoinLikeOperation.getTransaction() const hash = bitcoinLikeTransaction.getHash() const operationType = op.getOperationType() - const value = op.getAmount().toLong() + let value = op.getAmount().toLong() const fee = op.getFees().toLong() const OperationTypeMap: { [_: $Keys]: OperationType } = { @@ -323,6 +323,10 @@ function buildOperationRaw({ // if transaction is a send, amount becomes negative const type = OperationTypeMap[operationType] + if (type === 'OUT') { + value += fee + } + return { id, hash, diff --git a/src/icons/TriangleWarning.js b/src/icons/TriangleWarning.js new file mode 100644 index 00000000..569fff9d --- /dev/null +++ b/src/icons/TriangleWarning.js @@ -0,0 +1,16 @@ +// @flow + +import React from 'react' + +const path = ( + +) + +export default ({ height, width, ...p }: { height: number, width: number }) => ( + + {path} + +) diff --git a/src/main/bridge.js b/src/main/bridge.js index bd118629..2b71fcd1 100644 --- a/src/main/bridge.js +++ b/src/main/bridge.js @@ -27,12 +27,17 @@ sentry(() => sentryEnabled, userId) const killInternalProcess = () => { if (internalProcess) { logger.log('killing internal process...') + internalProcess.removeListener('exit', handleExit) internalProcess.kill('SIGINT') internalProcess = null } } const forkBundlePath = path.resolve(__dirname, `${__DEV__ ? '../../' : './'}dist/internals`) +const handleExit = code => { + logger.warn(`Internal process ended with code ${code}`) + internalProcess = null +} const bootInternalProcess = () => { logger.log('booting internal process...') @@ -45,10 +50,7 @@ const bootInternalProcess = () => { }, }) internalProcess.on('message', handleGlobalInternalMessage) - internalProcess.on('exit', code => { - logger.warn(`Internal process ended with code ${code}`) - internalProcess = null - }) + internalProcess.on('exit', handleExit) } process.on('exit', () => { diff --git a/src/reducers/settings.js b/src/reducers/settings.js index 2c28e884..eb6b8ec3 100644 --- a/src/reducers/settings.js +++ b/src/reducers/settings.js @@ -72,7 +72,7 @@ const INITIAL_STATE: SettingsState = { region, developerMode: !!process.env.__DEV__, loaded: false, - shareAnalytics: true, + shareAnalytics: false, sentryLogs: true, lastUsedVersion: __APP_VERSION__, } diff --git a/static/i18n/en/app.yml b/static/i18n/en/app.yml index 92010d51..7af86358 100644 --- a/static/i18n/en/app.yml +++ b/static/i18n/en/app.yml @@ -31,6 +31,7 @@ common: subTitle: Your application is locked description: Please enter your password to continue inputPlaceholder: Type your password + lostPassword: I lost my password sync: syncing: Syncing... upToDate: Up to date @@ -118,7 +119,7 @@ exchange: coinmama: 'Coinmama is a financial service that makes it fast, safe and fun to buy digital currency, anywhere in the world.' genuinecheck: modal: - title: Genuine check, bro + title: Genuine check addAccounts: title: Add accounts breadcrumb: @@ -309,9 +310,8 @@ settings: terms: Terms and Privacy policy termsDesc: Lorem ipsum dolor sit amet hardResetModal: - title: Hard reset - subTitle: Are you sure houston? - desc: Lorem ipsum dolor sit amet + title: Reset Ledger Live + desc: Resetting will erase all Ledger Live data stored on your computer, including your profile, accounts, transaction history and application settings. The keys to access your crypto assets in the blockchain remain secure on your Ledger device. softResetModal: title: Clean application cache subTitle: Are you sure houston? diff --git a/static/i18n/en/errors.yml b/static/i18n/en/errors.yml index 34d4f713..7b6596f8 100644 --- a/static/i18n/en/errors.yml +++ b/static/i18n/en/errors.yml @@ -11,3 +11,4 @@ LedgerAPINotAvailable: 'Ledger API is not available for currency {{currencyName} LedgerAPIError: 'A problem occurred with Ledger API. Please try again later. (HTTP {{status}})' NetworkDown: 'Your internet connection seems down. Please try again later.' NoAddressesFound: 'No accounts found' +UserRefusedOnDevice: Transaction have been aborted diff --git a/static/i18n/en/onboarding.yml b/static/i18n/en/onboarding.yml index 2fa67577..d09f78e2 100644 --- a/static/i18n/en/onboarding.yml +++ b/static/i18n/en/onboarding.yml @@ -74,7 +74,8 @@ writeSeed: note4: Never use a device supplied with a recovery phrase and/or a PIN code. genuineCheck: title: Final security check - desc: Your Ledger Nano S should now display Your device is now ready. Before getting started, please confirm that + descNano: Your Ledger Nano S should now display Your device is now ready. Before getting started, please confirm that + descBlue: Your Ledger Blue should now display Your device is now ready. Before getting started, please confirm that steps: step1: title: Did you choose your PIN code by yourself? diff --git a/static/i18n/fr/app.yml b/static/i18n/fr/app.yml index 0dac093f..e0739e35 100644 --- a/static/i18n/fr/app.yml +++ b/static/i18n/fr/app.yml @@ -32,6 +32,7 @@ common: subTitle: Your application is locked description: Please enter your password to continue inputPlaceholder: Type your password + lostPassword: I lost my password sync: syncing: Syncing... upToDate: Up to date @@ -119,7 +120,7 @@ exchange: coinmama: 'Coinmama is a financial service that makes it fast, safe and fun to buy digital currency, anywhere in the world.' genuinecheck: modal: - title: Genuine check, bro + title: Genuine check addAccounts: title: Add accounts breadcrumb: @@ -307,9 +308,8 @@ settings: terms: Terms and Privacy policy termsDesc: Lorem ipsum dolor sit amet hardResetModal: - title: Hard reset - subTitle: Are you sure houston? - desc: Lorem ipsum dolor sit amet + title: Reset Ledger Live + desc: Resetting will erase all Ledger Live data stored on your computer, including your profile, accounts, transaction history and application settings. The keys to access your crypto assets in the blockchain remain secure on your Ledger device. softResetModal: title: Clean application cache subTitle: Are you sure houston? diff --git a/static/i18n/fr/errors.yml b/static/i18n/fr/errors.yml index 731ccdc7..3a00f039 100644 --- a/static/i18n/fr/errors.yml +++ b/static/i18n/fr/errors.yml @@ -16,3 +16,4 @@ LedgerAPINotAvailable: 'Ledger API is not available for currency {{currencyName} LedgerAPIError: 'A problem occurred with Ledger API. Please try again later. (HTTP {{status}})' NetworkDown: 'Your internet connection seems down. Please try again later.' NoAddressesFound: 'No accounts found' +UserRefusedOnDevice: Transaction have been aborted diff --git a/static/i18n/fr/onboarding.yml b/static/i18n/fr/onboarding.yml index edf7b1a5..782ea6db 100644 --- a/static/i18n/fr/onboarding.yml +++ b/static/i18n/fr/onboarding.yml @@ -75,7 +75,8 @@ writeSeed: note4: Never use a device supplied with a recovery phrase and/or a PIN code. genuineCheck: title: Final security check - desc: Your Ledger Nano S should now display Your device is now ready. Before getting started, please confirm that + descNano: Your Ledger Nano S should now display Your device is now ready. Before getting started, please confirm that + descBlue: Your Ledger Blue should now display Your device is now ready. Before getting started, please confirm that steps: step1: title: Did you choose your PIN code by yourself?