Browse Source

Merge branch 'master' into pixel-push

master
Thibaut Boustany 7 years ago
parent
commit
73cdcd2405
No known key found for this signature in database GPG Key ID: 32475B11A2B13EEC
  1. 2
      package.json
  2. 2
      scripts/release.sh
  3. 12
      src/bridge/LibcoreBridge.js
  4. 7
      src/commands/getIsGenuine.js
  5. 6
      src/components/DashboardPage/EmptyState.js
  6. 4
      src/components/DeviceConfirm/index.js
  7. 53
      src/components/IsUnlocked.js
  8. 5
      src/components/ManagerPage/EnsureDashboard.js
  9. 17
      src/components/ManagerPage/EnsureGenuine.js
  10. 2
      src/components/ManagerPage/Workflow.js
  11. 2
      src/components/Onboarding/steps/Analytics.js
  12. 7
      src/components/Onboarding/steps/GenuineCheck.js
  13. 20
      src/components/SettingsPage/sections/Profile.js
  14. 2
      src/components/TopBar/ActivityIndicator.js
  15. 80
      src/components/base/InputCurrency/index.js
  16. 9
      src/components/base/Modal/ConfirmModal.js
  17. 1
      src/components/modals/AccountSettingRenderBody.js
  18. 4
      src/components/modals/AddAccounts/steps/03-step-import.js
  19. 2
      src/components/modals/Receive/index.js
  20. 5
      src/components/modals/Send/03-step-verification.js
  21. 16
      src/components/modals/Send/index.js
  22. 2
      src/config/constants.js
  23. 2
      src/helpers/apps/installApp.js
  24. 2
      src/helpers/apps/uninstallApp.js
  25. 34
      src/helpers/common.js
  26. 6
      src/helpers/constants.js
  27. 3
      src/helpers/createCustomErrorClass.js
  28. 3
      src/helpers/deviceAccess.js
  29. 13
      src/helpers/devices/getIsGenuine.js
  30. 2
      src/helpers/firmware/installFinalFirmware.js
  31. 2
      src/helpers/firmware/installOsuFirmware.js
  32. 6
      src/helpers/libcore.js
  33. 16
      src/icons/TriangleWarning.js
  34. 10
      src/main/bridge.js
  35. 2
      src/reducers/settings.js
  36. 8
      static/i18n/en/app.yml
  37. 1
      static/i18n/en/errors.yml
  38. 3
      static/i18n/en/onboarding.yml
  39. 8
      static/i18n/fr/app.yml
  40. 1
      static/i18n/fr/errors.yml
  41. 3
      static/i18n/fr/onboarding.yml

2
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": {

2
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

12
src/bridge/LibcoreBridge.js

