From 98efc47e30c92045c107572166b00d410c6ac564 Mon Sep 17 00:00:00 2001 From: Shane Jonas Date: Fri, 22 Sep 2023 10:45:16 -0400 Subject: [PATCH 01/25] Added polling-controller --- packages/polling-controller/CHANGELOG.md | 9 + packages/polling-controller/LICENSE | 20 ++ packages/polling-controller/README.md | 15 ++ packages/polling-controller/jest.config.js | 26 +++ packages/polling-controller/package.json | 66 ++++++ .../src/PollingController.test.ts | 214 ++++++++++++++++++ .../src/PollingController.ts | 99 ++++++++ packages/polling-controller/src/index.ts | 1 + .../polling-controller/tsconfig.build.json | 14 ++ packages/polling-controller/tsconfig.json | 12 + packages/polling-controller/typedoc.json | 7 + 11 files changed, 483 insertions(+) create mode 100644 packages/polling-controller/CHANGELOG.md create mode 100644 packages/polling-controller/LICENSE create mode 100644 packages/polling-controller/README.md create mode 100644 packages/polling-controller/jest.config.js create mode 100644 packages/polling-controller/package.json create mode 100644 packages/polling-controller/src/PollingController.test.ts create mode 100644 packages/polling-controller/src/PollingController.ts create mode 100644 packages/polling-controller/src/index.ts create mode 100644 packages/polling-controller/tsconfig.build.json create mode 100644 packages/polling-controller/tsconfig.json create mode 100644 packages/polling-controller/typedoc.json diff --git a/packages/polling-controller/CHANGELOG.md b/packages/polling-controller/CHANGELOG.md new file mode 100644 index 0000000000..d177a2af85 --- /dev/null +++ b/packages/polling-controller/CHANGELOG.md @@ -0,0 +1,9 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/gas-fee-controller diff --git a/packages/polling-controller/LICENSE b/packages/polling-controller/LICENSE new file mode 100644 index 0000000000..ddfbecf902 --- /dev/null +++ b/packages/polling-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/polling-controller/README.md b/packages/polling-controller/README.md new file mode 100644 index 0000000000..bade0a3e03 --- /dev/null +++ b/packages/polling-controller/README.md @@ -0,0 +1,15 @@ +# `@metamask/polling-controller` + +PollingController is used as the base for all controllers that need to poll for updates based on `networkClientId`. + +## Installation + +`yarn add @metamask/polling-controller` + +or + +`npm install @metamask/polling-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/polling-controller/jest.config.js b/packages/polling-controller/jest.config.js new file mode 100644 index 0000000000..17db4cd31b --- /dev/null +++ b/packages/polling-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: 65.31, + functions: 76.59, + lines: 75.83, + statements: 75.91, + }, + }, +}); diff --git a/packages/polling-controller/package.json b/packages/polling-controller/package.json new file mode 100644 index 0000000000..30a351a7a0 --- /dev/null +++ b/packages/polling-controller/package.json @@ -0,0 +1,66 @@ +{ + "name": "@metamask/polling-controller", + "version": "0.0.0", + "description": "Polling Controller is the base for controllers that polling by networkClientId", + "keywords": [ + "MetaMask", + "Ethereum" + ], + "homepage": "https://github.com/MetaMask/core/tree/main/packages/gas-fee-controller#readme", + "bugs": { + "url": "https://github.com/MetaMask/core/issues" + }, + "repository": { + "type": "git", + "url": "https://github.com/MetaMask/core.git" + }, + "license": "MIT", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "files": [ + "dist/" + ], + "scripts": { + "build:docs": "typedoc", + "changelog:validate": "../../scripts/validate-changelog.sh @metamask/gas-fee-controller", + "publish:preview": "yarn npm publish --tag preview", + "test": "jest", + "test:watch": "jest --watch" + }, + "dependencies": { + "@metamask/base-controller": "^3.2.1", + "@metamask/controller-utils": "^4.3.2", + "@metamask/eth-query": "^3.0.1", + "@metamask/network-controller": "^12.1.2", + "@metamask/utils": "^6.2.0", + "@types/uuid": "^8.3.0", + "ethereumjs-util": "^7.0.10", + "ethjs-unit": "^0.1.6", + "immer": "^9.0.6", + "uuid": "^8.3.2" + }, + "devDependencies": { + "@metamask/auto-changelog": "^3.1.0", + "@types/jest": "^27.4.1", + "@types/jest-when": "^2.7.3", + "deepmerge": "^4.2.2", + "jest": "^27.5.1", + "jest-when": "^3.4.2", + "nock": "^13.3.1", + "sinon": "^9.2.4", + "ts-jest": "^27.1.4", + "typedoc": "^0.22.15", + "typedoc-plugin-missing-exports": "^0.22.6", + "typescript": "~4.6.3" + }, + "peerDependencies": { + "@metamask/network-controller": "^12.1.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org/" + } +} diff --git a/packages/polling-controller/src/PollingController.test.ts b/packages/polling-controller/src/PollingController.test.ts new file mode 100644 index 0000000000..f5a06871a3 --- /dev/null +++ b/packages/polling-controller/src/PollingController.test.ts @@ -0,0 +1,214 @@ +import { ControllerMessenger } from '@metamask/base-controller'; + +import type { PollingCompleteType } from './PollingController'; +import GasFeeControllerPolling from './PollingController'; + +describe('PollingController', () => { + let executePollMock: GasFeeControllerPolling['executePoll']; + + beforeEach(() => { + executePollMock = jest.fn().mockImplementation(async () => { + return true; + }); + }); + + describe('start', () => { + it('should start polling if not polling', () => { + jest.useFakeTimers(); + + class MyGasFeeController extends GasFeeControllerPolling { + executePoll = executePollMock; + } + const mockMessenger = new ControllerMessenger(); + + const controller = new MyGasFeeController({ + messenger: mockMessenger, + metadata: {}, + name: 'GasFeeControllerPolling', + state: { foo: 'bar' }, + }); + controller.start('mainnet'); + jest.advanceTimersByTime(1500); + controller.stopAll(); + expect(executePollMock).toHaveBeenCalledTimes(1); + }); + }); + describe('stop', () => { + it('should stop polling if polling', () => { + jest.useFakeTimers(); + class MyGasFeeController extends GasFeeControllerPolling { + executePoll = executePollMock; + } + const mockMessenger = new ControllerMessenger(); + + const controller = new MyGasFeeController({ + messenger: mockMessenger, + metadata: {}, + name: 'GasFeeControllerPolling', + state: { foo: 'bar' }, + }); + const pollingToken = controller.start('mainnet'); + jest.advanceTimersByTime(1500); + controller.stop(pollingToken); + jest.advanceTimersByTime(1500); + expect(executePollMock).toHaveBeenCalledTimes(1); + controller.stopAll(); + }); + it('should not stop polling if multiple polling tokens exist', async () => { + jest.useFakeTimers(); + class MyGasFeeController extends GasFeeControllerPolling { + executePoll = executePollMock; + } + const mockMessenger = new ControllerMessenger(); + + const controller = new MyGasFeeController({ + messenger: mockMessenger, + metadata: {}, + name: 'GasFeeControllerPolling', + state: { foo: 'bar' }, + }); + const pollingToken1 = controller.start('mainnet'); + controller.start('mainnet'); + jest.advanceTimersByTime(1400); + await Promise.resolve(); + controller.stop(pollingToken1); + jest.advanceTimersByTime(1400); + await Promise.resolve(); + expect(executePollMock).toHaveBeenCalledTimes(2); + controller.stopAll(); + }); + it('should error if no poll token is passed', () => { + jest.useFakeTimers(); + class MyGasFeeController extends GasFeeControllerPolling { + executePoll = executePollMock; + } + const mockMessenger = new ControllerMessenger(); + + const controller = new MyGasFeeController({ + messenger: mockMessenger, + metadata: {}, + name: 'GasFeeControllerPolling', + state: { foo: 'bar' }, + }); + controller.start('mainnet'); + expect(() => { + controller.stop(undefined as unknown as any); + }).toThrow('pollingToken required'); + controller.stopAll(); + }); + it('should error if no poll token is found', () => { + jest.useFakeTimers(); + class MyGasFeeController extends GasFeeControllerPolling { + executePoll = executePollMock; + } + const mockMessenger = new ControllerMessenger(); + + const controller = new MyGasFeeController({ + messenger: mockMessenger, + metadata: {}, + name: 'GasFeeControllerPolling', + state: { foo: 'bar' }, + }); + controller.start('mainnet'); + expect(() => { + controller.stop('potato'); + }).toThrow('pollingToken not found'); + controller.stopAll(); + }); + }); + describe('poll', () => { + it('should call executePoll if polling', async () => { + jest.useFakeTimers(); + + class MyGasFeeController extends GasFeeControllerPolling { + executePoll = executePollMock; + } + const mockMessenger = new ControllerMessenger(); + + const controller = new MyGasFeeController({ + messenger: mockMessenger, + metadata: {}, + name: 'GasFeeControllerPolling', + state: { foo: 'bar' }, + }); + controller.start('mainnet'); + jest.advanceTimersByTime(1200); + await Promise.resolve(); + jest.advanceTimersByTime(1200); + await Promise.resolve(); + expect(executePollMock).toHaveBeenCalledTimes(2); + }); + it('should continue calling executePoll when start is called again with the same networkClientId', async () => { + jest.useFakeTimers(); + + class MyGasFeeController extends GasFeeControllerPolling { + executePoll = executePollMock; + } + const mockMessenger = new ControllerMessenger(); + + const controller = new MyGasFeeController({ + messenger: mockMessenger, + metadata: {}, + name: 'GasFeeControllerPolling', + state: { foo: 'bar' }, + }); + controller.start('mainnet'); + controller.start('mainnet'); + jest.advanceTimersByTime(1200); + await Promise.resolve(); + jest.advanceTimersByTime(1200); + await Promise.resolve(); + expect(executePollMock).toHaveBeenCalledTimes(2); + controller.stopAll(); + }); + it('should publish polligComplete when stop is called', async () => { + jest.useFakeTimers(); + const pollingComplete: any = jest.fn(); + class MyGasFeeController extends GasFeeControllerPolling { + executePoll = executePollMock; + } + const name = 'GasFeeControllerPolling'; + + const mockMessenger = new ControllerMessenger< + any, + PollingCompleteType + >(); + + mockMessenger.subscribe(`${name}:pollingComplete`, pollingComplete); + + const controller = new MyGasFeeController({ + messenger: mockMessenger, + metadata: {}, + name, + state: { foo: 'bar' }, + }); + const pollingToken = controller.start('mainnet'); + controller.stop(pollingToken); + expect(pollingComplete).toHaveBeenCalledTimes(1); + }); + }); + describe('multiple networkClientIds', () => { + it('should poll for each networkClientId', async () => { + jest.useFakeTimers(); + class MyGasFeeController extends GasFeeControllerPolling { + executePoll = executePollMock; + } + const mockMessenger = new ControllerMessenger(); + + const controller = new MyGasFeeController({ + messenger: mockMessenger, + metadata: {}, + name: 'GasFeeControllerPolling', + state: { foo: 'bar' }, + }); + controller.start('mainnet'); + controller.start('rinkeby'); + jest.advanceTimersByTime(1200); + await Promise.resolve(); + jest.advanceTimersByTime(1200); + await Promise.resolve(); + expect(executePollMock).toHaveBeenCalledTimes(4); + controller.stopAll(); + }); + }); +}); diff --git a/packages/polling-controller/src/PollingController.ts b/packages/polling-controller/src/PollingController.ts new file mode 100644 index 0000000000..795b644abb --- /dev/null +++ b/packages/polling-controller/src/PollingController.ts @@ -0,0 +1,99 @@ +import { BaseControllerV2 } from '@metamask/base-controller'; +import type { RestrictedControllerMessenger } from '@metamask/base-controller'; +import type { NetworkClientId } from '@metamask/network-controller'; +import type { Json } from '@metamask/utils'; +import { v1 as random } from 'uuid'; + +export type PollingCompleteType = { + type: `${N}:pollingComplete`; + payload: [string]; +}; + +export default abstract class ControllerPolling< + N extends string, + S extends Record, + messenger extends RestrictedControllerMessenger< + N, + any, + PollingCompleteType | any, + string, + string + >, +> extends BaseControllerV2 { + private readonly intervalLength = 1000; + + private readonly networkClientIdTokensMap: Map> = + new Map(); + + private readonly intervalIds: Record = {}; + + start(networkClientId: NetworkClientId) { + const innerPollToken = random(); + if (this.networkClientIdTokensMap.has(networkClientId)) { + // + const set = this.networkClientIdTokensMap.get(networkClientId); + set?.add(innerPollToken); + } else { + const set = new Set(); + set.add(innerPollToken); + this.networkClientIdTokensMap.set(networkClientId, set); + } + this.#poll(networkClientId); + + // call _poll + // add the inner poll token to the poll tokens set + return innerPollToken; + } + + stopAll() { + this.networkClientIdTokensMap.forEach((tokens, _networkClientId) => { + tokens.forEach((token) => { + this.stop(token); + }); + }); + } + + stop(pollingToken: string) { + if (!pollingToken) { + throw new Error('pollingToken required'); + } + let found = false; + this.networkClientIdTokensMap.forEach((tokens, networkClientId) => { + if (tokens.has(pollingToken)) { + found = true; + this.networkClientIdTokensMap + .get(networkClientId) + ?.delete(pollingToken); + if (this.networkClientIdTokensMap.get(networkClientId)?.size === 0) { + clearTimeout(this.intervalIds[networkClientId]); + delete this.intervalIds[networkClientId]; + this.networkClientIdTokensMap.delete(networkClientId); + this.messagingSystem.publish( + `${this.name}:pollingComplete`, + networkClientId, + ); + } + } + }); + if (!found) { + throw new Error('pollingToken not found'); + } + } + + abstract executePoll(networkClientId: NetworkClientId): Promise; + + #poll(networkClientId: NetworkClientId) { + if (this.intervalIds[networkClientId]) { + clearTimeout(this.intervalIds[networkClientId]); + delete this.intervalIds[networkClientId]; + } + this.intervalIds[networkClientId] = setTimeout(async () => { + try { + await this.executePoll(networkClientId); + } catch (error) { + console.error(error); + } + this.#poll(networkClientId); + }, this.intervalLength); + } +} diff --git a/packages/polling-controller/src/index.ts b/packages/polling-controller/src/index.ts new file mode 100644 index 0000000000..1458c0cfe9 --- /dev/null +++ b/packages/polling-controller/src/index.ts @@ -0,0 +1 @@ +export { default as PollingController } from './PollingController'; diff --git a/packages/polling-controller/tsconfig.build.json b/packages/polling-controller/tsconfig.build.json new file mode 100644 index 0000000000..ac0df4920c --- /dev/null +++ b/packages/polling-controller/tsconfig.build.json @@ -0,0 +1,14 @@ +{ + "extends": "../../tsconfig.packages.build.json", + "compilerOptions": { + "baseUrl": "./", + "outDir": "./dist", + "rootDir": "./src" + }, + "references": [ + { "path": "../base-controller/tsconfig.build.json" }, + { "path": "../controller-utils/tsconfig.build.json" }, + { "path": "../network-controller/tsconfig.build.json" } + ], + "include": ["../../types", "./src"] +} diff --git a/packages/polling-controller/tsconfig.json b/packages/polling-controller/tsconfig.json new file mode 100644 index 0000000000..4bbb0be81b --- /dev/null +++ b/packages/polling-controller/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../../tsconfig.packages.json", + "compilerOptions": { + "baseUrl": "./" + }, + "references": [ + { "path": "../base-controller" }, + { "path": "../controller-utils" }, + { "path": "../network-controller" } + ], + "include": ["../../types", "./src"] +} diff --git a/packages/polling-controller/typedoc.json b/packages/polling-controller/typedoc.json new file mode 100644 index 0000000000..c9da015dbf --- /dev/null +++ b/packages/polling-controller/typedoc.json @@ -0,0 +1,7 @@ +{ + "entryPoints": ["./src/index.ts"], + "excludePrivate": true, + "hideGenerator": true, + "out": "docs", + "tsconfig": "./tsconfig.build.json" +} From b3dafbfe0a52ccccd63fd3f9721ed6bcc87e6b4b Mon Sep 17 00:00:00 2001 From: Shane Jonas Date: Fri, 22 Sep 2023 10:53:10 -0400 Subject: [PATCH 02/25] Changed old names to PollingController in tests + other PR feedback fixes --- .../src/PollingController.test.ts | 40 +++++++++---------- .../src/PollingController.ts | 17 ++++---- 2 files changed, 27 insertions(+), 30 deletions(-) diff --git a/packages/polling-controller/src/PollingController.test.ts b/packages/polling-controller/src/PollingController.test.ts index f5a06871a3..8120f9fa3c 100644 --- a/packages/polling-controller/src/PollingController.test.ts +++ b/packages/polling-controller/src/PollingController.test.ts @@ -1,10 +1,10 @@ import { ControllerMessenger } from '@metamask/base-controller'; import type { PollingCompleteType } from './PollingController'; -import GasFeeControllerPolling from './PollingController'; +import PollingController from './PollingController'; describe('PollingController', () => { - let executePollMock: GasFeeControllerPolling['executePoll']; + let executePollMock: PollingController['executePoll']; beforeEach(() => { executePollMock = jest.fn().mockImplementation(async () => { @@ -16,7 +16,7 @@ describe('PollingController', () => { it('should start polling if not polling', () => { jest.useFakeTimers(); - class MyGasFeeController extends GasFeeControllerPolling { + class MyGasFeeController extends PollingController { executePoll = executePollMock; } const mockMessenger = new ControllerMessenger(); @@ -24,7 +24,7 @@ describe('PollingController', () => { const controller = new MyGasFeeController({ messenger: mockMessenger, metadata: {}, - name: 'GasFeeControllerPolling', + name: 'PollingController', state: { foo: 'bar' }, }); controller.start('mainnet'); @@ -36,7 +36,7 @@ describe('PollingController', () => { describe('stop', () => { it('should stop polling if polling', () => { jest.useFakeTimers(); - class MyGasFeeController extends GasFeeControllerPolling { + class MyGasFeeController extends PollingController { executePoll = executePollMock; } const mockMessenger = new ControllerMessenger(); @@ -44,7 +44,7 @@ describe('PollingController', () => { const controller = new MyGasFeeController({ messenger: mockMessenger, metadata: {}, - name: 'GasFeeControllerPolling', + name: 'PollingController', state: { foo: 'bar' }, }); const pollingToken = controller.start('mainnet'); @@ -56,7 +56,7 @@ describe('PollingController', () => { }); it('should not stop polling if multiple polling tokens exist', async () => { jest.useFakeTimers(); - class MyGasFeeController extends GasFeeControllerPolling { + class MyGasFeeController extends PollingController { executePoll = executePollMock; } const mockMessenger = new ControllerMessenger(); @@ -64,7 +64,7 @@ describe('PollingController', () => { const controller = new MyGasFeeController({ messenger: mockMessenger, metadata: {}, - name: 'GasFeeControllerPolling', + name: 'PollingController', state: { foo: 'bar' }, }); const pollingToken1 = controller.start('mainnet'); @@ -79,7 +79,7 @@ describe('PollingController', () => { }); it('should error if no poll token is passed', () => { jest.useFakeTimers(); - class MyGasFeeController extends GasFeeControllerPolling { + class MyGasFeeController extends PollingController { executePoll = executePollMock; } const mockMessenger = new ControllerMessenger(); @@ -87,7 +87,7 @@ describe('PollingController', () => { const controller = new MyGasFeeController({ messenger: mockMessenger, metadata: {}, - name: 'GasFeeControllerPolling', + name: 'PollingController', state: { foo: 'bar' }, }); controller.start('mainnet'); @@ -98,7 +98,7 @@ describe('PollingController', () => { }); it('should error if no poll token is found', () => { jest.useFakeTimers(); - class MyGasFeeController extends GasFeeControllerPolling { + class MyGasFeeController extends PollingController { executePoll = executePollMock; } const mockMessenger = new ControllerMessenger(); @@ -106,7 +106,7 @@ describe('PollingController', () => { const controller = new MyGasFeeController({ messenger: mockMessenger, metadata: {}, - name: 'GasFeeControllerPolling', + name: 'PollingController', state: { foo: 'bar' }, }); controller.start('mainnet'); @@ -120,7 +120,7 @@ describe('PollingController', () => { it('should call executePoll if polling', async () => { jest.useFakeTimers(); - class MyGasFeeController extends GasFeeControllerPolling { + class MyGasFeeController extends PollingController { executePoll = executePollMock; } const mockMessenger = new ControllerMessenger(); @@ -128,7 +128,7 @@ describe('PollingController', () => { const controller = new MyGasFeeController({ messenger: mockMessenger, metadata: {}, - name: 'GasFeeControllerPolling', + name: 'PollingController', state: { foo: 'bar' }, }); controller.start('mainnet'); @@ -141,7 +141,7 @@ describe('PollingController', () => { it('should continue calling executePoll when start is called again with the same networkClientId', async () => { jest.useFakeTimers(); - class MyGasFeeController extends GasFeeControllerPolling { + class MyGasFeeController extends PollingController { executePoll = executePollMock; } const mockMessenger = new ControllerMessenger(); @@ -149,7 +149,7 @@ describe('PollingController', () => { const controller = new MyGasFeeController({ messenger: mockMessenger, metadata: {}, - name: 'GasFeeControllerPolling', + name: 'PollingController', state: { foo: 'bar' }, }); controller.start('mainnet'); @@ -164,10 +164,10 @@ describe('PollingController', () => { it('should publish polligComplete when stop is called', async () => { jest.useFakeTimers(); const pollingComplete: any = jest.fn(); - class MyGasFeeController extends GasFeeControllerPolling { + class MyGasFeeController extends PollingController { executePoll = executePollMock; } - const name = 'GasFeeControllerPolling'; + const name = 'PollingController'; const mockMessenger = new ControllerMessenger< any, @@ -190,7 +190,7 @@ describe('PollingController', () => { describe('multiple networkClientIds', () => { it('should poll for each networkClientId', async () => { jest.useFakeTimers(); - class MyGasFeeController extends GasFeeControllerPolling { + class MyGasFeeController extends PollingController { executePoll = executePollMock; } const mockMessenger = new ControllerMessenger(); @@ -198,7 +198,7 @@ describe('PollingController', () => { const controller = new MyGasFeeController({ messenger: mockMessenger, metadata: {}, - name: 'GasFeeControllerPolling', + name: 'PollingController', state: { foo: 'bar' }, }); controller.start('mainnet'); diff --git a/packages/polling-controller/src/PollingController.ts b/packages/polling-controller/src/PollingController.ts index 795b644abb..9a1f3fa7d9 100644 --- a/packages/polling-controller/src/PollingController.ts +++ b/packages/polling-controller/src/PollingController.ts @@ -10,17 +10,17 @@ export type PollingCompleteType = { }; export default abstract class ControllerPolling< - N extends string, - S extends Record, + Name extends string, + State extends Record, messenger extends RestrictedControllerMessenger< - N, + Name, any, - PollingCompleteType | any, + PollingCompleteType | any, string, string >, -> extends BaseControllerV2 { - private readonly intervalLength = 1000; +> extends BaseControllerV2 { + readonly #intervalLength = 1000; private readonly networkClientIdTokensMap: Map> = new Map(); @@ -39,9 +39,6 @@ export default abstract class ControllerPolling< this.networkClientIdTokensMap.set(networkClientId, set); } this.#poll(networkClientId); - - // call _poll - // add the inner poll token to the poll tokens set return innerPollToken; } @@ -94,6 +91,6 @@ export default abstract class ControllerPolling< console.error(error); } this.#poll(networkClientId); - }, this.intervalLength); + }, this.#intervalLength); } } From 1066d888c9238f54b00b083aa690bee19dbeebe6 Mon Sep 17 00:00:00 2001 From: Shane Jonas Date: Fri, 22 Sep 2023 11:06:01 -0400 Subject: [PATCH 03/25] Added JSDoc for PollingController --- .../src/PollingController.ts | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/packages/polling-controller/src/PollingController.ts b/packages/polling-controller/src/PollingController.ts index 9a1f3fa7d9..f54c9e185d 100644 --- a/packages/polling-controller/src/PollingController.ts +++ b/packages/polling-controller/src/PollingController.ts @@ -9,6 +9,12 @@ export type PollingCompleteType = { payload: [string]; }; +/** + * PollingController is an abstract class that implements the polling + * functionality for a controller. It is meant to be extended by a controller + * that needs to poll for data by networkClientId. + * + */ export default abstract class ControllerPolling< Name extends string, State extends Record, @@ -27,6 +33,12 @@ export default abstract class ControllerPolling< private readonly intervalIds: Record = {}; + /** + * Starts polling for a networkClientId + * + * @param networkClientId - The networkClientId to start polling for + * @returns void + */ start(networkClientId: NetworkClientId) { const innerPollToken = random(); if (this.networkClientIdTokensMap.has(networkClientId)) { @@ -42,6 +54,9 @@ export default abstract class ControllerPolling< return innerPollToken; } + /** + * Stops polling for all networkClientIds + */ stopAll() { this.networkClientIdTokensMap.forEach((tokens, _networkClientId) => { tokens.forEach((token) => { @@ -50,6 +65,11 @@ export default abstract class ControllerPolling< }); } + /** + * Stops polling for a networkClientId + * + * @param pollingToken - The polling token to stop polling for + */ stop(pollingToken: string) { if (!pollingToken) { throw new Error('pollingToken required'); @@ -77,6 +97,11 @@ export default abstract class ControllerPolling< } } + /** + * Executes the poll for a networkClientId + * + * @param networkClientId - The networkClientId to execute the poll for + */ abstract executePoll(networkClientId: NetworkClientId): Promise; #poll(networkClientId: NetworkClientId) { From bdde0a2b55c68304d04a3ad8c4df8ff764870ba5 Mon Sep 17 00:00:00 2001 From: Shane Jonas Date: Fri, 22 Sep 2023 11:30:10 -0400 Subject: [PATCH 04/25] Added yarn.lock changes --- yarn.lock | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index f303cca1af..ccc388cf7d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1986,7 +1986,7 @@ __metadata: languageName: unknown linkType: soft -"@metamask/network-controller@^12.2.0, @metamask/network-controller@workspace:packages/network-controller": +"@metamask/network-controller@^12.1.2, @metamask/network-controller@^12.2.0, @metamask/network-controller@workspace:packages/network-controller": version: 0.0.0-use.local resolution: "@metamask/network-controller@workspace:packages/network-controller" dependencies: @@ -2121,6 +2121,37 @@ __metadata: languageName: unknown linkType: soft +"@metamask/polling-controller@workspace:packages/polling-controller": + version: 0.0.0-use.local + resolution: "@metamask/polling-controller@workspace:packages/polling-controller" + dependencies: + "@metamask/auto-changelog": ^3.1.0 + "@metamask/base-controller": ^3.2.1 + "@metamask/controller-utils": ^4.3.2 + "@metamask/eth-query": ^3.0.1 + "@metamask/network-controller": ^12.1.2 + "@metamask/utils": ^6.2.0 + "@types/jest": ^27.4.1 + "@types/jest-when": ^2.7.3 + "@types/uuid": ^8.3.0 + deepmerge: ^4.2.2 + ethereumjs-util: ^7.0.10 + ethjs-unit: ^0.1.6 + immer: ^9.0.6 + jest: ^27.5.1 + jest-when: ^3.4.2 + nock: ^13.3.1 + sinon: ^9.2.4 + ts-jest: ^27.1.4 + typedoc: ^0.22.15 + typedoc-plugin-missing-exports: ^0.22.6 + typescript: ~4.6.3 + uuid: ^8.3.2 + peerDependencies: + "@metamask/network-controller": ^12.1.2 + languageName: unknown + linkType: soft + "@metamask/post-message-stream@npm:^6.1.1, @metamask/post-message-stream@npm:^6.1.2": version: 6.1.2 resolution: "@metamask/post-message-stream@npm:6.1.2" From 18ec0fccf28b2985763842206be27fd4bb174904 Mon Sep 17 00:00:00 2001 From: Shane Jonas Date: Fri, 22 Sep 2023 11:40:06 -0400 Subject: [PATCH 05/25] Changed validate in npm scripts --- packages/polling-controller/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/polling-controller/package.json b/packages/polling-controller/package.json index 30a351a7a0..cc174ea2be 100644 --- a/packages/polling-controller/package.json +++ b/packages/polling-controller/package.json @@ -22,7 +22,7 @@ ], "scripts": { "build:docs": "typedoc", - "changelog:validate": "../../scripts/validate-changelog.sh @metamask/gas-fee-controller", + "changelog:validate": "../../scripts/validate-changelog.sh @metamask/polling-controller", "publish:preview": "yarn npm publish --tag preview", "test": "jest", "test:watch": "jest --watch" From 488b144c9f2b8d8d8a9d3a29825e404d0b7be98c Mon Sep 17 00:00:00 2001 From: Shane Jonas Date: Fri, 22 Sep 2023 11:45:12 -0400 Subject: [PATCH 06/25] Fixed homepage --- packages/polling-controller/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/polling-controller/package.json b/packages/polling-controller/package.json index cc174ea2be..9ae018c5b2 100644 --- a/packages/polling-controller/package.json +++ b/packages/polling-controller/package.json @@ -6,7 +6,7 @@ "MetaMask", "Ethereum" ], - "homepage": "https://github.com/MetaMask/core/tree/main/packages/gas-fee-controller#readme", + "homepage": "https://github.com/MetaMask/core/tree/main/packages/polling-controller#readme", "bugs": { "url": "https://github.com/MetaMask/core/issues" }, From 897f1562d50ebfc19b6331afe6bd3a7c2aa974ae Mon Sep 17 00:00:00 2001 From: Shane Jonas Date: Fri, 22 Sep 2023 12:50:13 -0400 Subject: [PATCH 07/25] Fixed constraints --- packages/polling-controller/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/polling-controller/package.json b/packages/polling-controller/package.json index 9ae018c5b2..7bcc7ccbd5 100644 --- a/packages/polling-controller/package.json +++ b/packages/polling-controller/package.json @@ -31,7 +31,7 @@ "@metamask/base-controller": "^3.2.1", "@metamask/controller-utils": "^4.3.2", "@metamask/eth-query": "^3.0.1", - "@metamask/network-controller": "^12.1.2", + "@metamask/network-controller": "^12.2.0", "@metamask/utils": "^6.2.0", "@types/uuid": "^8.3.0", "ethereumjs-util": "^7.0.10", @@ -54,7 +54,7 @@ "typescript": "~4.6.3" }, "peerDependencies": { - "@metamask/network-controller": "^12.1.2" + "@metamask/network-controller": "^12.2.0" }, "engines": { "node": ">=16.0.0" From 8c4782c1febf29c2c89a59ef9e42f48ec2ec9c99 Mon Sep 17 00:00:00 2001 From: Shane Jonas Date: Fri, 22 Sep 2023 14:09:20 -0400 Subject: [PATCH 08/25] Fixed yarn lock --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index ccc388cf7d..7f9cdddf13 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1986,7 +1986,7 @@ __metadata: languageName: unknown linkType: soft -"@metamask/network-controller@^12.1.2, @metamask/network-controller@^12.2.0, @metamask/network-controller@workspace:packages/network-controller": +"@metamask/network-controller@^12.2.0, @metamask/network-controller@workspace:packages/network-controller": version: 0.0.0-use.local resolution: "@metamask/network-controller@workspace:packages/network-controller" dependencies: @@ -2129,7 +2129,7 @@ __metadata: "@metamask/base-controller": ^3.2.1 "@metamask/controller-utils": ^4.3.2 "@metamask/eth-query": ^3.0.1 - "@metamask/network-controller": ^12.1.2 + "@metamask/network-controller": ^12.2.0 "@metamask/utils": ^6.2.0 "@types/jest": ^27.4.1 "@types/jest-when": ^2.7.3 @@ -2148,7 +2148,7 @@ __metadata: typescript: ~4.6.3 uuid: ^8.3.2 peerDependencies: - "@metamask/network-controller": ^12.1.2 + "@metamask/network-controller": ^12.2.0 languageName: unknown linkType: soft From a48104e146afd1baa5fd503ca35c0b17426a6b30 Mon Sep 17 00:00:00 2001 From: Shane Jonas Date: Fri, 22 Sep 2023 15:17:29 -0400 Subject: [PATCH 09/25] Fixed changelog --- packages/polling-controller/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/polling-controller/CHANGELOG.md b/packages/polling-controller/CHANGELOG.md index d177a2af85..27eb830b8c 100644 --- a/packages/polling-controller/CHANGELOG.md +++ b/packages/polling-controller/CHANGELOG.md @@ -6,4 +6,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/gas-fee-controller +[Unreleased]: https://github.com/MetaMask/core/ From 3177cd1edd4675dc564ca3994337b9f3b7160eeb Mon Sep 17 00:00:00 2001 From: Shane Date: Mon, 25 Sep 2023 07:50:40 -0700 Subject: [PATCH 10/25] Update packages/polling-controller/src/PollingController.test.ts Co-authored-by: Alex Donesky --- packages/polling-controller/src/PollingController.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/polling-controller/src/PollingController.test.ts b/packages/polling-controller/src/PollingController.test.ts index 8120f9fa3c..f88baaa72a 100644 --- a/packages/polling-controller/src/PollingController.test.ts +++ b/packages/polling-controller/src/PollingController.test.ts @@ -54,7 +54,7 @@ describe('PollingController', () => { expect(executePollMock).toHaveBeenCalledTimes(1); controller.stopAll(); }); - it('should not stop polling if multiple polling tokens exist', async () => { + it('should not stop polling if called with one of multiple active polling tokens for a given networkClient', async () => { jest.useFakeTimers(); class MyGasFeeController extends PollingController { executePoll = executePollMock; From 4104f26d1cf5258c587365e64c03202a4813e53d Mon Sep 17 00:00:00 2001 From: Shane Date: Mon, 25 Sep 2023 07:50:49 -0700 Subject: [PATCH 11/25] Update packages/polling-controller/src/PollingController.test.ts Co-authored-by: Alex Donesky --- packages/polling-controller/src/PollingController.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/polling-controller/src/PollingController.test.ts b/packages/polling-controller/src/PollingController.test.ts index f88baaa72a..f36c756b78 100644 --- a/packages/polling-controller/src/PollingController.test.ts +++ b/packages/polling-controller/src/PollingController.test.ts @@ -77,7 +77,7 @@ describe('PollingController', () => { expect(executePollMock).toHaveBeenCalledTimes(2); controller.stopAll(); }); - it('should error if no poll token is passed', () => { + it('should error if no pollingToken is passed', () => { jest.useFakeTimers(); class MyGasFeeController extends PollingController { executePoll = executePollMock; From 510585ce036b35a47f6417610d5dd1a36fcbdf45 Mon Sep 17 00:00:00 2001 From: Shane Date: Mon, 25 Sep 2023 07:50:55 -0700 Subject: [PATCH 12/25] Update packages/polling-controller/src/PollingController.test.ts Co-authored-by: Alex Donesky --- packages/polling-controller/src/PollingController.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/polling-controller/src/PollingController.test.ts b/packages/polling-controller/src/PollingController.test.ts index f36c756b78..08980e25e6 100644 --- a/packages/polling-controller/src/PollingController.test.ts +++ b/packages/polling-controller/src/PollingController.test.ts @@ -96,7 +96,7 @@ describe('PollingController', () => { }).toThrow('pollingToken required'); controller.stopAll(); }); - it('should error if no poll token is found', () => { + it('should error if no matching pollingToken is found', () => { jest.useFakeTimers(); class MyGasFeeController extends PollingController { executePoll = executePollMock; From ece957717b7f65fa6f510db32d5095ffbb22e1f4 Mon Sep 17 00:00:00 2001 From: Shane Date: Mon, 25 Sep 2023 07:51:11 -0700 Subject: [PATCH 13/25] Update packages/polling-controller/src/PollingController.test.ts Co-authored-by: Alex Donesky --- packages/polling-controller/src/PollingController.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/polling-controller/src/PollingController.test.ts b/packages/polling-controller/src/PollingController.test.ts index 08980e25e6..83ba7f802e 100644 --- a/packages/polling-controller/src/PollingController.test.ts +++ b/packages/polling-controller/src/PollingController.test.ts @@ -161,7 +161,7 @@ describe('PollingController', () => { expect(executePollMock).toHaveBeenCalledTimes(2); controller.stopAll(); }); - it('should publish polligComplete when stop is called', async () => { + it('should publish 'pollingComplete' when stop is called', async () => { jest.useFakeTimers(); const pollingComplete: any = jest.fn(); class MyGasFeeController extends PollingController { From 7cf3c1337a0981ff3538b85975316facf0b9afd1 Mon Sep 17 00:00:00 2001 From: Shane Jonas Date: Mon, 25 Sep 2023 10:56:37 -0400 Subject: [PATCH 14/25] Removed unused deps --- packages/polling-controller/package.json | 9 --------- .../polling-controller/src/PollingController.test.ts | 2 +- yarn.lock | 9 --------- 3 files changed, 1 insertion(+), 19 deletions(-) diff --git a/packages/polling-controller/package.json b/packages/polling-controller/package.json index 7bcc7ccbd5..0fa44e65af 100644 --- a/packages/polling-controller/package.json +++ b/packages/polling-controller/package.json @@ -30,24 +30,15 @@ "dependencies": { "@metamask/base-controller": "^3.2.1", "@metamask/controller-utils": "^4.3.2", - "@metamask/eth-query": "^3.0.1", "@metamask/network-controller": "^12.2.0", "@metamask/utils": "^6.2.0", "@types/uuid": "^8.3.0", - "ethereumjs-util": "^7.0.10", - "ethjs-unit": "^0.1.6", - "immer": "^9.0.6", "uuid": "^8.3.2" }, "devDependencies": { "@metamask/auto-changelog": "^3.1.0", "@types/jest": "^27.4.1", - "@types/jest-when": "^2.7.3", - "deepmerge": "^4.2.2", "jest": "^27.5.1", - "jest-when": "^3.4.2", - "nock": "^13.3.1", - "sinon": "^9.2.4", "ts-jest": "^27.1.4", "typedoc": "^0.22.15", "typedoc-plugin-missing-exports": "^0.22.6", diff --git a/packages/polling-controller/src/PollingController.test.ts b/packages/polling-controller/src/PollingController.test.ts index 83ba7f802e..51846a673c 100644 --- a/packages/polling-controller/src/PollingController.test.ts +++ b/packages/polling-controller/src/PollingController.test.ts @@ -161,7 +161,7 @@ describe('PollingController', () => { expect(executePollMock).toHaveBeenCalledTimes(2); controller.stopAll(); }); - it('should publish 'pollingComplete' when stop is called', async () => { + it('should publish "pollingComplete" when stop is called', async () => { jest.useFakeTimers(); const pollingComplete: any = jest.fn(); class MyGasFeeController extends PollingController { diff --git a/yarn.lock b/yarn.lock index 7f9cdddf13..313348e634 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2128,20 +2128,11 @@ __metadata: "@metamask/auto-changelog": ^3.1.0 "@metamask/base-controller": ^3.2.1 "@metamask/controller-utils": ^4.3.2 - "@metamask/eth-query": ^3.0.1 "@metamask/network-controller": ^12.2.0 "@metamask/utils": ^6.2.0 "@types/jest": ^27.4.1 - "@types/jest-when": ^2.7.3 "@types/uuid": ^8.3.0 - deepmerge: ^4.2.2 - ethereumjs-util: ^7.0.10 - ethjs-unit: ^0.1.6 - immer: ^9.0.6 jest: ^27.5.1 - jest-when: ^3.4.2 - nock: ^13.3.1 - sinon: ^9.2.4 ts-jest: ^27.1.4 typedoc: ^0.22.15 typedoc-plugin-missing-exports: ^0.22.6 From f663d295de8af3e39bd5b7493ba59c02cf10f6d1 Mon Sep 17 00:00:00 2001 From: Shane Jonas Date: Mon, 25 Sep 2023 10:57:47 -0400 Subject: [PATCH 15/25] Fixed naming on PollingController --- packages/polling-controller/src/PollingController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/polling-controller/src/PollingController.ts b/packages/polling-controller/src/PollingController.ts index f54c9e185d..06a41ab320 100644 --- a/packages/polling-controller/src/PollingController.ts +++ b/packages/polling-controller/src/PollingController.ts @@ -15,7 +15,7 @@ export type PollingCompleteType = { * that needs to poll for data by networkClientId. * */ -export default abstract class ControllerPolling< +export default abstract class PollingController< Name extends string, State extends Record, messenger extends RestrictedControllerMessenger< From b6db670eece4b68d5238022efe0cefbcd81eb4ee Mon Sep 17 00:00:00 2001 From: Shane Date: Mon, 25 Sep 2023 08:07:14 -0700 Subject: [PATCH 16/25] Update packages/polling-controller/src/PollingController.test.ts Co-authored-by: Alex Donesky --- packages/polling-controller/src/PollingController.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/polling-controller/src/PollingController.test.ts b/packages/polling-controller/src/PollingController.test.ts index 51846a673c..833400afd7 100644 --- a/packages/polling-controller/src/PollingController.test.ts +++ b/packages/polling-controller/src/PollingController.test.ts @@ -34,7 +34,7 @@ describe('PollingController', () => { }); }); describe('stop', () => { - it('should stop polling if polling', () => { + it('should stop polling when called with a valid polling that was the only active pollingToken for a given networkClient ', () => { jest.useFakeTimers(); class MyGasFeeController extends PollingController { executePoll = executePollMock; From 58ab401aad8815fcab584e8c8ad96874b6d27837 Mon Sep 17 00:00:00 2001 From: Shane Jonas Date: Mon, 25 Sep 2023 11:11:39 -0400 Subject: [PATCH 17/25] Changed PollingController test beforeEach to new createExecutePollMock method --- .../src/PollingController.test.ts | 45 +++++++++---------- .../src/PollingController.ts | 1 - 2 files changed, 22 insertions(+), 24 deletions(-) diff --git a/packages/polling-controller/src/PollingController.test.ts b/packages/polling-controller/src/PollingController.test.ts index 833400afd7..17a6dea009 100644 --- a/packages/polling-controller/src/PollingController.test.ts +++ b/packages/polling-controller/src/PollingController.test.ts @@ -3,21 +3,20 @@ import { ControllerMessenger } from '@metamask/base-controller'; import type { PollingCompleteType } from './PollingController'; import PollingController from './PollingController'; -describe('PollingController', () => { - let executePollMock: PollingController['executePoll']; - - beforeEach(() => { - executePollMock = jest.fn().mockImplementation(async () => { - return true; - }); +const createExecutePollMock = () => { + const executePollMock = jest.fn().mockImplementation(async () => { + return true; }); + return executePollMock; +}; +describe('PollingController', () => { describe('start', () => { it('should start polling if not polling', () => { jest.useFakeTimers(); class MyGasFeeController extends PollingController { - executePoll = executePollMock; + executePoll = createExecutePollMock(); } const mockMessenger = new ControllerMessenger(); @@ -30,14 +29,14 @@ describe('PollingController', () => { controller.start('mainnet'); jest.advanceTimersByTime(1500); controller.stopAll(); - expect(executePollMock).toHaveBeenCalledTimes(1); + expect(controller.executePoll).toHaveBeenCalledTimes(1); }); }); describe('stop', () => { - it('should stop polling when called with a valid polling that was the only active pollingToken for a given networkClient ', () => { + it('should stop polling when called with a valid polling that was the only active pollingToken for a given networkClient', () => { jest.useFakeTimers(); class MyGasFeeController extends PollingController { - executePoll = executePollMock; + executePoll = createExecutePollMock(); } const mockMessenger = new ControllerMessenger(); @@ -51,13 +50,13 @@ describe('PollingController', () => { jest.advanceTimersByTime(1500); controller.stop(pollingToken); jest.advanceTimersByTime(1500); - expect(executePollMock).toHaveBeenCalledTimes(1); + expect(controller.executePoll).toHaveBeenCalledTimes(1); controller.stopAll(); }); it('should not stop polling if called with one of multiple active polling tokens for a given networkClient', async () => { jest.useFakeTimers(); class MyGasFeeController extends PollingController { - executePoll = executePollMock; + executePoll = createExecutePollMock(); } const mockMessenger = new ControllerMessenger(); @@ -74,13 +73,13 @@ describe('PollingController', () => { controller.stop(pollingToken1); jest.advanceTimersByTime(1400); await Promise.resolve(); - expect(executePollMock).toHaveBeenCalledTimes(2); + expect(controller.executePoll).toHaveBeenCalledTimes(2); controller.stopAll(); }); it('should error if no pollingToken is passed', () => { jest.useFakeTimers(); class MyGasFeeController extends PollingController { - executePoll = executePollMock; + executePoll = createExecutePollMock(); } const mockMessenger = new ControllerMessenger(); @@ -99,7 +98,7 @@ describe('PollingController', () => { it('should error if no matching pollingToken is found', () => { jest.useFakeTimers(); class MyGasFeeController extends PollingController { - executePoll = executePollMock; + executePoll = createExecutePollMock(); } const mockMessenger = new ControllerMessenger(); @@ -121,7 +120,7 @@ describe('PollingController', () => { jest.useFakeTimers(); class MyGasFeeController extends PollingController { - executePoll = executePollMock; + executePoll = createExecutePollMock(); } const mockMessenger = new ControllerMessenger(); @@ -136,13 +135,13 @@ describe('PollingController', () => { await Promise.resolve(); jest.advanceTimersByTime(1200); await Promise.resolve(); - expect(executePollMock).toHaveBeenCalledTimes(2); + expect(controller.executePoll).toHaveBeenCalledTimes(2); }); it('should continue calling executePoll when start is called again with the same networkClientId', async () => { jest.useFakeTimers(); class MyGasFeeController extends PollingController { - executePoll = executePollMock; + executePoll = createExecutePollMock(); } const mockMessenger = new ControllerMessenger(); @@ -158,14 +157,14 @@ describe('PollingController', () => { await Promise.resolve(); jest.advanceTimersByTime(1200); await Promise.resolve(); - expect(executePollMock).toHaveBeenCalledTimes(2); + expect(controller.executePoll).toHaveBeenCalledTimes(2); controller.stopAll(); }); it('should publish "pollingComplete" when stop is called', async () => { jest.useFakeTimers(); const pollingComplete: any = jest.fn(); class MyGasFeeController extends PollingController { - executePoll = executePollMock; + executePoll = createExecutePollMock(); } const name = 'PollingController'; @@ -191,7 +190,7 @@ describe('PollingController', () => { it('should poll for each networkClientId', async () => { jest.useFakeTimers(); class MyGasFeeController extends PollingController { - executePoll = executePollMock; + executePoll = createExecutePollMock(); } const mockMessenger = new ControllerMessenger(); @@ -207,7 +206,7 @@ describe('PollingController', () => { await Promise.resolve(); jest.advanceTimersByTime(1200); await Promise.resolve(); - expect(executePollMock).toHaveBeenCalledTimes(4); + expect(controller.executePoll).toHaveBeenCalledTimes(4); controller.stopAll(); }); }); diff --git a/packages/polling-controller/src/PollingController.ts b/packages/polling-controller/src/PollingController.ts index 06a41ab320..54fe1e1730 100644 --- a/packages/polling-controller/src/PollingController.ts +++ b/packages/polling-controller/src/PollingController.ts @@ -42,7 +42,6 @@ export default abstract class PollingController< start(networkClientId: NetworkClientId) { const innerPollToken = random(); if (this.networkClientIdTokensMap.has(networkClientId)) { - // const set = this.networkClientIdTokensMap.get(networkClientId); set?.add(innerPollToken); } else { From 4251790f8b415fdc5b4449f31b30938e71643304 Mon Sep 17 00:00:00 2001 From: Shane Jonas Date: Mon, 25 Sep 2023 11:15:20 -0400 Subject: [PATCH 18/25] Added TICK_TIME constant for PollingController tests --- .../src/PollingController.test.ts | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/packages/polling-controller/src/PollingController.test.ts b/packages/polling-controller/src/PollingController.test.ts index 17a6dea009..2edb177413 100644 --- a/packages/polling-controller/src/PollingController.test.ts +++ b/packages/polling-controller/src/PollingController.test.ts @@ -3,6 +3,8 @@ import { ControllerMessenger } from '@metamask/base-controller'; import type { PollingCompleteType } from './PollingController'; import PollingController from './PollingController'; +const TICK_TIME = 1000; + const createExecutePollMock = () => { const executePollMock = jest.fn().mockImplementation(async () => { return true; @@ -27,7 +29,7 @@ describe('PollingController', () => { state: { foo: 'bar' }, }); controller.start('mainnet'); - jest.advanceTimersByTime(1500); + jest.advanceTimersByTime(TICK_TIME); controller.stopAll(); expect(controller.executePoll).toHaveBeenCalledTimes(1); }); @@ -47,9 +49,9 @@ describe('PollingController', () => { state: { foo: 'bar' }, }); const pollingToken = controller.start('mainnet'); - jest.advanceTimersByTime(1500); + jest.advanceTimersByTime(TICK_TIME); controller.stop(pollingToken); - jest.advanceTimersByTime(1500); + jest.advanceTimersByTime(TICK_TIME); expect(controller.executePoll).toHaveBeenCalledTimes(1); controller.stopAll(); }); @@ -68,10 +70,10 @@ describe('PollingController', () => { }); const pollingToken1 = controller.start('mainnet'); controller.start('mainnet'); - jest.advanceTimersByTime(1400); + jest.advanceTimersByTime(TICK_TIME); await Promise.resolve(); controller.stop(pollingToken1); - jest.advanceTimersByTime(1400); + jest.advanceTimersByTime(TICK_TIME); await Promise.resolve(); expect(controller.executePoll).toHaveBeenCalledTimes(2); controller.stopAll(); @@ -131,9 +133,9 @@ describe('PollingController', () => { state: { foo: 'bar' }, }); controller.start('mainnet'); - jest.advanceTimersByTime(1200); + jest.advanceTimersByTime(TICK_TIME); await Promise.resolve(); - jest.advanceTimersByTime(1200); + jest.advanceTimersByTime(TICK_TIME); await Promise.resolve(); expect(controller.executePoll).toHaveBeenCalledTimes(2); }); @@ -153,9 +155,9 @@ describe('PollingController', () => { }); controller.start('mainnet'); controller.start('mainnet'); - jest.advanceTimersByTime(1200); + jest.advanceTimersByTime(TICK_TIME); await Promise.resolve(); - jest.advanceTimersByTime(1200); + jest.advanceTimersByTime(TICK_TIME); await Promise.resolve(); expect(controller.executePoll).toHaveBeenCalledTimes(2); controller.stopAll(); @@ -202,9 +204,9 @@ describe('PollingController', () => { }); controller.start('mainnet'); controller.start('rinkeby'); - jest.advanceTimersByTime(1200); + jest.advanceTimersByTime(TICK_TIME); await Promise.resolve(); - jest.advanceTimersByTime(1200); + jest.advanceTimersByTime(TICK_TIME); await Promise.resolve(); expect(controller.executePoll).toHaveBeenCalledTimes(4); controller.stopAll(); From c72f5d574168ff81bbc863403d79cb0a01fa76e9 Mon Sep 17 00:00:00 2001 From: Shane Jonas Date: Mon, 25 Sep 2023 11:36:56 -0400 Subject: [PATCH 19/25] Added deepmerge dep --- packages/polling-controller/package.json | 1 + yarn.lock | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/polling-controller/package.json b/packages/polling-controller/package.json index 0fa44e65af..814f808ed9 100644 --- a/packages/polling-controller/package.json +++ b/packages/polling-controller/package.json @@ -38,6 +38,7 @@ "devDependencies": { "@metamask/auto-changelog": "^3.1.0", "@types/jest": "^27.4.1", + "deepmerge": "^4.3.1", "jest": "^27.5.1", "ts-jest": "^27.1.4", "typedoc": "^0.22.15", diff --git a/yarn.lock b/yarn.lock index 313348e634..9cced88541 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2132,6 +2132,7 @@ __metadata: "@metamask/utils": ^6.2.0 "@types/jest": ^27.4.1 "@types/uuid": ^8.3.0 + deepmerge: ^4.3.1 jest: ^27.5.1 ts-jest: ^27.1.4 typedoc: ^0.22.15 @@ -4579,7 +4580,7 @@ __metadata: languageName: node linkType: hard -"deepmerge@npm:^4.2.2": +"deepmerge@npm:^4.2.2, deepmerge@npm:^4.3.1": version: 4.3.1 resolution: "deepmerge@npm:4.3.1" checksum: 2024c6a980a1b7128084170c4cf56b0fd58a63f2da1660dcfe977415f27b17dbe5888668b59d0b063753f3220719d5e400b7f113609489c90160bb9a5518d052 From cb99a77d0e6e9e109653afd03a1c3746c1869dd9 Mon Sep 17 00:00:00 2001 From: Shane Jonas Date: Mon, 25 Sep 2023 11:41:47 -0400 Subject: [PATCH 20/25] Fixed deepmerge issue --- packages/polling-controller/package.json | 2 +- yarn.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/polling-controller/package.json b/packages/polling-controller/package.json index 814f808ed9..8216fd4839 100644 --- a/packages/polling-controller/package.json +++ b/packages/polling-controller/package.json @@ -38,7 +38,7 @@ "devDependencies": { "@metamask/auto-changelog": "^3.1.0", "@types/jest": "^27.4.1", - "deepmerge": "^4.3.1", + "deepmerge": "^4.2.2", "jest": "^27.5.1", "ts-jest": "^27.1.4", "typedoc": "^0.22.15", diff --git a/yarn.lock b/yarn.lock index 9cced88541..5b664d0a0e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2132,7 +2132,7 @@ __metadata: "@metamask/utils": ^6.2.0 "@types/jest": ^27.4.1 "@types/uuid": ^8.3.0 - deepmerge: ^4.3.1 + deepmerge: ^4.2.2 jest: ^27.5.1 ts-jest: ^27.1.4 typedoc: ^0.22.15 @@ -4580,7 +4580,7 @@ __metadata: languageName: node linkType: hard -"deepmerge@npm:^4.2.2, deepmerge@npm:^4.3.1": +"deepmerge@npm:^4.2.2": version: 4.3.1 resolution: "deepmerge@npm:4.3.1" checksum: 2024c6a980a1b7128084170c4cf56b0fd58a63f2da1660dcfe977415f27b17dbe5888668b59d0b063753f3220719d5e400b7f113609489c90160bb9a5518d052 From 0ee7b446f08749804880276bfd9cce9f22af4691 Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 25 Sep 2023 15:25:28 -0500 Subject: [PATCH 21/25] add interval setting in the constructor --- .../src/PollingController.test.ts | 9 +++++ .../src/PollingController.ts | 34 +++++++++++++++++-- 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/packages/polling-controller/src/PollingController.test.ts b/packages/polling-controller/src/PollingController.test.ts index 2edb177413..abf1b26a21 100644 --- a/packages/polling-controller/src/PollingController.test.ts +++ b/packages/polling-controller/src/PollingController.test.ts @@ -27,6 +27,7 @@ describe('PollingController', () => { metadata: {}, name: 'PollingController', state: { foo: 'bar' }, + pollingIntervalLength: 1000, }); controller.start('mainnet'); jest.advanceTimersByTime(TICK_TIME); @@ -47,6 +48,7 @@ describe('PollingController', () => { metadata: {}, name: 'PollingController', state: { foo: 'bar' }, + pollingIntervalLength: 1000, }); const pollingToken = controller.start('mainnet'); jest.advanceTimersByTime(TICK_TIME); @@ -67,6 +69,7 @@ describe('PollingController', () => { metadata: {}, name: 'PollingController', state: { foo: 'bar' }, + pollingIntervalLength: 1000, }); const pollingToken1 = controller.start('mainnet'); controller.start('mainnet'); @@ -90,6 +93,7 @@ describe('PollingController', () => { metadata: {}, name: 'PollingController', state: { foo: 'bar' }, + pollingIntervalLength: 1000, }); controller.start('mainnet'); expect(() => { @@ -109,6 +113,7 @@ describe('PollingController', () => { metadata: {}, name: 'PollingController', state: { foo: 'bar' }, + pollingIntervalLength: 1000, }); controller.start('mainnet'); expect(() => { @@ -131,6 +136,7 @@ describe('PollingController', () => { metadata: {}, name: 'PollingController', state: { foo: 'bar' }, + pollingIntervalLength: 1000, }); controller.start('mainnet'); jest.advanceTimersByTime(TICK_TIME); @@ -152,6 +158,7 @@ describe('PollingController', () => { metadata: {}, name: 'PollingController', state: { foo: 'bar' }, + pollingIntervalLength: 1000, }); controller.start('mainnet'); controller.start('mainnet'); @@ -182,6 +189,7 @@ describe('PollingController', () => { metadata: {}, name, state: { foo: 'bar' }, + pollingIntervalLength: 1000, }); const pollingToken = controller.start('mainnet'); controller.stop(pollingToken); @@ -201,6 +209,7 @@ describe('PollingController', () => { metadata: {}, name: 'PollingController', state: { foo: 'bar' }, + pollingIntervalLength: 1000, }); controller.start('mainnet'); controller.start('rinkeby'); diff --git a/packages/polling-controller/src/PollingController.ts b/packages/polling-controller/src/PollingController.ts index 54fe1e1730..f87ed0f0e5 100644 --- a/packages/polling-controller/src/PollingController.ts +++ b/packages/polling-controller/src/PollingController.ts @@ -1,5 +1,8 @@ import { BaseControllerV2 } from '@metamask/base-controller'; -import type { RestrictedControllerMessenger } from '@metamask/base-controller'; +import type { + RestrictedControllerMessenger, + StateMetadata, +} from '@metamask/base-controller'; import type { NetworkClientId } from '@metamask/network-controller'; import type { Json } from '@metamask/utils'; import { v1 as random } from 'uuid'; @@ -26,13 +29,40 @@ export default abstract class PollingController< string >, > extends BaseControllerV2 { - readonly #intervalLength = 1000; + readonly #intervalLength: number; private readonly networkClientIdTokensMap: Map> = new Map(); private readonly intervalIds: Record = {}; + constructor({ + name, + state, + messenger, + metadata, + pollingIntervalLength, + }: { + name: Name; + state: State; + metadata: StateMetadata; + messenger: messenger; + pollingIntervalLength: number; + }) { + super({ + name, + state, + messenger, + metadata, + }); + + if (!pollingIntervalLength) { + throw new Error('pollingIntervalLength required for PollingController'); + } + + this.#intervalLength = pollingIntervalLength; + } + /** * Starts polling for a networkClientId * From fac99241df8ae4145a774cb877108cd91c8755c4 Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 25 Sep 2023 15:37:40 -0500 Subject: [PATCH 22/25] update test --- .../src/PollingController.test.ts | 29 ++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/packages/polling-controller/src/PollingController.test.ts b/packages/polling-controller/src/PollingController.test.ts index abf1b26a21..125e28e1d7 100644 --- a/packages/polling-controller/src/PollingController.test.ts +++ b/packages/polling-controller/src/PollingController.test.ts @@ -27,7 +27,7 @@ describe('PollingController', () => { metadata: {}, name: 'PollingController', state: { foo: 'bar' }, - pollingIntervalLength: 1000, + pollingIntervalLength: TICK_TIME, }); controller.start('mainnet'); jest.advanceTimersByTime(TICK_TIME); @@ -48,7 +48,7 @@ describe('PollingController', () => { metadata: {}, name: 'PollingController', state: { foo: 'bar' }, - pollingIntervalLength: 1000, + pollingIntervalLength: TICK_TIME, }); const pollingToken = controller.start('mainnet'); jest.advanceTimersByTime(TICK_TIME); @@ -69,7 +69,7 @@ describe('PollingController', () => { metadata: {}, name: 'PollingController', state: { foo: 'bar' }, - pollingIntervalLength: 1000, + pollingIntervalLength: TICK_TIME, }); const pollingToken1 = controller.start('mainnet'); controller.start('mainnet'); @@ -93,7 +93,7 @@ describe('PollingController', () => { metadata: {}, name: 'PollingController', state: { foo: 'bar' }, - pollingIntervalLength: 1000, + pollingIntervalLength: TICK_TIME, }); controller.start('mainnet'); expect(() => { @@ -113,7 +113,7 @@ describe('PollingController', () => { metadata: {}, name: 'PollingController', state: { foo: 'bar' }, - pollingIntervalLength: 1000, + pollingIntervalLength: TICK_TIME, }); controller.start('mainnet'); expect(() => { @@ -136,7 +136,7 @@ describe('PollingController', () => { metadata: {}, name: 'PollingController', state: { foo: 'bar' }, - pollingIntervalLength: 1000, + pollingIntervalLength: TICK_TIME, }); controller.start('mainnet'); jest.advanceTimersByTime(TICK_TIME); @@ -158,7 +158,7 @@ describe('PollingController', () => { metadata: {}, name: 'PollingController', state: { foo: 'bar' }, - pollingIntervalLength: 1000, + pollingIntervalLength: TICK_TIME, }); controller.start('mainnet'); controller.start('mainnet'); @@ -189,7 +189,7 @@ describe('PollingController', () => { metadata: {}, name, state: { foo: 'bar' }, - pollingIntervalLength: 1000, + pollingIntervalLength: TICK_TIME, }); const pollingToken = controller.start('mainnet'); controller.stop(pollingToken); @@ -209,15 +209,24 @@ describe('PollingController', () => { metadata: {}, name: 'PollingController', state: { foo: 'bar' }, - pollingIntervalLength: 1000, + pollingIntervalLength: TICK_TIME, }); controller.start('mainnet'); controller.start('rinkeby'); jest.advanceTimersByTime(TICK_TIME); await Promise.resolve(); + expect(controller.executePoll.mock.calls).toMatchObject([ + ['mainnet'], + ['rinkeby'], + ]); jest.advanceTimersByTime(TICK_TIME); await Promise.resolve(); - expect(controller.executePoll).toHaveBeenCalledTimes(4); + expect(controller.executePoll.mock.calls).toMatchObject([ + ['mainnet'], + ['rinkeby'], + ['mainnet'], + ['rinkeby'], + ]); controller.stopAll(); }); }); From 911ff203ce8834c3282ab327812b95df95452e9f Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 25 Sep 2023 15:45:42 -0500 Subject: [PATCH 23/25] add intervallength test --- .../src/PollingController.test.ts | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/packages/polling-controller/src/PollingController.test.ts b/packages/polling-controller/src/PollingController.test.ts index 125e28e1d7..26c583d2c6 100644 --- a/packages/polling-controller/src/PollingController.test.ts +++ b/packages/polling-controller/src/PollingController.test.ts @@ -195,6 +195,35 @@ describe('PollingController', () => { controller.stop(pollingToken); expect(pollingComplete).toHaveBeenCalledTimes(1); }); + it('should poll at the interval length passed via the constructor', async () => { + jest.useFakeTimers(); + + class MyGasFeeController extends PollingController { + executePoll = createExecutePollMock(); + } + const mockMessenger = new ControllerMessenger(); + + const controller = new MyGasFeeController({ + messenger: mockMessenger, + metadata: {}, + name: 'PollingController', + state: { foo: 'bar' }, + pollingIntervalLength: TICK_TIME * 3, + }); + controller.start('mainnet'); + jest.advanceTimersByTime(TICK_TIME); + await Promise.resolve(); + expect(controller.executePoll).not.toHaveBeenCalled(); + jest.advanceTimersByTime(TICK_TIME); + await Promise.resolve(); + expect(controller.executePoll).not.toHaveBeenCalled(); + jest.advanceTimersByTime(TICK_TIME); + await Promise.resolve(); + expect(controller.executePoll).toHaveBeenCalledTimes(1); + jest.advanceTimersByTime(TICK_TIME * 3); + await Promise.resolve(); + expect(controller.executePoll).toHaveBeenCalledTimes(2); + }); }); describe('multiple networkClientIds', () => { it('should poll for each networkClientId', async () => { @@ -229,5 +258,51 @@ describe('PollingController', () => { ]); controller.stopAll(); }); + + it('should poll multiple networkClientIds at the interval length passed via the constructor', async () => { + jest.useFakeTimers(); + + class MyGasFeeController extends PollingController { + executePoll = createExecutePollMock(); + } + const mockMessenger = new ControllerMessenger(); + + const controller = new MyGasFeeController({ + messenger: mockMessenger, + metadata: {}, + name: 'PollingController', + state: { foo: 'bar' }, + pollingIntervalLength: TICK_TIME * 2, + }); + controller.start('mainnet'); + jest.advanceTimersByTime(TICK_TIME); + await Promise.resolve(); + controller.start('sepolia'); + expect(controller.executePoll.mock.calls).toMatchObject([]); + jest.advanceTimersByTime(TICK_TIME); + await Promise.resolve(); + expect(controller.executePoll.mock.calls).toMatchObject([['mainnet']]); + jest.advanceTimersByTime(TICK_TIME); + await Promise.resolve(); + expect(controller.executePoll.mock.calls).toMatchObject([ + ['mainnet'], + ['sepolia'], + ]); + jest.advanceTimersByTime(TICK_TIME); + await Promise.resolve(); + expect(controller.executePoll.mock.calls).toMatchObject([ + ['mainnet'], + ['sepolia'], + ['mainnet'], + ]); + jest.advanceTimersByTime(TICK_TIME); + await Promise.resolve(); + expect(controller.executePoll.mock.calls).toMatchObject([ + ['mainnet'], + ['sepolia'], + ['mainnet'], + ['sepolia'], + ]); + }); }); }); From 207e05c3e5d0c504dff76f9ecca24ea1ea09b598 Mon Sep 17 00:00:00 2001 From: Shane Jonas Date: Tue, 26 Sep 2023 09:51:44 -0400 Subject: [PATCH 24/25] Fixed constraint issues --- packages/polling-controller/package.json | 10 +++++----- yarn.lock | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/polling-controller/package.json b/packages/polling-controller/package.json index 8216fd4839..40eb086809 100644 --- a/packages/polling-controller/package.json +++ b/packages/polling-controller/package.json @@ -29,8 +29,8 @@ }, "dependencies": { "@metamask/base-controller": "^3.2.1", - "@metamask/controller-utils": "^4.3.2", - "@metamask/network-controller": "^12.2.0", + "@metamask/controller-utils": "^5.0.0", + "@metamask/network-controller": "^13.0.0", "@metamask/utils": "^6.2.0", "@types/uuid": "^8.3.0", "uuid": "^8.3.2" @@ -41,12 +41,12 @@ "deepmerge": "^4.2.2", "jest": "^27.5.1", "ts-jest": "^27.1.4", - "typedoc": "^0.22.15", - "typedoc-plugin-missing-exports": "^0.22.6", + "typedoc": "^0.23.15", + "typedoc-plugin-missing-exports": "^0.23.0", "typescript": "~4.6.3" }, "peerDependencies": { - "@metamask/network-controller": "^12.2.0" + "@metamask/network-controller": "^13.0.0" }, "engines": { "node": ">=16.0.0" diff --git a/yarn.lock b/yarn.lock index 0e930a1c71..056dd4d5f7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2127,20 +2127,20 @@ __metadata: dependencies: "@metamask/auto-changelog": ^3.1.0 "@metamask/base-controller": ^3.2.1 - "@metamask/controller-utils": ^4.3.2 - "@metamask/network-controller": ^12.2.0 + "@metamask/controller-utils": ^5.0.0 + "@metamask/network-controller": ^13.0.0 "@metamask/utils": ^6.2.0 "@types/jest": ^27.4.1 "@types/uuid": ^8.3.0 deepmerge: ^4.2.2 jest: ^27.5.1 ts-jest: ^27.1.4 - typedoc: ^0.22.15 - typedoc-plugin-missing-exports: ^0.22.6 + typedoc: ^0.23.15 + typedoc-plugin-missing-exports: ^0.23.0 typescript: ~4.6.3 uuid: ^8.3.2 peerDependencies: - "@metamask/network-controller": ^12.2.0 + "@metamask/network-controller": ^13.0.0 languageName: unknown linkType: soft From 4f02231434c4c922b76fb7892a57c0fed5c43ea9 Mon Sep 17 00:00:00 2001 From: Shane Jonas Date: Tue, 26 Sep 2023 10:31:24 -0400 Subject: [PATCH 25/25] Fixed PollingController uuid to use v4 --- packages/polling-controller/src/PollingController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/polling-controller/src/PollingController.ts b/packages/polling-controller/src/PollingController.ts index f87ed0f0e5..a9595cc4e8 100644 --- a/packages/polling-controller/src/PollingController.ts +++ b/packages/polling-controller/src/PollingController.ts @@ -5,7 +5,7 @@ import type { } from '@metamask/base-controller'; import type { NetworkClientId } from '@metamask/network-controller'; import type { Json } from '@metamask/utils'; -import { v1 as random } from 'uuid'; +import { v4 as random } from 'uuid'; export type PollingCompleteType = { type: `${N}:pollingComplete`;