From 5e262cf1f02cfac34c349f8ab4e52c81e4873172 Mon Sep 17 00:00:00 2001 From: Matteo Scurati Date: Thu, 6 Jun 2024 12:11:42 +0200 Subject: [PATCH 1/5] feat: :sparkles: update the counter over the fox to handle notifications --- app/scripts/background.js | 94 ++++++++++++++++--- .../metamask-notifications.ts | 31 +++++- app/scripts/metamask-controller.js | 5 + 3 files changed, 115 insertions(+), 15 deletions(-) diff --git a/app/scripts/background.js b/app/scripts/background.js index f08ce99e8f72..44a932afecac 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -80,6 +80,8 @@ import DesktopManager from '@metamask/desktop/dist/desktop-manager'; ///: END:ONLY_INCLUDE_IF /* eslint-enable import/order */ +import { TRIGGER_TYPES } from './controllers/metamask-notifications/constants/notification-schema'; + // Setup global hook for improved Sentry state snapshots during initialization const inTest = process.env.IN_TEST; const localStore = inTest ? new ReadOnlyNetworkStore() : new LocalStore(); @@ -826,6 +828,21 @@ export function setupController( updateBadge, ); + controller.controllerMessenger.subscribe( + METAMASK_CONTROLLER_EVENTS.METAMASK_NOTIFICATIONS_LIST_UPDATED, + updateBadge, + ); + + controller.controllerMessenger.subscribe( + METAMASK_CONTROLLER_EVENTS.METAMASK_NOTIFICATIONS_MARK_AS_READ, + updateBadge, + ); + + controller.controllerMessenger.subscribe( + METAMASK_CONTROLLER_EVENTS.NOTIFICATIONS_STATE_CHANGE, + updateBadge, + ); + controller.txController.initApprovals(); /** @@ -833,30 +850,81 @@ export function setupController( * The number reflects the current number of pending transactions or message signatures needing user approval. */ function updateBadge() { + const unapprovedTransactionCount = getUnapprovedTransactionCount(); + const unreadNotificationsCount = getUnreadNotificationsCount(); + let label = ''; - const count = getUnapprovedTransactionCount(); - if (count) { - label = String(count); + // eslint-disable-next-line @metamask/design-tokens/color-no-hex + let badgeColor = '#0376C9'; + if (unapprovedTransactionCount) { + label = '\u22EF'; // unicode ellipsis + } else if (unreadNotificationsCount > 0) { + label = + unreadNotificationsCount > 9 + ? String('9+') + : String(unreadNotificationsCount); + // eslint-disable-next-line @metamask/design-tokens/color-no-hex + badgeColor = '#D73847'; } - // browserAction has been replaced by action in MV3 - if (isManifestV3) { - browser.action.setBadgeText({ text: label }); - browser.action.setBadgeBackgroundColor({ color: '#037DD6' }); - } else { - browser.browserAction.setBadgeText({ text: label }); - browser.browserAction.setBadgeBackgroundColor({ color: '#037DD6' }); + + try { + const badgeText = { text: label }; + const badgeBackgroundColor = { color: badgeColor }; + + if (isManifestV3) { + browser.action.setBadgeText(badgeText); + browser.action.setBadgeBackgroundColor(badgeBackgroundColor); + } else { + browser.browserAction.setBadgeText(badgeText); + browser.browserAction.setBadgeBackgroundColor(badgeBackgroundColor); + } + } catch (error) { + console.error('Error updating browser badge:', error); } } function getUnapprovedTransactionCount() { - let count = + let unapprovedTransactionCount = controller.appStateController.waitingForUnlock.length + controller.approvalController.getTotalApprovalCount(); if (controller.preferencesController.getUseRequestQueue()) { - count += controller.queuedRequestController.state.queuedRequestCount; + unapprovedTransactionCount += + controller.queuedRequestController.state.queuedRequestCount; } - return count; + return unapprovedTransactionCount; + } + + function getUnreadNotificationsCount() { + const { isMetamaskNotificationsEnabled, isFeatureAnnouncementsEnabled } = + controller.metamaskNotificationsController.state; + + const snapNotificationCount = Object.values( + controller.notificationController.state.notifications, + ).filter((notification) => notification.readDate === null).length; + + const featureAnnouncementCount = isFeatureAnnouncementsEnabled + ? controller.metamaskNotificationsController.state.metamaskNotificationsList.filter( + (notification) => + !notification.isRead && + notification.type === TRIGGER_TYPES.FEATURES_ANNOUNCEMENT, + ).length + : 0; + + const walletNotificationCount = isMetamaskNotificationsEnabled + ? controller.metamaskNotificationsController.state.metamaskNotificationsList.filter( + (notification) => + !notification.isRead && + notification.type !== TRIGGER_TYPES.FEATURES_ANNOUNCEMENT, + ).length + : 0; + + const unreadNotificationsCount = + snapNotificationCount + + featureAnnouncementCount + + walletNotificationCount; + + return unreadNotificationsCount; } notificationManager.on( diff --git a/app/scripts/controllers/metamask-notifications/metamask-notifications.ts b/app/scripts/controllers/metamask-notifications/metamask-notifications.ts index 3153b4f07151..3c1488b9d35a 100644 --- a/app/scripts/controllers/metamask-notifications/metamask-notifications.ts +++ b/app/scripts/controllers/metamask-notifications/metamask-notifications.ts @@ -175,6 +175,16 @@ export declare type MetamaskNotificationsControllerSelectIsMetamaskNotifications handler: MetamaskNotificationsController['selectIsMetamaskNotificationsEnabled']; }; +export type MetamaskNotificationsControllerNotificationsListUpdatedEvent = { + type: `${typeof controllerName}:notificationsListUpdated`; + payload: [Notification[]]; +}; + +export type MetamaskNotificationsControllerMarkNotificationsAsRead = { + type: `${typeof controllerName}:markNotificationsAsRead`; + payload: [Notification[]]; +}; + // Messenger Actions export type Actions = | MetamaskNotificationsControllerUpdateMetamaskNotificationsList @@ -209,7 +219,9 @@ export type MetamaskNotificationsControllerMessengerEvents = // Allowed Events export type AllowedEvents = | KeyringControllerStateChangeEvent - | PushPlatformNotificationsControllerOnNewNotificationEvent; + | PushPlatformNotificationsControllerOnNewNotificationEvent + | MetamaskNotificationsControllerNotificationsListUpdatedEvent + | MetamaskNotificationsControllerMarkNotificationsAsRead; // Type for the messenger of MetamaskNotificationsController export type MetamaskNotificationsControllerMessenger = @@ -1004,6 +1016,11 @@ export class MetamaskNotificationsController extends BaseController< state.metamaskNotificationsList = metamaskNotifications; }); + this.messagingSystem.publish( + `${controllerName}:notificationsListUpdated`, + this.state.metamaskNotificationsList, + ); + this.#setIsFetchingMetamaskNotifications(false); return metamaskNotifications; } catch (err) { @@ -1068,7 +1085,7 @@ export class MetamaskNotificationsController extends BaseController< log.warn('Something failed when marking notifications as read', err); } - // Update the state (state is also used on counter & badge) + // Update the state this.update((state) => { const currentReadList = state.metamaskNotificationsReadList; const newReadIds = [...featureAnnouncementNotificationIds]; @@ -1085,6 +1102,12 @@ export class MetamaskNotificationsController extends BaseController< }, ); }); + + // Publish the event + this.messagingSystem.publish( + `${controllerName}:markNotificationsAsRead`, + this.state.metamaskNotificationsList, + ); } /** @@ -1117,6 +1140,10 @@ export class MetamaskNotificationsController extends BaseController< notification, ...state.metamaskNotificationsList, ]; + this.messagingSystem.publish( + `${controllerName}:notificationsListUpdated`, + state.metamaskNotificationsList, + ); } }); } diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 043e897fe858..927e4954e13d 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -341,6 +341,11 @@ export const METAMASK_CONTROLLER_EVENTS = { // TODO: Add this and similar enums to the `controllers` repo and export them APPROVAL_STATE_CHANGE: 'ApprovalController:stateChange', QUEUED_REQUEST_STATE_CHANGE: 'QueuedRequestController:stateChange', + METAMASK_NOTIFICATIONS_LIST_UPDATED: + 'MetamaskNotificationsController:notificationsListUpdated', + METAMASK_NOTIFICATIONS_MARK_AS_READ: + 'MetamaskNotificationsController:markNotificationsAsRead', + NOTIFICATIONS_STATE_CHANGE: 'NotificationController:stateChange', }; // stream channels From 622f1aae756bfd4ff55de9853dc6398b079b7994 Mon Sep 17 00:00:00 2001 From: Matteo Scurati Date: Thu, 6 Jun 2024 15:22:41 +0200 Subject: [PATCH 2/5] feat: :sparkles: use try/catch to avoid errors for the counters --- app/scripts/background.js | 82 ++++++++++++++++++++++----------------- 1 file changed, 46 insertions(+), 36 deletions(-) diff --git a/app/scripts/background.js b/app/scripts/background.js index fec940cfd26d..f8ba217dc05e 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -883,47 +883,57 @@ export function setupController( } function getUnapprovedTransactionCount() { - let unapprovedTransactionCount = - controller.appStateController.waitingForUnlock.length + - controller.approvalController.getTotalApprovalCount(); + try { + let unapprovedTransactionCount = + controller.appStateController.waitingForUnlock.length + + controller.approvalController.getTotalApprovalCount(); - if (controller.preferencesController.getUseRequestQueue()) { - unapprovedTransactionCount += - controller.queuedRequestController.state.queuedRequestCount; + if (controller.preferencesController.getUseRequestQueue()) { + unapprovedTransactionCount += + controller.queuedRequestController.state.queuedRequestCount; + } + return unapprovedTransactionCount; + } catch (error) { + console.error('Failed to get unapproved transaction count:', error); + return 0; } - return unapprovedTransactionCount; } function getUnreadNotificationsCount() { - const { isMetamaskNotificationsEnabled, isFeatureAnnouncementsEnabled } = - controller.metamaskNotificationsController.state; - - const snapNotificationCount = Object.values( - controller.notificationController.state.notifications, - ).filter((notification) => notification.readDate === null).length; - - const featureAnnouncementCount = isFeatureAnnouncementsEnabled - ? controller.metamaskNotificationsController.state.metamaskNotificationsList.filter( - (notification) => - !notification.isRead && - notification.type === TRIGGER_TYPES.FEATURES_ANNOUNCEMENT, - ).length - : 0; - - const walletNotificationCount = isMetamaskNotificationsEnabled - ? controller.metamaskNotificationsController.state.metamaskNotificationsList.filter( - (notification) => - !notification.isRead && - notification.type !== TRIGGER_TYPES.FEATURES_ANNOUNCEMENT, - ).length - : 0; - - const unreadNotificationsCount = - snapNotificationCount + - featureAnnouncementCount + - walletNotificationCount; - - return unreadNotificationsCount; + try { + const { isMetamaskNotificationsEnabled, isFeatureAnnouncementsEnabled } = + controller.metamaskNotificationsController.state; + + const snapNotificationCount = Object.values( + controller.notificationController.state.notifications, + ).filter((notification) => notification.readDate === null).length; + + const featureAnnouncementCount = isFeatureAnnouncementsEnabled + ? controller.metamaskNotificationsController.state.metamaskNotificationsList.filter( + (notification) => + !notification.isRead && + notification.type === TRIGGER_TYPES.FEATURES_ANNOUNCEMENT, + ).length + : 0; + + const walletNotificationCount = isMetamaskNotificationsEnabled + ? controller.metamaskNotificationsController.state.metamaskNotificationsList.filter( + (notification) => + !notification.isRead && + notification.type !== TRIGGER_TYPES.FEATURES_ANNOUNCEMENT, + ).length + : 0; + + const unreadNotificationsCount = + snapNotificationCount + + featureAnnouncementCount + + walletNotificationCount; + + return unreadNotificationsCount; + } catch (error) { + console.error('Failed to get unread notifications count:', error); + return 0; + } } notificationManager.on( From 0542b36825ae40da3d49c9617d98caa5554305ad Mon Sep 17 00:00:00 2001 From: Matteo Scurati Date: Fri, 7 Jun 2024 12:39:41 +0200 Subject: [PATCH 3/5] feat: :recycle: use constants to handle badge's variables --- app/scripts/background.js | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/app/scripts/background.js b/app/scripts/background.js index f8ba217dc05e..5feb21e3fc60 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -82,6 +82,13 @@ import DesktopManager from '@metamask/desktop/dist/desktop-manager'; import { TRIGGER_TYPES } from './controllers/metamask-notifications/constants/notification-schema'; +// eslint-disable-next-line @metamask/design-tokens/color-no-hex +const BADGE_COLOR_APPROVAL = '#0376C9'; +// eslint-disable-next-line @metamask/design-tokens/color-no-hex +const BADGE_COLOR_NOTIFICATION = '#D73847'; +const BADGE_LABEL_APPROVAL = '\u22EF'; // unicode ellipsis +const BADGE_MAX_NOTIFICATION_COUNT = '9'; + // Setup global hook for improved Sentry state snapshots during initialization const inTest = process.env.IN_TEST; const localStore = inTest ? new ReadOnlyNetworkStore() : new LocalStore(); @@ -849,21 +856,20 @@ export function setupController( * The number reflects the current number of pending transactions or message signatures needing user approval. */ function updateBadge() { - const unapprovedTransactionCount = getUnapprovedTransactionCount(); + const pendingApprovalCount = getPendingApprovalCount(); const unreadNotificationsCount = getUnreadNotificationsCount(); let label = ''; - // eslint-disable-next-line @metamask/design-tokens/color-no-hex - let badgeColor = '#0376C9'; - if (unapprovedTransactionCount) { - label = '\u22EF'; // unicode ellipsis + let badgeColor = BADGE_COLOR_APPROVAL; + + if (pendingApprovalCount) { + label = BADGE_LABEL_APPROVAL; } else if (unreadNotificationsCount > 0) { label = - unreadNotificationsCount > 9 - ? String('9+') + unreadNotificationsCount > BADGE_MAX_NOTIFICATION_COUNT + ? `${BADGE_MAX_NOTIFICATION_COUNT}+` : String(unreadNotificationsCount); - // eslint-disable-next-line @metamask/design-tokens/color-no-hex - badgeColor = '#D73847'; + badgeColor = BADGE_COLOR_NOTIFICATION; } try { @@ -882,17 +888,17 @@ export function setupController( } } - function getUnapprovedTransactionCount() { + function getPendingApprovalCount() { try { - let unapprovedTransactionCount = + let pendingApprovalCount = controller.appStateController.waitingForUnlock.length + controller.approvalController.getTotalApprovalCount(); if (controller.preferencesController.getUseRequestQueue()) { - unapprovedTransactionCount += + pendingApprovalCount += controller.queuedRequestController.state.queuedRequestCount; } - return unapprovedTransactionCount; + return pendingApprovalCount; } catch (error) { console.error('Failed to get unapproved transaction count:', error); return 0; @@ -941,7 +947,7 @@ export function setupController( ({ automaticallyClosed }) => { if (!automaticallyClosed) { rejectUnapprovedNotifications(); - } else if (getUnapprovedTransactionCount() > 0) { + } else if (getPendingApprovalCount() > 0) { triggerUi(); } From 843a2d501de6d86d5c2a45f302981a921ad02b5d Mon Sep 17 00:00:00 2001 From: Matteo Scurati Date: Fri, 7 Jun 2024 15:25:54 +0200 Subject: [PATCH 4/5] fix: :bug: convert a string into a number --- app/scripts/background.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/scripts/background.js b/app/scripts/background.js index 5feb21e3fc60..ecb16bc16d55 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -87,7 +87,7 @@ const BADGE_COLOR_APPROVAL = '#0376C9'; // eslint-disable-next-line @metamask/design-tokens/color-no-hex const BADGE_COLOR_NOTIFICATION = '#D73847'; const BADGE_LABEL_APPROVAL = '\u22EF'; // unicode ellipsis -const BADGE_MAX_NOTIFICATION_COUNT = '9'; +const BADGE_MAX_NOTIFICATION_COUNT = 9; // Setup global hook for improved Sentry state snapshots during initialization const inTest = process.env.IN_TEST; From 8c6ee98b82fcf11bc93eda01fb8619b578fc626b Mon Sep 17 00:00:00 2001 From: Matteo Scurati Date: Tue, 11 Jun 2024 13:51:48 +0200 Subject: [PATCH 5/5] feat: :speech_balloon: update a console log --- app/scripts/background.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/scripts/background.js b/app/scripts/background.js index cd257b3a11c8..ab7547dc249d 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -849,7 +849,7 @@ export function setupController( } return pendingApprovalCount; } catch (error) { - console.error('Failed to get unapproved transaction count:', error); + console.error('Failed to get pending approval count:', error); return 0; } }