diff --git a/src/helpers/db.js b/src/helpers/db.js index 19968185..fb1dfa65 100644 --- a/src/helpers/db.js +++ b/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 = {} diff --git a/src/helpers/user.js b/src/helpers/user.js new file mode 100644 index 00000000..15a8b7cb --- /dev/null +++ b/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 +} diff --git a/src/init-sentry.js b/src/init-sentry.js deleted file mode 100644 index 1af0c815..00000000 --- a/src/init-sentry.js +++ /dev/null @@ -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() -} diff --git a/src/internals/index.js b/src/internals/index.js index 94601aaa..f2879c08 100644 --- a/src/internals/index.js +++ b/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 } }) diff --git a/src/main/bridge.js b/src/main/bridge.js index ff9af1ab..bd118629 100644 --- a/src/main/bridge.js +++ b/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 = { diff --git a/src/main/index.js b/src/main/index.js index c0d18749..65952b72 100644 --- a/src/main/index.js +++ b/src/main/index.js @@ -4,7 +4,6 @@ process.setMaxListeners(0) require('../env') require('../globals') -require('../init-sentry') require('./app') setImmediate(() => require('./bridge')) diff --git a/src/middlewares/sentry.js b/src/middlewares/sentry.js new file mode 100644 index 00000000..212764c6 --- /dev/null +++ b/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 }) + } +} diff --git a/src/reducers/settings.js b/src/reducers/settings.js index fbe330f6..fc1aa1ca 100644 --- a/src/reducers/settings.js +++ b/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) diff --git a/src/renderer/createStore.js b/src/renderer/createStore.js index 6e14fa5e..75f46711 100644 --- a/src/renderer/createStore.js +++ b/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) } diff --git a/src/renderer/index.js b/src/renderer/index.js index d65ad8bf..5164fc17 100644 --- a/src/renderer/index.js +++ b/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') diff --git a/src/renderer/init.js b/src/renderer/init.js index 6ef76a10..c9e77136 100644 --- a/src/renderer/init.js +++ b/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' @@ -50,6 +51,7 @@ async function init() { const state = store.getState() const language = getLanguage(state) const locked = isLocked(state) + sentry(() => sentryLogsBooleanSelector(store.getState())) moment.locale(language) diff --git a/src/sentry/browser.js b/src/sentry/browser.js new file mode 100644 index 00000000..ac5fc55c --- /dev/null +++ b/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) +} diff --git a/src/sentry/install.js b/src/sentry/install.js new file mode 100644 index 00000000..d556339f --- /dev/null +++ b/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() +} diff --git a/src/sentry/node.js b/src/sentry/node.js new file mode 100644 index 00000000..a617487b --- /dev/null +++ b/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) +} diff --git a/webpack/plugins.js b/webpack/plugins.js index af1b4b64..e1bfb18d 100644 --- a/webpack/plugins.js +++ b/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__), }), ]