diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 89ca7627b9f0..5587b3d85526 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -57,7 +57,7 @@ import { SelectedNetworkController, createSelectedNetworkMiddleware, } from '@metamask/selected-network-controller'; -import { LoggingController } from '@metamask/logging-controller'; +import { LoggingController, LogType } from '@metamask/logging-controller'; ///: BEGIN:ONLY_INCLUDE_IN(snaps) import { encrypt, decrypt } from '@metamask/browser-passworder'; @@ -156,6 +156,7 @@ import { MetaMetricsEventCategory, MetaMetricsEventName, } from '../../shared/constants/metametrics'; +import { LOG_EVENT } from '../../shared/constants/logs'; import { getTokenIdParam, @@ -287,6 +288,13 @@ export default class MetamaskController extends EventEmitter { this.controllerMessenger = new ControllerMessenger(); + this.loggingController = new LoggingController({ + messenger: this.controllerMessenger.getRestricted({ + name: 'LoggingController', + }), + state: initState.LoggingController, + }); + // instance of a class that wraps the extension's storage local API. this.localStoreApiWrapper = opts.localStore; @@ -307,8 +315,18 @@ export default class MetamaskController extends EventEmitter { this.createVaultMutex = new Mutex(); this.extension.runtime.onInstalled.addListener((details) => { - if (details.reason === 'update' && version === '8.1.0') { - this.platform.openExtensionInBrowser(); + if (details.reason === 'update') { + if (version === '8.1.0') { + this.platform.openExtensionInBrowser(); + } + this.loggingController.add({ + type: LogType.GenericLog, + data: { + event: LOG_EVENT.VERSION_UPDATE, + previousVersion: details.previousVersion, + version, + }, + }); } }); @@ -352,13 +370,6 @@ export default class MetamaskController extends EventEmitter { ], }); - this.loggingController = new LoggingController({ - messenger: this.controllerMessenger.getRestricted({ - name: 'LoggingController', - }), - state: initState.LoggingController, - }); - ///: BEGIN:ONLY_INCLUDE_IN(build-mmi) this.mmiConfigurationController = new MmiConfigurationController({ initState: initState.MmiConfigurationController, diff --git a/app/scripts/metamask-controller.test.js b/app/scripts/metamask-controller.test.js index 40ad6d240527..6cd7bfc51095 100644 --- a/app/scripts/metamask-controller.test.js +++ b/app/scripts/metamask-controller.test.js @@ -16,12 +16,14 @@ import { } from '@metamask/phishing-controller'; import { NetworkType } from '@metamask/controller-utils'; import { ControllerMessenger } from '@metamask/base-controller'; +import { LoggingController, LogType } from '@metamask/logging-controller'; import { TransactionStatus } from '../../shared/constants/transaction'; import createTxMeta from '../../test/lib/createTxMeta'; import { NETWORK_TYPES } from '../../shared/constants/network'; import { createTestProviderTools } from '../../test/stub/provider'; import { HardwareDeviceNames } from '../../shared/constants/hardware-wallets'; import { KeyringType } from '../../shared/constants/keyring'; +import { LOG_EVENT } from '../../shared/constants/logs'; import { deferredPromise } from './lib/util'; import TransactionController from './controllers/transactions'; import MetaMaskController from './metamask-controller'; @@ -337,6 +339,64 @@ describe('MetaMaskController', () => { }); }); + describe('on new version install', () => { + const mockOnInstalledEventDetails = { + reason: 'update', + previousVersion: '1.0.0', + }; + browserPolyfillMock.runtime.onInstalled.addListener.mockImplementation( + (handler) => { + handler(mockOnInstalledEventDetails); + }, + ); + + it('should details with LoggingController', async () => { + const mockVersion = '1.3.7'; + const mockGetVersionInfo = jest.fn().mockReturnValue(mockVersion); + + jest.spyOn(LoggingController.prototype, 'add'); + + const localController = new MetaMaskController({ + initLangCode: 'en_US', + platform: { + getVersion: mockGetVersionInfo, + }, + browser: browserPolyfillMock, + infuraProjectId: 'foo', + }); + + expect(localController.loggingController.add).toHaveBeenCalledTimes(1); + expect(localController.loggingController.add).toHaveBeenCalledWith({ + type: LogType.GenericLog, + data: { + event: LOG_EVENT.VERSION_UPDATE, + previousVersion: mockOnInstalledEventDetails.previousVersion, + version: mockVersion, + }, + }); + }); + + it('should openExtensionInBrowser if version is 8.1.0', () => { + const mockVersion = '8.1.0'; + const mockGetVersionInfo = jest.fn().mockReturnValue(mockVersion); + + const openExtensionInBrowserMock = jest.fn(); + + // eslint-disable-next-line no-new + new MetaMaskController({ + initLangCode: 'en_US', + platform: { + getVersion: mockGetVersionInfo, + openExtensionInBrowser: openExtensionInBrowserMock, + }, + browser: browserPolyfillMock, + infuraProjectId: 'foo', + }); + + expect(openExtensionInBrowserMock).toHaveBeenCalledTimes(1); + }); + }); + describe('#importAccountWithStrategy', () => { const importPrivkey = '4cfd3e90fc78b0f86bf7524722150bb8da9c60cd532564d7ff43f5716514f553'; diff --git a/shared/constants/logs.ts b/shared/constants/logs.ts new file mode 100644 index 000000000000..1965a4cc0cd0 --- /dev/null +++ b/shared/constants/logs.ts @@ -0,0 +1,4 @@ +// This constant used for event property while logging events by LoggingController +export const LOG_EVENT = { + VERSION_UPDATE: 'Extension version update', +}; diff --git a/ui/index.js b/ui/index.js index e715e38faef2..aa3dea6f393c 100644 --- a/ui/index.js +++ b/ui/index.js @@ -240,14 +240,27 @@ function setupStateHooks(store) { const reduxState = store.getState(); return maskObject(reduxState, SENTRY_UI_STATE); }; + window.stateHooks.getLogs = function () { + // These logs are logged by LoggingController + const reduxState = store.getState(); + const { logs } = reduxState.metamask; + + const logsArray = Object.values(logs).sort((a, b) => { + return a.timestamp - b.timestamp; + }); + + return logsArray; + }; } window.logStateString = async function (cb) { const state = await window.stateHooks.getCleanAppState(); + const logs = window.stateHooks.getLogs(); browser.runtime .getPlatformInfo() .then((platform) => { state.platform = platform; + state.logs = logs; const stateString = JSON.stringify(state, null, 2); cb(null, stateString); })