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

Commit 852e6ee

Browse files
committed
Initial notification handling implementation
1 parent 0ed4637 commit 852e6ee

File tree

2 files changed

+184
-49
lines changed

2 files changed

+184
-49
lines changed

src/JsonRpcEngine.ts

Lines changed: 130 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import SafeEventEmitter from '@metamask/safe-event-emitter';
22
import { errorCodes, EthereumRpcError, serializeError } from 'eth-rpc-errors';
3+
import { isJsonRpcRequest } from './utils';
34

45
type Maybe<T> = Partial<T> | null | undefined;
56

@@ -32,38 +33,37 @@ export interface JsonRpcError {
3233
stack?: string;
3334
}
3435

35-
export interface JsonRpcRequest<T> {
36+
interface JsonRpcRequestBase<T> {
3637
jsonrpc: JsonRpcVersion;
3738
method: string;
38-
id: JsonRpcId;
3939
params?: T;
4040
}
4141

42-
export interface JsonRpcNotification<T> {
43-
jsonrpc: JsonRpcVersion;
44-
method: string;
45-
params?: T;
46-
}
42+
export type JsonRpcRequest<T> = JsonRpcRequestBase<T> & {
43+
id: JsonRpcId;
44+
};
45+
46+
export type JsonRpcNotification<T> = JsonRpcRequestBase<T>;
4747

4848
interface JsonRpcResponseBase {
4949
jsonrpc: JsonRpcVersion;
5050
id: JsonRpcId;
5151
}
5252

53-
export interface JsonRpcSuccess<T> extends JsonRpcResponseBase {
53+
export type JsonRpcSuccess<T> = JsonRpcResponseBase & {
5454
result: Maybe<T>;
55-
}
55+
};
5656

57-
export interface JsonRpcFailure extends JsonRpcResponseBase {
57+
export type JsonRpcFailure = JsonRpcResponseBase & {
5858
error: JsonRpcError;
59-
}
59+
};
6060

6161
export type JsonRpcResponse<T> = JsonRpcSuccess<T> | JsonRpcFailure;
6262

63-
export interface PendingJsonRpcResponse<T> extends JsonRpcResponseBase {
63+
export type PendingJsonRpcResponse<T> = JsonRpcResponseBase & {
6464
result?: T;
6565
error?: Error | JsonRpcError;
66-
}
66+
};
6767

6868
export type JsonRpcEngineCallbackError = Error | JsonRpcError | null;
6969

@@ -86,27 +86,56 @@ export type JsonRpcMiddleware<T, U> = (
8686
end: JsonRpcEngineEndCallback,
8787
) => void;
8888

89+
export type JsonRpcNotificationMiddleware<T> = (
90+
req: JsonRpcNotification<T>,
91+
) => Promise<void>;
92+
8993
/**
9094
* A JSON-RPC request and response processor.
9195
* Give it a stack of middleware, pass it requests, and get back responses.
9296
*/
9397
export class JsonRpcEngine extends SafeEventEmitter {
9498
private _middleware: JsonRpcMiddleware<unknown, unknown>[];
9599

100+
private _notificationMiddleware: JsonRpcNotificationMiddleware<unknown>[];
101+
96102
constructor() {
97103
super();
98104
this._middleware = [];
105+
this._notificationMiddleware = [];
99106
}
100107

101108
/**
102-
* Add a middleware function to the engine's middleware stack.
109+
* Add a middleware function to the engine's request middleware stack.
103110
*
104111
* @param middleware - The middleware function to add.
105112
*/
106-
push<T, U>(middleware: JsonRpcMiddleware<T, U>): void {
113+
pushRequestMiddleware<T, U>(middleware: JsonRpcMiddleware<T, U>): void {
107114
this._middleware.push(middleware as JsonRpcMiddleware<unknown, unknown>);
108115
}
109116

117+
/**
118+
* Alias for `pushRequestMiddleware`, for backwards compatibility.
119+
*
120+
* @param middleware - The request middleware function to add.
121+
*/
122+
push<T, U>(middleware: JsonRpcMiddleware<T, U>): void {
123+
this.pushRequestMiddleware(
124+
middleware as JsonRpcMiddleware<unknown, unknown>,
125+
);
126+
}
127+
128+
/**
129+
* Add a middleware function to the engine's notification middleware stack.
130+
*
131+
* @param middleware - The notification middleware function to add.
132+
*/
133+
pushNotificationMiddleware<T>(
134+
middleware: JsonRpcNotificationMiddleware<T>,
135+
): void {
136+
this._middleware.push(middleware as JsonRpcNotificationMiddleware<unknown>);
137+
}
138+
110139
/**
111140
* Handle a JSON-RPC request, and return a response.
112141
*
@@ -119,14 +148,26 @@ export class JsonRpcEngine extends SafeEventEmitter {
119148
): void;
120149

121150
/**
122-
* Handle an array of JSON-RPC requests, and return an array of responses.
151+
* Handle a JSON-RPC notification.
152+
*
153+
* @param notification - The notification to handle.
154+
* @param callback - An error-first callback that will receive a `void` response.
155+
*/
156+
handle<T>(
157+
request: JsonRpcNotification<T>,
158+
callback: (error: unknown, response: void) => void,
159+
): void;
160+
161+
/**
162+
* Handle an array of JSON-RPC requests and/or notifications, and return an
163+
* array of responses to any included requests.
123164
*
124165
* @param request - The requests to handle.
125166
* @param callback - An error-first callback that will receive the array of
126167
* responses.
127168
*/
128169
handle<T, U>(
129-
requests: JsonRpcRequest<T>[],
170+
requests: (JsonRpcRequest<T> | JsonRpcNotification<T>)[],
130171
callback: (error: unknown, responses: JsonRpcResponse<U>[]) => void,
131172
): void;
132173

@@ -139,12 +180,22 @@ export class JsonRpcEngine extends SafeEventEmitter {
139180
handle<T, U>(request: JsonRpcRequest<T>): Promise<JsonRpcResponse<U>>;
140181

141182
/**
142-
* Handle an array of JSON-RPC requests, and return an array of responses.
183+
* Handle a JSON-RPC notification.
184+
*
185+
* @param notification - The notification to handle.
186+
*/
187+
handle<T>(notification: JsonRpcNotification<T>): Promise<void>;
188+
189+
/**
190+
* Handle an array of JSON-RPC requests and/or notifications, and return an
191+
* array of responses to any included requests.
143192
*
144193
* @param request - The JSON-RPC requests to handle.
145194
* @returns An array of JSON-RPC responses.
146195
*/
147-
handle<T, U>(requests: JsonRpcRequest<T>[]): Promise<JsonRpcResponse<U>[]>;
196+
handle<T, U>(
197+
requests: (JsonRpcRequest<T> | JsonRpcNotification<T>)[],
198+
): Promise<JsonRpcResponse<U>[]>;
148199

149200
handle(req: unknown, callback?: any) {
150201
if (callback && typeof callback !== 'function') {
@@ -199,14 +250,14 @@ export class JsonRpcEngine extends SafeEventEmitter {
199250
* Like _handle, but for batch requests.
200251
*/
201252
private _handleBatch(
202-
reqs: JsonRpcRequest<unknown>[],
253+
reqs: (JsonRpcRequest<unknown> | JsonRpcNotification<unknown>)[],
203254
): Promise<JsonRpcResponse<unknown>[]>;
204255

205256
/**
206257
* Like _handle, but for batch requests.
207258
*/
208259
private _handleBatch(
209-
reqs: JsonRpcRequest<unknown>[],
260+
reqs: (JsonRpcRequest<unknown> | JsonRpcNotification<unknown>)[],
210261
callback: (error: unknown, responses?: JsonRpcResponse<unknown>[]) => void,
211262
): Promise<void>;
212263

@@ -219,23 +270,23 @@ export class JsonRpcEngine extends SafeEventEmitter {
219270
* @returns The array of responses, or nothing if a callback was specified.
220271
*/
221272
private async _handleBatch(
222-
reqs: JsonRpcRequest<unknown>[],
273+
reqs: (JsonRpcRequest<unknown> | JsonRpcNotification<unknown>)[],
223274
callback?: (error: unknown, responses?: JsonRpcResponse<unknown>[]) => void,
224275
): Promise<JsonRpcResponse<unknown>[] | void> {
225276
// The order here is important
226277
try {
227278
// 2. Wait for all requests to finish, or throw on some kind of fatal
228279
// error
229-
const responses = await Promise.all(
230-
// 1. Begin executing each request in the order received
231-
reqs.map(this._promiseHandle.bind(this)),
232-
);
280+
const responses = (
281+
await Promise.all(
282+
// 1. Begin executing each request in the order received
283+
reqs.map(this._promiseHandle.bind(this)),
284+
// Filter out falsy responses from notifications
285+
)
286+
).filter((response) => Boolean(response)) as JsonRpcResponse<unknown>[];
233287

234288
// 3. Return batch response
235-
if (callback) {
236-
return callback(null, responses);
237-
}
238-
return responses;
289+
return callback ? callback(null, responses) : responses;
239290
} catch (error) {
240291
if (callback) {
241292
return callback(error);
@@ -252,8 +303,8 @@ export class JsonRpcEngine extends SafeEventEmitter {
252303
* @returns The JSON-RPC response.
253304
*/
254305
private _promiseHandle(
255-
req: JsonRpcRequest<unknown>,
256-
): Promise<JsonRpcResponse<unknown>> {
306+
req: JsonRpcRequest<unknown> | JsonRpcNotification<unknown>,
307+
): Promise<JsonRpcResponse<unknown> | void> {
257308
return new Promise((resolve) => {
258309
this._handle(req, (_err, res) => {
259310
// There will always be a response, and it will always have any error
@@ -264,8 +315,8 @@ export class JsonRpcEngine extends SafeEventEmitter {
264315
}
265316

266317
/**
267-
* Ensures that the request object is valid, processes it, and passes any
268-
* error and the response object to the given callback.
318+
* Ensures that the request / notification object is valid, processes it, and
319+
* passes any error and response object to the given callback.
269320
*
270321
* Does not reject.
271322
*
@@ -274,8 +325,8 @@ export class JsonRpcEngine extends SafeEventEmitter {
274325
* @returns Nothing.
275326
*/
276327
private async _handle(
277-
callerReq: JsonRpcRequest<unknown>,
278-
callback: (error: unknown, response: JsonRpcResponse<unknown>) => void,
328+
callerReq: JsonRpcRequest<unknown> | JsonRpcNotification<unknown>,
329+
callback: (error: unknown, response?: JsonRpcResponse<unknown>) => void,
279330
): Promise<void> {
280331
if (
281332
!callerReq ||
@@ -296,18 +347,39 @@ export class JsonRpcEngine extends SafeEventEmitter {
296347
`Must specify a string method. Received: ${typeof callerReq.method}`,
297348
{ request: callerReq },
298349
);
299-
return callback(error, { id: callerReq.id, jsonrpc: '2.0', error });
350+
return callback(error, {
351+
id: (callerReq as any).id ?? null,
352+
jsonrpc: '2.0',
353+
error,
354+
});
355+
}
356+
357+
let error: JsonRpcEngineCallbackError = null;
358+
359+
// Handle notifications.
360+
// We can't use isJsonRpcNotification here because that narrows callerReq to
361+
// "never" after the if clause.
362+
if (!isJsonRpcRequest(callerReq)) {
363+
try {
364+
await JsonRpcEngine._processNotification(
365+
{ ...callerReq },
366+
this._notificationMiddleware,
367+
);
368+
} catch (_error) {
369+
error = _error;
370+
}
371+
return callback(error);
300372
}
301373

374+
// Handle requests.
302375
const req: JsonRpcRequest<unknown> = { ...callerReq };
303376
const res: PendingJsonRpcResponse<unknown> = {
304377
id: req.id,
305378
jsonrpc: req.jsonrpc,
306379
};
307-
let error: JsonRpcEngineCallbackError = null;
308380

309381
try {
310-
await this._processRequest(req, res);
382+
await JsonRpcEngine._processRequest(req, res, this._middleware);
311383
} catch (_error) {
312384
// A request handler error, a re-thrown middleware error, or something
313385
// unexpected.
@@ -325,20 +397,37 @@ export class JsonRpcEngine extends SafeEventEmitter {
325397
return callback(error, res as JsonRpcResponse<unknown>);
326398
}
327399

400+
/**
401+
* Runs all notification middleware on the specified notification.
402+
*
403+
* @param notification - The notification object to process.
404+
* @param middlewareStack - The stack of notification middleware functions.
405+
*/
406+
private static async _processNotification(
407+
notification: JsonRpcNotification<unknown>,
408+
middlewareStack: JsonRpcNotificationMiddleware<unknown>[],
409+
): Promise<void> {
410+
for (const middleware of middlewareStack) {
411+
await middleware(notification);
412+
}
413+
}
414+
328415
/**
329416
* For the given request and response, runs all middleware and their return
330417
* handlers, if any, and ensures that internal request processing semantics
331418
* are satisfied.
332419
*
333420
* @param req - The request object.
334421
* @param res - The response object.
422+
* @param middlewareStack - The stack of request middleware functions.
335423
*/
336-
private async _processRequest(
424+
private static async _processRequest(
337425
req: JsonRpcRequest<unknown>,
338426
res: PendingJsonRpcResponse<unknown>,
427+
middlewareStack: JsonRpcMiddleware<unknown, unknown>[],
339428
): Promise<void> {
340429
const [error, isComplete, returnHandlers] =
341-
await JsonRpcEngine._runAllMiddleware(req, res, this._middleware);
430+
await JsonRpcEngine._runAllMiddleware(req, res, middlewareStack);
342431

343432
// Throw if "end" was not called, or if the response has neither a result
344433
// nor an error.

0 commit comments

Comments
 (0)