diff --git a/.eslintrc b/.eslintrc index ac62de16..60aa4172 100644 --- a/.eslintrc +++ b/.eslintrc @@ -43,7 +43,7 @@ "no-return-assign": 0, "no-shadow": 0, "no-underscore-dangle": 0, - "no-console": [1, { "allow": [ "warn", "error" ] }], + "no-console": 2, "no-unused-vars": ["error", { "argsIgnorePattern": "^_", "vars": "all", "args": "after-used", "ignoreRestSiblings": true }], "no-use-before-define": 0, "no-restricted-syntax": 0, diff --git a/src/api/Ledger.js b/src/api/Ledger.js index bbf8eaed..c68aa590 100644 --- a/src/api/Ledger.js +++ b/src/api/Ledger.js @@ -1,5 +1,6 @@ // @flow import type { Currency } from '@ledgerhq/live-common/lib/types' +import logger from 'logger' const BASE_URL = process.env.LEDGER_REST_API_BASE || 'https://api.ledgerwallet.com/' @@ -20,13 +21,13 @@ export const userFriendlyError = (p: Promise): Promise => throw new Error(message) } } catch (e) { - console.log(e) + logger.warn("can't parse server result", e) } } throw new Error(msg) } } - console.log('Ledger API: HTTP status', error.response.status, 'data: ', error.response.data) + logger.log('Ledger API: HTTP status', error.response.status, 'data: ', error.response.data) throw new Error('A problem occurred with Ledger Servers. Please try again later.') } else if (error.request) { // The request was made but no response was received diff --git a/src/api/Ripple.js b/src/api/Ripple.js index 8b9686fc..52a7fe2f 100644 --- a/src/api/Ripple.js +++ b/src/api/Ripple.js @@ -1,4 +1,5 @@ // @flow +import logger from 'logger' import { RippleAPI } from 'ripple-lib' import type { CryptoCurrency } from '@ledgerhq/live-common/lib/types' import { @@ -18,7 +19,7 @@ export const apiForCurrency = (currency: CryptoCurrency) => { server: apiEndpoint[currency.id], }) api.on('error', (errorCode, errorMessage) => { - console.warn(`Ripple API error: ${errorCode}: ${errorMessage}`) + logger.warn(`Ripple API error: ${errorCode}: ${errorMessage}`) }) return api } @@ -33,7 +34,7 @@ export const parseAPICurrencyObject = ({ value: string, }) => { if (currency !== 'XRP') { - console.warn(`RippleJS: attempt to parse unknown currency ${currency}`) + logger.warn(`RippleJS: attempt to parse unknown currency ${currency}`) return 0 } return parseAPIValue(value) diff --git a/src/bridge/BridgeSyncContext.js b/src/bridge/BridgeSyncContext.js index 16b21d32..8dbe2685 100644 --- a/src/bridge/BridgeSyncContext.js +++ b/src/bridge/BridgeSyncContext.js @@ -1,4 +1,5 @@ // @flow +import logger from 'logger' import React, { Component } from 'react' import { connect } from 'react-redux' import type { Account } from '@ledgerhq/live-common/lib/types' @@ -150,7 +151,7 @@ class Provider extends Component { try { await this.api.syncAll() } catch (e) { - console.error('sync issues', e) + logger.error('sync issues', e) } setTimeout(syncLoop, 10 * 1000) } @@ -170,6 +171,9 @@ class Provider extends Component { } } -export const BridgeSyncProvider = connect(mapStateToProps, actions)(Provider) +export const BridgeSyncProvider = connect( + mapStateToProps, + actions, +)(Provider) export const BridgeSyncConsumer = BridgeSyncContext.Consumer diff --git a/src/bridge/LibcoreBridge.js b/src/bridge/LibcoreBridge.js index 960ef946..d056b640 100644 --- a/src/bridge/LibcoreBridge.js +++ b/src/bridge/LibcoreBridge.js @@ -1,4 +1,5 @@ // @flow +import logger from 'logger' import React from 'react' import { map } from 'rxjs/operators' import type { Account } from '@ledgerhq/live-common/lib/types' @@ -93,7 +94,7 @@ const LibcoreBridge: WalletBridge = { })() return { unsubscribe() { - console.warn('LibcoreBridge: unsub sync not implemented') + logger.warn('LibcoreBridge: unsub sync not implemented') }, } }, diff --git a/src/bridge/makeMockBridge.js b/src/bridge/makeMockBridge.js index eb41396d..8a30ec7a 100644 --- a/src/bridge/makeMockBridge.js +++ b/src/bridge/makeMockBridge.js @@ -1,4 +1,5 @@ // @flow +import logger from 'logger' import { genAccount, genAddingOperationsInAccount, @@ -45,7 +46,7 @@ function makeMockBridge(opts?: Opts): WalletBridge<*> { const accountId = initialAccount.id if (syncTimeouts[accountId]) { // this is just for tests. we'll assume impl don't need to handle race condition on this function. - console.warn('synchronize was called multiple pending time for same accounts!!!') + logger.warn('synchronize was called multiple pending time for same accounts!!!') } syncTimeouts[accountId] = setTimeout(() => { if (Math.random() < syncSuccessRate) { diff --git a/src/commands/isCurrencyAppOpened.js b/src/commands/isCurrencyAppOpened.js index baeae613..325bde88 100644 --- a/src/commands/isCurrencyAppOpened.js +++ b/src/commands/isCurrencyAppOpened.js @@ -43,7 +43,6 @@ const cmd: Command = createCommand( // in case of ETH / XRP, the address derivation is enough return true } catch (e) { - console.log(e) // if anything failed, it does not pass return false } diff --git a/src/commands/listenDevices.js b/src/commands/listenDevices.js index 529250fb..afef5057 100644 --- a/src/commands/listenDevices.js +++ b/src/commands/listenDevices.js @@ -1,5 +1,6 @@ // @flow +import logger from 'logger' import { createCommand } from 'helpers/ipc' import { Observable } from 'rxjs' import CommNodeHid from '@ledgerhq/hw-transport-node-hid' @@ -16,7 +17,7 @@ const cmd = createCommand('listenDevices', () => case 'add': { const pendingRemove = pendingRemovePerPath[e.descriptor] if (pendingRemove) { - console.warn(`Skipping remove/add usb event for ${e.descriptor}`) + logger.warn(`Skipping remove/add usb event for ${e.descriptor}`) // there where a recent "remove" event, we don't emit add because we didn't emit "remove" yet. clearTimeout(pendingRemove) delete pendingRemovePerPath[e.descriptor] diff --git a/src/components/DashboardPage/AccountsOrder.js b/src/components/DashboardPage/AccountsOrder.js index a3c7831a..a28f7063 100644 --- a/src/components/DashboardPage/AccountsOrder.js +++ b/src/components/DashboardPage/AccountsOrder.js @@ -1,5 +1,6 @@ // @flow +import logger from 'logger' import React, { Component } from 'react' import styled from 'styled-components' import { compose } from 'redux' @@ -63,7 +64,7 @@ function sortAccounts(accounts: Account[], orderAccounts: string, props: Props) } return ids } - console.warn(`sortAccounts not implemented for ${orderAccounts}`) + logger.warn(`sortAccounts not implemented for ${orderAccounts}`) return null } @@ -220,4 +221,10 @@ class AccountsOrder extends Component { } } -export default compose(connect(mapStateToProps, mapDispatchToProps), translate())(AccountsOrder) +export default compose( + connect( + mapStateToProps, + mapDispatchToProps, + ), + translate(), +)(AccountsOrder) diff --git a/src/components/EnsureDeviceApp/index.js b/src/components/EnsureDeviceApp/index.js index 36faaafe..4fed6072 100644 --- a/src/components/EnsureDeviceApp/index.js +++ b/src/components/EnsureDeviceApp/index.js @@ -1,6 +1,7 @@ // @flow import { PureComponent } from 'react' import { connect } from 'react-redux' +import logger from 'logger' import type { Account, CryptoCurrency } from '@ledgerhq/live-common/lib/types' import type { Device } from 'types/common' @@ -125,7 +126,7 @@ class EnsureDeviceApp extends PureComponent { .toPromise() const { freshAddress } = account if (account && freshAddress !== address) { - console.warn({ freshAddress, address }) + logger.warn({ freshAddress, address }) throw new Error('Account address is different than device address') } } else if (currency) { diff --git a/src/components/GenuineCheckModal/index.js b/src/components/GenuineCheckModal/index.js index b80fe0d9..8ede689b 100644 --- a/src/components/GenuineCheckModal/index.js +++ b/src/components/GenuineCheckModal/index.js @@ -1,5 +1,6 @@ // @flow +import logger from 'logger' import React, { PureComponent } from 'react' import { compose } from 'redux' import { connect } from 'react-redux' @@ -42,7 +43,7 @@ class GenuineCheck extends PureComponent { withGenuineCheck onGenuineCheck={onGenuineCheck} onStatusChange={status => { - console.log(`status changed to ${status}`) + logger.log(`status changed to ${status}`) }} render={({ appStatus, genuineCheckStatus, deviceSelected, errorMessage }) => ( { } } -export default compose(connect(mapStateToProps), translate())(GenuineCheck) +export default compose( + connect(mapStateToProps), + translate(), +)(GenuineCheck) diff --git a/src/components/ManagerPage/FirmwareUpdate.js b/src/components/ManagerPage/FirmwareUpdate.js index df2c619a..772dfc4c 100644 --- a/src/components/ManagerPage/FirmwareUpdate.js +++ b/src/components/ManagerPage/FirmwareUpdate.js @@ -1,5 +1,6 @@ // @flow +import logger from 'logger' import React, { PureComponent } from 'react' import isEqual from 'lodash/isEqual' import isEmpty from 'lodash/isEmpty' @@ -82,7 +83,7 @@ class FirmwareUpdate extends PureComponent { this.fetchLatestFirmware() } } catch (err) { - console.log(err) + logger.log(err) } } diff --git a/src/components/QRCodeCameraPickerCanvas.js b/src/components/QRCodeCameraPickerCanvas.js index 13c2c27d..271f2c4e 100644 --- a/src/components/QRCodeCameraPickerCanvas.js +++ b/src/components/QRCodeCameraPickerCanvas.js @@ -2,6 +2,7 @@ import React, { Component } from 'react' import QrCode from 'qrcode-reader' +import logger from 'logger' export default class QRCodeCameraPickerCanvas extends Component< { @@ -88,7 +89,7 @@ export default class QRCodeCameraPickerCanvas extends Component< try { video.play() } catch (e) { - console.error(e) + logger.error(e) } let lastCheck = 0 let raf diff --git a/src/components/SelectExchange.js b/src/components/SelectExchange.js index 870413ba..e8027e30 100644 --- a/src/components/SelectExchange.js +++ b/src/components/SelectExchange.js @@ -2,6 +2,7 @@ import React, { Component } from 'react' import type { Currency } from '@ledgerhq/live-common/lib/types' import type { Exchange } from '@ledgerhq/live-common/lib/countervalues/types' +import logger from 'logger' import Select from 'components/base/Select' import Spinner from 'components/base/Spinner' @@ -67,7 +68,7 @@ class ExchangeSelect extends Component< this.setState({ exchanges }) } } catch (error) { - console.error(error) + logger.error(error) if (!this._unmounted && this._loadId === _loadId) { this.setState({ error }) } diff --git a/src/components/base/SideBar/SideBarListItem.js b/src/components/base/SideBar/SideBarListItem.js index 56d09ddf..85e6e91e 100644 --- a/src/components/base/SideBar/SideBarListItem.js +++ b/src/components/base/SideBar/SideBarListItem.js @@ -24,11 +24,17 @@ export type Props = { class SideBarListItem extends PureComponent { render() { const { - item: { icon: Icon, label, desc, iconActiveColor, hasNotif, onClick }, + item: { icon: Icon, label, desc, iconActiveColor, hasNotif, onClick, value }, isActive, } = this.props return ( - + {!!Icon && } {typeof label === 'function' ? ( diff --git a/src/helpers/countervalues.js b/src/helpers/countervalues.js index 4859ad79..41e509a9 100644 --- a/src/helpers/countervalues.js +++ b/src/helpers/countervalues.js @@ -10,6 +10,7 @@ import { currencySettingsSelector, intermediaryCurrency, } from 'reducers/settings' +import logger from 'logger' const pairsSelector = createSelector( currenciesSelector, @@ -49,7 +50,7 @@ const addExtraPollingHooks = (schedulePoll, cancelPoll) => { } const CounterValues = createCounterValues({ - log: (...args) => console.log('CounterValues:', ...args), // eslint-disable-line no-console + log: (...args) => logger.log('CounterValues:', ...args), getAPIBaseURL: () => 'https://ledger-countervalue-poc.herokuapp.com', storeSelector: state => state.countervalues, pairsSelector, diff --git a/src/helpers/ipc.js b/src/helpers/ipc.js index 25053ef5..38abb40b 100644 --- a/src/helpers/ipc.js +++ b/src/helpers/ipc.js @@ -1,4 +1,5 @@ // @flow +import logger from 'logger' import { Observable } from 'rxjs' import uuidv4 from 'uuid/v4' @@ -48,20 +49,20 @@ function ipcRendererSendCommand(id: string, data: In): Observable { if (requestId !== msg.requestId) return switch (msg.type) { case 'NEXT': - console.log(`● CMD ${id}`, msg.data) + logger.log(`● CMD ${id}`, msg.data) if (msg.data) { o.next(msg.data) } break case 'COMPLETE': - console.log(`✔ CMD ${id} finished in ${(Date.now() - startTime).toFixed(0)}ms`) + logger.log(`✔ CMD ${id} finished in ${(Date.now() - startTime).toFixed(0)}ms`) o.complete() ipcRenderer.removeListener('command-event', handleCommandEvent) break case 'ERROR': - console.warn(`✖ CMD ${id} error`, msg.data) + logger.warn(`✖ CMD ${id} error`, msg.data) o.error(msg.data) ipcRenderer.removeListener('command-event', handleCommandEvent) break @@ -74,7 +75,7 @@ function ipcRendererSendCommand(id: string, data: In): Observable { ipcRenderer.send('command', { id, data, requestId }) - console.log(`CMD ${id}.send(`, data, ')') + logger.log(`CMD ${id}.send(`, data, ')') return unsubscribe }) diff --git a/src/helpers/libcore.js b/src/helpers/libcore.js index bdb7ee91..38d3c64a 100644 --- a/src/helpers/libcore.js +++ b/src/helpers/libcore.js @@ -1,5 +1,6 @@ // @flow +import logger from 'logger' import Btc from '@ledgerhq/hw-app-btc' import { withDevice } from 'helpers/deviceAccess' import { getCryptoCurrencyById } from '@ledgerhq/live-common/lib/helpers/currencies' @@ -216,12 +217,10 @@ async function buildAccountRaw({ }: { njsAccount: NJSAccount, isSegwit: boolean, - // $FlowFixMe wallet: NJSWallet, currencyId: string, accountIndex: number, core: *, - // $FlowFixMe ops: NJSOperation[], }): Promise { const njsBalance = await njsAccount.getBalance() @@ -373,7 +372,7 @@ export async function syncAccount({ syncedRawAccount.balance = njsBalance.toLong() - console.log(`Synced account [${syncedRawAccount.name}]: ${syncedRawAccount.balance}`) + logger.log(`Synced account [${syncedRawAccount.name}]: ${syncedRawAccount.balance}`) return syncedRawAccount } diff --git a/src/helpers/promise.js b/src/helpers/promise.js index 6cebf415..a80ba5af 100644 --- a/src/helpers/promise.js +++ b/src/helpers/promise.js @@ -1,5 +1,5 @@ // @flow - +import logger from 'logger' // small utilities for Promises export const delay = (ms: number): Promise => new Promise(f => setTimeout(f, ms)) @@ -21,7 +21,7 @@ export function retry(f: () => Promise, options?: $Shape) } // In case of failure, wait the interval, retry the action return result.catch(e => { - console.warn('Promise#retry', e) + logger.warn('Promise#retry', e) return delay(interval).then(() => rec(remainingTry - 1, interval * intervalMultiplicator)) }) } diff --git a/src/helpers/withLibcore.js b/src/helpers/withLibcore.js index babacb3e..37a9c49d 100644 --- a/src/helpers/withLibcore.js +++ b/src/helpers/withLibcore.js @@ -1,6 +1,7 @@ // @flow import invariant from 'invariant' +import logger from 'logger' const core = require('@ledgerhq/ledger-core') @@ -23,7 +24,7 @@ export default function withLibcore(job: Job): Promise { const p = queue.then(() => job(core, walletPool)) queue = p.catch(e => { - console.warn(`withLibCore: Error in job`, e) + logger.warn(`withLibCore: Error in job`, e) }) return p diff --git a/src/internals/index.js b/src/internals/index.js index 8cfb9226..4c53b555 100644 --- a/src/internals/index.js +++ b/src/internals/index.js @@ -1,5 +1,6 @@ // @flow import commands from 'commands' +import logger from 'logger' require('../env') require('../init-sentry') @@ -13,7 +14,7 @@ process.on('message', m => { const { data, requestId, id } = m.command const cmd = commands.find(cmd => cmd.id === id) if (!cmd) { - console.warn(`command ${id} not found`) + logger.warn(`command ${id} not found`) return } subscriptions[requestId] = cmd.impl(data).subscribe({ @@ -32,7 +33,7 @@ process.on('message', m => { }) }, error: error => { - console.warn('Command error:', error) + logger.warn('Command error:', error) delete subscriptions[requestId] process.send({ type: 'ERROR', @@ -55,4 +56,4 @@ process.on('message', m => { } }) -console.log('Internal process is up!') +logger.log('Internal process is up!') diff --git a/src/logger.js b/src/logger.js new file mode 100644 index 00000000..1e68f43c --- /dev/null +++ b/src/logger.js @@ -0,0 +1,45 @@ +// @flow +/* eslint-disable no-console */ + +/** + * IDEA: + * logger is an alternative to use for console.log that will be used for many purposes: + * - provide useful data for debugging during dev (idea is to have opt-in env var) + * - enabled in prod to provide useful data to debug when sending to Sentry + * - for analytics in the future + */ + +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) { + console.log(label, roleData) + } else { + console.log(label) + } + } + }, + + // tracks Redux actions (NB not all actions are serializable) + + onReduxAction: (action: Object) => { + if (!__DEV__ || process.env.DEBUG_ACTION) { + console.log(`⚛️ ${action.type}`, action) + } + }, + + // General functions in case the hooks don't apply + + log: (...args: any) => { + console.log(...args) + }, + warn: (...args: any) => { + console.warn(...args) + }, + error: (...args: any) => { + console.error(...args) + }, +} diff --git a/src/main/bridge.js b/src/main/bridge.js index e74b88c5..54acd85b 100644 --- a/src/main/bridge.js +++ b/src/main/bridge.js @@ -6,6 +6,7 @@ import { fork } from 'child_process' import { ipcMain, app } from 'electron' import { ipcMainListenReceiveCommands } from 'helpers/ipc' import path from 'path' +import logger from 'logger' import setupAutoUpdater, { quitAndInstall } from './autoUpdate' @@ -16,7 +17,7 @@ let internalProcess const killInternalProcess = () => { if (internalProcess) { - console.log('killing internal process...') + logger.log('killing internal process...') internalProcess.kill('SIGINT') internalProcess = null } @@ -25,12 +26,12 @@ const killInternalProcess = () => { const forkBundlePath = path.resolve(__dirname, `${__DEV__ ? '../../' : './'}dist/internals`) const bootInternalProcess = () => { - console.log('booting internal process...') + logger.log('booting internal process...') internalProcess = fork(forkBundlePath, { env: { ...process.env, LEDGER_LIVE_SQLITE_PATH }, }) internalProcess.on('exit', code => { - console.log(`Internal process ended with code ${code}`) + logger.warn(`Internal process ended with code ${code}`) internalProcess = null }) } diff --git a/src/middlewares/logger.js b/src/middlewares/logger.js new file mode 100644 index 00000000..5cbcc171 --- /dev/null +++ b/src/middlewares/logger.js @@ -0,0 +1,8 @@ +// @flow + +import logger from 'logger' + +export default () => (next: *) => (action: *) => { + logger.onReduxAction(action) + return next(action) +} diff --git a/src/reducers/accounts.js b/src/reducers/accounts.js index b3a9ac8e..99cdee31 100644 --- a/src/reducers/accounts.js +++ b/src/reducers/accounts.js @@ -3,6 +3,7 @@ import { createSelector } from 'reselect' import { handleActions } from 'redux-actions' import { createAccountModel } from '@ledgerhq/live-common/lib/models/account' +import logger from 'logger' import type { Account, AccountRaw } from '@ledgerhq/live-common/lib/types' @@ -25,7 +26,7 @@ const handlers: Object = { { payload: account }: { payload: Account }, ): AccountsState => { if (state.some(a => a.id === account.id)) { - console.warn('ADD_ACCOUNT attempt for an account that already exists!', account.id) + logger.warn('ADD_ACCOUNT attempt for an account that already exists!', account.id) return state } return [...state, account] diff --git a/src/renderer/createStore.js b/src/renderer/createStore.js index bd960576..6e14fa5e 100644 --- a/src/renderer/createStore.js +++ b/src/renderer/createStore.js @@ -5,6 +5,7 @@ import { routerMiddleware } from 'react-router-redux' import thunk from 'redux-thunk' import createHistory from 'history/createHashHistory' import type { HashHistory } from 'history' +import logger from 'middlewares/logger' import reducers from 'reducers' @@ -19,7 +20,7 @@ export default ({ state, history, dbMiddleware }: Props) => { if (!history) { history = createHistory() } - const middlewares = [routerMiddleware(history), thunk] + const middlewares = [routerMiddleware(history), thunk, logger] if (dbMiddleware) { middlewares.push(dbMiddleware) } diff --git a/src/renderer/events.js b/src/renderer/events.js index d5987f7e..22f68282 100644 --- a/src/renderer/events.js +++ b/src/renderer/events.js @@ -9,6 +9,7 @@ // events should all appear in the promise result / observer msgs as soon as they have this requestId import 'commands' +import logger from 'logger' import { ipcRenderer } from 'electron' import debug from 'debug' @@ -64,12 +65,12 @@ export default ({ store }: { store: Object, locked: boolean }) => { } }, error => { - console.warn('listenDevices error', error) + logger.warn('listenDevices error', error) store.dispatch(resetDevices()) syncDevices() }, () => { - console.warn('listenDevices ended unexpectedly. restarting') + logger.warn('listenDevices ended unexpectedly. restarting') store.dispatch(resetDevices()) syncDevices() }, diff --git a/src/renderer/init.js b/src/renderer/init.js index 1702884f..2dc57f6b 100644 --- a/src/renderer/init.js +++ b/src/renderer/init.js @@ -1,5 +1,6 @@ // @flow +import logger from 'logger' import React from 'react' import { remote } from 'electron' import { render } from 'react-dom' @@ -73,12 +74,24 @@ async function init() { events({ store, locked }) const libcoreVersion = await libcoreGetVersion.send().toPromise() - console.log('libcore', libcoreVersion) + logger.log('libcore', libcoreVersion) + + // DOM elements can have a data-role that identify the UI entity + // and that allow us to track interactions with this. + window.addEventListener('click', ({ target }) => { + const { dataset } = target + if (dataset) { + const { role, roledata } = dataset + if (role) { + logger.onClickElement(role, roledata) + } + } + }) } } init().catch(e => { // for now we make the app crash instead of pending forever. later we can render the error OR try to recover, but probably this is unrecoverable cases. - console.error(e) + logger.error(e) process.exit(1) })