diff --git a/README.md b/README.md index 9e5e3d51..6598c835 100644 --- a/README.md +++ b/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. -``` diff --git a/src/components/DeviceConnect/stories.js b/src/components/DeviceConnect/stories.js index f5b93dcb..f37eaf2e 100644 --- a/src/components/DeviceConnect/stories.js +++ b/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' diff --git a/src/components/ExportLogsBtn.js b/src/components/ExportLogsBtn.js new file mode 100644 index 00000000..71276c49 --- /dev/null +++ b/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 ( + + ) + } +} + +export default translate()(connect(mapStateToProps)(ExportLogsBtn)) diff --git a/src/components/SettingsPage/sections/Profile.js b/src/components/SettingsPage/sections/Profile.js index 1e97d802..27b859b3 100644 --- a/src/components/SettingsPage/sections/Profile.js +++ b/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 { {t('settings:profile.hardReset')} + + + { } componentDidCatch(error: Error) { + logger.error(error) this.setState({ error }) } @@ -83,9 +86,10 @@ ${error.stack} - + diff --git a/src/config/cryptocurrencies.js b/src/config/cryptocurrencies.js new file mode 100644 index 00000000..0b097368 --- /dev/null +++ b/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)), +) diff --git a/src/logger.js b/src/logger.js index 1e68f43c..eb1a2a80 100644 --- a/src/logger.js +++ b/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 }> => + logs.map(({ type, date, args }) => ({ + type, + date, + args: args.map(makeSerializableLog), + })), } diff --git a/src/reducers/settings.js b/src/reducers/settings.js index 76a14892..be751c6d 100644 --- a/src/reducers/settings.js +++ b/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' diff --git a/src/stories/currencies.stories.js b/src/stories/currencies.stories.js index c3b5803f..cd90b664 100644 --- a/src/stories/currencies.stories.js +++ b/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' diff --git a/static/i18n/en/settings.yml b/static/i18n/en/settings.yml index e8bd82af..2a48bb9d 100644 --- a/static/i18n/en/settings.yml +++ b/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