Browse Source

Merge pull request #929 from LedgerHQ/develop

Prepare for 1.0.0
master
Gaëtan Renaudeau 7 years ago
committed by GitHub
parent
commit
90cb5e49d9
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      .github/ISSUE_TEMPLATE/bug_report.md
  2. 4
      .github/ISSUE_TEMPLATE/discussion.md
  3. 18
      .github/ISSUE_TEMPLATE/enhancement.md
  4. 14
      .github/ISSUE_TEMPLATE/feature_request.md
  5. 2
      .github/ISSUE_TEMPLATE/question.md
  6. 29
      README.md
  7. 0
      docs/architecture.png
  8. BIN
      docs/screenshot.png
  9. 4
      package.json
  10. 5
      src/analytics/Track.js
  11. 4
      src/analytics/segment.js
  12. 2
      src/api/Ledger.js
  13. 12
      src/bridge/RippleJSBridge.js
  14. 4
      src/components/AccountPage/AccountHeaderActions.js
  15. 2
      src/components/AccountPage/index.js
  16. 3
      src/components/BalanceSummary/index.js
  17. 16
      src/components/SelectExchange.js
  18. 3
      src/components/base/Chart/Tooltip.js
  19. 4
      src/components/base/Markdown/index.js
  20. 2
      src/components/layout/Default.js
  21. 2
      src/components/modals/Receive/index.js
  22. 25
      src/components/modals/Send/steps/01-step-amount.js
  23. 6
      src/config/constants.js
  24. 34
      src/helpers/apps/listAppVersions.js
  25. 10
      src/helpers/apps/listApps.js
  26. 10
      src/helpers/apps/listCategories.js
  27. 66
      src/helpers/common.js
  28. 27
      src/helpers/devices/getCurrentFirmware.js
  29. 78
      src/helpers/devices/getLatestFirmwareForDevice.js
  30. 16
      src/helpers/devices/isDashboardOpen.js
  31. 74
      src/helpers/devices/shouldFlashMcu.js
  32. 16
      src/helpers/firmware/getMcus.js
  33. 30
      src/helpers/firmware/getNextMCU.js
  34. 2
      src/helpers/urls.js
  35. BIN
      static/docs/ledgerLogo.png
  36. 11
      static/i18n/en/app.yml
  37. 12
      static/i18n/en/onboarding.yml
  38. 11
      static/i18n/fr/app.yml
  39. 12
      static/i18n/fr/onboarding.yml
  40. 12
      yarn.lock

1
.github/ISSUE_TEMPLATE/bug_report.md

@ -21,3 +21,4 @@ about: Report a bug in Ledger Live Desktop or a regression.
#### Steps to reproduce the behavior
<!-- explain steps in detail so we can easily reproduce on our side -->
<!-- Alternatively provide a screenshot / gif -->

4
.github/ISSUE_TEMPLATE/discussion.md

@ -1,4 +0,0 @@
---
name: 🗣 Start a Discussion
about: Discuss to propose changes or suggest feature requests.
---

18
.github/ISSUE_TEMPLATE/enhancement.md

@ -0,0 +1,18 @@
---
name: 🗣 Start a Discussion
about: Discuss to propose changes to improve the state of Ledger Live.
---
#### Ledger Live Version
<!-- Precise your current app version (Settings > About or bottom-left corner on a crash screen) -->
- Ledger Live **version_here**
#### Part of the application to improve
<!-- which part is to improve? e.g. Send > Step 1 -->
#### Description
<!-- Explain precisely what you think should be improved and how you think it should work -->

14
.github/ISSUE_TEMPLATE/feature_request.md

@ -0,0 +1,14 @@
---
name: ✨ Feature Request
about: Any feature you find missing in Ledger Live? Discuss to suggest feature requests.
---
- [ ] I have checked this feature was not yet requested.
#### Part of the application
<!-- what part of the application would be impacted by this feature? -->
#### Description
<!-- Explain precisely what is the feature about -->

2
.github/ISSUE_TEMPLATE/question.md

@ -4,4 +4,4 @@ about: If you need help using the app, please contact the support at https://sup
---
<!-- Please prefer using the support for app usage questions, Github issues are only for technical / developer usage -->
<!-- https://support.ledgerwallet.com/ – Please prefer using the support for app usage questions, Github issues are only for technical / developer usage -->

29
README.md

