From 107bdf3669d2c618f947bffbb5bfb2bc724b162e Mon Sep 17 00:00:00 2001 From: Erik Marks <25517051+rekmarks@users.noreply.github.com> Date: Mon, 27 Oct 2025 22:03:36 -0700 Subject: [PATCH 1/3] feat: Add v2 scaffold middleware --- .../src/v2/JsonRpcEngineV2.test.ts | 2 - .../src/v2/createScaffoldMiddleware.test.ts | 37 ++++++++++++++++ .../src/v2/createScaffoldMiddleware.ts | 44 +++++++++++++++++++ packages/json-rpc-engine/src/v2/index.test.ts | 1 + packages/json-rpc-engine/src/v2/index.ts | 1 + packages/json-rpc-engine/tests/utils.ts | 24 ++++++---- 6 files changed, 98 insertions(+), 11 deletions(-) create mode 100644 packages/json-rpc-engine/src/v2/createScaffoldMiddleware.test.ts create mode 100644 packages/json-rpc-engine/src/v2/createScaffoldMiddleware.ts diff --git a/packages/json-rpc-engine/src/v2/JsonRpcEngineV2.test.ts b/packages/json-rpc-engine/src/v2/JsonRpcEngineV2.test.ts index 45e30a6afb8..ad5cbaddc0c 100644 --- a/packages/json-rpc-engine/src/v2/JsonRpcEngineV2.test.ts +++ b/packages/json-rpc-engine/src/v2/JsonRpcEngineV2.test.ts @@ -770,8 +770,6 @@ describe('JsonRpcEngineV2', () => { middleware: [inflightMiddleware, resultMiddleware], }); - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - Jest blows up here, but there's no error at dev time. const requests: JsonRpcRequest[] = Array.from({ length: N }, (_, i) => makeRequest({ id: `${i}`, diff --git a/packages/json-rpc-engine/src/v2/createScaffoldMiddleware.test.ts b/packages/json-rpc-engine/src/v2/createScaffoldMiddleware.test.ts new file mode 100644 index 00000000000..9ccc8d02757 --- /dev/null +++ b/packages/json-rpc-engine/src/v2/createScaffoldMiddleware.test.ts @@ -0,0 +1,37 @@ +import { rpcErrors } from '@metamask/rpc-errors'; + +import type { MiddlewareScaffold } from './createScaffoldMiddleware'; +import { createScaffoldMiddleware } from './createScaffoldMiddleware'; +import { JsonRpcEngineV2 } from './JsonRpcEngineV2'; +import { makeRequest } from '../../tests/utils'; + +describe('createScaffoldMiddleware', () => { + it('basic middleware test', async () => { + const scaffold: MiddlewareScaffold = { + method1: 'foo', + method2: () => 42, + method3: () => { + throw rpcErrors.internal({ message: 'method3' }); + }, + }; + + const engine = JsonRpcEngineV2.create({ + middleware: [createScaffoldMiddleware(scaffold), () => 'passthrough'], + }); + + const result1 = await engine.handle(makeRequest({ method: 'method1' })); + const result2 = await engine.handle(makeRequest({ method: 'method2' })); + const promise3 = engine.handle(makeRequest({ method: 'method3' })); + const result4 = await engine.handle(makeRequest({ method: 'unknown' })); + + expect(result1).toBe('foo'); + + expect(result2).toBe(42); + + await expect(promise3).rejects.toThrow( + rpcErrors.internal({ message: 'method3' }), + ); + + expect(result4).toBe('passthrough'); + }); +}); diff --git a/packages/json-rpc-engine/src/v2/createScaffoldMiddleware.ts b/packages/json-rpc-engine/src/v2/createScaffoldMiddleware.ts new file mode 100644 index 00000000000..88c7212db9f --- /dev/null +++ b/packages/json-rpc-engine/src/v2/createScaffoldMiddleware.ts @@ -0,0 +1,44 @@ +import type { Json, JsonRpcParams, JsonRpcRequest } from '@metamask/utils'; + +import type { JsonRpcMiddleware } from './JsonRpcEngineV2'; + +// Only permit primitive values as hard-coded scaffold middleware results. +type JsonPrimitive = string | number | boolean | null; + +export type ScaffoldMiddlewareHandler< + Params extends JsonRpcParams, + Result extends Json, +> = JsonRpcMiddleware, Result> | JsonPrimitive; + +/** + * A record of RPC method handler functions or hard-coded results, keyed to particular method names. + * Only primitive JSON values are permitted as hard-coded results. + */ +export type MiddlewareScaffold = Record< + string, + ScaffoldMiddlewareHandler +>; + +/** + * Creates a middleware function from an object of RPC method handler functions, + * keyed to particular method names. If a method corresponding to a key of this + * object is requested, this middleware will pass it to the corresponding + * handler and return the result. + * + * @param handlers - The RPC method handler functions. + * @returns The scaffold middleware function. + */ +export function createScaffoldMiddleware( + handlers: MiddlewareScaffold, +): JsonRpcMiddleware { + return ({ request, context, next }) => { + const handlerOrResult = handlers[request.method]; + if (handlerOrResult === undefined) { + return next(); + } + + return typeof handlerOrResult === 'function' + ? handlerOrResult({ request, context, next }) + : handlerOrResult; + }; +} diff --git a/packages/json-rpc-engine/src/v2/index.test.ts b/packages/json-rpc-engine/src/v2/index.test.ts index 84bb2a7354b..4195008be4f 100644 --- a/packages/json-rpc-engine/src/v2/index.test.ts +++ b/packages/json-rpc-engine/src/v2/index.test.ts @@ -9,6 +9,7 @@ describe('@metamask/json-rpc-engine/v2', () => { "JsonRpcServer", "MiddlewareContext", "asLegacyMiddleware", + "createScaffoldMiddleware", "getUniqueId", "isNotification", "isRequest", diff --git a/packages/json-rpc-engine/src/v2/index.ts b/packages/json-rpc-engine/src/v2/index.ts index be915b1e5b6..29ea635dca6 100644 --- a/packages/json-rpc-engine/src/v2/index.ts +++ b/packages/json-rpc-engine/src/v2/index.ts @@ -1,5 +1,6 @@ export { asLegacyMiddleware } from './asLegacyMiddleware'; export { getUniqueId } from '../getUniqueId'; +export { createScaffoldMiddleware } from './createScaffoldMiddleware'; export * from './JsonRpcEngineV2'; export { JsonRpcServer } from './JsonRpcServer'; export { MiddlewareContext } from './MiddlewareContext'; diff --git a/packages/json-rpc-engine/tests/utils.ts b/packages/json-rpc-engine/tests/utils.ts index 2ed886379a9..3372475ca12 100644 --- a/packages/json-rpc-engine/tests/utils.ts +++ b/packages/json-rpc-engine/tests/utils.ts @@ -4,22 +4,28 @@ import type { JsonRpcMiddleware } from 'src/v2/JsonRpcEngineV2'; import { requestProps } from '../src/v2/compatibility-utils'; import type { JsonRpcNotification } from '../src/v2/utils'; -export const makeRequest = >( - params: Request = {} as Request, +const jsonrpc = '2.0' as const; + +export const makeRequest = < + Input extends Partial, + Output extends Input & JsonRpcRequest, +>( + request: Input = {} as Input, ) => ({ - jsonrpc: '2.0', - id: '1', - method: 'test_request', - params: [], - ...params, - }) as const satisfies JsonRpcRequest; + jsonrpc, + id: request.id ?? '1', + method: request.method ?? 'test_request', + + params: request.params === undefined ? [] : request.params, + ...request, + }) as Output; export const makeNotification = >( params: Request = {} as Request, ) => ({ - jsonrpc: '2.0', + jsonrpc, method: 'test_request', params: [], ...params, From ddf4e731514850c400fb970cfe4e6c3426413c4f Mon Sep 17 00:00:00 2001 From: Erik Marks <25517051+rekmarks@users.noreply.github.com> Date: Mon, 27 Oct 2025 22:05:17 -0700 Subject: [PATCH 2/3] docs: Update changelog --- packages/json-rpc-engine/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/json-rpc-engine/CHANGELOG.md b/packages/json-rpc-engine/CHANGELOG.md index b47a5dbcd18..1dea6cc6675 100644 --- a/packages/json-rpc-engine/CHANGELOG.md +++ b/packages/json-rpc-engine/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- `JsonRpcEngineV2` ([#6176](https://github.com/MetaMask/core/pull/6176), [#6971](https://github.com/MetaMask/core/pull/6971)) +- `JsonRpcEngineV2` ([#6176](https://github.com/MetaMask/core/pull/6176), [#6971](https://github.com/MetaMask/core/pull/6971), [#6975](https://github.com/MetaMask/core/pull/6975)) - This is a complete rewrite of `JsonRpcEngine`, intended to replace the original implementation. See the readme for details. From 7b4e1b6d762ec7f8fed63ba1670174b8383b5250 Mon Sep 17 00:00:00 2001 From: Erik Marks <25517051+rekmarks@users.noreply.github.com> Date: Mon, 27 Oct 2025 22:48:17 -0700 Subject: [PATCH 3/3] refactor: Add defaults to MiddlewareParams generics --- packages/json-rpc-engine/src/v2/JsonRpcEngineV2.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/json-rpc-engine/src/v2/JsonRpcEngineV2.ts b/packages/json-rpc-engine/src/v2/JsonRpcEngineV2.ts index 4f55df4a382..24a41d82929 100644 --- a/packages/json-rpc-engine/src/v2/JsonRpcEngineV2.ts +++ b/packages/json-rpc-engine/src/v2/JsonRpcEngineV2.ts @@ -39,8 +39,8 @@ export type Next = ( ) => Promise> | undefined>; export type MiddlewareParams< - Request extends JsonRpcCall, - Context extends MiddlewareContext, + Request extends JsonRpcCall = JsonRpcCall, + Context extends MiddlewareContext = MiddlewareContext, > = { request: Readonly; context: Context;