-
-
Notifications
You must be signed in to change notification settings - Fork 246
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
Conversation
4876850
to
e3899db
Compare
776c9b8
to
c249e17
Compare
There was a problem hiding this 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
.
packages/messenger/src/Messenger.ts
Outdated
handler: | ||
| ActionHandler<Action, ActionType> | ||
| ActionHandler<ActionConstraint, ActionType>, |
There was a problem hiding this comment.
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?
handler: | |
| ActionHandler<Action, ActionType> | |
| ActionHandler<ActionConstraint, ActionType>, | |
handler: ActionHandler<Action, ActionType>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had to widen the type because of contravarient types. Essentially, TypeScript was complaining in some situations because the messenger we were delegating to had additional actions registered that the delegating messenger's type was not aware of. This doesn't present a problem for us in practice, but it did cause a type mismatch.
I added a comment to explain this.
Also I experimented with adding type tests in a separate branch: https://github.com/MetaMask/core/tree/demonstrate-type-error
Notice the two commits: first I narrowed this type, and wrote type tests to demonstrate the error. Then the next commit (the last one) makes the minimal changes necessary to fix the errors. Try experimenting with it locally, every single change in that last commit was necessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting! Thanks for making that branch and writing up those tests, that helped a lot. (tstyche
is pretty cool, too.) I am not fully convinced that this is the best solution long-term, but it does make sense, and in the end I don't think it impacts type-safety.
(On a long-term solution: I am curious whether the fact that we divorce the type of an action from its handler throughout the entire Messenger class may be the real obstacle here. I feel like if we received and passed the Action
or Event
type as one unit then it could work better and TypeScript would be able to understand that we are comparing a subset of all actions and types that are involved. But I haven't tested that out yet, so we might end up in the same place.)
(On type-safety: One drawback from widening these types is that now you won't get a type error if you try to delegate an action or event to a messenger that doesn't actually support it. It's not a big deal because if you tried to use the delegated messenger to call the action or subscribe to the event later it wouldn't work anyway, but it's something to keep in mind.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One drawback from widening these types is that now you won't get a type error if you try to delegate an action or event to a messenger that doesn't actually support it
Hmm. Good point. This does mean delegate
isn't really type-safe. I will revisit this. If we can't get type safety here, this could at least be simplified.
I am curious whether the fact that we divorce the type of an action from its handler throughout the entire Messenger class may be the real obstacle here. I feel like if we received and passed the Action or Event type as one unit then it could work better and TypeScript would be able to understand that we are comparing a subset of all actions and types that are involved
Could you elaborate on what you had in mind here? I'm not sure I follow what you mean when you talk about passing them as "one unit". Are you talking about how we're using the type union, or how we're referencing individual properties of an action or event?
The problem as I see it is that "comparing a subset" fundamentally doesn't work with contravarient types. But maybe I'm missing something.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The type for registerActionHandler
, publish
, subscribe
, call
, _internalRegisterDelegatedActionHandler
, etc. take just the name of an action or event and then derive an action/event handler type from it if necessary. I was suggesting that if it is possible for these methods to take the entire action or event object type then perhaps TypeScript could compare the subset of actions and events more easily between the DelegatedMessenger
type and the Messenger
type. But perhaps you are right; at the end of the day it would probably still have to compare function types, so perhaps it wouldn't matter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I managed to find a way to type check the delegate
and revoke
parameters using a conditional type. The DelegatedMesseger
type is still broadened for internal references past that point, but at least this type safety issue should be resolved. I've pushed updated tests to this branch as well: https://github.com/MetaMask/core/tree/demonstrate-type-error
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had to refactor it again. The old branch I used with the type tests wasn't easy to rebase onto this implementation because the way I produced an error was no longer applicable, so I squashed the tests together and moved them to this new branch to demonstrate that the types still work:
https://github.com/MetaMask/core/tree/add-messenger-type-tests
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Out of curiosity, did you plan on merging these type tests? Or were they just for exploring a path forward? The tstyche
package you found seems very useful at least.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wanted to test whether vite was a better alternative or not first. And an obstacle to that would be figuring out whether vite itself is a suitable replacement for Jest or not in this repo.
I guess we could add tstyche directly in the short term at least, maybe I'll propose that instead
2dd241a
to
6729773
Compare
6729773
to
0a80bbd
Compare
## Explanation Two Messenger methods were missing template directives. They were added in all three Messenger classes (which are soon to be consolidated into one). ## References Extracted from #6132 ## Checklist - [x] I've updated the test suite for new or updated code as appropriate - [x] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [x] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/contributing.md#updating-changelogs), highlighting breaking changes as necessary - [x] I've prepared draft pull requests for clients and consumer packages to resolve any breaking changes
1e6b6ab
to
d6f1dc4
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had one more comment, but it is non-blocking.
LGTM!
d6f1dc4
to
1a4a409
Compare
1a4a409
to
cdcf77c
Compare
The `delegate` and `revoke` methods now validate that the messenger we are delegating to (or revoking from) has the capabilities we are delegating/revoking. This is done with a conditional type. With this added validation, the generic parameters on `DelegatedMessenger` itself were no longer useful and have been removed.
…o match generic type to parameters to support type inference
f980adf
to
61f5053
Compare
|
||
if (subscriptions.size === 0) { | ||
this.#events.delete(eventType); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: Concurrent Modification Causes Unpredictable Behavior
The clearActions
and clearEventSubscriptions
methods modify their respective internal Maps (#actions
and #events
) while iterating over them, leading to unpredictable behavior where entries can be skipped.
Specifically:
clearActions
: Some action handlers and their associated delegation targets may not be properly unregistered.clearEventSubscriptions
: Some non-delegated event subscriptions may not be cleared. Additionally, for these uncleared subscriptions with selectors, their cached values in#eventPayloadCache
are not removed, leading to a memory leak.
Additional Locations (1)
## Explanation Add the `parent` constructor parameter to the `Messenger` class for automatic delegation of all capabilities under that messenger's namespace. This significantly reduces boilerplate, making the messenger easier to use in a similar manner to how the old `RestrictedMessenger` class wored. See this ADR PR for details: MetaMask/decisions#53 ## References Depends upon #6132 Relates to #5626 ## Checklist - [x] I've updated the test suite for new or updated code as appropriate - [x] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [x] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/contributing.md#updating-changelogs), highlighting breaking changes as necessary - [x] I've prepared draft pull requests for clients and consumer packages to resolve any breaking changes
Explanation
The methods
delegate
andrevoke
have been added to the Messenger class. These methods replace the need for theRestrictedMessenger
. ThegetRestricted
method, and theRestrictedMessenger
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