Skip to content

Commit

Permalink
Refactor worker controller to use generic execution environment inter…
Browse files Browse the repository at this point in the history
…face (#19)

This changes the interface names to not have `worker` in them, also changes the interface to them as to not require `workerId`, but use the `pluginName` instead.

It establishes a `ExecutionEnvironmentService` interface for other Execution Environment Services to follow and used in the `PluginController` to be able to swap out execution environments.

It also renames `WorkerController` to `WebWorkerExecutionEnvironmentService` which implements `ExecutionEnvironmentService`.

Co-authored-by: Erik Marks <[email protected]>
  • Loading branch information
shanejonas and rekmarks authored Jun 16, 2021
1 parent 857e209 commit e4bcb7f
Show file tree
Hide file tree
Showing 8 changed files with 302 additions and 204 deletions.
2 changes: 1 addition & 1 deletion packages/controllers/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export * from './workers/WorkerController';
export * from './services/WebWorkerExecutionEnvironmentService';
export * from './plugins/PluginController';
export * from './resource/ExternalResourceController';
143 changes: 118 additions & 25 deletions packages/controllers/src/plugins/PluginController.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import fs from 'fs';
import { ControllerMessenger } from '@metamask/controllers/dist/ControllerMessenger';
import { WorkerController } from '../workers/WorkerController';
import { WebWorkerExecutionEnvironmentService } from '../services/WebWorkerExecutionEnvironmentService';
import { ExecutionEnvironmentService } from '../services/ExecutionEnvironmentService';
import { PluginController } from './PluginController';

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

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 workerExecutionEnvironment = new WebWorkerExecutionEnvironmentService(
{
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,
terminateAllPlugins: workerExecutionEnvironment.terminateAllPlugins.bind(
workerExecutionEnvironment,
),
terminatePlugin: workerExecutionEnvironment.terminatePlugin.bind(
workerExecutionEnvironment,
),
executePlugin: workerExecutionEnvironment.executePlugin.bind(
workerExecutionEnvironment,
),
terminateAll: workerController.terminateAll.bind(workerController),
terminateWorkerOf: workerController.terminateWorkerOf.bind(
workerController,
getRpcMessageHandler: workerExecutionEnvironment.getRpcMessageHandler.bind(
workerExecutionEnvironment,
),
startPlugin: workerController.startPlugin.bind(workerController),
removeAllPermissionsFor: jest.fn(),
getPermissions: jest.fn(),
hasPermission: jest.fn(),
Expand All @@ -41,21 +47,26 @@ 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(),
workerUrl: new URL(URL.createObjectURL(new Blob([workerCode]))),
});
it('can add a plugin and use its JSON-RPC api with a WebWorkerExecutionEnvironmentService', async () => {
const webWorkerExecutionEnvironment = new WebWorkerExecutionEnvironmentService(
{
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,
terminateAllPlugins: webWorkerExecutionEnvironment.terminateAllPlugins.bind(
webWorkerExecutionEnvironment,
),
terminatePlugin: webWorkerExecutionEnvironment.terminatePlugin.bind(
webWorkerExecutionEnvironment,
),
executePlugin: webWorkerExecutionEnvironment.executePlugin.bind(
webWorkerExecutionEnvironment,
),
terminateAll: workerController.terminateAll.bind(workerController),
terminateWorkerOf: workerController.terminateWorkerOf.bind(
workerController,
getRpcMessageHandler: webWorkerExecutionEnvironment.getRpcMessageHandler.bind(
webWorkerExecutionEnvironment,
),
startPlugin: workerController.startPlugin.bind(workerController),
removeAllPermissionsFor: jest.fn(),
getPermissions: jest.fn(),
hasPermission: jest.fn(),
Expand Down Expand Up @@ -87,7 +98,89 @@ describe('PluginController Controller', () => {
},
});
await pluginController.startPlugin(plugin.name);
const handle = pluginController.getRpcMessageHandler(plugin.name);
const handle = await 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');
});

it('can add a plugin and use its JSON-RPC api with a stub execution env service', async () => {
class ExecutionEnvironmentStub implements ExecutionEnvironmentService {
async terminateAllPlugins() {
// empty stub
}

async getRpcMessageHandler() {
return (_: any, request: Record<string, unknown>) => {
return new Promise((resolve) => {
const results = `${request.method}${request.id}`;
resolve(results);
});
};
}

async executePlugin() {
return 'some-unique-id';
}

async terminatePlugin() {
// empty stub
}
}

const executionEnvironmentStub = new ExecutionEnvironmentStub();

const pluginController = new PluginController({
terminateAllPlugins: executionEnvironmentStub.terminateAllPlugins.bind(
executionEnvironmentStub,
),
terminatePlugin: executionEnvironmentStub.terminatePlugin.bind(
executionEnvironmentStub,
),
executePlugin: executionEnvironmentStub.executePlugin.bind(
executionEnvironmentStub,
),
getRpcMessageHandler: executionEnvironmentStub.getRpcMessageHandler.bind(
executionEnvironmentStub,
),
removeAllPermissionsFor: jest.fn(),
getPermissions: jest.fn(),
hasPermission: jest.fn(),
requestPermissions: jest.fn(),
closeAllConnections: jest.fn(),
messenger: new ControllerMessenger<any, any>().getRestricted({
name: 'PluginController',
}),
state: {
inlinePluginIsRunning: false,
pluginStates: {},
plugins: {},
},
});
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 = await pluginController.getRpcMessageHandler(plugin.name);
if (!handle) {
throw Error('rpc handler not found');
}
Expand Down
Loading

0 comments on commit e4bcb7f

Please sign in to comment.