44 JsonRpcError ,
55 JsonRpcRequest ,
66 JsonRpcResponse ,
7+ JsonRpcNotification ,
8+ isJsonRpcRequest ,
9+ isNullOrUndefined ,
710} from '@metamask/utils' ;
811import { 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 */
4350export 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