Skip to content

[base-controller] Controller constructors accept messengers that do not allow any of its internal actions and/or events #4501

Open
@MajorLift

Description

@MajorLift

Problem

Constructor controllers currently raise a type error when instantiated with a messenger that was defined with an incomplete list of internal actions/events, but fail to raise that error if either the Action or Event type union of the messenger's parent ControllerMessenger doesn't contain any of the controller's internal actions/events.

Repro

/**
 * 1. Controller constructor erroneously accepts messenger if one or more of its internal message lists are "empty"
 */

/* A. Both internal message lists are empty */

const emptyInternalMessageListsControllerMessenger = new ControllerMessenger<
  // `TokenRatesControllerGetStateAction` is the only member of `TokenRatesControllerActions`
  Exclude<TokenRatesControllerActions, TokenRatesControllerGetStateAction> | TokenRatesControllerAllowedActions,
  // `TokenRatesControllerStateChangeEvent` is the only member of `TokenRatesControllerEvents`
  Exclude<TokenRatesControllerEvents, TokenRatesControllerStateChangeEvent> | TokenRatesControllerAllowedEvents
>()
  
new TokenRatesController({
  messenger: emptyInternalMessageListsControllerMessenger.getRestricted({
    name: 'TokenRatesController',
    allowedActions: [
      'TokensController:getState',
      'NetworkController:getNetworkClientById',
      'NetworkController:getState',
      'AccountsController:getAccount',
      'AccountsController:getSelectedAccount',
    ],
    allowedEvents: [
      'TokensController:stateChange',
      'NetworkController:stateChange',
      'AccountsController:selectedEvmAccountChange'
    ],
  }),
  tokenPricesService: buildMockTokenPricesService(),
})

/* B. Only one internal message list is empty */

const emptyInternalMessageListControllerMessenger = new ControllerMessenger<
  TokenRatesControllerActions | TokenRatesControllerAllowedActions,
  // `TokenRatesControllerStateChangeEvent` is the only member of `TokenRatesControllerEvents`
  Exclude<TokenRatesControllerEvents, TokenRatesControllerStateChangeEvent> | TokenRatesControllerAllowedEvents
>()
  
new TokenRatesController({
  messenger: emptyInternalMessageListControllerMessenger.getRestricted({
    name: 'TokenRatesController',
    allowedActions: [
      'TokensController:getState',
      'NetworkController:getNetworkClientById',
      'NetworkController:getState',
      'AccountsController:getAccount',
      'AccountsController:getSelectedAccount',
    ],
    allowedEvents: [
      'TokensController:stateChange',
      'NetworkController:stateChange',
      'AccountsController:selectedEvmAccountChange'
    ],
  }),
  tokenPricesService: buildMockTokenPricesService(),
})

// Argument of type '"TokenRatesController:getState"' is not assignable to parameter of type 
// '"TokensController:getState" | "NetworkController:getNetworkClientById" | "NetworkController:getState" | 
// "AccountsController:getAccount" | "AccountsController:getSelectedAccount"'.(2345)
emptyInternalMessageListsControllerMessenger.call('TokenRatesController:getState')
emptyInternalMessageListsControllerMessenger.call('NetworkController:getState')

// Argument of type '"TokenRatesController:stateChange"' is not assignable to parameter of type 
// '"TokensController:stateChange" | "NetworkController:stateChange" | "AccountsController:selectedEvmAccountChange"'.(2345)
emptyInternalMessageListControllerMessenger.subscribe('TokenRatesController:stateChange', [])


/**
 * 2. Controller constructor raises error when passed messenger with "incomplete" internal message lists
 */

const incompleteInternalMessageListControllerMessenger = new ControllerMessenger<
  | Extract<
      AccountsControllerActions, 
      | AccountsControllerGetAccountAction 
      | AccountsControllerGetSelectedAccountAction
    > 
  | AccountsControllerAllowedActions,
  | Extract<
      AccountsControllerEvents,
      AccountsControllerSelectedEvmAccountChangeEvent
    > 
  | AccountsControllerAllowedEvents
>()

new AccountsController({
//   Type 'ControllerMessenger<AccountsControllerGetAccountAction | AccountsControllerGetSelectedAccountAction 
// | AccountsControllerAllowedActions, AccountsControllerSelectedEvmAccountChangeEvent | AccountsControllerAllowedEvents>' 
// is not assignable to type 'AccountsControllerMessenger'.
//   Property '#private' in type 'ControllerMessenger' refers to a different member 
// that cannot be accessed from within type 'RestrictedControllerMessenger'.(2322)
// AccountsController.d.ts(102, 9): The expected type comes from property 'messenger' 
// which is declared here on type '{ messenger: AccountsControllerMessenger; state: AccountsControllerState; }'
  messenger: incompleteInternalMessageListControllerMessenger,
  state: {
    internalAccounts: {
      accounts: {},
      selectedAccount: '',
    },
  },
})

playground link

Acceptance Criteria

Controllers inheriting from BaseControllerV2 must raise a type and/or runtime error if they are initialized with a messenger that is derived from an unrestricted ControllerMessenger that does not contain any of the controller's internal actions and/or events.

References

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions