feat: Add subscribeOnce and waitUntil methods#8575
feat: Add subscribeOnce and waitUntil methods#8575FrederikBolding wants to merge 9 commits intomainfrom
subscribeOnce and waitUntil methods#8575Conversation
| const promise = messenger.waitUntil('Fixture:message'); | ||
| messenger.publish('Fixture:message', 'foo'); | ||
|
|
||
| expect(await promise).toBe('foo'); |
There was a problem hiding this comment.
When not using a selector this just returns the first parameter of the event. The other option is returning the entire array, but that feels a bit complicated 🤔
There was a problem hiding this comment.
Hmm, why does it feel complicated? If it's easy to support, I say we give consumers the whole payload.
There was a problem hiding this comment.
Ah, I should have clarified. What I was worried about is the ergonomics of dealing with the entire payload array versus just the first parameter (since more often than not the first parameter is probably what you want). But it may be fine.
See 0ab75e3
| | ((value: SelectorReturnValue) => boolean); | ||
| }, | ||
| ): Promise<SelectorReturnValue | ExtractEventPayload<Event, EventType>[0]> { | ||
| return new Promise((resolve) => { |
There was a problem hiding this comment.
How we expect waitUntil to be used? In Slack you gave this example:
getUnlockPromise: () => {
if (engineContext.KeyringController.isUnlocked()) {
return Promise.resolve();
}
return new Promise<void>((resolve) => {
controllerMessenger.subscribeOnceIf(
'KeyringController:unlock',
resolve,
() => true,
);
});
},Is the idea that with this method you could shorten this to the following?
getUnlockPromise: () => {
return await controllerMessenger.waitUntil(
'KeyringController:unlock',
{
condition: () => {
return engineContext.KeyringController.isUnlocked();
}
}
);
},Or is it that we would expect people to write this instead?
getUnlockPromise: () => {
return await controllerMessenger.waitUntil(
'KeyringController:stateChange',
{
selector: (state) => state.isUnlocked,
}
);
},There was a problem hiding this comment.
Either way would we want to check the condition beforehand to make sure is not true before subscribing? (I guess the same goes for subscribeOnce as well.)
There was a problem hiding this comment.
The condition is only applied when a state update comes in, it is meant for a scenario like this:
await messenger.waitUntil(
'TransactionController:transactionSuccess',
{ condition: (tx) => tx.hash === 'foo' }
);There was a problem hiding this comment.
For the unlock example it would likely have to be:
getUnlockPromise: () => {
if (engineContext.KeyringController.isUnlocked()) {
return Promise.resolve();
}
return controllerMessenger.waitUntil('KeyringController:unlock');
},There was a problem hiding this comment.
Gotcha. If we mainly would use condition for waiting for an object to reach a certain status, then do we need condition at all? I feel like having a specialized event solves the same problem but without needing to check anything else at runtime:
await messenger.waitFor(
`TransactionController:transactionSuccess:${tx.hash}`,
);There was a problem hiding this comment.
Sure, that's another way of doing it. But I was under the impression that we agreed on having both tools would be useful and that forcing more granular events is not necessarily the path we wanted.
There was a problem hiding this comment.
The value you want to check for may also not necessarily be serialisable to a string.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ 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 0ab75e3. Configure here.

Explanation
Add
subscribeOnceandwaitUntilutility methods toMessenger. These are useful for subscribing to and waiting for a certain event to fire without wanting to listen for more than one invocation.Additionally the methods support a
conditionparameter which can be used as a predicate for whether the event should be consumed in the first place.A similar approach is already in-use on mobile, this is a "native" replacement.
References
https://consensyssoftware.atlassian.net/browse/WPC-985
Checklist
Note
Medium Risk
Adds new subscription/promise utilities on the core
Messengerevent path, which could introduce subtle behavior or typing issues around selectors, conditions, and auto-unsubscribe semantics.Overview
Adds
Messenger.subscribeOnceto subscribe to an event (optionally with selector + predicate) and automatically unsubscribe after the first matching invocation.Adds
Messenger.waitUntil, a Promise-based helper built onsubscribeOnce, resolving with the next matching event payload/selected value.Updates tests to cover one-shot subscription behavior, selector support, and conditional consumption, and documents the new APIs in the package changelog.
Reviewed by Cursor Bugbot for commit eb007dc. Bugbot is set up for automated code reviews on this repo. Configure here.