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

Commit fdd6da9

Browse files
committed
Initial notification handling implementation
1 parent 61e159f commit fdd6da9

File tree

2 files changed

+85
-29
lines changed

2 files changed

+85
-29
lines changed

src/engine.test.ts renamed to src/JsonRpcEngine.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,13 @@ describe('JsonRpcEngine', () => {
3030

3131
it('handle: returns error for invalid request method', async () => {
3232
const engine = new JsonRpcEngine();
33-
let response: any = await engine.handle({ method: null } as any);
33+
let response: any = await engine.handle({ id: 1, method: null } as any);
3434
expect(response.error.code).toStrictEqual(-32600);
3535
expect(response.result).toBeUndefined();
3636

37+
// No response if duck-typed as a notification
3738
response = await engine.handle({ method: true } as any);
38-
expect(response.error.code).toStrictEqual(-32600);
39-
expect(response.result).toBeUndefined();
39+
expect(response).toBeUndefined();
4040
});
4141

4242
it('handle: basic middleware test 1', async () => {

src/JsonRpcEngine.ts

Lines changed: 82 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import {
44
JsonRpcError,
55
JsonRpcRequest,
66
JsonRpcResponse,
7+
JsonRpcNotification,
8+
isJsonRpcRequest,
9+
isNullOrUndefined,
710
} from '@metamask/utils';
811
import { errorCodes, EthereumRpcError, serializeError } from 'eth-rpc-errors';
912

@@ -36,16 +39,23 @@ export type JsonRpcMiddleware<Params, Result> = (
3639
end: JsonRpcEngineEndCallback,
3740
) => void;
3841

42+
export type JsonRpcNotificationHandler<Params> = (
43+
notification: JsonRpcNotification<Params>,
44+
) => void | Promise<void>;
45+
3946
/**
4047
* A JSON-RPC request and response processor.
4148
* Give it a stack of middleware, pass it requests, and get back responses.
4249
*/
4350
export class JsonRpcEngine extends SafeEventEmitter {
4451
private _middleware: JsonRpcMiddleware<unknown, unknown>[];
4552

46-
constructor() {
53+
private readonly _notificationHandler?: JsonRpcNotificationHandler<unknown>;
54+
55+
constructor(notificationHandler?: JsonRpcNotificationHandler<unknown>) {
4756
super();
4857
this._middleware = [];
58+
this._notificationHandler = notificationHandler;
4959
}
5060

5161
/**
@@ -69,14 +79,26 @@ export class JsonRpcEngine extends SafeEventEmitter {
6979
): void;
7080

7181
/**
72-
* Handle an array of JSON-RPC requests, and return an array of responses.
82+
* Handle a JSON-RPC notification.
83+
*
84+
* @param notification - The notification to handle.
85+
* @param callback - An error-first callback that will receive a `void` response.
86+
*/
87+
handle<Params>(
88+
notification: JsonRpcNotification<Params>,
89+
callback: (error: unknown, response: void) => void,
90+
): void;
91+
92+
/**
93+
* Handle an array of JSON-RPC requests and/or notifications, and return an
94+
* array of responses to any included requests.
7395
*
7496
* @param request - The requests to handle.
7597
* @param callback - An error-first callback that will receive the array of
7698
* responses.
7799
*/
78100
handle<Params, Result>(
79-
requests: JsonRpcRequest<Params>[],
101+
requests: (JsonRpcRequest<Params> | JsonRpcNotification<Params>)[],
80102
callback: (error: unknown, responses: JsonRpcResponse<Result>[]) => void,
81103
): void;
82104

@@ -91,13 +113,21 @@ export class JsonRpcEngine extends SafeEventEmitter {
91113
): Promise<JsonRpcResponse<Result>>;
92114

93115
/**
94-
* Handle an array of JSON-RPC requests, and return an array of responses.
116+
* Handle a JSON-RPC notification.
117+
*
118+
* @param notification - The notification to handle.
119+
*/
120+
handle<Params>(notification: JsonRpcNotification<Params>): Promise<void>;
121+
122+
/**
123+
* Handle an array of JSON-RPC requests and/or notifications, and return an
124+
* array of responses to any included requests.
95125
*
96126
* @param request - The JSON-RPC requests to handle.
97127
* @returns An array of JSON-RPC responses.
98128
*/
99129
handle<Params, Result>(
100-
requests: JsonRpcRequest<Params>[],
130+
requests: (JsonRpcRequest<Params> | JsonRpcNotification<Params>)[],
101131
): Promise<JsonRpcResponse<Result>[]>;
102132

103133
handle(req: unknown, callback?: any) {
@@ -153,14 +183,14 @@ export class JsonRpcEngine extends SafeEventEmitter {
153183
* Like _handle, but for batch requests.
154184
*/
155185
private _handleBatch(
156-
reqs: JsonRpcRequest<unknown>[],
186+
reqs: (JsonRpcRequest<unknown> | JsonRpcNotification<unknown>)[],
157187
): Promise<JsonRpcResponse<unknown>[]>;
158188

159189
/**
160190
* Like _handle, but for batch requests.
161191
*/
162192
private _handleBatch(
163-
reqs: JsonRpcRequest<unknown>[],
193+
reqs: (JsonRpcRequest<unknown> | JsonRpcNotification<unknown>)[],
164194
callback: (error: unknown, responses?: JsonRpcResponse<unknown>[]) => void,
165195
): Promise<void>;
166196

@@ -173,17 +203,22 @@ export class JsonRpcEngine extends SafeEventEmitter {
173203
* @returns The array of responses, or nothing if a callback was specified.
174204
*/
175205
private async _handleBatch(
176-
reqs: JsonRpcRequest<unknown>[],
206+
reqs: (JsonRpcRequest<unknown> | JsonRpcNotification<unknown>)[],
177207
callback?: (error: unknown, responses?: JsonRpcResponse<unknown>[]) => void,
178208
): Promise<JsonRpcResponse<unknown>[] | void> {
179209
// The order here is important
180210
try {
181211
// 2. Wait for all requests to finish, or throw on some kind of fatal
182212
// error
183-
const responses = await Promise.all(
184-
// 1. Begin executing each request in the order received
185-
reqs.map(this._promiseHandle.bind(this)),
186-
);
213+
const responses = (
214+
await Promise.all(
215+
// 1. Begin executing each request in the order received
216+
reqs.map(this._promiseHandle.bind(this)),
217+
// Filter out falsy responses from notifications
218+
)
219+
).filter(
220+
(response) => !isNullOrUndefined(response),
221+
) as JsonRpcResponse<unknown>[];
187222

188223
// 3. Return batch response
189224
if (callback) {
@@ -206,8 +241,8 @@ export class JsonRpcEngine extends SafeEventEmitter {
206241
* @returns The JSON-RPC response.
207242
*/
208243
private _promiseHandle(
209-
req: JsonRpcRequest<unknown>,
210-
): Promise<JsonRpcResponse<unknown>> {
244+
req: JsonRpcRequest<unknown> | JsonRpcNotification<unknown>,
245+
): Promise<JsonRpcResponse<unknown> | void> {
211246
return new Promise((resolve) => {
212247
this._handle(req, (_err, res) => {
213248
// There will always be a response, and it will always have any error
@@ -218,8 +253,8 @@ export class JsonRpcEngine extends SafeEventEmitter {
218253
}
219254

220255
/**
221-
* Ensures that the request object is valid, processes it, and passes any
222-
* error and the response object to the given callback.
256+
* Ensures that the request / notification object is valid, processes it, and
257+
* passes any error and response object to the given callback.
223258
*
224259
* Does not reject.
225260
*
@@ -228,8 +263,8 @@ export class JsonRpcEngine extends SafeEventEmitter {
228263
* @returns Nothing.
229264
*/
230265
private async _handle(
231-
callerReq: JsonRpcRequest<unknown>,
232-
callback: (error: unknown, response: JsonRpcResponse<unknown>) => void,
266+
callerReq: JsonRpcRequest<unknown> | JsonRpcNotification<unknown>,
267+
callback: (error?: unknown, response?: JsonRpcResponse<unknown>) => void,
233268
): Promise<void> {
234269
if (
235270
!callerReq ||
@@ -250,18 +285,37 @@ export class JsonRpcEngine extends SafeEventEmitter {
250285
`Must specify a string method. Received: ${typeof callerReq.method}`,
251286
{ request: callerReq },
252287
);
253-
return callback(error, { id: callerReq.id, jsonrpc: '2.0', error });
288+
289+
if (isJsonRpcRequest(callerReq)) {
290+
return callback(error, {
291+
id: callerReq.id ?? null,
292+
jsonrpc: '2.0',
293+
error,
294+
});
295+
}
296+
// Do not reply to notifications, even malformed ones.
297+
return callback(null);
254298
}
255299

300+
// Handle notifications.
301+
// We can't use isJsonRpcNotification here because that narrows callerReq to
302+
// "never" after the if clause for unknown reasons.
303+
if (!isJsonRpcRequest(callerReq)) {
304+
await this._notificationHandler?.(callerReq);
305+
return callback(null);
306+
}
307+
308+
let error: JsonRpcEngineCallbackError = null;
309+
310+
// Handle requests.
256311
const req: JsonRpcRequest<unknown> = { ...callerReq };
257312
const res: PendingJsonRpcResponse<unknown> = {
258313
id: req.id,
259314
jsonrpc: req.jsonrpc,
260315
};
261-
let error: JsonRpcEngineCallbackError = null;
262316

263317
try {
264-
await this._processRequest(req, res);
318+
await JsonRpcEngine._processRequest(req, res, this._middleware);
265319
} catch (_error) {
266320
// A request handler error, a re-thrown middleware error, or something
267321
// unexpected.
@@ -286,13 +340,15 @@ export class JsonRpcEngine extends SafeEventEmitter {
286340
*
287341
* @param req - The request object.
288342
* @param res - The response object.
343+
* @param middlewares - The stack of middleware functions.
289344
*/
290-
private async _processRequest(
345+
private static async _processRequest(
291346
req: JsonRpcRequest<unknown>,
292347
res: PendingJsonRpcResponse<unknown>,
348+
middlewares: JsonRpcMiddleware<unknown, unknown>[],
293349
): Promise<void> {
294350
const [error, isComplete, returnHandlers] =
295-
await JsonRpcEngine._runAllMiddleware(req, res, this._middleware);
351+
await JsonRpcEngine._runAllMiddleware(req, res, middlewares);
296352

297353
// Throw if "end" was not called, or if the response has neither a result
298354
// nor an error.
@@ -314,15 +370,15 @@ export class JsonRpcEngine extends SafeEventEmitter {
314370
*
315371
* @param req - The request object.
316372
* @param res - The response object.
317-
* @param middlewareStack - The stack of middleware functions to execute.
373+
* @param middlewares - The stack of middleware functions to execute.
318374
* @returns An array of any error encountered during middleware execution,
319375
* a boolean indicating whether the request was completed, and an array of
320376
* middleware-defined return handlers.
321377
*/
322378
private static async _runAllMiddleware(
323379
req: JsonRpcRequest<unknown>,
324380
res: PendingJsonRpcResponse<unknown>,
325-
middlewareStack: JsonRpcMiddleware<unknown, unknown>[],
381+
middlewares: JsonRpcMiddleware<unknown, unknown>[],
326382
): Promise<
327383
[
328384
unknown, // error
@@ -335,7 +391,7 @@ export class JsonRpcEngine extends SafeEventEmitter {
335391
let isComplete = false;
336392

337393
// Go down stack of middleware, call and collect optional returnHandlers
338-
for (const middleware of middlewareStack) {
394+
for (const middleware of middlewares) {
339395
[error, isComplete] = await JsonRpcEngine._runMiddleware(
340396
req,
341397
res,

0 commit comments

Comments
 (0)