1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-25 11:28:51 +01:00

Capture app and migration version (#20458)

* Add AppMetadataController so current and previous application and migration version can be captured in sentry

* Add currentAppVersion, previousAppVersion, previousMigrationVersion, currentMigrationVersion to SENTRY_OBJECT

* Update app/scripts/controllers/app-metadata.ts

Co-authored-by: Mark Stacey <markjstacey@gmail.com>

* Update app/scripts/controllers/app-metadata.ts

Co-authored-by: Mark Stacey <markjstacey@gmail.com>

* Update app/scripts/controllers/app-metadata.ts

Co-authored-by: Mark Stacey <markjstacey@gmail.com>

* Fix types

* Add tests for app-metadata.test.ts

* Lint fixes

* Modify loadStateFromPersistence to return the whole versionData object, so that the migration version can be passed to the metamask-controller on instantiation

* Remove reference to implementation details in test descriptions in app/scripts/controllers/app-metadata.test.ts

* Reset all mocks afterEach in AppMetadataController

* Refactor AppMetadataController to be passed version instead of calling platform.version directly (for ease of unit testing the MetaMask Controller)

* Make maybeUpdateAppVersion and maybeUpdateMigrationVersion private, and remove unit tests of those specific functions

---------

Co-authored-by: Mark Stacey <markjstacey@gmail.com>
This commit is contained in:
Dan J Miller 2023-08-16 12:22:38 -02:30 committed by Dan Miller
parent 594dde58b1
commit 789122d587
5 changed files with 225 additions and 2 deletions

View File

@ -264,7 +264,8 @@ browser.runtime.onConnectExternal.addListener(async (...args) => {
*/
async function initialize() {
try {
const initState = await loadStateFromPersistence();
const initData = await loadStateFromPersistence();
const initState = initData.data;
const initLangCode = await getFirstPreferredLangCode();
///: BEGIN:ONLY_INCLUDE_IN(desktop)
@ -287,6 +288,7 @@ async function initialize() {
initLangCode,
{},
isFirstMetaMaskControllerSetup,
initData.meta,
);
if (!isManifestV3) {
await loadPhishingWarningPage();
@ -417,7 +419,7 @@ export async function loadStateFromPersistence() {
localStore.set(versionedData.data);
// return just the data
return versionedData.data;
return versionedData;
}
/**
@ -430,12 +432,14 @@ export async function loadStateFromPersistence() {
* @param {string} initLangCode - The region code for the language preferred by the current user.
* @param {object} overrides - object with callbacks that are allowed to override the setup controller logic (usefull for desktop app)
* @param isFirstMetaMaskControllerSetup
* @param {object} stateMetadata - Metadata about the initial state and migrations, including the most recent migration version
*/
export function setupController(
initState,
initLangCode,
overrides,
isFirstMetaMaskControllerSetup,
stateMetadata,
) {
//
// MetaMask Controller
@ -462,6 +466,7 @@ export function setupController(
localStore,
overrides,
isFirstMetaMaskControllerSetup,
currentMigrationVersion: stateMetadata.version,
});
setupEnsIpfsResolver({

View File

@ -0,0 +1,104 @@
import assert from 'assert';
import AppMetadataController from './app-metadata';
const EXPECTED_DEFAULT_STATE = {
currentAppVersion: '',
previousAppVersion: '',
previousMigrationVersion: 0,
currentMigrationVersion: 0,
};
describe('AppMetadataController', () => {
describe('constructor', () => {
it('accepts initial state and does not modify it if currentMigrationVersion and platform.getVersion() match respective values in state', async () => {
const initState = {
currentAppVersion: '1',
previousAppVersion: '1',
previousMigrationVersion: 1,
currentMigrationVersion: 1,
};
const appMetadataController = new AppMetadataController({
state: initState,
currentMigrationVersion: 1,
currentAppVersion: '1',
});
assert.deepStrictEqual(appMetadataController.store.getState(), initState);
});
it('sets default state and does not modify it', async () => {
const appMetadataController = new AppMetadataController({
state: {},
});
assert.deepStrictEqual(
appMetadataController.store.getState(),
EXPECTED_DEFAULT_STATE,
);
});
it('sets default state and does not modify it if options version parameters match respective default values', async () => {
const appMetadataController = new AppMetadataController({
state: {},
currentMigrationVersion: 0,
currentAppVersion: '',
});
assert.deepStrictEqual(
appMetadataController.store.getState(),
EXPECTED_DEFAULT_STATE,
);
});
it('updates the currentAppVersion state property if options.currentAppVersion does not match the default value', async () => {
const appMetadataController = new AppMetadataController({
state: {},
currentMigrationVersion: 0,
currentAppVersion: '1',
});
assert.deepStrictEqual(appMetadataController.store.getState(), {
...EXPECTED_DEFAULT_STATE,
currentAppVersion: '1',
});
});
it('updates the currentAppVersion and previousAppVersion state properties if options.currentAppVersion, currentAppVersion and previousAppVersion are all different', async () => {
const appMetadataController = new AppMetadataController({
state: {
currentAppVersion: '2',
previousAppVersion: '1',
},
currentAppVersion: '3',
currentMigrationVersion: 0,
});
assert.deepStrictEqual(appMetadataController.store.getState(), {
...EXPECTED_DEFAULT_STATE,
currentAppVersion: '3',
previousAppVersion: '2',
});
});
it('updates the currentMigrationVersion state property if the currentMigrationVersion param does not match the default value', async () => {
const appMetadataController = new AppMetadataController({
state: {},
currentMigrationVersion: 1,
});
assert.deepStrictEqual(appMetadataController.store.getState(), {
...EXPECTED_DEFAULT_STATE,
currentMigrationVersion: 1,
});
});
it('updates the currentMigrationVersion and previousMigrationVersion state properties if the currentMigrationVersion param, the currentMigrationVersion state property and the previousMigrationVersion state property are all different', async () => {
const appMetadataController = new AppMetadataController({
state: {
currentMigrationVersion: 2,
previousMigrationVersion: 1,
},
currentMigrationVersion: 3,
});
assert.deepStrictEqual(appMetadataController.store.getState(), {
...EXPECTED_DEFAULT_STATE,
currentMigrationVersion: 3,
previousMigrationVersion: 2,
});
});
});
});

View File

@ -0,0 +1,99 @@
import EventEmitter from 'events';
import { ObservableStore } from '@metamask/obs-store';
/**
* The state of the AppMetadataController
*/
export type AppMetadataControllerState = {
currentAppVersion: string;
previousAppVersion: string;
previousMigrationVersion: number;
currentMigrationVersion: number;
};
/**
* The options that NetworkController takes.
*/
export type AppMetadataControllerOptions = {
currentMigrationVersion?: number;
currentAppVersion?: string;
state?: Partial<AppMetadataControllerState>;
};
const defaultState: AppMetadataControllerState = {
currentAppVersion: '',
previousAppVersion: '',
previousMigrationVersion: 0,
currentMigrationVersion: 0,
};
/**
* The AppMetadata controller stores metadata about the current extension instance,
* including the currently and previously installed versions, and the most recently
* run migration.
*
*/
export default class AppMetadataController extends EventEmitter {
/**
* Observable store containing controller data.
*/
store: ObservableStore<AppMetadataControllerState>;
/**
* Constructs a AppMetadata controller.
*
* @param options - the controller options
* @param options.state - Initial controller state.
* @param options.currentMigrationVersion
* @param options.currentAppVersion
*/
constructor({
currentAppVersion = '',
currentMigrationVersion = 0,
state = {},
}: AppMetadataControllerOptions) {
super();
this.store = new ObservableStore({
...defaultState,
...state,
});
this.#maybeUpdateAppVersion(currentAppVersion);
this.#maybeUpdateMigrationVersion(currentMigrationVersion);
}
/**
* Updates the currentAppVersion in state, and sets the previousAppVersion to the old currentAppVersion.
*
* @param maybeNewAppVersion
*/
#maybeUpdateAppVersion(maybeNewAppVersion: string): void {
const oldCurrentAppVersion = this.store.getState().currentAppVersion;
if (maybeNewAppVersion !== oldCurrentAppVersion) {
this.store.updateState({
currentAppVersion: maybeNewAppVersion,
previousAppVersion: oldCurrentAppVersion,
});
}
}
/**
* Updates the migrationVersion in state.
*
* @param maybeNewMigrationVersion
*/
#maybeUpdateMigrationVersion(maybeNewMigrationVersion: number): void {
const oldCurrentMigrationVersion =
this.store.getState().currentMigrationVersion;
if (maybeNewMigrationVersion !== oldCurrentMigrationVersion) {
this.store.updateState({
previousMigrationVersion: oldCurrentMigrationVersion,
currentMigrationVersion: maybeNewMigrationVersion,
});
}
}
}

View File

@ -35,9 +35,11 @@ export const SENTRY_STATE = {
connectedStatusPopoverHasBeenShown: true,
conversionDate: true,
conversionRate: true,
currentAppVersion: true,
currentBlockGasLimit: true,
currentCurrency: true,
currentLocale: true,
currentMigrationVersion: true,
customNonceValue: true,
defaultHomeActiveTabName: true,
desktopEnabled: true,
@ -56,6 +58,8 @@ export const SENTRY_STATE = {
nextNonce: true,
participateInMetaMetrics: true,
preferences: true,
previousAppVersion: true,
previousMigrationVersion: true,
providerConfig: {
nickname: true,
ticker: true,

View File

@ -192,6 +192,7 @@ import createMetaRPCHandler from './lib/createMetaRPCHandler';
import { previousValueComparator } from './lib/util';
import createMetamaskMiddleware from './lib/createMetamaskMiddleware';
import EncryptionPublicKeyController from './controllers/encryption-public-key';
import AppMetadataController from './controllers/app-metadata';
import {
CaveatMutatorFactories,
@ -258,6 +259,8 @@ export default class MetamaskController extends EventEmitter {
// instance of a class that wraps the extension's storage local API.
this.localStoreApiWrapper = opts.localStore;
this.currentMigrationVersion = opts.currentMigrationVersion;
// observable state store
this.store = new ComposableObservableStore({
state: initState,
@ -278,6 +281,12 @@ export default class MetamaskController extends EventEmitter {
}
});
this.appMetadataController = new AppMetadataController({
state: initState.AppMetadataController,
currentMigrationVersion: this.currentMigrationVersion,
currentAppVersion: version,
});
// next, we will initialize the controllers
// controller initialization order matters
@ -1506,6 +1515,7 @@ export default class MetamaskController extends EventEmitter {
this.store.updateStructure({
AppStateController: this.appStateController.store,
AppMetadataController: this.appMetadataController.store,
TransactionController: this.txController.store,
KeyringController: this.keyringController.store,
PreferencesController: this.preferencesController.store,
@ -1550,6 +1560,7 @@ export default class MetamaskController extends EventEmitter {
this.memStore = new ComposableObservableStore({
config: {
AppStateController: this.appStateController.store,
AppMetadataController: this.appMetadataController.store,
NetworkController: this.networkController,
CachedBalancesController: this.cachedBalancesController.store,
KeyringController: this.keyringController.memStore,