From 9dc1438f668abd47d33cc98c6c949d9d79769d91 Mon Sep 17 00:00:00 2001 From: shanejonas Date: Fri, 24 Sep 2021 12:23:31 -0700 Subject: [PATCH 1/3] Added out of band error support --- .../WebWorkerExecutionEnvironmentService.ts | 26 ++++++++++++++++++- .../package.json | 1 + .../IframeExecutionEnvironmentService.test.ts | 4 ++- .../src/IframeExecutionEnvironmentService.ts | 22 ++++++++++++++++ yarn.lock | 7 +++++ 5 files changed, 58 insertions(+), 2 deletions(-) diff --git a/packages/controllers/src/services/WebWorkerExecutionEnvironmentService.ts b/packages/controllers/src/services/WebWorkerExecutionEnvironmentService.ts index 4e2a65599f..5af3e27f64 100644 --- a/packages/controllers/src/services/WebWorkerExecutionEnvironmentService.ts +++ b/packages/controllers/src/services/WebWorkerExecutionEnvironmentService.ts @@ -12,13 +12,16 @@ import { JsonRpcRequest, PendingJsonRpcResponse, } from 'json-rpc-engine'; +import { EthereumRpcError } from 'eth-rpc-errors'; import { ExecutionEnvironmentService } from './ExecutionEnvironmentService'; export type SetupPluginProvider = (pluginName: string, stream: Duplex) => void; +type OnError = (pluginName: string, error: EthereumRpcError) => void; interface WorkerControllerArgs { setupPluginProvider: SetupPluginProvider; workerUrl: URL; + onError?: OnError; } interface WorkerStreams { @@ -57,7 +60,13 @@ export class WebWorkerExecutionEnvironmentService private workerToPluginMap: Map; - constructor({ setupPluginProvider, workerUrl }: WorkerControllerArgs) { + private _onError?: OnError; + + constructor({ + setupPluginProvider, + workerUrl, + onError, + }: WorkerControllerArgs) { this.workerUrl = workerUrl; this.setupPluginProvider = setupPluginProvider; this.store = new ObservableStore({ workers: {} }); @@ -65,6 +74,7 @@ export class WebWorkerExecutionEnvironmentService this.pluginToWorkerMap = new Map(); this.workerToPluginMap = new Map(); this._pluginRpcHooks = new Map(); + this._onError = onError; } private _setWorker(workerId: string, workerWrapper: WorkerWrapper): void { @@ -233,6 +243,20 @@ export class WebWorkerExecutionEnvironmentService const worker = new Worker(this.workerUrl, { name: workerId, }); + const handler = (ev: ErrorEvent) => { + if (this._onError) { + const err = new EthereumRpcError( + ev.error.code, + ev.error.message, + ev.error.data, + ); + const pluginName = this.workerToPluginMap.get(workerId); + if (pluginName) { + this._onError(pluginName, err); + } + } + }; + worker.addEventListener('error', handler, { once: true }); const streams = this._initWorkerStreams(worker, workerId); const rpcEngine = new JsonRpcEngine(); diff --git a/packages/iframe-execution-environment-service/package.json b/packages/iframe-execution-environment-service/package.json index c9cba786cc..f546382c6b 100644 --- a/packages/iframe-execution-environment-service/package.json +++ b/packages/iframe-execution-environment-service/package.json @@ -31,6 +31,7 @@ "@metamask/snap-controllers": "^0.1.0", "@metamask/snap-types": "^0.1.0", "@metamask/snap-workers": "^0.1.0", + "eth-rpc-errors": "^4.0.3", "json-rpc-engine": "^6.1.0", "json-rpc-middleware-stream": "^3.0.0", "nanoid": "^3.1.23", diff --git a/packages/iframe-execution-environment-service/src/IframeExecutionEnvironmentService.test.ts b/packages/iframe-execution-environment-service/src/IframeExecutionEnvironmentService.test.ts index fba7bd8b6f..f623eac12a 100644 --- a/packages/iframe-execution-environment-service/src/IframeExecutionEnvironmentService.test.ts +++ b/packages/iframe-execution-environment-service/src/IframeExecutionEnvironmentService.test.ts @@ -13,8 +13,8 @@ describe('Iframe Controller', () => { 'https://metamask.github.io/iframe-execution-environment/', ), }); - iframeExecutionEnvironmentService.terminateAllPlugins(); expect(iframeExecutionEnvironmentService).toBeDefined(); + await iframeExecutionEnvironmentService.terminateAllPlugins(); }); it('can create a plugin worker and start the plugin', async () => { @@ -38,6 +38,7 @@ describe('Iframe Controller', () => { }); expect(response).toStrictEqual('OK'); removeListener(); + await iframeExecutionEnvironmentService.terminateAllPlugins(); }); it('can handle a crashed plugin', async () => { @@ -66,6 +67,7 @@ describe('Iframe Controller', () => { await expect(action()).rejects.toThrow( /Error while running plugin 'TestPlugin'/u, ); + await iframeExecutionEnvironmentService.terminateAllPlugins(); removeListener(); }); }); diff --git a/packages/iframe-execution-environment-service/src/IframeExecutionEnvironmentService.ts b/packages/iframe-execution-environment-service/src/IframeExecutionEnvironmentService.ts index 2123395a00..d9209467f4 100644 --- a/packages/iframe-execution-environment-service/src/IframeExecutionEnvironmentService.ts +++ b/packages/iframe-execution-environment-service/src/IframeExecutionEnvironmentService.ts @@ -12,13 +12,16 @@ import { PendingJsonRpcResponse, } from 'json-rpc-engine'; import { ExecutionEnvironmentService } from '@metamask/snap-controllers'; +import { EthereumRpcError } from 'eth-rpc-errors'; export type SetupPluginProvider = (pluginName: string, stream: Duplex) => void; +type OnError = (pluginName: string, error: EthereumRpcError) => void; interface IframeExecutionEnvironmentServiceArgs { createWindowTimeout?: number; setupPluginProvider: SetupPluginProvider; iframeUrl: URL; + onError?: OnError; } interface JobStreams { @@ -58,9 +61,12 @@ export class IframeExecutionEnvironmentService private _createWindowTimeout: number; + private _onError?: OnError; + constructor({ setupPluginProvider, iframeUrl, + onError, createWindowTimeout = 60000, }: IframeExecutionEnvironmentServiceArgs) { this._createWindowTimeout = createWindowTimeout; @@ -70,6 +76,7 @@ export class IframeExecutionEnvironmentService this.pluginToJobMap = new Map(); this.jobToPluginMap = new Map(); this._pluginRpcHooks = new Map(); + this._onError = onError; } private _setJob(jobId: string, jobWrapper: EnvMetadata): void { @@ -262,6 +269,21 @@ export class IframeExecutionEnvironmentService ); const commandStream = mux.createStream(PLUGIN_STREAM_NAMES.COMMAND); + const handler = (data: any) => { + if (data.error && this._onError) { + const err = new EthereumRpcError( + data.error.code, + data.error.message, + data.error.data, + ); + const pluginName = this.jobToPluginMap.get(jobId); + if (pluginName) { + this._onError(pluginName, err); + } + commandStream.off('data', handler); + } + }; + commandStream.on('data', handler); const rpcStream = mux.createStream(PLUGIN_STREAM_NAMES.JSON_RPC); // Typecast: stream type mismatch diff --git a/yarn.lock b/yarn.lock index 4fcbf4df92..0a489537c3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5820,6 +5820,13 @@ eth-rpc-errors@^4.0.0, eth-rpc-errors@^4.0.2: dependencies: fast-safe-stringify "^2.0.6" +eth-rpc-errors@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/eth-rpc-errors/-/eth-rpc-errors-4.0.3.tgz#6ddb6190a4bf360afda82790bb7d9d5e724f423a" + integrity sha512-Z3ymjopaoft7JDoxZcEb3pwdGh7yiYMhOwm2doUt6ASXlMavpNlK6Cre0+IMl2VSGyEU9rkiperQhp5iRxn5Pg== + dependencies: + fast-safe-stringify "^2.0.6" + eth-sig-util@^1.4.0, eth-sig-util@^1.4.2: version "1.4.2" resolved "https://registry.yarnpkg.com/eth-sig-util/-/eth-sig-util-1.4.2.tgz#8d958202c7edbaae839707fba6f09ff327606210" From 41bff4e2e3cde0ae33e5be02233932fb5055670e Mon Sep 17 00:00:00 2001 From: shanejonas Date: Fri, 24 Sep 2021 12:51:44 -0700 Subject: [PATCH 2/3] Changed handler to errorHandler --- .../src/services/WebWorkerExecutionEnvironmentService.ts | 5 +++-- .../src/IframeExecutionEnvironmentService.ts | 7 ++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/controllers/src/services/WebWorkerExecutionEnvironmentService.ts b/packages/controllers/src/services/WebWorkerExecutionEnvironmentService.ts index 5af3e27f64..0f51be7762 100644 --- a/packages/controllers/src/services/WebWorkerExecutionEnvironmentService.ts +++ b/packages/controllers/src/services/WebWorkerExecutionEnvironmentService.ts @@ -243,7 +243,8 @@ export class WebWorkerExecutionEnvironmentService const worker = new Worker(this.workerUrl, { name: workerId, }); - const handler = (ev: ErrorEvent) => { + // Handle out-of-band errors, i.e. errors thrown from the a plugin outside of the req/res cycle. + const errorHandler = (ev: ErrorEvent) => { if (this._onError) { const err = new EthereumRpcError( ev.error.code, @@ -256,7 +257,7 @@ export class WebWorkerExecutionEnvironmentService } } }; - worker.addEventListener('error', handler, { once: true }); + worker.addEventListener('error', errorHandler, { once: true }); const streams = this._initWorkerStreams(worker, workerId); const rpcEngine = new JsonRpcEngine(); diff --git a/packages/iframe-execution-environment-service/src/IframeExecutionEnvironmentService.ts b/packages/iframe-execution-environment-service/src/IframeExecutionEnvironmentService.ts index d9209467f4..e12ab623e8 100644 --- a/packages/iframe-execution-environment-service/src/IframeExecutionEnvironmentService.ts +++ b/packages/iframe-execution-environment-service/src/IframeExecutionEnvironmentService.ts @@ -269,7 +269,8 @@ export class IframeExecutionEnvironmentService ); const commandStream = mux.createStream(PLUGIN_STREAM_NAMES.COMMAND); - const handler = (data: any) => { + // Handle out-of-band errors, i.e. errors thrown from the a plugin outside of the req/res cycle. + const errorHandler = (data: any) => { if (data.error && this._onError) { const err = new EthereumRpcError( data.error.code, @@ -280,10 +281,10 @@ export class IframeExecutionEnvironmentService if (pluginName) { this._onError(pluginName, err); } - commandStream.off('data', handler); + commandStream.off('data', errorHandler); } }; - commandStream.on('data', handler); + commandStream.on('data', errorHandler); const rpcStream = mux.createStream(PLUGIN_STREAM_NAMES.JSON_RPC); // Typecast: stream type mismatch From c41ae97bbdfca7166812429c8cd495af803b0bf7 Mon Sep 17 00:00:00 2001 From: shanejonas Date: Fri, 24 Sep 2021 12:54:02 -0700 Subject: [PATCH 3/3] Fixed comment typo --- .../src/services/WebWorkerExecutionEnvironmentService.ts | 2 +- .../src/IframeExecutionEnvironmentService.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/controllers/src/services/WebWorkerExecutionEnvironmentService.ts b/packages/controllers/src/services/WebWorkerExecutionEnvironmentService.ts index 0f51be7762..6f15b31284 100644 --- a/packages/controllers/src/services/WebWorkerExecutionEnvironmentService.ts +++ b/packages/controllers/src/services/WebWorkerExecutionEnvironmentService.ts @@ -243,7 +243,7 @@ export class WebWorkerExecutionEnvironmentService const worker = new Worker(this.workerUrl, { name: workerId, }); - // Handle out-of-band errors, i.e. errors thrown from the a plugin outside of the req/res cycle. + // Handle out-of-band errors, i.e. errors thrown from the plugin outside of the req/res cycle. const errorHandler = (ev: ErrorEvent) => { if (this._onError) { const err = new EthereumRpcError( diff --git a/packages/iframe-execution-environment-service/src/IframeExecutionEnvironmentService.ts b/packages/iframe-execution-environment-service/src/IframeExecutionEnvironmentService.ts index e12ab623e8..73ded6e862 100644 --- a/packages/iframe-execution-environment-service/src/IframeExecutionEnvironmentService.ts +++ b/packages/iframe-execution-environment-service/src/IframeExecutionEnvironmentService.ts @@ -269,7 +269,7 @@ export class IframeExecutionEnvironmentService ); const commandStream = mux.createStream(PLUGIN_STREAM_NAMES.COMMAND); - // Handle out-of-band errors, i.e. errors thrown from the a plugin outside of the req/res cycle. + // Handle out-of-band errors, i.e. errors thrown from the plugin outside of the req/res cycle. const errorHandler = (data: any) => { if (data.error && this._onError) { const err = new EthereumRpcError(