Skip to content

feat: Add messenger delegate and revoke methods #6132

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

Gudahtt
Copy link
Member

@Gudahtt Gudahtt commented Jul 17, 2025

Explanation

The methods delegate and revoke have been added to the Messenger class. These methods replace the need for the RestrictedMessenger. The getRestricted method, and the RestrictedMessenger class, have been removed as obsolete.

Note that the parent constructor parameter described in the ADR is not implemented yet, that will come in the next PR.

References

See this ADR PR for details: MetaMask/decisions#53
Relates to #5626

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, highlighting breaking changes as necessary
  • I've prepared draft pull requests for clients and consumer packages to resolve any breaking changes

@Gudahtt Gudahtt force-pushed the create-messenger-package branch 2 times, most recently from 4876850 to e3899db Compare July 17, 2025 12:36
Base automatically changed from create-messenger-package to main July 17, 2025 12:41
@Gudahtt Gudahtt force-pushed the add-messenger-delegation branch 7 times, most recently from 776c9b8 to c249e17 Compare July 17, 2025 16:09
@Gudahtt Gudahtt marked this pull request as ready for review July 17, 2025 16:09
@Gudahtt Gudahtt requested a review from a team as a code owner July 17, 2025 16:10
cursor[bot]

This comment was marked as outdated.

Copy link
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.

I had some comments, mostly around documentation, but I was also curious about some behavior in delegate which seems to be replicated in #registerInitialEventPayload.

constructor({ namespace }: { namespace: Namespace }) {
this.#namespace = namespace;
}

/**
* Register an action handler.
Copy link
Contributor

Choose a reason for hiding this comment

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

To better distinguish this method from registerDelegatedActionHandler, should we clarify that the action here is one that the messenger "owns"?

Suggested change
* Register an action handler.
* Register a handler for an action under this messenger's namespace.

Or, perhaps literally:

Suggested change
* Register an action handler.
* Register an action owned by this messenger.

@@ -160,7 +196,42 @@ export class Messenger<
* @throws Will throw when a handler has been registered for this action type already.
* @template ActionType - A type union of Action type strings.
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe this should say:

Suggested change
* @template ActionType - A type union of Action type strings.
* @template ActionType - A union of type strings for actions under this messenger's namespace.

Comment on lines +214 to +216
* Register a delegated action handler.
*
* This will make the registered function available to call via the `call` method.
Copy link
Contributor

@mcmire mcmire Jul 23, 2025

Choose a reason for hiding this comment

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

Is it worth expanding this description slightly to clarify what "delegated" means? My thought is that it might seem odd that we are defining a method that is very similar to registerActionHandler, but doesn't require that the action has this messenger's namespace or that the action is one that this messenger recognizes.

Suggested change
* Register a delegated action handler.
*
* This will make the registered function available to call via the `call` method.
* Register a delegated action handler (that is, a handler which comes from another messenger
* that this messenger is permitted to call).
*
* This will make the registered function available to call via the `call` method.

Copy link
Contributor

@mcmire mcmire Jul 23, 2025

Choose a reason for hiding this comment

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

I guess if we make this change, we may have to clarify what "delegated" means for other methods that handle delegation, so there is that.

Alternatively, we could tell people to read the docs for delegate if they want more context. I know we mention this method in the @deprecate tag, but it might be missed.

Copy link
Member Author

Choose a reason for hiding this comment

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

The idea was that these delegated____ methods are internal-only (i.e. called only from within this class). I wasn't too concerned with making the intended usage clear, so long as we understand the intended usage. Everyone else just needs to understand not to call this, ever.

Maybe there is something better than deprecating we can do to achieve this though, e.g. move it under a namespace or something.

Copy link
Contributor

Choose a reason for hiding this comment

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

The idea was that these delegated____ methods are internal-only (i.e. called only from within this class). I wasn't too concerned with making the intended usage clear, so long as we understand the intended usage. Everyone else just needs to understand not to call this, ever.

Yeah, that's fair.

Maybe there is something better than deprecating we can do to achieve this though, e.g. move it under a namespace or something.

Maybe we could prefix the methods with something ugly like __INTERNAL__ or __PRIVATE__ or just _? But then again, VSCode will highlight the usage of the method as deprecated with an underline, so if you were to use it, I feel like you would know. Maybe the @deprecated is good enough.

Copy link
Member Author

Choose a reason for hiding this comment

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

I'll try something different out, I wasn't totally happy with relying on just the method name prefix + @deprecated tag. I'll attempt to revise the description as well.

@@ -180,7 +251,7 @@ export class Messenger<
* handlers
*/
registerMethodActionHandlers<
MessengerClient extends { name: string },
MessengerClient extends { name: Namespace },
Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, good catch.

