Skip to content

feat: Add createMethodMiddleware function#8506

Open
FrederikBolding wants to merge 9 commits intomainfrom
fb/create-method-middleware
Open

feat: Add createMethodMiddleware function#8506
FrederikBolding wants to merge 9 commits intomainfrom
fb/create-method-middleware

Conversation

@FrederikBolding
Copy link
Copy Markdown
Member

@FrederikBolding FrederikBolding commented Apr 17, 2026

Explanation

This PR implements a createMethodMiddleware function which allows usage of both hooks and the messenger. Currently hooks are the only way JSON-RPC implementations can leverage controllers, services etc. They require more glue code to be written and are more difficult to type. This PR aims to provide a shared utility for constructing middlewares with multiple method implementations, while also allowing these implementations to use the messenger, cutting down the amount of glue code required in each client significantly.

References

https://consensyssoftware.atlassian.net/browse/WPC-981

Checklist

  • I've updated the test suite for new or updated code as appropriate
  • I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate
  • I've communicated my changes to consumers by updating changelogs for packages I've changed
  • I've introduced breaking changes in this PR and have prepared draft pull requests for clients and consumer packages to resolve them

Note

Medium Risk
Introduces new middleware construction and messenger delegation behavior; while additive, mistakes could cause incorrect action routing or over-broad hook exposure across RPC methods.

Overview
Adds createMethodMiddleware to @metamask/json-rpc-engine/v2, enabling per-method JSON-RPC handlers to receive a least-privilege subset of declared hooks and a delegated @metamask/messenger instance for calling registered actions.

Updates exports/tests to include createMethodMiddleware, selectHooks, and the MethodHandler type, and wires in @metamask/messenger as a dependency (including TS project references) plus a small README dependency-graph edge and changelog entry.

Reviewed by Cursor Bugbot for commit fb74b9f. Bugbot is set up for automated code reviews on this repo. Configure here.

messenger: Messenger<string, MessengerActions>;
hooks: Hooks;
}) => Promise<Result> | Result;
hookNames?: { [Key in keyof Hooks]: true };
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering why this wasn't just a list of names in our current production implementations. Open to changing it here.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is because, at the time it was first implemented, you couldn't create an array type like [Key in keyof Hooks]. Idk if this has changed.

@FrederikBolding FrederikBolding marked this pull request as ready for review April 20, 2026 10:42
@FrederikBolding FrederikBolding requested review from a team as code owners April 20, 2026 10:42
Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 9ad6497. Configure here.

Comment thread packages/json-rpc-engine/src/v2/createMethodMiddleware.ts Outdated
Comment thread packages/json-rpc-engine/src/v2/index.ts
Copy link
Copy Markdown
Contributor

@mcmire mcmire left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few minor suggestions, but I tested this out and it seems to work pretty well.

messenger,
});

const engine = JsonRpcEngineV2.create({ middleware: [middleware] });
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we be sharing values across tests like this? Just curious whether it would be less error-prone in the future to create them inside of each test, perhaps using a function to encapsulate the setup.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's ever justified. Even if I know that the value is stateless, I still create a makeX() utility or do let x; beforeEach(() => { x = ... });.

options: CreateMethodMiddlewareOptions<Handlers>,
): JsonRpcMiddleware<JsonRpcRequest, Json, Context> {
const { messenger: rootMessenger } = options;
const allHooks = options.hooks as Record<string, unknown>;
Copy link
Copy Markdown
Contributor

@mcmire mcmire Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was curious why we needed this type assertion. I asked Claude, and if I understand correctly, because we are using a conditional type in HandlerHooks, TypeScript doesn't have enough information to resolve UnionToIntersection<HandlerHooks<Handlers[keyof Handlers]>> to Record<string, unknown> at this point in time. I'm not sure what TypeScript would need or how else we could set up the types for createMethodMiddleware. But the suggestion I was given was that we could change CreateMethodMiddlewareOptions from this:

export type CreateMethodMiddlewareOptions<
  Handlers extends Record<string, AnyMethodHandler>,
> = {
  handlers: Handlers;
  messenger: Messenger<string, HandlerActions<Handlers[keyof Handlers]>>;
  hooks: UnionToIntersection<HandlerHooks<Handlers[keyof Handlers]>>;
};

to this:

export type CreateMethodMiddlewareOptions<
  Handlers extends Record<string, AnyMethodHandler>,
> = {
  handlers: Handlers;
  messenger: Messenger<string, HandlerActions<Handlers[keyof Handlers]>>;
  hooks: UnionToIntersection<HandlerHooks<Handlers[keyof Handlers]>> &
    Record<string, unknown>;
};

and then here we can remove the type assertion:

Suggested change
const allHooks = options.hooks as Record<string, unknown>;
const allHooks = options.hooks;

My interpretation of this is we are telling TypeScript, "hey, just so you know, hooks is also a Record<string, unknown>, so treat it like that too".

What do you think?


### Added

- Add `createMethodMiddleware` ([#8506](https://github.com/MetaMask/core/pull/8506))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we be explicit that this was added to the v2 path? Something like:

Suggested change
- Add `createMethodMiddleware` ([#8506](https://github.com/MetaMask/core/pull/8506))
- Add `createMethodMiddleware` to `@metamask/json-rpc-engine/v2` ([#8506](https://github.com/MetaMask/core/pull/8506))

or even just

Suggested change
- Add `createMethodMiddleware` ([#8506](https://github.com/MetaMask/core/pull/8506))
- Add `createMethodMiddleware` to `v2` ([#8506](https://github.com/MetaMask/core/pull/8506))

Comment on lines +40 to +41
messenger: Messenger<string, MessengerActions>;
hooks: Hooks;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should these be optional? Could they be optional?

messenger: Messenger<string, MessengerActions>;
hooks: Hooks;
}) => Promise<Result> | Result;
hookNames?: { [Key in keyof Hooks]: true };
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is because, at the time it was first implemented, you couldn't create an array type like [Key in keyof Hooks]. Idk if this has changed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants