From 04c5fc63240f7f55f3e7c21400e5f4cef37d6681 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Thu, 22 Aug 2024 17:05:02 -0230 Subject: [PATCH] fix: sentry sessions (#26192) [cherry-pick] (#26620) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cherry-pick of #26192 for v12.1.0. Original description: ## **Description** Fix the automatic tracking of Sentry sessions, and refactor Sentry setup to simplify logic and improve logging. Specifically: - Remove all dynamic `autoSessionTracking` enablement. - Add a custom Sentry transport to prevent any requests reaching Sentry if metrics are disabled. - Only consider metrics enabled when preference is set and onboarding is complete. - Enable Sentry if `METAMASK_ENVIRONMENT` is not `production` and `SENTRY_DSN_DEV` is specified. - Remove the arguments to the `setupSentry` function. - Since the file already references `process.env` and `global.stateHooks` internally. - Flatten all the functions within `setupSentry.js`. - Since they no longer require a dynamic `getState` callback. - Log all Sentry messages via the `debug` package based on the `DEBUG` environment variable. - Create separate loggers for UI and background to differentiate logs. - e.g. `DEBUG=metamask:sentry:*` in `.metamask.rc` - Add additional log messages for easier debug. - Including all sent events and completed sessions if `METAMASK_DEBUG` is enabled. - Add mock `SENTRY_DSN_DEV` to Flask and MMI test builds. - Remove duplication in package scripts. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/26007?quickstart=1) ## **Related issues** Fixes: [#2555](https://github.com/MetaMask/MetaMask-planning/issues/2555) #15691 ## **Manual testing steps** ## **Screenshots/Recordings** ### **Before** ### **After** Updated Logs ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I’ve included tests if applicable - [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. Co-authored-by: Matthew Walsh --- .metamaskrc.dist | 7 +- app/scripts/controllers/metametrics.js | 17 +- app/scripts/controllers/metametrics.test.js | 7 +- app/scripts/lib/sentry-filter-events.ts | 15 +- app/scripts/lib/setupSentry.js | 410 ++++++++++---------- app/scripts/sentry-install.js | 23 +- development/build/scripts.js | 1 + package.json | 21 +- test/e2e/tests/metrics/errors.spec.js | 4 +- types/global.d.ts | 13 +- ui/store/actions.test.js | 7 - ui/store/actions.ts | 7 +- yarn.lock | 9 +- 13 files changed, 249 insertions(+), 292 deletions(-) diff --git a/.metamaskrc.dist b/.metamaskrc.dist index c7431cc0719b..62dfdeb64634 100644 --- a/.metamaskrc.dist +++ b/.metamaskrc.dist @@ -24,7 +24,12 @@ BLOCKAID_PUBLIC_KEY= ; Set this to true to enable the snap path for the Firefox WebDriver (Linux) ; FIREFOX_SNAP= -ENABLE_CONFIRMATION_REDESIGN= +; Enable the redesigned confirmations still in development, without needing to toggle the developer setting. +; ENABLE_CONFIRMATION_REDESIGN= ; Enables the Settings Page - Developer Options ; ENABLE_SETTINGS_PAGE_DEV_OPTIONS=true + +; The endpoint used to submit errors and tracing data to Sentry. +; The below is the `test-metamask` project. +; SENTRY_DSN_DEV=https://f59f3dd640d2429d9d0e2445a87ea8e1@sentry.io/273496 diff --git a/app/scripts/controllers/metametrics.js b/app/scripts/controllers/metametrics.js index c7bd148e62d3..6c3f3392e929 100644 --- a/app/scripts/controllers/metametrics.js +++ b/app/scripts/controllers/metametrics.js @@ -454,16 +454,15 @@ export default class MetaMetricsController { * if not set */ async setParticipateInMetaMetrics(participateInMetaMetrics) { - let { metaMetricsId } = this.state; - if (participateInMetaMetrics && !metaMetricsId) { - // We also need to start sentry automatic session tracking at this point - await globalThis.sentry?.startSession(); - metaMetricsId = this.generateMetaMetricsId(); - } else if (participateInMetaMetrics === false) { - // We also need to stop sentry automatic session tracking at this point - await globalThis.sentry?.endSession(); - } + const { metaMetricsId: existingMetaMetricsId } = this.state; + + const metaMetricsId = + participateInMetaMetrics && !existingMetaMetricsId + ? this.generateMetaMetricsId() + : existingMetaMetricsId; + this.store.updateState({ participateInMetaMetrics, metaMetricsId }); + if (participateInMetaMetrics) { this.trackEventsAfterMetricsOptIn(); this.clearEventsAfterMetricsOptIn(); diff --git a/app/scripts/controllers/metametrics.test.js b/app/scripts/controllers/metametrics.test.js index 43834f01a4b9..4206de67a7fd 100644 --- a/app/scripts/controllers/metametrics.test.js +++ b/app/scripts/controllers/metametrics.test.js @@ -143,10 +143,7 @@ function getMetaMetricsController({ describe('MetaMetricsController', function () { const now = new Date(); beforeEach(function () { - globalThis.sentry = { - startSession: jest.fn(), - endSession: jest.fn(), - }; + globalThis.sentry = {}; jest.useFakeTimers().setSystemTime(now.getTime()); jest.spyOn(Utils, 'generateRandomId').mockReturnValue('DUMMY_RANDOM_ID'); }); @@ -316,7 +313,6 @@ describe('MetaMetricsController', function () { metaMetricsController.state.participateInMetaMetrics, ).toStrictEqual(null); await metaMetricsController.setParticipateInMetaMetrics(true); - expect(globalThis.sentry.startSession).toHaveBeenCalledTimes(1); expect( metaMetricsController.state.participateInMetaMetrics, ).toStrictEqual(true); @@ -339,7 +335,6 @@ describe('MetaMetricsController', function () { it('should not nullify the metaMetricsId when set to false', async function () { const metaMetricsController = getMetaMetricsController(); await metaMetricsController.setParticipateInMetaMetrics(false); - expect(globalThis.sentry.endSession).toHaveBeenCalledTimes(1); expect(metaMetricsController.state.metaMetricsId).toStrictEqual( TEST_META_METRICS_ID, ); diff --git a/app/scripts/lib/sentry-filter-events.ts b/app/scripts/lib/sentry-filter-events.ts index 9d406fa413f7..9c802d872c15 100644 --- a/app/scripts/lib/sentry-filter-events.ts +++ b/app/scripts/lib/sentry-filter-events.ts @@ -1,5 +1,4 @@ import { Event as SentryEvent, Integration } from '@sentry/types'; -import { logger } from '@sentry/utils'; const NAME = 'FilterEvents'; @@ -8,17 +7,27 @@ const NAME = 'FilterEvents'; * * @param options - Options bag. * @param options.getMetaMetricsEnabled - Function that returns whether MetaMetrics is enabled. + * @param options.log - Function to log messages. */ export function filterEvents({ getMetaMetricsEnabled, + log, }: { getMetaMetricsEnabled: () => Promise; + log: (message: string) => void; }): Integration { return { name: NAME, processEvent: async (event: SentryEvent) => { - if (!(await getMetaMetricsEnabled())) { - logger.warn(`Event dropped due to MetaMetrics setting.`); + // This integration is required in addition to the custom transport as it provides an + // asynchronous context which we may need in order to read the persisted state from the + // store, so it can later be added to the event via the `beforeSend` overload. + // It also provides a more native solution for discarding events, but any + // session requests will always be handled by the custom transport. + const metricsEnabled = await getMetaMetricsEnabled(); + + if (!metricsEnabled) { + log('Event dropped as metrics disabled'); return null; } diff --git a/app/scripts/lib/setupSentry.js b/app/scripts/lib/setupSentry.js index 9f976c4d3bee..84971754e5ff 100644 --- a/app/scripts/lib/setupSentry.js +++ b/app/scripts/lib/setupSentry.js @@ -1,20 +1,29 @@ import * as Sentry from '@sentry/browser'; +import { createModuleLogger, createProjectLogger } from '@metamask/utils'; +import { logger } from '@sentry/utils'; import browser from 'webextension-polyfill'; import { isManifestV3 } from '../../../shared/modules/mv3.utils'; import { filterEvents } from './sentry-filter-events'; import extractEthjsErrorMessage from './extractEthjsErrorMessage'; +const projectLogger = createProjectLogger('sentry'); + +const log = createModuleLogger( + projectLogger, + globalThis.document ? 'ui' : 'background', +); + let installType = 'unknown'; /* eslint-disable prefer-destructuring */ // Destructuring breaks the inlining of the environment variables +const METAMASK_BUILD_TYPE = process.env.METAMASK_BUILD_TYPE; const METAMASK_DEBUG = process.env.METAMASK_DEBUG; const METAMASK_ENVIRONMENT = process.env.METAMASK_ENVIRONMENT; -const SENTRY_DSN_DEV = - process.env.SENTRY_DSN_DEV || - 'https://f59f3dd640d2429d9d0e2445a87ea8e1@sentry.io/273496'; -const METAMASK_BUILD_TYPE = process.env.METAMASK_BUILD_TYPE; -const IN_TEST = process.env.IN_TEST; +const RELEASE = process.env.METAMASK_VERSION; +const SENTRY_DSN = process.env.SENTRY_DSN; +const SENTRY_DSN_DEV = process.env.SENTRY_DSN_DEV; +const SENTRY_DSN_MMI = process.env.SENTRY_MMI_DSN; /* eslint-enable prefer-destructuring */ export const ERROR_URL_ALLOWLIST = { @@ -25,6 +34,54 @@ export const ERROR_URL_ALLOWLIST = { SEGMENT: 'segment.io', }; +export default function setupSentry() { + if (!RELEASE) { + throw new Error('Missing release'); + } + + if (!getSentryTarget()) { + log('Skipped initialization'); + return undefined; + } + + log('Initializing'); + + integrateLogging(); + setSentryClient(); + + return { + ...Sentry, + }; +} + +function getClientOptions() { + const environment = getSentryEnvironment(); + const sentryTarget = getSentryTarget(); + + return { + beforeBreadcrumb: beforeBreadcrumb(), + beforeSend: (report) => rewriteReport(report), + debug: METAMASK_DEBUG, + dsn: sentryTarget, + dist: isManifestV3 ? 'mv3' : 'mv2', + environment, + integrations: [ + Sentry.dedupeIntegration(), + Sentry.extraErrorDataIntegration(), + filterEvents({ getMetaMetricsEnabled, log }), + ], + release: RELEASE, + // Client reports are automatically sent when a page's visibility changes to + // "hidden", but cancelled (with an Error) that gets logged to the console. + // Our test infra sometimes reports these errors as unexpected failures, + // which results in test flakiness. We don't use these client reports, so + // we can safely turn them off by setting the `sendClientReports` option to + // `false`. + sendClientReports: false, + transport: makeTransport, + }; +} + /** * Returns whether MetaMetrics is enabled, given the application state. * @@ -68,15 +125,12 @@ function getMetaMetricsEnabledFromPersistedState(persistedState) { * Returns whether onboarding has completed, given the application state. * * @param {Record} appState - Application state - * @returns `true` if MetaMask's state has been initialized, and MetaMetrics - * is enabled, `false` otherwise. + * @returns `true` if onboarding has completed, `false` otherwise. */ function getOnboardingCompleteFromAppState(appState) { // during initialization after loading persisted state if (appState.persistedState) { - return Boolean( - appState.persistedState.data?.OnboardingController?.completedOnboarding, - ); + return getOnboardingCompleteFromPersistedState(appState.persistedState); // After initialization } else if (appState.state) { // UI @@ -90,92 +144,82 @@ function getOnboardingCompleteFromAppState(appState) { return false; } -export default function setupSentry({ release, getState }) { - if (!release) { - throw new Error('Missing release'); - } 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 - */ - return undefined; +/** + * Returns whether onboarding has completed, given the persisted state. + * + * @param {Record} persistedState - Persisted state + * @returns `true` if onboarding has completed, `false` otherwise. + */ +function getOnboardingCompleteFromPersistedState(persistedState) { + return Boolean( + persistedState.data?.OnboardingController?.completedOnboarding, + ); +} + +function getSentryEnvironment() { + if (METAMASK_BUILD_TYPE === 'main') { + return METAMASK_ENVIRONMENT; } - const environment = - METAMASK_BUILD_TYPE === 'main' - ? METAMASK_ENVIRONMENT - : `${METAMASK_ENVIRONMENT}-${METAMASK_BUILD_TYPE}`; - - let sentryTarget; - if (METAMASK_ENVIRONMENT === 'production') { - if (!process.env.SENTRY_DSN) { - throw new Error( - `Missing SENTRY_DSN environment variable in production environment`, - ); - } + return `${METAMASK_ENVIRONMENT}-${METAMASK_BUILD_TYPE}`; +} - ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) - sentryTarget = process.env.SENTRY_MMI_DSN; - ///: END:ONLY_INCLUDE_IF +function getSentryTarget() { + if (METAMASK_ENVIRONMENT !== 'production') { + return SENTRY_DSN_DEV; + } - ///: BEGIN:ONLY_INCLUDE_IF(build-main,build-beta,build-flask) - sentryTarget = process.env.SENTRY_DSN; - ///: END:ONLY_INCLUDE_IF + if (METAMASK_BUILD_TYPE === 'mmi') { + return SENTRY_DSN_MMI; + } - console.log( - `Setting up Sentry Remote Error Reporting for '${environment}': SENTRY_DSN`, - ); - } else { - console.log( - `Setting up Sentry Remote Error Reporting for '${environment}': SENTRY_DSN_DEV`, + if (!SENTRY_DSN) { + throw new Error( + `Missing SENTRY_DSN environment variable in production environment`, ); - sentryTarget = SENTRY_DSN_DEV; } - /** - * Returns whether MetaMetrics is enabled. If the application hasn't yet - * been initialized, the persisted state will be used (if any). - * - * @returns `true` if MetaMetrics is enabled, `false` otherwise. - */ - async function getMetaMetricsEnabled() { - if (METAMASK_BUILD_TYPE === 'mmi') { - return true; - } + return SENTRY_DSN; +} - const appState = getState(); - if (appState.state || appState.persistedState) { - return getMetaMetricsEnabledFromAppState(appState); - } - // If we reach here, it means the error was thrown before initialization - // completed, and before we loaded the persisted state for the first time. - try { - const persistedState = await globalThis.stateHooks.getPersistedState(); - return getMetaMetricsEnabledFromPersistedState(persistedState); - } catch (error) { - console.error(error); - return false; - } +/** + * Returns whether MetaMetrics is enabled. If the application hasn't yet + * been initialized, the persisted state will be used (if any). + * + * @returns `true` if MetaMetrics is enabled, `false` otherwise. + */ +async function getMetaMetricsEnabled() { + if (METAMASK_BUILD_TYPE === 'mmi') { + return true; } - /** - * Returns whether Sentry should be enabled or not. If the build type is mmi - * it will always be enabled, if it's main it will first check for MetaMetrics - * value before returning true or false - * - * @returns `true` if Sentry should be enabled, depends on the build type and - * whether MetaMetrics is on or off for all build types except mmi - */ - async function getSentryEnabled() { - // For MMI we want Sentry always logging, doesn't depend on MetaMetrics being on or off - if (METAMASK_BUILD_TYPE === 'mmi') { - return true; - } - return getMetaMetricsEnabled(); + const appState = getState(); + + if (appState.state || appState.persistedState) { + return ( + getMetaMetricsEnabledFromAppState(appState) && + getOnboardingCompleteFromAppState(appState) + ); } + // If we reach here, it means the error was thrown before initialization + // completed, and before we loaded the persisted state for the first time. + try { + const persistedState = await globalThis.stateHooks.getPersistedState(); + return ( + getMetaMetricsEnabledFromPersistedState(persistedState) && + getOnboardingCompleteFromPersistedState(persistedState) + ); + } catch (error) { + log('Error retrieving persisted state', error); + return false; + } +} + +function setSentryClient() { + const clientOptions = getClientOptions(); + const { dsn, environment, release } = clientOptions; + // Normally this would be awaited, but getSelf should be available by the time the report is finalized. // If it's not, we still get the extensionId, but the installType will default to "unknown" browser.management @@ -197,130 +241,17 @@ export default function setupSentry({ release, getState }) { */ globalThis.nw = {}; - Sentry.init({ - dsn: sentryTarget, - debug: METAMASK_DEBUG, - dist: isManifestV3 ? 'mv3' : 'mv2', - /** - * autoSessionTracking defaults to true and operates by sending a session - * packet to sentry. This session packet does not appear to be filtered out - * via our beforeSend or FilterEvents integration. To avoid sending a - * request before we have the state tree and can validate the users - * preferences, we initiate this to false. Later, in startSession and - * endSession we modify this option and start the session or end the - * session manually. - * - * In sentry-install we call toggleSession after the page loads and state - * is available, this handles initiating the session for a user who has - * opted into MetaMetrics. This script is ran in both the background and UI - * so it should be effective at starting the session in both places. - * - * In the MetaMetricsController the session is manually started or stopped - * when the user opts in or out of MetaMetrics. This occurs in the - * setParticipateInMetaMetrics function which is exposed to the UI via the - * MetaMaskController. - * - * In actions.ts, after sending the updated participateInMetaMetrics flag - * to the background, we call toggleSession to ensure sentry is kept in - * sync with the user's preference. - * - * Types for the global Sentry object, and the new methods added as part of - * this effort were added to global.d.ts in the types folder. - */ - autoSessionTracking: false, + log('Updating client', { environment, - integrations: [ - /** - * Filtering of events must happen in this FilterEvents custom - * integration instead of in the beforeSend handler because the Dedupe - * integration is unaware of the beforeSend functionality. If an event is - * queued in the sentry context, additional events of the same name will - * be filtered out by Dedupe even if the original event was not sent due - * to the beforeSend method returning null. - * - * @see https://github.com/MetaMask/metamask-extension/pull/15677 - */ - filterEvents({ getMetaMetricsEnabled }), - Sentry.dedupeIntegration(), - Sentry.extraErrorDataIntegration(), - Sentry.browserProfilingIntegration(), - ], + dsn, release, - /** - * Setting a value for `tracesSampleRate` enables performance monitoring in Sentry. - * Once performance monitoring is enabled, transactions are sent to Sentry every time - * a user loads a page or navigates within the app. - * Since the amount of traffic the app gets is important, this means a lot of - * transactions are sent. By setting `tracesSampleRate` to a value lower than 1.0, we - * reduce the volume of transactions to a more reasonable amount. - */ - tracesSampleRate: 0.01, - beforeSend: (report) => rewriteReport(report, getState), - beforeBreadcrumb: beforeBreadcrumb(getState), - // Client reports are automatically sent when a page's visibility changes to - // "hidden", but cancelled (with an Error) that gets logged to the console. - // Our test infra sometimes reports these errors as unexpected failures, - // which results in test flakiness. We don't use these client reports, so - // we can safely turn them off by setting the `sendClientReports` option to - // `false`. - sendClientReports: false, }); - /** - * As long as a reference to the Sentry Hub can be found, and the user has - * opted into MetaMetrics, change the autoSessionTracking option and start - * a new sentry session. - */ - const startSession = async () => { - const client = Sentry.getClient(); - const options = client?.getOptions?.() ?? {}; - if (client && (await getSentryEnabled()) === true) { - options.autoSessionTracking = true; - Sentry.startSession(); - } - }; + Sentry.init(clientOptions); - /** - * As long as a reference to the Sentry Hub can be found, and the user has - * opted out of MetaMetrics, change the autoSessionTracking option and end - * the current sentry session. - */ - const endSession = async () => { - const client = Sentry.getClient(); - const options = client?.getOptions?.() ?? {}; - if (client && (await getSentryEnabled()) === false) { - options.autoSessionTracking = false; - Sentry.endSession(); - } - }; - - /** - * Call the appropriate method (either startSession or endSession) depending - * on the state of metaMetrics optin and the state of autoSessionTracking on - * the Sentry client. - */ - const toggleSession = async () => { - const client = Sentry.getClient(); - const options = client?.getOptions?.() ?? { - autoSessionTracking: false, - }; - const isSentryEnabled = await getSentryEnabled(); - if (isSentryEnabled === true && options.autoSessionTracking === false) { - await startSession(); - } else if ( - isSentryEnabled === false && - options.autoSessionTracking === true - ) { - await endSession(); - } - }; + addDebugListeners(); - return { - ...Sentry, - startSession, - endSession, - toggleSession, - }; + return true; } /** @@ -342,10 +273,9 @@ function hideUrlIfNotInternal(url) { /** * 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) { +export function beforeBreadcrumb() { return (breadcrumb) => { if (!getState) { return null; @@ -391,11 +321,9 @@ export function removeUrlsFromBreadCrumb(breadcrumb) { * 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) { +export function rewriteReport(report) { try { // simplify certain complex error messages (e.g. Ethjs) simplifyErrorMessages(report); @@ -408,21 +336,21 @@ export function rewriteReport(report, getState) { sanitizeAddressesFromErrorMessages(report); // modify report urls rewriteReportUrls(report); + // append app state - if (getState) { - const appState = getState(); - if (!report.extra) { - report.extra = {}; - } - report.extra.appState = appState; + const appState = getState(); + + if (!report.extra) { + report.extra = {}; } + report.extra.appState = appState; if (browser.runtime && browser.runtime.id) { report.extra.extensionId = browser.runtime.id; } report.extra.installType = installType; } catch (err) { - console.warn(err); + log('Error rewriting report', err); } return report; } @@ -534,3 +462,59 @@ function toMetamaskUrl(origUrl) { const metamaskUrl = `/metamask${filePath}`; return metamaskUrl; } + +function getState() { + return globalThis.stateHooks?.getSentryState?.() || {}; +} + +function integrateLogging() { + if (!METAMASK_DEBUG) { + return; + } + + for (const loggerType of ['log', 'error']) { + logger[loggerType] = (...args) => { + const message = args[0].replace(`Sentry Logger [${loggerType}]: `, ''); + log(message, ...args.slice(1)); + }; + } + + log('Integrated logging'); +} + +function addDebugListeners() { + if (!METAMASK_DEBUG) { + return; + } + + const client = Sentry.getClient(); + + client?.on('beforeEnvelope', (event) => { + const type = event?.[1]?.[0]?.[0]?.type; + const data = event?.[1]?.[0]?.[1] ?? {}; + + if (type !== 'session' || data.status !== 'exited') { + return; + } + + log('Completed session', data); + }); + + client?.on('afterSendEvent', (event) => { + log('Event', event); + }); + + log('Added debug listeners'); +} + +function makeTransport(options) { + return Sentry.makeFetchTransport(options, async (...args) => { + const metricsEnabled = await getMetaMetricsEnabled(); + + if (!metricsEnabled) { + throw new Error('Network request skipped as metrics disabled'); + } + + return await fetch(...args); + }); +} diff --git a/app/scripts/sentry-install.js b/app/scripts/sentry-install.js index 99d8ddd39327..cbbc03ab10e2 100644 --- a/app/scripts/sentry-install.js +++ b/app/scripts/sentry-install.js @@ -4,25 +4,4 @@ import setupSentry from './lib/setupSentry'; global.stateHooks = {}; // setup sentry error reporting -global.sentry = setupSentry({ - release: process.env.METAMASK_VERSION, - getState: () => global.stateHooks?.getSentryState?.() || {}, -}); - -/** - * As soon as state is available via getSentryState we can start automatic - * session tracking. - */ -async function waitForStateHooks() { - if (global.stateHooks.getSentryState) { - // sentry is not defined in dev mode, so if sentry doesn't exist at this - // point it means that we are in dev mode and do not need to toggle the - // session. Using optional chaining is sufficient to prevent the error in - // development. - await global.sentry?.startSession(); - } else { - setTimeout(waitForStateHooks, 100); - } -} - -waitForStateHooks(); +global.sentry = setupSentry(); diff --git a/development/build/scripts.js b/development/build/scripts.js index 297577a59031..c87197bfaf1c 100644 --- a/development/build/scripts.js +++ b/development/build/scripts.js @@ -79,6 +79,7 @@ const scuttlingConfigBase = { console: '', crypto: '', Map: '', + isFinite: '', // {clear/set}Timeout are "this sensitive" clearTimeout: 'window', setTimeout: 'window', diff --git a/package.json b/package.json index cf3b33313e97..2b890b689cad 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "url": "https://github.com/MetaMask/metamask-extension.git" }, "scripts": { + "env:e2e": "SEGMENT_HOST='https://api.segment.io' SEGMENT_WRITE_KEY='FAKE' SENTRY_DSN_DEV=https://fake@sentry.io/0000000 yarn", "start": "yarn build:dev dev --apply-lavamoat=false --snow=false", "start:skip-onboarding": "SKIP_ONBOARDING=true yarn start", "start:mv2": "ENABLE_MV3=false yarn build:dev dev --apply-lavamoat=false --snow=false", @@ -19,20 +20,20 @@ "dist:mmi:debug": "yarn dist --build-type mmi --apply-lavamoat=false --snow=false", "build": "yarn lavamoat:build", "build:dev": "node development/build/index.js", - "start:test": "SEGMENT_HOST='https://api.segment.io' SEGMENT_WRITE_KEY='FAKE' SENTRY_DSN_DEV=https://fake@sentry.io/0000000 yarn build:dev testDev", - "start:test:flask": "SEGMENT_HOST='https://api.segment.io' SEGMENT_WRITE_KEY='FAKE' SENTRY_DSN_DEV=https://fake@sentry.io/0000000 yarn build:dev testDev --build-type flask --apply-lavamoat=false --snow=false", - "start:test:mv2:flask": "ENABLE_MV3=false SEGMENT_HOST='https://api.segment.io' SEGMENT_WRITE_KEY='FAKE' SENTRY_DSN_DEV=https://fake@sentry.io/0000000 yarn build:dev testDev --build-type flask --apply-lavamoat=false --snow=false", - "start:test:mv2": "ENABLE_MV3=false BLOCKAID_FILE_CDN=static.cx.metamask.io/api/v1/confirmations/ppom SEGMENT_HOST='https://api.segment.io' SEGMENT_WRITE_KEY='FAKE' SENTRY_DSN_DEV=https://fake@sentry.io/0000000 yarn build:dev testDev --apply-lavamoat=false --snow=false", + "start:test": "yarn env:e2e build:dev testDev", + "start:test:flask": "yarn start:test --build-type flask", + "start:test:mv2:flask": "ENABLE_MV3=false yarn start:test:flask --apply-lavamoat=false --snow=false", + "start:test:mv2": "ENABLE_MV3=false BLOCKAID_FILE_CDN=static.cx.metamask.io/api/v1/confirmations/ppom yarn start:test --apply-lavamoat=false --snow=false", "benchmark:chrome": "SELENIUM_BROWSER=chrome ts-node test/e2e/benchmark.js", "mv3:stats:chrome": "SELENIUM_BROWSER=chrome ts-node test/e2e/mv3-perf-stats/index.js", "user-actions-benchmark:chrome": "SELENIUM_BROWSER=chrome ts-node test/e2e/user-actions-benchmark.js", "benchmark:firefox": "SELENIUM_BROWSER=firefox ts-node test/e2e/benchmark.js", - "build:test": "SEGMENT_HOST='https://api.segment.io' SEGMENT_WRITE_KEY='FAKE' SENTRY_DSN_DEV=https://fake@sentry.io/0000000 yarn build test", - "build:test:dev": "SEGMENT_HOST='https://api.segment.io' SEGMENT_WRITE_KEY='FAKE' SENTRY_DSN_DEV=https://fake@sentry.io/0000000 yarn build:dev testDev --apply-lavamoat=false", - "build:test:flask": "SEGMENT_HOST='https://api.segment.io' SEGMENT_WRITE_KEY='FAKE' yarn build test --build-type flask", - "build:test:flask:mv2": "ENABLE_MV3=false SEGMENT_HOST='https://api.segment.io' SEGMENT_WRITE_KEY='FAKE' yarn build test --build-type flask", - "build:test:mmi": "SEGMENT_HOST='https://api.segment.io' SEGMENT_WRITE_KEY='FAKE' yarn build test --build-type mmi", - "build:test:mv2": "ENABLE_MV3=false BLOCKAID_FILE_CDN=static.cx.metamask.io/api/v1/confirmations/ppom SEGMENT_HOST='https://api.segment.io' SEGMENT_WRITE_KEY='FAKE' SENTRY_DSN_DEV=https://fake@sentry.io/0000000 yarn build test", + "build:test": "yarn env:e2e build test", + "build:test:dev": "yarn env:e2e build:dev testDev --apply-lavamoat=false", + "build:test:flask": "yarn build:test --build-type flask", + "build:test:flask:mv2": "ENABLE_MV3=false yarn build:test:flask", + "build:test:mmi": "yarn build:test --build-type mmi", + "build:test:mv2": "ENABLE_MV3=false BLOCKAID_FILE_CDN=static.cx.metamask.io/api/v1/confirmations/ppom yarn build:test", "test": "yarn lint && yarn test:unit && yarn test:unit:jest", "dapp": "node development/static-server.js node_modules/@metamask/test-dapp/dist --port 8080", "dapp-chain": "GANACHE_ARGS='-b 2' concurrently -k -n ganache,dapp -p '[{time}][{name}]' 'yarn ganache:start' 'sleep 5 && yarn dapp'", diff --git a/test/e2e/tests/metrics/errors.spec.js b/test/e2e/tests/metrics/errors.spec.js index 1ff7ca4faecc..54e7c2b0e6aa 100644 --- a/test/e2e/tests/metrics/errors.spec.js +++ b/test/e2e/tests/metrics/errors.spec.js @@ -221,7 +221,7 @@ describe('Sentry errors', function () { ], }; - describe('before initialization, after opting out of metrics', function () { + describe('before initialization, after opting out of metrics @no-mmi', function () { it('should NOT send error events in the background', async function () { await withFixtures( { @@ -534,7 +534,7 @@ describe('Sentry errors', function () { }); }); - describe('after initialization, after opting out of metrics', function () { + describe('after initialization, after opting out of metrics @no-mmi', function () { it('should NOT send error events in the background', async function () { await withFixtures( { diff --git a/types/global.d.ts b/types/global.d.ts index c061fba57001..451168775331 100644 --- a/types/global.d.ts +++ b/types/global.d.ts @@ -224,18 +224,7 @@ declare class Chrome { runtime: Runtime; } -type SentryObject = Sentry & { - // Verifies that the user has opted into metrics and then updates the sentry - // instance to track sessions and begins the session. - startSession: () => void; - - // Verifies that the user has opted out of metrics and then updates the - // sentry instance to NOT track sessions and ends the current session. - endSession: () => void; - - // Calls either startSession or endSession based on optin status - toggleSession: () => void; -}; +type SentryObject = Sentry; export declare global { var platform: Platform; diff --git a/ui/store/actions.test.js b/ui/store/actions.test.js index d2a0740389fb..57e52dc9a7b3 100644 --- a/ui/store/actions.test.js +++ b/ui/store/actions.test.js @@ -1619,12 +1619,6 @@ describe('Actions', () => { }); describe('#setParticipateInMetaMetrics', () => { - beforeAll(() => { - window.sentry = { - toggleSession: jest.fn(), - endSession: jest.fn(), - }; - }); it('sets participateInMetaMetrics to true', async () => { const store = mockStore(); const setParticipateInMetaMetricsStub = jest.fn((_, cb) => cb()); @@ -1640,7 +1634,6 @@ describe('Actions', () => { true, expect.anything(), ); - expect(window.sentry.toggleSession).toHaveBeenCalled(); }); }); diff --git a/ui/store/actions.ts b/ui/store/actions.ts index ba53d0a8eeb7..a64cd2e6bb9e 100644 --- a/ui/store/actions.ts +++ b/ui/store/actions.ts @@ -3224,17 +3224,12 @@ export function setParticipateInMetaMetrics( reject(err); return; } - /** - * We need to inform sentry that the user's optin preference may have - * changed. The logic to determine which way to toggle is in the - * toggleSession handler in setupSentry.js. - */ - window.sentry?.toggleSession(); dispatch({ type: actionConstants.SET_PARTICIPATE_IN_METAMETRICS, value: participationPreference, }); + resolve([participationPreference, metaMetricsId as string]); }, ); diff --git a/yarn.lock b/yarn.lock index adfdbd060291..2ba27f09fc0f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8008,13 +8008,20 @@ __metadata: languageName: node linkType: hard -"@sentry/types@npm:8.19.0, @sentry/types@npm:^8.19.0": +"@sentry/types@npm:8.19.0": version: 8.19.0 resolution: "@sentry/types@npm:8.19.0" checksum: 10/8812f7394c6c031197abc04d80e5b5b3693742dc065b877c535a9ceb538aabd60ee27fc2b13824e2b8fc264819868109bbd4de3642fd1c7bf30d304fb0c21aa9 languageName: node linkType: hard +"@sentry/types@npm:^8.19.0": + version: 8.20.0 + resolution: "@sentry/types@npm:8.20.0" + checksum: 10/c7d7ed17975f0fc0b4bf5aece58084953c2a76e8f417923a476fe1fd42a2c9339c548d701edbc4b938c9252cf680d3eff4c6c2a986bc7ac62649aebf656c5b64 + languageName: node + linkType: hard + "@sentry/utils@npm:8.19.0, @sentry/utils@npm:^8.19.0": version: 8.19.0 resolution: "@sentry/utils@npm:8.19.0"