From c89e588e00621581560f8cac0c6c0ed54f9634ac Mon Sep 17 00:00:00 2001 From: shanejonas Date: Thu, 20 May 2021 17:40:54 -0700 Subject: [PATCH 01/10] refactor plugin controller to use v2 - add tests around plugin controller + worker controller + rpc handler --- packages/controllers/package.json | 1 + .../src/plugins/PluginController.test.ts | 122 +++++++++++ .../src/plugins/PluginController.ts | 200 +++++++++++------- .../src/workers/WorkerController.ts | 2 +- yarn.lock | 95 ++++++++- 5 files changed, 339 insertions(+), 81 deletions(-) create mode 100644 packages/controllers/src/plugins/PluginController.test.ts diff --git a/packages/controllers/package.json b/packages/controllers/package.json index bfc4380fd6..5f745edc86 100644 --- a/packages/controllers/package.json +++ b/packages/controllers/package.json @@ -23,6 +23,7 @@ "prepare": "yarn lint && yarn build" }, "dependencies": { + "@metamask/controllers": "^8.0.0", "@metamask/object-multiplex": "^1.1.0", "@metamask/obs-store": "^6.0.2", "@metamask/post-message-stream": "4.0.0", diff --git a/packages/controllers/src/plugins/PluginController.test.ts b/packages/controllers/src/plugins/PluginController.test.ts new file mode 100644 index 0000000000..a3b753fd69 --- /dev/null +++ b/packages/controllers/src/plugins/PluginController.test.ts @@ -0,0 +1,122 @@ +import fs from 'fs'; +import { ControllerMessenger } from '@metamask/controllers/dist/ControllerMessenger'; +import { WorkerController } from '../workers/WorkerController'; +import { PluginController } from './PluginController'; + +const workerCode = fs.readFileSync( + require.resolve('@mm-snap/workers/dist/PluginWorker.js'), + 'utf8', +); + +describe('PluginController Controller', () => { + it('can create a worker and plugin controller', async () => { + const workerController = new WorkerController({ + setupWorkerConnection: jest.fn(), + workerUrl: new URL(URL.createObjectURL(new Blob([workerCode]))), + }); + const pluginController = new PluginController({ + command: workerController.command.bind(workerController), + createPluginWorker: workerController.createPluginWorker.bind( + workerController, + ), + terminateAll: workerController.terminateAll.bind(workerController), + terminateWorkerOf: workerController.terminateWorkerOf.bind( + workerController, + ), + startPlugin: workerController.startPlugin.bind(workerController), + removeAllPermissionsFor: jest.fn(), + getPermissions: jest.fn(), + hasPermission: jest.fn(), + requestPermissions: jest.fn(), + closeAllConnections: jest.fn(), + messenger: new ControllerMessenger().getRestricted({ + name: 'PluginController', + allowedActions: [], + allowedEvents: [], + }), + state: { + pluginStates: {}, + plugins: {}, + }, + metadata: { + pluginStates: { + persist: false, + anonymous: false, + }, + plugins: { + persist: false, + anonymous: false, + }, + }, + }); + expect(pluginController).toBeDefined(); + }); + it('can add a plugin and use its JSON-RPC api', async () => { + const workerController = new WorkerController({ + setupWorkerConnection: jest.fn(), + workerUrl: new URL(URL.createObjectURL(new Blob([workerCode]))), + }); + const pluginController = new PluginController({ + command: workerController.command.bind(workerController), + createPluginWorker: workerController.createPluginWorker.bind( + workerController, + ), + terminateAll: workerController.terminateAll.bind(workerController), + terminateWorkerOf: workerController.terminateWorkerOf.bind( + workerController, + ), + startPlugin: workerController.startPlugin.bind(workerController), + removeAllPermissionsFor: jest.fn(), + getPermissions: jest.fn(), + hasPermission: jest.fn(), + requestPermissions: jest.fn(), + closeAllConnections: jest.fn(), + messenger: new ControllerMessenger().getRestricted({ + name: 'PluginController', + allowedActions: [], + allowedEvents: [], + }), + state: { + pluginStates: {}, + plugins: {}, + }, + metadata: { + pluginStates: { + persist: false, + anonymous: false, + }, + plugins: { + persist: false, + anonymous: false, + }, + }, + }); + const plugin = await pluginController.add({ + name: 'TestPlugin', + sourceCode: ` + wallet.registerRpcMessageHandler(async (origin, request) => { + const {method, params, id} = request; + return method + id; + }); + `, + manifest: { + web3Wallet: { + initialPermissions: {}, + }, + version: '0.0.0-development', + }, + }); + await pluginController.startPlugin(plugin.name); + const handle = pluginController.getRpcMessageHandler(plugin.name); + if (!handle) { + throw Error('rpc handler not found'); + } + const result = await handle('foo.com', { + jsonrpc: '2.0', + method: 'test', + params: {}, + id: 1, + }); + expect(result).toEqual('test1'); + }); +}); diff --git a/packages/controllers/src/plugins/PluginController.ts b/packages/controllers/src/plugins/PluginController.ts index 7192a31754..e179767285 100644 --- a/packages/controllers/src/plugins/PluginController.ts +++ b/packages/controllers/src/plugins/PluginController.ts @@ -1,27 +1,31 @@ +/* eslint-disable @typescript-eslint/consistent-type-definitions */ import { ObservableStore } from '@metamask/obs-store'; -import EventEmitter from '@metamask/safe-event-emitter'; import { ethErrors, serializeError } from 'eth-rpc-errors'; import { IOcapLdCapability } from 'rpc-cap/dist/src/@types/ocap-ld'; import { IRequestedPermissions } from 'rpc-cap/dist/src/@types'; import { nanoid } from 'nanoid'; import { - WorkerController, - SetupWorkerConnection, -} from '../workers/WorkerController'; + BaseController, + StateMetadata, +} from '@metamask/controllers/dist/BaseControllerV2'; + +import { RestrictedControllerMessenger } from '@metamask/controllers/dist/ControllerMessenger'; +import { Json, JsonRpcRequest } from 'json-rpc-engine'; +import { PluginData } from '@mm-snap/types'; +import { PluginWorkerMetadata } from '../workers/WorkerController'; import { INLINE_PLUGINS } from './inlinePlugins'; export const PLUGIN_PREFIX = 'wallet_plugin_'; export const PLUGIN_PREFIX_REGEX = new RegExp(`^${PLUGIN_PREFIX}`, 'u'); - const SERIALIZABLE_PLUGIN_PROPERTIES = new Set([ 'initialPermissions', 'name', 'permissionName', ]); -interface InititalPermissions { - [permission: string]: Record; -} +type InititalPermissions = { + [permission: string]: IOcapLdCapability; +}; export interface SerializablePlugin { initialPermissions: InititalPermissions; @@ -54,37 +58,55 @@ type CloseAllConnectionsFunction = (domain: string) => void; type RequestPermissionsFunction = ( domain: string, requestedPermissions: IRequestedPermissions, -) => IOcapLdCapability[]; +) => Promise; type HasPermissionFunction = ( domain: string, permissionName: string, ) => boolean; type GetPermissionsFunction = (domain: string) => IOcapLdCapability[]; +type TerminateWorkerOf = (pluginName: string) => void; +type Command = ( + workerId: string, + message: JsonRpcRequest, +) => Promise; +type TerminateAll = () => void; +type CreatePluginWorker = (metadate: PluginWorkerMetadata) => Promise; +type StartPlugin = ( + workerId: string, + pluginData: PluginData, +) => Promise; -interface StoredPlugins { - [pluginId: string]: Plugin; -} +type StoredPlugins = Record; -export interface PluginControllerState { +export type PluginControllerState = { plugins: StoredPlugins; - pluginStates: Record; -} + pluginStates: { + [id: string]: Json; + }; +}; export interface PluginControllerMemState { inlinePluginIsRunning: boolean; - plugins: { [pluginId: string]: SerializablePlugin }; - pluginStates: Record; + plugins: Record; + pluginStates: { + [id: string]: Json; + }; } interface PluginControllerArgs { - initState: Partial; + messenger: RestrictedControllerMessenger; + metadata: StateMetadata; + state: PluginControllerState; removeAllPermissionsFor: RemoveAllPermissionsFunction; - setupWorkerPluginProvider: SetupWorkerConnection; closeAllConnections: CloseAllConnectionsFunction; requestPermissions: RequestPermissionsFunction; getPermissions: GetPermissionsFunction; hasPermission: HasPermissionFunction; - workerUrl: URL; + terminateWorkerOf: TerminateWorkerOf; + command: Command; + terminateAll: TerminateAll; + createPluginWorker: CreatePluginWorker; + startPlugin: StartPlugin; } interface AddPluginBase { @@ -108,6 +130,13 @@ interface AddPluginDirectlyArgs extends AddPluginBase { type AddPluginArgs = AddPluginByFetchingArgs | AddPluginDirectlyArgs; +const defaultState: PluginControllerState = { + plugins: {}, + pluginStates: {}, +}; + +const name = 'PluginController'; + /* * A plugin is initialized in three phases: * - Add: Loads the plugin from a remote source and parses it. @@ -115,13 +144,12 @@ type AddPluginArgs = AddPluginByFetchingArgs | AddPluginDirectlyArgs; * - Start: Initializes the plugin in its SES realm with the authorized permissions. */ -export class PluginController extends EventEmitter { - public store: ObservableStore; - +export class PluginController extends BaseController< + string, + PluginControllerState +> { public memStore: ObservableStore; - private workerController: WorkerController; - private _removeAllPermissionsFor: RemoveAllPermissionsFunction; private _pluginRpcHooks: Map; @@ -134,28 +162,38 @@ export class PluginController extends EventEmitter { private _hasPermission: HasPermissionFunction; + private _terminateWorkerOf: TerminateWorkerOf; + + private _command: Command; + + private _terminateAll: TerminateAll; + + private _createPluginWorker: CreatePluginWorker; + + private _startPlugin: StartPlugin; + private _pluginsBeingAdded: Map>; constructor({ - initState, - setupWorkerPluginProvider, removeAllPermissionsFor, closeAllConnections, requestPermissions, getPermissions, + terminateWorkerOf, + terminateAll, hasPermission, - workerUrl, + createPluginWorker, + startPlugin, + command, + messenger, + metadata, + state, }: PluginControllerArgs) { - super(); - const _initState: PluginControllerState = { - plugins: {}, - pluginStates: {}, - ...initState, - }; - - this.store = new ObservableStore({ - plugins: {}, - pluginStates: {}, + super({ + messenger, + metadata, + name, + state: { ...defaultState, ...state }, }); this.memStore = new ObservableStore({ @@ -163,18 +201,18 @@ export class PluginController extends EventEmitter { plugins: {}, pluginStates: {}, }); - this.updateState(_initState); - - this.workerController = new WorkerController({ - setupWorkerConnection: setupWorkerPluginProvider, - workerUrl, - }); + this.updateState(state); this._removeAllPermissionsFor = removeAllPermissionsFor; this._closeAllConnections = closeAllConnections; this._requestPermissions = requestPermissions; this._getPermissions = getPermissions; this._hasPermission = hasPermission; + this._terminateWorkerOf = terminateWorkerOf; + this._terminateAll = terminateAll; + this._createPluginWorker = createPluginWorker; + this._startPlugin = startPlugin; + this._command = command; this._pluginRpcHooks = new Map(); this._pluginsBeingAdded = new Map(); @@ -184,7 +222,14 @@ export class PluginController extends EventEmitter { * Updates the state of this controller. */ updateState(newState: Partial) { - this.store.updateState(newState); + this.update((state) => { + if (newState.pluginStates) { + state.pluginStates = newState.pluginStates as any; + } + if (newState.plugins) { + state.plugins = newState.plugins as any; + } + }); this.memStore.updateState(this._filterMemStoreState(newState)); } @@ -204,10 +249,12 @@ export class PluginController extends EventEmitter { memState.plugins = this.memStore.getState().plugins || {}; // Remove sourceCode from updated memState plugin objects - Object.keys(newState.plugins).forEach((name) => { - const plugin = { ...(newState as PluginControllerState).plugins[name] }; + Object.keys(newState.plugins).forEach((pluginName) => { + const plugin = { + ...(newState as PluginControllerState).plugins[pluginName], + }; delete (plugin as Partial).sourceCode; - (memState as PluginControllerMemState).plugins[name] = plugin; + (memState as PluginControllerMemState).plugins[pluginName] = plugin; }); } @@ -219,7 +266,7 @@ export class PluginController extends EventEmitter { * Deletes any plugins that cannot be started. */ async runExistingPlugins(): Promise { - const { plugins } = this.store.getState(); + const { plugins } = this.state; if (Object.keys(plugins).length > 0) { console.log('Starting existing plugins...', plugins); @@ -295,7 +342,7 @@ export class PluginController extends EventEmitter { private _stopPlugin(pluginName: string, setNotRunning = true): void { this._removePluginHooks(pluginName); this._closeAllConnections(pluginName); - this.workerController.terminateWorkerOf(pluginName); + this._terminateWorkerOf(pluginName); if (setNotRunning) { this._setPluginToNotRunning(pluginName); } @@ -322,7 +369,7 @@ export class PluginController extends EventEmitter { * @param pluginName - The name of the plugin to check for. */ has(pluginName: string): boolean { - return pluginName in this.store.getState().plugins; + return pluginName in this.state.plugins; } /** @@ -333,7 +380,7 @@ export class PluginController extends EventEmitter { * @param pluginName - The name of the plugin to get. */ get(pluginName: string) { - return this.store.getState().plugins[pluginName]; + return this.state.plugins[pluginName]; } /** @@ -366,11 +413,10 @@ export class PluginController extends EventEmitter { */ async updatePluginState( pluginName: string, - newPluginState: unknown, + newPluginState: Json, ): Promise { - const state = this.store.getState(); const newPluginStates = { - ...state.pluginStates, + ...this.state.pluginStates, [pluginName]: newPluginState, }; @@ -386,7 +432,7 @@ export class PluginController extends EventEmitter { * @param pluginName - The name of the plugin whose state to get. */ async getPluginState(pluginName: string): Promise { - return this.store.getState().pluginStates[pluginName]; + return this.state.pluginStates[pluginName]; } /** @@ -395,15 +441,15 @@ export class PluginController extends EventEmitter { */ clearState() { this._pluginRpcHooks.clear(); - const pluginNames = Object.keys(this.store.getState().plugins); + const pluginNames = Object.keys(this.state.plugins); this.updateState({ plugins: {}, pluginStates: {}, }); - pluginNames.forEach((name) => { - this._closeAllConnections(name); + pluginNames.forEach((pluginName) => { + this._closeAllConnections(pluginName); }); - this.workerController.terminateAll(); + this._terminateAll(); this._removeAllPermissionsFor(pluginNames); this.memStore.updateState({ inlinePluginIsRunning: false, @@ -431,14 +477,13 @@ export class PluginController extends EventEmitter { throw new Error('Expected array of plugin names.'); } - const state = this.store.getState(); - const newPlugins = { ...state.plugins }; - const newPluginStates = { ...state.pluginStates }; + const newPlugins = { ...this.state.plugins }; + const newPluginStates = { ...this.state.pluginStates }; - pluginNames.forEach((name) => { - this._stopPlugin(name, false); - delete newPlugins[name]; - delete newPluginStates[name]; + pluginNames.forEach((pluginName) => { + this._stopPlugin(pluginName, false); + delete newPlugins[pluginName]; + delete newPluginStates[pluginName]; }); this._removeAllPermissionsFor(pluginNames); @@ -619,7 +664,7 @@ export class PluginController extends EventEmitter { version: manifest.version, }; - const pluginsState = this.store.getState().plugins; + const pluginsState = this.state.plugins; // restore relevant plugin state if it exists if (pluginsState[pluginName]) { @@ -644,7 +689,7 @@ export class PluginController extends EventEmitter { * @param manifestUrl - The URL of the plugin's manifest file. */ private async _fetchPlugin( - name: string, + pluginName: string, manifestUrl: string, ): Promise<[PluginManifest, string]> { try { @@ -663,7 +708,9 @@ export class PluginController extends EventEmitter { return [manifest, sourceCode]; } catch (err) { - throw new Error(`Problem fetching plugin "${name}": ${err.message}`); + throw new Error( + `Problem fetching plugin "${pluginName}": ${err.message}`, + ); } } @@ -676,7 +723,7 @@ export class PluginController extends EventEmitter { */ async authorize(pluginName: string): Promise { console.log(`Authorizing plugin: ${pluginName}`); - const pluginsState = this.store.getState().plugins; + const pluginsState = this.state.plugins; const plugin = pluginsState[pluginName]; const { initialPermissions } = plugin; @@ -684,6 +731,9 @@ export class PluginController extends EventEmitter { if (Object.keys(initialPermissions).length === 0) { return []; } + if (initialPermissions === null) { + return []; + } try { const approvedPermissions = await this._requestPermissions( @@ -717,11 +767,11 @@ export class PluginController extends EventEmitter { } private async _startPluginInWorker(pluginName: string, sourceCode: string) { - const workerId = await this.workerController.createPluginWorker({ + const workerId = await this._createPluginWorker({ hostname: pluginName, }); this._createPluginHooks(pluginName, workerId); - await this.workerController.startPlugin(workerId, { + await this._startPlugin(workerId, { pluginName, sourceCode, }); @@ -742,7 +792,7 @@ export class PluginController extends EventEmitter { origin: string, request: Record, ) => { - return await this.workerController.command(workerId, { + return await this._command(workerId, { id: nanoid(), jsonrpc: '2.0', method: 'pluginRpc', @@ -774,7 +824,7 @@ export class PluginController extends EventEmitter { property: keyof Plugin, value: unknown, ) { - const { plugins } = this.store.getState(); + const { plugins } = this.state; const plugin = plugins[pluginName]; const newPlugin = { ...plugin, [property]: value }; const newPlugins = { ...plugins, [pluginName]: newPlugin }; diff --git a/packages/controllers/src/workers/WorkerController.ts b/packages/controllers/src/workers/WorkerController.ts index 05366530cc..0adaef0837 100644 --- a/packages/controllers/src/workers/WorkerController.ts +++ b/packages/controllers/src/workers/WorkerController.ts @@ -16,7 +16,7 @@ import { export type SetupWorkerConnection = (metadata: any, stream: Duplex) => void; -interface PluginWorkerMetadata { +export interface PluginWorkerMetadata { hostname: string; } interface WorkerControllerArgs { diff --git a/yarn.lock b/yarn.lock index 7c59c8fe78..5a8ef14d4a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1522,6 +1522,11 @@ resolved "https://registry.yarnpkg.com/@metamask/contract-metadata/-/contract-metadata-1.22.0.tgz#55cc84756c703c433176b484b1d34f0e03d16d1e" integrity sha512-t4ijbU+4OH9UAlrPkfLPFo6KmkRTRZJHB+Vly4ajF8oZMnota5YjVVl/SmltsoRC9xvJtRn9DUVf3YMHMIdofw== +"@metamask/contract-metadata@^1.24.0": + version "1.25.0" + resolved "https://registry.yarnpkg.com/@metamask/contract-metadata/-/contract-metadata-1.25.0.tgz#442ace91fb40165310764b68d8096d0017bb0492" + integrity sha512-yhmYB9CQPv0dckNcPoWDcgtrdUp0OgK0uvkRE5QIBv4b3qENI1/03BztvK2ijbTuMlORUpjPq7/1MQDUPoRPVw== + "@metamask/controllers@^5.0.0": version "5.1.0" resolved "https://registry.yarnpkg.com/@metamask/controllers/-/controllers-5.1.0.tgz#02c1957295bcb6db1655a716d165665d170e7f34" @@ -1550,6 +1555,38 @@ web3 "^0.20.7" web3-provider-engine "^16.0.1" +"@metamask/controllers@^8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@metamask/controllers/-/controllers-8.0.0.tgz#42ac5aaef67a03d3fe599a67a36597e01902ca8d" + integrity sha512-TrteMifsCxV1g3WHcSD1X98fF4hKep3sXZNGfrvkPqa8mrF03hJke21WBSTRtvJ3vkNLRWgi+5I6lVXFTzbYuQ== + dependencies: + "@metamask/contract-metadata" "^1.24.0" + "@types/uuid" "^8.3.0" + async-mutex "^0.2.6" + babel-runtime "^6.26.0" + eth-ens-namehash "^2.0.8" + eth-json-rpc-infura "^5.1.0" + eth-keyring-controller "^6.1.0" + eth-method-registry "1.1.0" + eth-phishing-detect "^1.1.13" + eth-query "^2.1.2" + eth-rpc-errors "^4.0.0" + eth-sig-util "^3.0.0" + ethereumjs-tx "^1.3.7" + ethereumjs-util "^6.1.0" + ethereumjs-wallet "^1.0.1" + ethjs-util "^0.1.6" + human-standard-collectible-abi "^1.0.2" + human-standard-token-abi "^2.0.0" + immer "^8.0.1" + isomorphic-fetch "^3.0.0" + jsonschema "^1.2.4" + nanoid "^3.1.12" + single-call-balance-checker-abi "^1.0.0" + uuid "^8.3.2" + web3 "^0.20.7" + web3-provider-engine "^16.0.1" + "@metamask/eslint-config-nodejs@^6.0.0": version "6.0.0" resolved "https://registry.yarnpkg.com/@metamask/eslint-config-nodejs/-/eslint-config-nodejs-6.0.0.tgz#df77bb35b91556030f1b23ad4ff51c1caf033339" @@ -1706,6 +1743,13 @@ dependencies: "@types/node" "*" +"@types/bn.js@^5.1.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-5.1.0.tgz#32c5d271503a12653c62cf4d2b45e6eab8cebc68" + integrity sha512-QSSVYj7pYFN49kW77o2s9xTCwZ8F2xLbjLLSEVh8D2F4JUhZtPAGOFLTD+ffqksBx/u4cE/KImFjyhqCjn/LIA== + dependencies: + "@types/node" "*" + "@types/graceful-fs@^4.1.2": version "4.1.5" resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.5.tgz#21ffba0d98da4350db64891f92a9e5db3cdb4e15" @@ -1822,6 +1866,11 @@ resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.0.tgz#7036640b4e21cc2f259ae826ce843d277dad8cff" integrity sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw== +"@types/uuid@^8.3.0": + version "8.3.0" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.0.tgz#215c231dff736d5ba92410e6d602050cce7e273f" + integrity sha512-eQ9qFW/fhfGJF8WKHGEHZEyVWfZxrT+6CLIJGBcZPfxUh/+BnEj+UCGYMlr9qZuX/2AltsvwrGqp0LhEW8D0zQ== + "@types/yargs-parser@*": version "20.2.0" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-20.2.0.tgz#dd3e6699ba3237f0348cd085e4698780204842f9" @@ -2518,6 +2567,11 @@ bn.js@^5.0.0, bn.js@^5.1.1: resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.1.3.tgz#beca005408f642ebebea80b042b4d18d2ac0ee6b" integrity sha512-GkTiFpjFtUzU9CbMeJ5iazkCzGL3jrhzerzZIuqLABjbwRaFt33I9tUdSNryIptM+RxDet6OKm2WnLXzW51KsQ== +bn.js@^5.1.2: + version "5.2.0" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.0.tgz#358860674396c6997771a9d051fcc1b57d4ae002" + integrity sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw== + boolean@^3.0.1: version "3.0.4" resolved "https://registry.yarnpkg.com/boolean/-/boolean-3.0.4.tgz#aa1df8749af41d7211b66b4eee584722ff428c27" @@ -4394,7 +4448,7 @@ ethereumjs-common@^1.1.0, ethereumjs-common@^1.5.0: resolved "https://registry.yarnpkg.com/ethereumjs-common/-/ethereumjs-common-1.5.2.tgz#2065dbe9214e850f2e955a80e650cb6999066979" integrity sha512-hTfZjwGX52GS2jcVO6E2sx4YuFnf0Fhp5ylo4pEPhEffNln7vS59Hr5sLnp3/QCazFLluuBZ+FZ6J5HTp0EqCA== -ethereumjs-tx@^1.2.0, ethereumjs-tx@^1.2.2: +ethereumjs-tx@^1.2.0, ethereumjs-tx@^1.2.2, ethereumjs-tx@^1.3.7: version "1.3.7" resolved "https://registry.yarnpkg.com/ethereumjs-tx/-/ethereumjs-tx-1.3.7.tgz#88323a2d875b10549b8347e09f4862b546f3d89a" integrity sha512-wvLMxzt1RPhAQ9Yi3/HKZTn0FZYpnsmQdbKYfUUpi4j1SEIcbkd9tndVjcPrufY3V7j2IebOpC00Zp2P/Ay2kA== @@ -4436,6 +4490,18 @@ ethereumjs-util@^6.0.0, ethereumjs-util@^6.1.0: ethjs-util "0.1.6" rlp "^2.2.3" +ethereumjs-util@^7.0.2: + version "7.0.10" + resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-7.0.10.tgz#5fb7b69fa1fda0acc59634cf39d6b0291180fc1f" + integrity sha512-c/xThw6A+EAnej5Xk5kOzFzyoSnw0WX0tSlZ6pAsfGVvQj3TItaDg9b1+Fz1RJXA+y2YksKwQnuzgt1eY6LKzw== + dependencies: + "@types/bn.js" "^5.1.0" + bn.js "^5.1.2" + create-hash "^1.1.2" + ethereum-cryptography "^0.1.3" + ethjs-util "0.1.6" + rlp "^2.2.4" + ethereumjs-vm@^2.3.4: version "2.6.0" resolved "https://registry.yarnpkg.com/ethereumjs-vm/-/ethereumjs-vm-2.6.0.tgz#76243ed8de031b408793ac33907fb3407fe400c6" @@ -4468,6 +4534,20 @@ ethereumjs-wallet@^0.6.0, ethereumjs-wallet@^0.6.4: utf8 "^3.0.0" uuid "^3.3.2" +ethereumjs-wallet@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/ethereumjs-wallet/-/ethereumjs-wallet-1.0.1.tgz#664a4bcacfc1291ca2703de066df1178938dba1c" + integrity sha512-3Z5g1hG1das0JWU6cQ9HWWTY2nt9nXCcwj7eXVNAHKbo00XAZO8+NHlwdgXDWrL0SXVQMvTWN8Q/82DRH/JhPw== + dependencies: + aes-js "^3.1.1" + bs58check "^2.1.2" + ethereum-cryptography "^0.1.3" + ethereumjs-util "^7.0.2" + randombytes "^2.0.6" + scrypt-js "^3.0.1" + utf8 "^3.0.0" + uuid "^3.3.2" + ethjs-abi@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/ethjs-abi/-/ethjs-abi-0.2.0.tgz#d3e2c221011520fc499b71682036c14fcc2f5b25" @@ -4567,7 +4647,7 @@ ethjs-util@0.1.3: is-hex-prefixed "1.0.0" strip-hex-prefix "1.0.0" -ethjs-util@0.1.6, ethjs-util@^0.1.3: +ethjs-util@0.1.6, ethjs-util@^0.1.3, ethjs-util@^0.1.6: version "0.1.6" resolved "https://registry.yarnpkg.com/ethjs-util/-/ethjs-util-0.1.6.tgz#f308b62f185f9fe6237132fb2a9818866a5cd536" integrity sha512-CUnVOQq7gSpDHZVVrQW8ExxUETWrnrvXYvYz55wOU8Uj4VCgw56XC2B/fVqQN+f7gmrnRHSLVnFAwsCuNwji8w== @@ -5395,6 +5475,11 @@ immediate@^3.2.3: resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.3.0.tgz#1aef225517836bcdf7f2a2de2600c79ff0269266" integrity sha512-HR7EVodfFUdQCTIeySw+WDRFJlPcLOJbXfwwZ7Oom6tjsvZ3bOkCDJHehQC3nxJrv7+f9XecwazynjU8e4Vw3Q== +immer@^8.0.1: + version "8.0.4" + resolved "https://registry.yarnpkg.com/immer/-/immer-8.0.4.tgz#3a21605a4e2dded852fb2afd208ad50969737b7a" + integrity sha512-jMfL18P+/6P6epANRvRk6q8t+3gGhqsJ9EuJ25AXE+9bNTYtssvzeYbEd0mXRYWCmmXSIbnlpz6vd6iJlmGGGQ== + import-fresh@^3.0.0, import-fresh@^3.2.1: version "3.3.0" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" @@ -8405,7 +8490,7 @@ ripemd160@^2.0.0, ripemd160@^2.0.1: hash-base "^3.0.0" inherits "^2.0.1" -rlp@^2.0.0, rlp@^2.2.3: +rlp@^2.0.0, rlp@^2.2.3, rlp@^2.2.4: version "2.2.6" resolved "https://registry.yarnpkg.com/rlp/-/rlp-2.2.6.tgz#c80ba6266ac7a483ef1e69e8e2f056656de2fb2c" integrity sha512-HAfAmL6SDYNWPUOJNrM500x4Thn4PZsEy5pijPh40U9WfNk0z15hUYzO9xVIMAdIHdFtD8CBDHd75Td1g36Mjg== @@ -8522,7 +8607,7 @@ scope-analyzer@^2.0.0: estree-is-function "^1.0.0" get-assigned-identifiers "^1.1.0" -scrypt-js@^3.0.0: +scrypt-js@^3.0.0, scrypt-js@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/scrypt-js/-/scrypt-js-3.0.1.tgz#d314a57c2aef69d1ad98a138a21fe9eafa9ee312" integrity sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA== @@ -9673,7 +9758,7 @@ uuid@^3.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== -uuid@^8.3.0: +uuid@^8.3.0, uuid@^8.3.2: version "8.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== From 3ea1d9fc58c2f5205069614fd38b00c49846595b Mon Sep 17 00:00:00 2001 From: shanejonas Date: Tue, 25 May 2021 15:17:01 -0700 Subject: [PATCH 02/10] fix initialPermission types + immer Draft state type --- packages/controllers/package.json | 2 +- .../src/plugins/PluginController.test.ts | 4 -- .../src/plugins/PluginController.ts | 14 ++--- yarn.lock | 59 +++++++++++++++---- 4 files changed, 56 insertions(+), 23 deletions(-) diff --git a/packages/controllers/package.json b/packages/controllers/package.json index 5f745edc86..713cbaee81 100644 --- a/packages/controllers/package.json +++ b/packages/controllers/package.json @@ -23,7 +23,7 @@ "prepare": "yarn lint && yarn build" }, "dependencies": { - "@metamask/controllers": "^8.0.0", + "@metamask/controllers": "^9.1.0", "@metamask/object-multiplex": "^1.1.0", "@metamask/obs-store": "^6.0.2", "@metamask/post-message-stream": "4.0.0", diff --git a/packages/controllers/src/plugins/PluginController.test.ts b/packages/controllers/src/plugins/PluginController.test.ts index a3b753fd69..c09307c661 100644 --- a/packages/controllers/src/plugins/PluginController.test.ts +++ b/packages/controllers/src/plugins/PluginController.test.ts @@ -31,8 +31,6 @@ describe('PluginController Controller', () => { closeAllConnections: jest.fn(), messenger: new ControllerMessenger().getRestricted({ name: 'PluginController', - allowedActions: [], - allowedEvents: [], }), state: { pluginStates: {}, @@ -73,8 +71,6 @@ describe('PluginController Controller', () => { closeAllConnections: jest.fn(), messenger: new ControllerMessenger().getRestricted({ name: 'PluginController', - allowedActions: [], - allowedEvents: [], }), state: { pluginStates: {}, diff --git a/packages/controllers/src/plugins/PluginController.ts b/packages/controllers/src/plugins/PluginController.ts index e179767285..0c5e464006 100644 --- a/packages/controllers/src/plugins/PluginController.ts +++ b/packages/controllers/src/plugins/PluginController.ts @@ -2,7 +2,6 @@ import { ObservableStore } from '@metamask/obs-store'; import { ethErrors, serializeError } from 'eth-rpc-errors'; import { IOcapLdCapability } from 'rpc-cap/dist/src/@types/ocap-ld'; -import { IRequestedPermissions } from 'rpc-cap/dist/src/@types'; import { nanoid } from 'nanoid'; import { BaseController, @@ -24,7 +23,7 @@ const SERIALIZABLE_PLUGIN_PROPERTIES = new Set([ ]); type InititalPermissions = { - [permission: string]: IOcapLdCapability; + [permission: string]: Json; }; export interface SerializablePlugin { @@ -57,7 +56,7 @@ type RemoveAllPermissionsFunction = (pluginIds: string[]) => void; type CloseAllConnectionsFunction = (domain: string) => void; type RequestPermissionsFunction = ( domain: string, - requestedPermissions: IRequestedPermissions, + requestedPermissions: InititalPermissions, ) => Promise; type HasPermissionFunction = ( domain: string, @@ -208,6 +207,7 @@ export class PluginController extends BaseController< this._requestPermissions = requestPermissions; this._getPermissions = getPermissions; this._hasPermission = hasPermission; + this._terminateWorkerOf = terminateWorkerOf; this._terminateAll = terminateAll; this._createPluginWorker = createPluginWorker; @@ -222,12 +222,12 @@ export class PluginController extends BaseController< * Updates the state of this controller. */ updateState(newState: Partial) { - this.update((state) => { + this.update((state: any) => { if (newState.pluginStates) { - state.pluginStates = newState.pluginStates as any; + state.pluginStates = newState.pluginStates; } if (newState.plugins) { - state.plugins = newState.plugins as any; + state.plugins = newState.plugins; } }); this.memStore.updateState(this._filterMemStoreState(newState)); @@ -528,7 +528,7 @@ export class PluginController extends BaseController< */ async installPlugins( origin: string, - requestedPlugins: IRequestedPermissions, + requestedPlugins: InititalPermissions, ): Promise { const result: InstallPluginsResult = {}; diff --git a/yarn.lock b/yarn.lock index 5a8ef14d4a..7cdc23f155 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1522,7 +1522,7 @@ resolved "https://registry.yarnpkg.com/@metamask/contract-metadata/-/contract-metadata-1.22.0.tgz#55cc84756c703c433176b484b1d34f0e03d16d1e" integrity sha512-t4ijbU+4OH9UAlrPkfLPFo6KmkRTRZJHB+Vly4ajF8oZMnota5YjVVl/SmltsoRC9xvJtRn9DUVf3YMHMIdofw== -"@metamask/contract-metadata@^1.24.0": +"@metamask/contract-metadata@^1.25.0": version "1.25.0" resolved "https://registry.yarnpkg.com/@metamask/contract-metadata/-/contract-metadata-1.25.0.tgz#442ace91fb40165310764b68d8096d0017bb0492" integrity sha512-yhmYB9CQPv0dckNcPoWDcgtrdUp0OgK0uvkRE5QIBv4b3qENI1/03BztvK2ijbTuMlORUpjPq7/1MQDUPoRPVw== @@ -1555,20 +1555,20 @@ web3 "^0.20.7" web3-provider-engine "^16.0.1" -"@metamask/controllers@^8.0.0": - version "8.0.0" - resolved "https://registry.yarnpkg.com/@metamask/controllers/-/controllers-8.0.0.tgz#42ac5aaef67a03d3fe599a67a36597e01902ca8d" - integrity sha512-TrteMifsCxV1g3WHcSD1X98fF4hKep3sXZNGfrvkPqa8mrF03hJke21WBSTRtvJ3vkNLRWgi+5I6lVXFTzbYuQ== +"@metamask/controllers@^9.1.0": + version "9.1.0" + resolved "https://registry.yarnpkg.com/@metamask/controllers/-/controllers-9.1.0.tgz#4434f22eba2522889224b35aa08bc7b67d7248b7" + integrity sha512-jn/F0BNbaPsgEevHaPqk0lGAONKom4re1a4yBC67h7Vu6yu26CRi30SJl4xIh3IW4+ySbPhVLaiXFiXr3fESRQ== dependencies: - "@metamask/contract-metadata" "^1.24.0" + "@metamask/contract-metadata" "^1.25.0" "@types/uuid" "^8.3.0" async-mutex "^0.2.6" babel-runtime "^6.26.0" eth-ens-namehash "^2.0.8" eth-json-rpc-infura "^5.1.0" - eth-keyring-controller "^6.1.0" + eth-keyring-controller "^6.2.1" eth-method-registry "1.1.0" - eth-phishing-detect "^1.1.13" + eth-phishing-detect "^1.1.14" eth-query "^2.1.2" eth-rpc-errors "^4.0.0" eth-sig-util "^3.0.0" @@ -1582,6 +1582,7 @@ isomorphic-fetch "^3.0.0" jsonschema "^1.2.4" nanoid "^3.1.12" + punycode "^2.1.1" single-call-balance-checker-abi "^1.0.0" uuid "^8.3.2" web3 "^0.20.7" @@ -4236,6 +4237,17 @@ eth-hd-keyring@^3.5.0: events "^1.1.1" xtend "^4.0.1" +eth-hd-keyring@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/eth-hd-keyring/-/eth-hd-keyring-3.6.0.tgz#6835d30aa411b8d3ef098e82f6427b5325082abb" + integrity sha512-n2CwE9VNXsxLrXQa6suv0Umt4NT6+HtoahKgWx3YviXx4rQFwVT5nDwZfjhwrT31ESuoXYNIeJgz5hKLD96QeQ== + dependencies: + bip39 "^2.2.0" + eth-sig-util "^3.0.1" + eth-simple-keyring "^4.2.0" + ethereumjs-util "^7.0.9" + ethereumjs-wallet "^1.0.1" + eth-json-rpc-filters@^4.2.1: version "4.2.2" resolved "https://registry.yarnpkg.com/eth-json-rpc-filters/-/eth-json-rpc-filters-4.2.2.tgz#eb35e1dfe9357ace8a8908e7daee80b2cd60a10d" @@ -4290,6 +4302,21 @@ eth-keyring-controller@^6.1.0: loglevel "^1.5.0" obs-store "^4.0.3" +eth-keyring-controller@^6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/eth-keyring-controller/-/eth-keyring-controller-6.2.1.tgz#61901071fc74059ed37cb5ae93870fdcae6e3781" + integrity sha512-x2gTM1iHp2Kbvdtd9Eslysw0qzVZiqOzpVB3AU/ni2Xiit+rlcv2H80zYKjrEwlfWFDj4YILD3bOqlnEMmRJOA== + dependencies: + bip39 "^2.4.0" + bluebird "^3.5.0" + browser-passworder "^2.0.3" + eth-hd-keyring "^3.6.0" + eth-sig-util "^3.0.1" + eth-simple-keyring "^4.2.0" + ethereumjs-util "^7.0.9" + loglevel "^1.5.0" + obs-store "^4.0.3" + eth-method-registry@1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/eth-method-registry/-/eth-method-registry-1.1.0.tgz#3cc01bd23dcf513428d14a0bb19910652cc5cac0" @@ -4297,7 +4324,7 @@ eth-method-registry@1.1.0: dependencies: ethjs "^0.3.0" -eth-phishing-detect@^1.1.13: +eth-phishing-detect@^1.1.13, eth-phishing-detect@^1.1.14: version "1.1.14" resolved "https://registry.yarnpkg.com/eth-phishing-detect/-/eth-phishing-detect-1.1.14.tgz#64dcd35dd3a7a95266d875cbc5280842cda133d7" integrity sha512-nMQmzrgYabZ52YpuKZ38lStJy9Lozww2WBhyFbu/oepjsZzPvnl2KwJ6TJ78kk14USdl8Htt+eEMk2WAdAjlWg== @@ -4344,7 +4371,7 @@ eth-sig-util@^2.4.4, eth-sig-util@^2.5.0: tweetnacl "^1.0.3" tweetnacl-util "^0.15.0" -eth-sig-util@^3.0.0: +eth-sig-util@^3.0.0, eth-sig-util@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/eth-sig-util/-/eth-sig-util-3.0.1.tgz#8753297c83a3f58346bd13547b59c4b2cd110c96" integrity sha512-0Us50HiGGvZgjtWTyAI/+qTzYPMLy5Q451D0Xy68bxq1QMWdoOddDwGvsqcFT27uohKgalM9z/yxplyt+mY2iQ== @@ -4366,6 +4393,16 @@ eth-simple-keyring@^3.5.0: events "^1.1.1" xtend "^4.0.1" +eth-simple-keyring@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/eth-simple-keyring/-/eth-simple-keyring-4.2.0.tgz#c197a4bd4cce7d701b5f3607d0b843112ddb17e3" + integrity sha512-lBxFObXJTBjktDkQszXrqoB317wghEUYATQ3W19vLAjaznrmbdy1lccPhXIRMT9bHIUgNKOJQkLohNZiT9tO8Q== + dependencies: + eth-sig-util "^3.0.1" + ethereumjs-util "^7.0.9" + ethereumjs-wallet "^1.0.1" + events "^1.1.1" + ethereum-common@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/ethereum-common/-/ethereum-common-0.2.0.tgz#13bf966131cce1eeade62a1b434249bb4cb120ca" @@ -4490,7 +4527,7 @@ ethereumjs-util@^6.0.0, ethereumjs-util@^6.1.0: ethjs-util "0.1.6" rlp "^2.2.3" -ethereumjs-util@^7.0.2: +ethereumjs-util@^7.0.2, ethereumjs-util@^7.0.9: version "7.0.10" resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-7.0.10.tgz#5fb7b69fa1fda0acc59634cf39d6b0291180fc1f" integrity sha512-c/xThw6A+EAnej5Xk5kOzFzyoSnw0WX0tSlZ6pAsfGVvQj3TItaDg9b1+Fz1RJXA+y2YksKwQnuzgt1eY6LKzw== From 5baaa4e4fad3d6c37f91a5e263da0097228f491d Mon Sep 17 00:00:00 2001 From: shanejonas Date: Tue, 25 May 2021 15:22:22 -0700 Subject: [PATCH 03/10] move metadata into controller --- .../src/plugins/PluginController.test.ts | 21 +------------------ .../src/plugins/PluginController.ts | 18 +++++++++------- 2 files changed, 12 insertions(+), 27 deletions(-) diff --git a/packages/controllers/src/plugins/PluginController.test.ts b/packages/controllers/src/plugins/PluginController.test.ts index c09307c661..d781f0585d 100644 --- a/packages/controllers/src/plugins/PluginController.test.ts +++ b/packages/controllers/src/plugins/PluginController.test.ts @@ -36,16 +36,6 @@ describe('PluginController Controller', () => { pluginStates: {}, plugins: {}, }, - metadata: { - pluginStates: { - persist: false, - anonymous: false, - }, - plugins: { - persist: false, - anonymous: false, - }, - }, }); expect(pluginController).toBeDefined(); }); @@ -76,22 +66,13 @@ describe('PluginController Controller', () => { pluginStates: {}, plugins: {}, }, - metadata: { - pluginStates: { - persist: false, - anonymous: false, - }, - plugins: { - persist: false, - anonymous: false, - }, - }, }); const plugin = await pluginController.add({ name: 'TestPlugin', sourceCode: ` wallet.registerRpcMessageHandler(async (origin, request) => { const {method, params, id} = request; + wallet.request({method: 'setState'}) return method + id; }); `, diff --git a/packages/controllers/src/plugins/PluginController.ts b/packages/controllers/src/plugins/PluginController.ts index 0c5e464006..f9ee9da258 100644 --- a/packages/controllers/src/plugins/PluginController.ts +++ b/packages/controllers/src/plugins/PluginController.ts @@ -3,10 +3,7 @@ import { ObservableStore } from '@metamask/obs-store'; import { ethErrors, serializeError } from 'eth-rpc-errors'; import { IOcapLdCapability } from 'rpc-cap/dist/src/@types/ocap-ld'; import { nanoid } from 'nanoid'; -import { - BaseController, - StateMetadata, -} from '@metamask/controllers/dist/BaseControllerV2'; +import { BaseController } from '@metamask/controllers/dist/BaseControllerV2'; import { RestrictedControllerMessenger } from '@metamask/controllers/dist/ControllerMessenger'; import { Json, JsonRpcRequest } from 'json-rpc-engine'; @@ -94,7 +91,6 @@ export interface PluginControllerMemState { interface PluginControllerArgs { messenger: RestrictedControllerMessenger; - metadata: StateMetadata; state: PluginControllerState; removeAllPermissionsFor: RemoveAllPermissionsFunction; closeAllConnections: CloseAllConnectionsFunction; @@ -185,12 +181,20 @@ export class PluginController extends BaseController< startPlugin, command, messenger, - metadata, state, }: PluginControllerArgs) { super({ messenger, - metadata, + metadata: { + pluginStates: { + persist: false, + anonymous: false, + }, + plugins: { + persist: false, + anonymous: false, + }, + }, name, state: { ...defaultState, ...state }, }); From 16efd4010b709d53986e4e0fa8f509bc36d0ba3d Mon Sep 17 00:00:00 2001 From: Shane Date: Wed, 26 May 2021 21:34:21 -0700 Subject: [PATCH 04/10] Update packages/controllers/src/plugins/PluginController.ts Co-authored-by: Mark Stacey --- packages/controllers/src/plugins/PluginController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/controllers/src/plugins/PluginController.ts b/packages/controllers/src/plugins/PluginController.ts index f9ee9da258..b794c0fb41 100644 --- a/packages/controllers/src/plugins/PluginController.ts +++ b/packages/controllers/src/plugins/PluginController.ts @@ -3,7 +3,7 @@ import { ObservableStore } from '@metamask/obs-store'; import { ethErrors, serializeError } from 'eth-rpc-errors'; import { IOcapLdCapability } from 'rpc-cap/dist/src/@types/ocap-ld'; import { nanoid } from 'nanoid'; -import { BaseController } from '@metamask/controllers/dist/BaseControllerV2'; +import { BaseControllerV2: BaseController, RestrictedControllerMessenger } from '@metamask/controllers'; import { RestrictedControllerMessenger } from '@metamask/controllers/dist/ControllerMessenger'; import { Json, JsonRpcRequest } from 'json-rpc-engine'; From 6311cfa002d7097a8e2c90a048b4b19f879e02bb Mon Sep 17 00:00:00 2001 From: shanejonas Date: Wed, 26 May 2021 21:59:49 -0700 Subject: [PATCH 05/10] rename InitialPermissions => RequestedPluginPermissions + fix controller import --- .../controllers/src/plugins/PluginController.ts | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/packages/controllers/src/plugins/PluginController.ts b/packages/controllers/src/plugins/PluginController.ts index b794c0fb41..ca5453a432 100644 --- a/packages/controllers/src/plugins/PluginController.ts +++ b/packages/controllers/src/plugins/PluginController.ts @@ -3,9 +3,10 @@ import { ObservableStore } from '@metamask/obs-store'; import { ethErrors, serializeError } from 'eth-rpc-errors'; import { IOcapLdCapability } from 'rpc-cap/dist/src/@types/ocap-ld'; import { nanoid } from 'nanoid'; -import { BaseControllerV2: BaseController, RestrictedControllerMessenger } from '@metamask/controllers'; - -import { RestrictedControllerMessenger } from '@metamask/controllers/dist/ControllerMessenger'; +import { + BaseControllerV2 as BaseController, + RestrictedControllerMessenger, +} from '@metamask/controllers'; import { Json, JsonRpcRequest } from 'json-rpc-engine'; import { PluginData } from '@mm-snap/types'; import { PluginWorkerMetadata } from '../workers/WorkerController'; @@ -19,12 +20,12 @@ const SERIALIZABLE_PLUGIN_PROPERTIES = new Set([ 'permissionName', ]); -type InititalPermissions = { +type RequestedPluginPermissions = { [permission: string]: Json; }; export interface SerializablePlugin { - initialPermissions: InititalPermissions; + initialPermissions: RequestedPluginPermissions; name: string; permissionName: string; version: string; @@ -53,7 +54,7 @@ type RemoveAllPermissionsFunction = (pluginIds: string[]) => void; type CloseAllConnectionsFunction = (domain: string) => void; type RequestPermissionsFunction = ( domain: string, - requestedPermissions: InititalPermissions, + requestedPermissions: RequestedPluginPermissions, ) => Promise; type HasPermissionFunction = ( domain: string, @@ -115,7 +116,7 @@ interface AddPluginByFetchingArgs extends AddPluginBase { // The parts of a plugin package.json file that we care about interface PluginManifest { version: string; - web3Wallet: { initialPermissions: InititalPermissions }; + web3Wallet: { initialPermissions: RequestedPluginPermissions }; } interface AddPluginDirectlyArgs extends AddPluginBase { @@ -532,7 +533,7 @@ export class PluginController extends BaseController< */ async installPlugins( origin: string, - requestedPlugins: InititalPermissions, + requestedPlugins: RequestedPluginPermissions, ): Promise { const result: InstallPluginsResult = {}; From a56c8b1671518dfade72a43c259a7fbc0bd2eca1 Mon Sep 17 00:00:00 2001 From: shanejonas Date: Thu, 27 May 2021 12:50:23 -0700 Subject: [PATCH 06/10] remove memstore and unneeded updateState function --- .../src/plugins/PluginController.test.ts | 2 + .../src/plugins/PluginController.ts | 105 +++++------------- 2 files changed, 29 insertions(+), 78 deletions(-) diff --git a/packages/controllers/src/plugins/PluginController.test.ts b/packages/controllers/src/plugins/PluginController.test.ts index d781f0585d..9024f3d1b6 100644 --- a/packages/controllers/src/plugins/PluginController.test.ts +++ b/packages/controllers/src/plugins/PluginController.test.ts @@ -33,6 +33,7 @@ describe('PluginController Controller', () => { name: 'PluginController', }), state: { + inlinePluginIsRunning: false, pluginStates: {}, plugins: {}, }, @@ -63,6 +64,7 @@ describe('PluginController Controller', () => { name: 'PluginController', }), state: { + inlinePluginIsRunning: false, pluginStates: {}, plugins: {}, }, diff --git a/packages/controllers/src/plugins/PluginController.ts b/packages/controllers/src/plugins/PluginController.ts index ca5453a432..811ee9408d 100644 --- a/packages/controllers/src/plugins/PluginController.ts +++ b/packages/controllers/src/plugins/PluginController.ts @@ -1,5 +1,4 @@ /* eslint-disable @typescript-eslint/consistent-type-definitions */ -import { ObservableStore } from '@metamask/obs-store'; import { ethErrors, serializeError } from 'eth-rpc-errors'; import { IOcapLdCapability } from 'rpc-cap/dist/src/@types/ocap-ld'; import { nanoid } from 'nanoid'; @@ -76,6 +75,7 @@ type StartPlugin = ( type StoredPlugins = Record; export type PluginControllerState = { + inlinePluginIsRunning: boolean; plugins: StoredPlugins; pluginStates: { [id: string]: Json; @@ -127,6 +127,7 @@ interface AddPluginDirectlyArgs extends AddPluginBase { type AddPluginArgs = AddPluginByFetchingArgs | AddPluginDirectlyArgs; const defaultState: PluginControllerState = { + inlinePluginIsRunning: false, plugins: {}, pluginStates: {}, }; @@ -144,8 +145,6 @@ export class PluginController extends BaseController< string, PluginControllerState > { - public memStore: ObservableStore; - private _removeAllPermissionsFor: RemoveAllPermissionsFunction; private _pluginRpcHooks: Map; @@ -187,6 +186,10 @@ export class PluginController extends BaseController< super({ messenger, metadata: { + inlinePluginIsRunning: { + persist: false, + anonymous: false, + }, pluginStates: { persist: false, anonymous: false, @@ -200,12 +203,11 @@ export class PluginController extends BaseController< state: { ...defaultState, ...state }, }); - this.memStore = new ObservableStore({ - inlinePluginIsRunning: false, - plugins: {}, - pluginStates: {}, + this.update((_state: any) => { + _state.inlinePluginIsRunning = state.inlinePluginIsRunning; + _state.plugins = state.plugins; + _state.pluginStates = state.pluginStates; }); - this.updateState(state); this._removeAllPermissionsFor = removeAllPermissionsFor; this._closeAllConnections = closeAllConnections; @@ -223,49 +225,6 @@ export class PluginController extends BaseController< this._pluginsBeingAdded = new Map(); } - /** - * Updates the state of this controller. - */ - updateState(newState: Partial) { - this.update((state: any) => { - if (newState.pluginStates) { - state.pluginStates = newState.pluginStates; - } - if (newState.plugins) { - state.plugins = newState.plugins; - } - }); - this.memStore.updateState(this._filterMemStoreState(newState)); - } - - /** - * Takes in a full state object and filters out the parts that we don't want - * to keep in memory. Currently just the sourceCode property of any plugins. - */ - private _filterMemStoreState( - newState: Partial, - ): Partial { - const memState: Partial = { - ...newState, - }; - - if (newState.plugins) { - // Copy existing plugins to the new memState - memState.plugins = this.memStore.getState().plugins || {}; - - // Remove sourceCode from updated memState plugin objects - Object.keys(newState.plugins).forEach((pluginName) => { - const plugin = { - ...(newState as PluginControllerState).plugins[pluginName], - }; - delete (plugin as Partial).sourceCode; - (memState as PluginControllerMemState).plugins[pluginName] = plugin; - }); - } - - return memState; - } - /** * Runs existing (installed) plugins. * Deletes any plugins that cannot be started. @@ -420,13 +379,8 @@ export class PluginController extends BaseController< pluginName: string, newPluginState: Json, ): Promise { - const newPluginStates = { - ...this.state.pluginStates, - [pluginName]: newPluginState, - }; - - this.updateState({ - pluginStates: newPluginStates, + this.update((state: any) => { + state[pluginName] = newPluginState; }); } @@ -447,17 +401,15 @@ export class PluginController extends BaseController< clearState() { this._pluginRpcHooks.clear(); const pluginNames = Object.keys(this.state.plugins); - this.updateState({ - plugins: {}, - pluginStates: {}, - }); pluginNames.forEach((pluginName) => { this._closeAllConnections(pluginName); }); this._terminateAll(); this._removeAllPermissionsFor(pluginNames); - this.memStore.updateState({ - inlinePluginIsRunning: false, + this.update((state: any) => { + state.inlinePluginIsRunning = false; + state.plugins = {}; + state.pluginStates = {}; }); } @@ -492,9 +444,9 @@ export class PluginController extends BaseController< }); this._removeAllPermissionsFor(pluginNames); - this.updateState({ - plugins: newPlugins, - pluginStates: newPluginStates, + this.update((state: any) => { + state.plugins = newPlugins; + state.pluginStates = newPluginStates; }); } @@ -677,11 +629,8 @@ export class PluginController extends BaseController< } // store the plugin back in state - this.updateState({ - plugins: { - ...pluginsState, - [pluginName]: plugin, - }, + this.update((state: any) => { + state.plugins[pluginName] = plugin; }); return plugin; @@ -756,8 +705,8 @@ export class PluginController extends BaseController< */ runInlinePlugin(inlinePluginName: keyof typeof INLINE_PLUGINS = 'IDLE') { this._startPluginInWorker('inlinePlugin', INLINE_PLUGINS[inlinePluginName]); - this.memStore.updateState({ - inlinePluginIsRunning: true, + this.update((state: any) => { + state.inlinePluginIsRunning = true; }); } @@ -765,8 +714,8 @@ export class PluginController extends BaseController< * Test method. */ removeInlinePlugin() { - this.memStore.updateState({ - inlinePluginIsRunning: false, + this.update((state: any) => { + state.inlinePluginIsRunning = false; }); this.removePlugin('inlinePlugin'); } @@ -833,8 +782,8 @@ export class PluginController extends BaseController< const plugin = plugins[pluginName]; const newPlugin = { ...plugin, [property]: value }; const newPlugins = { ...plugins, [pluginName]: newPlugin }; - this.updateState({ - plugins: newPlugins, + this.update((state: any) => { + state.plugins = newPlugins; }); } } From 63ca4c509456ee4fe70163b2780204dd48346a9f Mon Sep 17 00:00:00 2001 From: shanejonas Date: Thu, 27 May 2021 13:35:25 -0700 Subject: [PATCH 07/10] fix metadata to persist: true for pluginStates + plugins --- packages/controllers/src/plugins/PluginController.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/controllers/src/plugins/PluginController.ts b/packages/controllers/src/plugins/PluginController.ts index 811ee9408d..789b132375 100644 --- a/packages/controllers/src/plugins/PluginController.ts +++ b/packages/controllers/src/plugins/PluginController.ts @@ -191,11 +191,11 @@ export class PluginController extends BaseController< anonymous: false, }, pluginStates: { - persist: false, + persist: true, anonymous: false, }, plugins: { - persist: false, + persist: true, anonymous: false, }, }, From cd00c8bdffcb9a526f0b4e337ab91edcad0cc616 Mon Sep 17 00:00:00 2001 From: Shane Date: Mon, 31 May 2021 10:54:27 -0700 Subject: [PATCH 08/10] Update packages/controllers/src/plugins/PluginController.test.ts Co-authored-by: Erik Marks <25517051+rekmarks@users.noreply.github.com> --- packages/controllers/src/plugins/PluginController.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/controllers/src/plugins/PluginController.test.ts b/packages/controllers/src/plugins/PluginController.test.ts index 9024f3d1b6..ad7a1102a2 100644 --- a/packages/controllers/src/plugins/PluginController.test.ts +++ b/packages/controllers/src/plugins/PluginController.test.ts @@ -40,6 +40,7 @@ describe('PluginController Controller', () => { }); expect(pluginController).toBeDefined(); }); + it('can add a plugin and use its JSON-RPC api', async () => { const workerController = new WorkerController({ setupWorkerConnection: jest.fn(), From 2dbf04f704f5d60a418420d8273ed2ff02faaf58 Mon Sep 17 00:00:00 2001 From: shanejonas Date: Mon, 31 May 2021 13:44:58 -0700 Subject: [PATCH 09/10] cleanup PluginController --- .../src/plugins/PluginController.ts | 32 ++++++------------- 1 file changed, 10 insertions(+), 22 deletions(-) diff --git a/packages/controllers/src/plugins/PluginController.ts b/packages/controllers/src/plugins/PluginController.ts index 789b132375..5d5b046c76 100644 --- a/packages/controllers/src/plugins/PluginController.ts +++ b/packages/controllers/src/plugins/PluginController.ts @@ -72,24 +72,17 @@ type StartPlugin = ( pluginData: PluginData, ) => Promise; -type StoredPlugins = Record; +type PluginId = string; +type StoredPlugins = Record; export type PluginControllerState = { inlinePluginIsRunning: boolean; plugins: StoredPlugins; pluginStates: { - [id: string]: Json; + [PluginId: string]: Json; }; }; -export interface PluginControllerMemState { - inlinePluginIsRunning: boolean; - plugins: Record; - pluginStates: { - [id: string]: Json; - }; -} - interface PluginControllerArgs { messenger: RestrictedControllerMessenger; state: PluginControllerState; @@ -434,20 +427,15 @@ export class PluginController extends BaseController< throw new Error('Expected array of plugin names.'); } - const newPlugins = { ...this.state.plugins }; - const newPluginStates = { ...this.state.pluginStates }; - - pluginNames.forEach((pluginName) => { - this._stopPlugin(pluginName, false); - delete newPlugins[pluginName]; - delete newPluginStates[pluginName]; - }); - this._removeAllPermissionsFor(pluginNames); - this.update((state: any) => { - state.plugins = newPlugins; - state.pluginStates = newPluginStates; + pluginNames.forEach((pluginName) => { + this._stopPlugin(pluginName, false); + delete state.plugins[pluginName]; + delete state.pluginStates[pluginName]; + }); }); + + this._removeAllPermissionsFor(pluginNames); } /** From 33074ac569f2281ca8e7230252d5091f8fc6908a Mon Sep 17 00:00:00 2001 From: shanejonas Date: Mon, 31 May 2021 14:00:58 -0700 Subject: [PATCH 10/10] fix controllers eslint --- packages/controllers/.eslintrc.js | 4 ++++ packages/controllers/src/plugins/PluginController.ts | 1 - 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/controllers/.eslintrc.js b/packages/controllers/.eslintrc.js index 5ddba4b3e5..42650ded8f 100644 --- a/packages/controllers/.eslintrc.js +++ b/packages/controllers/.eslintrc.js @@ -1,6 +1,10 @@ module.exports = { extends: ['../../.eslintrc.js'], + rules: { + '@typescript-eslint/consistent-type-definitions': 'off', + }, + overrides: [ { files: ['*.d.ts'], diff --git a/packages/controllers/src/plugins/PluginController.ts b/packages/controllers/src/plugins/PluginController.ts index 5d5b046c76..16ca8f2ecb 100644 --- a/packages/controllers/src/plugins/PluginController.ts +++ b/packages/controllers/src/plugins/PluginController.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/consistent-type-definitions */ import { ethErrors, serializeError } from 'eth-rpc-errors'; import { IOcapLdCapability } from 'rpc-cap/dist/src/@types/ocap-ld'; import { nanoid } from 'nanoid';