From 62f4f475997553da89969f74bd8be770ebe28e28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Renaudeau?= Date: Sun, 24 Jun 2018 13:19:27 +0200 Subject: [PATCH] De/serialize process related errors so they get correctly reported to sentry also should now have the stack --- src/api/Ethereum.js | 2 +- src/api/Fees.js | 2 +- src/api/network.js | 2 +- src/commands/libcoreGetFees.js | 2 +- src/commands/libcoreHardReset.js | 2 +- src/components/EnsureDeviceApp/index.js | 2 +- src/components/RequestAmount/index.js | 2 +- src/components/modals/Send/index.js | 2 +- src/helpers/apps/installApp.js | 2 +- src/helpers/apps/uninstallApp.js | 2 +- src/helpers/createCustomErrorClass.js | 14 ----- src/helpers/devices/getNextMCU.js | 2 +- src/helpers/errors.js | 79 +++++++++++++++++++++++- src/helpers/getAddressForCurrency/btc.js | 2 +- src/helpers/ipc.js | 7 ++- src/helpers/libcore.js | 2 +- src/helpers/socket.js | 2 +- src/internals/index.js | 7 +-- 18 files changed, 99 insertions(+), 36 deletions(-) delete mode 100644 src/helpers/createCustomErrorClass.js diff --git a/src/api/Ethereum.js b/src/api/Ethereum.js index 216c65a5..c6a6efcb 100644 --- a/src/api/Ethereum.js +++ b/src/api/Ethereum.js @@ -1,6 +1,6 @@ // @flow import type { CryptoCurrency } from '@ledgerhq/live-common/lib/types' -import createCustomErrorClass from 'helpers/createCustomErrorClass' +import { createCustomErrorClass } from 'helpers/errors' import network from './network' import { blockchainBaseURL } from './Ledger' diff --git a/src/api/Fees.js b/src/api/Fees.js index 7967f9a7..19d32e77 100644 --- a/src/api/Fees.js +++ b/src/api/Fees.js @@ -2,7 +2,7 @@ import invariant from 'invariant' import LRU from 'lru-cache' import type { Currency } from '@ledgerhq/live-common/lib/types' -import createCustomErrorClass from 'helpers/createCustomErrorClass' +import { createCustomErrorClass } from 'helpers/errors' import { blockchainBaseURL } from './Ledger' import network from './network' diff --git a/src/api/network.js b/src/api/network.js index cc0bf83d..f4311539 100644 --- a/src/api/network.js +++ b/src/api/network.js @@ -3,7 +3,7 @@ import axios from 'axios' import { GET_CALLS_RETRY, GET_CALLS_TIMEOUT } from 'config/constants' import { retry } from 'helpers/promise' import logger from 'logger' -import createCustomErrorClass from 'helpers/createCustomErrorClass' +import { createCustomErrorClass } from 'helpers/errors' export const LedgerAPIErrorWithMessage = createCustomErrorClass('LedgerAPIErrorWithMessage') export const LedgerAPIError = createCustomErrorClass('LedgerAPIError') diff --git a/src/commands/libcoreGetFees.js b/src/commands/libcoreGetFees.js index 9aea3046..fb0dc690 100644 --- a/src/commands/libcoreGetFees.js +++ b/src/commands/libcoreGetFees.js @@ -5,7 +5,7 @@ import withLibcore from 'helpers/withLibcore' import { createCommand, Command } from 'helpers/ipc' import * as accountIdHelper from 'helpers/accountId' import { isValidAddress } from 'helpers/libcore' -import createCustomErrorClass from 'helpers/createCustomErrorClass' +import { createCustomErrorClass } from 'helpers/errors' const InvalidAddress = createCustomErrorClass('InvalidAddress') diff --git a/src/commands/libcoreHardReset.js b/src/commands/libcoreHardReset.js index e8cad9b4..6b52a6a9 100644 --- a/src/commands/libcoreHardReset.js +++ b/src/commands/libcoreHardReset.js @@ -3,7 +3,7 @@ import { createCommand } from 'helpers/ipc' import { fromPromise } from 'rxjs/observable/fromPromise' import withLibcore from 'helpers/withLibcore' -import createCustomErrorClass from 'helpers/createCustomErrorClass' +import { createCustomErrorClass } from 'helpers/errors' const HardResetFail = createCustomErrorClass('HardResetFail') diff --git a/src/components/EnsureDeviceApp/index.js b/src/components/EnsureDeviceApp/index.js index 964f3ad5..1f5a8730 100644 --- a/src/components/EnsureDeviceApp/index.js +++ b/src/components/EnsureDeviceApp/index.js @@ -13,7 +13,7 @@ import type { State as StoreState } from 'reducers/index' import getAddress from 'commands/getAddress' import { standardDerivation } from 'helpers/derivations' import isDashboardOpen from 'commands/isDashboardOpen' -import createCustomErrorClass from 'helpers/createCustomErrorClass' +import { createCustomErrorClass } from 'helpers/errors' import { CHECK_APP_INTERVAL_WHEN_VALID, CHECK_APP_INTERVAL_WHEN_INVALID } from 'config/constants' diff --git a/src/components/RequestAmount/index.js b/src/components/RequestAmount/index.js index 139710cc..792a5309 100644 --- a/src/components/RequestAmount/index.js +++ b/src/components/RequestAmount/index.js @@ -21,7 +21,7 @@ import InputCurrency from 'components/base/InputCurrency' import Button from 'components/base/Button' import Box from 'components/base/Box' import type { State } from 'reducers' -import createCustomErrorClass from 'helpers/createCustomErrorClass' +import { createCustomErrorClass } from 'helpers/errors' const NotEnoughBalance = createCustomErrorClass('NotEnoughBalance') diff --git a/src/components/modals/Send/index.js b/src/components/modals/Send/index.js index 029e8b7b..85bba72b 100644 --- a/src/components/modals/Send/index.js +++ b/src/components/modals/Send/index.js @@ -15,7 +15,7 @@ import { getBridgeForCurrency } from 'bridge' import { accountsSelector } from 'reducers/accounts' import { updateAccountWithUpdater } from 'actions/accounts' -import createCustomErrorClass from 'helpers/createCustomErrorClass' +import { createCustomErrorClass } from 'helpers/errors' import { MODAL_SEND } from 'config/constants' import Modal, { ModalBody, ModalContent, ModalTitle } from 'components/base/Modal' diff --git a/src/helpers/apps/installApp.js b/src/helpers/apps/installApp.js index 00dfb4fd..e3d134ca 100644 --- a/src/helpers/apps/installApp.js +++ b/src/helpers/apps/installApp.js @@ -7,7 +7,7 @@ import { createDeviceSocket } from 'helpers/socket' import type { LedgerScriptParams } from 'helpers/common' -import createCustomErrorClass from '../createCustomErrorClass' +import { createCustomErrorClass } from '../errors' const ManagerUnexpectedError = createCustomErrorClass('ManagerUnexpected') const ManagerNotEnoughSpaceError = createCustomErrorClass('ManagerNotEnoughSpace') diff --git a/src/helpers/apps/uninstallApp.js b/src/helpers/apps/uninstallApp.js index 1611a1b4..41aee470 100644 --- a/src/helpers/apps/uninstallApp.js +++ b/src/helpers/apps/uninstallApp.js @@ -6,7 +6,7 @@ import { BASE_SOCKET_URL_SECURE } from 'config/constants' import { createDeviceSocket } from 'helpers/socket' import type { LedgerScriptParams } from 'helpers/common' -import createCustomErrorClass from '../createCustomErrorClass' +import { createCustomErrorClass } from '../errors' const ManagerUnexpectedError = createCustomErrorClass('ManagerUnexpectedError') const ManagerDeviceLockedError = createCustomErrorClass('ManagerDeviceLocked') diff --git a/src/helpers/createCustomErrorClass.js b/src/helpers/createCustomErrorClass.js deleted file mode 100644 index 698dc61e..00000000 --- a/src/helpers/createCustomErrorClass.js +++ /dev/null @@ -1,14 +0,0 @@ -// @flow - -export default (name: string): Class => { - const C = function CustomError(message?: string, fields?: Object) { - this.name = name - this.message = message || name - this.stack = new Error().stack - Object.assign(this, fields) - } - // $FlowFixMe - C.prototype = new Error() - // $FlowFixMe we can't easily type a subset of Error for now... - return C -} diff --git a/src/helpers/devices/getNextMCU.js b/src/helpers/devices/getNextMCU.js index 9023d27b..a83daf5a 100644 --- a/src/helpers/devices/getNextMCU.js +++ b/src/helpers/devices/getNextMCU.js @@ -2,7 +2,7 @@ import network from 'api/network' import { GET_NEXT_MCU } from 'helpers/urls' -import createCustomErrorClass from 'helpers/createCustomErrorClass' +import { createCustomErrorClass } from 'helpers/errors' const LatestMCUInstalledError = createCustomErrorClass('LatestMCUInstalledError') diff --git a/src/helpers/errors.js b/src/helpers/errors.js index 9e7bd079..61e10c3d 100644 --- a/src/helpers/errors.js +++ b/src/helpers/errors.js @@ -1,3 +1,80 @@ // @flow +/* eslint-disable no-continue */ -export const formatError = (e: Error) => e.message +const errorClasses = {} + +export const createCustomErrorClass = (name: string): Class => { + const C = function CustomError(message?: string, fields?: Object) { + this.name = name + this.message = message || name + this.stack = new Error().stack + Object.assign(this, fields) + } + // $FlowFixMe + C.prototype = new Error() + + errorClasses[name] = C + // $FlowFixMe we can't easily type a subset of Error for now... + return C +} + +// inspired from https://github.com/programble/errio/blob/master/index.js +export const deserializeError = (object: mixed): Error => { + if (typeof object === 'object' && object) { + const constructor = (typeof object.name === 'string' && errorClasses[object.name]) || Error + const error = Object.create(constructor.prototype) + for (const prop in object) { + if (object.hasOwnProperty(prop)) { + error[prop] = object[prop] + } + } + if (!error.stack && Error.captureStackTrace) { + Error.captureStackTrace(error, deserializeError) + } + return error + } + return new Error(String(object)) +} + +// inspired from https://github.com/sindresorhus/serialize-error/blob/master/index.js +export const serializeError = (value: mixed) => { + if (!value) return value + if (typeof value === 'object') { + return destroyCircular(value, []) + } + if (typeof value === 'function') { + return `[Function: ${value.name || 'anonymous'}]` + } + return value +} + +// https://www.npmjs.com/package/destroy-circular +function destroyCircular(from: Object, seen) { + const to = {} + seen.push(from) + for (const key of Object.keys(from)) { + const value = from[key] + if (typeof value === 'function') { + continue + } + if (!value || typeof value !== 'object') { + to[key] = value + continue + } + if (seen.indexOf(from[key]) === -1) { + to[key] = destroyCircular(from[key], seen.slice(0)) + continue + } + to[key] = '[Circular]' + } + if (typeof from.name === 'string') { + to.name = from.name + } + if (typeof from.message === 'string') { + to.message = from.message + } + if (typeof from.stack === 'string') { + to.stack = from.stack + } + return to +} diff --git a/src/helpers/getAddressForCurrency/btc.js b/src/helpers/getAddressForCurrency/btc.js index 5c79f86e..b2dbdd26 100644 --- a/src/helpers/getAddressForCurrency/btc.js +++ b/src/helpers/getAddressForCurrency/btc.js @@ -4,7 +4,7 @@ import type { CryptoCurrency } from '@ledgerhq/live-common/lib/types' import Btc from '@ledgerhq/hw-app-btc' import type Transport from '@ledgerhq/hw-transport' import getBitcoinLikeInfo from '../devices/getBitcoinLikeInfo' -import createCustomErrorClass from '../createCustomErrorClass' +import { createCustomErrorClass } from '../errors' const BtcUnmatchedApp = createCustomErrorClass('BtcUnmatchedApp') diff --git a/src/helpers/ipc.js b/src/helpers/ipc.js index 26e3df5e..d506ca0f 100644 --- a/src/helpers/ipc.js +++ b/src/helpers/ipc.js @@ -2,6 +2,7 @@ import logger from 'logger' import { Observable } from 'rxjs' import uuidv4 from 'uuid/v4' +import { deserializeError } from './errors' export function createCommand(id: string, impl: In => Observable): Command { return new Command(id, impl) @@ -60,10 +61,12 @@ function ipcRendererSendCommand(id: string, data: In): Observable { ipcRenderer.removeListener('command-event', handleCommandEvent) break - case 'cmd.ERROR': - o.error(msg.data) + case 'cmd.ERROR': { + const error = deserializeError(msg.data) + o.error(error) ipcRenderer.removeListener('command-event', handleCommandEvent) break + } default: } diff --git a/src/helpers/libcore.js b/src/helpers/libcore.js index 3ab6f875..e42bf774 100644 --- a/src/helpers/libcore.js +++ b/src/helpers/libcore.js @@ -11,7 +11,7 @@ import type { NJSAccount, NJSOperation } from '@ledgerhq/ledger-core/src/ledgerc import { isSegwitAccount } from 'helpers/bip32' import * as accountIdHelper from 'helpers/accountId' -import createCustomErrorClass from './createCustomErrorClass' +import { createCustomErrorClass } from './errors' import { getAccountPlaceholderName, getNewAccountPlaceholderName } from './accountName' diff --git a/src/helpers/socket.js b/src/helpers/socket.js index 1204b042..35b30270 100644 --- a/src/helpers/socket.js +++ b/src/helpers/socket.js @@ -5,7 +5,7 @@ import logger from 'logger' import Websocket from 'ws' import type Transport from '@ledgerhq/hw-transport' import { Observable } from 'rxjs' -import createCustomErrorClass from './createCustomErrorClass' +import { createCustomErrorClass } from './errors' const WebsocketConnectionError = createCustomErrorClass('WebsocketConnectionError') const WebsocketConnectionFailed = createCustomErrorClass('WebsocketConnectionFailed') diff --git a/src/internals/index.js b/src/internals/index.js index c9d156bb..272a9153 100644 --- a/src/internals/index.js +++ b/src/internals/index.js @@ -5,6 +5,7 @@ import uuid from 'uuid/v4' import { setImplementation } from 'api/network' import sentry from 'sentry/node' import { DEBUG_NETWORK } from 'config/constants' +import { serializeError } from 'helpers/errors' require('../env') @@ -75,11 +76,7 @@ process.on('message', m => { process.send({ type: 'cmd.ERROR', requestId, - data: { - ...error, - name: error && error.name, - message: error && error.message, - }, + data: serializeError(error), }) }, })