Browse Source

Merge branch 'master' into polish/focus

master
Meriadec Pillet 7 years ago
committed by GitHub
parent
commit
85eacee0d2
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 9
      README.md
  2. 19
      src/components/MainSideBar/TopGradient.js
  3. 2
      src/components/MainSideBar/index.js
  4. 110
      src/components/ManagerPage/AppsList.js
  5. 51
      src/components/ManagerPage/Dashboard.js
  6. 2
      src/components/ManagerPage/FinalFirmwareUpdate.js
  7. 10
      src/components/ManagerPage/FirmwareUpdate.js
  8. 5
      src/components/ManagerPage/ManagerApp.js
  9. 8
      src/components/ManagerPage/PlugYourDevice.js
  10. 4
      src/components/ManagerPage/UpdateFirmwareButton.js
  11. 193
      src/components/ManagerPage/Workflow.js
  12. 187
      src/components/ManagerPage/WorkflowDefault.js
  13. 101
      src/components/ManagerPage/index.js
  14. 6
      src/components/SettingsPage/sections/Display.js
  15. 82
      src/components/base/Progress/index.js
  16. 2
      src/helpers/db.js
  17. 1
      src/helpers/deviceAccess.js
  18. 15
      src/helpers/user.js
  19. 7
      src/init-sentry.js
  20. 9
      src/internals/index.js
  21. 21
      src/main/bridge.js
  22. 1
      src/main/index.js
  23. 14
      src/middlewares/sentry.js
  24. 3
      src/reducers/settings.js
  25. 4
      src/renderer/createStore.js
  26. 11
      src/renderer/index.js
  27. 4
      src/renderer/init.js
  28. 9
      src/sentry/browser.js
  29. 24
      src/sentry/install.js
  30. 8
      src/sentry/node.js
  31. 19
      static/i18n/en/app.yml
  32. 3
      webpack/plugins.js

9
README.md

@ -37,12 +37,15 @@ yarn
# Where errors will be tracked (you may not want to edit this line)
# SENTRY_URL=
# api base url
API_BASE_URL=http://...
# OPTIONAL ENV VARIABLES
# ----------------------
# API base url, fallback to our API if not set
API_BASE_URL=http://...
# Setup device debug mode
DEBUG_DEVICE=0
# Developer tools position (used only in dev)
# can be one of: right, bottom, undocked, detach
DEV_TOOLS_MODE=bottom

19
src/components/MainSideBar/TopGradient.js

@ -0,0 +1,19 @@
// @flow
import React from 'react'
import styled from 'styled-components'
const TopGradientBox = styled.div`
width: 100%;
height: 80px;
position: absolute;
top: 0;
left: 0;
background: linear-gradient(#ffffff 40%, rgba(255, 255, 255, 0));
z-index: 2;
pointer-events: none;
`
const el = <TopGradientBox />
export default () => el

2
src/components/MainSideBar/index.js

