Skip to content

Commit

Permalink
integrate snap notification services
Browse files Browse the repository at this point in the history
  • Loading branch information
hmalik88 committed Oct 19, 2024
1 parent 9716e94 commit 9468e38
Show file tree
Hide file tree
Showing 18 changed files with 168 additions and 251 deletions.
3 changes: 2 additions & 1 deletion app/scripts/background.js
Original file line number Diff line number Diff line change
Expand Up @@ -1078,7 +1078,8 @@ export function setupController(
controller.notificationServicesController.state;

const snapNotificationCount = Object.values(
controller.notificationController.state.notifications,
controller.notificationServicesController.state
.metamaskNotificationsList,
).filter((notification) => notification.readDate === null).length;

const featureAnnouncementCount = isFeatureAnnouncementsEnabled
Expand Down
3 changes: 0 additions & 3 deletions app/scripts/constants/sentry-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,9 +184,6 @@ export const SENTRY_BACKGROUND_STATE = {
allNfts: false,
ignoredNfts: false,
},
NotificationController: {
notifications: false,
},
OnboardingController: {
completedOnboarding: true,
firstTimeFlowType: true,
Expand Down
82 changes: 39 additions & 43 deletions app/scripts/metamask-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,6 @@ import { LoggingController, LogType } from '@metamask/logging-controller';
import { PermissionLogController } from '@metamask/permission-log-controller';

import { RateLimitController } from '@metamask/rate-limit-controller';
import { NotificationController } from '@metamask/notification-controller';
import {
CronjobController,
JsonSnapsRegistry,
Expand Down Expand Up @@ -369,6 +368,7 @@ import createTracingMiddleware from './lib/createTracingMiddleware';
import { PatchStore } from './lib/PatchStore';
import { sanitizeUIState } from './lib/state-utils';

const { TRIGGER_TYPES } = NotificationServicesController.Constants;
export const METAMASK_CONTROLLER_EVENTS = {
// Fired after state changes that impact the extension badge (unapproved msg count)
// The process of updating the badge happens in app/scripts/background.js.
Expand Down Expand Up @@ -1392,13 +1392,6 @@ export default class MetamaskController extends EventEmitter {
},
});

this.notificationController = new NotificationController({
messenger: this.controllerMessenger.getRestricted({
name: 'NotificationController',
}),
state: initState.NotificationController,
});

this.rateLimitController = new RateLimitController({
state: initState.RateLimitController,
messenger: this.controllerMessenger.getRestricted({
Expand Down Expand Up @@ -1426,11 +1419,28 @@ export default class MetamaskController extends EventEmitter {
rateLimitTimeout: 300000,
},
showInAppNotification: {
method: (origin, message) => {
method: (origin, args) => {
const { message, title, footerLink } = args;

const detailedView = {
title,
...(footerLink ? { footerLink } : {}),
interfaceId: args.content,
};

const notification = {
data: {
message,
origin,
...(args.content ? { detailedView } : {}),
},
type: 'snap',
readDate: null,
};

this.controllerMessenger.call(
'NotificationController:show',
origin,
message,
'NotificationServicesController:updateMetamaskNotificationsList',
notification,
);

return null;
Expand Down Expand Up @@ -1488,6 +1498,7 @@ export default class MetamaskController extends EventEmitter {
`${this.approvalController.name}:hasRequest`,
`${this.approvalController.name}:acceptRequest`,
`${this.snapController.name}:get`,
'NotificationServicesController:getNotificationsByType',
],
});

Expand Down Expand Up @@ -2398,7 +2409,6 @@ export default class MetamaskController extends EventEmitter {
SnapController: this.snapController,
CronjobController: this.cronjobController,
SnapsRegistry: this.snapsRegistry,
NotificationController: this.notificationController,
SnapInterfaceController: this.snapInterfaceController,
SnapInsightsController: this.snapInsightsController,
///: BEGIN:ONLY_INCLUDE_IF(build-mmi)
Expand Down Expand Up @@ -2453,7 +2463,6 @@ export default class MetamaskController extends EventEmitter {
SnapController: this.snapController,
CronjobController: this.cronjobController,
SnapsRegistry: this.snapsRegistry,
NotificationController: this.notificationController,
SnapInterfaceController: this.snapInterfaceController,
SnapInsightsController: this.snapInsightsController,
///: BEGIN:ONLY_INCLUDE_IF(build-mmi)
Expand Down Expand Up @@ -2773,14 +2782,15 @@ export default class MetamaskController extends EventEmitter {
origin,
args.message,
),
showInAppNotification: (origin, args) =>
this.controllerMessenger.call(
showInAppNotification: (origin, args) => {
return this.controllerMessenger.call(
'RateLimitController:call',
origin,
'showInAppNotification',
origin,
args.message,
),
args,
);
},
updateSnapState: this.controllerMessenger.call.bind(
this.controllerMessenger,
'SnapController:updateSnapState',
Expand Down Expand Up @@ -2825,24 +2835,6 @@ export default class MetamaskController extends EventEmitter {
};
}

/**
* Deletes the specified notifications from state.
*
* @param {string[]} ids - The notifications ids to delete.
*/
dismissNotifications(ids) {
this.notificationController.dismiss(ids);
}

/**
* Updates the readDate attribute of the specified notifications.
*
* @param {string[]} ids - The notifications ids to mark as read.
*/
markNotificationsAsRead(ids) {
this.notificationController.markRead(ids);
}

/**
* Sets up BaseController V2 event subscriptions. Currently, this includes
* the subscriptions necessary to notify permission subjects of account
Expand Down Expand Up @@ -3042,16 +3034,16 @@ export default class MetamaskController extends EventEmitter {
this.controllerMessenger.subscribe(
`${this.snapController.name}:snapUninstalled`,
(truncatedSnap) => {
const notificationIds = Object.values(
this.notificationController.state.notifications,
const notificationIds = this.getNotificationsByType(
TRIGGER_TYPES.SNAP,
).reduce((idList, notification) => {
if (notification.origin === truncatedSnap.id) {
idList.push(notification.id);
}
return idList;
}, []);

this.dismissNotifications(notificationIds);
this.deleteNotificationsById(notificationIds);

const snapId = truncatedSnap.id;
const snapCategory = this._getSnapMetadata(snapId)?.category;
Expand Down Expand Up @@ -3809,8 +3801,6 @@ export default class MetamaskController extends EventEmitter {
this.controllerMessenger,
'SnapController:revokeDynamicPermissions',
),
dismissNotifications: this.dismissNotifications.bind(this),
markNotificationsAsRead: this.markNotificationsAsRead.bind(this),
disconnectOriginFromSnap: this.controllerMessenger.call.bind(
this.controllerMessenger,
'SnapController:disconnectOrigin',
Expand Down Expand Up @@ -4094,6 +4084,14 @@ export default class MetamaskController extends EventEmitter {
notificationServicesController.fetchAndUpdateMetamaskNotifications.bind(
notificationServicesController,
),
deleteNotificationsById:
notificationServicesController.deleteNotificationsById.bind(
notificationServicesController,
),
getNotificationsByType:
notificationServicesController.getNotificationsByType.bind(
notificationServicesController,
),
markMetamaskNotificationsAsRead:
notificationServicesController.markMetamaskNotificationsAsRead.bind(
notificationServicesController,
Expand Down Expand Up @@ -4322,8 +4320,6 @@ export default class MetamaskController extends EventEmitter {

// Clear snap state
this.snapController.clearState();
// Clear notification state
this.notificationController.clear();

// clear accounts in AccountTrackerController
this.accountTrackerController.clearAccounts();
Expand Down
28 changes: 0 additions & 28 deletions app/scripts/metamask-controller.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -245,17 +245,6 @@ const firstTimeState = {
},
),
},
NotificationController: {
notifications: {
[NOTIFICATION_ID]: {
id: NOTIFICATION_ID,
origin: 'local:http://localhost:8086/',
createdDate: 1652967897732,
readDate: null,
message: 'Hello, http://localhost:8086!',
},
},
},
PhishingController: {
phishingLists: [
{
Expand Down Expand Up @@ -1812,23 +1801,6 @@ describe('MetaMaskController', () => {
});
});

describe('markNotificationsAsRead', () => {
it('marks the notification as read', () => {
metamaskController.markNotificationsAsRead([NOTIFICATION_ID]);
const readNotification =
metamaskController.getState().notifications[NOTIFICATION_ID];
expect(readNotification.readDate).not.toBeNull();
});
});

describe('dismissNotifications', () => {
it('deletes the notification from state', () => {
metamaskController.dismissNotifications([NOTIFICATION_ID]);
const state = metamaskController.getState().notifications;
expect(Object.values(state)).not.toContain(NOTIFICATION_ID);
});
});

describe('getTokenStandardAndDetails', () => {
it('gets token data from the token list if available, and with a balance retrieved by fetchTokenBalance', async () => {
const providerResultStub = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,6 @@
"allNfts": "object",
"ignoredNfts": "object"
},
"NotificationController": { "notifications": "object" },
"NotificationServicesController": {
"subscriptionAccountsSeen": "object",
"isMetamaskNotificationsFeatureSeen": "boolean",
Expand Down
12 changes: 6 additions & 6 deletions ui/hooks/metamask-notifications/useCounter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ import {
getFeatureAnnouncementsUnreadCount,
getOnChainMetamaskNotificationsReadCount,
getOnChainMetamaskNotificationsUnreadCount,
getSnapNotificationsReadCount,
getSnapNotificationsUnreadCount,
} from '../../selectors/metamask-notifications/metamask-notifications';
import {
getReadNotificationsCount,
getUnreadNotificationsCount,
} from '../../selectors';

const useSnapNotificationdCount = () => {
const unreadSnapNotificationsCount = useSelector(getUnreadNotificationsCount);
const readSnapNotificationsCount = useSelector(getReadNotificationsCount);
const unreadSnapNotificationsCount = useSelector(
getSnapNotificationsUnreadCount,
);
const readSnapNotificationsCount = useSelector(getSnapNotificationsReadCount);
return { unreadSnapNotificationsCount, readSnapNotificationsCount };
};

Expand Down
24 changes: 18 additions & 6 deletions ui/pages/notifications/notification-components/snap/snap.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,38 @@
import React, { useContext } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { useHistory } from 'react-router-dom';
import { NotificationServicesController } from '@metamask/notification-services-controller'; } from '@metamask/notification-services-controller';
import {
MetaMetricsEventCategory,
MetaMetricsEventName,
} from '../../../../../shared/constants/metametrics';
import { MetaMetricsContext } from '../../../../contexts/metametrics';
import { NotificationListItemSnap } from '../../../../components/multichain';
import type { SnapNotification } from '../../snap/types/types';
import { getSnapsMetadata } from '../../../../selectors';
import { markNotificationsAsRead } from '../../../../store/actions';
import { getSnapRoute, getSnapName } from '../../../../helpers/utils/util';
import { useMarkNotificationAsRead } from '../../../../hooks/metamask-notifications/useNotifications';

type SnapComponentProps = {
snapNotification: SnapNotification;
snapNotification: NotificationServicesController.Types.INotification;
};

export const SnapComponent = ({ snapNotification }: SnapComponentProps) => {
const dispatch = useDispatch();
const history = useHistory();
const trackEvent = useContext(MetaMetricsContext);
const { markNotificationAsRead } = useMarkNotificationAsRead();

const snapsMetadata = useSelector(getSnapsMetadata);

const snapsNameGetter = getSnapName(snapsMetadata);

const handleSnapClick = () => {
dispatch(markNotificationsAsRead([snapNotification.id]));
markNotificationAsRead([
{
id: snapNotification.id,
type: snapNotification.type,
isRead: snapNotification.isRead,
},
]);
trackEvent({
category: MetaMetricsEventCategory.NotificationInteraction,
event: MetaMetricsEventName.NotificationClicked,
Expand All @@ -39,7 +45,13 @@ export const SnapComponent = ({ snapNotification }: SnapComponentProps) => {
};

const handleSnapButton = () => {
dispatch(markNotificationsAsRead([snapNotification.id]));
markNotificationAsRead([
{
id: snapNotification.id,
type: snapNotification.type,
isRead: snapNotification.isRead,
},
]);
trackEvent({
category: MetaMetricsEventCategory.NotificationInteraction,
event: MetaMetricsEventName.NotificationClicked,
Expand Down
6 changes: 2 additions & 4 deletions ui/pages/notifications/notifications-list-read-all-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,13 @@ import { getUnreadNotifications } from '../../selectors';
import { markNotificationsAsRead } from '../../store/actions';
import { Box, Button, ButtonVariant } from '../../components/component-library';
import { BlockSize } from '../../helpers/constants/design-system';
import type { NotificationType } from './notifications';
import { SNAP } from './snap/types/types';

type Notification = NotificationServicesController.Types.INotification;
type MarkAsReadNotificationsParam =
NotificationServicesController.Types.MarkAsReadNotificationsParam;

export type NotificationsListReadAllButtonProps = {
notifications: NotificationType[];
notifications: Notification[];
};

export const NotificationsListReadAllButton = ({
Expand All @@ -40,7 +38,7 @@ export const NotificationsListReadAllButton = ({
.filter(
(notification): notification is Notification =>
(notification as Notification).id !== undefined &&
notification.type !== SNAP,
notification.type !== TRIGGER_TYPES.SNAP,
)
.map((notification: Notification) => ({
id: notification.id,
Expand Down
2 changes: 1 addition & 1 deletion ui/pages/notifications/notifications-list.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { NotificationsList } from './notifications-list';
import { TAB_KEYS } from './notifications';

jest.mock('../../store/actions', () => ({
deleteExpiredNotifications: jest.fn(() => () => Promise.resolve()),
deleteExpiredSnapNotifications: jest.fn(() => () => Promise.resolve()),
fetchAndUpdateMetamaskNotifications: jest.fn(() => () => Promise.resolve()),
}));

Expand Down
8 changes: 4 additions & 4 deletions ui/pages/notifications/notifications-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ import { SnapComponent } from './notification-components/snap/snap';
import { NotificationsPlaceholder } from './notifications-list-placeholder';
import { NotificationsListTurnOnNotifications } from './notifications-list-turn-on-notifications';
import { NotificationsListItem } from './notifications-list-item';
import { NotificationType, TAB_KEYS } from './notifications';
import { type Notification, TAB_KEYS } from './notifications';
import { NotificationsListReadAllButton } from './notifications-list-read-all-button';

export type NotificationsListProps = {
activeTab: TAB_KEYS;
notifications: NotificationType[];
notifications: Notification[];
isLoading: boolean;
isError: boolean;
notificationsCount: number;
Expand Down Expand Up @@ -62,9 +62,9 @@ function ErrorContent() {
);
}

function NotificationItem(props: { notification: NotificationType }) {
function NotificationItem(props: { notification: Notification }) {
const { notification } = props;
if (notification.type === 'SNAP') {
if (notification.type === TRIGGER_TYPES.SNAP) {
return <SnapComponent snapNotification={notification} />;
}

Expand Down
Loading

0 comments on commit 9468e38

Please sign in to comment.