From 60b5ae3af552b77b56d375e5870fdc5357aa9fe9 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 29 Oct 2025 07:37:30 +0000 Subject: [PATCH 1/7] feat: Allow converting legacy middleware to V2 middleware Co-authored-by: erik.marks --- packages/json-rpc-engine/src/README.md | 36 +++++++- .../src/asV2Middleware.test.ts | 90 +++++++++++++++++++ .../json-rpc-engine/src/asV2Middleware.ts | 54 ++++++++++- 3 files changed, 176 insertions(+), 4 deletions(-) diff --git a/packages/json-rpc-engine/src/README.md b/packages/json-rpc-engine/src/README.md index a83af82fbc2..d6d5b1f924f 100644 --- a/packages/json-rpc-engine/src/README.md +++ b/packages/json-rpc-engine/src/README.md @@ -23,9 +23,11 @@ engine.push(function (req, res, next, end) { ### V2 compatibility -Use the `asV2Middleware` function to use a `JsonRpcEngine` as a middleware in a +Use the `asV2Middleware` function to use a `JsonRpcEngine` or legacy middleware as middleware in a `JsonRpcEngineV2`: +#### Converting a legacy engine + ```ts import { JsonRpcEngineV2 } from '@metamask/json-rpc-engine/v2'; import { asV2Middleware, JsonRpcEngine } from '@metamask/json-rpc-engine'; @@ -38,6 +40,38 @@ const v2Engine = JsonRpcEngineV2.create({ }); ``` +#### Converting legacy middleware + +You can also directly convert one or more legacy middlewares without creating an engine: + +```ts +import { JsonRpcEngineV2 } from '@metamask/json-rpc-engine/v2'; +import { asV2Middleware } from '@metamask/json-rpc-engine'; + +// Convert a single legacy middleware +const middleware1 = (req, res, next, end) => { + /* ... */ +}; + +const v2Engine = JsonRpcEngineV2.create({ + middleware: [asV2Middleware(middleware1)], +}); + +// Convert multiple legacy middlewares at once +const middleware2 = (req, res, next, end) => { + /* ... */ +}; + +const v2Engine2 = JsonRpcEngineV2.create({ + middleware: [asV2Middleware(middleware1, middleware2)], +}); + +// Or pass an array of legacy middlewares +const v2Engine3 = JsonRpcEngineV2.create({ + middleware: [asV2Middleware([middleware1, middleware2])], +}); +``` + Non-JSON-RPC string properties on the request object will be copied over to the V2 engine's `context` object once the legacy engine is done with the request, _unless_ they already exist on the `context`, in which case they will be ignored. diff --git a/packages/json-rpc-engine/src/asV2Middleware.test.ts b/packages/json-rpc-engine/src/asV2Middleware.test.ts index 81d06190e93..67ca5d69170 100644 --- a/packages/json-rpc-engine/src/asV2Middleware.test.ts +++ b/packages/json-rpc-engine/src/asV2Middleware.test.ts @@ -117,4 +117,94 @@ describe('asV2Middleware', () => { await v2Engine.handle(makeRequest()); expect(observedContextValues).toStrictEqual([1, 2]); }); + + describe('with legacy middleware', () => { + it('accepts a single legacy middleware', async () => { + const legacyMiddleware = jest.fn((_req, res, _next, end) => { + res.result = 'test-result'; + end(); + }); + + const v2Engine = JsonRpcEngineV2.create({ + middleware: [asV2Middleware(legacyMiddleware)], + }); + + const result = await v2Engine.handle(makeRequest()); + expect(result).toBe('test-result'); + expect(legacyMiddleware).toHaveBeenCalledTimes(1); + }); + + it('accepts multiple legacy middlewares via rest params', async () => { + const middleware1 = jest.fn((req, _res, next) => { + req.visited1 = true; + next(); + }); + + const middleware2 = jest.fn((req, res, _next, end) => { + expect(req.visited1).toBe(true); + res.result = 'composed-result'; + end(); + }); + + const v2Engine = JsonRpcEngineV2.create({ + middleware: [asV2Middleware(middleware1, middleware2)], + }); + + const result = await v2Engine.handle(makeRequest()); + expect(result).toBe('composed-result'); + expect(middleware1).toHaveBeenCalledTimes(1); + expect(middleware2).toHaveBeenCalledTimes(1); + }); + + it('accepts an array of legacy middlewares', async () => { + const middleware1 = jest.fn((req, _res, next) => { + req.visited1 = true; + next(); + }); + + const middleware2 = jest.fn((req, res, _next, end) => { + expect(req.visited1).toBe(true); + res.result = 'array-result'; + end(); + }); + + const v2Engine = JsonRpcEngineV2.create({ + middleware: [asV2Middleware([middleware1, middleware2])], + }); + + const result = await v2Engine.handle(makeRequest()); + expect(result).toBe('array-result'); + expect(middleware1).toHaveBeenCalledTimes(1); + expect(middleware2).toHaveBeenCalledTimes(1); + }); + + it('forwards errors from legacy middleware', async () => { + const legacyMiddleware = jest.fn((_req, res, _next, end) => { + res.error = rpcErrors.internal('legacy-error'); + end(); + }); + + const v2Engine = JsonRpcEngineV2.create({ + middleware: [asV2Middleware(legacyMiddleware)], + }); + + await expect(v2Engine.handle(makeRequest())).rejects.toThrow( + rpcErrors.internal('legacy-error'), + ); + }); + + it('allows v2 engine to continue when legacy middleware does not end', async () => { + const legacyMiddleware = jest.fn((_req, _res, next) => { + next(); + }); + + const v2Engine = JsonRpcEngineV2.create({ + middleware: [asV2Middleware(legacyMiddleware), makeNullMiddleware()], + }); + + const result = await v2Engine.handle(makeRequest()); + expect(result).toBeNull(); + expect(legacyMiddleware).toHaveBeenCalledTimes(1); + }); + }); }); diff --git a/packages/json-rpc-engine/src/asV2Middleware.ts b/packages/json-rpc-engine/src/asV2Middleware.ts index 31d514643c8..eaa6422ff4d 100644 --- a/packages/json-rpc-engine/src/asV2Middleware.ts +++ b/packages/json-rpc-engine/src/asV2Middleware.ts @@ -2,15 +2,20 @@ import { serializeError } from '@metamask/rpc-errors'; import type { JsonRpcFailure, JsonRpcResponse } from '@metamask/utils'; import { hasProperty, + type Json, type JsonRpcParams, type JsonRpcRequest, } from '@metamask/utils'; import type { - JsonRpcEngine, JsonRpcEngineEndCallback, JsonRpcEngineNextCallback, } from './JsonRpcEngine'; +import { + JsonRpcEngine, + type JsonRpcMiddleware as LegacyMiddleware, +} from './JsonRpcEngine'; +import { mergeMiddleware } from './mergeMiddleware'; import { deepClone, fromLegacyRequest, @@ -35,8 +40,51 @@ import type { export function asV2Middleware< Params extends JsonRpcParams, Request extends JsonRpcRequest, ->(engine: JsonRpcEngine): JsonRpcMiddleware { - const middleware = engine.asMiddleware(); +>(engine: JsonRpcEngine): JsonRpcMiddleware; + +/** + * Convert one or more legacy middlewares into a {@link JsonRpcEngineV2} middleware. + * + * @param middlewares - The legacy middleware(s) to convert. + * @returns The {@link JsonRpcEngineV2} middleware. + */ +export function asV2Middleware< + Params extends JsonRpcParams, + Request extends JsonRpcRequest, +>( + ...middlewares: LegacyMiddleware[] +): JsonRpcMiddleware; + +/** + * Convert an array of legacy middlewares into a {@link JsonRpcEngineV2} middleware. + * + * @param middlewares - The array of legacy middlewares to convert. + * @returns The {@link JsonRpcEngineV2} middleware. + */ +export function asV2Middleware< + Params extends JsonRpcParams, + Request extends JsonRpcRequest, +>(middlewares: LegacyMiddleware[]): JsonRpcMiddleware; + +export function asV2Middleware< + Params extends JsonRpcParams, + Request extends JsonRpcRequest, +>( + engineOrMiddleware: + | JsonRpcEngine + | LegacyMiddleware + | LegacyMiddleware[], + ...rest: LegacyMiddleware[] +): JsonRpcMiddleware { + // Determine the legacy middleware function from input(s) + const middleware = + engineOrMiddleware instanceof JsonRpcEngine + ? engineOrMiddleware.asMiddleware() + : mergeMiddleware( + Array.isArray(engineOrMiddleware) + ? engineOrMiddleware + : [engineOrMiddleware, ...rest], + ); return async ({ request, context, next }) => { const req = deepClone(request) as JsonRpcRequest; propagateToRequest(req, context); From 545279df3d15b2988b4bb30d52741c37fd6a24d4 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 29 Oct 2025 07:49:58 +0000 Subject: [PATCH 2/7] Refactor asV2Middleware to accept engine or middleware array Co-authored-by: erik.marks --- packages/json-rpc-engine/src/asV2Middleware.ts | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/packages/json-rpc-engine/src/asV2Middleware.ts b/packages/json-rpc-engine/src/asV2Middleware.ts index eaa6422ff4d..64ab32fc7ec 100644 --- a/packages/json-rpc-engine/src/asV2Middleware.ts +++ b/packages/json-rpc-engine/src/asV2Middleware.ts @@ -32,15 +32,17 @@ import type { } from './v2/JsonRpcEngineV2'; /** - * Convert a legacy {@link JsonRpcEngine} into a {@link JsonRpcEngineV2} middleware. + * Convert a legacy {@link JsonRpcEngine} or an array of legacy middlewares into a {@link JsonRpcEngineV2} middleware. * - * @param engine - The legacy engine to convert. + * @param engineOrMiddlewares - The legacy engine or array of legacy middlewares to convert. * @returns The {@link JsonRpcEngineV2} middleware. */ export function asV2Middleware< Params extends JsonRpcParams, Request extends JsonRpcRequest, ->(engine: JsonRpcEngine): JsonRpcMiddleware; +>( + engineOrMiddlewares: JsonRpcEngine | LegacyMiddleware[], +): JsonRpcMiddleware; /** * Convert one or more legacy middlewares into a {@link JsonRpcEngineV2} middleware. @@ -56,16 +58,12 @@ export function asV2Middleware< ): JsonRpcMiddleware; /** - * Convert an array of legacy middlewares into a {@link JsonRpcEngineV2} middleware. + * Implementation of asV2Middleware that handles all input types. * - * @param middlewares - The array of legacy middlewares to convert. + * @param engineOrMiddleware - A legacy engine, a single legacy middleware, or an array of legacy middlewares. + * @param rest - Additional legacy middlewares when the first argument is a single middleware. * @returns The {@link JsonRpcEngineV2} middleware. */ -export function asV2Middleware< - Params extends JsonRpcParams, - Request extends JsonRpcRequest, ->(middlewares: LegacyMiddleware[]): JsonRpcMiddleware; - export function asV2Middleware< Params extends JsonRpcParams, Request extends JsonRpcRequest, From 63f3eb5ef34f2e740e23ca00f815da68fc6e80c5 Mon Sep 17 00:00:00 2001 From: Erik Marks <25517051+rekmarks@users.noreply.github.com> Date: Wed, 29 Oct 2025 10:34:18 -0700 Subject: [PATCH 3/7] refactor: Tweak asV2Middleware signatures --- packages/json-rpc-engine/src/README.md | 7 +--- .../src/asV2Middleware.test.ts | 22 ----------- .../json-rpc-engine/src/asV2Middleware.ts | 38 +++++++------------ 3 files changed, 16 insertions(+), 51 deletions(-) diff --git a/packages/json-rpc-engine/src/README.md b/packages/json-rpc-engine/src/README.md index d6d5b1f924f..e8c67b36d0e 100644 --- a/packages/json-rpc-engine/src/README.md +++ b/packages/json-rpc-engine/src/README.md @@ -65,13 +65,10 @@ const middleware2 = (req, res, next, end) => { const v2Engine2 = JsonRpcEngineV2.create({ middleware: [asV2Middleware(middleware1, middleware2)], }); - -// Or pass an array of legacy middlewares -const v2Engine3 = JsonRpcEngineV2.create({ - middleware: [asV2Middleware([middleware1, middleware2])], -}); ``` +#### Context propagation + Non-JSON-RPC string properties on the request object will be copied over to the V2 engine's `context` object once the legacy engine is done with the request, _unless_ they already exist on the `context`, in which case they will be ignored. diff --git a/packages/json-rpc-engine/src/asV2Middleware.test.ts b/packages/json-rpc-engine/src/asV2Middleware.test.ts index 67ca5d69170..d268b4cf3f9 100644 --- a/packages/json-rpc-engine/src/asV2Middleware.test.ts +++ b/packages/json-rpc-engine/src/asV2Middleware.test.ts @@ -156,28 +156,6 @@ describe('asV2Middleware', () => { expect(middleware2).toHaveBeenCalledTimes(1); }); - it('accepts an array of legacy middlewares', async () => { - const middleware1 = jest.fn((req, _res, next) => { - req.visited1 = true; - next(); - }); - - const middleware2 = jest.fn((req, res, _next, end) => { - expect(req.visited1).toBe(true); - res.result = 'array-result'; - end(); - }); - - const v2Engine = JsonRpcEngineV2.create({ - middleware: [asV2Middleware([middleware1, middleware2])], - }); - - const result = await v2Engine.handle(makeRequest()); - expect(result).toBe('array-result'); - expect(middleware1).toHaveBeenCalledTimes(1); - expect(middleware2).toHaveBeenCalledTimes(1); - }); - it('forwards errors from legacy middleware', async () => { const legacyMiddleware = jest.fn((_req, res, _next, end) => { res.error = rpcErrors.internal('legacy-error'); diff --git a/packages/json-rpc-engine/src/asV2Middleware.ts b/packages/json-rpc-engine/src/asV2Middleware.ts index 64ab32fc7ec..2482aa3ebea 100644 --- a/packages/json-rpc-engine/src/asV2Middleware.ts +++ b/packages/json-rpc-engine/src/asV2Middleware.ts @@ -10,11 +10,9 @@ import { import type { JsonRpcEngineEndCallback, JsonRpcEngineNextCallback, -} from './JsonRpcEngine'; -import { JsonRpcEngine, - type JsonRpcMiddleware as LegacyMiddleware, } from './JsonRpcEngine'; +import { type JsonRpcMiddleware as LegacyMiddleware } from './JsonRpcEngine'; import { mergeMiddleware } from './mergeMiddleware'; import { deepClone, @@ -32,57 +30,49 @@ import type { } from './v2/JsonRpcEngineV2'; /** - * Convert a legacy {@link JsonRpcEngine} or an array of legacy middlewares into a {@link JsonRpcEngineV2} middleware. + * Convert a legacy {@link JsonRpcEngine} into a {@link JsonRpcEngineV2} middleware. * - * @param engineOrMiddlewares - The legacy engine or array of legacy middlewares to convert. + * @param engine - The legacy engine to convert. * @returns The {@link JsonRpcEngineV2} middleware. */ export function asV2Middleware< Params extends JsonRpcParams, Request extends JsonRpcRequest, ->( - engineOrMiddlewares: JsonRpcEngine | LegacyMiddleware[], -): JsonRpcMiddleware; +>(engine: JsonRpcEngine): JsonRpcMiddleware; /** - * Convert one or more legacy middlewares into a {@link JsonRpcEngineV2} middleware. + * Convert one or more legacy middleware into a {@link JsonRpcEngineV2} middleware. * - * @param middlewares - The legacy middleware(s) to convert. + * @param middleware - The legacy middleware(s) to convert. * @returns The {@link JsonRpcEngineV2} middleware. */ export function asV2Middleware< Params extends JsonRpcParams, Request extends JsonRpcRequest, >( - ...middlewares: LegacyMiddleware[] + ...middleware: LegacyMiddleware[] ): JsonRpcMiddleware; /** * Implementation of asV2Middleware that handles all input types. * - * @param engineOrMiddleware - A legacy engine, a single legacy middleware, or an array of legacy middlewares. - * @param rest - Additional legacy middlewares when the first argument is a single middleware. + * @param engineOrMiddleware - A legacy engine, a single legacy middleware, or an array of legacy middleware. + * @param rest - Additional legacy middleware when the first argument is a single middleware. * @returns The {@link JsonRpcEngineV2} middleware. */ export function asV2Middleware< Params extends JsonRpcParams, Request extends JsonRpcRequest, >( - engineOrMiddleware: - | JsonRpcEngine - | LegacyMiddleware - | LegacyMiddleware[], + engineOrMiddleware: JsonRpcEngine | LegacyMiddleware, ...rest: LegacyMiddleware[] ): JsonRpcMiddleware { // Determine the legacy middleware function from input(s) const middleware = - engineOrMiddleware instanceof JsonRpcEngine - ? engineOrMiddleware.asMiddleware() - : mergeMiddleware( - Array.isArray(engineOrMiddleware) - ? engineOrMiddleware - : [engineOrMiddleware, ...rest], - ); + typeof engineOrMiddleware === 'function' + ? mergeMiddleware([engineOrMiddleware, ...rest]) + : engineOrMiddleware.asMiddleware(); + return async ({ request, context, next }) => { const req = deepClone(request) as JsonRpcRequest; propagateToRequest(req, context); From 802773fb5bae19c1852ee816da045cb6352666ba Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 29 Oct 2025 17:45:49 +0000 Subject: [PATCH 4/7] feat: Allow converting V2 middleware to legacy middleware Co-authored-by: erik.marks --- packages/json-rpc-engine/README.md | 33 ++++++- .../src/v2/asLegacyMiddleware.test.ts | 89 +++++++++++++++++++ .../src/v2/asLegacyMiddleware.ts | 48 +++++++++- 3 files changed, 165 insertions(+), 5 deletions(-) diff --git a/packages/json-rpc-engine/README.md b/packages/json-rpc-engine/README.md index 464755feaf8..c22d1832f8d 100644 --- a/packages/json-rpc-engine/README.md +++ b/packages/json-rpc-engine/README.md @@ -81,8 +81,9 @@ await server.handle(notification); ### Legacy compatibility -Use the `asLegacyMiddleware` function to use a `JsonRpcEngineV2` as a -middleware in a legacy `JsonRpcEngine`: +Use the `asLegacyMiddleware` function to use a `JsonRpcEngineV2` or V2 middleware as middleware in a legacy `JsonRpcEngine`: + +#### Converting a V2 engine ```ts import { @@ -102,6 +103,34 @@ const v2Engine = JsonRpcEngineV2.create({ legacyEngine.push(asLegacyMiddleware(v2Engine)); ``` +#### Converting V2 middleware + +You can also directly convert one or more V2 middlewares without creating an engine: + +```ts +import { + asLegacyMiddleware, + type JsonRpcMiddleware, +} from '@metamask/json-rpc-engine/v2'; +import { JsonRpcEngine } from '@metamask/json-rpc-engine'; + +// Convert a single V2 middleware +const middleware1: JsonRpcMiddleware = ({ request }) => { + /* ... */ +}; + +const legacyEngine = new JsonRpcEngine(); +legacyEngine.push(asLegacyMiddleware(middleware1)); + +// Convert multiple V2 middlewares at once +const middleware2: JsonRpcMiddleware = ({ context, next }) => { + /* ... */ +}; + +const legacyEngine2 = new JsonRpcEngine(); +legacyEngine2.push(asLegacyMiddleware(middleware1, middleware2)); +``` + In keeping with the conventions of the legacy engine, non-JSON-RPC string properties of the `context` will be copied over to the request once the V2 engine is done with the request. _Note that **only `string` keys** of the `context` will be copied over._ diff --git a/packages/json-rpc-engine/src/v2/asLegacyMiddleware.test.ts b/packages/json-rpc-engine/src/v2/asLegacyMiddleware.test.ts index b7968898f62..4274f2be027 100644 --- a/packages/json-rpc-engine/src/v2/asLegacyMiddleware.test.ts +++ b/packages/json-rpc-engine/src/v2/asLegacyMiddleware.test.ts @@ -190,4 +190,93 @@ describe('asLegacyMiddleware', () => { await legacyEngine.handle(makeRequest()); expect(observedContextValues).toStrictEqual([1, 2]); }); + + describe('with V2 middleware', () => { + it('accepts a single V2 middleware', async () => { + const v2Middleware: JsonRpcMiddleware = jest.fn( + () => 'test-result', + ); + + const legacyEngine = new JsonRpcEngine(); + legacyEngine.push(asLegacyMiddleware(v2Middleware)); + + const response = (await legacyEngine.handle( + makeRequest(), + )) as JsonRpcSuccess; + + expect(response.result).toBe('test-result'); + expect(v2Middleware).toHaveBeenCalledTimes(1); + }); + + it('accepts multiple V2 middlewares via rest params', async () => { + const middleware1: JsonRpcMiddleware = jest.fn( + ({ context, next }) => { + context.set('visited1', true); + return next(); + }, + ); + + const middleware2: JsonRpcMiddleware = jest.fn( + ({ context }) => { + expect(context.get('visited1')).toBe(true); + return 'composed-result'; + }, + ); + + const legacyEngine = new JsonRpcEngine(); + legacyEngine.push(asLegacyMiddleware(middleware1, middleware2)); + + const response = (await legacyEngine.handle( + makeRequest(), + )) as JsonRpcSuccess; + + expect(response.result).toBe('composed-result'); + expect(middleware1).toHaveBeenCalledTimes(1); + expect(middleware2).toHaveBeenCalledTimes(1); + }); + + it('forwards errors from V2 middleware', async () => { + const v2Middleware: JsonRpcMiddleware = jest.fn(() => { + throw new Error('v2-error'); + }); + + const legacyEngine = new JsonRpcEngine(); + legacyEngine.push(asLegacyMiddleware(v2Middleware)); + + const response = (await legacyEngine.handle( + makeRequest(), + )) as JsonRpcFailure; + + expect(response.error).toStrictEqual({ + message: 'v2-error', + code: -32603, + data: { + cause: { + message: 'v2-error', + stack: expect.any(String), + }, + }, + }); + }); + + it('allows legacy engine to continue when V2 middleware does not end', async () => { + const v2Middleware: JsonRpcMiddleware = jest.fn( + ({ next }) => next(), + ); + + const legacyEngine = new JsonRpcEngine(); + legacyEngine.push(asLegacyMiddleware(v2Middleware)); + legacyEngine.push((_req, res, _next, end) => { + res.result = 'continued'; + end(); + }); + + const response = (await legacyEngine.handle( + makeRequest(), + )) as JsonRpcSuccess; + + expect(response.result).toBe('continued'); + expect(v2Middleware).toHaveBeenCalledTimes(1); + }); + }); }); diff --git a/packages/json-rpc-engine/src/v2/asLegacyMiddleware.ts b/packages/json-rpc-engine/src/v2/asLegacyMiddleware.ts index f5cfe6be406..9e782326c27 100644 --- a/packages/json-rpc-engine/src/v2/asLegacyMiddleware.ts +++ b/packages/json-rpc-engine/src/v2/asLegacyMiddleware.ts @@ -6,7 +6,12 @@ import { makeContext, propagateToRequest, } from './compatibility-utils'; -import type { JsonRpcEngineV2, ResultConstraint } from './JsonRpcEngineV2'; +import type { + JsonRpcEngineV2, + JsonRpcMiddleware as V2Middleware, + ResultConstraint, +} from './JsonRpcEngineV2'; +import { JsonRpcEngineV2 as EngineV2 } from './JsonRpcEngineV2'; import { createAsyncMiddleware } from '..'; import type { JsonRpcMiddleware as LegacyMiddleware } from '..'; @@ -21,8 +26,45 @@ export function asLegacyMiddleware< Request extends JsonRpcRequest, >( engine: JsonRpcEngineV2, +): LegacyMiddleware>; + +/** + * Convert one or more V2 middlewares into a legacy middleware. + * + * @param middleware - The V2 middleware(s) to convert. + * @returns The legacy middleware. + */ +export function asLegacyMiddleware< + Params extends JsonRpcParams, + Request extends JsonRpcRequest, +>( + ...middleware: V2Middleware>[] +): LegacyMiddleware>; + +/** + * Implementation of asLegacyMiddleware that handles all input types. + * + * @param engineOrMiddleware - A V2 engine, a single V2 middleware, or an array of V2 middleware. + * @param rest - Additional V2 middleware when the first argument is a single middleware. + * @returns The legacy middleware. + */ +export function asLegacyMiddleware< + Params extends JsonRpcParams, + Request extends JsonRpcRequest, +>( + engineOrMiddleware: + | JsonRpcEngineV2 + | V2Middleware>, + ...rest: V2Middleware>[] ): LegacyMiddleware> { - const middleware = engine.asMiddleware(); + // Determine the V2 middleware function from input(s) + const middleware = + typeof engineOrMiddleware === 'function' + ? // eslint-disable-next-line @typescript-eslint/no-explicit-any + EngineV2.create({ + middleware: [engineOrMiddleware, ...rest], + }).asMiddleware() + : engineOrMiddleware.asMiddleware(); return createAsyncMiddleware(async (req, res, next) => { const request = fromLegacyRequest(req as Request); const context = makeContext(req); @@ -32,7 +74,7 @@ export function asLegacyMiddleware< request, context, next: (finalRequest) => { - modifiedRequest = finalRequest; + modifiedRequest = finalRequest as Request | undefined; return Promise.resolve(undefined); }, }); From c55eedb56748b106ed74c06276234cec112eefcf Mon Sep 17 00:00:00 2001 From: Erik Marks <25517051+rekmarks@users.noreply.github.com> Date: Wed, 29 Oct 2025 11:12:01 -0700 Subject: [PATCH 5/7] refactor: Tweak asLegacyMiddleware and docs --- packages/json-rpc-engine/README.md | 14 +++++------ packages/json-rpc-engine/src/README.md | 12 +++++----- .../json-rpc-engine/src/asV2Middleware.ts | 7 +++--- .../src/v2/asLegacyMiddleware.ts | 23 ++++++++----------- 4 files changed, 25 insertions(+), 31 deletions(-) diff --git a/packages/json-rpc-engine/README.md b/packages/json-rpc-engine/README.md index c22d1832f8d..7114dd09966 100644 --- a/packages/json-rpc-engine/README.md +++ b/packages/json-rpc-engine/README.md @@ -81,7 +81,13 @@ await server.handle(notification); ### Legacy compatibility -Use the `asLegacyMiddleware` function to use a `JsonRpcEngineV2` or V2 middleware as middleware in a legacy `JsonRpcEngine`: +Use the `asLegacyMiddleware` function to use a `JsonRpcEngineV2` or V2 middleware as middleware in a legacy `JsonRpcEngine`. + +#### Context propagation + +In keeping with the conventions of the legacy engine, non-JSON-RPC string properties of the `context` will be +copied over to the request once the V2 engine is done with the request. _Note that **only `string` keys** of +the `context` will be copied over._ #### Converting a V2 engine @@ -105,8 +111,6 @@ legacyEngine.push(asLegacyMiddleware(v2Engine)); #### Converting V2 middleware -You can also directly convert one or more V2 middlewares without creating an engine: - ```ts import { asLegacyMiddleware, @@ -131,10 +135,6 @@ const legacyEngine2 = new JsonRpcEngine(); legacyEngine2.push(asLegacyMiddleware(middleware1, middleware2)); ``` -In keeping with the conventions of the legacy engine, non-JSON-RPC string properties of the `context` will be -copied over to the request once the V2 engine is done with the request. _Note that **only `string` keys** of -the `context` will be copied over._ - ### Middleware Middleware functions can be sync or async. diff --git a/packages/json-rpc-engine/src/README.md b/packages/json-rpc-engine/src/README.md index e8c67b36d0e..53038424af5 100644 --- a/packages/json-rpc-engine/src/README.md +++ b/packages/json-rpc-engine/src/README.md @@ -26,6 +26,12 @@ engine.push(function (req, res, next, end) { Use the `asV2Middleware` function to use a `JsonRpcEngine` or legacy middleware as middleware in a `JsonRpcEngineV2`: +#### Context propagation + +Non-JSON-RPC string properties on the request object will be copied over to the V2 engine's `context` object +once the legacy engine is done with the request, _unless_ they already exist on the `context`, in which case +they will be ignored. + #### Converting a legacy engine ```ts @@ -67,12 +73,6 @@ const v2Engine2 = JsonRpcEngineV2.create({ }); ``` -#### Context propagation - -Non-JSON-RPC string properties on the request object will be copied over to the V2 engine's `context` object -once the legacy engine is done with the request, _unless_ they already exist on the `context`, in which case -they will be ignored. - ### Middleware Requests are handled asynchronously, stepping down the stack until complete. diff --git a/packages/json-rpc-engine/src/asV2Middleware.ts b/packages/json-rpc-engine/src/asV2Middleware.ts index 2482aa3ebea..77204e8b9ba 100644 --- a/packages/json-rpc-engine/src/asV2Middleware.ts +++ b/packages/json-rpc-engine/src/asV2Middleware.ts @@ -22,7 +22,7 @@ import { unserializeError, } from './v2/compatibility-utils'; import type { - // JsonRpcEngineV2 is used in docs. + // Used in docs. // eslint-disable-next-line @typescript-eslint/no-unused-vars JsonRpcEngineV2, JsonRpcMiddleware, @@ -67,8 +67,7 @@ export function asV2Middleware< engineOrMiddleware: JsonRpcEngine | LegacyMiddleware, ...rest: LegacyMiddleware[] ): JsonRpcMiddleware { - // Determine the legacy middleware function from input(s) - const middleware = + const legacyMiddleware = typeof engineOrMiddleware === 'function' ? mergeMiddleware([engineOrMiddleware, ...rest]) : engineOrMiddleware.asMiddleware(); @@ -98,7 +97,7 @@ export function asV2Middleware< const legacyNext = ((cb: JsonRpcEngineEndCallback) => cb(end)) as JsonRpcEngineNextCallback; - middleware(req, res, legacyNext, end); + legacyMiddleware(req, res, legacyNext, end); }); propagateToContext(req, context); diff --git a/packages/json-rpc-engine/src/v2/asLegacyMiddleware.ts b/packages/json-rpc-engine/src/v2/asLegacyMiddleware.ts index 9e782326c27..3dc8ad19479 100644 --- a/packages/json-rpc-engine/src/v2/asLegacyMiddleware.ts +++ b/packages/json-rpc-engine/src/v2/asLegacyMiddleware.ts @@ -6,12 +6,8 @@ import { makeContext, propagateToRequest, } from './compatibility-utils'; -import type { - JsonRpcEngineV2, - JsonRpcMiddleware as V2Middleware, - ResultConstraint, -} from './JsonRpcEngineV2'; -import { JsonRpcEngineV2 as EngineV2 } from './JsonRpcEngineV2'; +import type { JsonRpcMiddleware, ResultConstraint } from './JsonRpcEngineV2'; +import { JsonRpcEngineV2 } from './JsonRpcEngineV2'; import { createAsyncMiddleware } from '..'; import type { JsonRpcMiddleware as LegacyMiddleware } from '..'; @@ -38,7 +34,7 @@ export function asLegacyMiddleware< Params extends JsonRpcParams, Request extends JsonRpcRequest, >( - ...middleware: V2Middleware>[] + ...middleware: JsonRpcMiddleware>[] ): LegacyMiddleware>; /** @@ -54,23 +50,22 @@ export function asLegacyMiddleware< >( engineOrMiddleware: | JsonRpcEngineV2 - | V2Middleware>, - ...rest: V2Middleware>[] + | JsonRpcMiddleware>, + ...rest: JsonRpcMiddleware>[] ): LegacyMiddleware> { - // Determine the V2 middleware function from input(s) - const middleware = + const JsonRpcMiddleware = typeof engineOrMiddleware === 'function' - ? // eslint-disable-next-line @typescript-eslint/no-explicit-any - EngineV2.create({ + ? JsonRpcEngineV2.create({ middleware: [engineOrMiddleware, ...rest], }).asMiddleware() : engineOrMiddleware.asMiddleware(); + return createAsyncMiddleware(async (req, res, next) => { const request = fromLegacyRequest(req as Request); const context = makeContext(req); let modifiedRequest: Request | undefined; - const result = await middleware({ + const result = await JsonRpcMiddleware({ request, context, next: (finalRequest) => { From 7371b97249cff8de77345205b09c3e17f219b5d0 Mon Sep 17 00:00:00 2001 From: Erik Marks <25517051+rekmarks@users.noreply.github.com> Date: Wed, 29 Oct 2025 11:15:59 -0700 Subject: [PATCH 6/7] 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 e3617409f25..6b0fcd75c3c 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), [#6975](https://github.com/MetaMask/core/pull/6975), [#6990](https://github.com/MetaMask/core/pull/6990)) +- `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), [#6990](https://github.com/MetaMask/core/pull/6990), [#6991](https://github.com/MetaMask/core/pull/6991)) - This is a complete rewrite of `JsonRpcEngine`, intended to replace the original implementation. See the readme for details. From d1fe8972c343ec05960bb05f340cf8d742a03d8a Mon Sep 17 00:00:00 2001 From: Erik Marks <25517051+rekmarks@users.noreply.github.com> Date: Wed, 29 Oct 2025 11:24:42 -0700 Subject: [PATCH 7/7] refactor: Tweak docs and some variable names --- packages/json-rpc-engine/README.md | 2 +- packages/json-rpc-engine/src/README.md | 3 +-- packages/json-rpc-engine/src/asV2Middleware.ts | 10 +++++----- .../json-rpc-engine/src/v2/asLegacyMiddleware.ts | 12 ++++++------ 4 files changed, 13 insertions(+), 14 deletions(-) diff --git a/packages/json-rpc-engine/README.md b/packages/json-rpc-engine/README.md index 7114dd09966..8611bba9141 100644 --- a/packages/json-rpc-engine/README.md +++ b/packages/json-rpc-engine/README.md @@ -81,7 +81,7 @@ await server.handle(notification); ### Legacy compatibility -Use the `asLegacyMiddleware` function to use a `JsonRpcEngineV2` or V2 middleware as middleware in a legacy `JsonRpcEngine`. +Use `asLegacyMiddleware()` to convert a `JsonRpcEngineV2` or one or more V2 middleware into a legacy middleware. #### Context propagation diff --git a/packages/json-rpc-engine/src/README.md b/packages/json-rpc-engine/src/README.md index 53038424af5..48be9407e05 100644 --- a/packages/json-rpc-engine/src/README.md +++ b/packages/json-rpc-engine/src/README.md @@ -23,8 +23,7 @@ engine.push(function (req, res, next, end) { ### V2 compatibility -Use the `asV2Middleware` function to use a `JsonRpcEngine` or legacy middleware as middleware in a -`JsonRpcEngineV2`: +Use `asV2Middleware()` to convert a `JsonRpcEngine` or one or more legacy middleware into a V2 middleware. #### Context propagation diff --git a/packages/json-rpc-engine/src/asV2Middleware.ts b/packages/json-rpc-engine/src/asV2Middleware.ts index 77204e8b9ba..34f4bd34877 100644 --- a/packages/json-rpc-engine/src/asV2Middleware.ts +++ b/packages/json-rpc-engine/src/asV2Middleware.ts @@ -8,9 +8,9 @@ import { } from '@metamask/utils'; import type { + JsonRpcEngine, JsonRpcEngineEndCallback, JsonRpcEngineNextCallback, - JsonRpcEngine, } from './JsonRpcEngine'; import { type JsonRpcMiddleware as LegacyMiddleware } from './JsonRpcEngine'; import { mergeMiddleware } from './mergeMiddleware'; @@ -43,7 +43,7 @@ export function asV2Middleware< /** * Convert one or more legacy middleware into a {@link JsonRpcEngineV2} middleware. * - * @param middleware - The legacy middleware(s) to convert. + * @param middleware - The legacy middleware to convert. * @returns The {@link JsonRpcEngineV2} middleware. */ export function asV2Middleware< @@ -54,10 +54,10 @@ export function asV2Middleware< ): JsonRpcMiddleware; /** - * Implementation of asV2Middleware that handles all input types. + * The asV2Middleware implementation. * - * @param engineOrMiddleware - A legacy engine, a single legacy middleware, or an array of legacy middleware. - * @param rest - Additional legacy middleware when the first argument is a single middleware. + * @param engineOrMiddleware - A legacy engine or legacy middleware. + * @param rest - Any additional legacy middleware when the first argument is a middleware. * @returns The {@link JsonRpcEngineV2} middleware. */ export function asV2Middleware< diff --git a/packages/json-rpc-engine/src/v2/asLegacyMiddleware.ts b/packages/json-rpc-engine/src/v2/asLegacyMiddleware.ts index 3dc8ad19479..66ceba3b26a 100644 --- a/packages/json-rpc-engine/src/v2/asLegacyMiddleware.ts +++ b/packages/json-rpc-engine/src/v2/asLegacyMiddleware.ts @@ -38,10 +38,10 @@ export function asLegacyMiddleware< ): LegacyMiddleware>; /** - * Implementation of asLegacyMiddleware that handles all input types. + * The asLegacyMiddleware implementation. * - * @param engineOrMiddleware - A V2 engine, a single V2 middleware, or an array of V2 middleware. - * @param rest - Additional V2 middleware when the first argument is a single middleware. + * @param engineOrMiddleware - A V2 engine or V2 middleware. + * @param rest - Any additional V2 middleware when the first argument is a middleware. * @returns The legacy middleware. */ export function asLegacyMiddleware< @@ -53,7 +53,7 @@ export function asLegacyMiddleware< | JsonRpcMiddleware>, ...rest: JsonRpcMiddleware>[] ): LegacyMiddleware> { - const JsonRpcMiddleware = + const v2Middleware = typeof engineOrMiddleware === 'function' ? JsonRpcEngineV2.create({ middleware: [engineOrMiddleware, ...rest], @@ -65,11 +65,11 @@ export function asLegacyMiddleware< const context = makeContext(req); let modifiedRequest: Request | undefined; - const result = await JsonRpcMiddleware({ + const result = await v2Middleware({ request, context, next: (finalRequest) => { - modifiedRequest = finalRequest as Request | undefined; + modifiedRequest = finalRequest; return Promise.resolve(undefined); }, });