Skip to content

Commit

Permalink
refactor plugin + worker interface
Browse files Browse the repository at this point in the history
  • Loading branch information
shanejonas committed Jun 4, 2021
1 parent 857e209 commit 6356a42
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 75 deletions.
46 changes: 29 additions & 17 deletions packages/controllers/src/plugins/PluginController.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import fs from 'fs';
import { ControllerMessenger } from '@metamask/controllers/dist/ControllerMessenger';
import { WorkerController } from '../workers/WorkerController';
import { WorkerExecutionEnvironment } from '../services/WorkerExecutionEnvironment';
import { PluginController } from './PluginController';

const workerCode = fs.readFileSync(
Expand All @@ -10,20 +10,26 @@ const workerCode = fs.readFileSync(

describe('PluginController Controller', () => {
it('can create a worker and plugin controller', async () => {
const workerController = new WorkerController({
const workerExecutionEnvironment = new WorkerExecutionEnvironment({
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,
command: workerExecutionEnvironment.command.bind(
workerExecutionEnvironment,
),
terminateAll: workerController.terminateAll.bind(workerController),
terminateWorkerOf: workerController.terminateWorkerOf.bind(
workerController,
createPluginEnvironment: workerExecutionEnvironment.createPluginEnvironment.bind(
workerExecutionEnvironment,
),
terminateAll: workerExecutionEnvironment.terminateAllPlugins.bind(
workerExecutionEnvironment,
),
terminatePlugin: workerExecutionEnvironment.terminatePlugin.bind(
workerExecutionEnvironment,
),
startPlugin: workerExecutionEnvironment.startPlugin.bind(
workerExecutionEnvironment,
),
startPlugin: workerController.startPlugin.bind(workerController),
removeAllPermissionsFor: jest.fn(),
getPermissions: jest.fn(),
hasPermission: jest.fn(),
Expand All @@ -42,20 +48,26 @@ describe('PluginController Controller', () => {
});

it('can add a plugin and use its JSON-RPC api', async () => {
const workerController = new WorkerController({
const workerExecutionEnvironment = new WorkerExecutionEnvironment({
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,
command: workerExecutionEnvironment.command.bind(
workerExecutionEnvironment,
),
createPluginEnvironment: workerExecutionEnvironment.createPluginEnvironment.bind(
workerExecutionEnvironment,
),
terminateAll: workerExecutionEnvironment.terminateAllPlugins.bind(
workerExecutionEnvironment,
),
terminatePlugin: workerExecutionEnvironment.terminatePlugin.bind(
workerExecutionEnvironment,
),
terminateAll: workerController.terminateAll.bind(workerController),
terminateWorkerOf: workerController.terminateWorkerOf.bind(
workerController,
startPlugin: workerExecutionEnvironment.startPlugin.bind(
workerExecutionEnvironment,
),
startPlugin: workerController.startPlugin.bind(workerController),
removeAllPermissionsFor: jest.fn(),
getPermissions: jest.fn(),
hasPermission: jest.fn(),
Expand Down
70 changes: 36 additions & 34 deletions packages/controllers/src/plugins/PluginController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,14 @@ 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';
import { Json } from 'json-rpc-engine';
import {
Command,
CreatePluginEnvironment,
StartPlugin,
TerminateAll,
TerminatePlugin,
} from '../services/PluginExecutionEnvironmentService';
import { INLINE_PLUGINS } from './inlinePlugins';

export const PLUGIN_PREFIX = 'wallet_plugin_';
Expand Down Expand Up @@ -59,18 +64,6 @@ type HasPermissionFunction = (
permissionName: string,
) => boolean;
type GetPermissionsFunction = (domain: string) => IOcapLdCapability[];
type TerminateWorkerOf = (pluginName: string) => void;
type Command = (
workerId: string,
message: JsonRpcRequest<unknown>,
) => Promise<unknown>;
type TerminateAll = () => void;
type CreatePluginWorker = (metadate: PluginWorkerMetadata) => Promise<string>;
type StartPlugin = (
workerId: string,
pluginData: PluginData,
) => Promise<unknown>;

type PluginId = string;
type StoredPlugins = Record<PluginId, Plugin>;

Expand All @@ -90,10 +83,10 @@ interface PluginControllerArgs {
requestPermissions: RequestPermissionsFunction;
getPermissions: GetPermissionsFunction;
hasPermission: HasPermissionFunction;
terminateWorkerOf: TerminateWorkerOf;
terminatePlugin: TerminatePlugin;
command: Command;
terminateAll: TerminateAll;
createPluginWorker: CreatePluginWorker;
createPluginEnvironment: CreatePluginEnvironment;
startPlugin: StartPlugin;
}

Expand Down Expand Up @@ -149,13 +142,13 @@ export class PluginController extends BaseController<

private _hasPermission: HasPermissionFunction;

private _terminateWorkerOf: TerminateWorkerOf;
private _terminatePlugin: TerminatePlugin;

private _command: Command;

private _terminateAll: TerminateAll;

private _createPluginWorker: CreatePluginWorker;
private _createPluginEnvironment: CreatePluginEnvironment;

private _startPlugin: StartPlugin;

Expand All @@ -166,10 +159,10 @@ export class PluginController extends BaseController<
closeAllConnections,
requestPermissions,
getPermissions,
terminateWorkerOf,
terminatePlugin,
terminateAll,
hasPermission,
createPluginWorker,
createPluginEnvironment,
startPlugin,
command,
messenger,
Expand Down Expand Up @@ -207,9 +200,9 @@ export class PluginController extends BaseController<
this._getPermissions = getPermissions;
this._hasPermission = hasPermission;

this._terminateWorkerOf = terminateWorkerOf;
this._terminatePlugin = terminatePlugin;
this._terminateAll = terminateAll;
this._createPluginWorker = createPluginWorker;
this._createPluginEnvironment = createPluginEnvironment;
this._startPlugin = startPlugin;
this._command = command;

Expand All @@ -236,7 +229,7 @@ export class PluginController extends BaseController<
console.log(`Starting: ${pluginName}`);

try {
await this._startPluginInWorker(pluginName, sourceCode);
await this._startPluginInExecutionEnvironment(pluginName, sourceCode);
} catch (err) {
console.warn(`Failed to start "${pluginName}", deleting it.`, err);
// Clean up failed plugins:
Expand All @@ -262,7 +255,10 @@ export class PluginController extends BaseController<
}

try {
await this._startPluginInWorker(pluginName, plugin.sourceCode);
await this._startPluginInExecutionEnvironment(
pluginName,
plugin.sourceCode,
);
} catch (err) {
console.error(`Failed to start "${pluginName}".`, err);
}
Expand Down Expand Up @@ -298,7 +294,7 @@ export class PluginController extends BaseController<
private _stopPlugin(pluginName: string, setNotRunning = true): void {
this._removePluginHooks(pluginName);
this._closeAllConnections(pluginName);
this._terminateWorkerOf(pluginName);
this._terminatePlugin(pluginName);
if (setNotRunning) {
this._setPluginToNotRunning(pluginName);
}
Expand Down Expand Up @@ -525,7 +521,7 @@ export class PluginController extends BaseController<

await this.authorize(pluginName);

await this._startPluginInWorker(pluginName, sourceCode);
await this._startPluginInExecutionEnvironment(pluginName, sourceCode);

return this.getSerializable(pluginName) as SerializablePlugin;
} catch (err) {
Expand Down Expand Up @@ -691,7 +687,10 @@ export class PluginController extends BaseController<
* Test method.
*/
runInlinePlugin(inlinePluginName: keyof typeof INLINE_PLUGINS = 'IDLE') {
this._startPluginInWorker('inlinePlugin', INLINE_PLUGINS[inlinePluginName]);
this._startPluginInExecutionEnvironment(
'inlinePlugin',
INLINE_PLUGINS[inlinePluginName],
);
this.update((state: any) => {
state.inlinePluginIsRunning = true;
});
Expand All @@ -707,12 +706,15 @@ export class PluginController extends BaseController<
this.removePlugin('inlinePlugin');
}

private async _startPluginInWorker(pluginName: string, sourceCode: string) {
const workerId = await this._createPluginWorker({
private async _startPluginInExecutionEnvironment(
pluginName: string,
sourceCode: string,
) {
await this._createPluginEnvironment({
hostname: pluginName,
});
this._createPluginHooks(pluginName, workerId);
await this._startPlugin(workerId, {
this._createPluginHooks(pluginName);
await this._startPlugin({
pluginName,
sourceCode,
});
Expand All @@ -728,12 +730,12 @@ export class PluginController extends BaseController<
return this._pluginRpcHooks.get(pluginName);
}

private _createPluginHooks(pluginName: string, workerId: string) {
private _createPluginHooks(pluginName: string) {
const rpcHook = async (
origin: string,
request: Record<string, unknown>,
) => {
return await this._command(workerId, {
return await this._command(pluginName, {
id: nanoid(),
jsonrpc: '2.0',
method: 'pluginRpc',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { JsonRpcRequest } from 'json-rpc-engine';
import { PluginData } from '@mm-snap/types';

export interface PluginMetadata {
hostname: string;
}

export type TerminatePlugin = (pluginName: string) => void;
export type Command = (
pluginName: string,
message: JsonRpcRequest<unknown>,
) => Promise<unknown>;
export type TerminateAll = () => void;
export type CreatePluginEnvironment = (
metadata: PluginMetadata,
) => Promise<string>;
export type StartPlugin = (pluginData: PluginData) => Promise<unknown>;

export interface PluginExecutionEnvironmentService {
terminatePlugin: TerminatePlugin;
terminateAllPlugins: TerminateAll;
createPluginEnvironment: CreatePluginEnvironment;
command: Command;
startPlugin: StartPlugin;
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import fs from 'fs';
import { WorkerController } from './WorkerController';
import { WorkerExecutionEnvironment } from './WorkerExecutionEnvironment';

const workerCode = fs.readFileSync(
require.resolve('@mm-snap/workers/dist/PluginWorker.js'),
Expand All @@ -8,7 +8,7 @@ const workerCode = fs.readFileSync(

describe('Worker Controller', () => {
it('can boot', async () => {
const workerController = new WorkerController({
const workerController = new WorkerExecutionEnvironment({
setupWorkerConnection: () => {
// do nothing
},
Expand All @@ -17,30 +17,30 @@ describe('Worker Controller', () => {
expect(workerController).toBeDefined();
});
it('can create a plugin worker and get the workerId back', async () => {
const workerController = new WorkerController({
const workerController = new WorkerExecutionEnvironment({
setupWorkerConnection: () => {
// do nothing
},
workerUrl: new URL(URL.createObjectURL(new Blob([workerCode]))),
});
expect(
typeof (await workerController.createPluginWorker({
typeof (await workerController.createPluginEnvironment({
hostname: 'foobarbaz',
})),
).toEqual('string');
});
it('can create a plugin worker and start the plugin', async () => {
const workerController = new WorkerController({
const workerController = new WorkerExecutionEnvironment({
setupWorkerConnection: () => {
// do nothing
},
workerUrl: new URL(URL.createObjectURL(new Blob([workerCode]))),
});
const pluginName = 'foo.bar.baz';
const workerId = await workerController.createPluginWorker({
await workerController.createPluginEnvironment({
hostname: pluginName,
});
const response = await workerController.startPlugin(workerId, {
const response = await workerController.startPlugin({
pluginName,
sourceCode: `
console.log('foo');
Expand Down
Loading

0 comments on commit 6356a42

Please sign in to comment.