core icon indicating copy to clipboard operation
core copied to clipboard

[base-controller] Implement utility function `registerEventSubscriptions` for batch registering all internal events of a messenger client class

Open MajorLift opened this issue 1 year ago • 0 comments

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 EventSubscriptionMap of its subscriptions (and associated handlers, selectors).
    • This would replace the hard-coded subscribe calls with flexible input data.
    • Consumers could be optionally allowed to override the default EventSubscriptionMap argument, 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.

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;
  }
  ...
}

MajorLift avatar Sep 19 '24 17:09 MajorLift