Browse Source

Merge branch 'master' into better-error-precision-for-receive

master
Gaëtan Renaudeau 7 years ago
committed by GitHub
parent
commit
dd5643044d
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 26
      README.md
  2. 6
      src/components/DeviceConnect/stories.js
  3. 60
      src/components/ExportLogsBtn.js
  4. 4
      src/components/SettingsPage/sections/Profile.js
  5. 6
      src/components/ThrowBlock.js
  6. 34
      src/config/cryptocurrencies.js
  7. 66
      src/logger.js
  8. 2
      src/reducers/settings.js
  9. 2
      src/stories/currencies.stories.js
  10. 4
      static/i18n/en/settings.yml

26
README.md

@ -80,29 +80,3 @@ yarn dist
```
**Note:** Use `yarn dist:dir` to speed up the process: it will skip the packaging step. Handy for debugging builds. You can also use `BUNDLE_ANALYZER=1 yarn dist:dir` to generate [webpack-bundle-analyzer](https://github.com/webpack-contrib/webpack-bundle-analyzer) report.
## License
```
The MIT License
Copyright (c) 2017-present Ledger https://www.ledgerwallet.com/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
```

6
src/components/DeviceConnect/stories.js

@ -4,10 +4,8 @@ import React from 'react'
import { storiesOf } from '@storybook/react'
import { select } from '@storybook/addon-knobs'
import { action } from '@storybook/addon-actions'
import {
getCryptoCurrencyById,
listCryptoCurrencies,
} from '@ledgerhq/live-common/lib/helpers/currencies'
import { getCryptoCurrencyById } from '@ledgerhq/live-common/lib/helpers/currencies'
import { listCryptoCurrencies } from 'config/cryptocurrencies'
import type { Currency } from '@ledgerhq/live-common/lib/types'

60
src/components/ExportLogsBtn.js

@ -0,0 +1,60 @@
// @flow
import logger from 'logger'
import moment from 'moment'
import fs from 'fs'
import { webFrame, remote } from 'electron'
import React, { Component } from 'react'
import { translate } from 'react-i18next'
import { connect } from 'react-redux'
import { createStructuredSelector, createSelector } from 'reselect'
import { accountsSelector, encodeAccountsModel } from 'reducers/accounts'
import { storeSelector as settingsSelector } from 'reducers/settings'
import Button from './base/Button'
const mapStateToProps = createStructuredSelector({
accounts: createSelector(accountsSelector, encodeAccountsModel),
settings: settingsSelector,
})
class ExportLogsBtn extends Component<{
t: *,
settings: *,
accounts: *,
}> {
handleExportLogs = () => {
const { accounts, settings } = this.props
const logs = logger.exportLogs()
const resourceUsage = webFrame.getResourceUsage()
const report = { resourceUsage, logs, accounts, settings, date: new Date() }
console.log(report) // eslint-disable-line no-console
const reportJSON = JSON.stringify(report)
const path = remote.dialog.showSaveDialog({
title: 'Export logs',
defaultPath: `ledger_export_${moment().format('YYYY-MM-DD_HHmmss')}.json`,
filters: [
{
name: 'All Files',
extensions: ['json'],
},
],
})
if (path) {
fs.writeFile(path, reportJSON, err => {
if (err) {
logger.error(err)
}
})
}
}
render() {
const { t } = this.props
return (
<Button primary onClick={this.handleExportLogs}>
{t('settings:exportLogs.btn')}
</Button>
)
}
}
export default translate()(connect(mapStateToProps)(ExportLogsBtn))

4
src/components/SettingsPage/sections/Profile.js

@ -13,6 +13,7 @@ import { delay } from 'helpers/promise'
import type { SettingsState } from 'reducers/settings'
import type { T } from 'types/common'
import ExportLogsBtn from 'components/ExportLogsBtn'
import CheckBox from 'components/base/CheckBox'
import Box from 'components/base/Box'
import Button from 'components/base/Button'
@ -181,6 +182,9 @@ class TabProfile extends PureComponent<Props, State> {
{t('settings:profile.hardReset')}
</Button>
</Row>
<Row title={t('settings:exportLogs.title')} desc={t('settings:exportLogs.desc')}>
<ExportLogsBtn />
</Row>
</Body>
<ConfirmModal

6
src/components/ThrowBlock.js

@ -1,5 +1,6 @@
// @flow
import logger from 'logger'
import React, { PureComponent } from 'react'
import styled from 'styled-components'
import { shell, remote } from 'electron'
@ -8,6 +9,7 @@ import qs from 'querystring'
import { rgba } from 'styles/helpers'
import db from 'helpers/db'
import ExportLogsBtn from 'components/ExportLogsBtn'
import Box from 'components/base/Box'
import Button from 'components/base/Button'
@ -43,6 +45,7 @@ class ThrowBlock extends PureComponent<Props, State> {
}
componentDidCatch(error: Error) {
logger.error(error)
this.setState({ error })
}
@ -83,9 +86,10 @@ ${error.stack}
<Button primary onClick={this.handleRestart}>
{'Restart app'}
</Button>
<Button primary onClick={this.handleReset}>
<Button danger onClick={this.handleReset}>
{'Reset app files'}
</Button>
<ExportLogsBtn />
<Button primary onClick={this.handleCreateIssue}>
{'Create ticket'}
</Button>

34
src/config/cryptocurrencies.js

@ -0,0 +1,34 @@
// @flow
import memoize from 'lodash/memoize'
import { listCryptoCurrencies as listCC } from '@ledgerhq/live-common/lib/helpers/currencies'
import type { CryptoCurrencyIds } from '@ledgerhq/live-common/lib/types'
const supported: CryptoCurrencyIds[] = [
'bitcoin',
'ethereum',
'ripple',
'bitcoin_cash',
'litecoin',
'dash',
'ethereum_classic',
'qtum',
'zcash',
'bitcoin_gold',
'stratis',
'dogecoin',
'digibyte',
'hcash',
'komodo',
'pivx',
'zencash',
'vertcoin',
'peercoin',
'viacoin',
'stealthcoin',
'poswallet',
'bitcoin_testnet',
]
export const listCryptoCurrencies = memoize((withDevCrypto?: boolean) =>
listCC(withDevCrypto).filter(c => supported.includes(c.id)),
)

66
src/logger.js

@ -9,37 +9,93 @@
* - for analytics in the future
*/
const logs = []
const MAX_LOG_LENGTH = 500
const MAX_LOG_JSON_THRESHOLD = 2000
function addLog(type, ...args) {
logs.push({ type, date: new Date(), args })
if (logs.length > MAX_LOG_LENGTH) {
logs.shift()
}
}
const makeSerializableLog = (o: mixed) => {
if (typeof o === 'string') return o
if (typeof o === 'number') return o
if (typeof o === 'object' && o) {
try {
const json = JSON.stringify(o)
if (json.length < MAX_LOG_JSON_THRESHOLD) {
return o
}
// try to make a one level object on the same principle
const oneLevel = {}
Object.keys(o).forEach(key => {
// $FlowFixMe
oneLevel[key] = makeSerializableLog(o[key])
})
const json2 = JSON.stringify(oneLevel)
if (json2.length < MAX_LOG_JSON_THRESHOLD) {
return oneLevel
}
} catch (e) {
// This is not serializable so we will just stringify it
}
}
return String(o)
}
const logClicks = !__DEV__ || process.env.DEBUG_CLICK_ELEMENT
const logRedux = !__DEV__ || process.env.DEBUG_ACTION
export default {
// tracks the user interactions (click, input focus/blur, what else?)
onClickElement: (role: string, roleData: ?Object) => {
if (!__DEV__ || process.env.DEBUG_CLICK_ELEMENT) {
const label = `👆 ${role}`
if (roleData) {
const label = `👆 ${role}`
if (roleData) {
if (logClicks) {
console.log(label, roleData)
} else {
}
addLog('click', label, roleData)
} else {
if (logClicks) {
console.log(label)
}
addLog('click', label, roleData)
}
},
// tracks Redux actions (NB not all actions are serializable)
onReduxAction: (action: Object) => {
if (!__DEV__ || process.env.DEBUG_ACTION) {
if (logRedux) {
console.log(`⚛️ ${action.type}`, action)
}
addLog('action', `⚛️ ${action.type}`, action)
},
// General functions in case the hooks don't apply
log: (...args: any) => {
console.log(...args)
addLog('log', ...args)
},
warn: (...args: any) => {
console.warn(...args)
addLog('warn', ...args)
},
error: (...args: any) => {
console.error(...args)
addLog('error', ...args)
},
exportLogs: (): Array<{ type: string, date: Date, args: Array<any> }> =>
logs.map(({ type, date, args }) => ({
type,
date,
args: args.map(makeSerializableLog),
})),
}

2
src/reducers/settings.js

@ -5,8 +5,8 @@ import {
findCurrencyByTicker,
getCryptoCurrencyById,
getFiatCurrencyByTicker,
listCryptoCurrencies,
} from '@ledgerhq/live-common/lib/helpers/currencies'
import { listCryptoCurrencies } from 'config/cryptocurrencies'
import languages from 'config/languages'
import { createSelector } from 'reselect'
import type { InputSelector as Selector } from 'reselect'

2
src/stories/currencies.stories.js

@ -2,7 +2,7 @@
import React, { Fragment } from 'react'
import { storiesOf } from '@storybook/react'
import { listCryptoCurrencies } from '@ledgerhq/live-common/lib/helpers/currencies'
import { listCryptoCurrencies } from 'config/cryptocurrencies'
import { getCryptoCurrencyIcon } from '@ledgerhq/live-common/lib/react'
import type { CryptoCurrency } from '@ledgerhq/live-common/lib/types'

4
static/i18n/en/settings.yml

@ -70,3 +70,7 @@ softResetModal:
title: Clean application cache
subTitle: Are you sure houston?
desc: Lorem ipsum dolor sit amet
exportLogs:
title: Export Logs
desc: Export Logs
btn: Export Logs

Loading…
Cancel
Save