@ -32,6 +32,7 @@ import IconExchange from 'icons/Exchange'
import AccountListItem from './AccountListItem'
import AddAccountButton from './AddAccountButton'
import TopGradient from './TopGradient'
const mapStateToProps = state => ({
accounts: accountsSelector(state),
@ -84,6 +85,7 @@ class MainSideBar extends PureComponent<Props> {
return (
<Box relative bg="white" style={{ width: 230 }}>
<TopGradient />
<GrowScroll>
<Space of={70} />
<SideBarList title={t('app:sidebar.menu')}>

110
src/components/ManagerPage/AppsList.js

@ -4,6 +4,8 @@ import React, { PureComponent } from 'react'
import styled from 'styled-components'
import { translate } from 'react-i18next'
import type { Device, T } from 'types/common'
import listApps from 'commands/listApps'
import installApp from 'commands/installApp'
import uninstallApp from 'commands/uninstallApp'
@ -11,9 +13,13 @@ import uninstallApp from 'commands/uninstallApp'
import Box from 'components/base/Box'
import Modal, { ModalBody } from 'components/base/Modal'
import Tooltip from 'components/base/Tooltip'
import ExclamationCircle from 'icons/ExclamationCircle'
import Text from 'components/base/Text'
import Progress from 'components/base/Progress'
import type { Device, T } from 'types/common'
import ExclamationCircle from 'icons/ExclamationCircle'
import Update from 'icons/Update'
import Trash from 'icons/Trash'
import CheckCircle from 'icons/CheckCircle'
import ManagerApp from './ManagerApp'
import AppSearchBar from './AppSearchBar'
@ -32,6 +38,7 @@ const ICONS_FALLBACK = {
}
type Status = 'loading' | 'idle' | 'busy' | 'success' | 'error'
type Mode = '' | 'installing' | 'uninstalling'
type LedgerApp = {
name: string,
@ -54,6 +61,8 @@ type State = {
status: Status,
error: string | null,
appsList: LedgerApp[],
app: string,
mode: Mode,
}
class AppsList extends PureComponent<Props, State> {
@ -61,6 +70,8 @@ class AppsList extends PureComponent<Props, State> {
status: 'loading',
error: null,
appsList: [],
app: '',
mode: '',
}
componentDidMount() {
@ -86,40 +97,87 @@ class AppsList extends PureComponent<Props, State> {
}
}
handleInstallApp = (args: { app: any }) => async () => {
const appParams = args.app
this.setState({ status: 'busy' })
handleInstallApp = (args: { app: any, name: string }) => async () => {
const { app: appParams, name } = args
this.setState({ status: 'busy', app: name, mode: 'installing' })
try {
const {
device: { path: devicePath },
} = this.props
const data = { appParams, devicePath }
await installApp.send(data).toPromise()
this.setState({ status: 'success' })
this.setState({ status: 'success', app: '' })
} catch (err) {
this.setState({ status: 'error', error: err.message })
this.setState({ status: 'error', error: err.message, app: '', mode: '' })
}
}
handleUninstallApp = (args: { app: any }) => async () => {
const appParams = args.app
this.setState({ status: 'busy' })
handleUninstallApp = (args: { app: any, name: string }) => async () => {
const { app: appParams, name } = args
this.setState({ status: 'busy', app: name, mode: 'uninstalling' })
try {
const {
device: { path: devicePath },
} = this.props
const data = { appParams, devicePath }
await uninstallApp.send(data).toPromise()
this.setState({ status: 'success' })
this.setState({ status: 'success', app: '' })
} catch (err) {
this.setState({ status: 'error', error: err.message })
this.setState({ status: 'error', error: err.message, app: '', mode: '' })
}
}
handleCloseModal = () => this.setState({ status: 'idle' })
handleCloseModal = () => this.setState({ status: 'idle', mode: '' })
renderModal = () => {
const { t } = this.props
const { app, status, error, mode } = this.state
return (
<Modal
isOpened={status !== 'idle' && status !== 'loading'}
render={() => (
<ModalBody p={6} align="center" justify="center" style={{ height: 300 }}>
{status === 'busy' || status === 'idle' ? (
<Box align="center" justify="center" flow={3}>
{mode === 'installing' ? <Update size={30} /> : <Trash size={30} />}
<Text ff="Museo Sans|Regular" fontSize={6} color="dark">
{t(`app:manager.apps.${mode}`, { app })}
</Text>
<Box my={5} style={{ width: 250 }}>
<Progress style={{ width: '100%' }} infinite />
</Box>
</Box>
) : status === 'error' ? (
<Box align="center" justify="center" flow={3}>
<div>{'error happened'}</div>
{error}
<button onClick={this.handleCloseModal}>close</button>
</Box>
) : status === 'success' ? (
<Box align="center" justify="center" flow={3}>
<Box color="positiveGreen">
<CheckCircle size={30} />
</Box>
<Text ff="Museo Sans|Regular" fontSize={6} color="dark">
{t(
`app:manager.apps.${
mode === 'installing' ? 'installSuccess' : 'uninstallSuccess'
}`,
{ app },
)}
</Text>
<button onClick={this.handleCloseModal}>close</button>
</Box>
) : null}
</ModalBody>
)}
/>
)
}
renderList() {
const { status, error, appsList } = this.state
const { appsList } = this.state
return (
<Box>
<AppSearchBar list={appsList}>
@ -138,27 +196,7 @@ class AppsList extends PureComponent<Props, State> {
</List>
)}
</AppSearchBar>
<Modal
isOpened={status !== 'idle' && status !== 'loading'}
render={() => (
<ModalBody p={6} align="center" justify="center" style={{ height: 300 }}>
{status === 'busy' ? (
<Box>{'Loading...'}</Box>
) : status === 'error' ? (
<Box>
<div>{'error happened'}</div>
{error}
<button onClick={this.handleCloseModal}>close</button>
</Box>
) : status === 'success' ? (
<Box>
{'success'}
<button onClick={this.handleCloseModal}>close</button>
</Box>
) : null}
</ModalBody>
)}
/>
{this.renderModal()}
</Box>
)
}
@ -169,7 +207,7 @@ class AppsList extends PureComponent<Props, State> {
<Box flow={6}>
<Box>
<Box mb={4} color="dark" ff="Museo Sans" fontSize={5} flow={2} horizontal>
<span>{t('app:manager.allApps')}</span>
<span>{t('app:manager.apps.all')}</span>
<span>
<Tooltip
render={() => (

51
src/components/ManagerPage/Dashboard.js

@ -0,0 +1,51 @@
// @flow
import React from 'react'
import { translate } from 'react-i18next'
import type { T, Device } from 'types/common'
import Box from 'components/base/Box'
import Text from 'components/base/Text'
import AppsList from './AppsList'
import FirmwareUpdate from './FirmwareUpdate'
type DeviceInfo = {
targetId: number | string,
version: string,
final: boolean,
mcu: boolean,
}
type Props = {
t: T,
device: Device,
deviceInfo: DeviceInfo,
}
const Dashboard = ({ device, deviceInfo, t }: Props) => (
<Box flow={4}>
<Box>
<Text ff="Museo Sans|Regular" fontSize={7} color="black">
{t('app:manager.title')}
</Text>
<Text ff="Museo Sans|Light" fontSize={5}>
{t('app:manager.subtitle')}
</Text>
</Box>
<Box mt={7}>
<FirmwareUpdate
infos={{
targetId: deviceInfo.targetId,
version: deviceInfo.version,
}}
device={device}
/>
</Box>
<Box>
<AppsList device={device} targetId={deviceInfo.targetId} />
</Box>
</Box>
)
export default translate()(Dashboard)

2
src/components/ManagerPage/FinalFirmwareUpdate.js

@ -41,7 +41,7 @@ class FirmwareUpdate extends PureComponent<Props, State> {
return (
<Box flow={4} {...props}>
<Box color="dark" ff="Museo Sans" fontSize={6}>
{t('app:manager.firmwareUpdate')}
{t('app:manager.firmware.update')}
</Box>
<Card flow={2} {...props}>
<Box horizontal align="center" flow={2} />

10
src/components/ManagerPage/FirmwareUpdate.js

@ -1,11 +1,12 @@
// @flow
import React, { PureComponent } from 'react'
import { translate } from 'react-i18next'
import isEqual from 'lodash/isEqual'
import isEmpty from 'lodash/isEmpty'
import invariant from 'invariant'
import logger from 'logger'
import type { Device } from 'types/common'
import type { Device, T } from 'types/common'
import getLatestFirmwareForDevice from 'commands/getLatestFirmwareForDevice'
import installOsuFirmware from 'commands/installOsuFirmware'
@ -31,6 +32,7 @@ type DeviceInfos = {
}
type Props = {
t: T,
device: Device,
infos: DeviceInfos,
}
@ -96,7 +98,7 @@ class FirmwareUpdate extends PureComponent<Props, State> {
}
render() {
const { infos } = this.props
const { infos, t } = this.props
const { latestFirmware } = this.state
return (
@ -115,7 +117,7 @@ class FirmwareUpdate extends PureComponent<Props, State> {
</Box>
</Box>
<Text ff="Open Sans|SemiBold" fontSize={2}>
Firmware {infos.version}
{t('app:manager.firmware.installed', { version: infos.version })}
</Text>
</Box>
<UpdateFirmwareButton firmware={latestFirmware} installFirmware={this.installFirmware} />
@ -125,4 +127,4 @@ class FirmwareUpdate extends PureComponent<Props, State> {
}
}
export default FirmwareUpdate
export default translate()(FirmwareUpdate)

5
src/components/ManagerPage/ManagerApp.js

@ -52,8 +52,7 @@ type Props = {
onUninstall: Function,
}
function ManagerApp(props: Props) {
const { name, version, icon, onInstall, onUninstall, t } = props
function ManagerApp({ name, version, icon, onInstall, onUninstall, t }: Props) {
const iconUrl = `https://api.ledgerwallet.com/update/assets/icons/${icon}`
return (
<Container>
@ -65,7 +64,7 @@ function ManagerApp(props: Props) {
</Text>
</Box>
<Button outline onClick={onInstall}>
{t('app:manager.installApps')}
{t('app:manager.apps.install')}
</Button>
<Button outline onClick={onUninstall} outlineColor="grey">
<Trash size={16} fill="grey" />

8
src/components/ManagerPage/PlugYourDevice.js

@ -8,6 +8,8 @@ import type { T } from 'types/common'
import Box, { Card } from 'components/base/Box'
import Button from 'components/base/Button'
// TODO: NOT IN USE, REMOVE
type Props = {
t: T,
}
@ -19,12 +21,12 @@ function PlugYourDevice(props: Props) {
<Box align="center" style={{ width: 365 }}>
<Box mb={5}>hey</Box>
<Box textAlign="center" mb={1} ff="Museo Sans|Regular" color="dark" fontSize={6}>
{t('app:manager.plugYourDevice.title')}
{t('app:manager.device.title')}
</Box>
<Box textAlign="center" mb={5} ff="Open Sans|Regular" color="smoke" fontSize={4}>
{t('app:manager.plugYourDevice.desc')}
{t('app:manager.device.desc')}
</Box>
<Button primary>{t('app:manager.plugYourDevice.cta')}</Button>
<Button primary>{t('app:manager.device.cta')}</Button>
</Box>
</Card>
)

4
src/components/ManagerPage/UpdateFirmwareButton.js

@ -22,10 +22,10 @@ const UpdateFirmwareButton = ({ t, firmware, installFirmware }: Props) =>
firmware ? (
<Fragment>
<Text ff="Open Sans|Regular" fontSize={4} style={{ marginLeft: 'auto', marginRight: 15 }}>
{t('app:manager.latestFirmware', { version: firmware.name })}
{t('app:manager.firmware.latest', { version: firmware.name })}
</Text>
<Button primary onClick={installFirmware}>
{t('app:manager.installFirmware')}
{t('app:manager.firmware.update')}
</Button>
</Fragment>
) : null

193
src/components/ManagerPage/Workflow.js

@ -1,23 +1,9 @@
// @flow
import React, { PureComponent } from 'react'
import styled from 'styled-components'
import { Trans, translate } from 'react-i18next'
import isNull from 'lodash/isNull'
import type { Node } from 'react'
import type { Device, T } from 'types/common'
import { i } from 'helpers/staticPath'
import Box from 'components/base/Box'
import Space from 'components/base/Space'
import Spinner from 'components/base/Spinner'
import Text from 'components/base/Text'
import IconCheck from 'icons/Check'
import IconExclamationCircle from 'icons/ExclamationCircle'
import IconUsb from 'icons/Usb'
import IconHome from 'icons/Home'
import type { Device } from 'types/common'
import EnsureDevice from './EnsureDevice'
import EnsureDashboard from './EnsureDashboard'
@ -36,7 +22,12 @@ type Error = {
}
type Props = {
t: T,
renderDefault: (
device: ?Device,
deviceInfo: ?DeviceInfo,
dashboardError: ?Error,
isGenuine: ?boolean,
) => Node,
renderMcuUpdate: (deviceInfo: DeviceInfo) => Node,
renderFinalUpdate: (deviceInfo: DeviceInfo) => Node,
renderDashboard: (device: Device, deviceInfo: DeviceInfo) => Node,
@ -44,73 +35,15 @@ type Props = {
}
type State = {}
const Step = styled(Box).attrs({
borderRadius: 1,
justifyContent: 'center',
fontSize: 4,
})`
border: 1px solid
${p =>
p.validated
? p.theme.colors.wallet
: p.hasErrors
? p.theme.colors.alertRed
: p.theme.colors.fog};
`
const StepIcon = styled(Box).attrs({
alignItems: 'center',
justifyContent: 'center',
})`
width: 64px;
`
const StepContent = styled(Box).attrs({
color: 'dark',
horizontal: true,
alignItems: 'center',
})`
height: 60px;
line-height: 1.2;
strong {
font-weight: 600;
}
`
const StepCheck = ({ checked, hasErrors }: { checked: ?boolean, hasErrors?: boolean }) => (
<Box pr={5}>
{checked ? (
<Box color="wallet">
<IconCheck size={16} />
</Box>
) : hasErrors ? (
<Box color="alertRed">
<IconExclamationCircle size={16} />
</Box>
) : (
<Spinner size={16} />
)}
</Box>
)
StepCheck.defaultProps = {
hasErrors: false,
}
const WrapperIconCurrency = styled(Box).attrs({
alignItems: 'center',
justifyContent: 'center',
})`
border: 1px solid ${p => p.theme.colors[p.color]};
border-radius: 8px;
height: 24px;
width: 24px;
`
class Workflow extends PureComponent<Props, State> {
render() {
const { renderDashboard, renderFinalUpdate, renderMcuUpdate, renderError, t } = this.props
const {
renderDashboard,
renderFinalUpdate,
renderMcuUpdate,
renderError,
renderDefault,
} = this.props
return (
<EnsureDevice>
{(device: Device) => (
@ -141,101 +74,7 @@ class Workflow extends PureComponent<Props, State> {
return renderDashboard(device, deviceInfo)
}
return (
<Box align="center">
<Space of={152} />
<Box align="center" style={{ maxWidth: 460, padding: '0 10px' }}>
<img
src={i('logos/connectDevice.png')}
alt="connect your device"
style={{ marginBottom: 30, maxWidth: 362, width: '100%' }}
/>
<Text
ff="Museo Sans|Regular"
fontSize={7}
color="black"
style={{ marginBottom: 10 }}
>
{t('app:manager.plugYourDevice:title')}
</Text>
<Text ff="Museo Sans|Light" fontSize={5} color="grey" align="center">
{t('app:manager.plugYourDevice:desc')}
</Text>
</Box>
<Box flow={4} style={{ maxWidth: 460, padding: '60px 10px 0' }}>
{/* DEVICE CHECK */}
<Step validated={!!device}>
<StepContent>
<StepIcon>
<IconUsb size={36} />
</StepIcon>
<Box grow shrink>
<Trans i18nKey="deviceConnect:step1.connect" parent="div">
{'Connect your '}
<strong>Ledger device</strong>
{' to your computer and enter your '}
<strong>PIN code</strong>
{' on your device'}
</Trans>
</Box>
<StepCheck checked={!!device} />
</StepContent>
</Step>
{/* DASHBOARD CHECK */}
<Step
validated={!!device && !!deviceInfo}
hasErrors={!!device && !!dashboardError}
>
<StepContent>
<StepIcon>
<WrapperIconCurrency>
<IconHome size={12} />
</WrapperIconCurrency>
</StepIcon>
<Box grow shrink>
<Trans i18nKey="deviceConnect:dashboard.open" parent="div">
{'Go to the '}
<strong>{'dashboard'}</strong>
{' on your device'}
</Trans>
</Box>
<StepCheck
checked={!!device && !!deviceInfo}
hasErrors={!!device && !!dashboardError}
/>
</StepContent>
</Step>
{/* GENUINE CHECK */}
<Step
validated={(!!device && !isNull(isGenuine) && isGenuine) || undefined}
hasErrors={(!!device && !isNull(isGenuine) && !isGenuine) || undefined}
>
<StepContent>
<StepIcon>
<WrapperIconCurrency>
<IconCheck size={12} />
</WrapperIconCurrency>
</StepIcon>
<Box grow shrink>
<Trans i18nKey="deviceConnect:stepGenuine.open" parent="div">
{'Confirm '}
<strong>{'authentication'}</strong>
{' on your device'}
</Trans>
</Box>
<StepCheck
checked={(!!device && !isNull(isGenuine) && isGenuine) || undefined}
hasErrors={
(!!device && !isNull(isGenuine) && !isGenuine) || undefined
}
/>
</StepContent>
</Step>
</Box>
</Box>
)
return renderDefault(device, deviceInfo, dashboardError, isGenuine)
}}
</EnsureGenuine>
)}
@ -246,4 +85,4 @@ class Workflow extends PureComponent<Props, State> {
}
}
export default translate()(Workflow)
export default Workflow

187
src/components/ManagerPage/WorkflowDefault.js

@ -0,0 +1,187 @@
// @flow
import React from 'react'
import { Trans, translate } from 'react-i18next'
import styled from 'styled-components'
import isNull from 'lodash/isNull'
import type { Device, T } from 'types/common'
import { i } from 'helpers/staticPath'
import Box from 'components/base/Box'
import Space from 'components/base/Space'
import Text from 'components/base/Text'
import Spinner from 'components/base/Spinner'
import IconCheck from 'icons/Check'
import IconExclamationCircle from 'icons/ExclamationCircle'
import IconUsb from 'icons/Usb'
import IconHome from 'icons/Home'
const WrapperIconCurrency = styled(Box).attrs({
alignItems: 'center',
justifyContent: 'center',
})`
border: 1px solid ${p => p.theme.colors[p.color]};
border-radius: 8px;
height: 24px;
width: 24px;
`
const Step = styled(Box).attrs({
borderRadius: 1,
justifyContent: 'center',
fontSize: 4,
})`
border: 1px solid
${p =>
p.validated
? p.theme.colors.wallet
: p.hasErrors
? p.theme.colors.alertRed
: p.theme.colors.fog};
`
const StepIcon = styled(Box).attrs({
alignItems: 'center',
justifyContent: 'center',
})`
width: 64px;
`
const StepContent = styled(Box).attrs({
color: 'dark',
horizontal: true,
alignItems: 'center',
})`
height: 60px;
line-height: 1.2;
strong {
font-weight: 600;
}
`
const StepCheck = ({ checked, hasErrors }: { checked: ?boolean, hasErrors?: boolean }) => (
<Box pr={5}>
{checked ? (
<Box color="wallet">
<IconCheck size={16} />
</Box>
) : hasErrors ? (
<Box color="alertRed">
<IconExclamationCircle size={16} />
</Box>
) : (
<Spinner size={16} />
)}
</Box>
)
StepCheck.defaultProps = {
hasErrors: false,
}
type DeviceInfo = {
targetId: number | string,
version: string,
final: boolean,
mcu: boolean,
}
type Error = {
message: string,
stack: string,
}
type Props = {
t: T,
device: ?Device,
deviceInfo: ?DeviceInfo,
dashboardError: ?Error,
isGenuine: boolean,
}
const WorkflowDefault = ({ device, deviceInfo, dashboardError, isGenuine, t }: Props) => (
<Box align="center">
<Space of={152} />
<Box align="center" style={{ maxWidth: 460, padding: '0 10px' }}>
<img
src={i('logos/connectDevice.png')}
alt="connect your device"
style={{ marginBottom: 30, maxWidth: 362, width: '100%' }}
/>
<Text ff="Museo Sans|Regular" fontSize={7} color="black" style={{ marginBottom: 10 }}>
{t('app:manager.device.title')}
</Text>
<Text ff="Museo Sans|Light" fontSize={5} color="grey" align="center">
{t('app:manager.device.desc')}
</Text>
</Box>
<Box flow={4} style={{ maxWidth: 460, padding: '60px 10px 0' }}>
{/* DEVICE CHECK */}
<Step validated={!!device}>
<StepContent>
<StepIcon>
<IconUsb size={36} />
</StepIcon>
<Box grow shrink>
<Trans i18nKey="deviceConnect:step1.connect" parent="div">
{'Connect your '}
<strong>Ledger device</strong>
{' to your computer and enter your '}
<strong>PIN code</strong>
{' on your device'}
</Trans>
</Box>
<StepCheck checked={!!device} />
</StepContent>
</Step>
{/* DASHBOARD CHECK */}
<Step validated={!!device && !!deviceInfo} hasErrors={!!device && !!dashboardError}>
<StepContent>
<StepIcon>
<WrapperIconCurrency>
<IconHome size={12} />
</WrapperIconCurrency>
</StepIcon>
<Box grow shrink>
<Trans i18nKey="deviceConnect:dashboard.open" parent="div">
{'Go to the '}
<strong>{'dashboard'}</strong>
{' on your device'}
</Trans>
</Box>
<StepCheck checked={!!device && !!deviceInfo} hasErrors={!!device && !!dashboardError} />
</StepContent>
</Step>
{/* GENUINE CHECK */}
<Step
validated={(!!device && !isNull(isGenuine) && isGenuine) || undefined}
hasErrors={(!!device && !isNull(isGenuine) && !isGenuine) || undefined}
>
<StepContent>
<StepIcon>
<WrapperIconCurrency>
<IconCheck size={12} />
</WrapperIconCurrency>
</StepIcon>
<Box grow shrink>
<Trans i18nKey="deviceConnect:stepGenuine.open" parent="div">
{'Confirm '}
<strong>{'authentication'}</strong>
{' on your device'}
</Trans>
</Box>
<StepCheck
checked={(!!device && !isNull(isGenuine) && isGenuine) || undefined}
hasErrors={(!!device && !isNull(isGenuine) && !isGenuine) || undefined}
/>
</StepContent>
</Step>
</Box>
</Box>
)
export default translate()(WorkflowDefault)

101
src/components/ManagerPage/index.js

@ -1,17 +1,13 @@
// @flow
import React, { PureComponent } from 'react'
import { translate } from 'react-i18next'
import React from 'react'
import type { Node } from 'react'
import type { T, Device } from 'types/common'
import type { Device } from 'types/common'
import Box from 'components/base/Box'
import Text from 'components/base/Text'
import AppsList from './AppsList'
import FirmwareUpdate from './FirmwareUpdate'
import Workflow from './Workflow'
import WorkflowDefault from './WorkflowDefault'
import Dashboard from './Dashboard'
type DeviceInfo = {
targetId: number | string,
@ -25,61 +21,38 @@ type Error = {
stack: string,
}
type Props = {
t: T,
}
type State = {
modalOpen: boolean,
}
class ManagerPage extends PureComponent<Props, State> {
renderDashboard = (device: Device, deviceInfo: DeviceInfo) => {
const { t } = this.props
return (
<Box flow={4}>
<Box>
<Text ff="Museo Sans|Regular" fontSize={7} color="black">
{t('app:manager.title')}
</Text>
<Text ff="Museo Sans|Light" fontSize={5}>
{t('app:manager.subtitle')}
</Text>
</Box>
<Box mt={7}>
<FirmwareUpdate
infos={{
targetId: deviceInfo.targetId,
version: deviceInfo.version,
}}
device={device}
/>
</Box>
<Box>
<AppsList device={device} targetId={deviceInfo.targetId} />
</Box>
</Box>
)
}
render(): Node {
return (
<Workflow
renderError={(dashboardError: ?Error, genuineError: ?Error) => {
if (dashboardError) return <span>Dashboard Error: {dashboardError.message}</span>
if (genuineError) return <span>Genuine Error: {genuineError.message}</span>
return <span>Error</span>
}}
renderFinalUpdate={(deviceInfo: DeviceInfo) => (
<p>UPDATE FINAL FIRMARE (TEMPLATE + ACTION WIP) {deviceInfo.final}</p>
)}
renderMcuUpdate={(deviceInfo: DeviceInfo) => (
<p>FLASH MCU (TEMPLATE + ACTION WIP) {deviceInfo.mcu}</p>
)}
renderDashboard={this.renderDashboard}
/>
)
}
function ManagerPage(): Node {
return (
<Workflow
renderError={(dashboardError: ?Error, genuineError: ?Error) => {
if (dashboardError) return <span>Dashboard Error: {dashboardError.message}</span>
if (genuineError) return <span>Genuine Error: {genuineError.message}</span>
return <span>Error</span>
}}
renderFinalUpdate={(deviceInfo: DeviceInfo) => (
<p>UPDATE FINAL FIRMARE (TEMPLATE + ACTION WIP) {deviceInfo.final}</p>
)}
renderMcuUpdate={(deviceInfo: DeviceInfo) => (
<p>FLASH MCU (TEMPLATE + ACTION WIP) {deviceInfo.mcu}</p>
)}
renderDashboard={(device: Device, deviceInfo: DeviceInfo) => (
<Dashboard device={device} deviceInfo={deviceInfo} />
)}
renderDefault={(
device: ?Device,
deviceInfo: ?DeviceInfo,
dashboardError: ?Error,
isGenuine: ?boolean,
) => (
<WorkflowDefault
device={device}
deviceInfo={deviceInfo}
dashboardError={dashboardError}
isGenuine={isGenuine}
/>
)}
/>
)
}
export default translate()(ManagerPage)
export default ManagerPage

6
src/components/SettingsPage/sections/Display.js

@ -173,12 +173,6 @@ class TabProfile extends PureComponent<Props, State> {
/>
</Box>
</Row>
<Row
title={t('app:settings.display.exchange', {
ticker: `${intermediaryCurrency.ticker}${counterValueCurrency.ticker}`,
})}
desc={t('app:settings.display.exchangeDesc')}
/>
<Row
title={t('app:settings.display.language')}
desc={t('app:settings.display.languageDesc')}

82
src/components/base/Progress/index.js

@ -0,0 +1,82 @@
// @flow
import React, { Component } from 'react'
import styled, { keyframes } from 'styled-components'
import Box from 'components/base/Box'
const inifiteAnimation = keyframes`
0% {
left: 0
}
100% {
left: 102%
}
`
const fillInAnimation = keyframes`
0% {
transform: translate3d(-110%, 0, 0);
}
50% {
transform: translate3d(-30%, 0, 0);
}
100% {
transform: translate3d(0);
}
`
const Bar = styled(Box).attrs({
color: 'fog',
borderRadius: '2.5px',
})`
height: 5px;
width: 100%;
position: relative;
background-color: currentColor;
overflow: hidden;
`
const Progression = styled(Bar).attrs({
color: 'wallet',
})`
position: absolute;
top: 0;
left: 0;
${p =>
p.infinite
? `
animation: 1000ms ${inifiteAnimation} ease-out infinite;
`
: `
animation: ${p.timing}ms ${fillInAnimation} ease-out;
animation-fill-mode: forwards;
`};
`
type Props = {
infinite: boolean,
timing?: number,
color?: string,
}
type State = {}
class Progress extends Component<Props, State> {
static defaultProps = {
infinite: false,
timing: 3000,
color: 'wallet',
}
render() {
const { infinite, color, timing } = this.props
const styles = infinite ? { width: '20%' } : { width: '100%' }
return (
<Bar>
<Progression infinite={infinite} color={color} style={styles} timing={timing} />
</Bar>
)
}
}
export default Progress

2
src/helpers/db.js

@ -6,7 +6,7 @@ import get from 'lodash/get'
import { decodeAccountsModel, encodeAccountsModel } from 'reducers/accounts'
type DBKey = 'settings' | 'accounts' | 'countervalues'
type DBKey = 'settings' | 'accounts' | 'countervalues' | 'user'
const encryptionKey = {}

1
src/helpers/deviceAccess.js

@ -19,6 +19,7 @@ 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)
try {
const res = await job(t)
// $FlowFixMe

15
src/helpers/user.js

@ -0,0 +1,15 @@
// @flow
import db from 'helpers/db'
import uuid from 'uuid/v4'
// a user is an anonymous way to identify a same instance of the app
export default () => {
let user = db.get('user')
if (!user) {
user = { id: uuid() }
db.set('user', user)
}
return user
}

7
src/init-sentry.js

@ -1,7 +0,0 @@
const { SENTRY_URL } = process.env
if (__PROD__ && SENTRY_URL) {
const Raven = require('raven')
const ravenConfig = { captureUnhandledRejections: true }
Raven.config(SENTRY_URL, ravenConfig).install()
}

9
src/internals/index.js

@ -3,14 +3,18 @@ import commands from 'commands'
import logger from 'logger'
import uuid from 'uuid/v4'
import { setImplementation } from 'api/network'
import sentry from 'sentry/node'
require('../env')
require('../init-sentry')
process.title = 'Internal'
const defers = {}
let sentryEnabled = process.env.INITIAL_SENTRY_ENABLED || false
sentry(() => sentryEnabled, process.env.SENTRY_USER_ID)
if (process.env.DEBUG_NETWORK) {
setImplementation(networkArg => {
const id = uuid()
@ -92,6 +96,9 @@ process.on('message', m => {
} else {
defer.reject(payload.error)
}
} else if (m.type === 'sentryLogsChanged') {
const { payload } = m
sentryEnabled = payload.value
}
})

21
src/main/bridge.js

@ -7,6 +7,8 @@ import { ipcMain, app } from 'electron'
import { ipcMainListenReceiveCommands } from 'helpers/ipc'
import path from 'path'
import logger from 'logger'
import sentry from 'sentry/node'
import user from 'helpers/user'
import setupAutoUpdater, { quitAndInstall } from './autoUpdate'
@ -17,6 +19,11 @@ const LEDGER_LIVE_SQLITE_PATH = path.resolve(app.getPath('userData'), 'sqlite')
let internalProcess
let sentryEnabled = false
const userId = user().id
sentry(() => sentryEnabled, userId)
const killInternalProcess = () => {
if (internalProcess) {
logger.log('killing internal process...')
@ -30,7 +37,12 @@ const forkBundlePath = path.resolve(__dirname, `${__DEV__ ? '../../' : './'}dist
const bootInternalProcess = () => {
logger.log('booting internal process...')
internalProcess = fork(forkBundlePath, {
env: { ...process.env, LEDGER_LIVE_SQLITE_PATH },
env: {
...process.env,
LEDGER_LIVE_SQLITE_PATH,
INITIAL_SENTRY_ENABLED: sentryEnabled,
SENTRY_USER_ID: userId,
},
})
internalProcess.on('message', handleGlobalInternalMessage)
internalProcess.on('exit', code => {
@ -102,6 +114,13 @@ ipcMain.on('executeHttpQueryPayload', (event, payload) => {
p.send({ type: 'executeHttpQueryPayload', payload })
})
ipcMain.on('sentryLogsChanged', (event, payload) => {
sentryEnabled = payload.value
const p = internalProcess
if (!p) return
p.send({ type: 'sentryLogsChanged', payload })
})
// TODO move this to "command" pattern
ipcMain.on('updater', (event, { type, data }) => {
const handler = {

1
src/main/index.js

@ -4,7 +4,6 @@ process.setMaxListeners(0)
require('../env')
require('../globals')
require('../init-sentry')
require('./app')
setImmediate(() => require('./bridge'))

14
src/middlewares/sentry.js

@ -0,0 +1,14 @@
import { ipcRenderer } from 'electron'
import { sentryLogsBooleanSelector } from 'reducers/settings'
let isSentryInstalled = false
export default store => next => action => {
next(action)
const state = store.getState()
const sentryLogs = sentryLogsBooleanSelector(state)
if (sentryLogs !== isSentryInstalled) {
isSentryInstalled = sentryLogs
ipcRenderer.send('sentryLogsChanged', { value: sentryLogs })
}
}

3
src/reducers/settings.js

@ -73,7 +73,7 @@ const INITIAL_STATE: SettingsState = {
developerMode: !!process.env.__DEV__,
loaded: false,
shareAnalytics: false,
sentryLogs: false,
sentryLogs: true,
lastUsedVersion: __APP_VERSION__,
}
@ -214,5 +214,6 @@ export const exchangeSettingsForAccountSelector: ESFAS = createSelector(
)
export const marketIndicatorSelector = (state: State) => state.settings.marketIndicator
export const sentryLogsBooleanSelector = (state: State) => state.settings.sentryLogs
export default handleActions(handlers, INITIAL_STATE)

4
src/renderer/createStore.js

@ -6,7 +6,7 @@ import thunk from 'redux-thunk'
import createHistory from 'history/createHashHistory'
import type { HashHistory } from 'history'
import logger from 'middlewares/logger'
import sentry from 'middlewares/sentry'
import reducers from 'reducers'
type Props = {
@ -20,7 +20,7 @@ export default ({ state, history, dbMiddleware }: Props) => {
if (!history) {
history = createHistory()
}
const middlewares = [routerMiddleware(history), thunk, logger]
const middlewares = [routerMiddleware(history), thunk, logger, sentry]
if (dbMiddleware) {
middlewares.push(dbMiddleware)
}

11
src/renderer/index.js

@ -1,14 +1,3 @@
require('@babel/polyfill')
const Raven = require('raven-js')
require('../env')
const { SENTRY_URL } = process.env
if (__PROD__ && SENTRY_URL) {
Raven.config(SENTRY_URL, { allowSecretKey: true }).install()
window.addEventListener('unhandledrejection', event => Raven.captureException(event.reason))
}
require('./init')

4
src/renderer/init.js

@ -14,7 +14,7 @@ import events from 'renderer/events'
import { fetchAccounts } from 'actions/accounts'
import { fetchSettings } from 'actions/settings'
import { isLocked } from 'reducers/application'
import { getLanguage } from 'reducers/settings'
import { getLanguage, sentryLogsBooleanSelector } from 'reducers/settings'
import libcoreGetVersion from 'commands/libcoreGetVersion'
import db from 'helpers/db'
@ -22,6 +22,7 @@ import dbMiddleware from 'middlewares/db'
import CounterValues from 'helpers/countervalues'
import hardReset from 'helpers/hardReset'
import sentry from 'sentry/browser'
import App from 'components/App'
import 'styles/global'
@ -59,6 +60,7 @@ async function init() {
const state = store.getState()
const language = getLanguage(state)
const locked = isLocked(state)
sentry(() => sentryLogsBooleanSelector(store.getState()))
moment.locale(language)

9
src/sentry/browser.js

@ -0,0 +1,9 @@
// @flow
import Raven from 'raven-js'
import user from 'helpers/user'
import install from './install'
export default (shouldSendCallback: () => boolean) => {
install(Raven, shouldSendCallback, user().id)
}

24
src/sentry/install.js

@ -0,0 +1,24 @@
// @flow
require('../env')
export default (Raven: any, shouldSendCallback: () => boolean, userId: string) => {
if (!__SENTRY_URL__) return
let r = Raven.config(__SENTRY_URL__, {
captureUnhandledRejections: true,
allowSecretKey: true,
release: __APP_VERSION__,
environment: __DEV__ ? 'development' : 'production',
shouldSendCallback,
})
const user = {
ip_address: null,
id: userId,
}
if (r.setUserContext) {
r = r.setUserContext(user)
} else if (r.setContext) {
r = r.setContext({ user })
}
r.install()
}

8
src/sentry/node.js

@ -0,0 +1,8 @@
// @flow
import Raven from 'raven'
import install from './install'
export default (shouldSendCallback: () => boolean, userId: string) => {
install(Raven, shouldSendCallback, userId)
}

19
static/i18n/en/app.yml

@ -151,16 +151,23 @@ manager:
tabs:
apps: Apps
device: My device
installApps: Install
installFirmware: Update firmware
allApps: Apps
apps:
install: Install
all: Apps
installing: 'Installing {{app}}...'
uninstalling: 'Uninstalling {{app}}...'
installSuccess: '{{app}} app successfully installed'
uninstallSuccess: '{{app}} app successfully uninstalled'
alreadyInstalled: '{{app}} app is already installed'
help: To update an app, you have to uninstall the app and re install it.
firmware:
installed: 'Firmware {{version}}'
update: Update firmware
updateTitle: Firmware update
latest: 'A new firmware {{version}} is available'
title: Manager
subtitle: Get all your apps here
firmwareUpdate: Firmware update
latestFirmware: A new firmware {{version}} is available
plugYourDevice:
device:
title: Plug your device
desc: Please connect your Ledger device and follow the steps below to access the manager
cta: Plug my device

3
webpack/plugins.js

@ -3,7 +3,7 @@ const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer')
const pkg = require('../package.json')
require('../src/globals')
const { BUNDLE_ANALYZER } = process.env
const { BUNDLE_ANALYZER, SENTRY_URL } = process.env
module.exports = type => {
const plugins = [
@ -12,6 +12,7 @@ module.exports = type => {
__GLOBAL_STYLES__: JSON.stringify(__GLOBAL_STYLES__),
__DEV__,
__PROD__,
__SENTRY_URL__: JSON.stringify(SENTRY_URL || null),
'process.env.NODE_ENV': JSON.stringify(__ENV__),
}),
]

Loading…
Cancel
Save