core
core copied to clipboard
[base-controller] Implement utility function `registerEventSubscriptions` for batch registering all internal events of a messenger client class
Motivation
- Currently, we manually enumerate each individual
.subscribe()call in messenger client constructors. We should have a DRY-er way of doing this. - A "data as code" paradigm could be implemented by supplying the constructor with a map pairing each event type with an
EventSubscriptionMapof its subscriptions (and associated handlers, selectors).- This would replace the hard-coded
subscribecalls with flexible input data. - Consumers could be optionally allowed to override the default
EventSubscriptionMapargument, thereby making the event handler and selector behavior programmable. - The presence of the default argument means that if alteration of behavior is unneeded, consumers can simply instantiate the class without any additional requirements.
- This would replace the hard-coded
Proof of Concept (WIP)
MessengerClient
export type EventsMap<
AllowedEvents extends EventConstraint,
Output = {
[Event in AllowedEvents as Event['type']]: EventSubscriptionMap<Event>;
},
> = Map<keyof Output & string, Output[keyof Output]>;
export class MessengerClient<
WalletComponentName extends string,
WalletComponentMessenger extends RestrictedControllerMessengerConstraint<WalletComponentName>,
> {
...
constructor({
name,
messenger,
eventSubscriptions = new Map()
}: {
name: WalletComponentName;
messenger: WalletComponentMessenger;
eventSubscriptions: EventsMap<EventConstraint>;
},
) {
this.name = name;
this.messagingSystem = messenger;
this.#registerEventSubscriptions(eventSubscriptions);
}
...
#registerEventSubscriptions(subscriptionsMap: EventsMap<EventConstraint>) {
for (const [eventType, subscriptions] of subscriptionsMap.entries()) {
for (const [handler, selector] of subscriptions.entries()) {
!selector
? this.messagingSystem.subscribe(eventType, handler)
: this.messagingSystem.subscribe(eventType, handler, selector);
}
}
}
Call Site: AssetsContractController
export class AssetsContractController extends MessengerClient<
typeof name,
AssetsContractControllerMessenger
> {
...
constructor({
messenger,
chainId: initialChainId,
eventSubscriptions = new Map([
[
`PreferencesController:stateChange` as const,
new Map([
(
...[
{ ipfsGateway },
_,
]: ExtractEventPayload<PreferencesControllerStateChangeEvent>
) => {
this.#ipfsGateway = ipfsGateway;
},
undefined,
]) as EventSubscriptionMap<PreferencesControllerStateChangeEvent>,
],
[
`NetworkController:networkDidChange` as const,
new Map([
(
...[
{ selectedNetworkClientId },
]: ExtractEventPayload<NetworkControllerNetworkDidChangeEvent>
) => {
const chainId = this.#getCorrectChainId(selectedNetworkClientId);
if (this.#chainId !== chainId) {
this.#chainId = chainId;
// @ts-expect-error TODO: remove this annotation once the `Eip1193Provider` class is released
this.#provider = this.#getCorrectProvider();
}
},
undefined,
]) as EventSubscriptionMap<NetworkControllerNetworkDidChangeEvent>,
],
]),
}: {
messenger: AssetsContractControllerMessenger;
chainId: Hex;
eventSubscriptions: EventsMap<AllowedEvents>;
}) {
super({
name,
messenger,
eventSubscriptions,
});
this.#provider = undefined;
this.#ipfsGateway = IPFS_DEFAULT_GATEWAY_URL;
this.#chainId = initialChainId;
}
...
}