@ -48,7 +48,7 @@ const EditAdvancedOptions = ({ onChange, value }: EditProps<Transaction>) => (
const recipientValidLRU = LRU({ max: 100 })
const isRecipientValid = (currency, recipient): Promise<boolean> => {
const isRecipientValid = (currency, recipient) => {
const key = `${currency.id}_${recipient}`
let promise = recipientValidLRU.get(key)
if (promise) return promise
@ -172,12 +172,16 @@ const LibcoreBridge: WalletBridge<Transaction> = {
isValidTransaction: (a, t) => (t.amount > 0 && t.recipient && true) || false,
canBeSpent: (a, t) =>
getFees(a, t)
.then(fees => fees !== null)
!t.amount
? Promise.resolve(true)
: getFees(a, t)
.then(() => true)
.catch(() => false),
getTotalSpent: (a, t) =>
getFees(a, t)
!t.amount
? Promise.resolve(0)
: getFees(a, t)
.then(totalFees => t.amount + (totalFees || 0))
.catch(() => 0),

7
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<Input, Result> = createCommand('getIsGenuine', () => fromPromise(getIsGenuine()))
const cmd: Command<Input, Result> = createCommand('getIsGenuine', ({ devicePath, targetId }) =>
fromPromise(withDevice(devicePath)(transport => getIsGenuine(transport, { targetId }))),
)
export default cmd

6
src/components/DashboardPage/EmptyState.js

@ -48,6 +48,9 @@ class EmptyState extends PureComponent<Props, *> {
<Title>{t('app:emptyState.dashboard.title')}</Title>
<Description>{t('app:emptyState.dashboard.desc')}</Description>
<Box mt={3} horizontal justifyContent="space-around" style={{ width: 300 }}>
<Button padded primary style={{ width: 120 }} onClick={this.handleInstallApp}>
{t('app:emptyState.dashboard.buttons.installApp')}
</Button>
<Button
padded
primary
@ -56,9 +59,6 @@ class EmptyState extends PureComponent<Props, *> {
>
{t('app:emptyState.dashboard.buttons.addAccount')}
</Button>
<Button padded primary style={{ width: 120 }} onClick={this.handleInstallApp}>
{t('app:emptyState.dashboard.buttons.installApp')}
</Button>
</Box>
</Box>
</Box>

4
src/components/DeviceConfirm/index.js

@ -41,7 +41,7 @@ const WrapperIcon = styled(Box)`
}
`
const Check = ({ error }: { error: * }) => (
const Check = ({ error }: { error?: boolean }) => (
<WrapperIcon error={error}>
{error ? <IconCross size={10} /> : <IconCheck size={10} />}
</WrapperIcon>
@ -74,7 +74,7 @@ const PushButton = styled(Box)`
`
type Props = {
error: *,
error?: boolean,
}
const SVG = (

53
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<Props, State> {
...prev.inputValue,
[key]: value,
},
incorrectPassword: false,
}))
handleSubmit = async (e: SyntheticEvent<HTMLFormElement>) => {
@ -117,8 +126,25 @@ class IsUnlocked extends Component<Props, State> {
}
}
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 = () => (
<IconWrapperCircle color="alertRed">
<IconTriangleWarning width={23} height={21} />
</IconWrapperCircle>
)
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<Props, State> {
error={incorrectPassword && t('app:password.errorMessageIncorrectPassword')}
/>
</Box>
<Button type="button" mt={3} small onClick={this.handleOpenHardResetModal}>
{t('app:common.lockScreen.lostPassword')}
</Button>
</Box>
</form>
<ConfirmModal
isDanger
isLoading={isHardResetting}
isOpened={isHardResetModalOpened}
onClose={this.handleCloseHardResetModal}
onReject={this.handleCloseHardResetModal}
onConfirm={this.handleHardReset}
title={t('app:settings.hardResetModal.title')}
desc={t('app:settings.hardResetModal.desc')}
renderIcon={this.hardResetIconRender}
/>
</Box>
)
}
@ -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;
`

5
src/components/ManagerPage/EnsureDashboard.js

@ -42,6 +42,7 @@ class EnsureDashboard extends PureComponent<Props, State> {
componentDidMount() {
this.checkForDashboard()
this._interval = setInterval(this.checkForDashboard, 1000)
}
componentDidUpdate() {
@ -50,12 +51,14 @@ class EnsureDashboard extends PureComponent<Props, State> {
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

17
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<Props, State> {
_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) {

2
src/components/ManagerPage/Workflow.js

@ -52,7 +52,7 @@ class Workflow extends PureComponent<Props, State> {
{(device: Device) => (
<EnsureDashboard device={device}>
{(deviceInfo: ?DeviceInfo, dashboardError: ?Error) => (
<EnsureGenuine device={device}>
<EnsureGenuine device={device} infos={deviceInfo}>
{(isGenuine: ?boolean, genuineError: ?Error) => {
if (dashboardError || genuineError) {
return renderError

2
src/components/Onboarding/steps/Analytics.js

@ -19,7 +19,7 @@ type State = {
}
const INITIAL_STATE = {
analyticsToggle: true,
analyticsToggle: false,
sentryLogsToggle: true,
}

7
src/components/Onboarding/steps/GenuineCheck.js

@ -129,7 +129,12 @@ class GenuineCheck extends PureComponent<StepProps, State> {
<FixedTopContainer>
<Box grow alignItems="center">
<Title>{t('onboarding:genuineCheck.title')}</Title>
<Description>{t('onboarding:genuineCheck.desc')}</Description>
{onboarding.isLedgerNano ? (
<Description>{t('onboarding:genuineCheck.descNano')}</Description>
) : (
<Description>{t('onboarding:genuineCheck.descBlue')}</Description>
)}
<Box mt={5}>
<CardWrapper>
<Box justify="center">

20
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<Props, State> {
})
}
hardResetIconRender = () => (
<IconWrapperCircle color="alertRed">
<IconTriangleWarning width={23} height={21} />
</IconWrapperCircle>
)
render() {
const { t, settings, saveSettings } = this.props
const {
@ -223,8 +231,8 @@ class TabProfile extends PureComponent<Props, State> {
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}
/>
<PasswordModal
@ -253,3 +261,13 @@ export default connect(
null,
mapDispatchToProps,
)(TabProfile)
// TODO: need a helper file for common styles across the app
const IconWrapperCircle = styled(Box).attrs({})`
width: 50px;
height: 50px;
border-radius: 50%;
background: #ea2e4919;
text-align: -webkit-center;
justify-content: center;
`

2
src/components/TopBar/ActivityIndicator.js

@ -70,7 +70,7 @@ class ActivityIndicatorInner extends Component<Props, State> {
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 (

80
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<Props, State> {
componentWillReceiveProps(nextProps: Props) {
const { value, showAllDigits, unit } = this.props
const needsToBeReformatted =
value !== nextProps.value ||
!this.state.isFocused &&
(value !== nextProps.value ||
showAllDigits !== nextProps.showAllDigits ||
unit !== nextProps.unit
unit !== nextProps.unit)
if (needsToBeReformatted) {
const { isFocused } = this.state
this.setState({
@ -104,28 +137,13 @@ class InputCurrency extends PureComponent<Props, State> {
}
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<Props, State> {
})
}
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 => <Currency>{item.code}</Currency>

9
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<Props> {
onReject,
onConfirm,
isLoading,
renderIcon,
t,
...props
} = this.props
@ -57,6 +59,11 @@ class ConfirmModal extends PureComponent<Props> {
{subTitle}
</Box>
)}
{renderIcon && (
<Box justifyContent="center" alignItems="center" mt={4} mb={3}>
{renderIcon()}
</Box>
)}
<Box ff="Open Sans" color="smoke" fontSize={4} textAlign="center">
{desc}
</Box>

1
src/components/modals/AccountSettingRenderBody.js

@ -151,6 +151,7 @@ class HelperComp extends PureComponent<Props, State> {
<Box>
<Input
value={account.name}
maxLength={30}
onChange={this.handleChangeName}
renderLeft={<InputLeft currency={account.currency} />}
onFocus={e => this.handleFocus(e, 'accountName')}

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

@ -160,7 +160,9 @@ class StepImport extends PureComponent<StepProps> {
count: importableAccounts.length,
})
const importableAccountsEmpty = t('app:addAccounts.noAccountToImport', { currencyName: currency ? ` ${currency.name}}` : ''})
const importableAccountsEmpty = t('app:addAccounts.noAccountToImport', {
currencyName: currency ? ` ${currency.name}}` : '',
})
return (
<Fragment>

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

@ -198,7 +198,7 @@ class ReceiveModal extends PureComponent<Props, State> {
})
} else {
this.setState({
account: accounts[0]
account: accounts[0],
})
}
}

5
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) => (
<Container>
<WarnBox>{multiline(t('app:send.steps.verification.warning'))}</WarnBox>
<Info>{t('app:send.steps.verification.body')}</Info>
<DeviceConfirm error={hasError} />
<DeviceConfirm />
</Container>
)

16
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<Props, State<*>> {
})
}
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 => {

2
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

2
src/helpers/apps/installApp.js

@ -12,5 +12,5 @@ export default async function installApp(
transport: Transport<*>,
{ appParams }: { appParams: LedgerScriptParams },
): Promise<void> {
return createSocketDialog(transport, '/update/install', appParams)
return createSocketDialog(transport, '/install', appParams)
}

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

34
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<void> {
// return createSocketDialog(transport, '/update/install', appParams)
// }
/**
* Uninstall an app on the device
*/
export async function uninstallApp(
transport: Transport<*>,
{ appParams }: { appParams: LedgerScriptParams },
): Promise<void> {
return createSocketDialog(transport, '/update/install', {
...appParams,
firmware: appParams.delete,
firmwareKey: appParams.deleteKey,
})
}
export async function getMemInfos(transport: Transport<*>): Promise<Object> {
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 => {

6
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 = {

3
src/helpers/createCustomErrorClass.js

@ -1,6 +1,6 @@
// @flow
export default (name: string) => {
export default (name: string): Class<any> => {
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
}

3
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

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

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

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

6
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<typeof core.OPERATION_TYPES>]: 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,

16
src/icons/TriangleWarning.js

@ -0,0 +1,16 @@
// @flow
import React from 'react'
const path = (
<path
fill="currentColor"
d="M6.217 2.188a2.085 2.085 0 0 1 3.566 0l5.653 9.437a2.083 2.083 0 0 1-1.79 3.125h-11.3A2.083 2.083 0 0 1 .57 11.615l5.647-9.427zm1.285.773l-5.64 9.414a.583.583 0 0 0 .491.875h11.285a.583.583 0 0 0 .505-.865L8.5 2.962a.583.583 0 0 0-.997-.001zM7.25 6a.75.75 0 0 1 1.5 0v2.667a.75.75 0 0 1-1.5 0V6zm1.5 5a.75.75 0 1 1-1.5 0v-.01a.75.75 0 1 1 1.5 0V11z"
/>
)
export default ({ height, width, ...p }: { height: number, width: number }) => (
<svg viewBox="0 0 19 17" height={height} width={width} {...p}>
{path}
</svg>
)

10
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', () => {

2
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__,
}

8
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?

1
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

3
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?

8
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?

1
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

3
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?

Loading…
Cancel
Save