diff --git a/packages/permission-controller/CHANGELOG.md b/packages/permission-controller/CHANGELOG.md index c40ec7ea188..29ef7ac679b 100644 --- a/packages/permission-controller/CHANGELOG.md +++ b/packages/permission-controller/CHANGELOG.md @@ -7,6 +7,47 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Expose missing public `PermissionController` methods through its messenger ([#8201](https://github.com/MetaMask/core/pull/8201)) + - The following actions are now available: + - `PermissionController:clearState` + - Corresponding action types (e.g. `PermissionControllerClearStateAction`) are + available as well. +- Expose missing public `SubjectMetadataController` methods through its messenger ([#8201](https://github.com/MetaMask/core/pull/8201)) + - The following actions are now available: + - `SubjectMetadataController:clearState` + - `SubjectMetadataController:trimMetadataState` + - Corresponding action types + (e.g. `SubjectMetadataControllerClearStateAction`) are available as well. + +### Changed + +- Deprecate action types in favor of `PermissionController...Action` and `SubjectMetadataController...Action` types ([#8201](https://github.com/MetaMask/core/pull/8201)) + - For the `PermissionController`: + - `GetPermissionControllerState` is now `PermissionControllerGetStateAction`. + - `GetSubjects` is now `PermissionControllerGetSubjectsAction`. + - `GetPermissions` is now `PermissionControllerGetPermissionsAction`. + - `HasPermissions` is now `PermissionControllerHasPermissionsAction`. + - `HasPermission` is now `PermissionControllerHasPermissionAction`. + - `GrantPermissions` is now `PermissionControllerGrantPermissionsAction`. + - `GrantPermissionsIncremental` is now `PermissionControllerGrantPermissionsIncrementalAction`. + - `RequestPermissions` is now `PermissionControllerRequestPermissionsAction`. + - `RequestPermissionsIncremental` is now `PermissionControllerRequestPermissionsIncrementalAction`. + - `RevokePermissions` is now `PermissionControllerRevokePermissionsAction`. + - `RevokeAllPermissions` is now `PermissionControllerRevokeAllPermissionsAction`. + - `RevokePermissionForAllSubjects` is now `PermissionControllerRevokePermissionForAllSubjectsAction`. + - `UpdateCaveat` is now `PermissionControllerUpdateCaveatAction`. + - `GetCaveat` is now `PermissionControllerGetCaveatAction`. + - `ClearPermissions` is now `PermissionControllerClearPermissionsAction`. + - `GetEndowments` is now `PermissionControllerGetEndowmentsAction`. + - For the `SubjectMetadataController`: + - `GetSubjectMetadataControllerState` is now `SubjectMetadataControllerGetStateAction`. + - `GetSubjectMetadata` is now `SubjectMetadataControllerGetMetadataAction`. + - `AddSubjectMetadata` is now `SubjectMetadataControllerAddMetadataAction`. + - The old types are still exported but are now marked as deprecated and will + be removed in a future release. + ## [12.2.1] ### Changed diff --git a/packages/permission-controller/package.json b/packages/permission-controller/package.json index 74ad2adad49..352abd393b3 100644 --- a/packages/permission-controller/package.json +++ b/packages/permission-controller/package.json @@ -40,6 +40,7 @@ "build:docs": "typedoc", "changelog:update": "../../scripts/update-changelog.sh @metamask/permission-controller", "changelog:validate": "../../scripts/validate-changelog.sh @metamask/permission-controller", + "generate-method-action-types": "tsx ../../scripts/generate-method-action-types.ts", "since-latest-release": "../../scripts/since-latest-release.sh", "test": "NODE_OPTIONS=--experimental-vm-modules jest --reporters=jest-silent-reporter", "test:clean": "NODE_OPTIONS=--experimental-vm-modules jest --clearCache", @@ -66,6 +67,7 @@ "deepmerge": "^4.2.2", "jest": "^29.7.0", "ts-jest": "^29.2.5", + "tsx": "^4.20.5", "typedoc": "^0.25.13", "typedoc-plugin-missing-exports": "^2.0.0", "typescript": "~5.3.3" diff --git a/packages/permission-controller/src/PermissionController-method-action-types.ts b/packages/permission-controller/src/PermissionController-method-action-types.ts new file mode 100644 index 00000000000..b434f719e89 --- /dev/null +++ b/packages/permission-controller/src/PermissionController-method-action-types.ts @@ -0,0 +1,290 @@ +/** + * This file is auto generated by `scripts/generate-method-action-types.ts`. + * Do not edit manually. + */ + +import type { PermissionController } from './PermissionController'; + +/** + * Clears the state of the controller. + */ +export type PermissionControllerClearStateAction = { + type: `PermissionController:clearState`; + handler: PermissionController['clearState']; +}; + +/** + * Gets a list of all origins of subjects. + * + * @returns The origins (i.e. IDs) of all subjects. + */ +export type PermissionControllerGetSubjectNamesAction = { + type: `PermissionController:getSubjectNames`; + handler: PermissionController['getSubjectNames']; +}; + +/** + * Gets all permissions for the specified subject, if any. + * + * @param origin - The origin of the subject. + * @returns The permissions of the subject, if any. + */ +export type PermissionControllerGetPermissionsAction = { + type: `PermissionController:getPermissions`; + handler: PermissionController['getPermissions']; +}; + +/** + * Checks whether the subject with the specified origin has the specified + * permission. + * + * @param origin - The origin of the subject. + * @param target - The target name of the permission. + * @returns Whether the subject has the permission. + */ +export type PermissionControllerHasPermissionAction = { + type: `PermissionController:hasPermission`; + handler: PermissionController['hasPermission']; +}; + +/** + * Checks whether the subject with the specified origin has any permissions. + * Use this if you want to know if a subject "exists". + * + * @param origin - The origin of the subject to check. + * @returns Whether the subject has any permissions. + */ +export type PermissionControllerHasPermissionsAction = { + type: `PermissionController:hasPermissions`; + handler: PermissionController['hasPermissions']; +}; + +/** + * Revokes all permissions from the specified origin. + * + * Throws an error of the origin has no permissions. + * + * @param origin - The origin whose permissions to revoke. + */ +export type PermissionControllerRevokeAllPermissionsAction = { + type: `PermissionController:revokeAllPermissions`; + handler: PermissionController['revokeAllPermissions']; +}; + +/** + * Revokes the specified permissions from the specified subjects. + * + * Throws an error if any of the subjects or permissions do not exist. + * + * @param subjectsAndPermissions - An object mapping subject origins + * to arrays of permission target names to revoke. + */ +export type PermissionControllerRevokePermissionsAction = { + type: `PermissionController:revokePermissions`; + handler: PermissionController['revokePermissions']; +}; + +/** + * Revokes all permissions corresponding to the specified target for all subjects. + * Does nothing if no subjects or no such permission exists. + * + * @param target - The name of the target to revoke all permissions for. + */ +export type PermissionControllerRevokePermissionForAllSubjectsAction = { + type: `PermissionController:revokePermissionForAllSubjects`; + handler: PermissionController['revokePermissionForAllSubjects']; +}; + +/** + * Gets the caveat of the specified type, if any, for the permission of + * the subject corresponding to the given origin. + * + * Throws an error if the subject does not have a permission with the + * specified target name. + * + * @template TargetName - The permission target name. Should be inferred. + * @template CaveatType - The valid caveat types for the permission. Should + * be inferred. + * @param origin - The origin of the subject. + * @param target - The target name of the permission. + * @param caveatType - The type of the caveat to get. + * @returns The caveat, or `undefined` if no such caveat exists. + */ +export type PermissionControllerGetCaveatAction = { + type: `PermissionController:getCaveat`; + handler: PermissionController['getCaveat']; +}; + +/** + * Updates the value of the caveat of the specified type belonging to the + * permission corresponding to the given subject origin and permission + * target. + * + * For adding new caveats, use + * {@link PermissionController.addCaveat}. + * + * Throws an error if no such permission or caveat exists. + * + * @template TargetName - The permission target name. Should be inferred. + * @template CaveatType - The valid caveat types for the permission. Should + * be inferred. + * @param origin - The origin of the subject. + * @param target - The target name of the permission. + * @param caveatType - The type of the caveat to update. + * @param caveatValue - The new value of the caveat. + */ +export type PermissionControllerUpdateCaveatAction = { + type: `PermissionController:updateCaveat`; + handler: PermissionController['updateCaveat']; +}; + +/** + * Grants _approved_ permissions to the specified subject. Every permission and + * caveat is stringently validated—including by calling their specification + * validators—and an error is thrown if validation fails. + * + * ATTN: This method does **not** prompt the user for approval. User consent must + * first be obtained through some other means. + * + * @see {@link PermissionController.requestPermissions} For initiating a + * permissions request requiring user approval. + * @param options - Options bag. + * @param options.approvedPermissions - The requested permissions approved by + * the user. + * @param options.requestData - Permission request data. Passed to permission + * factory functions. + * @param options.preserveExistingPermissions - Whether to preserve the + * subject's existing permissions. + * @param options.subject - The subject to grant permissions to. + * @returns The subject's new permission state. It may or may not have changed. + */ +export type PermissionControllerGrantPermissionsAction = { + type: `PermissionController:grantPermissions`; + handler: PermissionController['grantPermissions']; +}; + +/** + * Incrementally grants _approved_ permissions to the specified subject. Every + * permission and caveat is stringently validated—including by calling their + * specification validators—and an error is thrown if validation fails. + * + * ATTN: This method does **not** prompt the user for approval. User consent must + * first be obtained through some other means. + * + * @see {@link PermissionController.requestPermissionsIncremental} For initiating + * an incremental permissions request requiring user approval. + * @param options - Options bag. + * @param options.approvedPermissions - The requested permissions approved by + * the user. + * @param options.requestData - Permission request data. Passed to permission + * factory functions. + * @param options.subject - The subject to grant permissions to. + * @returns The subject's new permission state. It may or may not have changed. + */ +export type PermissionControllerGrantPermissionsIncrementalAction = { + type: `PermissionController:grantPermissionsIncremental`; + handler: PermissionController['grantPermissionsIncremental']; +}; + +/** + * Initiates a permission request that requires user approval. + * + * Either this or {@link PermissionController.requestPermissionsIncremental} + * should always be used to grant additional permissions to a subject, + * unless user approval has been obtained through some other means. + * + * Permissions are validated at every step of the approval process, and this + * method will reject if validation fails. + * + * @see {@link ApprovalController} For the user approval logic. + * @see {@link PermissionController.acceptPermissionsRequest} For the method + * that _accepts_ the request and resolves the user approval promise. + * @see {@link PermissionController.rejectPermissionsRequest} For the method + * that _rejects_ the request and the user approval promise. + * @param subject - The grantee subject. + * @param requestedPermissions - The requested permissions. + * @param options - Additional options. + * @param options.id - The id of the permissions request. Defaults to a unique + * id. + * @param options.preserveExistingPermissions - Whether to preserve the + * subject's existing permissions. Defaults to `true`. + * @param options.metadata - Additional metadata about the permission request. + * @returns The granted permissions and request metadata. + */ +export type PermissionControllerRequestPermissionsAction = { + type: `PermissionController:requestPermissions`; + handler: PermissionController['requestPermissions']; +}; + +/** + * Initiates an incremental permission request that prompts for user approval. + * Incremental permission requests allow the caller to replace existing and/or + * add brand new permissions and caveats for the specified subject. + * + * Incremental permission request are merged with the subject's existing permissions + * through a right-biased union, where the incremental permission are the right-hand + * side of the merger. If both sides of the merger specify the same caveats for a + * given permission, the caveats are merged using their specification's caveat value + * merger property. + * + * Either this or {@link PermissionController.requestPermissions} should + * always be used to grant additional permissions to a subject, unless user + * approval has been obtained through some other means. + * + * Permissions are validated at every step of the approval process, and this + * method will reject if validation fails. + * + * @see {@link ApprovalController} For the user approval logic. + * @see {@link PermissionController.acceptPermissionsRequest} For the method + * that _accepts_ the request and resolves the user approval promise. + * @see {@link PermissionController.rejectPermissionsRequest} For the method + * that _rejects_ the request and the user approval promise. + * @param subject - The grantee subject. + * @param requestedPermissions - The requested permissions. + * @param options - Additional options. + * @param options.id - The id of the permissions request. Defaults to a unique + * id. + * @param options.metadata - Additional metadata about the permission request. + * @returns The granted permissions and request metadata. + */ +export type PermissionControllerRequestPermissionsIncrementalAction = { + type: `PermissionController:requestPermissionsIncremental`; + handler: PermissionController['requestPermissionsIncremental']; +}; + +/** + * Gets the subject's endowments per the specified endowment permission. + * Throws if the subject does not have the required permission or if the + * permission is not an endowment permission. + * + * @param origin - The origin of the subject whose endowments to retrieve. + * @param targetName - The name of the endowment permission. This must be a + * valid permission target name. + * @param requestData - Additional data associated with the request, if any. + * Forwarded to the endowment getter function for the permission. + * @returns The endowments, if any. + */ +export type PermissionControllerGetEndowmentsAction = { + type: `PermissionController:getEndowments`; + handler: PermissionController['getEndowments']; +}; + +/** + * Union of all PermissionController action types. + */ +export type PermissionControllerMethodActions = + | PermissionControllerClearStateAction + | PermissionControllerGetSubjectNamesAction + | PermissionControllerGetPermissionsAction + | PermissionControllerHasPermissionAction + | PermissionControllerHasPermissionsAction + | PermissionControllerRevokeAllPermissionsAction + | PermissionControllerRevokePermissionsAction + | PermissionControllerRevokePermissionForAllSubjectsAction + | PermissionControllerGetCaveatAction + | PermissionControllerUpdateCaveatAction + | PermissionControllerGrantPermissionsAction + | PermissionControllerGrantPermissionsIncrementalAction + | PermissionControllerRequestPermissionsAction + | PermissionControllerRequestPermissionsIncrementalAction + | PermissionControllerGetEndowmentsAction; diff --git a/packages/permission-controller/src/PermissionController.test.ts b/packages/permission-controller/src/PermissionController.test.ts index a8ea599ad20..b7f7460f632 100644 --- a/packages/permission-controller/src/PermissionController.test.ts +++ b/packages/permission-controller/src/PermissionController.test.ts @@ -5635,8 +5635,10 @@ describe('PermissionController', () => { }); describe('controller actions', () => { - it('action: PermissionController:clearPermissions', () => { + it('action: PermissionController:clearState', () => { const messenger = getRootMessenger(); + jest.spyOn(messenger, 'call'); + const options = getPermissionControllerOptions({ messenger: getPermissionControllerMessenger(messenger), }); @@ -5644,7 +5646,6 @@ describe('PermissionController', () => { DefaultPermissionSpecifications, DefaultCaveatSpecifications >(options); - const clearStateSpy = jest.spyOn(controller, 'clearState'); controller.grantPermissions({ subject: { origin: 'foo' }, @@ -5655,13 +5656,19 @@ describe('PermissionController', () => { expect(hasProperty(controller.state.subjects, 'foo')).toBe(true); - messenger.call('PermissionController:clearPermissions'); - expect(clearStateSpy).toHaveBeenCalledTimes(1); + messenger.call('PermissionController:clearState'); + expect(messenger.call).toHaveBeenCalledTimes(1); + expect(messenger.call).toHaveBeenNthCalledWith( + 1, + 'PermissionController:clearState', + ); expect(controller.state).toStrictEqual({ subjects: {} }); }); it('action: PermissionController:getEndowments', async () => { const messenger = getRootMessenger(); + jest.spyOn(messenger, 'call'); + const options = getPermissionControllerOptions({ messenger: getPermissionControllerMessenger(messenger), }); @@ -5669,7 +5676,6 @@ describe('PermissionController', () => { DefaultPermissionSpecifications, DefaultCaveatSpecifications >(options); - const getEndowmentsSpy = jest.spyOn(controller, 'getEndowments'); await expect( messenger.call( @@ -5710,23 +5716,24 @@ describe('PermissionController', () => { ), ).toStrictEqual(['endowment1']); - expect(getEndowmentsSpy).toHaveBeenCalledTimes(3); - expect(getEndowmentsSpy).toHaveBeenNthCalledWith( + expect(messenger.call).toHaveBeenCalledTimes(3); + expect(messenger.call).toHaveBeenNthCalledWith( 1, + 'PermissionController:getEndowments', 'foo', PermissionNames.endowmentAnySubject, - undefined, ); - expect(getEndowmentsSpy).toHaveBeenNthCalledWith( + expect(messenger.call).toHaveBeenNthCalledWith( 2, + 'PermissionController:getEndowments', 'foo', PermissionNames.endowmentAnySubject, - undefined, ); - expect(getEndowmentsSpy).toHaveBeenNthCalledWith( + expect(messenger.call).toHaveBeenNthCalledWith( 3, + 'PermissionController:getEndowments', 'foo', PermissionNames.endowmentAnySubject, { arbitrary: 'requestData' }, @@ -5735,6 +5742,8 @@ describe('PermissionController', () => { it('action: PermissionController:getSubjectNames', () => { const messenger = getRootMessenger(); + jest.spyOn(messenger, 'call'); + const options = getPermissionControllerOptions({ messenger: getPermissionControllerMessenger(messenger), }); @@ -5742,7 +5751,6 @@ describe('PermissionController', () => { DefaultPermissionSpecifications, DefaultCaveatSpecifications >(options); - const getSubjectNamesSpy = jest.spyOn(controller, 'getSubjectNames'); expect( messenger.call('PermissionController:getSubjectNames'), @@ -5758,11 +5766,21 @@ describe('PermissionController', () => { expect( messenger.call('PermissionController:getSubjectNames'), ).toStrictEqual(['foo']); - expect(getSubjectNamesSpy).toHaveBeenCalledTimes(2); + expect(messenger.call).toHaveBeenCalledTimes(2); + expect(messenger.call).toHaveBeenNthCalledWith( + 1, + 'PermissionController:getSubjectNames', + ); + expect(messenger.call).toHaveBeenNthCalledWith( + 2, + 'PermissionController:getSubjectNames', + ); }); it('action: PermissionController:hasPermission', () => { const messenger = getRootMessenger(); + jest.spyOn(messenger, 'call'); + const options = getPermissionControllerOptions({ messenger: getPermissionControllerMessenger(messenger), }); @@ -5770,7 +5788,6 @@ describe('PermissionController', () => { DefaultPermissionSpecifications, DefaultCaveatSpecifications >(options); - const hasPermissionSpy = jest.spyOn(controller, 'hasPermission'); expect( messenger.call( @@ -5803,21 +5820,24 @@ describe('PermissionController', () => { ), ).toBe(false); - expect(hasPermissionSpy).toHaveBeenCalledTimes(3); - expect(hasPermissionSpy).toHaveBeenNthCalledWith( + expect(messenger.call).toHaveBeenCalledTimes(3); + expect(messenger.call).toHaveBeenNthCalledWith( 1, + 'PermissionController:hasPermission', 'foo', PermissionNames.wallet_getSecretArray, ); - expect(hasPermissionSpy).toHaveBeenNthCalledWith( + expect(messenger.call).toHaveBeenNthCalledWith( 2, + 'PermissionController:hasPermission', 'foo', PermissionNames.wallet_getSecretArray, ); - expect(hasPermissionSpy).toHaveBeenNthCalledWith( + expect(messenger.call).toHaveBeenNthCalledWith( 3, + 'PermissionController:hasPermission', 'foo', PermissionNames.wallet_getSecretObject, ); @@ -5825,6 +5845,8 @@ describe('PermissionController', () => { it('action: PermissionController:hasPermissions', () => { const messenger = getRootMessenger(); + jest.spyOn(messenger, 'call'); + const options = getPermissionControllerOptions({ messenger: getPermissionControllerMessenger(messenger), }); @@ -5832,7 +5854,6 @@ describe('PermissionController', () => { DefaultPermissionSpecifications, DefaultCaveatSpecifications >(options); - const hasPermissionsSpy = jest.spyOn(controller, 'hasPermissions'); expect(messenger.call('PermissionController:hasPermissions', 'foo')).toBe( false, @@ -5848,13 +5869,23 @@ describe('PermissionController', () => { expect(messenger.call('PermissionController:hasPermissions', 'foo')).toBe( true, ); - expect(hasPermissionsSpy).toHaveBeenCalledTimes(2); - expect(hasPermissionsSpy).toHaveBeenNthCalledWith(1, 'foo'); - expect(hasPermissionsSpy).toHaveBeenNthCalledWith(2, 'foo'); + expect(messenger.call).toHaveBeenCalledTimes(2); + expect(messenger.call).toHaveBeenNthCalledWith( + 1, + 'PermissionController:hasPermissions', + 'foo', + ); + expect(messenger.call).toHaveBeenNthCalledWith( + 2, + 'PermissionController:hasPermissions', + 'foo', + ); }); it('action: PermissionController:getPermissions', () => { const messenger = getRootMessenger(); + jest.spyOn(messenger, 'call'); + const options = getPermissionControllerOptions({ messenger: getPermissionControllerMessenger(messenger), }); @@ -5862,7 +5893,6 @@ describe('PermissionController', () => { DefaultPermissionSpecifications, DefaultCaveatSpecifications >(options); - const getPermissionsSpy = jest.spyOn(controller, 'getPermissions'); expect( messenger.call('PermissionController:getPermissions', 'foo'), @@ -5882,13 +5912,23 @@ describe('PermissionController', () => { ), ).toStrictEqual(['wallet_getSecretArray']); - expect(getPermissionsSpy).toHaveBeenCalledTimes(3); - expect(getPermissionsSpy).toHaveBeenNthCalledWith(1, 'foo'); - expect(getPermissionsSpy).toHaveBeenNthCalledWith(2, 'foo'); + expect(messenger.call).toHaveBeenCalledTimes(2); + expect(messenger.call).toHaveBeenNthCalledWith( + 1, + 'PermissionController:getPermissions', + 'foo', + ); + expect(messenger.call).toHaveBeenNthCalledWith( + 2, + 'PermissionController:getPermissions', + 'foo', + ); }); it('action: PermissionController:revokeAllPermissions', () => { const messenger = getRootMessenger(); + jest.spyOn(messenger, 'call'); + const options = getPermissionControllerOptions({ messenger: getPermissionControllerMessenger(messenger), }); @@ -5903,10 +5943,6 @@ describe('PermissionController', () => { wallet_getSecretArray: {}, }, }); - const revokeAllPermissionsSpy = jest.spyOn( - controller, - 'revokeAllPermissions', - ); expect(controller.hasPermission('foo', 'wallet_getSecretArray')).toBe( true, @@ -5917,12 +5953,18 @@ describe('PermissionController', () => { expect(controller.hasPermission('foo', 'wallet_getSecretArray')).toBe( false, ); - expect(revokeAllPermissionsSpy).toHaveBeenCalledTimes(1); - expect(revokeAllPermissionsSpy).toHaveBeenNthCalledWith(1, 'foo'); + expect(messenger.call).toHaveBeenCalledTimes(1); + expect(messenger.call).toHaveBeenNthCalledWith( + 1, + 'PermissionController:revokeAllPermissions', + 'foo', + ); }); it('action: PermissionController:revokePermissionForAllSubjects', () => { const messenger = getRootMessenger(); + jest.spyOn(messenger, 'call'); + const options = getPermissionControllerOptions({ messenger: getPermissionControllerMessenger(messenger), }); @@ -5937,10 +5979,6 @@ describe('PermissionController', () => { wallet_getSecretArray: {}, }, }); - const revokePermissionForAllSubjectsSpy = jest.spyOn( - controller, - 'revokePermissionForAllSubjects', - ); expect(controller.hasPermission('foo', 'wallet_getSecretArray')).toBe( true, @@ -5954,9 +5992,10 @@ describe('PermissionController', () => { expect(controller.hasPermission('foo', 'wallet_getSecretArray')).toBe( false, ); - expect(revokePermissionForAllSubjectsSpy).toHaveBeenCalledTimes(1); - expect(revokePermissionForAllSubjectsSpy).toHaveBeenNthCalledWith( + expect(messenger.call).toHaveBeenCalledTimes(1); + expect(messenger.call).toHaveBeenNthCalledWith( 1, + 'PermissionController:revokePermissionForAllSubjects', 'wallet_getSecretArray', ); }); @@ -6011,18 +6050,19 @@ describe('PermissionController', () => { const options = getPermissionControllerOptions({ messenger: getPermissionControllerMessenger(messenger), }); - const controller = new PermissionController< + + // eslint-disable-next-line no-new + new PermissionController< DefaultPermissionSpecifications, DefaultCaveatSpecifications >(options); - // requestPermissions calls unregistered action ApprovalController:addRequest that - // can't be easily mocked, thus we mock the whole implementation - const requestPermissionsSpy = jest - .spyOn(controller, 'requestPermissions') - .mockImplementation(); + messenger.registerActionHandler( + 'ApprovalController:addRequest', + async ({ requestData }) => requestData, + ); - await messenger.call( + const [result] = await messenger.call( 'PermissionController:requestPermissions', { origin: 'foo' }, { @@ -6030,26 +6070,29 @@ describe('PermissionController', () => { }, ); - expect(requestPermissionsSpy).toHaveBeenCalledTimes(1); + expect(result).toHaveProperty('wallet_getSecretArray'); }); it('action: PermissionController:requestPermissionsIncremental', async () => { const messenger = getRootMessenger(); + jest.spyOn(messenger, 'call'); + const options = getPermissionControllerOptions({ messenger: getPermissionControllerMessenger(messenger), }); - const controller = new PermissionController< + + // eslint-disable-next-line no-new + new PermissionController< DefaultPermissionSpecifications, DefaultCaveatSpecifications >(options); - // requestPermissionsIncremental calls unregistered action ApprovalController:addRequest - // that can't be easily mocked, thus we mock the whole implementation. - const requestPermissionsIncrementalSpy = jest - .spyOn(controller, 'requestPermissionsIncremental') - .mockImplementation(); + messenger.registerActionHandler( + 'ApprovalController:addRequest', + async ({ requestData }) => requestData, + ); - await messenger.call( + const [result] = await messenger.call( 'PermissionController:requestPermissionsIncremental', { origin: 'foo' }, { @@ -6057,11 +6100,18 @@ describe('PermissionController', () => { }, ); - expect(requestPermissionsIncrementalSpy).toHaveBeenCalledTimes(1); + expect(result).toHaveProperty('wallet_getSecretArray'); + expect(messenger.call).toHaveBeenCalledWith( + 'PermissionController:requestPermissionsIncremental', + { origin: 'foo' }, + { wallet_getSecretArray: {} }, + ); }); it('action: PermissionController:updateCaveat', async () => { const messenger = getRootMessenger(); + jest.spyOn(messenger, 'call'); + const state = { subjects: { 'metamask.io': { @@ -6090,8 +6140,6 @@ describe('PermissionController', () => { DefaultCaveatSpecifications >(options); - const updateCaveatSpy = jest.spyOn(controller, 'updateCaveat'); - messenger.call( 'PermissionController:updateCaveat', 'metamask.io', @@ -6100,7 +6148,15 @@ describe('PermissionController', () => { ['baz'], ); - expect(updateCaveatSpy).toHaveBeenCalledTimes(1); + expect(messenger.call).toHaveBeenCalledTimes(1); + expect(messenger.call).toHaveBeenNthCalledWith( + 1, + 'PermissionController:updateCaveat', + 'metamask.io', + 'wallet_getSecretArray', + CaveatTypes.filterArrayResponse, + ['baz'], + ); expect(controller.state).toStrictEqual({ subjects: { 'metamask.io': { diff --git a/packages/permission-controller/src/PermissionController.ts b/packages/permission-controller/src/PermissionController.ts index 098d3017c97..ee5c7f4a647 100644 --- a/packages/permission-controller/src/PermissionController.ts +++ b/packages/permission-controller/src/PermissionController.ts @@ -94,7 +94,8 @@ import { PermissionType, } from './Permission'; import { getPermissionMiddlewareFactory } from './permission-middleware'; -import type { GetSubjectMetadata } from './SubjectMetadataController'; +import type { PermissionControllerMethodActions } from './PermissionController-method-action-types'; +import type { SubjectMetadataControllerGetSubjectMetadataAction } from './SubjectMetadataController-method-action-types'; import { collectUniqueAndPairedCaveats, MethodNames } from './utils'; /** @@ -173,6 +174,24 @@ export type SideEffects = { */ const controllerName = 'PermissionController'; +const MESSENGER_EXPOSED_METHODS = [ + 'clearState', + 'getEndowments', + 'getSubjectNames', + 'getPermissions', + 'hasPermission', + 'hasPermissions', + 'grantPermissions', + 'grantPermissionsIncremental', + 'requestPermissions', + 'requestPermissionsIncremental', + 'revokeAllPermissions', + 'revokePermissionForAllSubjects', + 'revokePermissions', + 'updateCaveat', + 'getCaveat', +] as const; + /** * Permissions associated with a {@link PermissionController} subject. */ @@ -248,13 +267,22 @@ function getDefaultState< /** * Gets the state of the {@link PermissionController}. */ -export type GetPermissionControllerState = ControllerGetStateAction< +export type PermissionControllerGetStateAction = ControllerGetStateAction< typeof controllerName, PermissionControllerState >; +/** + * Gets the state of the {@link PermissionController}. + * + * @deprecated Use `PermissionControllerGetStateAction` instead. + */ +export type GetPermissionControllerState = PermissionControllerGetStateAction; + /** * Gets the names of all subjects from the {@link PermissionController}. + * + * @deprecated Use `PermissionControllerGetSubjectNamesAction` instead. */ export type GetSubjects = { type: `${typeof controllerName}:getSubjectNames`; @@ -262,7 +290,9 @@ export type GetSubjects = { }; /** - * Gets the permissions for specified subject + * Gets the permissions for specified subject. + * + * @deprecated Use `PermissionControllerGetPermissionsAction` instead. */ export type GetPermissions = { type: `${typeof controllerName}:getPermissions`; @@ -271,6 +301,8 @@ export type GetPermissions = { /** * Checks whether the specified subject has any permissions. + * + * @deprecated Use `PermissionControllerHasPermissionAction` instead. */ export type HasPermissions = { type: `${typeof controllerName}:hasPermissions`; @@ -279,6 +311,8 @@ export type HasPermissions = { /** * Checks whether the specified subject has a specific permission. + * + * @deprecated Use `PermissionControllerHasPermissionAction` instead. */ export type HasPermission = { type: `${typeof controllerName}:hasPermission`; @@ -286,7 +320,9 @@ export type HasPermission = { }; /** - * Directly grants given permissions for a specified origin without requesting user approval + * Directly grants given permissions for a specified origin without requesting user approval. + * + * @deprecated Use `PermissionControllerGrantPermissionsAction` instead. */ export type GrantPermissions = { type: `${typeof controllerName}:grantPermissions`; @@ -294,7 +330,10 @@ export type GrantPermissions = { }; /** - * Directly grants given permissions for a specified origin without requesting user approval + * Directly grants given permissions for a specified origin without requesting user approval. + * + * @deprecated Use `PermissionControllerGrantPermissionsIncrementalAction` + * instead. */ export type GrantPermissionsIncremental = { type: `${typeof controllerName}:grantPermissionsIncremental`; @@ -302,7 +341,9 @@ export type GrantPermissionsIncremental = { }; /** - * Requests given permissions for a specified origin + * Requests given permissions for a specified origin. + * + * @deprecated Use `PermissionControllerRequestPermissionsAction` instead. */ export type RequestPermissions = { type: `${typeof controllerName}:requestPermissions`; @@ -310,7 +351,9 @@ export type RequestPermissions = { }; /** - * Requests given permissions for a specified origin + * Requests given permissions for a specified origin. + * + * @deprecated Use `PermissionControllerRequestPermissionsAction` instead. */ export type RequestPermissionsIncremental = { type: `${typeof controllerName}:requestPermissionsIncremental`; @@ -319,6 +362,8 @@ export type RequestPermissionsIncremental = { /** * Removes the specified permissions for each origin. + * + * @deprecated Use `PermissionControllerRevokePermissionsAction` instead. */ export type RevokePermissions = { type: `${typeof controllerName}:revokePermissions`; @@ -326,7 +371,9 @@ export type RevokePermissions = { }; /** - * Removes all permissions for a given origin + * Removes all permissions for a given origin. + * + * @deprecated Use `PermissionControllerRevokeAllPermissionAction` instead. */ export type RevokeAllPermissions = { type: `${typeof controllerName}:revokeAllPermissions`; @@ -336,6 +383,9 @@ export type RevokeAllPermissions = { /** * Revokes all permissions corresponding to the specified target for all subjects. * Does nothing if no subjects or no such permission exists. + * + * @deprecated Use `PermissionControllerRevokePermissionForAllSubjectsAction` + * instead. */ export type RevokePermissionForAllSubjects = { type: `${typeof controllerName}:revokePermissionForAllSubjects`; @@ -344,6 +394,8 @@ export type RevokePermissionForAllSubjects = { /** * Updates a caveat value for a specified caveat type belonging to a specific target and origin. + * + * @deprecated Use `PermissionControllerUpdateCaveatAction` instead. */ export type UpdateCaveat = { type: `${typeof controllerName}:updateCaveat`; @@ -352,6 +404,8 @@ export type UpdateCaveat = { /** * Get a caveat value for a specified caveat type belonging to a specific target and origin. + * + * @deprecated Use `PermissionControllerGetCaveatAction` instead. */ export type GetCaveat = { type: `${typeof controllerName}:getCaveat`; @@ -360,6 +414,8 @@ export type GetCaveat = { /** * Clears all permissions from the {@link PermissionController}. + * + * @deprecated Use `PermissionControllerClearStateAction` instead. */ export type ClearPermissions = { type: `${typeof controllerName}:clearPermissions`; @@ -368,6 +424,8 @@ export type ClearPermissions = { /** * Gets the endowments for the given subject and permission. + * + * @deprecated Use `PermissionControllerGetEndowmentsAction` instead. */ export type GetEndowments = { type: `${typeof controllerName}:getEndowments`; @@ -378,22 +436,8 @@ export type GetEndowments = { * The {@link Messenger} actions of the {@link PermissionController}. */ export type PermissionControllerActions = - | ClearPermissions - | GetEndowments - | GetPermissionControllerState - | GetSubjects - | GetPermissions - | HasPermission - | HasPermissions - | GrantPermissions - | GrantPermissionsIncremental - | RequestPermissions - | RequestPermissionsIncremental - | RevokeAllPermissions - | RevokePermissionForAllSubjects - | RevokePermissions - | UpdateCaveat - | GetCaveat; + | PermissionControllerGetStateAction + | PermissionControllerMethodActions; /** * The generic state change event of the {@link PermissionController}. @@ -421,7 +465,7 @@ type AllowedActions = | ApprovalControllerHasRequestAction | ApprovalControllerAcceptRequestAction | ApprovalControllerRejectRequestAction - | GetSubjectMetadata; + | SubjectMetadataControllerGetSubjectMetadataAction; /** * The messenger of the {@link PermissionController}. @@ -587,8 +631,10 @@ export type PermissionControllerOptions< * caveat specifications available to the controller. */ export class PermissionController< - ControllerPermissionSpecification extends PermissionSpecificationConstraint, - ControllerCaveatSpecification extends CaveatSpecificationConstraint, + ControllerPermissionSpecification extends + PermissionSpecificationConstraint = PermissionSpecificationConstraint, + ControllerCaveatSpecification extends + CaveatSpecificationConstraint = CaveatSpecificationConstraint, > extends BaseController< typeof controllerName, PermissionControllerState< @@ -694,7 +740,11 @@ export class PermissionController< ...permissionSpecifications, }); - this.#registerMessageHandlers(); + this.messenger.registerMethodActionHandlers( + this, + MESSENGER_EXPOSED_METHODS, + ); + this.createPermissionMiddleware = getPermissionMiddlewareFactory({ executeRestrictedMethod: this.#executeRestrictedMethod.bind(this), getRestrictedMethod: this.getRestrictedMethod.bind(this), @@ -816,107 +866,6 @@ export class PermissionController< ); } - /** - * Constructor helper for registering the controller's messenger actions. - */ - #registerMessageHandlers(): void { - this.messenger.registerActionHandler( - `${controllerName}:clearPermissions` as const, - () => this.clearState(), - ); - - this.messenger.registerActionHandler( - `${controllerName}:getEndowments` as const, - (origin: string, targetName: string, requestData?: unknown) => - this.getEndowments(origin, targetName, requestData), - ); - - this.messenger.registerActionHandler( - `${controllerName}:getSubjectNames` as const, - () => this.getSubjectNames(), - ); - - this.messenger.registerActionHandler( - `${controllerName}:getPermissions` as const, - (origin: OriginString) => this.getPermissions(origin), - ); - - this.messenger.registerActionHandler( - `${controllerName}:hasPermission` as const, - (origin: OriginString, targetName: string) => - this.hasPermission(origin, targetName), - ); - - this.messenger.registerActionHandler( - `${controllerName}:hasPermissions` as const, - (origin: OriginString) => this.hasPermissions(origin), - ); - - this.messenger.registerActionHandler( - `${controllerName}:grantPermissions` as const, - this.grantPermissions.bind(this), - ); - - this.messenger.registerActionHandler( - `${controllerName}:grantPermissionsIncremental` as const, - this.grantPermissionsIncremental.bind(this), - ); - - this.messenger.registerActionHandler( - `${controllerName}:requestPermissions` as const, - (subject: PermissionSubjectMetadata, permissions: RequestedPermissions) => - this.requestPermissions(subject, permissions), - ); - - this.messenger.registerActionHandler( - `${controllerName}:requestPermissionsIncremental` as const, - (subject: PermissionSubjectMetadata, permissions: RequestedPermissions) => - this.requestPermissionsIncremental(subject, permissions), - ); - - this.messenger.registerActionHandler( - `${controllerName}:revokeAllPermissions` as const, - (origin: OriginString) => this.revokeAllPermissions(origin), - ); - - this.messenger.registerActionHandler( - `${controllerName}:revokePermissionForAllSubjects` as const, - ( - target: ExtractPermission< - ControllerPermissionSpecification, - ControllerCaveatSpecification - >['parentCapability'], - ) => this.revokePermissionForAllSubjects(target), - ); - - this.messenger.registerActionHandler( - `${controllerName}:revokePermissions` as const, - this.revokePermissions.bind(this), - ); - - this.messenger.registerActionHandler( - `${controllerName}:updateCaveat` as const, - (origin, target, caveatType, caveatValue) => { - this.updateCaveat( - origin, - target, - caveatType as ExtractAllowedCaveatTypes, - caveatValue, - ); - }, - ); - - this.messenger.registerActionHandler( - `${controllerName}:getCaveat` as const, - (origin, target, caveatType) => - this.getCaveat( - origin, - target, - caveatType as ExtractAllowedCaveatTypes, - ), - ); - } - /** * Clears the state of the controller. */ diff --git a/packages/permission-controller/src/SubjectMetadataController-method-action-types.ts b/packages/permission-controller/src/SubjectMetadataController-method-action-types.ts new file mode 100644 index 00000000000..2d5608af6e3 --- /dev/null +++ b/packages/permission-controller/src/SubjectMetadataController-method-action-types.ts @@ -0,0 +1,60 @@ +/** + * This file is auto generated by `scripts/generate-method-action-types.ts`. + * Do not edit manually. + */ + +import type { SubjectMetadataController } from './SubjectMetadataController'; + +/** + * Clears the state of this controller. Also resets the cache of subjects + * encountered since startup, so as to not prematurely reach the cache limit. + */ +export type SubjectMetadataControllerClearStateAction = { + type: `SubjectMetadataController:clearState`; + handler: SubjectMetadataController['clearState']; +}; + +/** + * Stores domain metadata for the given origin (subject). Deletes metadata for + * subjects without permissions in a FIFO manner once more than + * {@link SubjectMetadataController.subjectCacheLimit} distinct origins have + * been added since boot. + * + * In order to prevent a degraded user experience, + * metadata is never deleted for subjects with permissions, since metadata + * cannot yet be requested on demand. + * + * @param metadata - The subject metadata to store. + */ +export type SubjectMetadataControllerAddSubjectMetadataAction = { + type: `SubjectMetadataController:addSubjectMetadata`; + handler: SubjectMetadataController['addSubjectMetadata']; +}; + +/** + * Gets the subject metadata for the given origin, if any. + * + * @param origin - The origin for which to get the subject metadata. + * @returns The subject metadata, if any, or `undefined` otherwise. + */ +export type SubjectMetadataControllerGetSubjectMetadataAction = { + type: `SubjectMetadataController:getSubjectMetadata`; + handler: SubjectMetadataController['getSubjectMetadata']; +}; + +/** + * Deletes all subjects without permissions from the controller's state. + */ +export type SubjectMetadataControllerTrimMetadataStateAction = { + type: `SubjectMetadataController:trimMetadataState`; + handler: SubjectMetadataController['trimMetadataState']; +}; + +/** + * Union of all SubjectMetadataController action types. + */ +export type SubjectMetadataControllerMethodActions = + | SubjectMetadataControllerClearStateAction + | SubjectMetadataControllerAddSubjectMetadataAction + | SubjectMetadataControllerGetSubjectMetadataAction + | SubjectMetadataControllerTrimMetadataStateAction; diff --git a/packages/permission-controller/src/SubjectMetadataController.ts b/packages/permission-controller/src/SubjectMetadataController.ts index ee430fd750a..19a3e516601 100644 --- a/packages/permission-controller/src/SubjectMetadataController.ts +++ b/packages/permission-controller/src/SubjectMetadataController.ts @@ -8,12 +8,20 @@ import type { Json } from '@metamask/utils'; import type { GenericPermissionController, - HasPermissions, PermissionSubjectMetadata, } from './PermissionController'; +import type { PermissionControllerHasPermissionsAction } from './PermissionController-method-action-types'; +import type { SubjectMetadataControllerMethodActions } from './SubjectMetadataController-method-action-types'; const controllerName = 'SubjectMetadataController'; +const MESSENGER_EXPOSED_METHODS = [ + 'clearState', + 'addSubjectMetadata', + 'getSubjectMetadata', + 'trimMetadataState', +] as const; + type SubjectOrigin = string; /** @@ -60,25 +68,35 @@ const defaultState: SubjectMetadataControllerState = { subjectMetadata: {}, }; -export type GetSubjectMetadataState = ControllerGetStateAction< +export type SubjectMetadataControllerGetStateAction = ControllerGetStateAction< typeof controllerName, SubjectMetadataControllerState >; +/** + * @deprecated Use `SubjectMetadataControllerGetStateAction` instead. + */ +export type GetSubjectMetadataState = SubjectMetadataControllerGetStateAction; + +/** + * @deprecated Use `SubjectMetadataControllerGetSubjectMetadataAction` instead. + */ export type GetSubjectMetadata = { type: `${typeof controllerName}:getSubjectMetadata`; handler: (origin: SubjectOrigin) => SubjectMetadata | undefined; }; +/** + * @deprecated Use `SubjectMetadataControllerAddSubjectMetadataAction` instead. + */ export type AddSubjectMetadata = { type: `${typeof controllerName}:addSubjectMetadata`; handler: (metadata: SubjectMetadataToAdd) => void; }; export type SubjectMetadataControllerActions = - | GetSubjectMetadataState - | GetSubjectMetadata - | AddSubjectMetadata; + | SubjectMetadataControllerGetStateAction + | SubjectMetadataControllerMethodActions; export type SubjectMetadataStateChange = ControllerStateChangeEvent< typeof controllerName, @@ -87,7 +105,7 @@ export type SubjectMetadataStateChange = ControllerStateChangeEvent< export type SubjectMetadataControllerEvents = SubjectMetadataStateChange; -type AllowedActions = HasPermissions; +type AllowedActions = PermissionControllerHasPermissionsAction; export type SubjectMetadataControllerMessenger = Messenger< typeof controllerName, @@ -144,14 +162,9 @@ export class SubjectMetadataController extends BaseController< this.#subjectCacheLimit = subjectCacheLimit; this.#subjectsWithoutPermissionsEncounteredSinceStartup = new Set(); - this.messenger.registerActionHandler( - `${this.name}:getSubjectMetadata`, - this.getSubjectMetadata.bind(this), - ); - - this.messenger.registerActionHandler( - `${this.name}:addSubjectMetadata`, - this.addSubjectMetadata.bind(this), + this.messenger.registerMethodActionHandlers( + this, + MESSENGER_EXPOSED_METHODS, ); } diff --git a/packages/permission-controller/src/index.ts b/packages/permission-controller/src/index.ts index 693f3b46617..29130935379 100644 --- a/packages/permission-controller/src/index.ts +++ b/packages/permission-controller/src/index.ts @@ -2,6 +2,23 @@ export * from './Caveat'; export * from './errors'; export * from './Permission'; export * from './PermissionController'; +export type { + PermissionControllerClearStateAction, + PermissionControllerGetSubjectNamesAction, + PermissionControllerGetPermissionsAction, + PermissionControllerHasPermissionAction, + PermissionControllerHasPermissionsAction, + PermissionControllerRevokeAllPermissionsAction, + PermissionControllerRevokePermissionsAction, + PermissionControllerRevokePermissionForAllSubjectsAction, + PermissionControllerGetCaveatAction, + PermissionControllerUpdateCaveatAction, + PermissionControllerGrantPermissionsAction, + PermissionControllerGrantPermissionsIncrementalAction, + PermissionControllerRequestPermissionsAction, + PermissionControllerRequestPermissionsIncrementalAction, + PermissionControllerGetEndowmentsAction, +} from './PermissionController-method-action-types'; export type { ExtractSpecifications, HandlerMiddlewareFunction, @@ -11,3 +28,9 @@ export type { export { MethodNames } from './utils'; export * as permissionRpcMethods from './rpc-methods'; export * from './SubjectMetadataController'; +export type { + SubjectMetadataControllerClearStateAction, + SubjectMetadataControllerAddSubjectMetadataAction, + SubjectMetadataControllerGetSubjectMetadataAction, + SubjectMetadataControllerTrimMetadataStateAction, +} from './SubjectMetadataController-method-action-types'; diff --git a/packages/permission-log-controller/CHANGELOG.md b/packages/permission-log-controller/CHANGELOG.md index 7295a29619b..c65e122348f 100644 --- a/packages/permission-log-controller/CHANGELOG.md +++ b/packages/permission-log-controller/CHANGELOG.md @@ -9,6 +9,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Expose missing public `PermissionLogController` methods through its messenger ([#8201](https://github.com/MetaMask/core/pull/8201)) + - The following actions are now available: + - `PermissionLogController:createMiddleware` + - `PermissionLogController:updateAccountsHistory` + - Corresponding action types + (e.g. `PermissionLogControllerCreateMiddlewareAction`) are available as + well. - Upgrade `@metamask/utils` from `^11.8.1` to `^11.9.0` ([#7511](https://github.com/MetaMask/core/pull/7511)) - Bump `@metamask/json-rpc-engine` from `^10.1.1` to `^10.2.3` ([#7202](https://github.com/MetaMask/core/pull/7202), [#7642](https://github.com/MetaMask/core/pull/7642), [#7856](https://github.com/MetaMask/core/pull/7856), [#8078](https://github.com/MetaMask/core/pull/8078)) diff --git a/packages/permission-log-controller/package.json b/packages/permission-log-controller/package.json index a525465f0ef..080c73294d3 100644 --- a/packages/permission-log-controller/package.json +++ b/packages/permission-log-controller/package.json @@ -40,6 +40,7 @@ "build:docs": "typedoc", "changelog:update": "../../scripts/update-changelog.sh @metamask/permission-log-controller", "changelog:validate": "../../scripts/validate-changelog.sh @metamask/permission-log-controller", + "generate-method-action-types": "tsx ../../scripts/generate-method-action-types.ts", "since-latest-release": "../../scripts/since-latest-release.sh", "test": "NODE_OPTIONS=--experimental-vm-modules jest --reporters=jest-silent-reporter", "test:clean": "NODE_OPTIONS=--experimental-vm-modules jest --clearCache", @@ -62,6 +63,7 @@ "jest": "^29.7.0", "nanoid": "^3.3.8", "ts-jest": "^29.2.5", + "tsx": "^4.20.5", "typedoc": "^0.25.13", "typedoc-plugin-missing-exports": "^2.0.0", "typescript": "~5.3.3" diff --git a/packages/permission-log-controller/src/PermissionLogController-method-action-types.ts b/packages/permission-log-controller/src/PermissionLogController-method-action-types.ts new file mode 100644 index 00000000000..d4d6a321218 --- /dev/null +++ b/packages/permission-log-controller/src/PermissionLogController-method-action-types.ts @@ -0,0 +1,42 @@ +/** + * This file is auto generated by `scripts/generate-method-action-types.ts`. + * Do not edit manually. + */ + +import type { PermissionLogController } from './PermissionLogController'; + +/** + * Updates the exposed account history for the given origin. + * Sets the 'last seen' time to Date.now() for the given accounts. + * Does **not** update the 'lastApproved' time for the permission itself. + * Returns if the accounts array is empty. + * + * @param origin - The origin that the accounts are exposed to. + * @param accounts - The accounts. + */ +export type PermissionLogControllerUpdateAccountsHistoryAction = { + type: `PermissionLogController:updateAccountsHistory`; + handler: PermissionLogController['updateAccountsHistory']; +}; + +/** + * Create a permissions log middleware. Records permissions activity and history: + * + * Activity: requests and responses for restricted and most wallet_ methods. + * + * History: for each origin, the last time a permission was granted, including + * which accounts were exposed, if any. + * + * @returns The permissions log middleware. + */ +export type PermissionLogControllerCreateMiddlewareAction = { + type: `PermissionLogController:createMiddleware`; + handler: PermissionLogController['createMiddleware']; +}; + +/** + * Union of all PermissionLogController action types. + */ +export type PermissionLogControllerMethodActions = + | PermissionLogControllerUpdateAccountsHistoryAction + | PermissionLogControllerCreateMiddlewareAction; diff --git a/packages/permission-log-controller/src/PermissionLogController.ts b/packages/permission-log-controller/src/PermissionLogController.ts index 4874a16ddd2..24a71602715 100644 --- a/packages/permission-log-controller/src/PermissionLogController.ts +++ b/packages/permission-log-controller/src/PermissionLogController.ts @@ -20,6 +20,7 @@ import { WALLET_PREFIX, CAVEAT_TYPES, } from './enums'; +import type { PermissionLogControllerMethodActions } from './PermissionLogController-method-action-types'; export type JsonRpcRequestWithOrigin< Params extends JsonRpcParams = JsonRpcParams, @@ -79,7 +80,8 @@ export type PermissionLogControllerGetStateAction = ControllerGetStateAction< >; export type PermissionLogControllerActions = - PermissionLogControllerGetStateAction; + | PermissionLogControllerGetStateAction + | PermissionLogControllerMethodActions; export type PermissionLogControllerStateChangeEvent = ControllerStateChangeEvent; @@ -100,6 +102,11 @@ const defaultState: PermissionLogControllerState = { const name = 'PermissionLogController'; +const MESSENGER_EXPOSED_METHODS = [ + 'updateAccountsHistory', + 'createMiddleware', +] as const; + /** * Controller with middleware for logging requests and responses to restricted * and permissions-related methods. @@ -136,6 +143,10 @@ export class PermissionLogController extends BaseController< state: { ...defaultState, ...state }, }); this.#restrictedMethods = restrictedMethods; + this.messenger.registerMethodActionHandlers( + this, + MESSENGER_EXPOSED_METHODS, + ); } /** diff --git a/packages/permission-log-controller/src/index.ts b/packages/permission-log-controller/src/index.ts index 297218d2cc1..edf1e9e47c5 100644 --- a/packages/permission-log-controller/src/index.ts +++ b/packages/permission-log-controller/src/index.ts @@ -1,4 +1,8 @@ export { PermissionLogController } from './PermissionLogController'; +export type { + PermissionLogControllerUpdateAccountsHistoryAction, + PermissionLogControllerCreateMiddlewareAction, +} from './PermissionLogController-method-action-types'; export type { JsonRpcRequestWithOrigin, Caveat, diff --git a/packages/permission-log-controller/tests/PermissionLogController.test.ts b/packages/permission-log-controller/tests/PermissionLogController.test.ts index acab9ecbc70..3c1a6403674 100644 --- a/packages/permission-log-controller/tests/PermissionLogController.test.ts +++ b/packages/permission-log-controller/tests/PermissionLogController.test.ts @@ -60,15 +60,23 @@ function getRootMessenger(): RootMessenger { }); } -const initController = ({ - restrictedMethods, - state, -}: { - restrictedMethods: Set; - state?: Partial; -}): PermissionLogController => { - const rootMessenger = getRootMessenger(); - const messenger = new Messenger< +type ControllerMessenger = Messenger< + typeof name, + AllPermissionLogControllerActions, + AllPermissionLogControllerEvents, + RootMessenger +>; + +/** + * Creates a controller messenger for testing. + * + * @param rootMessenger - The root messenger to use. + * @returns A controller messenger. + */ +function getControllerMessenger( + rootMessenger: RootMessenger, +): ControllerMessenger { + return new Messenger< typeof name, AllPermissionLogControllerActions, AllPermissionLogControllerEvents, @@ -77,11 +85,23 @@ const initController = ({ namespace: name, parent: rootMessenger, }); - return new PermissionLogController({ +} + +const initController = ({ + restrictedMethods, + state, +}: { + restrictedMethods: Set; + state?: Partial; +}): { controller: PermissionLogController; rootMessenger: RootMessenger } => { + const rootMessenger = getRootMessenger(); + const messenger = getControllerMessenger(rootMessenger); + const controller = new PermissionLogController({ messenger, restrictedMethods, state, }); + return { controller, rootMessenger }; }; const mockNext = @@ -126,10 +146,12 @@ describe('PermissionLogController', () => { }); it('records activity for a successful restricted method request', () => { - const controller = initController({ + const { controller, rootMessenger } = initController({ restrictedMethods: new Set(['test_method']), }); - const logMiddleware = controller.createMiddleware(); + const logMiddleware = rootMessenger.call( + 'PermissionLogController:createMiddleware', + ); const req = RPC_REQUESTS.test_method(SUBJECTS.a.origin); const res = { ...PendingJsonRpcResponseStruct.TYPE, @@ -152,10 +174,12 @@ describe('PermissionLogController', () => { }); it('records activity for a failed restricted method request', () => { - const controller = initController({ + const { controller, rootMessenger } = initController({ restrictedMethods: new Set(['eth_accounts']), }); - const logMiddleware = controller.createMiddleware(); + const logMiddleware = rootMessenger.call( + 'PermissionLogController:createMiddleware', + ); const req = RPC_REQUESTS.eth_accounts(SUBJECTS.b.origin); const res: PendingJsonRpcResponse = { id: REQUEST_IDS.a, @@ -179,10 +203,12 @@ describe('PermissionLogController', () => { }); it('records activity for a restricted method request with successful eth_requestAccounts', () => { - const controller = initController({ + const { controller, rootMessenger } = initController({ restrictedMethods: new Set([]), }); - const logMiddleware = controller.createMiddleware(); + const logMiddleware = rootMessenger.call( + 'PermissionLogController:createMiddleware', + ); const req = RPC_REQUESTS.eth_requestAccounts(SUBJECTS.c.origin); const res = { ...PendingJsonRpcResponseStruct.TYPE, @@ -205,10 +231,12 @@ describe('PermissionLogController', () => { }); it('handles a restricted method request without a response', () => { - const controller = initController({ + const { controller, rootMessenger } = initController({ restrictedMethods: new Set(['test_method']), }); - const logMiddleware = controller.createMiddleware(); + const logMiddleware = rootMessenger.call( + 'PermissionLogController:createMiddleware', + ); const req = RPC_REQUESTS.test_method(SUBJECTS.a.origin); // @ts-expect-error We are intentionally passing bad input. const res: PendingJsonRpcResponse = null; @@ -229,10 +257,12 @@ describe('PermissionLogController', () => { }); it('ensures that "request" and "response" properties are not present in log entries', () => { - const controller = initController({ + const { controller, rootMessenger } = initController({ restrictedMethods: new Set([]), }); - const logMiddleware = controller.createMiddleware(); + const logMiddleware = rootMessenger.call( + 'PermissionLogController:createMiddleware', + ); const req = RPC_REQUESTS.test_method(SUBJECTS.a.origin); const res = { ...PendingJsonRpcResponseStruct.TYPE, @@ -248,10 +278,12 @@ describe('PermissionLogController', () => { }); it('handles responses added out of order', () => { - const controller = initController({ + const { controller, rootMessenger } = initController({ restrictedMethods: new Set(['test_method']), }); - const logMiddleware = controller.createMiddleware(); + const logMiddleware = rootMessenger.call( + 'PermissionLogController:createMiddleware', + ); const handlerArray: JsonRpcEngineReturnHandler[] = []; const req = RPC_REQUESTS.test_method(SUBJECTS.a.origin); @@ -333,10 +365,12 @@ describe('PermissionLogController', () => { }); it('handles a lack of response', () => { - const controller = initController({ + const { controller, rootMessenger } = initController({ restrictedMethods: new Set(['test_method']), }); - const logMiddleware = controller.createMiddleware(); + const logMiddleware = rootMessenger.call( + 'PermissionLogController:createMiddleware', + ); const req1 = { ...RPC_REQUESTS.test_method(SUBJECTS.a.origin), id: REQUEST_IDS.a, @@ -404,10 +438,12 @@ describe('PermissionLogController', () => { }); it('ignores activity for expected methods', () => { - const controller = initController({ + const { controller, rootMessenger } = initController({ restrictedMethods: new Set([]), }); - const logMiddleware = controller.createMiddleware(); + const logMiddleware = rootMessenger.call( + 'PermissionLogController:createMiddleware', + ); expect(controller.state.permissionActivityLog).toHaveLength(0); const res = { @@ -429,10 +465,12 @@ describe('PermissionLogController', () => { }); it('fills up the log to its limit without exceeding', () => { - const controller = initController({ + const { controller, rootMessenger } = initController({ restrictedMethods: new Set(['test_method']), }); - const logMiddleware = controller.createMiddleware(); + const logMiddleware = rootMessenger.call( + 'PermissionLogController:createMiddleware', + ); const req = RPC_REQUESTS.test_method(SUBJECTS.a.origin); const res = { ...PendingJsonRpcResponseStruct.TYPE, result: ['bar'] }; @@ -444,10 +482,12 @@ describe('PermissionLogController', () => { }); it('removes the oldest log entry when a new one is added after reaching the limit', () => { - const controller = initController({ + const { controller, rootMessenger } = initController({ restrictedMethods: new Set(['test_method']), }); - const logMiddleware = controller.createMiddleware(); + const logMiddleware = rootMessenger.call( + 'PermissionLogController:createMiddleware', + ); const req = RPC_REQUESTS.test_method(SUBJECTS.a.origin); const res = { ...PendingJsonRpcResponseStruct.TYPE, result: ['bar'] }; @@ -473,10 +513,12 @@ describe('PermissionLogController', () => { }); it('ensures the log does not exceed the limit when adding multiple entries', () => { - const controller = initController({ + const { controller, rootMessenger } = initController({ restrictedMethods: new Set(['test_method']), }); - const logMiddleware = controller.createMiddleware(); + const logMiddleware = rootMessenger.call( + 'PermissionLogController:createMiddleware', + ); const req = RPC_REQUESTS.test_method(SUBJECTS.a.origin); const res = { ...PendingJsonRpcResponseStruct.TYPE, result: ['bar'] }; @@ -498,10 +540,12 @@ describe('PermissionLogController', () => { }); it('only updates history on responses', () => { - const controller = initController({ + const { controller, rootMessenger } = initController({ restrictedMethods: new Set([]), }); - const logMiddleware = controller.createMiddleware(); + const logMiddleware = rootMessenger.call( + 'PermissionLogController:createMiddleware', + ); const req = RPC_REQUESTS.requestPermission( SUBJECTS.a.origin, PERM_NAMES.test_method, @@ -525,10 +569,12 @@ describe('PermissionLogController', () => { }); it('ignores malformed permissions requests', () => { - const controller = initController({ + const { controller, rootMessenger } = initController({ restrictedMethods: new Set([]), }); - const logMiddleware = controller.createMiddleware(); + const logMiddleware = rootMessenger.call( + 'PermissionLogController:createMiddleware', + ); const req = RPC_REQUESTS.requestPermission( SUBJECTS.a.origin, PERM_NAMES.test_method, @@ -553,10 +599,12 @@ describe('PermissionLogController', () => { }); it('records and updates account history as expected', async () => { - const controller = initController({ + const { controller, rootMessenger } = initController({ restrictedMethods: new Set([]), }); - const logMiddleware = controller.createMiddleware(); + const logMiddleware = rootMessenger.call( + 'PermissionLogController:createMiddleware', + ); const req = RPC_REQUESTS.requestPermission( SUBJECTS.a.origin, PERM_NAMES.eth_accounts, @@ -584,10 +632,12 @@ describe('PermissionLogController', () => { }); it('handles eth_accounts response without caveats', async () => { - const controller = initController({ + const { controller, rootMessenger } = initController({ restrictedMethods: new Set([]), }); - const logMiddleware = controller.createMiddleware(); + const logMiddleware = rootMessenger.call( + 'PermissionLogController:createMiddleware', + ); const req = RPC_REQUESTS.requestPermission( SUBJECTS.a.origin, PERM_NAMES.eth_accounts, @@ -606,10 +656,12 @@ describe('PermissionLogController', () => { }); it('handles extra caveats for eth_accounts', async () => { - const controller = initController({ + const { controller, rootMessenger } = initController({ restrictedMethods: new Set([]), }); - const logMiddleware = controller.createMiddleware(); + const logMiddleware = rootMessenger.call( + 'PermissionLogController:createMiddleware', + ); const req = RPC_REQUESTS.requestPermission( SUBJECTS.a.origin, PERM_NAMES.eth_accounts, @@ -631,10 +683,12 @@ describe('PermissionLogController', () => { // wallet_requestPermissions returns all permissions approved for the // requesting origin, including old ones it('handles unrequested permissions on the response', async () => { - const controller = initController({ + const { controller, rootMessenger } = initController({ restrictedMethods: new Set([]), }); - const logMiddleware = controller.createMiddleware(); + const logMiddleware = rootMessenger.call( + 'PermissionLogController:createMiddleware', + ); const req = RPC_REQUESTS.requestPermission( SUBJECTS.a.origin, PERM_NAMES.eth_accounts, @@ -655,10 +709,12 @@ describe('PermissionLogController', () => { }); it('does not update history if no new permissions are approved', async () => { - const controller = initController({ + const { controller, rootMessenger } = initController({ restrictedMethods: new Set([]), }); - const logMiddleware = controller.createMiddleware(); + const logMiddleware = rootMessenger.call( + 'PermissionLogController:createMiddleware', + ); let req = RPC_REQUESTS.requestPermission( SUBJECTS.a.origin, PERM_NAMES.test_method, @@ -695,10 +751,12 @@ describe('PermissionLogController', () => { }); it('records and updates history for multiple origins, regardless of response order', async () => { - const controller = initController({ + const { controller, rootMessenger } = initController({ restrictedMethods: new Set([]), }); - const logMiddleware = controller.createMiddleware(); + const logMiddleware = rootMessenger.call( + 'PermissionLogController:createMiddleware', + ); const round1: { req: JsonRpcRequest; @@ -803,17 +861,21 @@ describe('PermissionLogController', () => { }); it('does nothing if the list of accounts is empty', () => { - const controller = initController({ + const { controller, rootMessenger } = initController({ restrictedMethods: new Set([]), }); - controller.updateAccountsHistory('foo.com', []); + rootMessenger.call( + 'PermissionLogController:updateAccountsHistory', + 'foo.com', + [], + ); expect(controller.state.permissionHistory).toStrictEqual({}); }); it('updates the account history', () => { - const controller = initController({ + const { controller, rootMessenger } = initController({ restrictedMethods: new Set(['eth_accounts']), state: { permissionHistory: { @@ -830,7 +892,11 @@ describe('PermissionLogController', () => { }); jest.advanceTimersByTime(1); - controller.updateAccountsHistory('foo.com', ['0x1', '0x2']); + rootMessenger.call( + 'PermissionLogController:updateAccountsHistory', + 'foo.com', + ['0x1', '0x2'], + ); expect(controller.state.permissionHistory).toStrictEqual({ 'foo.com': { @@ -848,7 +914,7 @@ describe('PermissionLogController', () => { describe('metadata', () => { it('includes expected state in debug snapshots', () => { - const controller = initController({ + const { controller } = initController({ restrictedMethods: new Set(['test_method']), }); @@ -862,7 +928,7 @@ describe('PermissionLogController', () => { }); it('includes expected state in state logs', () => { - const controller = initController({ + const { controller } = initController({ restrictedMethods: new Set(['test_method']), }); @@ -881,7 +947,7 @@ describe('PermissionLogController', () => { }); it('persists expected state', () => { - const controller = initController({ + const { controller } = initController({ restrictedMethods: new Set(['test_method']), }); @@ -899,7 +965,7 @@ describe('PermissionLogController', () => { }); it('exposes expected state to UI', () => { - const controller = initController({ + const { controller } = initController({ restrictedMethods: new Set(['test_method']), }); diff --git a/packages/selected-network-controller/CHANGELOG.md b/packages/selected-network-controller/CHANGELOG.md index f724bf22f55..f4c7a6dfd18 100644 --- a/packages/selected-network-controller/CHANGELOG.md +++ b/packages/selected-network-controller/CHANGELOG.md @@ -7,8 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Expose missing public `SelectedNetworkController` methods through its messenger ([#8201](https://github.com/MetaMask/core/pull/8201)) + - The following actions are now available: + - `SelectedNetworkController:getProviderAndBlockTracker` + - Corresponding action types + (e.g. `SelectedNetworkControllerGetProviderAndBlockTrackerAction`) are + available as well. + ### Changed +- Deprecate `SelectedNetworkControllerGetSelectedNetworkStateAction` type in favor of `SelectedNetworkControllerGetStateAction` ([#8201](https://github.com/MetaMask/core/pull/8201)) + - The old types is still exported but is now marked as deprecated and will + be removed in a future release. - Bump `@metamask/permission-controller` from `^12.2.0` to `^12.2.1` ([#8225](https://github.com/MetaMask/core/pull/8225)) - Bump `@metamask/json-rpc-engine` from `^10.2.2` to `^10.2.3` ([#8078](https://github.com/MetaMask/core/pull/8078)) diff --git a/packages/selected-network-controller/package.json b/packages/selected-network-controller/package.json index bca4c1e42db..4e7abcaf3d2 100644 --- a/packages/selected-network-controller/package.json +++ b/packages/selected-network-controller/package.json @@ -40,6 +40,7 @@ "build:docs": "typedoc", "changelog:update": "../../scripts/update-changelog.sh @metamask/selected-network-controller", "changelog:validate": "../../scripts/validate-changelog.sh @metamask/selected-network-controller", + "generate-method-action-types": "tsx ../../scripts/generate-method-action-types.ts", "since-latest-release": "../../scripts/since-latest-release.sh", "test": "NODE_OPTIONS=--experimental-vm-modules jest --reporters=jest-silent-reporter", "test:clean": "NODE_OPTIONS=--experimental-vm-modules jest --clearCache", @@ -66,6 +67,7 @@ "lodash": "^4.17.21", "nock": "^13.3.1", "ts-jest": "^29.2.5", + "tsx": "^4.20.5", "typedoc": "^0.25.13", "typedoc-plugin-missing-exports": "^2.0.0", "typescript": "~5.3.3" diff --git a/packages/selected-network-controller/src/SelectedNetworkController-method-action-types.ts b/packages/selected-network-controller/src/SelectedNetworkController-method-action-types.ts new file mode 100644 index 00000000000..8f527b4bb75 --- /dev/null +++ b/packages/selected-network-controller/src/SelectedNetworkController-method-action-types.ts @@ -0,0 +1,35 @@ +/** + * This file is auto generated by `scripts/generate-method-action-types.ts`. + * Do not edit manually. + */ + +import type { SelectedNetworkController } from './SelectedNetworkController'; + +export type SelectedNetworkControllerSetNetworkClientIdForDomainAction = { + type: `SelectedNetworkController:setNetworkClientIdForDomain`; + handler: SelectedNetworkController['setNetworkClientIdForDomain']; +}; + +export type SelectedNetworkControllerGetNetworkClientIdForDomainAction = { + type: `SelectedNetworkController:getNetworkClientIdForDomain`; + handler: SelectedNetworkController['getNetworkClientIdForDomain']; +}; + +/** + * Accesses the provider and block tracker for the currently selected network. + * + * @param domain - the domain for the provider + * @returns The proxy and block tracker proxies. + */ +export type SelectedNetworkControllerGetProviderAndBlockTrackerAction = { + type: `SelectedNetworkController:getProviderAndBlockTracker`; + handler: SelectedNetworkController['getProviderAndBlockTracker']; +}; + +/** + * Union of all SelectedNetworkController action types. + */ +export type SelectedNetworkControllerMethodActions = + | SelectedNetworkControllerSetNetworkClientIdForDomainAction + | SelectedNetworkControllerGetNetworkClientIdForDomainAction + | SelectedNetworkControllerGetProviderAndBlockTrackerAction; diff --git a/packages/selected-network-controller/src/SelectedNetworkController.ts b/packages/selected-network-controller/src/SelectedNetworkController.ts index 1dd7aef0b5b..bc2e5093236 100644 --- a/packages/selected-network-controller/src/SelectedNetworkController.ts +++ b/packages/selected-network-controller/src/SelectedNetworkController.ts @@ -15,8 +15,8 @@ import type { } from '@metamask/network-controller'; import type { PermissionControllerStateChange, - GetSubjects as PermissionControllerGetSubjectsAction, - HasPermissions as PermissionControllerHasPermissions, + PermissionControllerGetSubjectNamesAction as PermissionControllerGetSubjectsAction, + PermissionControllerHasPermissionsAction as PermissionControllerHasPermissions, } from '@metamask/permission-controller'; import { createEventEmitterProxy, @@ -24,8 +24,16 @@ import { } from '@metamask/swappable-obj-proxy'; import type { Hex } from '@metamask/utils'; +import type { SelectedNetworkControllerMethodActions } from './SelectedNetworkController-method-action-types'; + const controllerName = 'SelectedNetworkController'; +const MESSENGER_EXPOSED_METHODS = [ + 'setNetworkClientIdForDomain', + 'getNetworkClientIdForDomain', + 'getProviderAndBlockTracker', +] as const; + const stateMetadata = { domains: { includeInStateLogs: true, @@ -63,26 +71,20 @@ export type SelectedNetworkControllerStateChangeEvent = SelectedNetworkControllerState >; -export type SelectedNetworkControllerGetSelectedNetworkStateAction = - ControllerGetStateAction< - typeof controllerName, - SelectedNetworkControllerState - >; - -export type SelectedNetworkControllerGetNetworkClientIdForDomainAction = { - type: typeof SelectedNetworkControllerActionTypes.getNetworkClientIdForDomain; - handler: SelectedNetworkController['getNetworkClientIdForDomain']; -}; +export type SelectedNetworkControllerGetStateAction = ControllerGetStateAction< + typeof controllerName, + SelectedNetworkControllerState +>; -export type SelectedNetworkControllerSetNetworkClientIdForDomainAction = { - type: typeof SelectedNetworkControllerActionTypes.setNetworkClientIdForDomain; - handler: SelectedNetworkController['setNetworkClientIdForDomain']; -}; +/** + * @deprecated Use `SelectedNetworkControllerGetStateAction` instead. + */ +export type SelectedNetworkControllerGetSelectedNetworkStateAction = + SelectedNetworkControllerGetStateAction; export type SelectedNetworkControllerActions = - | SelectedNetworkControllerGetSelectedNetworkStateAction - | SelectedNetworkControllerGetNetworkClientIdForDomainAction - | SelectedNetworkControllerSetNetworkClientIdForDomainAction; + | SelectedNetworkControllerGetStateAction + | SelectedNetworkControllerMethodActions; type AllowedActions = | NetworkControllerGetNetworkClientByIdAction @@ -145,7 +147,10 @@ export class SelectedNetworkController extends BaseController< state, }); this.#domainProxyMap = domainProxyMap; - this.#registerMessageHandlers(); + this.messenger.registerMethodActionHandlers( + this, + MESSENGER_EXPOSED_METHODS, + ); // this is fetching all the dapp permissions from the PermissionsController and looking for any domains that are not in domains state in this controller. Then we take any missing domains and add them to state here, setting it with the globally selected networkClientId (fetched from the NetworkController) this.messenger @@ -242,17 +247,6 @@ export class SelectedNetworkController extends BaseController< ); } - #registerMessageHandlers(): void { - this.messenger.registerActionHandler( - SelectedNetworkControllerActionTypes.getNetworkClientIdForDomain, - this.getNetworkClientIdForDomain.bind(this), - ); - this.messenger.registerActionHandler( - SelectedNetworkControllerActionTypes.setNetworkClientIdForDomain, - this.setNetworkClientIdForDomain.bind(this), - ); - } - #setNetworkClientIdForDomain( domain: Domain, networkClientId: NetworkClientId, diff --git a/packages/selected-network-controller/src/index.ts b/packages/selected-network-controller/src/index.ts index 7d78f7f6b4b..f07208bb983 100644 --- a/packages/selected-network-controller/src/index.ts +++ b/packages/selected-network-controller/src/index.ts @@ -1,9 +1,8 @@ export type { SelectedNetworkControllerState, SelectedNetworkControllerStateChangeEvent, + SelectedNetworkControllerGetStateAction, SelectedNetworkControllerGetSelectedNetworkStateAction, - SelectedNetworkControllerGetNetworkClientIdForDomainAction, - SelectedNetworkControllerSetNetworkClientIdForDomainAction, SelectedNetworkControllerActions, SelectedNetworkControllerEvents, SelectedNetworkControllerMessenger, @@ -17,5 +16,10 @@ export { SelectedNetworkController, METAMASK_DOMAIN, } from './SelectedNetworkController'; +export type { + SelectedNetworkControllerSetNetworkClientIdForDomainAction, + SelectedNetworkControllerGetNetworkClientIdForDomainAction, + SelectedNetworkControllerGetProviderAndBlockTrackerAction, +} from './SelectedNetworkController-method-action-types'; export type { SelectedNetworkMiddlewareJsonRpcRequest } from './SelectedNetworkMiddleware'; export { createSelectedNetworkMiddleware } from './SelectedNetworkMiddleware'; diff --git a/packages/selected-network-controller/tests/SelectedNetworkController.test.ts b/packages/selected-network-controller/tests/SelectedNetworkController.test.ts index 275136be1d7..f57aeebbb2f 100644 --- a/packages/selected-network-controller/tests/SelectedNetworkController.test.ts +++ b/packages/selected-network-controller/tests/SelectedNetworkController.test.ts @@ -461,9 +461,13 @@ describe('SelectedNetworkController', () => { describe('setNetworkClientIdForDomain', () => { it('should throw an error when passed "metamask" as domain arg', () => { - const { controller } = setup(); + const { controller, rootMessenger } = setup(); expect(() => { - controller.setNetworkClientIdForDomain('metamask', 'mainnet'); + rootMessenger.call( + 'SelectedNetworkController:setNetworkClientIdForDomain', + 'metamask', + 'mainnet', + ); }).toThrow( 'NetworkClientId for domain "metamask" cannot be set on the SelectedNetworkController', ); @@ -472,25 +476,30 @@ describe('SelectedNetworkController', () => { describe('when the requesting domain is a snap (starts with "npm:" or "local:"', () => { it('sets the networkClientId for the passed in snap ID', () => { - const { controller, mockHasPermissions } = setup({ + const { controller, rootMessenger, mockHasPermissions } = setup({ state: { domains: {} }, }); mockHasPermissions.mockReturnValue(true); const domain = 'npm:foo-snap'; const networkClientId = 'network1'; - controller.setNetworkClientIdForDomain(domain, networkClientId); + rootMessenger.call( + 'SelectedNetworkController:setNetworkClientIdForDomain', + domain, + networkClientId, + ); expect(controller.state.domains[domain]).toBe(networkClientId); }); it('updates the provider and block tracker proxy when they already exist for the snap ID', () => { - const { controller, mockProviderProxy, mockHasPermissions } = setup({ + const { rootMessenger, mockProviderProxy, mockHasPermissions } = setup({ state: { domains: {} }, }); mockHasPermissions.mockReturnValue(true); const initialNetworkClientId = '123'; // creates the proxy for the new domain - controller.setNetworkClientIdForDomain( + rootMessenger.call( + 'SelectedNetworkController:setNetworkClientIdForDomain', 'npm:foo-snap', initialNetworkClientId, ); @@ -499,7 +508,8 @@ describe('SelectedNetworkController', () => { expect(mockProviderProxy.setTarget).toHaveBeenCalledTimes(1); // calls setTarget on the proxy - controller.setNetworkClientIdForDomain( + rootMessenger.call( + 'SelectedNetworkController:setNetworkClientIdForDomain', 'npm:foo-snap', newNetworkClientId, ); @@ -514,25 +524,30 @@ describe('SelectedNetworkController', () => { describe('when the requesting domain has existing permissions', () => { it('sets the networkClientId for the passed in domain', () => { - const { controller, mockHasPermissions } = setup({ + const { controller, rootMessenger, mockHasPermissions } = setup({ state: { domains: {} }, }); mockHasPermissions.mockReturnValue(true); const domain = 'example.com'; const networkClientId = 'network1'; - controller.setNetworkClientIdForDomain(domain, networkClientId); + rootMessenger.call( + 'SelectedNetworkController:setNetworkClientIdForDomain', + domain, + networkClientId, + ); expect(controller.state.domains[domain]).toBe(networkClientId); }); it('updates the provider and block tracker proxy when they already exist for the domain', () => { - const { controller, mockProviderProxy, mockHasPermissions } = setup({ + const { rootMessenger, mockProviderProxy, mockHasPermissions } = setup({ state: { domains: {} }, }); mockHasPermissions.mockReturnValue(true); const initialNetworkClientId = '123'; // creates the proxy for the new domain - controller.setNetworkClientIdForDomain( + rootMessenger.call( + 'SelectedNetworkController:setNetworkClientIdForDomain', 'example.com', initialNetworkClientId, ); @@ -541,7 +556,8 @@ describe('SelectedNetworkController', () => { expect(mockProviderProxy.setTarget).toHaveBeenCalledTimes(1); // calls setTarget on the proxy - controller.setNetworkClientIdForDomain( + rootMessenger.call( + 'SelectedNetworkController:setNetworkClientIdForDomain', 'example.com', newNetworkClientId, ); @@ -556,7 +572,7 @@ describe('SelectedNetworkController', () => { describe('when the requesting domain does not have permissions', () => { it('throws an error and does not set the networkClientId for the passed in domain', () => { - const { controller, mockHasPermissions } = setup({ + const { controller, rootMessenger, mockHasPermissions } = setup({ state: { domains: {} }, }); mockHasPermissions.mockReturnValue(false); @@ -564,7 +580,11 @@ describe('SelectedNetworkController', () => { const domain = 'example.com'; const networkClientId = 'network1'; expect(() => { - controller.setNetworkClientIdForDomain(domain, networkClientId); + rootMessenger.call( + 'SelectedNetworkController:setNetworkClientIdForDomain', + domain, + networkClientId, + ); }).toThrow( 'NetworkClientId for domain cannot be called with a domain that has not yet been granted permissions', ); @@ -576,7 +596,7 @@ describe('SelectedNetworkController', () => { describe('getNetworkClientIdForDomain', () => { it('returns the networkClientId from state when a networkClientId has been set for the requested domain', () => { - const { controller } = setup({ + const { rootMessenger } = setup({ state: { domains: { 'example.com': '1', @@ -584,17 +604,23 @@ describe('getNetworkClientIdForDomain', () => { }, }); - const result = controller.getNetworkClientIdForDomain('example.com'); + const result = rootMessenger.call( + 'SelectedNetworkController:getNetworkClientIdForDomain', + 'example.com', + ); expect(result).toBe('1'); }); it('returns the selectedNetworkClientId from the NetworkController when no networkClientId has been set for the requested domain', () => { - const { controller } = setup({ + const { rootMessenger } = setup({ state: { domains: {} }, }); - expect(controller.getNetworkClientIdForDomain('example.com')).toBe( - 'mainnet', - ); + expect( + rootMessenger.call( + 'SelectedNetworkController:getNetworkClientIdForDomain', + 'example.com', + ), + ).toBe('mainnet'); }); }); @@ -623,14 +649,17 @@ describe('getProviderAndBlockTracker', () => { }, ], ]); - const { controller } = setup({ + const { rootMessenger } = setup({ state: { domains: {}, }, domainProxyMap, }); - const result = controller.getProviderAndBlockTracker('example.com'); + const result = rootMessenger.call( + 'SelectedNetworkController:getProviderAndBlockTracker', + 'example.com', + ); expect(result).toStrictEqual({ provider: mockProxyProvider, blockTracker: mockProxyBlockTracker, @@ -640,7 +669,7 @@ describe('getProviderAndBlockTracker', () => { describe('when the domain does not have a cached networkProxy in the domainProxyMap', () => { describe('when the domain has permissions', () => { it('calls to NetworkController:getNetworkClientById and creates a new proxy provider and block tracker with the non-proxied globally selected network client', () => { - const { controller, messenger, mockHasPermissions } = setup({ + const { rootMessenger, messenger, mockHasPermissions } = setup({ state: { domains: {}, }, @@ -648,7 +677,10 @@ describe('getProviderAndBlockTracker', () => { jest.spyOn(messenger, 'call'); mockHasPermissions.mockReturnValue(true); - const result = controller.getProviderAndBlockTracker('example.com'); + const result = rootMessenger.call( + 'SelectedNetworkController:getProviderAndBlockTracker', + 'example.com', + ); expect(result).toBeDefined(); // unfortunately checking which networkController method is called is the best // proxy (no pun intended) for checking that the correct instance of the networkClient is used @@ -661,14 +693,17 @@ describe('getProviderAndBlockTracker', () => { describe('when the domain does not have permissions', () => { it('calls to NetworkController:getSelectedNetworkClient and creates a new proxy provider and block tracker with the proxied globally selected network client', () => { - const { controller, messenger, mockHasPermissions } = setup({ + const { rootMessenger, messenger, mockHasPermissions } = setup({ state: { domains: {}, }, }); jest.spyOn(messenger, 'call'); mockHasPermissions.mockReturnValue(false); - const result = controller.getProviderAndBlockTracker('example.com'); + const result = rootMessenger.call( + 'SelectedNetworkController:getProviderAndBlockTracker', + 'example.com', + ); expect(result).toBeDefined(); // unfortunately checking which networkController method is called is the best // proxy (no pun intended) for checking that the correct instance of the networkClient is used @@ -678,17 +713,23 @@ describe('getProviderAndBlockTracker', () => { }); it('throws an error if the globally selected network client is not initialized', () => { - const { controller, mockGetSelectedNetworkClient, mockHasPermissions } = - setup({ - state: { - domains: {}, - }, - }); + const { + rootMessenger, + mockGetSelectedNetworkClient, + mockHasPermissions, + } = setup({ + state: { + domains: {}, + }, + }); mockHasPermissions.mockReturnValue(false); mockGetSelectedNetworkClient.mockReturnValue(undefined); expect(() => - controller.getProviderAndBlockTracker('example.com'), + rootMessenger.call( + 'SelectedNetworkController:getProviderAndBlockTracker', + 'example.com', + ), ).toThrow('Selected network not initialized'); }); }); @@ -697,14 +738,17 @@ describe('getProviderAndBlockTracker', () => { // TODO - improve these tests by using a full NetworkController and doing more robust behavioral testing describe('when the domain is a snap (starts with "npm:" or "local:")', () => { it('calls to NetworkController:getSelectedNetworkClient and creates a new proxy provider and block tracker with the proxied globally selected network client', () => { - const { controller, messenger } = setup({ + const { rootMessenger, messenger } = setup({ state: { domains: {}, }, }); jest.spyOn(messenger, 'call'); - const result = controller.getProviderAndBlockTracker('npm:foo-snap'); + const result = rootMessenger.call( + 'SelectedNetworkController:getProviderAndBlockTracker', + 'npm:foo-snap', + ); expect(result).toBeDefined(); // unfortunately checking which networkController method is called is the best // proxy (no pun intended) for checking that the correct instance of the networkClient is used @@ -715,32 +759,41 @@ describe('getProviderAndBlockTracker', () => { }); it('throws an error if the globally selected network client is not initialized', () => { - const { controller, mockGetSelectedNetworkClient, mockHasPermissions } = - setup({ - state: { - domains: {}, - }, - }); + const { + rootMessenger, + mockGetSelectedNetworkClient, + mockHasPermissions, + } = setup({ + state: { + domains: {}, + }, + }); const snapDomain = 'npm:@metamask/bip32-example-snap'; mockHasPermissions.mockReturnValue(false); mockGetSelectedNetworkClient.mockReturnValue(undefined); - expect(() => controller.getProviderAndBlockTracker(snapDomain)).toThrow( - 'Selected network not initialized', - ); + expect(() => + rootMessenger.call( + 'SelectedNetworkController:getProviderAndBlockTracker', + snapDomain, + ), + ).toThrow('Selected network not initialized'); }); }); describe('when the domain is a "metamask"', () => { it('returns a proxied globally selected networkClient and does not create a new proxy in the domainProxyMap', () => { - const { controller, domainProxyMap, messenger } = setup({ + const { rootMessenger, domainProxyMap, messenger } = setup({ state: { domains: {}, }, }); jest.spyOn(messenger, 'call'); - const result = controller.getProviderAndBlockTracker(METAMASK_DOMAIN); + const result = rootMessenger.call( + 'SelectedNetworkController:getProviderAndBlockTracker', + METAMASK_DOMAIN, + ); expect(result).toBeDefined(); expect(domainProxyMap.get(METAMASK_DOMAIN)).toBeUndefined(); @@ -750,7 +803,7 @@ describe('getProviderAndBlockTracker', () => { }); it('throws an error if the globally selected network client is not initialized', () => { - const { controller, mockGetSelectedNetworkClient } = setup({ + const { rootMessenger, mockGetSelectedNetworkClient } = setup({ state: { domains: {}, }, @@ -758,7 +811,10 @@ describe('getProviderAndBlockTracker', () => { mockGetSelectedNetworkClient.mockReturnValue(undefined); expect(() => - controller.getProviderAndBlockTracker(METAMASK_DOMAIN), + rootMessenger.call( + 'SelectedNetworkController:getProviderAndBlockTracker', + METAMASK_DOMAIN, + ), ).toThrow('Selected network not initialized'); }); }); @@ -816,7 +872,10 @@ describe('PermissionController:stateChange', () => { const { controller, rootMessenger, mockProviderProxy } = setup({ state: { domains: { 'example.com': 'foo' } }, }); - controller.getProviderAndBlockTracker('example.com'); + rootMessenger.call( + 'SelectedNetworkController:getProviderAndBlockTracker', + 'example.com', + ); rootMessenger.publish( 'PermissionController:stateChange', @@ -840,7 +899,6 @@ describe('PermissionController:stateChange', () => { it('should delete the proxy if the globally selected network client is not initialized but a proxy exists for the domain', async () => { const { - controller, rootMessenger, domainProxyMap, mockProviderProxy, @@ -848,7 +906,10 @@ describe('PermissionController:stateChange', () => { } = setup({ state: { domains: { 'example.com': 'foo' } }, }); - controller.getProviderAndBlockTracker('example.com'); + rootMessenger.call( + 'SelectedNetworkController:getProviderAndBlockTracker', + 'example.com', + ); mockGetSelectedNetworkClient.mockReturnValue(undefined); expect(domainProxyMap.get('example.com')).toBeDefined(); diff --git a/yarn.lock b/yarn.lock index d9a735e2cbc..ca993662deb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4673,6 +4673,7 @@ __metadata: jest: "npm:^29.7.0" nanoid: "npm:^3.3.8" ts-jest: "npm:^29.2.5" + tsx: "npm:^4.20.5" typedoc: "npm:^0.25.13" typedoc-plugin-missing-exports: "npm:^2.0.0" typescript: "npm:~5.3.3" @@ -4696,6 +4697,7 @@ __metadata: jest: "npm:^29.7.0" nanoid: "npm:^3.3.8" ts-jest: "npm:^29.2.5" + tsx: "npm:^4.20.5" typedoc: "npm:^0.25.13" typedoc-plugin-missing-exports: "npm:^2.0.0" typescript: "npm:~5.3.3" @@ -5093,6 +5095,7 @@ __metadata: lodash: "npm:^4.17.21" nock: "npm:^13.3.1" ts-jest: "npm:^29.2.5" + tsx: "npm:^4.20.5" typedoc: "npm:^0.25.13" typedoc-plugin-missing-exports: "npm:^2.0.0" typescript: "npm:~5.3.3"