Comment on lines +227 to +229
handler:
| ActionHandler<Action, ActionType>
| ActionHandler<ActionConstraint, ActionType>,
Copy link
Contributor

Choose a reason for hiding this comment

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

This seems to allow any kind of action handler, but if ActionType must refer to an action that this messenger recognizes, then shouldn't the handler be connected to that action?

Suggested change
handler:
| ActionHandler<Action, ActionType>
| ActionHandler<ActionConstraint, ActionType>,
handler: ActionHandler<Action, ActionType>,

}

/**
* Publish a delegated event.
Copy link
Contributor

Choose a reason for hiding this comment

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

Similar as above — is it worth adding more language here?

Suggested change
* Publish a delegated event.
* Publish a delegated event (that is, an event published by another messenger that this
* messenger is permitted to subscribe to).

* @template AllowedEvent - A type union of the 'type' string for any allowed events.
* This must not include internal events that are in the messenger's namespace.
* @returns The restricted messenger.
* Delegate actions and/or events to another messenger.
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it worth explaining what this does?

Suggested change
* Delegate actions and/or events to another messenger.
* Delegate actions and/or events to another messenger.
*
* - For all actions specified, this method will register an action handler
* on the given messenger that calls the same action within this messenger.
* - For all events specified, this method will register an event handler
* on this messenger that publishes the same event within that messenger.
*
* In other words:
*
* - The delegated messenger is able to call any of the specified actions
* known to this messenger as though they had been registered directly; when
* they are called there, they are called here.
* - The delegated messenger is able to subscribe to any of the specified
* events known to this messenger; when they are published here they will
* be published there as well.
*
* Note that to delegate an action to a messenger, it needs to be already
* present in the type union of all Action types for that messenger; and
* similarly for an event.

Copy link
Contributor

Choose a reason for hiding this comment

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

(Also, if any of that is wrong, feel free to correct me)

Comment on lines +685 to +690
const getPayload = this.#initialEventPayloadGetters.get(eventType);
if (getPayload) {
messenger.registerDelegatedInitialEventPayload({
eventType,
getPayload,
});
}
Copy link
Contributor

Choose a reason for hiding this comment

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

It seems like we are already calling registerDelegatedInitialEventPayload for all delegation targets within #registerInitialEventPayload. Do we need this?

Suggested change
const getPayload = this.#initialEventPayloadGetters.get(eventType);
if (getPayload) {
messenger.registerDelegatedInitialEventPayload({
eventType,
getPayload,
});
}

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes; this is for the case where the initial payload is registered before the delegation. My intent was to support both scenarios (see the correctly registers initial event payload when delegated before payload is set and correctly registers initial event payload when delegated after payload is set tests)

Copy link
Contributor

Choose a reason for hiding this comment

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

Nice!


- **BREAKING:** Add `Namespace` type parameter and required `namespace` constructor parameter ([#6132](https://github.com/MetaMask/core/pull/6132))
- All published events and registered actions should fall under the given namespace. Typically the namespace is the controller or service name. This is the equivalent to the `Namespace` parameter from the old `RestrictedMessenger` class.
- **BREAKING:** Remove `RestrictedMessenger` class ([#6132](https://github.com/MetaMask/core/pull/6132))
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this be in "Removed"?

Action extends ActionConstraint,
Event extends EventConstraint,
> = Pick<
Messenger<string, Action | ActionConstraint, Event | EventConstraint>,
Copy link
Contributor

Choose a reason for hiding this comment

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

Similar question to https://github.com/MetaMask/core/pull/6132/files#r2226392393 — why do we need to allow any kind of action or event?

Copy link
Member Author

Choose a reason for hiding this comment

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

Good question. I ran into problems with covariance/contravariance here (see https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)), but I can't recall the specifics. I'll investigate.

Gudahtt and others added 3 commits July 23, 2025 17:41
The methods `delegate` and `revoke` have been added to the Messenger
class. These methods replace the need for the `RestrictedMessenger`.
The `getRestricted` method, and the `RestrictedMessenger` class, have
been removed as obsolete.

See this ADR PR for details: MetaMask/decisions#53
Co-authored-by: Elliot Winkler <elliot.winkler@gmail.com>
@Gudahtt Gudahtt force-pushed the add-messenger-delegation branch from 2dd241a to 6729773 Compare July 23, 2025 20:11
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.

2 participants