From b34b4ffaf09bfc42d56f2db3b56b97825c804517 Mon Sep 17 00:00:00 2001 From: Frank von Hoven <141057783+frankvonhoven@users.noreply.github.com> Date: Wed, 12 Feb 2025 13:39:37 -0600 Subject: [PATCH 1/3] Init app metadata controller & tests --- .../src/AppMetadataController.test.ts | 175 ++++++++++++++++++ .../src/AppMetadataController.ts | 149 +++++++++++++++ 2 files changed, 324 insertions(+) create mode 100644 packages/app-metadata-controller/src/AppMetadataController.test.ts create mode 100644 packages/app-metadata-controller/src/AppMetadataController.ts diff --git a/packages/app-metadata-controller/src/AppMetadataController.test.ts b/packages/app-metadata-controller/src/AppMetadataController.test.ts new file mode 100644 index 00000000000..7a0e9f47ced --- /dev/null +++ b/packages/app-metadata-controller/src/AppMetadataController.test.ts @@ -0,0 +1,175 @@ +// packages/app-metadata/src/types.ts +import { ControllerGetStateAction, ControllerStateChangeEvent, RestrictedMessenger } from '@metamask/base-controller'; + +// Unique name for the controller +export const controllerName = 'AppMetadataController'; + +/** + * The state of the AppMetadataController + */ +export type AppMetadataControllerState = { + currentAppVersion: string; + previousAppVersion: string; + previousMigrationVersion: number; + currentMigrationVersion: number; +}; + +/** + * Function to get default state of the {@link AppMetadataController}. + */ +export const getDefaultAppMetadataControllerState = (): AppMetadataControllerState => ({ + currentAppVersion: '', + previousAppVersion: '', + previousMigrationVersion: 0, + currentMigrationVersion: 0, +}); + +/** + * Returns the state of the {@link AppMetadataController}. + */ +export type AppMetadataControllerGetStateAction = ControllerGetStateAction< + typeof controllerName, + AppMetadataControllerState +>; + +/** + * Event emitted when the state of the {@link AppMetadataController} changes. + */ +export type AppMetadataControllerStateChangeEvent = ControllerStateChangeEvent< + typeof controllerName, + AppMetadataControllerState +>; + +/** + * Actions exposed by the {@link AppMetadataController}. + */ +export type AppMetadataControllerActions = AppMetadataControllerGetStateAction; + +/** + * Events emitted by the {@link AppMetadataController}. + */ +export type AppMetadataControllerEvents = AppMetadataControllerStateChangeEvent; + +/** + * Messenger type for the {@link AppMetadataController}. + */ +export type AppMetadataControllerMessenger = RestrictedMessenger< + typeof controllerName, + AppMetadataControllerActions, + AppMetadataControllerEvents, + never, + never +>; + +/** + * Metadata configuration for state persistence and anonymity. + */ +export const controllerMetadata = { + currentAppVersion: { persist: true, anonymous: true }, + previousAppVersion: { persist: true, anonymous: true }, + previousMigrationVersion: { persist: true, anonymous: true }, + currentMigrationVersion: { persist: true, anonymous: true }, +}; + +// packages/app-metadata/src/AppMetadataController.ts +import { BaseController } from '@metamask/base-controller'; +import { + AppMetadataControllerState, + getDefaultAppMetadataControllerState, + AppMetadataControllerMessenger, + controllerMetadata, + controllerName, +} from './types'; + +/** + * The AppMetadata controller stores metadata about the current application, + * including versioning and migration history. + */ +export default class AppMetadataController extends BaseController< + typeof controllerName, + AppMetadataControllerState, + AppMetadataControllerMessenger +> { + /** + * Constructs an AppMetadataController. + * + * @param options - Controller options. + */ + constructor({ + state = {}, + messenger, + currentAppVersion = '', + currentMigrationVersion = 0, + }: { + state?: Partial; + messenger: AppMetadataControllerMessenger; + currentAppVersion?: string; + currentMigrationVersion?: number; + }) { + super({ + name: controllerName, + metadata: controllerMetadata, + state: { ...getDefaultAppMetadataControllerState(), ...state }, + messenger, + }); + + this.#maybeUpdateAppVersion(currentAppVersion); + this.#maybeUpdateMigrationVersion(currentMigrationVersion); + } + + /** + * Updates the app version in state, tracking previous versions. + */ + #maybeUpdateAppVersion(maybeNewAppVersion: string): void { + const oldVersion = this.state.currentAppVersion; + if (maybeNewAppVersion !== oldVersion) { + this.update((state) => { + state.currentAppVersion = maybeNewAppVersion; + state.previousAppVersion = oldVersion; + }); + } + } + + /** + * Updates the migration version in state. + */ + #maybeUpdateMigrationVersion(maybeNewMigrationVersion: number): void { + const oldMigrationVersion = this.state.currentMigrationVersion; + if (maybeNewMigrationVersion !== oldMigrationVersion) { + this.update((state) => { + state.previousMigrationVersion = oldMigrationVersion; + state.currentMigrationVersion = maybeNewMigrationVersion; + }); + } + } +} + +// packages/app-metadata/src/index.ts +export { default as AppMetadataController } from './AppMetadataController'; +export * from './types'; + +// packages/app-metadata/tests/AppMetadataController.test.ts +import { AppMetadataController, getDefaultAppMetadataControllerState } from '../src'; + +describe('AppMetadataController', () => { + it('should initialize with default state', () => { + const controller = new AppMetadataController({ messenger: {} as any }); + expect(controller.state).toEqual(getDefaultAppMetadataControllerState()); + }); + + it('should update the app version correctly', () => { + const controller = new AppMetadataController({ messenger: {} as any, currentAppVersion: '1.0.0' }); + expect(controller.state.currentAppVersion).toBe('1.0.0'); + controller["#maybeUpdateAppVersion"]('1.0.1'); + expect(controller.state.previousAppVersion).toBe('1.0.0'); + expect(controller.state.currentAppVersion).toBe('1.0.1'); + }); + + it('should update the migration version correctly', () => { + const controller = new AppMetadataController({ messenger: {} as any, currentMigrationVersion: 1 }); + expect(controller.state.currentMigrationVersion).toBe(1); + controller["#maybeUpdateMigrationVersion"](2); + expect(controller.state.previousMigrationVersion).toBe(1); + expect(controller.state.currentMigrationVersion).toBe(2); + }); +}); diff --git a/packages/app-metadata-controller/src/AppMetadataController.ts b/packages/app-metadata-controller/src/AppMetadataController.ts new file mode 100644 index 00000000000..05333610ff3 --- /dev/null +++ b/packages/app-metadata-controller/src/AppMetadataController.ts @@ -0,0 +1,149 @@ +// packages/app-metadata/src/types.ts +import { ControllerGetStateAction, ControllerStateChangeEvent, RestrictedMessenger } from '@metamask/base-controller'; + +// Unique name for the controller +export const controllerName = 'AppMetadataController'; + +/** + * The state of the AppMetadataController + */ +export type AppMetadataControllerState = { + currentAppVersion: string; + previousAppVersion: string; + previousMigrationVersion: number; + currentMigrationVersion: number; +}; + +/** + * Function to get default state of the {@link AppMetadataController}. + */ +export const getDefaultAppMetadataControllerState = (): AppMetadataControllerState => ({ + currentAppVersion: '', + previousAppVersion: '', + previousMigrationVersion: 0, + currentMigrationVersion: 0, +}); + +/** + * Returns the state of the {@link AppMetadataController}. + */ +export type AppMetadataControllerGetStateAction = ControllerGetStateAction< + typeof controllerName, + AppMetadataControllerState +>; + +/** + * Event emitted when the state of the {@link AppMetadataController} changes. + */ +export type AppMetadataControllerStateChangeEvent = ControllerStateChangeEvent< + typeof controllerName, + AppMetadataControllerState +>; + +/** + * Actions exposed by the {@link AppMetadataController}. + */ +export type AppMetadataControllerActions = AppMetadataControllerGetStateAction; + +/** + * Events emitted by the {@link AppMetadataController}. + */ +export type AppMetadataControllerEvents = AppMetadataControllerStateChangeEvent; + +/** + * Messenger type for the {@link AppMetadataController}. + */ +export type AppMetadataControllerMessenger = RestrictedMessenger< + typeof controllerName, + AppMetadataControllerActions, + AppMetadataControllerEvents, + never, + never +>; + +/** + * Metadata configuration for state persistence and anonymity. + */ +export const controllerMetadata = { + currentAppVersion: { persist: true, anonymous: true }, + previousAppVersion: { persist: true, anonymous: true }, + previousMigrationVersion: { persist: true, anonymous: true }, + currentMigrationVersion: { persist: true, anonymous: true }, +}; + +// packages/app-metadata/src/AppMetadataController.ts +import { BaseController } from '@metamask/base-controller'; +import { + AppMetadataControllerState, + getDefaultAppMetadataControllerState, + AppMetadataControllerMessenger, + controllerMetadata, + controllerName, +} from './types'; + +/** + * The AppMetadata controller stores metadata about the current application, + * including versioning and migration history. + */ +export default class AppMetadataController extends BaseController< + typeof controllerName, + AppMetadataControllerState, + AppMetadataControllerMessenger +> { + /** + * Constructs an AppMetadataController. + * + * @param options - Controller options. + */ + constructor({ + state = {}, + messenger, + currentAppVersion = '', + currentMigrationVersion = 0, + }: { + state?: Partial; + messenger: AppMetadataControllerMessenger; + currentAppVersion?: string; + currentMigrationVersion?: number; + }) { + super({ + name: controllerName, + metadata: controllerMetadata, + state: { ...getDefaultAppMetadataControllerState(), ...state }, + messenger, + }); + + this.#maybeUpdateAppVersion(currentAppVersion); + this.#maybeUpdateMigrationVersion(currentMigrationVersion); + } + + /** + * Updates the app version in state, tracking previous versions. + */ + #maybeUpdateAppVersion(maybeNewAppVersion: string): void { + const oldVersion = this.state.currentAppVersion; + if (maybeNewAppVersion !== oldVersion) { + this.update((state) => { + state.currentAppVersion = maybeNewAppVersion; + state.previousAppVersion = oldVersion; + }); + } + } + + /** + * Updates the migration version in state. + */ + #maybeUpdateMigrationVersion(maybeNewMigrationVersion: number): void { + const oldMigrationVersion = this.state.currentMigrationVersion; + if (maybeNewMigrationVersion !== oldMigrationVersion) { + this.update((state) => { + state.previousMigrationVersion = oldMigrationVersion; + state.currentMigrationVersion = maybeNewMigrationVersion; + }); + } + } +} + +// packages/app-metadata/src/index.ts +export { default as AppMetadataController } from './AppMetadataController'; +export * from './types'; From 24a23c13c4ba94b6a5171917a3ba6aa0bb49de0d Mon Sep 17 00:00:00 2001 From: Frank von Hoven <141057783+frankvonhoven@users.noreply.github.com> Date: Wed, 12 Feb 2025 13:50:51 -0600 Subject: [PATCH 2/3] Add package json --- packages/app-metadata-controller/package.json | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 packages/app-metadata-controller/package.json diff --git a/packages/app-metadata-controller/package.json b/packages/app-metadata-controller/package.json new file mode 100644 index 00000000000..1c37933c3a9 --- /dev/null +++ b/packages/app-metadata-controller/package.json @@ -0,0 +1,73 @@ +{ + "name": "@metamask/app-metadata-controller", + "version": "1.0.0", + "description": "Manages requests that for app metadata", + "keywords": [ + "MetaMask", + "Ethereum" + ], + "homepage": "https://github.com/MetaMask/core/tree/main/packages/app-metadata-controller#readme", + "bugs": { + "url": "https://github.com/MetaMask/core/issues" + }, + "repository": { + "type": "git", + "url": "https://github.com/MetaMask/core.git" + }, + "license": "MIT", + "sideEffects": false, + "exports": { + ".": { + "import": { + "types": "./dist/index.d.mts", + "default": "./dist/index.mjs" + }, + "require": { + "types": "./dist/index.d.cts", + "default": "./dist/index.cjs" + } + }, + "./package.json": "./package.json" + }, + "main": "./dist/index.cjs", + "types": "./dist/index.d.cts", + "files": [ + "dist/" + ], + "scripts": { + "build": "ts-bridge --project tsconfig.build.json --verbose --clean --no-references", + "build:docs": "typedoc", + "changelog:update": "../../scripts/update-changelog.sh @metamask/app-metadata-controller", + "changelog:validate": "../../scripts/validate-changelog.sh @metamask/app-metadata-controller", + "publish:preview": "yarn npm publish --tag preview", + "since-latest-release": "../../scripts/since-latest-release.sh", + "test": "NODE_OPTIONS=--experimental-vm-modules jest --reporters=jest-silent-reporter", + "test:clean": "NODE_OPTIONS=--experimental-vm-modules jest --clearCache", + "test:verbose": "NODE_OPTIONS=--experimental-vm-modules jest --verbose", + "test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --watch" + }, + "dependencies": { + "@metamask/base-controller": "^8.0.0", + "@metamask/rpc-errors": "^7.0.2", + "@metamask/utils": "^11.1.0", + "nanoid": "^3.3.8" + }, + "devDependencies": { + "@metamask/auto-changelog": "^3.4.4", + "@types/jest": "^27.4.1", + "deepmerge": "^4.2.2", + "jest": "^27.5.1", + "sinon": "^9.2.4", + "ts-jest": "^27.1.4", + "typedoc": "^0.24.8", + "typedoc-plugin-missing-exports": "^2.0.0", + "typescript": "~5.2.2" + }, + "engines": { + "node": "^18.18 || >=20" + }, + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org/" + } +} From fc71cbb0199b054bc60e22f0985d2930ef66ce26 Mon Sep 17 00:00:00 2001 From: Frank von Hoven Date: Thu, 13 Feb 2025 10:23:20 -0600 Subject: [PATCH 3/3] Update the module and tests passing --- packages/app-metadata-controller/LICENSE | 20 ++ packages/app-metadata-controller/README.md | 15 + .../app-metadata-controller/jest.config.js | 26 ++ .../src/AppMetadataController.test.ts | 295 ++++++++---------- .../src/AppMetadataController.ts | 149 +++++---- .../tsconfig.build.json | 13 + .../app-metadata-controller/tsconfig.json | 11 + packages/app-metadata-controller/typedoc.json | 7 + 8 files changed, 321 insertions(+), 215 deletions(-) create mode 100644 packages/app-metadata-controller/LICENSE create mode 100644 packages/app-metadata-controller/README.md create mode 100644 packages/app-metadata-controller/jest.config.js create mode 100644 packages/app-metadata-controller/tsconfig.build.json create mode 100644 packages/app-metadata-controller/tsconfig.json create mode 100644 packages/app-metadata-controller/typedoc.json diff --git a/packages/app-metadata-controller/LICENSE b/packages/app-metadata-controller/LICENSE new file mode 100644 index 00000000000..ddfbecf9020 --- /dev/null +++ b/packages/app-metadata-controller/LICENSE @@ -0,0 +1,20 @@ +MIT License + +Copyright (c) 2018 MetaMask + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE diff --git a/packages/app-metadata-controller/README.md b/packages/app-metadata-controller/README.md new file mode 100644 index 00000000000..2f7316b10a8 --- /dev/null +++ b/packages/app-metadata-controller/README.md @@ -0,0 +1,15 @@ +# `@metamask/app-metadata-controller` + +Manages the Metadata for the App + +## Installation + +`yarn add @metamask/app-metadata-controller` + +or + +`npm install @metamask/app-metadata-controller` + +## Contributing + +This package is part of a monorepo. Instructions for contributing can be found in the [monorepo README](https://github.com/MetaMask/core#readme). diff --git a/packages/app-metadata-controller/jest.config.js b/packages/app-metadata-controller/jest.config.js new file mode 100644 index 00000000000..ca084133399 --- /dev/null +++ b/packages/app-metadata-controller/jest.config.js @@ -0,0 +1,26 @@ +/* + * For a detailed explanation regarding each configuration property and type check, visit: + * https://jestjs.io/docs/configuration + */ + +const merge = require('deepmerge'); +const path = require('path'); + +const baseConfig = require('../../jest.config.packages'); + +const displayName = path.basename(__dirname); + +module.exports = merge(baseConfig, { + // The display name when running multiple projects + displayName, + + // An object that configures minimum threshold enforcement for coverage results + coverageThreshold: { + global: { + branches: 100, + functions: 100, + lines: 100, + statements: 100, + }, + }, +}); diff --git a/packages/app-metadata-controller/src/AppMetadataController.test.ts b/packages/app-metadata-controller/src/AppMetadataController.test.ts index 7a0e9f47ced..01c43510b3a 100644 --- a/packages/app-metadata-controller/src/AppMetadataController.test.ts +++ b/packages/app-metadata-controller/src/AppMetadataController.test.ts @@ -1,175 +1,154 @@ -// packages/app-metadata/src/types.ts -import { ControllerGetStateAction, ControllerStateChangeEvent, RestrictedMessenger } from '@metamask/base-controller'; - -// Unique name for the controller -export const controllerName = 'AppMetadataController'; - -/** - * The state of the AppMetadataController - */ -export type AppMetadataControllerState = { - currentAppVersion: string; - previousAppVersion: string; - previousMigrationVersion: number; - currentMigrationVersion: number; -}; - -/** - * Function to get default state of the {@link AppMetadataController}. - */ -export const getDefaultAppMetadataControllerState = (): AppMetadataControllerState => ({ - currentAppVersion: '', - previousAppVersion: '', - previousMigrationVersion: 0, - currentMigrationVersion: 0, -}); - -/** - * Returns the state of the {@link AppMetadataController}. - */ -export type AppMetadataControllerGetStateAction = ControllerGetStateAction< - typeof controllerName, - AppMetadataControllerState ->; - -/** - * Event emitted when the state of the {@link AppMetadataController} changes. - */ -export type AppMetadataControllerStateChangeEvent = ControllerStateChangeEvent< - typeof controllerName, - AppMetadataControllerState ->; +import { Messenger } from '@metamask/base-controller'; +import AppMetadataController, { + getDefaultAppMetadataControllerState, + type AppMetadataControllerOptions, +} from './AppMetadataController'; -/** - * Actions exposed by the {@link AppMetadataController}. - */ -export type AppMetadataControllerActions = AppMetadataControllerGetStateAction; +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, + }; + withController( + { + state: initState, + currentMigrationVersion: 1, + currentAppVersion: '1', + }, + ({ controller }) => { + expect(controller.state).toStrictEqual(initState); + }, + ); + }); -/** - * Events emitted by the {@link AppMetadataController}. - */ -export type AppMetadataControllerEvents = AppMetadataControllerStateChangeEvent; + it('sets default state and does not modify it', () => { + withController({ state: {} }, ({ controller }) => { + expect(controller.state).toStrictEqual( + getDefaultAppMetadataControllerState(), + ); + }); + }); -/** - * Messenger type for the {@link AppMetadataController}. - */ -export type AppMetadataControllerMessenger = RestrictedMessenger< - typeof controllerName, - AppMetadataControllerActions, - AppMetadataControllerEvents, - never, - never ->; + it('sets default state and does not modify it if options version parameters match respective default values', () => { + withController( + { + state: {}, + currentMigrationVersion: 0, + currentAppVersion: '', + }, + ({ controller }) => { + expect(controller.state).toStrictEqual( + getDefaultAppMetadataControllerState(), + ); + }, + ); + }); -/** - * Metadata configuration for state persistence and anonymity. - */ -export const controllerMetadata = { - currentAppVersion: { persist: true, anonymous: true }, - previousAppVersion: { persist: true, anonymous: true }, - previousMigrationVersion: { persist: true, anonymous: true }, - currentMigrationVersion: { persist: true, anonymous: true }, -}; + it('updates the currentAppVersion state property if options.currentAppVersion does not match the default value', () => { + withController( + { + state: {}, + currentMigrationVersion: 0, + currentAppVersion: '1', + }, + ({ controller }) => { + expect(controller.state).toStrictEqual({ + ...getDefaultAppMetadataControllerState(), + currentAppVersion: '1', + }); + }, + ); + }); -// packages/app-metadata/src/AppMetadataController.ts -import { BaseController } from '@metamask/base-controller'; -import { - AppMetadataControllerState, - getDefaultAppMetadataControllerState, - AppMetadataControllerMessenger, - controllerMetadata, - controllerName, -} from './types'; + it('updates the currentAppVersion and previousAppVersion state properties if options.currentAppVersion, currentAppVersion and previousAppVersion are all different', () => { + withController( + { + state: { + currentAppVersion: '2', + previousAppVersion: '1', + }, + currentAppVersion: '3', + currentMigrationVersion: 0, + }, + ({ controller }) => { + expect(controller.state).toStrictEqual({ + ...getDefaultAppMetadataControllerState(), + currentAppVersion: '3', + previousAppVersion: '2', + }); + }, + ); + }); -/** - * The AppMetadata controller stores metadata about the current application, - * including versioning and migration history. - */ -export default class AppMetadataController extends BaseController< - typeof controllerName, - AppMetadataControllerState, - AppMetadataControllerMessenger -> { - /** - * Constructs an AppMetadataController. - * - * @param options - Controller options. - */ - constructor({ - state = {}, - messenger, - currentAppVersion = '', - currentMigrationVersion = 0, - }: { - state?: Partial; - messenger: AppMetadataControllerMessenger; - currentAppVersion?: string; - currentMigrationVersion?: number; - }) { - super({ - name: controllerName, - metadata: controllerMetadata, - state: { ...getDefaultAppMetadataControllerState(), ...state }, - messenger, + it('updates the currentMigrationVersion state property if the currentMigrationVersion param does not match the default value', () => { + withController( + { + state: {}, + currentMigrationVersion: 1, + }, + ({ controller }) => { + expect(controller.state).toStrictEqual({ + ...getDefaultAppMetadataControllerState(), + currentMigrationVersion: 1, + }); + }, + ); }); - this.#maybeUpdateAppVersion(currentAppVersion); - this.#maybeUpdateMigrationVersion(currentMigrationVersion); - } + it('updates the currentMigrationVersion and previousMigrationVersion state properties if the currentMigrationVersion param, the currentMigrationVersion state property and the previousMigrationVersion state property are all different', () => { + withController( + { + state: { + currentMigrationVersion: 2, + previousMigrationVersion: 1, + }, + currentMigrationVersion: 3, + }, + ({ controller }) => { + expect(controller.state).toStrictEqual({ + ...getDefaultAppMetadataControllerState(), + currentMigrationVersion: 3, + previousMigrationVersion: 2, + }); + }, + ); + }); + }); +}); - /** - * Updates the app version in state, tracking previous versions. - */ - #maybeUpdateAppVersion(maybeNewAppVersion: string): void { - const oldVersion = this.state.currentAppVersion; - if (maybeNewAppVersion !== oldVersion) { - this.update((state) => { - state.currentAppVersion = maybeNewAppVersion; - state.previousAppVersion = oldVersion; - }); - } - } +type WithControllerOptions = Partial; - /** - * Updates the migration version in state. - */ - #maybeUpdateMigrationVersion(maybeNewMigrationVersion: number): void { - const oldMigrationVersion = this.state.currentMigrationVersion; - if (maybeNewMigrationVersion !== oldMigrationVersion) { - this.update((state) => { - state.previousMigrationVersion = oldMigrationVersion; - state.currentMigrationVersion = maybeNewMigrationVersion; - }); - } - } -} +type WithControllerCallback = ({ + controller, +}: { + controller: AppMetadataController; +}) => ReturnValue; -// packages/app-metadata/src/index.ts -export { default as AppMetadataController } from './AppMetadataController'; -export * from './types'; +type WithControllerArgs = + | [WithControllerCallback] + | [WithControllerOptions, WithControllerCallback]; -// packages/app-metadata/tests/AppMetadataController.test.ts -import { AppMetadataController, getDefaultAppMetadataControllerState } from '../src'; +function withController( + ...args: WithControllerArgs +): ReturnValue { + const [options = {}, fn] = args.length === 2 ? args : [{}, args[0]]; -describe('AppMetadataController', () => { - it('should initialize with default state', () => { - const controller = new AppMetadataController({ messenger: {} as any }); - expect(controller.state).toEqual(getDefaultAppMetadataControllerState()); - }); + const messenger = new Messenger(); - it('should update the app version correctly', () => { - const controller = new AppMetadataController({ messenger: {} as any, currentAppVersion: '1.0.0' }); - expect(controller.state.currentAppVersion).toBe('1.0.0'); - controller["#maybeUpdateAppVersion"]('1.0.1'); - expect(controller.state.previousAppVersion).toBe('1.0.0'); - expect(controller.state.currentAppVersion).toBe('1.0.1'); + const appMetadataControllerMessenger = messenger.getRestricted({ + name: 'AppMetadataController', + allowedActions: [], + allowedEvents: [], }); - it('should update the migration version correctly', () => { - const controller = new AppMetadataController({ messenger: {} as any, currentMigrationVersion: 1 }); - expect(controller.state.currentMigrationVersion).toBe(1); - controller["#maybeUpdateMigrationVersion"](2); - expect(controller.state.previousMigrationVersion).toBe(1); - expect(controller.state.currentMigrationVersion).toBe(2); + return fn({ + controller: new AppMetadataController({ + messenger: appMetadataControllerMessenger, + ...options, + }), }); -}); +} diff --git a/packages/app-metadata-controller/src/AppMetadataController.ts b/packages/app-metadata-controller/src/AppMetadataController.ts index 05333610ff3..16ebbcaf573 100644 --- a/packages/app-metadata-controller/src/AppMetadataController.ts +++ b/packages/app-metadata-controller/src/AppMetadataController.ts @@ -1,8 +1,22 @@ -// packages/app-metadata/src/types.ts -import { ControllerGetStateAction, ControllerStateChangeEvent, RestrictedMessenger } from '@metamask/base-controller'; +import { + BaseController, + ControllerGetStateAction, + ControllerStateChangeEvent, + RestrictedMessenger, +} from '@metamask/base-controller'; // Unique name for the controller -export const controllerName = 'AppMetadataController'; +const controllerName = 'AppMetadataController'; + +/** + * The options that AppMetadataController takes. + */ +export type AppMetadataControllerOptions = { + state?: Partial; + messenger: AppMetadataControllerMessenger; + currentMigrationVersion?: number; + currentAppVersion?: string; +}; /** * The state of the AppMetadataController @@ -17,12 +31,13 @@ export type AppMetadataControllerState = { /** * Function to get default state of the {@link AppMetadataController}. */ -export const getDefaultAppMetadataControllerState = (): AppMetadataControllerState => ({ - currentAppVersion: '', - previousAppVersion: '', - previousMigrationVersion: 0, - currentMigrationVersion: 0, -}); +export const getDefaultAppMetadataControllerState = + (): AppMetadataControllerState => ({ + currentAppVersion: '', + previousAppVersion: '', + previousMigrationVersion: 0, + currentMigrationVersion: 0, + }); /** * Returns the state of the {@link AppMetadataController}. @@ -32,6 +47,11 @@ export type AppMetadataControllerGetStateAction = ControllerGetStateAction< AppMetadataControllerState >; +/** + * Actions exposed by the {@link AppMetadataController}. + */ +export type AppMetadataControllerActions = AppMetadataControllerGetStateAction; + /** * Event emitted when the state of the {@link AppMetadataController} changes. */ @@ -40,50 +60,60 @@ export type AppMetadataControllerStateChangeEvent = ControllerStateChangeEvent< AppMetadataControllerState >; +export type AppMetadataControllerEvents = AppMetadataControllerStateChangeEvent; + /** - * Actions exposed by the {@link AppMetadataController}. + * Actions that this controller is allowed to call. */ -export type AppMetadataControllerActions = AppMetadataControllerGetStateAction; +type AllowedActions = never; /** - * Events emitted by the {@link AppMetadataController}. + * Events that this controller is allowed to subscribe. */ -export type AppMetadataControllerEvents = AppMetadataControllerStateChangeEvent; +type AllowedEvents = never; /** * Messenger type for the {@link AppMetadataController}. */ -export type AppMetadataControllerMessenger = RestrictedMessenger< +type AppMetadataControllerMessenger = RestrictedMessenger< typeof controllerName, - AppMetadataControllerActions, - AppMetadataControllerEvents, - never, - never + AppMetadataControllerActions | AllowedActions, + AppMetadataControllerEvents | AllowedEvents, + AllowedActions['type'], + AllowedEvents['type'] >; /** - * Metadata configuration for state persistence and anonymity. + * {@link AppMetadataController}'s metadata. + * + * This allows us to choose if fields of the state should be persisted or not + * using the `persist` flag; and if they can be sent to Sentry or not, using + * the `anonymous` flag. */ -export const controllerMetadata = { - currentAppVersion: { persist: true, anonymous: true }, - previousAppVersion: { persist: true, anonymous: true }, - previousMigrationVersion: { persist: true, anonymous: true }, - currentMigrationVersion: { persist: true, anonymous: true }, +const controllerMetadata = { + currentAppVersion: { + persist: true, + anonymous: true, + }, + previousAppVersion: { + persist: true, + anonymous: true, + }, + previousMigrationVersion: { + persist: true, + anonymous: true, + }, + currentMigrationVersion: { + persist: true, + anonymous: true, + }, }; -// packages/app-metadata/src/AppMetadataController.ts -import { BaseController } from '@metamask/base-controller'; -import { - AppMetadataControllerState, - getDefaultAppMetadataControllerState, - AppMetadataControllerMessenger, - controllerMetadata, - controllerName, -} from './types'; - /** - * The AppMetadata controller stores metadata about the current application, - * including versioning and migration history. + * 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 BaseController< typeof controllerName, @@ -91,59 +121,64 @@ export default class AppMetadataController extends BaseController< AppMetadataControllerMessenger > { /** - * Constructs an AppMetadataController. + * Constructs a AppMetadata controller. * - * @param options - Controller options. + * @param options - the controller options + * @param options.state - Initial controller state. + * @param options.messenger - Messenger used to communicate with BaseV2 controller. + * @param options.currentMigrationVersion + * @param options.currentAppVersion */ constructor({ state = {}, messenger, currentAppVersion = '', currentMigrationVersion = 0, - }: { - state?: Partial; - messenger: AppMetadataControllerMessenger; - currentAppVersion?: string; - currentMigrationVersion?: number; - }) { + }: AppMetadataControllerOptions) { super({ name: controllerName, metadata: controllerMetadata, - state: { ...getDefaultAppMetadataControllerState(), ...state }, + state: { + ...getDefaultAppMetadataControllerState(), + ...state, + }, messenger, }); this.#maybeUpdateAppVersion(currentAppVersion); + this.#maybeUpdateMigrationVersion(currentMigrationVersion); } /** - * Updates the app version in state, tracking previous versions. + * Updates the currentAppVersion in state, and sets the previousAppVersion to the old currentAppVersion. + * + * @param maybeNewAppVersion */ #maybeUpdateAppVersion(maybeNewAppVersion: string): void { - const oldVersion = this.state.currentAppVersion; - if (maybeNewAppVersion !== oldVersion) { + const oldCurrentAppVersion = this.state.currentAppVersion; + + if (maybeNewAppVersion !== oldCurrentAppVersion) { this.update((state) => { state.currentAppVersion = maybeNewAppVersion; - state.previousAppVersion = oldVersion; + state.previousAppVersion = oldCurrentAppVersion; }); } } /** - * Updates the migration version in state. + * Updates the migrationVersion in state. + * + * @param maybeNewMigrationVersion */ #maybeUpdateMigrationVersion(maybeNewMigrationVersion: number): void { - const oldMigrationVersion = this.state.currentMigrationVersion; - if (maybeNewMigrationVersion !== oldMigrationVersion) { + const oldCurrentMigrationVersion = this.state.currentMigrationVersion; + + if (maybeNewMigrationVersion !== oldCurrentMigrationVersion) { this.update((state) => { - state.previousMigrationVersion = oldMigrationVersion; + state.previousMigrationVersion = oldCurrentMigrationVersion; state.currentMigrationVersion = maybeNewMigrationVersion; }); } } } - -// packages/app-metadata/src/index.ts -export { default as AppMetadataController } from './AppMetadataController'; -export * from './types'; diff --git a/packages/app-metadata-controller/tsconfig.build.json b/packages/app-metadata-controller/tsconfig.build.json new file mode 100644 index 00000000000..bbfe057a207 --- /dev/null +++ b/packages/app-metadata-controller/tsconfig.build.json @@ -0,0 +1,13 @@ +{ + "extends": "../../tsconfig.packages.build.json", + "compilerOptions": { + "baseUrl": "./", + "outDir": "./dist", + "rootDir": "./src" + }, + "references": [ + { "path": "../base-controller/tsconfig.build.json" }, + { "path": "../controller-utils/tsconfig.build.json" } + ], + "include": ["../../types", "./src"] +} diff --git a/packages/app-metadata-controller/tsconfig.json b/packages/app-metadata-controller/tsconfig.json new file mode 100644 index 00000000000..6e8eadbdbd6 --- /dev/null +++ b/packages/app-metadata-controller/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.packages.json", + "compilerOptions": { + "baseUrl": "./", + "target": "ES6", + "module": "Node16", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true + }, +} diff --git a/packages/app-metadata-controller/typedoc.json b/packages/app-metadata-controller/typedoc.json new file mode 100644 index 00000000000..c9da015dbf8 --- /dev/null +++ b/packages/app-metadata-controller/typedoc.json @@ -0,0 +1,7 @@ +{ + "entryPoints": ["./src/index.ts"], + "excludePrivate": true, + "hideGenerator": true, + "out": "docs", + "tsconfig": "./tsconfig.build.json" +}