Skip to content

Commit

Permalink
Add logs on new extension version updates and put them in state logs (#…
Browse files Browse the repository at this point in the history
…21077)

## **Description**

This PR aims to implement logs on new extension version install & first
time event. LoggingController logs are intended to extend "State Logs",
hence this PR also includes that change. Later, we will also implement
`SignatureController` logs on this task:
MetaMask/MetaMask-planning#1321

## **Manual testing steps**

I couldn't find a appropriate way to test this manually because
`onInstalled` event (my guess) could only triggered by Chrome itself.
Tested these but no luck
1. Change package.json versions on dev env back and forth
2. Change dist versions back and forth & use "update" button on chrome
extension tab
3. Changed dist version manifest.json manually and "update" button on
chrome extension tab

These are updating the version of extension which shown in the extension
tab (chrome://extensions/) but not triggering the event.

But the log exporter works as expected because earlier I made some tests
for signatures, here is the exported JSON.

[MetaMask state
logs.txt](https://github.com/MetaMask/metamask-extension/files/12740832/MetaMask.state.logs.txt)

## **Screenshots/Recordings**

N/A

## **Related issues**

Fixes #19478

## **Pre-merge author checklist**

- [X] I’ve followed [MetaMask Coding
Standards](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/CODING_GUIDELINES.md).
- [X] I've clearly explained:
  - [X] What problem this PR is solving.
  - [X] How this problem was solved.
  - [X] How reviewers can test my changes.
- [X] I’ve indicated what issue this PR is linked to: Fixes #???
- [X] I’ve included tests if applicable.
- [X] I’ve documented any added code.
- [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)).
- [X] I’ve properly set the pull request status:
  - [X] In case it's not yet "ready for review", I've set it to "draft".
- [X] In case it's "ready for review", I've changed it from "draft" to
"non-draft".

## **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.
  • Loading branch information
OGPoyraz authored Oct 4, 2023
1 parent e269310 commit 23f3f60
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 10 deletions.
31 changes: 21 additions & 10 deletions app/scripts/metamask-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -156,6 +156,7 @@ import {
MetaMetricsEventCategory,
MetaMetricsEventName,
} from '../../shared/constants/metametrics';
import { LOG_EVENT } from '../../shared/constants/logs';

import {
getTokenIdParam,
Expand Down Expand Up @@ -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;

Expand All @@ -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,
},
});
}
});

Expand Down Expand Up @@ -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,
Expand Down
60 changes: 60 additions & 0 deletions app/scripts/metamask-controller.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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';
Expand Down
4 changes: 4 additions & 0 deletions shared/constants/logs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// This constant used for event property while logging events by LoggingController
export const LOG_EVENT = {
VERSION_UPDATE: 'Extension version update',
};
13 changes: 13 additions & 0 deletions ui/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
})
Expand Down

0 comments on commit 23f3f60

Please sign in to comment.