Skip to content
This repository was archived by the owner on Nov 9, 2023. It is now read-only.

Commit 0569f2d

Browse files
committed
Fix notification handler error logic
1 parent 0032493 commit 0569f2d

File tree

2 files changed

+57
-9
lines changed

2 files changed

+57
-9
lines changed

src/JsonRpcEngine.test.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,42 @@ describe('JsonRpcEngine', () => {
9191
expect(middleware).not.toHaveBeenCalled();
9292
});
9393

94+
it('handle: re-throws errors from notification handlers (async)', async () => {
95+
const notificationHandler = jest.fn().mockImplementation(() => {
96+
throw new Error('baz');
97+
});
98+
const engine = new JsonRpcEngine({ notificationHandler });
99+
100+
await expect(engine.handle({ jsonrpc, method: 'foo' })).rejects.toThrow(
101+
new Error('baz'),
102+
);
103+
expect(notificationHandler).toHaveBeenCalledTimes(1);
104+
expect(notificationHandler).toHaveBeenCalledWith({
105+
jsonrpc,
106+
method: 'foo',
107+
});
108+
});
109+
110+
it('handle: re-throws errors from notification handlers (callback)', async () => {
111+
const notificationHandler = jest.fn().mockImplementation(() => {
112+
throw new Error('baz');
113+
});
114+
const engine = new JsonRpcEngine({ notificationHandler });
115+
116+
await new Promise<void>((resolve) => {
117+
engine.handle({ jsonrpc, method: 'foo' }, (error, response) => {
118+
expect(response).toBeUndefined();
119+
expect(error).toStrictEqual(new Error('baz'));
120+
expect(notificationHandler).toHaveBeenCalledTimes(1);
121+
expect(notificationHandler).toHaveBeenCalledWith({
122+
jsonrpc,
123+
method: 'foo',
124+
});
125+
resolve();
126+
});
127+
});
128+
});
129+
94130
it('handle: basic middleware test 1', async () => {
95131
const engine = new JsonRpcEngine();
96132

src/JsonRpcEngine.ts

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import {
66
JsonRpcResponse,
77
JsonRpcNotification,
88
isJsonRpcRequest,
9-
isNullOrUndefined,
109
} from '@metamask/utils';
1110
import { errorCodes, EthereumRpcError, serializeError } from 'eth-rpc-errors';
1211

@@ -49,7 +48,8 @@ interface JsonRpcEngineArgs {
4948
* defined as a JSON-RPC request without an `id` property. If this option is
5049
* _not_ provided, notifications will be treated the same as requests. If this
5150
* option _is_ provided, notifications will be passed to the handler
52-
* function without touching the engine's middleware stack.
51+
* function without touching the engine's middleware stack. This function
52+
* should not** throw or reject.
5353
*/
5454
notificationHandler?: JsonRpcNotificationHandler<unknown>;
5555
}
@@ -72,7 +72,8 @@ export class JsonRpcEngine extends SafeEventEmitter {
7272
* without an `id` property. If this option is _not_ provided, notifications
7373
* will be treated the same as requests. If this option _is_ provided,
7474
* notifications will be passed to the handler function without touching
75-
* the engine's middleware stack.
75+
* the engine's middleware stack. This function **should not** throw or
76+
* reject.
7677
*/
7778
constructor({ notificationHandler }: JsonRpcEngineArgs = {}) {
7879
super();
@@ -239,7 +240,8 @@ export class JsonRpcEngine extends SafeEventEmitter {
239240
// Filter out falsy responses from notifications
240241
)
241242
).filter(
242-
(response) => !isNullOrUndefined(response),
243+
// Filter out any notification responses.
244+
(response) => response !== undefined,
243245
) as JsonRpcResponse<unknown>[];
244246

245247
// 3. Return batch response
@@ -265,10 +267,16 @@ export class JsonRpcEngine extends SafeEventEmitter {
265267
private _promiseHandle(
266268
req: JsonRpcRequest<unknown> | JsonRpcNotification<unknown>,
267269
): Promise<JsonRpcResponse<unknown> | void> {
268-
return new Promise((resolve) => {
269-
this._handle(req, (_err, res) => {
270-
// There will always be a response, and it will always have any error
271-
// that is caught and propagated.
270+
return new Promise((resolve, reject) => {
271+
this._handle(req, (error, res) => {
272+
// For notifications, the response will be `undefined`, and any caught
273+
// errors are unexpected and should be surfaced to the caller.
274+
if (error && res === undefined) {
275+
reject(error);
276+
}
277+
278+
// Excepting notifications, there will always be a response, and it will
279+
// always have any error that is caught and propagated.
272280
resolve(res);
273281
});
274282
});
@@ -324,7 +332,11 @@ export class JsonRpcEngine extends SafeEventEmitter {
324332
// We can't use isJsonRpcNotification here because that narrows callerReq to
325333
// "never" after the if clause for unknown reasons.
326334
if (this._notificationHandler && !isJsonRpcRequest(callerReq)) {
327-
await this._notificationHandler(callerReq);
335+
try {
336+
await this._notificationHandler(callerReq);
337+
} catch (error) {
338+
return callback(error);
339+
}
328340
return callback(null);
329341
}
330342

0 commit comments

Comments
 (0)