@ -1,20 +1,17 @@
# Ledger Live - Desktop
# Ledger Live (desktop) [![CircleCI](https://circleci.com/gh/LedgerHQ/ledger-live-desktop.svg?style=svg)](https://circleci.com/gh/LedgerHQ/ledger-live-desktop) [![Crowdin](https://d322cqt584bo4o.cloudfront.net/ledger-wallet/localized.svg)](https://crowdin.com/project/ledger-wallet)
[![CircleCI](https://circleci.com/gh/LedgerHQ/ledger-live-desktop.svg?style=svg)](https://circleci.com/gh/LedgerHQ/ledger-live-desktop)
[![Crowdin](https://d322cqt584bo4o.cloudfront.net/ledger-wallet/localized.svg)](https://crowdin.com/project/ledger-wallet)
> Ledger Live is a new generation wallet desktop application providing a unique interface to maintain multiple cryptocurrencies for your Ledger Nano S / Blue. Manage your device, create accounts, receive and send cryptoassets, [...and many more](https://www.ledgerwallet.com/#LINK_TO_ANNOUNCEMENT).
:warning: Disclaimer: this project is under active development. Use at your own risks.
<img src="/static/docs/ledgerLogo.png" width="200"/>
> 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)
<p align="center">
<img src="/docs/screenshot.png" width="550"/>
</p>
## Architecture
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.
Ledger Live is an hybrid desktop application built with Electron, React, Redux, RxJS,.. and highly optimized with [ledger-core](https://github.com/LedgerHQ/lib-ledger-core) C++ library to deal with blockchains (sync, broadcast,..) via [ledger-core-node-bindings](https://github.com/LedgerHQ/lib-ledger-core-node-bindings). It communicates to Ledger hardware wallet devices (Nano S / Blue) to verify address and sign transactions with [ledgerjs](https://github.com/LedgerHQ/ledgerjs). Some logic is shared with [live-common](https://github.com/LedgerHQ/ledger-live-common).
<p align="center">
<img src="/static/docs/architecture.png" width="550"/>
<img src="/docs/architecture.png" width="550"/>
</p>
## Setup
@ -32,23 +29,15 @@ From one side Ledger Desktop app connected to the Blockchain via the in-house wr
## Install
1. Clone or fork the repo
```bash
git clone git@github.com:LedgerHQ/ledger-live-desktop.git
```
2. Install dependencies
```bash
# install dependencies
yarn
```
## Run
Launch the app
```bash
# launch the app
yarn start
```

0
static/docs/architecture.png → docs/architecture.png

Before

Width:  |  Height:  |  Size: 92 KiB

After

Width:  |  Height:  |  Size: 92 KiB

BIN
docs/screenshot.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 346 KiB

4
package.json

@ -40,8 +40,8 @@
"@ledgerhq/hw-app-xrp": "^4.13.0",
"@ledgerhq/hw-transport": "^4.13.0",
"@ledgerhq/hw-transport-node-hid": "^4.13.0",
"@ledgerhq/ledger-core": "2.0.0-rc.3",
"@ledgerhq/live-common": "2.31.0",
"@ledgerhq/ledger-core": "2.0.0-rc.4",
"@ledgerhq/live-common": "^2.32.0",
"animated": "^0.2.2",
"async": "^2.6.1",
"axios": "^0.18.0",

5
src/analytics/Track.js

@ -7,6 +7,7 @@ class Track extends PureComponent<{
onUnmount?: boolean,
onUpdate?: boolean,
event: string,
mandatory?: boolean,
}> {
componentDidMount() {
if (typeof this.props.event !== 'string') {
@ -21,8 +22,8 @@ class Track extends PureComponent<{
if (this.props.onUnmount) this.track()
}
track = () => {
const { event, onMount, onUnmount, onUpdate, ...properties } = this.props
track(event, properties)
const { event, onMount, onUnmount, onUpdate, mandatory, ...properties } = this.props
track(event, properties, mandatory)
}
render() {
return null

4
src/analytics/segment.js

@ -72,9 +72,9 @@ export const stop = () => {
analytics.reset()
}
export const track = (event: string, properties: ?Object) => {
export const track = (event: string, properties: ?Object, mandatory: ?boolean) => {
logger.analyticsTrack(event, properties)
if (!storeInstance || !shareAnalyticsSelector(storeInstance.getState())) {
if (!storeInstance || (!mandatory && !shareAnalyticsSelector(storeInstance.getState()))) {
return
}
const { analytics } = window

2
src/api/Ledger.js

@ -3,4 +3,4 @@ import type { Currency } from '@ledgerhq/live-common/lib/types'
import { LEDGER_REST_API_BASE } from 'config/constants'
export const blockchainBaseURL = ({ ledgerExplorerId }: Currency): ?string =>
ledgerExplorerId ? `${LEDGER_REST_API_BASE}blockchain/v2/${ledgerExplorerId}` : null
ledgerExplorerId ? `${LEDGER_REST_API_BASE}/blockchain/v2/${ledgerExplorerId}` : null

12
src/bridge/RippleJSBridge.js

@ -351,7 +351,12 @@ const RippleJSBridge: WalletBridge<Transaction> = {
return unsubscribe
}),
synchronize: ({ endpointConfig, freshAddress, blockHeight }) =>
synchronize: ({
endpointConfig,
freshAddress,
blockHeight,
operations: { length: currentOpsLength },
}) =>
Observable.create(o => {
let finished = false
const unsubscribe = () => {
@ -394,7 +399,10 @@ const RippleJSBridge: WalletBridge<Transaction> = {
o.next(a => ({ ...a, balance }))
const transactions = await api.getTransactions(freshAddress, {
minLedgerVersion: Math.max(blockHeight, minLedgerVersion),
minLedgerVersion: Math.max(
currentOpsLength === 0 ? 0 : blockHeight, // if there is no ops, it might be after a clear and we prefer to pull from the oldest possible history
minLedgerVersion,
),
maxLedgerVersion,
})

4
src/components/AccountPage/AccountHeaderActions.js

@ -62,7 +62,7 @@ class AccountHeaderActions extends PureComponent<Props> {
const { account, openModal, t } = this.props
return (
<Box horizontal alignItems="center" justifyContent="flex-end" flow={2}>
{account.operations.length > 0 && (
{account.operations.length > 0 || account.balance > 0 ? (
<Fragment>
<Button small primary onClick={() => openModal(MODAL_SEND, { account })}>
<Box horizontal flow={1} alignItems="center">
@ -78,7 +78,7 @@ class AccountHeaderActions extends PureComponent<Props> {
</Box>
</Button>
</Fragment>
)}
) : null}
<Tooltip render={() => t('app:account.settings.title')}>
<ButtonSettings onClick={() => openModal(MODAL_SETTINGS_ACCOUNT, { account })}>
<Box justifyContent="center">

2
src/components/AccountPage/index.js

@ -82,7 +82,7 @@ class AccountPage extends PureComponent<Props> {
<AccountHeaderActions account={account} />
</Box>
{account.operations.length > 0 ? (
{account.operations.length > 0 || account.balance > 0 ? (
<Fragment>
<Box mb={7}>
<BalanceSummary

3
src/components/BalanceSummary/index.js

@ -1,6 +1,7 @@
// @flow
import React, { Fragment } from 'react'
import moment from 'moment'
import { formatShort } from '@ledgerhq/live-common/lib/helpers/currencies'
import type { Currency, Account } from '@ledgerhq/live-common/lib/types'
@ -90,7 +91,7 @@ const BalanceSummary = ({
val={d.value}
/>
<Box ff="Open Sans|Regular" color="grey" fontSize={3} mt={2}>
{d.date.toISOString().substr(0, 10)}
{moment(d.date).format('LL')}
</Box>
</Fragment>
)

16
src/components/SelectExchange.js

@ -37,12 +37,14 @@ class SelectExchange extends Component<
prevFromTo: string,
exchanges: ?(Exchange[]),
error: ?Error,
isLoading: boolean,
},
> {
state = {
prevFromTo: '', // eslint-disable-line
exchanges: null,
error: null,
isLoading: false,
}
static getDerivedStateFromProps(nextProps: *, prevState: *) {
@ -75,25 +77,25 @@ class SelectExchange extends Component<
async _load() {
this._loadId++
if (this._unmounted) return
this.setState({ exchanges: [] })
this.setState({ exchanges: [], isLoading: true })
const { _loadId } = this
const { from, to } = this.props
try {
const exchanges = await getExchanges(from, to)
if (!this._unmounted && this._loadId === _loadId) {
this.setState({ exchanges })
this.setState({ exchanges, isLoading: false })
}
} catch (error) {
logger.error(error)
if (!this._unmounted && this._loadId === _loadId) {
this.setState({ error })
this.setState({ error, isLoading: false })
}
}
}
render() {
const { onChange, exchangeId, style, t, from, to, ...props } = this.props
const { exchanges, error } = this.state
const { exchanges, error, isLoading } = this.state
const options = exchanges ? exchanges.map(e => ({ value: e.id, label: e.name, ...e })) : []
const value = options.find(e => e.id === exchangeId)
@ -115,10 +117,12 @@ class SelectExchange extends Component<
value={value}
options={options}
onChange={onChange}
isLoading={options.length === 0}
isLoading={isLoading}
placeholder={t('app:common.selectExchange')}
noOptionsMessage={({ inputValue }) =>
t('app:common.selectExchangeNoOption', { exchangeName: inputValue })
inputValue
? t('app:common.selectExchangeNoOption', { exchangeName: inputValue })
: t('app:common.selectExchangeNoOptionAtAll')
}
{...props}
/>

3
src/components/base/Chart/Tooltip.js

@ -1,6 +1,7 @@
// @flow
import React, { Fragment } from 'react'
import moment from 'moment'
import styled from 'styled-components'
import type { Unit, Currency } from '@ledgerhq/live-common/lib/types'
@ -68,7 +69,7 @@ const Tooltip = ({
/>
)}
<Box ff="Open Sans|Regular" color="grey" fontSize={3} mt={2}>
{item.date.toISOString().substr(0, 10)}
{moment(item.date).format('LL')}
</Box>
</Fragment>
)}

4
src/components/base/Markdown/index.js

@ -79,6 +79,10 @@ export const Notes = styled(Box).attrs({
color: #6a737d;
}
strong {
font-weight: bold;
}
img {
max-width: 100%;
}

2
src/components/layout/Default.js

@ -13,6 +13,7 @@ import type { Location } from 'react-router'
import * as modals from 'components/modals'
import Box from 'components/base/Box'
import GrowScroll from 'components/base/GrowScroll'
import Track from 'analytics/Track'
import AccountPage from 'components/AccountPage'
import DashboardPage from 'components/DashboardPage'
@ -84,6 +85,7 @@ class Default extends Component<Props> {
<TriggerAppReady />
{process.platform === 'darwin' && <AppRegionDrag />}
<ExportLogsBtn hookToShortcut />
<Track mandatory onMount event="App Starts" />
<OnboardingOrElse>
<IsUnlocked>

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

@ -194,7 +194,7 @@ class ReceiveModal extends PureComponent<Props, State> {
? [verifyAddressError.name === 'UserRefusedAddress' ? 2 : 3]
: []
const isModalLocked = stepId === 'confirm' && isAddressVerified === null
const isModalLocked = stepId === 'receive' && isAddressVerified === null
return (
<Modal

25
src/components/modals/Send/steps/01-step-amount.js

@ -2,6 +2,7 @@
import React, { PureComponent, Fragment } from 'react'
import logger from 'logger'
import Box from 'components/base/Box'
import Button from 'components/base/Button'
import Label from 'components/base/Label'
@ -92,14 +93,14 @@ export class StepAmountFooter extends PureComponent<
StepProps<*>,
{
totalSpent: number,
canBeSpent: boolean,
canNext: boolean,
isSyncing: boolean,
},
> {
state = {
isSyncing: false,
totalSpent: 0,
canBeSpent: true,
canNext: false,
}
componentDidMount() {
@ -127,6 +128,7 @@ export class StepAmountFooter extends PureComponent<
const syncId = ++this.syncId
if (!account || !transaction || !bridge) {
this.setState({ canNext: false, isSyncing: false })
return
}
@ -135,21 +137,26 @@ export class StepAmountFooter extends PureComponent<
try {
const totalSpent = await bridge.getTotalSpent(account, transaction)
if (syncId !== this.syncId) return
const isRecipientValid = await bridge.isRecipientValid(
account.currency,
bridge.getTransactionRecipient(account, transaction),
)
if (syncId !== this.syncId) return
const canBeSpent = await bridge
.checkCanBeSpent(account, transaction)
.then(() => true, () => false)
if (syncId !== this.syncId) return
this.setState({ totalSpent, canBeSpent, isSyncing: false })
const canNext = isRecipientValid && canBeSpent
this.setState({ totalSpent, canNext, isSyncing: false })
} catch (err) {
this.setState({ isSyncing: false })
logger.critical(err)
this.setState({ totalSpent: 0, canNext: false, isSyncing: false })
}
}
render() {
const { t, transitionTo, account, transaction, bridge } = this.props
const { totalSpent, canBeSpent, isSyncing } = this.state
const canNext =
account && transaction && bridge && bridge.isValidTransaction(account, transaction)
const { t, transitionTo, account } = this.props
const { isSyncing, totalSpent, canNext } = this.state
return (
<Fragment>
<Box grow>
@ -186,7 +193,7 @@ export class StepAmountFooter extends PureComponent<
{isSyncing && <Spinner size={10} />}
</Box>
</Box>
<Button disabled={!canBeSpent || !canNext} primary onClick={() => transitionTo('device')}>
<Button disabled={!canNext} primary onClick={() => transitionTo('device')}>
{t('app:common.continue')}
</Button>
</Fragment>

6
src/config/constants.js

@ -43,15 +43,15 @@ export const SYNC_TIMEOUT = intFromEnv('SYNC_TIMEOUT', 30 * 1000)
export const LEDGER_COUNTERVALUES_API = stringFromEnv(
'LEDGER_COUNTERVALUES_API',
'https://beta.manager.live.ledger.fr/countervalues',
'https://countervalues.api.live.ledger.com',
)
export const LEDGER_REST_API_BASE = stringFromEnv(
'LEDGER_REST_API_BASE',
'https://explorers.api.live.ledger.com/',
'https://explorers.api.live.ledger.com',
)
export const MANAGER_API_BASE = stringFromEnv(
'MANAGER_API_BASE',
'https://beta.manager.live.ledger.fr/api',
'https://manager.api.live.ledger.com/api',
)
export const BASE_SOCKET_URL = stringFromEnv('BASE_SOCKET_URL', 'wss://api.ledgerwallet.com/update')

34
src/helpers/apps/listAppVersions.js

@ -7,25 +7,19 @@ import getDeviceVersion from 'helpers/devices/getDeviceVersion'
import getCurrentFirmware from 'helpers/devices/getCurrentFirmware'
export default async (deviceInfo: DeviceInfo) => {
try {
const deviceData = await getDeviceVersion(deviceInfo.targetId, deviceInfo.providerId)
const firmwareData = await getCurrentFirmware({
deviceId: deviceData.id,
fullVersion: deviceInfo.fullVersion,
provider: deviceInfo.providerId,
})
const params = {
provider: deviceInfo.providerId,
current_se_firmware_final_version: firmwareData.id,
device_version: deviceData.id,
}
const {
data: { application_versions },
} = await network({ method: 'POST', url: APPLICATIONS_BY_DEVICE, data: params })
return application_versions.length > 0 ? application_versions : []
} catch (err) {
const error = Error(err.message)
error.stack = err.stack
throw err
const deviceData = await getDeviceVersion(deviceInfo.targetId, deviceInfo.providerId)
const firmwareData = await getCurrentFirmware({
deviceId: deviceData.id,
fullVersion: deviceInfo.fullVersion,
provider: deviceInfo.providerId,
})
const params = {
provider: deviceInfo.providerId,
current_se_firmware_final_version: firmwareData.id,
device_version: deviceData.id,
}
const {
data: { application_versions },
} = await network({ method: 'POST', url: APPLICATIONS_BY_DEVICE, data: params })
return application_versions.length > 0 ? application_versions : []
}

10
src/helpers/apps/listApps.js

@ -4,12 +4,6 @@ import network from 'api/network'
import { GET_APPLICATIONS } from 'helpers/urls'
export default async () => {
try {
const { data } = await network({ method: 'GET', url: GET_APPLICATIONS })
return data.length > 0 ? data : []
} catch (err) {
const error = Error(err.message)
error.stack = err.stack
throw err
}
const { data } = await network({ method: 'GET', url: GET_APPLICATIONS })
return data.length > 0 ? data : []
}

10
src/helpers/apps/listCategories.js

@ -4,12 +4,6 @@ import network from 'api/network'
import { GET_CATEGORIES } from 'helpers/urls'
export default async () => {
try {
const { data } = await network({ method: 'GET', url: GET_CATEGORIES })
return data.length > 0 ? data : []
} catch (err) {
const error = Error(err.message)
error.stack = err.stack
throw err
}
const { data } = await network({ method: 'GET', url: GET_CATEGORIES })
return data.length > 0 ? data : []
}

66
src/helpers/common.js

@ -27,44 +27,38 @@ export type LedgerScriptParams = {
* Retrieve targetId and firmware version from device
*/
export async function getFirmwareInfo(transport: Transport<*>) {
try {
const res = await transport.send(...APDUS.GET_FIRMWARE)
const byteArray = [...res]
const data = byteArray.slice(0, byteArray.length - 2)
const targetIdStr = Buffer.from(data.slice(0, 4))
const targetId = targetIdStr.readUIntBE(0, 4)
const seVersionLength = data[4]
const seVersion = Buffer.from(data.slice(5, 5 + seVersionLength)).toString()
const flagsLength = data[5 + seVersionLength]
const flags = Buffer.from(
data.slice(5 + seVersionLength + 1, 5 + seVersionLength + 1 + flagsLength),
).toString()
const res = await transport.send(...APDUS.GET_FIRMWARE)
const byteArray = [...res]
const data = byteArray.slice(0, byteArray.length - 2)
const targetIdStr = Buffer.from(data.slice(0, 4))
const targetId = targetIdStr.readUIntBE(0, 4)
const seVersionLength = data[4]
const seVersion = Buffer.from(data.slice(5, 5 + seVersionLength)).toString()
const flagsLength = data[5 + seVersionLength]
const flags = Buffer.from(
data.slice(5 + seVersionLength + 1, 5 + seVersionLength + 1 + flagsLength),
).toString()
const mcuVersionLength = data[5 + seVersionLength + 1 + flagsLength]
let mcuVersion = Buffer.from(
data.slice(
7 + seVersionLength + flagsLength,
7 + seVersionLength + flagsLength + mcuVersionLength,
),
)
if (mcuVersion[mcuVersion.length - 1] === 0) {
mcuVersion = mcuVersion.slice(0, mcuVersion.length - 1)
}
mcuVersion = mcuVersion.toString()
const mcuVersionLength = data[5 + seVersionLength + 1 + flagsLength]
let mcuVersion = Buffer.from(
data.slice(
7 + seVersionLength + flagsLength,
7 + seVersionLength + flagsLength + mcuVersionLength,
),
)
if (mcuVersion[mcuVersion.length - 1] === 0) {
mcuVersion = mcuVersion.slice(0, mcuVersion.length - 1)
}
mcuVersion = mcuVersion.toString()
if (!seVersionLength) {
return {
targetId,
seVersion: '0.0.0',
flags: '',
mcuVersion: '',
}
if (!seVersionLength) {
return {
targetId,
seVersion: '0.0.0',
flags: '',
mcuVersion: '',
}
return { targetId, seVersion, flags, mcuVersion }
} catch (err) {
const error = new Error(err.message)
error.stack = err.stack
throw error
}
return { targetId, seVersion, flags, mcuVersion }
}

27
src/helpers/devices/getCurrentFirmware.js

@ -9,22 +9,15 @@ type Input = {
provider: number,
}
let error
export default async (input: Input): Promise<*> => {
try {
const { data } = await network({
method: 'POST',
url: GET_CURRENT_FIRMWARE,
data: {
device_version: input.deviceId,
version_name: input.fullVersion,
provider: input.provider,
},
})
return data
} catch (err) {
error = Error(err.message)
error.stack = err.stack
throw error
}
const { data } = await network({
method: 'POST',
url: GET_CURRENT_FIRMWARE,
data: {
device_version: input.deviceId,
version_name: input.fullVersion,
provider: input.provider,
},
})
return data
}

78
src/helpers/devices/getLatestFirmwareForDevice.js

@ -10,53 +10,47 @@ import getCurrentFirmware from './getCurrentFirmware'
import getDeviceVersion from './getDeviceVersion'
export default async (deviceInfo: DeviceInfo) => {
try {
// Get device infos from targetId
const deviceVersion = await getDeviceVersion(deviceInfo.targetId, deviceInfo.providerId)
// Get firmware infos with firmware name and device version
const seFirmwareVersion = await getCurrentFirmware({
fullVersion: deviceInfo.fullVersion,
deviceId: deviceVersion.id,
// Get device infos from targetId
const deviceVersion = await getDeviceVersion(deviceInfo.targetId, deviceInfo.providerId)
// Get firmware infos with firmware name and device version
const seFirmwareVersion = await getCurrentFirmware({
fullVersion: deviceInfo.fullVersion,
deviceId: deviceVersion.id,
provider: deviceInfo.providerId,
})
// Fetch next possible firmware
const { data } = await network({
method: 'POST',
url: GET_LATEST_FIRMWARE,
data: {
current_se_firmware_final_version: seFirmwareVersion.id,
device_version: deviceVersion.id,
provider: deviceInfo.providerId,
})
// Fetch next possible firmware
const { data } = await network({
method: 'POST',
url: GET_LATEST_FIRMWARE,
data: {
current_se_firmware_final_version: seFirmwareVersion.id,
device_version: deviceVersion.id,
provider: deviceInfo.providerId,
},
})
if (data.result === 'null') {
return null
}
},
})
const { se_firmware_osu_version } = data
const { next_se_firmware_final_version } = se_firmware_osu_version
const seFirmwareFinalVersion = await getFinalFirmwareById(next_se_firmware_final_version)
if (data.result === 'null') {
return null
}
const mcus = await getMcus()
const { se_firmware_osu_version } = data
const { next_se_firmware_final_version } = se_firmware_osu_version
const seFirmwareFinalVersion = await getFinalFirmwareById(next_se_firmware_final_version)
const currentMcuVersionId = mcus
.filter(mcu => mcu.name === deviceInfo.mcuVersion)
.map(mcu => mcu.id)
const mcus = await getMcus()
if (!seFirmwareFinalVersion.mcu_versions.includes(...currentMcuVersionId)) {
return {
...se_firmware_osu_version,
shouldFlashMcu: true,
}
}
const currentMcuVersionId = mcus
.filter(mcu => mcu.name === deviceInfo.mcuVersion)
.map(mcu => mcu.id)
return { ...se_firmware_osu_version, shouldFlashMcu: false }
} catch (err) {
const error = Error(err.message)
error.stack = err.stack
throw error
if (!seFirmwareFinalVersion.mcu_versions.includes(...currentMcuVersionId)) {
return {
...se_firmware_osu_version,
shouldFlashMcu: true,
}
}
return { ...se_firmware_osu_version, shouldFlashMcu: false }
}

16
src/helpers/devices/isDashboardOpen.js

@ -7,16 +7,10 @@ import { getFirmwareInfo } from 'helpers/common'
type Result = boolean
export default async (transport: Transport<*>): Promise<Result> => {
try {
const { targetId, seVersion } = await getFirmwareInfo(transport)
if (targetId && seVersion) {
return true
}
return false
} catch (err) {
const error = Error(err.message)
error.stack = err.stack
throw error
const { targetId, seVersion } = await getFirmwareInfo(transport)
if (targetId && seVersion) {
return true
}
return false
}

74
src/helpers/devices/shouldFlashMcu.js

@ -10,46 +10,40 @@ import getOsuFirmware from './getOsuFirmware'
import getDeviceVersion from './getDeviceVersion'
export default async (deviceInfo: DeviceInfo): Promise<boolean> => {
try {
// Get device infos from targetId
const deviceVersion = await getDeviceVersion(deviceInfo.targetId, deviceInfo.providerId)
// Get firmware infos with firmware name and device version
const seFirmwareVersion = await getOsuFirmware({
version: deviceInfo.fullVersion,
deviceId: deviceVersion.id,
// Get device infos from targetId
const deviceVersion = await getDeviceVersion(deviceInfo.targetId, deviceInfo.providerId)
// Get firmware infos with firmware name and device version
const seFirmwareVersion = await getOsuFirmware({
version: deviceInfo.fullVersion,
deviceId: deviceVersion.id,
provider: deviceInfo.providerId,
})
// Fetch next possible firmware
const { data } = await network({
method: 'POST',
url: GET_LATEST_FIRMWARE,
data: {
current_se_firmware_final_version: seFirmwareVersion.id,
device_version: deviceVersion.id,
provider: deviceInfo.providerId,
})
// Fetch next possible firmware
const { data } = await network({
method: 'POST',
url: GET_LATEST_FIRMWARE,
data: {
current_se_firmware_final_version: seFirmwareVersion.id,
device_version: deviceVersion.id,
provider: deviceInfo.providerId,
},
})
if (data.result === 'null') {
return false
}
const { se_firmware_osu_version } = data
const { next_se_firmware_final_version } = se_firmware_osu_version
const seFirmwareFinalVersion = await getFinalFirmwareById(next_se_firmware_final_version)
const mcus = await getMcus()
const currentMcuVersionId = mcus
.filter(mcu => mcu.name === deviceInfo.mcuVersion)
.map(mcu => mcu.id)
return !seFirmwareFinalVersion.mcu_versions.includes(...currentMcuVersionId)
} catch (err) {
const error = Error(err.message)
error.stack = err.stack
throw error
},
})
if (data.result === 'null') {
return false
}
const { se_firmware_osu_version } = data
const { next_se_firmware_final_version } = se_firmware_osu_version
const seFirmwareFinalVersion = await getFinalFirmwareById(next_se_firmware_final_version)
const mcus = await getMcus()
const currentMcuVersionId = mcus
.filter(mcu => mcu.name === deviceInfo.mcuVersion)
.map(mcu => mcu.id)
return !seFirmwareFinalVersion.mcu_versions.includes(...currentMcuVersionId)
}

16
src/helpers/firmware/getMcus.js

@ -4,16 +4,10 @@ import network from 'api/network'
import { GET_MCUS } from 'helpers/urls'
export default async (): Promise<*> => {
try {
const { data } = await network({
method: 'GET',
url: GET_MCUS,
})
const { data } = await network({
method: 'GET',
url: GET_MCUS,
})
return data
} catch (err) {
const error = Error(err.message)
error.stack = err.stack
throw err
}
return data
}

30
src/helpers/firmware/getNextMCU.js

@ -7,24 +7,18 @@ import { createCustomErrorClass } from 'helpers/errors'
const LatestMCUInstalledError = createCustomErrorClass('LatestMCUInstalledError')
export default async (bootloaderVersion: string): Promise<*> => {
try {
const { data } = await network({
method: 'POST',
url: GET_NEXT_MCU,
data: {
bootloader_version: bootloaderVersion,
},
})
const { data } = await network({
method: 'POST',
url: GET_NEXT_MCU,
data: {
bootloader_version: bootloaderVersion,
},
})
// FIXME: nextVersion will not be able to "default" when
// Error handling is standardize on the API side
if (data === 'default' || !data.name) {
throw new LatestMCUInstalledError('there is no next mcu version to install')
}
return data
} catch (err) {
const error = Error(err.message)
error.stack = err.stack
throw err
// FIXME: nextVersion will not be able to "default" when
// Error handling is standardize on the API side
if (data === 'default' || !data.name) {
throw new LatestMCUInstalledError('there is no next mcu version to install')
}
return data
}

2
src/helpers/urls.js

@ -14,6 +14,8 @@ const wsURLBuilder = (endpoint: string) => (params?: Object) =>
// const wsURLBuilderProxy = (endpoint: string) => (params?: Object) =>
// `ws://manager.ledger.fr:3501/${endpoint}${params ? `?${qs.stringify(params)}` : ''}`
// FIXME we shouldn't do this here. we should just collocate these where it's used.
export const GET_FINAL_FIRMWARE: string = managerUrlbuilder('firmware_final_versions')
export const GET_DEVICE_VERSION: string = managerUrlbuilder('get_device_version')
export const APPLICATIONS_BY_DEVICE: string = managerUrlbuilder('get_apps')

BIN
static/docs/ledgerLogo.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

11
static/i18n/en/app.yml

@ -7,7 +7,7 @@ common:
cancel: Cancel
delete: Delete
continue: Continue
learnMore: Learn More
learnMore: Learn more
skipThisStep: Skip this step
needHelp: Need help?
chooseWalletPlaceholder: Choose a wallet...
@ -18,6 +18,7 @@ common:
selectCurrencyNoOption: 'No crypto asset "{{currencyName}}"'
selectExchange: Select an exchange
selectExchangeNoOption: 'No exchange matching "{{exchangeName}}"'
selectExchangeNoOptionAtAll: 'No exchange found'
sortBy: Sort by
search: Search
save: Save
@ -137,7 +138,7 @@ deviceConnect:
dashboard: Not used. # This key is not used. Still managed in JS.
emptyState:
sidebar:
text: Press the + button to add an account to your portfolio
text: Press this button to add accounts to your portfolio
dashboard:
title: 'Add accounts to your portfolio'
desc: Your portfolio has no accounts the first time Ledger Live is launched. Open the Manager to install apps on your Ledger device before you start adding accounts to your portfolio.
@ -273,7 +274,7 @@ send:
totalSpent: Total to debit
steps:
amount:
title: crypto assets
title: Details
selectAccountDebit: Select an account to debit
recipientAddress: Recipient address # can't control the tooltip!
amount: Amount
@ -360,7 +361,7 @@ settings: # Always ensure descriptions carry full stops (.)
reportErrors: Report bugs
reportErrorsDesc: Share anonymous usage and diagnostics data to help improve Ledger products, services and security features.
about:
desc: Learn about Ledger Live features
desc: Information about Ledger Live, terms and conditions, and privacy policy.
help:
desc: Learn about Ledger Live features or get help.
version: Version
@ -370,7 +371,7 @@ settings: # Always ensure descriptions carry full stops (.)
terms: Terms and conditions
termsDesc: By using Ledger Live you are deemed to have accepted our terms and conditions.
privacy: Privacy policy
privacyDesc: Refer to our privacy policy to learn what personal data we collect, and why and how we use it.
privacyDesc: Refer to our privacy policy to learn what personal data we collect, why and how we use it.
hardResetModal:
title: Reset Ledger Live
desc: Erase all Ledger Live data stored on your computer, including your accounts, transaction history and settings. The private keys to access your crypto assets in the blockchain remain secure on your Ledger device and on your Recovery sheet.

12
static/i18n/en/onboarding.yml

@ -132,19 +132,19 @@ analytics:
title: Share analytics
desc: Enable analytics to help Ledger understand how to improve the user experience.
mandatoryContextual:
item1: Page visits
item2: Actions (send, receive, logout)
item1: In-app page visits
item2: Actions (send, receive, lock)
item3: Clicks
item4: Redirections to webpages
item5: Scrolled to end of page
item6: Install/Uninstall
item7: Number of accounts, currencies and operations
item8: Overall and page session duration
item9: Device product ID
item10: Device firmware and app versions
item9: Type of Ledger device
item10: Device firmware and app version numbers
sentryLogs:
title: Report bugs
desc: Automatically send reports to help Ledger fix bugs
desc: Automatically send reports to help Ledger fix bugs.
technicalData:
title: Technical data *
desc: Ledger will automatically collect technical information to get basic feedback on usage. This information is anonymous and does not contain personal data.
@ -155,7 +155,7 @@ analytics:
item2: OS name and version
item3: Ledger Live version
item4: Application language or region
item5: OS Language/Region
item5: OS language or region
finish:
title: Your device is ready!
desc: Proceed to your portfolio and start adding your accounts...

11
static/i18n/fr/app.yml

@ -8,7 +8,7 @@ common:
cancel: Cancel
delete: Delete
continue: Continue
learnMore: Learn More
learnMore: Learn more
skipThisStep: Skip this step
needHelp: Need help?
chooseWalletPlaceholder: Choose a wallet...
@ -19,6 +19,7 @@ common:
selectCurrencyNoOption: 'No crypto asset "{{currencyName}}"'
selectExchange: Select an exchange
selectExchangeNoOption: 'No exchange matching "{{exchangeName}}"'
selectExchangeNoOptionAtAll: 'No exchange found'
sortBy: Sort by
search: Search
save: Save
@ -134,7 +135,7 @@ deviceConnect:
dashboard: Not used.
emptyState:
sidebar:
text: Press the + button to add an account to your portfolio
text: Press this button to add accounts to your portfolio
dashboard:
title: 'Add accounts to your portfolio'
desc: Your portfolio has no accounts the first time Ledger Live is launched. Open the Manager to install apps on your Ledger device before you start adding accounts to your portfolio.
@ -270,7 +271,7 @@ send:
totalSpent: Total to debit
steps:
amount:
title: crypto assets
title: Details
selectAccountDebit: Select an account to debit
recipientAddress: Recipient address
amount: Amount
@ -357,7 +358,7 @@ settings:
reportErrors: Report bugs
reportErrorsDesc: Share anonymous usage and diagnostics data to help improve Ledger products, services and security features.
about:
desc: Learn about Ledger Live features
desc: Information about Ledger Live, terms and conditions, and privacy policy.
help:
desc: Learn about Ledger Live features or get help.
version: Version
@ -367,7 +368,7 @@ settings:
terms: Terms and conditions
termsDesc: By using Ledger Live you are deemed to have accepted our terms and conditions.
privacy: Privacy policy
privacyDesc: Refer to our privacy policy to learn what personal data we collect, and why and how we use it.
privacyDesc: Refer to our privacy policy to learn what personal data we collect, why and how we use it.
hardResetModal:
title: Reset Ledger Live
desc: Erase all Ledger Live data stored on your computer, including your accounts, transaction history and settings. The private keys to access your crypto assets in the blockchain remain secure on your Ledger device and on your Recovery sheet.

12
static/i18n/fr/onboarding.yml

@ -133,19 +133,19 @@ analytics:
title: Share analytics
desc: Enable analytics to help Ledger understand how to improve the user experience.
mandatoryContextual:
item1: Page visits
item2: Actions (send, receive, logout)
item1: In-app page visits
item2: Actions (send, receive, lock)
item3: Clicks
item4: Redirections to webpages
item5: Scrolled to end of page
item6: Install/Uninstall
item7: Number of accounts, currencies and operations
item8: Overall and page session duration
item9: Device product ID
item10: Device firmware and app versions
item9: Type of Ledger device
item10: Device firmware and app version numbers
sentryLogs:
title: Report bugs
desc: Automatically send reports to help Ledger fix bugs
desc: Automatically send reports to help Ledger fix bugs.
technicalData:
title: Technical data *
desc: Ledger will automatically collect technical information to get basic feedback on usage. This information is anonymous and does not contain personal data.
@ -156,7 +156,7 @@ analytics:
item2: OS name and version
item3: Ledger Live version
item4: Application language or region
item5: OS Language/Region
item5: OS language or region
finish:
title: Your device is ready!
desc: Proceed to your portfolio and start adding your accounts...

12
yarn.lock

@ -1521,9 +1521,9 @@
dependencies:
events "^2.0.0"
"@ledgerhq/ledger-core@2.0.0-rc.3":
version "2.0.0-rc.3"
resolved "https://registry.yarnpkg.com/@ledgerhq/ledger-core/-/ledger-core-2.0.0-rc.3.tgz#21b04239e9ba6b7fdcb89958eea8ad47a4a28a88"
"@ledgerhq/ledger-core@2.0.0-rc.4":
version "2.0.0-rc.4"
resolved "https://registry.yarnpkg.com/@ledgerhq/ledger-core/-/ledger-core-2.0.0-rc.4.tgz#0ec80a763c666658bea94bd38b86aa90d5a24906"
dependencies:
"@ledgerhq/hw-app-btc" "^4.7.3"
"@ledgerhq/hw-transport-node-hid" "^4.7.6"
@ -1534,9 +1534,9 @@
npm "^5.7.1"
prebuild-install "^2.2.2"
"@ledgerhq/live-common@2.31.0":
version "2.31.0"
resolved "https://registry.yarnpkg.com/@ledgerhq/live-common/-/live-common-2.31.0.tgz#0f599a1e23b64d9ed74a845d3a9c82f0696f1df3"
"@ledgerhq/live-common@^2.32.0":
version "2.32.0"
resolved "https://registry.yarnpkg.com/@ledgerhq/live-common/-/live-common-2.32.0.tgz#6c2108d58ec44335077d87442a6418e0ec4d3372"
dependencies:
axios "^0.18.0"
invariant "^2.2.2"

Loading…
Cancel
Save