2021-02-04 19:15:23 +01:00
|
|
|
import * as Sentry from '@sentry/browser';
|
|
|
|
import { Dedupe, ExtraErrorData } from '@sentry/integrations';
|
2020-01-09 04:34:58 +01:00
|
|
|
|
2022-08-23 20:44:14 +02:00
|
|
|
import { FilterEvents } from './sentry-filter-events';
|
2021-02-04 19:15:23 +01:00
|
|
|
import extractEthjsErrorMessage from './extractEthjsErrorMessage';
|
2020-01-09 04:34:58 +01:00
|
|
|
|
2020-10-08 18:22:14 +02:00
|
|
|
/* eslint-disable prefer-destructuring */
|
|
|
|
// Destructuring breaks the inlining of the environment variables
|
2021-02-04 19:15:23 +01:00
|
|
|
const METAMASK_DEBUG = process.env.METAMASK_DEBUG;
|
|
|
|
const METAMASK_ENVIRONMENT = process.env.METAMASK_ENVIRONMENT;
|
2022-09-16 18:58:43 +02:00
|
|
|
const SENTRY_DSN_DEV =
|
|
|
|
process.env.SENTRY_DSN_DEV ||
|
|
|
|
'https://f59f3dd640d2429d9d0e2445a87ea8e1@sentry.io/273496';
|
2021-10-25 18:57:30 +02:00
|
|
|
const METAMASK_BUILD_TYPE = process.env.METAMASK_BUILD_TYPE;
|
2022-08-26 01:07:31 +02:00
|
|
|
const IN_TEST = process.env.IN_TEST;
|
2020-10-08 18:22:14 +02:00
|
|
|
/* eslint-enable prefer-destructuring */
|
2018-01-17 23:59:15 +01:00
|
|
|
|
2022-12-16 16:26:23 +01:00
|
|
|
export const ERROR_URL_ALLOWLIST = {
|
|
|
|
CRYPTOCOMPARE: 'cryptocompare.com',
|
|
|
|
COINGECKO: 'coingecko.com',
|
|
|
|
ETHERSCAN: 'etherscan.io',
|
|
|
|
CODEFI: 'codefi.network',
|
|
|
|
SEGMENT: 'segment.io',
|
|
|
|
};
|
|
|
|
|
2020-07-17 17:09:38 +02:00
|
|
|
// This describes the subset of Redux state attached to errors sent to Sentry
|
|
|
|
// These properties have some potential to be useful for debugging, and they do
|
|
|
|
// not contain any identifiable information.
|
|
|
|
export const SENTRY_STATE = {
|
|
|
|
gas: true,
|
|
|
|
history: true,
|
|
|
|
metamask: {
|
|
|
|
alertEnabledness: true,
|
|
|
|
completedOnboarding: true,
|
|
|
|
connectedStatusPopoverHasBeenShown: true,
|
|
|
|
conversionDate: true,
|
|
|
|
conversionRate: true,
|
|
|
|
currentBlockGasLimit: true,
|
|
|
|
currentCurrency: true,
|
|
|
|
currentLocale: true,
|
|
|
|
customNonceValue: true,
|
|
|
|
defaultHomeActiveTabName: true,
|
2023-02-20 18:13:12 +01:00
|
|
|
desktopEnabled: true,
|
2020-07-17 17:09:38 +02:00
|
|
|
featureFlags: true,
|
|
|
|
firstTimeFlowType: true,
|
|
|
|
forgottenPassword: true,
|
2021-03-19 22:54:30 +01:00
|
|
|
incomingTxLastFetchedBlockByChainId: true,
|
2020-07-17 17:09:38 +02:00
|
|
|
ipfsGateway: true,
|
|
|
|
isAccountMenuOpen: true,
|
|
|
|
isInitialized: true,
|
|
|
|
isUnlocked: true,
|
|
|
|
metaMetricsId: true,
|
|
|
|
nativeCurrency: true,
|
NetworkController: Split `network` into `networkId` and `networkStatus` (#17556)
The `network` store of the network controller crams two types of data
into one place. It roughly tracks whether we have enough information to
make requests to the network and whether the network is capable of
receiving requests, but it also stores the ID of the network (as
obtained via `net_version`).
Generally we shouldn't be using the network ID for anything, as it has
been completely replaced by chain ID, which all custom RPC endpoints
have been required to support for over a year now. However, as the
network ID is used in various places within the extension codebase,
removing it entirely would be a non-trivial effort. So, minimally, this
commit splits `network` into two stores: `networkId` and
`networkStatus`. But it also expands the concept of network status.
Previously, the network was in one of two states: "loading" and
"not-loading". But now it can be in one of four states:
- `available`: The network is able to receive and respond to requests.
- `unavailable`: The network is not able to receive and respond to
requests for unknown reasons.
- `blocked`: The network is actively blocking requests based on the
user's geolocation. (This is specific to Infura.)
- `unknown`: We don't know whether the network can receive and respond
to requests, either because we haven't checked or we tried to check
and were unsuccessful.
This commit also changes how the network status is determined —
specifically, how many requests are used to determine that status, when
they occur, and whether they are awaited. Previously, the network
controller would make 2 to 3 requests during the course of running
`lookupNetwork`.
* First, if it was an Infura network, it would make a request for
`eth_blockNumber` to determine whether Infura was blocking requests or
not, then emit an appropriate event. This operation was not awaited.
* Then, regardless of the network, it would fetch the network ID via
`net_version`. This operation was awaited.
* Finally, regardless of the network, it would fetch the latest block
via `eth_getBlockByNumber`, then use the result to determine whether
the network supported EIP-1559. This operation was awaited.
Now:
* One fewer request is made, specifically `eth_blockNumber`, as we don't
need to make an extra request to determine whether Infura is blocking
requests; we can reuse `eth_getBlockByNumber`;
* All requests are awaited, which makes `lookupNetwork` run fully
in-band instead of partially out-of-band; and
* Both requests for `net_version` and `eth_getBlockByNumber` are
performed in parallel to make `lookupNetwork` run slightly faster.
2023-03-31 00:49:12 +02:00
|
|
|
networkId: true,
|
|
|
|
networkStatus: true,
|
2020-07-17 17:09:38 +02:00
|
|
|
nextNonce: true,
|
|
|
|
participateInMetaMetrics: true,
|
|
|
|
preferences: true,
|
2023-05-02 17:53:20 +02:00
|
|
|
providerConfig: {
|
2020-07-17 17:09:38 +02:00
|
|
|
nickname: true,
|
|
|
|
ticker: true,
|
|
|
|
type: true,
|
|
|
|
},
|
|
|
|
seedPhraseBackedUp: true,
|
|
|
|
unapprovedDecryptMsgCount: true,
|
|
|
|
unapprovedEncryptionPublicKeyMsgCount: true,
|
|
|
|
unapprovedMsgCount: true,
|
|
|
|
unapprovedPersonalMsgCount: true,
|
|
|
|
unapprovedTypedMessagesCount: true,
|
|
|
|
useBlockie: true,
|
|
|
|
useNonceField: true,
|
|
|
|
usePhishDetect: true,
|
|
|
|
welcomeScreenSeen: true,
|
|
|
|
},
|
|
|
|
unconnectedAccount: true,
|
2021-02-04 19:15:23 +01:00
|
|
|
};
|
2020-07-17 17:09:38 +02:00
|
|
|
|
2020-11-03 00:41:28 +01:00
|
|
|
export default function setupSentry({ release, getState }) {
|
2022-04-04 21:14:32 +02:00
|
|
|
if (!release) {
|
|
|
|
throw new Error('Missing release');
|
2022-08-26 01:07:31 +02:00
|
|
|
} else if (METAMASK_DEBUG && !IN_TEST) {
|
|
|
|
/**
|
|
|
|
* Workaround until the following issue is resolved
|
|
|
|
* https://github.com/MetaMask/metamask-extension/issues/15691
|
|
|
|
* The IN_TEST condition allows the e2e tests to run with both
|
|
|
|
* yarn start:test and yarn build:test
|
|
|
|
*/
|
2021-02-04 19:15:23 +01:00
|
|
|
return undefined;
|
2022-01-03 14:59:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
const environment =
|
2023-04-25 16:32:51 +02:00
|
|
|
METAMASK_BUILD_TYPE === 'main'
|
2022-01-03 14:59:44 +01:00
|
|
|
? METAMASK_ENVIRONMENT
|
|
|
|
: `${METAMASK_ENVIRONMENT}-${METAMASK_BUILD_TYPE}`;
|
|
|
|
|
2022-04-04 21:14:32 +02:00
|
|
|
let sentryTarget;
|
2022-01-03 14:59:44 +01:00
|
|
|
if (METAMASK_ENVIRONMENT === 'production') {
|
2020-07-29 18:14:08 +02:00
|
|
|
if (!process.env.SENTRY_DSN) {
|
2020-11-03 00:41:28 +01:00
|
|
|
throw new Error(
|
|
|
|
`Missing SENTRY_DSN environment variable in production environment`,
|
2021-02-04 19:15:23 +01:00
|
|
|
);
|
2020-07-29 18:14:08 +02:00
|
|
|
}
|
2020-11-03 00:41:28 +01:00
|
|
|
console.log(
|
2022-01-03 14:59:44 +01:00
|
|
|
`Setting up Sentry Remote Error Reporting for '${environment}': SENTRY_DSN`,
|
2021-02-04 19:15:23 +01:00
|
|
|
);
|
|
|
|
sentryTarget = process.env.SENTRY_DSN;
|
2020-07-29 18:14:08 +02:00
|
|
|
} else {
|
2020-11-03 00:41:28 +01:00
|
|
|
console.log(
|
2022-01-03 14:59:44 +01:00
|
|
|
`Setting up Sentry Remote Error Reporting for '${environment}': SENTRY_DSN_DEV`,
|
2021-02-04 19:15:23 +01:00
|
|
|
);
|
|
|
|
sentryTarget = SENTRY_DSN_DEV;
|
2018-01-17 23:59:15 +01:00
|
|
|
}
|
|
|
|
|
2022-08-23 20:44:14 +02:00
|
|
|
/**
|
|
|
|
* A function that returns whether MetaMetrics is enabled. This should also
|
|
|
|
* return `false` if state has not yet been initialzed.
|
|
|
|
*
|
|
|
|
* @returns `true` if MetaMask's state has been initialized, and MetaMetrics
|
|
|
|
* is enabled, `false` otherwise.
|
|
|
|
*/
|
2023-08-01 01:05:50 +02:00
|
|
|
async function getMetaMetricsEnabled() {
|
|
|
|
const appState = getState();
|
|
|
|
if (Object.keys(appState) > 0) {
|
|
|
|
return Boolean(appState?.store?.metamask?.participateInMetaMetrics);
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
const persistedState = await globalThis.stateHooks.getPersistedState();
|
|
|
|
return Boolean(
|
|
|
|
persistedState?.data?.MetaMetricsController?.participateInMetaMetrics,
|
|
|
|
);
|
|
|
|
} catch (error) {
|
|
|
|
console.error(error);
|
2022-08-23 20:44:14 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-20 08:22:50 +02:00
|
|
|
Sentry.init({
|
|
|
|
dsn: sentryTarget,
|
|
|
|
debug: METAMASK_DEBUG,
|
2021-10-25 18:57:30 +02:00
|
|
|
environment,
|
2022-08-23 20:44:14 +02:00
|
|
|
integrations: [
|
|
|
|
new FilterEvents({ getMetaMetricsEnabled }),
|
|
|
|
new Dedupe(),
|
|
|
|
new ExtraErrorData(),
|
|
|
|
],
|
2018-08-09 22:49:40 +02:00
|
|
|
release,
|
2022-12-16 16:26:23 +01:00
|
|
|
beforeSend: (report) => rewriteReport(report, getState),
|
2023-02-20 18:13:12 +01:00
|
|
|
beforeBreadcrumb: beforeBreadcrumb(getState),
|
2021-02-04 19:15:23 +01:00
|
|
|
});
|
2018-07-19 08:22:56 +02:00
|
|
|
|
2022-12-16 16:26:23 +01:00
|
|
|
return Sentry;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Receives a string and returns that string if it is a
|
|
|
|
* regex match for a url with a `chrome-extension` or `moz-extension`
|
|
|
|
* protocol, and an empty string otherwise.
|
|
|
|
*
|
|
|
|
* @param {string} url - The URL to check.
|
|
|
|
* @returns {string} An empty string if the URL was internal, or the unmodified URL otherwise.
|
|
|
|
*/
|
|
|
|
function hideUrlIfNotInternal(url) {
|
|
|
|
const re = /^(chrome-extension|moz-extension):\/\//u;
|
|
|
|
if (!url.match(re)) {
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
return url;
|
|
|
|
}
|
|
|
|
|
2023-02-20 18:13:12 +01:00
|
|
|
/**
|
|
|
|
* Returns a method that handles the Sentry breadcrumb using a specific method to get the extension state
|
|
|
|
*
|
|
|
|
* @param {Function} getState - A method that returns the state of the extension
|
|
|
|
* @returns {(breadcrumb: object) => object} A method that modifies a Sentry breadcrumb object
|
|
|
|
*/
|
|
|
|
export function beforeBreadcrumb(getState) {
|
|
|
|
return (breadcrumb) => {
|
|
|
|
if (getState) {
|
|
|
|
const appState = getState();
|
|
|
|
if (
|
|
|
|
Object.values(appState).length &&
|
|
|
|
(!appState?.store?.metamask?.participateInMetaMetrics ||
|
|
|
|
!appState?.store?.metamask?.completedOnboarding ||
|
|
|
|
breadcrumb?.category === 'ui.input')
|
|
|
|
) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
const newBreadcrumb = removeUrlsFromBreadCrumb(breadcrumb);
|
|
|
|
return newBreadcrumb;
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2022-12-16 16:26:23 +01:00
|
|
|
/**
|
|
|
|
* Receives a Sentry breadcrumb object and potentially removes urls
|
|
|
|
* from its `data` property, it particular those possibly found at
|
|
|
|
* data.from, data.to and data.url
|
|
|
|
*
|
|
|
|
* @param {object} breadcrumb - A Sentry breadcrumb object: https://develop.sentry.dev/sdk/event-payloads/breadcrumbs/
|
|
|
|
* @returns {object} A modified Sentry breadcrumb object.
|
|
|
|
*/
|
|
|
|
export function removeUrlsFromBreadCrumb(breadcrumb) {
|
|
|
|
if (breadcrumb?.data?.url) {
|
|
|
|
breadcrumb.data.url = hideUrlIfNotInternal(breadcrumb.data.url);
|
|
|
|
}
|
|
|
|
if (breadcrumb?.data?.to) {
|
|
|
|
breadcrumb.data.to = hideUrlIfNotInternal(breadcrumb.data.to);
|
|
|
|
}
|
|
|
|
if (breadcrumb?.data?.from) {
|
|
|
|
breadcrumb.data.from = hideUrlIfNotInternal(breadcrumb.data.from);
|
|
|
|
}
|
|
|
|
return breadcrumb;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Receives a Sentry event object and modifies it before the
|
|
|
|
* error is sent to Sentry. Modifications include both sanitization
|
|
|
|
* of data via helper methods and addition of state data from the
|
|
|
|
* return value of the second parameter passed to the function.
|
|
|
|
*
|
|
|
|
* @param {object} report - A Sentry event object: https://develop.sentry.dev/sdk/event-payloads/
|
|
|
|
* @param {Function} getState - A function that should return an object representing some amount
|
|
|
|
* of app state that we wish to submit with our error reports
|
|
|
|
* @returns {object} A modified Sentry event object.
|
|
|
|
*/
|
|
|
|
export function rewriteReport(report, getState) {
|
|
|
|
try {
|
|
|
|
// simplify certain complex error messages (e.g. Ethjs)
|
|
|
|
simplifyErrorMessages(report);
|
|
|
|
// remove urls from error message
|
|
|
|
sanitizeUrlsFromErrorMessages(report);
|
|
|
|
// Remove evm addresses from error message.
|
|
|
|
// Note that this is redundent with data scrubbing we do within our sentry dashboard,
|
|
|
|
// but putting the code here as well gives public visibility to how we are handling
|
|
|
|
// privacy with respect to sentry.
|
|
|
|
sanitizeAddressesFromErrorMessages(report);
|
|
|
|
// modify report urls
|
|
|
|
rewriteReportUrls(report);
|
|
|
|
// append app state
|
|
|
|
if (getState) {
|
|
|
|
const appState = getState();
|
|
|
|
if (!report.extra) {
|
|
|
|
report.extra = {};
|
2020-07-17 17:09:38 +02:00
|
|
|
}
|
2022-12-16 16:26:23 +01:00
|
|
|
report.extra.appState = appState;
|
2018-10-20 08:22:50 +02:00
|
|
|
}
|
2022-12-16 16:26:23 +01:00
|
|
|
} catch (err) {
|
|
|
|
console.warn(err);
|
2018-10-20 08:22:50 +02:00
|
|
|
}
|
2022-12-16 16:26:23 +01:00
|
|
|
return report;
|
|
|
|
}
|
2018-03-23 23:24:32 +01:00
|
|
|
|
2022-12-16 16:26:23 +01:00
|
|
|
/**
|
|
|
|
* Receives a Sentry event object and modifies it so that urls are removed from any of its
|
|
|
|
* error messages.
|
|
|
|
*
|
|
|
|
* @param {object} report - the report to modify
|
|
|
|
*/
|
|
|
|
function sanitizeUrlsFromErrorMessages(report) {
|
|
|
|
rewriteErrorMessages(report, (errorMessage) => {
|
|
|
|
let newErrorMessage = errorMessage;
|
|
|
|
const re = /(([-.+a-zA-Z]+:\/\/)|(www\.))\S+[@:.]\S+/gu;
|
|
|
|
const urlsInMessage = newErrorMessage.match(re) || [];
|
|
|
|
urlsInMessage.forEach((url) => {
|
|
|
|
try {
|
|
|
|
const urlObj = new URL(url);
|
|
|
|
const { hostname } = urlObj;
|
|
|
|
if (
|
|
|
|
!Object.values(ERROR_URL_ALLOWLIST).some(
|
|
|
|
(allowedHostname) =>
|
|
|
|
hostname === allowedHostname ||
|
|
|
|
hostname.endsWith(`.${allowedHostname}`),
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
newErrorMessage = newErrorMessage.replace(url, '**');
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
newErrorMessage = newErrorMessage.replace(url, '**');
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return newErrorMessage;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Receives a Sentry event object and modifies it so that ethereum addresses are removed from
|
|
|
|
* any of its error messages.
|
|
|
|
*
|
|
|
|
* @param {object} report - the report to modify
|
|
|
|
*/
|
|
|
|
function sanitizeAddressesFromErrorMessages(report) {
|
|
|
|
rewriteErrorMessages(report, (errorMessage) => {
|
|
|
|
const newErrorMessage = errorMessage.replace(/0x[A-Fa-f0-9]{40}/u, '0x**');
|
|
|
|
return newErrorMessage;
|
|
|
|
});
|
2018-04-30 21:10:15 +02:00
|
|
|
}
|
|
|
|
|
2020-11-03 00:41:28 +01:00
|
|
|
function simplifyErrorMessages(report) {
|
2018-05-01 01:05:01 +02:00
|
|
|
rewriteErrorMessages(report, (errorMessage) => {
|
|
|
|
// simplify ethjs error messages
|
2021-02-04 19:15:23 +01:00
|
|
|
let simplifiedErrorMessage = extractEthjsErrorMessage(errorMessage);
|
2018-05-01 01:05:01 +02:00
|
|
|
// simplify 'Transaction Failed: known transaction'
|
2020-11-03 00:41:28 +01:00
|
|
|
if (
|
|
|
|
simplifiedErrorMessage.indexOf(
|
|
|
|
'Transaction Failed: known transaction',
|
|
|
|
) === 0
|
|
|
|
) {
|
2018-05-01 01:05:01 +02:00
|
|
|
// cut the hash from the error message
|
2021-02-04 19:15:23 +01:00
|
|
|
simplifiedErrorMessage = 'Transaction Failed: known transaction';
|
2018-05-01 01:05:01 +02:00
|
|
|
}
|
2021-02-04 19:15:23 +01:00
|
|
|
return simplifiedErrorMessage;
|
|
|
|
});
|
2018-05-01 01:05:01 +02:00
|
|
|
}
|
|
|
|
|
2020-11-03 00:41:28 +01:00
|
|
|
function rewriteErrorMessages(report, rewriteFn) {
|
2018-05-01 01:05:01 +02:00
|
|
|
// rewrite top level message
|
2019-11-20 01:03:20 +01:00
|
|
|
if (typeof report.message === 'string') {
|
2021-02-04 19:15:23 +01:00
|
|
|
report.message = rewriteFn(report.message);
|
2019-11-20 01:03:20 +01:00
|
|
|
}
|
2018-05-01 01:05:01 +02:00
|
|
|
// rewrite each exception message
|
2018-04-30 21:07:48 +02:00
|
|
|
if (report.exception && report.exception.values) {
|
2020-02-15 21:34:12 +01:00
|
|
|
report.exception.values.forEach((item) => {
|
2019-11-20 01:03:20 +01:00
|
|
|
if (typeof item.value === 'string') {
|
2021-02-04 19:15:23 +01:00
|
|
|
item.value = rewriteFn(item.value);
|
2019-11-20 01:03:20 +01:00
|
|
|
}
|
2021-02-04 19:15:23 +01:00
|
|
|
});
|
2018-04-30 21:07:48 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-03 00:41:28 +01:00
|
|
|
function rewriteReportUrls(report) {
|
2023-02-20 18:13:12 +01:00
|
|
|
if (report.request?.url) {
|
|
|
|
// update request url
|
|
|
|
report.request.url = toMetamaskUrl(report.request.url);
|
|
|
|
}
|
|
|
|
|
2018-03-23 23:24:32 +01:00
|
|
|
// update exception stack trace
|
2018-04-29 22:46:07 +02:00
|
|
|
if (report.exception && report.exception.values) {
|
2020-02-15 21:34:12 +01:00
|
|
|
report.exception.values.forEach((item) => {
|
2020-03-12 14:51:05 +01:00
|
|
|
if (item.stacktrace) {
|
|
|
|
item.stacktrace.frames.forEach((frame) => {
|
2021-02-04 19:15:23 +01:00
|
|
|
frame.filename = toMetamaskUrl(frame.filename);
|
|
|
|
});
|
2020-03-12 14:51:05 +01:00
|
|
|
}
|
2021-02-04 19:15:23 +01:00
|
|
|
});
|
2018-04-29 22:46:07 +02:00
|
|
|
}
|
2018-03-23 23:24:32 +01:00
|
|
|
}
|
|
|
|
|
2020-11-03 00:41:28 +01:00
|
|
|
function toMetamaskUrl(origUrl) {
|
2023-02-20 18:13:12 +01:00
|
|
|
if (!globalThis.location?.origin) {
|
|
|
|
return origUrl;
|
|
|
|
}
|
|
|
|
|
2022-12-16 16:26:23 +01:00
|
|
|
const filePath = origUrl?.split(globalThis.location.origin)[1];
|
2019-11-20 01:03:20 +01:00
|
|
|
if (!filePath) {
|
2021-02-04 19:15:23 +01:00
|
|
|
return origUrl;
|
2019-11-20 01:03:20 +01:00
|
|
|
}
|
2023-07-21 21:03:25 +02:00
|
|
|
const metamaskUrl = `/metamask${filePath}`;
|
2021-02-04 19:15:23 +01:00
|
|
|
return metamaskUrl;
|
2018-03-23 23:24:32 +01:00
|
|
|
}
|