diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index b0d6dd9b8..a3afaeebb 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -2362,6 +2362,10 @@ "message": "Store and manage its data on your device.", "description": "The description for the `snap_manageState` permission" }, + "permission_notifications": { + "message": "Show notifications.", + "description": "The description for the `snap_notify` permission" + }, "permission_unknown": { "message": "Unknown permission: $1", "description": "$1 is the name of a requested permission that is not recognized." diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 1f4441c2e..bd5fbdb5e 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -40,6 +40,9 @@ import { CollectibleDetectionController, PermissionController, SubjectMetadataController, + ///: BEGIN:ONLY_INCLUDE_IN(flask) + RateLimitController, + ///: END:ONLY_INCLUDE_IN } from '@metamask/controllers'; import SmartTransactionsController from '@metamask/smart-transactions-controller'; ///: BEGIN:ONLY_INCLUDE_IN(flask) @@ -634,6 +637,27 @@ export default class MetamaskController extends EventEmitter { state: initState.SnapController, messenger: snapControllerMessenger, }); + + this.rateLimitController = new RateLimitController({ + messenger: this.controllerMessenger.getRestricted({ + name: 'RateLimitController', + }), + implementations: { + showNativeNotification: (origin, message) => { + const subjectMetadataState = this.controllerMessenger.call( + 'SubjectMetadataController:getState', + ); + + const originMetadata = subjectMetadataState.subjectMetadata[origin]; + + this.platform._showNotification( + originMetadata?.name ?? origin, + message, + ); + return null; + }, + }, + }); ///: END:ONLY_INCLUDE_IN this.detectTokensController = new DetectTokensController({ @@ -1036,6 +1060,14 @@ export default class MetamaskController extends EventEmitter { type: MESSAGE_TYPE.SNAP_CONFIRM, requestData: confirmationData, }), + showNotification: (origin, args) => + this.controllerMessenger.call( + 'RateLimitController:call', + origin, + 'showNativeNotification', + origin, + args.message, + ), updateSnapState: this.controllerMessenger.call.bind( this.controllerMessenger, 'SnapController:updateSnapState', diff --git a/shared/constants/permissions.js b/shared/constants/permissions.js index 982f20e7b..c1e4e69e7 100644 --- a/shared/constants/permissions.js +++ b/shared/constants/permissions.js @@ -6,6 +6,7 @@ export const RestrictedMethods = Object.freeze({ eth_accounts: 'eth_accounts', ///: BEGIN:ONLY_INCLUDE_IN(flask) snap_confirm: 'snap_confirm', + snap_notify: 'snap_notify', snap_manageState: 'snap_manageState', 'snap_getBip44Entropy_*': 'snap_getBip44Entropy_*', 'wallet_snap_*': 'wallet_snap_*', @@ -23,5 +24,5 @@ export const EndowmentPermissions = Object.freeze({ }); // Methods / permissions in external packages that we are temporarily excluding. -export const ExcludedSnapPermissions = new Set(['snap_notify']); +export const ExcludedSnapPermissions = new Set([]); ///: END:ONLY_INCLUDE_IN diff --git a/ui/helpers/utils/permission.js b/ui/helpers/utils/permission.js index baa21dc18..7625880ea 100644 --- a/ui/helpers/utils/permission.js +++ b/ui/helpers/utils/permission.js @@ -24,6 +24,11 @@ const PERMISSION_DESCRIPTIONS = deepFreeze({ leftIcon: 'fas fa-user-check', rightIcon: null, }, + [RestrictedMethods.snap_notify]: { + leftIcon: 'fas fa-bell', + label: (t) => t('permission_notifications'), + rightIcon: null, + }, [RestrictedMethods['snap_getBip44Entropy_*']]: { label: (t, permissionName) => { const coinType = permissionName.split('_').slice(-1);