From e87c936ce82dee9656a2f2f65d674b2c254e8f4d Mon Sep 17 00:00:00 2001 From: Frederik Bolding Date: Mon, 7 Mar 2022 11:31:16 +0100 Subject: [PATCH 01/17] Revive NotificationControllerV2 for in-app notifications --- .../NotificationControllerV2.test.ts | 134 +++++++++++++ src/notification/NotificationControllerV2.ts | 183 ++++++++++++++++++ 2 files changed, 317 insertions(+) create mode 100644 src/notification/NotificationControllerV2.test.ts create mode 100644 src/notification/NotificationControllerV2.ts diff --git a/src/notification/NotificationControllerV2.test.ts b/src/notification/NotificationControllerV2.test.ts new file mode 100644 index 0000000000..e421ca120a --- /dev/null +++ b/src/notification/NotificationControllerV2.test.ts @@ -0,0 +1,134 @@ +import { ControllerMessenger } from '../ControllerMessenger'; +import { GetSubjectMetadataState } from '../subject-metadata'; +import { + ControllerActions, + GetNotificationState, + NotificationController, + NotificationMessenger, + NotificationStateChange, + NotificationType, + ShowNotification, +} from './NotificationControllerV2'; + +const name = 'NotificationControllerV2'; + +/** + * Constructs a unrestricted controller messenger. + * + * @returns A unrestricted controller messenger. + */ +function getUnrestrictedMessenger() { + return new ControllerMessenger< + GetNotificationState | ShowNotification | GetSubjectMetadataState, + NotificationStateChange + >(); +} + +/** + * Constructs a restricted controller messenger. + * + * @param controllerMessenger - An optional unrestricted messenger + * @returns A restricted controller messenger. + */ +function getRestrictedMessenger( + controllerMessenger = getUnrestrictedMessenger(), +) { + return controllerMessenger.getRestricted< + typeof name, + ControllerActions['type'] | GetSubjectMetadataState['type'], + never + >({ + name, + allowedActions: [ + 'SubjectMetadataController:getState', + 'NotificationControllerV2:show', + ], + }) as NotificationMessenger; +} + +const SNAP_NAME = 'Test Snap Name'; +const origin = 'snap_test'; +const message = 'foo'; + +const subjectMetadata = { + snap_test: { origin: 'snap_test', name: SNAP_NAME }, +}; + +describe('NotificationControllerV2', () => { + it('action: NotificationControllerV2:show', async () => { + const unrestricted = getUnrestrictedMessenger(); + const messenger = getRestrictedMessenger(unrestricted); + const callActionSpy = jest + .spyOn(messenger, 'call') + .mockImplementationOnce((..._args: any) => ({ + subjectMetadata, + })); + + const showNativeNotification = jest.fn(); + new NotificationController({ + showNativeNotification, + messenger, + }); + + expect( + await unrestricted.call('NotificationControllerV2:show', origin, { + type: NotificationType.Native, + message, + }), + ).toBeUndefined(); + expect(showNativeNotification).toHaveBeenCalledTimes(1); + expect(callActionSpy).toHaveBeenCalledTimes(1); + }); + + it('uses showNativeNotification to show a notification', () => { + const messenger = getRestrictedMessenger(); + const callActionSpy = jest + .spyOn(messenger, 'call') + .mockImplementationOnce((..._args: any) => ({ + subjectMetadata, + })); + + const showNativeNotification = jest.fn(); + const controller = new NotificationController({ + showNativeNotification, + messenger, + }); + expect( + controller.show(origin, { + type: NotificationType.Native, + message, + }), + ).toBeUndefined(); + expect(showNativeNotification).toHaveBeenCalledWith(SNAP_NAME, message); + expect(callActionSpy).toHaveBeenCalledTimes(1); + expect(callActionSpy).toHaveBeenCalledWith( + 'SubjectMetadataController:getState', + ); + }); + + it('falls back to origin if no metadata present', () => { + const messenger = getRestrictedMessenger(); + const callActionSpy = jest + .spyOn(messenger, 'call') + .mockImplementationOnce((..._args: any) => ({ + subjectMetadata: {}, + })); + + const showNativeNotification = jest.fn(); + const controller = new NotificationController({ + showNativeNotification, + messenger, + }); + expect( + controller.show(origin, { + type: NotificationType.Native, + message, + }), + ).toBeUndefined(); + expect(showNativeNotification).toHaveBeenCalledWith(origin, message); + expect(callActionSpy).toHaveBeenCalledTimes(1); + expect(callActionSpy).toHaveBeenCalledWith( + 'SubjectMetadataController:getState', + ); + }); +}); diff --git a/src/notification/NotificationControllerV2.ts b/src/notification/NotificationControllerV2.ts new file mode 100644 index 0000000000..e70fb59cce --- /dev/null +++ b/src/notification/NotificationControllerV2.ts @@ -0,0 +1,183 @@ +import type { Patch } from 'immer'; +import { nanoid } from 'nanoid'; + +import { BaseController } from '../BaseControllerV2'; + +import type { RestrictedControllerMessenger } from '../ControllerMessenger'; +import type { GetSubjectMetadataState } from '../subject-metadata'; + +/** + * @type NotificationState + * @property notifications - + */ +export type NotificationState = { + notifications: Record; +}; + +export type Notification = { + id: string; + type: NotificationType; + title: string; + message: string; +}; + +export enum NotificationType { + Native = 'native', + InApp = 'in-app', +} + +export interface NotificationArgs { + /** + * Enum type to determine notification type. + */ + type: NotificationType; + + /** + * A message to show on the notification. + */ + message: string; +} + +const name = 'NotificationControllerV2'; + +export type NotificationStateChange = { + type: `${typeof name}:stateChange`; + payload: [NotificationState, Patch[]]; +}; + +export type GetNotificationState = { + type: `${typeof name}:getState`; + handler: () => NotificationState; +}; + +export type ShowNotification = { + type: `${typeof name}:show`; + handler: NotificationController['show']; +}; + +export type ControllerActions = GetNotificationState | ShowNotification; + +type AllowedActions = GetSubjectMetadataState; + +export type NotificationMessenger = RestrictedControllerMessenger< + typeof name, + ControllerActions | AllowedActions, + NotificationStateChange, + AllowedActions['type'], + never +>; + +const metadata = { + notifications: { persist: true, anonymous: false }, +}; + +const defaultState = { + notifications: {}, +}; + +/** + * Controller that handles showing notifications to the user and rate limiting origins + */ +export class NotificationController extends BaseController< + typeof name, + NotificationState, + NotificationMessenger +> { + private showNativeNotification; + + /** + * Creates a NotificationController instance. + * + * @param options - Constructor options. + * @param options.messenger - A reference to the messaging system. + * @param options.state - Initial state to set on this controller. + * @param options.showNativeNotification - Function that shows a native notification in the consumer + */ + constructor({ + messenger, + state, + showNativeNotification, + }: { + messenger: NotificationMessenger; + state?: Partial; + showNativeNotification: ( + title: string, + message: string, + url?: string, + ) => void; + }) { + super({ + name, + metadata, + messenger, + state: { ...defaultState, ...state }, + }); + this.showNativeNotification = showNativeNotification; + + this.messagingSystem.registerActionHandler( + `${name}:show` as const, + (origin: string, args: NotificationArgs) => this.show(origin, args), + ); + } + + /** + * Shows a notification. + * + * @param origin - The origin trying to send a notification + * @param args - Notification arguments, containing the notification message etc. + */ + show(origin: string, args: NotificationArgs) { + const subjectMetadataState = this.messagingSystem.call( + 'SubjectMetadataController:getState', + ); + + const originMetadata = subjectMetadataState.subjectMetadata[origin]; + + switch (args.type) { + case NotificationType.Native: + this.showNativeNotification( + originMetadata?.name ?? origin, + args.message, + ); + break; + case NotificationType.InApp: + this.add(originMetadata?.name ?? origin, args.message); + break; + default: + throw new Error('Invalid notification type'); + } + } + + private add(title: string, message: string) { + const id = nanoid(); + const notification = { + id, + type: NotificationType.InApp, + title, + message, + }; + this.update((state) => { + state.notifications[id] = notification; + }); + } + + /** + * Dimisses a notification. + * + * @param id - The notification's ID + */ + dismiss(id: string) { + this.update((state) => { + delete state.notifications[id]; + }); + } + + /** + * Gets the current number of notifications. + * + * @returns The number of current notifications + */ + getCount() { + return Object.values(this.state.notifications).length; + } +} From 9c99015f75b8464f1fc03b6157cbbe50aa8f8214 Mon Sep 17 00:00:00 2001 From: Frederik Bolding Date: Tue, 8 Mar 2022 10:46:30 +0100 Subject: [PATCH 02/17] Add more functionality and tests --- .../NotificationControllerV2.test.ts | 109 +++++++++++++++++- src/notification/NotificationControllerV2.ts | 61 ++++++++-- 2 files changed, 161 insertions(+), 9 deletions(-) diff --git a/src/notification/NotificationControllerV2.test.ts b/src/notification/NotificationControllerV2.test.ts index e421ca120a..3e9b0d124d 100644 --- a/src/notification/NotificationControllerV2.test.ts +++ b/src/notification/NotificationControllerV2.test.ts @@ -2,6 +2,9 @@ import { ControllerMessenger } from '../ControllerMessenger'; import { GetSubjectMetadataState } from '../subject-metadata'; import { ControllerActions, + DismissNotification, + GetCurrentNotification, + GetNotificationCount, GetNotificationState, NotificationController, NotificationMessenger, @@ -19,7 +22,12 @@ const name = 'NotificationControllerV2'; */ function getUnrestrictedMessenger() { return new ControllerMessenger< - GetNotificationState | ShowNotification | GetSubjectMetadataState, + | GetNotificationState + | ShowNotification + | DismissNotification + | GetNotificationCount + | GetCurrentNotification + | GetSubjectMetadataState, NotificationStateChange >(); } @@ -55,7 +63,7 @@ const subjectMetadata = { }; describe('NotificationControllerV2', () => { - it('action: NotificationControllerV2:show', async () => { + it('action: NotificationControllerV2:show native notifications', async () => { const unrestricted = getUnrestrictedMessenger(); const messenger = getRestrictedMessenger(unrestricted); const callActionSpy = jest @@ -80,6 +88,103 @@ describe('NotificationControllerV2', () => { expect(callActionSpy).toHaveBeenCalledTimes(1); }); + it('action: NotificationControllerV2:show in-app notifications', async () => { + const unrestricted = getUnrestrictedMessenger(); + const messenger = getRestrictedMessenger(unrestricted); + const callActionSpy = jest + .spyOn(messenger, 'call') + .mockImplementationOnce((..._args: any) => ({ + subjectMetadata, + })); + + const showNativeNotification = jest.fn(); + new NotificationController({ + showNativeNotification, + messenger, + }); + + expect( + await unrestricted.call('NotificationControllerV2:show', origin, { + type: NotificationType.InApp, + message, + }), + ).toBeUndefined(); + expect(showNativeNotification).toHaveBeenCalledTimes(0); + const count = await unrestricted.call('NotificationControllerV2:getCount'); + expect(count).toBe(1); + expect(callActionSpy).toHaveBeenCalledTimes(1); + }); + + it('action: NotificationControllerV2:getCurrent', async () => { + const unrestricted = getUnrestrictedMessenger(); + const messenger = getRestrictedMessenger(unrestricted); + const callActionSpy = jest + .spyOn(messenger, 'call') + .mockImplementationOnce((..._args: any) => ({ + subjectMetadata, + })); + + const showNativeNotification = jest.fn(); + new NotificationController({ + showNativeNotification, + messenger, + }); + + expect( + await unrestricted.call('NotificationControllerV2:show', origin, { + type: NotificationType.InApp, + message, + }), + ).toBeUndefined(); + expect(showNativeNotification).toHaveBeenCalledTimes(0); + const current = await unrestricted.call( + 'NotificationControllerV2:getCurrent', + ); + expect(current).toStrictEqual({ + id: expect.any(String), + message, + title: SNAP_NAME, + type: NotificationType.InApp, + }); + expect(callActionSpy).toHaveBeenCalledTimes(1); + }); + + it('action: NotificationControllerV2:dismiss', async () => { + const unrestricted = getUnrestrictedMessenger(); + const messenger = getRestrictedMessenger(unrestricted); + const callActionSpy = jest + .spyOn(messenger, 'call') + .mockImplementationOnce((..._args: any) => ({ + subjectMetadata, + })); + + const showNativeNotification = jest.fn(); + new NotificationController({ + showNativeNotification, + messenger, + }); + + expect( + await unrestricted.call('NotificationControllerV2:show', origin, { + type: NotificationType.InApp, + message, + }), + ).toBeUndefined(); + expect(showNativeNotification).toHaveBeenCalledTimes(0); + expect(await unrestricted.call('NotificationControllerV2:getCount')).toBe( + 1, + ); + expect(callActionSpy).toHaveBeenCalledTimes(1); + const current = await unrestricted.call( + 'NotificationControllerV2:getCurrent', + ); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + await unrestricted.call('NotificationControllerV2:dismiss', current!.id); + expect(await unrestricted.call('NotificationControllerV2:getCount')).toBe( + 0, + ); + }); + it('uses showNativeNotification to show a notification', () => { const messenger = getRestrictedMessenger(); const callActionSpy = jest diff --git a/src/notification/NotificationControllerV2.ts b/src/notification/NotificationControllerV2.ts index e70fb59cce..aa727f3802 100644 --- a/src/notification/NotificationControllerV2.ts +++ b/src/notification/NotificationControllerV2.ts @@ -8,7 +8,7 @@ import type { GetSubjectMetadataState } from '../subject-metadata'; /** * @type NotificationState - * @property notifications - + * @property notifications - Stores existing notifications to be shown in the UI */ export type NotificationState = { notifications: Record; @@ -55,7 +55,27 @@ export type ShowNotification = { handler: NotificationController['show']; }; -export type ControllerActions = GetNotificationState | ShowNotification; +export type DismissNotification = { + type: `${typeof name}:dismiss`; + handler: NotificationController['dismiss']; +}; + +export type GetCurrentNotification = { + type: `${typeof name}:getCurrent`; + handler: NotificationController['getCurrent']; +}; + +export type GetNotificationCount = { + type: `${typeof name}:getCount`; + handler: NotificationController['getCount']; +}; + +export type ControllerActions = + | GetNotificationState + | ShowNotification + | DismissNotification + | GetCurrentNotification + | GetNotificationCount; type AllowedActions = GetSubjectMetadataState; @@ -118,6 +138,21 @@ export class NotificationController extends BaseController< `${name}:show` as const, (origin: string, args: NotificationArgs) => this.show(origin, args), ); + + this.messagingSystem.registerActionHandler( + `${name}:dismiss` as const, + (id: string) => this.dismiss(id), + ); + + this.messagingSystem.registerActionHandler( + `${name}:getCurrent` as const, + () => this.getCurrent(), + ); + + this.messagingSystem.registerActionHandler( + `${name}:getCount` as const, + () => this.getCount(), + ); } /** @@ -132,16 +167,14 @@ export class NotificationController extends BaseController< ); const originMetadata = subjectMetadataState.subjectMetadata[origin]; + const title = originMetadata?.name ?? origin; switch (args.type) { case NotificationType.Native: - this.showNativeNotification( - originMetadata?.name ?? origin, - args.message, - ); + this.showNativeNotification(title, args.message); break; case NotificationType.InApp: - this.add(originMetadata?.name ?? origin, args.message); + this.add(title, args.message); break; default: throw new Error('Invalid notification type'); @@ -172,6 +205,20 @@ export class NotificationController extends BaseController< }); } + /** + * Gets the current notification to be shown. + * + * @returns The current notification + */ + getCurrent() { + // @todo Do we want a special ordering? + const values = Object.values(this.state.notifications); + if (values.length === 0) { + return null; + } + return values[0]; + } + /** * Gets the current number of notifications. * From 85c266c6523337408df23188efda456303c259d1 Mon Sep 17 00:00:00 2001 From: Frederik Bolding Date: Tue, 8 Mar 2022 10:53:37 +0100 Subject: [PATCH 03/17] Test another case --- src/notification/NotificationControllerV2.test.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/notification/NotificationControllerV2.test.ts b/src/notification/NotificationControllerV2.test.ts index 3e9b0d124d..815be452ba 100644 --- a/src/notification/NotificationControllerV2.test.ts +++ b/src/notification/NotificationControllerV2.test.ts @@ -146,6 +146,11 @@ describe('NotificationControllerV2', () => { title: SNAP_NAME, type: NotificationType.InApp, }); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + await unrestricted.call('NotificationControllerV2:dismiss', current!.id); + expect( + await unrestricted.call('NotificationControllerV2:getCurrent'), + ).toBeNull(); expect(callActionSpy).toHaveBeenCalledTimes(1); }); From ebda8849d548422e77a537f81d60a70feba82dd0 Mon Sep 17 00:00:00 2001 From: Frederik Bolding Date: Wed, 6 Apr 2022 13:26:31 +0200 Subject: [PATCH 04/17] Remove getCurrent, add getNotifications --- .../NotificationControllerV2.test.ts | 29 +++++++++---------- src/notification/NotificationControllerV2.ts | 25 +++++++--------- 2 files changed, 24 insertions(+), 30 deletions(-) diff --git a/src/notification/NotificationControllerV2.test.ts b/src/notification/NotificationControllerV2.test.ts index 815be452ba..35fa6fe4e7 100644 --- a/src/notification/NotificationControllerV2.test.ts +++ b/src/notification/NotificationControllerV2.test.ts @@ -3,8 +3,8 @@ import { GetSubjectMetadataState } from '../subject-metadata'; import { ControllerActions, DismissNotification, - GetCurrentNotification, GetNotificationCount, + GetNotifications, GetNotificationState, NotificationController, NotificationMessenger, @@ -26,7 +26,7 @@ function getUnrestrictedMessenger() { | ShowNotification | DismissNotification | GetNotificationCount - | GetCurrentNotification + | GetNotifications | GetSubjectMetadataState, NotificationStateChange >(); @@ -115,7 +115,7 @@ describe('NotificationControllerV2', () => { expect(callActionSpy).toHaveBeenCalledTimes(1); }); - it('action: NotificationControllerV2:getCurrent', async () => { + it('action: NotificationControllerV2:getNotifications', async () => { const unrestricted = getUnrestrictedMessenger(); const messenger = getRestrictedMessenger(unrestricted); const callActionSpy = jest @@ -137,20 +137,16 @@ describe('NotificationControllerV2', () => { }), ).toBeUndefined(); expect(showNativeNotification).toHaveBeenCalledTimes(0); - const current = await unrestricted.call( - 'NotificationControllerV2:getCurrent', + const notifications = await unrestricted.call( + 'NotificationControllerV2:getNotifications', ); - expect(current).toStrictEqual({ + expect(notifications).toHaveLength(1); + expect(notifications).toContainEqual({ id: expect.any(String), message, title: SNAP_NAME, type: NotificationType.InApp, }); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - await unrestricted.call('NotificationControllerV2:dismiss', current!.id); - expect( - await unrestricted.call('NotificationControllerV2:getCurrent'), - ).toBeNull(); expect(callActionSpy).toHaveBeenCalledTimes(1); }); @@ -180,11 +176,14 @@ describe('NotificationControllerV2', () => { 1, ); expect(callActionSpy).toHaveBeenCalledTimes(1); - const current = await unrestricted.call( - 'NotificationControllerV2:getCurrent', + const notifications = await unrestricted.call( + 'NotificationControllerV2:getNotifications', + ); + await unrestricted.call( + 'NotificationControllerV2:dismiss', + notifications[0].id, ); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - await unrestricted.call('NotificationControllerV2:dismiss', current!.id); + expect(await unrestricted.call('NotificationControllerV2:getCount')).toBe( 0, ); diff --git a/src/notification/NotificationControllerV2.ts b/src/notification/NotificationControllerV2.ts index aa727f3802..d1594af789 100644 --- a/src/notification/NotificationControllerV2.ts +++ b/src/notification/NotificationControllerV2.ts @@ -60,9 +60,9 @@ export type DismissNotification = { handler: NotificationController['dismiss']; }; -export type GetCurrentNotification = { - type: `${typeof name}:getCurrent`; - handler: NotificationController['getCurrent']; +export type GetNotifications = { + type: `${typeof name}:getNotifications`; + handler: NotificationController['getNotifications']; }; export type GetNotificationCount = { @@ -74,7 +74,7 @@ export type ControllerActions = | GetNotificationState | ShowNotification | DismissNotification - | GetCurrentNotification + | GetNotifications | GetNotificationCount; type AllowedActions = GetSubjectMetadataState; @@ -145,8 +145,8 @@ export class NotificationController extends BaseController< ); this.messagingSystem.registerActionHandler( - `${name}:getCurrent` as const, - () => this.getCurrent(), + `${name}:getNotifications` as const, + () => this.getNotifications(), ); this.messagingSystem.registerActionHandler( @@ -206,17 +206,12 @@ export class NotificationController extends BaseController< } /** - * Gets the current notification to be shown. + * Gets the notifications. * - * @returns The current notification + * @returns The notifications */ - getCurrent() { - // @todo Do we want a special ordering? - const values = Object.values(this.state.notifications); - if (values.length === 0) { - return null; - } - return values[0]; + getNotifications() { + return Object.values(this.state.notifications); } /** From 7fd789adb49704c564a588a7f7f0c4f64cbfcdf8 Mon Sep 17 00:00:00 2001 From: Frederik Bolding Date: Wed, 6 Apr 2022 14:14:19 +0200 Subject: [PATCH 05/17] Add a bit more information to the notifications --- .../NotificationControllerV2.test.ts | 44 +++++++++++++++++++ src/notification/NotificationControllerV2.ts | 39 ++++++++++++++-- 2 files changed, 80 insertions(+), 3 deletions(-) diff --git a/src/notification/NotificationControllerV2.test.ts b/src/notification/NotificationControllerV2.test.ts index 35fa6fe4e7..fcfd87ce76 100644 --- a/src/notification/NotificationControllerV2.test.ts +++ b/src/notification/NotificationControllerV2.test.ts @@ -6,6 +6,7 @@ import { GetNotificationCount, GetNotifications, GetNotificationState, + MarkNotificationViewed, NotificationController, NotificationMessenger, NotificationStateChange, @@ -25,6 +26,7 @@ function getUnrestrictedMessenger() { | GetNotificationState | ShowNotification | DismissNotification + | MarkNotificationViewed | GetNotificationCount | GetNotifications | GetSubjectMetadataState, @@ -143,6 +145,9 @@ describe('NotificationControllerV2', () => { expect(notifications).toHaveLength(1); expect(notifications).toContainEqual({ id: expect.any(String), + date: expect.any(Number), + origin, + viewed: false, message, title: SNAP_NAME, type: NotificationType.InApp, @@ -150,6 +155,45 @@ describe('NotificationControllerV2', () => { expect(callActionSpy).toHaveBeenCalledTimes(1); }); + it('action: NotificationControllerV2:markViewed', async () => { + const unrestricted = getUnrestrictedMessenger(); + const messenger = getRestrictedMessenger(unrestricted); + const callActionSpy = jest + .spyOn(messenger, 'call') + .mockImplementationOnce((..._args: any) => ({ + subjectMetadata, + })); + + const showNativeNotification = jest.fn(); + new NotificationController({ + showNativeNotification, + messenger, + }); + + expect( + await unrestricted.call('NotificationControllerV2:show', origin, { + type: NotificationType.InApp, + message, + }), + ).toBeUndefined(); + expect(showNativeNotification).toHaveBeenCalledTimes(0); + expect(await unrestricted.call('NotificationControllerV2:getCount')).toBe( + 1, + ); + expect(callActionSpy).toHaveBeenCalledTimes(1); + const notifications = await unrestricted.call( + 'NotificationControllerV2:getNotifications', + ); + await unrestricted.call( + 'NotificationControllerV2:markViewed', + notifications[0].id, + ); + + expect( + await unrestricted.call('NotificationControllerV2:getNotifications'), + ).toContainEqual({ ...notifications[0], viewed: true }); + }); + it('action: NotificationControllerV2:dismiss', async () => { const unrestricted = getUnrestrictedMessenger(); const messenger = getRestrictedMessenger(unrestricted); diff --git a/src/notification/NotificationControllerV2.ts b/src/notification/NotificationControllerV2.ts index d1594af789..1cc5e67c83 100644 --- a/src/notification/NotificationControllerV2.ts +++ b/src/notification/NotificationControllerV2.ts @@ -1,6 +1,7 @@ import type { Patch } from 'immer'; import { nanoid } from 'nanoid'; +import { hasProperty } from '../util'; import { BaseController } from '../BaseControllerV2'; import type { RestrictedControllerMessenger } from '../ControllerMessenger'; @@ -17,6 +18,9 @@ export type NotificationState = { export type Notification = { id: string; type: NotificationType; + origin: string; + date: number; + viewed: boolean; title: string; message: string; }; @@ -60,6 +64,11 @@ export type DismissNotification = { handler: NotificationController['dismiss']; }; +export type MarkNotificationViewed = { + type: `${typeof name}:markViewed`; + handler: NotificationController['markViewed']; +}; + export type GetNotifications = { type: `${typeof name}:getNotifications`; handler: NotificationController['getNotifications']; @@ -74,6 +83,7 @@ export type ControllerActions = | GetNotificationState | ShowNotification | DismissNotification + | MarkNotificationViewed | GetNotifications | GetNotificationCount; @@ -144,6 +154,11 @@ export class NotificationController extends BaseController< (id: string) => this.dismiss(id), ); + this.messagingSystem.registerActionHandler( + `${name}:markViewed` as const, + (id: string) => this.markViewed(id), + ); + this.messagingSystem.registerActionHandler( `${name}:getNotifications` as const, () => this.getNotifications(), @@ -174,18 +189,21 @@ export class NotificationController extends BaseController< this.showNativeNotification(title, args.message); break; case NotificationType.InApp: - this.add(title, args.message); + this.add(origin, title, args.message); break; default: throw new Error('Invalid notification type'); } } - private add(title: string, message: string) { + private add(origin: string, title: string, message: string) { const id = nanoid(); const notification = { id, type: NotificationType.InApp, + origin, + date: Date.now(), + viewed: false, title, message, }; @@ -201,7 +219,22 @@ export class NotificationController extends BaseController< */ dismiss(id: string) { this.update((state) => { - delete state.notifications[id]; + if (hasProperty(state.notifications, id)) { + delete state.notifications[id]; + } + }); + } + + /** + * Marks a notification as viewed. + * + * @param id - The notification's ID + */ + markViewed(id: string) { + this.update((state) => { + if (hasProperty(state.notifications, id)) { + state.notifications[id].viewed = true; + } }); } From 6536e635cbfd0de3a3879b3b22ec3206518c286c Mon Sep 17 00:00:00 2001 From: Frederik Bolding Date: Wed, 6 Apr 2022 14:25:33 +0200 Subject: [PATCH 06/17] Add getUnreadCount --- .../NotificationControllerV2.test.ts | 20 ++++++--- src/notification/NotificationControllerV2.ts | 45 ++++++++++++++----- 2 files changed, 48 insertions(+), 17 deletions(-) diff --git a/src/notification/NotificationControllerV2.test.ts b/src/notification/NotificationControllerV2.test.ts index fcfd87ce76..2fee5b6693 100644 --- a/src/notification/NotificationControllerV2.test.ts +++ b/src/notification/NotificationControllerV2.test.ts @@ -6,7 +6,8 @@ import { GetNotificationCount, GetNotifications, GetNotificationState, - MarkNotificationViewed, + GetUnreadNotificationCount, + MarkNotificationRead, NotificationController, NotificationMessenger, NotificationStateChange, @@ -26,8 +27,9 @@ function getUnrestrictedMessenger() { | GetNotificationState | ShowNotification | DismissNotification - | MarkNotificationViewed + | MarkNotificationRead | GetNotificationCount + | GetUnreadNotificationCount | GetNotifications | GetSubjectMetadataState, NotificationStateChange @@ -147,7 +149,7 @@ describe('NotificationControllerV2', () => { id: expect.any(String), date: expect.any(Number), origin, - viewed: false, + read: false, message, title: SNAP_NAME, type: NotificationType.InApp, @@ -185,13 +187,21 @@ describe('NotificationControllerV2', () => { 'NotificationControllerV2:getNotifications', ); await unrestricted.call( - 'NotificationControllerV2:markViewed', + 'NotificationControllerV2:markRead', notifications[0].id, ); expect( await unrestricted.call('NotificationControllerV2:getNotifications'), - ).toContainEqual({ ...notifications[0], viewed: true }); + ).toContainEqual({ ...notifications[0], read: true }); + + expect(await unrestricted.call('NotificationControllerV2:getCount')).toBe( + 1, + ); + + expect( + await unrestricted.call('NotificationControllerV2:getUnreadCount'), + ).toBe(0); }); it('action: NotificationControllerV2:dismiss', async () => { diff --git a/src/notification/NotificationControllerV2.ts b/src/notification/NotificationControllerV2.ts index 1cc5e67c83..5f8c069152 100644 --- a/src/notification/NotificationControllerV2.ts +++ b/src/notification/NotificationControllerV2.ts @@ -20,7 +20,7 @@ export type Notification = { type: NotificationType; origin: string; date: number; - viewed: boolean; + read: boolean; title: string; message: string; }; @@ -64,9 +64,9 @@ export type DismissNotification = { handler: NotificationController['dismiss']; }; -export type MarkNotificationViewed = { - type: `${typeof name}:markViewed`; - handler: NotificationController['markViewed']; +export type MarkNotificationRead = { + type: `${typeof name}:markRead`; + handler: NotificationController['markRead']; }; export type GetNotifications = { @@ -79,13 +79,19 @@ export type GetNotificationCount = { handler: NotificationController['getCount']; }; +export type GetUnreadNotificationCount = { + type: `${typeof name}:getUnreadCount`; + handler: NotificationController['getUnreadCount']; +}; + export type ControllerActions = | GetNotificationState | ShowNotification | DismissNotification - | MarkNotificationViewed + | MarkNotificationRead | GetNotifications - | GetNotificationCount; + | GetNotificationCount + | GetUnreadNotificationCount; type AllowedActions = GetSubjectMetadataState; @@ -155,8 +161,8 @@ export class NotificationController extends BaseController< ); this.messagingSystem.registerActionHandler( - `${name}:markViewed` as const, - (id: string) => this.markViewed(id), + `${name}:markRead` as const, + (id: string) => this.markRead(id), ); this.messagingSystem.registerActionHandler( @@ -168,6 +174,11 @@ export class NotificationController extends BaseController< `${name}:getCount` as const, () => this.getCount(), ); + + this.messagingSystem.registerActionHandler( + `${name}:getUnreadCount` as const, + () => this.getUnreadCount(), + ); } /** @@ -203,7 +214,7 @@ export class NotificationController extends BaseController< type: NotificationType.InApp, origin, date: Date.now(), - viewed: false, + read: false, title, message, }; @@ -226,14 +237,14 @@ export class NotificationController extends BaseController< } /** - * Marks a notification as viewed. + * Marks a notification as read. * * @param id - The notification's ID */ - markViewed(id: string) { + markRead(id: string) { this.update((state) => { if (hasProperty(state.notifications, id)) { - state.notifications[id].viewed = true; + state.notifications[id].read = true; } }); } @@ -255,4 +266,14 @@ export class NotificationController extends BaseController< getCount() { return Object.values(this.state.notifications).length; } + + /** + * Gets the current number of unread notifications. + * + * @returns The number of current notifications + */ + getUnreadCount() { + return Object.values(this.state.notifications).filter((n) => !n.read) + .length; + } } From c0bddf8f60c4f3f507dc01c52219277b5d63976b Mon Sep 17 00:00:00 2001 From: Frederik Bolding Date: Fri, 8 Apr 2022 14:06:44 +0200 Subject: [PATCH 07/17] Rename to NotificationController --- ...test.ts => NotificationController.test.ts} | 60 ++++++++----------- ...trollerV2.ts => NotificationController.ts} | 2 +- 2 files changed, 27 insertions(+), 35 deletions(-) rename src/notification/{NotificationControllerV2.test.ts => NotificationController.test.ts} (80%) rename src/notification/{NotificationControllerV2.ts => NotificationController.ts} (99%) diff --git a/src/notification/NotificationControllerV2.test.ts b/src/notification/NotificationController.test.ts similarity index 80% rename from src/notification/NotificationControllerV2.test.ts rename to src/notification/NotificationController.test.ts index 2fee5b6693..d2a01b9751 100644 --- a/src/notification/NotificationControllerV2.test.ts +++ b/src/notification/NotificationController.test.ts @@ -13,9 +13,9 @@ import { NotificationStateChange, NotificationType, ShowNotification, -} from './NotificationControllerV2'; +} from './NotificationController'; -const name = 'NotificationControllerV2'; +const name = 'NotificationController'; /** * Constructs a unrestricted controller messenger. @@ -53,7 +53,7 @@ function getRestrictedMessenger( name, allowedActions: [ 'SubjectMetadataController:getState', - 'NotificationControllerV2:show', + 'NotificationController:show', ], }) as NotificationMessenger; } @@ -66,8 +66,8 @@ const subjectMetadata = { snap_test: { origin: 'snap_test', name: SNAP_NAME }, }; -describe('NotificationControllerV2', () => { - it('action: NotificationControllerV2:show native notifications', async () => { +describe('NotificationController', () => { + it('action: NotificationController:show native notifications', async () => { const unrestricted = getUnrestrictedMessenger(); const messenger = getRestrictedMessenger(unrestricted); const callActionSpy = jest @@ -83,7 +83,7 @@ describe('NotificationControllerV2', () => { }); expect( - await unrestricted.call('NotificationControllerV2:show', origin, { + await unrestricted.call('NotificationController:show', origin, { type: NotificationType.Native, message, }), @@ -92,7 +92,7 @@ describe('NotificationControllerV2', () => { expect(callActionSpy).toHaveBeenCalledTimes(1); }); - it('action: NotificationControllerV2:show in-app notifications', async () => { + it('action: NotificationController:show in-app notifications', async () => { const unrestricted = getUnrestrictedMessenger(); const messenger = getRestrictedMessenger(unrestricted); const callActionSpy = jest @@ -108,18 +108,18 @@ describe('NotificationControllerV2', () => { }); expect( - await unrestricted.call('NotificationControllerV2:show', origin, { + await unrestricted.call('NotificationController:show', origin, { type: NotificationType.InApp, message, }), ).toBeUndefined(); expect(showNativeNotification).toHaveBeenCalledTimes(0); - const count = await unrestricted.call('NotificationControllerV2:getCount'); + const count = await unrestricted.call('NotificationController:getCount'); expect(count).toBe(1); expect(callActionSpy).toHaveBeenCalledTimes(1); }); - it('action: NotificationControllerV2:getNotifications', async () => { + it('action: NotificationController:getNotifications', async () => { const unrestricted = getUnrestrictedMessenger(); const messenger = getRestrictedMessenger(unrestricted); const callActionSpy = jest @@ -135,14 +135,14 @@ describe('NotificationControllerV2', () => { }); expect( - await unrestricted.call('NotificationControllerV2:show', origin, { + await unrestricted.call('NotificationController:show', origin, { type: NotificationType.InApp, message, }), ).toBeUndefined(); expect(showNativeNotification).toHaveBeenCalledTimes(0); const notifications = await unrestricted.call( - 'NotificationControllerV2:getNotifications', + 'NotificationController:getNotifications', ); expect(notifications).toHaveLength(1); expect(notifications).toContainEqual({ @@ -157,7 +157,7 @@ describe('NotificationControllerV2', () => { expect(callActionSpy).toHaveBeenCalledTimes(1); }); - it('action: NotificationControllerV2:markViewed', async () => { + it('action: NotificationController:markViewed', async () => { const unrestricted = getUnrestrictedMessenger(); const messenger = getRestrictedMessenger(unrestricted); const callActionSpy = jest @@ -173,38 +173,34 @@ describe('NotificationControllerV2', () => { }); expect( - await unrestricted.call('NotificationControllerV2:show', origin, { + await unrestricted.call('NotificationController:show', origin, { type: NotificationType.InApp, message, }), ).toBeUndefined(); expect(showNativeNotification).toHaveBeenCalledTimes(0); - expect(await unrestricted.call('NotificationControllerV2:getCount')).toBe( - 1, - ); + expect(await unrestricted.call('NotificationController:getCount')).toBe(1); expect(callActionSpy).toHaveBeenCalledTimes(1); const notifications = await unrestricted.call( - 'NotificationControllerV2:getNotifications', + 'NotificationController:getNotifications', ); await unrestricted.call( - 'NotificationControllerV2:markRead', + 'NotificationController:markRead', notifications[0].id, ); expect( - await unrestricted.call('NotificationControllerV2:getNotifications'), + await unrestricted.call('NotificationController:getNotifications'), ).toContainEqual({ ...notifications[0], read: true }); - expect(await unrestricted.call('NotificationControllerV2:getCount')).toBe( - 1, - ); + expect(await unrestricted.call('NotificationController:getCount')).toBe(1); expect( - await unrestricted.call('NotificationControllerV2:getUnreadCount'), + await unrestricted.call('NotificationController:getUnreadCount'), ).toBe(0); }); - it('action: NotificationControllerV2:dismiss', async () => { + it('action: NotificationController:dismiss', async () => { const unrestricted = getUnrestrictedMessenger(); const messenger = getRestrictedMessenger(unrestricted); const callActionSpy = jest @@ -220,27 +216,23 @@ describe('NotificationControllerV2', () => { }); expect( - await unrestricted.call('NotificationControllerV2:show', origin, { + await unrestricted.call('NotificationController:show', origin, { type: NotificationType.InApp, message, }), ).toBeUndefined(); expect(showNativeNotification).toHaveBeenCalledTimes(0); - expect(await unrestricted.call('NotificationControllerV2:getCount')).toBe( - 1, - ); + expect(await unrestricted.call('NotificationController:getCount')).toBe(1); expect(callActionSpy).toHaveBeenCalledTimes(1); const notifications = await unrestricted.call( - 'NotificationControllerV2:getNotifications', + 'NotificationController:getNotifications', ); await unrestricted.call( - 'NotificationControllerV2:dismiss', + 'NotificationController:dismiss', notifications[0].id, ); - expect(await unrestricted.call('NotificationControllerV2:getCount')).toBe( - 0, - ); + expect(await unrestricted.call('NotificationController:getCount')).toBe(0); }); it('uses showNativeNotification to show a notification', () => { diff --git a/src/notification/NotificationControllerV2.ts b/src/notification/NotificationController.ts similarity index 99% rename from src/notification/NotificationControllerV2.ts rename to src/notification/NotificationController.ts index 5f8c069152..241d0a7910 100644 --- a/src/notification/NotificationControllerV2.ts +++ b/src/notification/NotificationController.ts @@ -42,7 +42,7 @@ export interface NotificationArgs { message: string; } -const name = 'NotificationControllerV2'; +const name = 'NotificationController'; export type NotificationStateChange = { type: `${typeof name}:stateChange`; From 19630b207bbdaee5d06db0cf2443cc895bb19b37 Mon Sep 17 00:00:00 2001 From: Frederik Bolding Date: Fri, 8 Apr 2022 15:37:54 +0200 Subject: [PATCH 08/17] Support multiple ids for dismissing and marking as read --- .../NotificationController.test.ts | 10 +++--- src/notification/NotificationController.ts | 33 ++++++++++--------- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/src/notification/NotificationController.test.ts b/src/notification/NotificationController.test.ts index d2a01b9751..b51dc7c5d9 100644 --- a/src/notification/NotificationController.test.ts +++ b/src/notification/NotificationController.test.ts @@ -184,10 +184,9 @@ describe('NotificationController', () => { const notifications = await unrestricted.call( 'NotificationController:getNotifications', ); - await unrestricted.call( - 'NotificationController:markRead', + await unrestricted.call('NotificationController:markRead', [ notifications[0].id, - ); + ]); expect( await unrestricted.call('NotificationController:getNotifications'), @@ -227,10 +226,9 @@ describe('NotificationController', () => { const notifications = await unrestricted.call( 'NotificationController:getNotifications', ); - await unrestricted.call( - 'NotificationController:dismiss', + await unrestricted.call('NotificationController:dismiss', [ notifications[0].id, - ); + ]); expect(await unrestricted.call('NotificationController:getCount')).toBe(0); }); diff --git a/src/notification/NotificationController.ts b/src/notification/NotificationController.ts index 241d0a7910..5ef9d996b7 100644 --- a/src/notification/NotificationController.ts +++ b/src/notification/NotificationController.ts @@ -157,12 +157,12 @@ export class NotificationController extends BaseController< this.messagingSystem.registerActionHandler( `${name}:dismiss` as const, - (id: string) => this.dismiss(id), + (ids: string[]) => this.dismiss(ids), ); this.messagingSystem.registerActionHandler( `${name}:markRead` as const, - (id: string) => this.markRead(id), + (ids: string[]) => this.markRead(ids), ); this.messagingSystem.registerActionHandler( @@ -224,27 +224,31 @@ export class NotificationController extends BaseController< } /** - * Dimisses a notification. + * Dimisses a list of notifications. * - * @param id - The notification's ID + * @param ids - A list of notification IDs */ - dismiss(id: string) { + dismiss(ids: string[]) { this.update((state) => { - if (hasProperty(state.notifications, id)) { - delete state.notifications[id]; + for (const id of ids) { + if (hasProperty(state.notifications, id)) { + delete state.notifications[id]; + } } }); } /** - * Marks a notification as read. + * Marks a list of notifications as read. * - * @param id - The notification's ID + * @param ids - A list of notification IDs */ - markRead(id: string) { + markRead(ids: string[]) { this.update((state) => { - if (hasProperty(state.notifications, id)) { - state.notifications[id].read = true; + for (const id of ids) { + if (hasProperty(state.notifications, id)) { + state.notifications[id].read = true; + } } }); } @@ -264,7 +268,7 @@ export class NotificationController extends BaseController< * @returns The number of current notifications */ getCount() { - return Object.values(this.state.notifications).length; + return this.getNotifications().length; } /** @@ -273,7 +277,6 @@ export class NotificationController extends BaseController< * @returns The number of current notifications */ getUnreadCount() { - return Object.values(this.state.notifications).filter((n) => !n.read) - .length; + return this.getNotifications().filter((n) => !n.read).length; } } From 265272d0b47ae155f783d59c9a27493ef23f2053 Mon Sep 17 00:00:00 2001 From: Frederik Bolding Date: Wed, 13 Apr 2022 16:45:02 +0200 Subject: [PATCH 09/17] Simplify further --- .../NotificationController.test.ts | 71 +------------------ src/notification/NotificationController.ts | 26 ++----- 2 files changed, 8 insertions(+), 89 deletions(-) diff --git a/src/notification/NotificationController.test.ts b/src/notification/NotificationController.test.ts index b51dc7c5d9..ee0703c545 100644 --- a/src/notification/NotificationController.test.ts +++ b/src/notification/NotificationController.test.ts @@ -62,19 +62,10 @@ const SNAP_NAME = 'Test Snap Name'; const origin = 'snap_test'; const message = 'foo'; -const subjectMetadata = { - snap_test: { origin: 'snap_test', name: SNAP_NAME }, -}; - describe('NotificationController', () => { it('action: NotificationController:show native notifications', async () => { const unrestricted = getUnrestrictedMessenger(); const messenger = getRestrictedMessenger(unrestricted); - const callActionSpy = jest - .spyOn(messenger, 'call') - .mockImplementationOnce((..._args: any) => ({ - subjectMetadata, - })); const showNativeNotification = jest.fn(); new NotificationController({ @@ -89,17 +80,12 @@ describe('NotificationController', () => { }), ).toBeUndefined(); expect(showNativeNotification).toHaveBeenCalledTimes(1); - expect(callActionSpy).toHaveBeenCalledTimes(1); + expect(showNativeNotification).toHaveBeenCalledWith(origin, message); }); it('action: NotificationController:show in-app notifications', async () => { const unrestricted = getUnrestrictedMessenger(); const messenger = getRestrictedMessenger(unrestricted); - const callActionSpy = jest - .spyOn(messenger, 'call') - .mockImplementationOnce((..._args: any) => ({ - subjectMetadata, - })); const showNativeNotification = jest.fn(); new NotificationController({ @@ -116,17 +102,11 @@ describe('NotificationController', () => { expect(showNativeNotification).toHaveBeenCalledTimes(0); const count = await unrestricted.call('NotificationController:getCount'); expect(count).toBe(1); - expect(callActionSpy).toHaveBeenCalledTimes(1); }); it('action: NotificationController:getNotifications', async () => { const unrestricted = getUnrestrictedMessenger(); const messenger = getRestrictedMessenger(unrestricted); - const callActionSpy = jest - .spyOn(messenger, 'call') - .mockImplementationOnce((..._args: any) => ({ - subjectMetadata, - })); const showNativeNotification = jest.fn(); new NotificationController({ @@ -151,20 +131,13 @@ describe('NotificationController', () => { origin, read: false, message, - title: SNAP_NAME, type: NotificationType.InApp, }); - expect(callActionSpy).toHaveBeenCalledTimes(1); }); it('action: NotificationController:markViewed', async () => { const unrestricted = getUnrestrictedMessenger(); const messenger = getRestrictedMessenger(unrestricted); - const callActionSpy = jest - .spyOn(messenger, 'call') - .mockImplementationOnce((..._args: any) => ({ - subjectMetadata, - })); const showNativeNotification = jest.fn(); new NotificationController({ @@ -180,7 +153,6 @@ describe('NotificationController', () => { ).toBeUndefined(); expect(showNativeNotification).toHaveBeenCalledTimes(0); expect(await unrestricted.call('NotificationController:getCount')).toBe(1); - expect(callActionSpy).toHaveBeenCalledTimes(1); const notifications = await unrestricted.call( 'NotificationController:getNotifications', ); @@ -202,11 +174,6 @@ describe('NotificationController', () => { it('action: NotificationController:dismiss', async () => { const unrestricted = getUnrestrictedMessenger(); const messenger = getRestrictedMessenger(unrestricted); - const callActionSpy = jest - .spyOn(messenger, 'call') - .mockImplementationOnce((..._args: any) => ({ - subjectMetadata, - })); const showNativeNotification = jest.fn(); new NotificationController({ @@ -222,7 +189,6 @@ describe('NotificationController', () => { ).toBeUndefined(); expect(showNativeNotification).toHaveBeenCalledTimes(0); expect(await unrestricted.call('NotificationController:getCount')).toBe(1); - expect(callActionSpy).toHaveBeenCalledTimes(1); const notifications = await unrestricted.call( 'NotificationController:getNotifications', ); @@ -235,37 +201,6 @@ describe('NotificationController', () => { it('uses showNativeNotification to show a notification', () => { const messenger = getRestrictedMessenger(); - const callActionSpy = jest - .spyOn(messenger, 'call') - .mockImplementationOnce((..._args: any) => ({ - subjectMetadata, - })); - - const showNativeNotification = jest.fn(); - const controller = new NotificationController({ - showNativeNotification, - messenger, - }); - expect( - controller.show(origin, { - type: NotificationType.Native, - message, - }), - ).toBeUndefined(); - expect(showNativeNotification).toHaveBeenCalledWith(SNAP_NAME, message); - expect(callActionSpy).toHaveBeenCalledTimes(1); - expect(callActionSpy).toHaveBeenCalledWith( - 'SubjectMetadataController:getState', - ); - }); - - it('falls back to origin if no metadata present', () => { - const messenger = getRestrictedMessenger(); - const callActionSpy = jest - .spyOn(messenger, 'call') - .mockImplementationOnce((..._args: any) => ({ - subjectMetadata: {}, - })); const showNativeNotification = jest.fn(); const controller = new NotificationController({ @@ -279,9 +214,5 @@ describe('NotificationController', () => { }), ).toBeUndefined(); expect(showNativeNotification).toHaveBeenCalledWith(origin, message); - expect(callActionSpy).toHaveBeenCalledTimes(1); - expect(callActionSpy).toHaveBeenCalledWith( - 'SubjectMetadataController:getState', - ); }); }); diff --git a/src/notification/NotificationController.ts b/src/notification/NotificationController.ts index 5ef9d996b7..25e173d79a 100644 --- a/src/notification/NotificationController.ts +++ b/src/notification/NotificationController.ts @@ -21,7 +21,6 @@ export type Notification = { origin: string; date: number; read: boolean; - title: string; message: string; }; @@ -93,13 +92,11 @@ export type ControllerActions = | GetNotificationCount | GetUnreadNotificationCount; -type AllowedActions = GetSubjectMetadataState; - export type NotificationMessenger = RestrictedControllerMessenger< typeof name, - ControllerActions | AllowedActions, + ControllerActions, NotificationStateChange, - AllowedActions['type'], + never, never >; @@ -137,9 +134,8 @@ export class NotificationController extends BaseController< messenger: NotificationMessenger; state?: Partial; showNativeNotification: ( - title: string, - message: string, - url?: string, + origin: string, + message: string ) => void; }) { super({ @@ -188,26 +184,19 @@ export class NotificationController extends BaseController< * @param args - Notification arguments, containing the notification message etc. */ show(origin: string, args: NotificationArgs) { - const subjectMetadataState = this.messagingSystem.call( - 'SubjectMetadataController:getState', - ); - - const originMetadata = subjectMetadataState.subjectMetadata[origin]; - const title = originMetadata?.name ?? origin; - switch (args.type) { case NotificationType.Native: - this.showNativeNotification(title, args.message); + this.showNativeNotification(origin, args.message); break; case NotificationType.InApp: - this.add(origin, title, args.message); + this.add(origin, args.message); break; default: throw new Error('Invalid notification type'); } } - private add(origin: string, title: string, message: string) { + private add(origin: string, message: string) { const id = nanoid(); const notification = { id, @@ -215,7 +204,6 @@ export class NotificationController extends BaseController< origin, date: Date.now(), read: false, - title, message, }; this.update((state) => { From cf06bdc86c645fcedfb8e75cf4e81fc08245cbf3 Mon Sep 17 00:00:00 2001 From: Frederik Bolding Date: Wed, 13 Apr 2022 16:47:41 +0200 Subject: [PATCH 10/17] Run linting --- src/notification/NotificationController.test.ts | 1 - src/notification/NotificationController.ts | 6 +----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/notification/NotificationController.test.ts b/src/notification/NotificationController.test.ts index ee0703c545..e6a332f0bf 100644 --- a/src/notification/NotificationController.test.ts +++ b/src/notification/NotificationController.test.ts @@ -58,7 +58,6 @@ function getRestrictedMessenger( }) as NotificationMessenger; } -const SNAP_NAME = 'Test Snap Name'; const origin = 'snap_test'; const message = 'foo'; diff --git a/src/notification/NotificationController.ts b/src/notification/NotificationController.ts index 25e173d79a..043d7c35ed 100644 --- a/src/notification/NotificationController.ts +++ b/src/notification/NotificationController.ts @@ -5,7 +5,6 @@ import { hasProperty } from '../util'; import { BaseController } from '../BaseControllerV2'; import type { RestrictedControllerMessenger } from '../ControllerMessenger'; -import type { GetSubjectMetadataState } from '../subject-metadata'; /** * @type NotificationState @@ -133,10 +132,7 @@ export class NotificationController extends BaseController< }: { messenger: NotificationMessenger; state?: Partial; - showNativeNotification: ( - origin: string, - message: string - ) => void; + showNativeNotification: (origin: string, message: string) => void; }) { super({ name, From 5e02aa1d7a89ffacee2c5c9260757c34772225c3 Mon Sep 17 00:00:00 2001 From: Frederik Bolding Date: Tue, 19 Apr 2022 10:15:19 +0200 Subject: [PATCH 11/17] Fix some PR comments --- .../NotificationController.test.ts | 28 ++++--------------- src/notification/NotificationController.ts | 28 +++++++++---------- 2 files changed, 20 insertions(+), 36 deletions(-) diff --git a/src/notification/NotificationController.test.ts b/src/notification/NotificationController.test.ts index e6a332f0bf..8ac48a3568 100644 --- a/src/notification/NotificationController.test.ts +++ b/src/notification/NotificationController.test.ts @@ -1,18 +1,10 @@ import { ControllerMessenger } from '../ControllerMessenger'; -import { GetSubjectMetadataState } from '../subject-metadata'; import { ControllerActions, - DismissNotification, - GetNotificationCount, - GetNotifications, - GetNotificationState, - GetUnreadNotificationCount, - MarkNotificationRead, NotificationController, - NotificationMessenger, - NotificationStateChange, + NotificationControllerMessenger, + NotificationControllerStateChange, NotificationType, - ShowNotification, } from './NotificationController'; const name = 'NotificationController'; @@ -24,15 +16,8 @@ const name = 'NotificationController'; */ function getUnrestrictedMessenger() { return new ControllerMessenger< - | GetNotificationState - | ShowNotification - | DismissNotification - | MarkNotificationRead - | GetNotificationCount - | GetUnreadNotificationCount - | GetNotifications - | GetSubjectMetadataState, - NotificationStateChange + ControllerActions, + NotificationControllerStateChange >(); } @@ -47,15 +32,14 @@ function getRestrictedMessenger( ) { return controllerMessenger.getRestricted< typeof name, - ControllerActions['type'] | GetSubjectMetadataState['type'], + ControllerActions['type'], never >({ name, allowedActions: [ - 'SubjectMetadataController:getState', 'NotificationController:show', ], - }) as NotificationMessenger; + }) as NotificationControllerMessenger; } const origin = 'snap_test'; diff --git a/src/notification/NotificationController.ts b/src/notification/NotificationController.ts index 043d7c35ed..80bb3059a8 100644 --- a/src/notification/NotificationController.ts +++ b/src/notification/NotificationController.ts @@ -7,10 +7,10 @@ import { BaseController } from '../BaseControllerV2'; import type { RestrictedControllerMessenger } from '../ControllerMessenger'; /** - * @type NotificationState + * @typedef NotificationControllerState * @property notifications - Stores existing notifications to be shown in the UI */ -export type NotificationState = { +export type NotificationControllerState = { notifications: Record; }; @@ -42,14 +42,14 @@ export interface NotificationArgs { const name = 'NotificationController'; -export type NotificationStateChange = { +export type NotificationControllerStateChange = { type: `${typeof name}:stateChange`; - payload: [NotificationState, Patch[]]; + payload: [NotificationControllerState, Patch[]]; }; -export type GetNotificationState = { +export type GetNotificationControllerState = { type: `${typeof name}:getState`; - handler: () => NotificationState; + handler: () => NotificationControllerState; }; export type ShowNotification = { @@ -83,7 +83,7 @@ export type GetUnreadNotificationCount = { }; export type ControllerActions = - | GetNotificationState + | GetNotificationControllerState | ShowNotification | DismissNotification | MarkNotificationRead @@ -91,10 +91,10 @@ export type ControllerActions = | GetNotificationCount | GetUnreadNotificationCount; -export type NotificationMessenger = RestrictedControllerMessenger< +export type NotificationControllerMessenger = RestrictedControllerMessenger< typeof name, ControllerActions, - NotificationStateChange, + NotificationControllerStateChange, never, never >; @@ -108,12 +108,12 @@ const defaultState = { }; /** - * Controller that handles showing notifications to the user and rate limiting origins + * Controller that handles storing notifications and showing them to the user */ export class NotificationController extends BaseController< typeof name, - NotificationState, - NotificationMessenger + NotificationControllerState, + NotificationControllerMessenger > { private showNativeNotification; @@ -130,8 +130,8 @@ export class NotificationController extends BaseController< state, showNativeNotification, }: { - messenger: NotificationMessenger; - state?: Partial; + messenger: NotificationControllerMessenger; + state?: Partial; showNativeNotification: (origin: string, message: string) => void; }) { super({ From 4a4e246f856243e849b6819ed88a55434df1f12b Mon Sep 17 00:00:00 2001 From: Frederik Bolding Date: Tue, 19 Apr 2022 10:23:54 +0200 Subject: [PATCH 12/17] Remove getters --- .../NotificationController.test.ts | 71 +++++-------------- src/notification/NotificationController.ts | 62 +--------------- 2 files changed, 17 insertions(+), 116 deletions(-) diff --git a/src/notification/NotificationController.test.ts b/src/notification/NotificationController.test.ts index 8ac48a3568..e25364a2df 100644 --- a/src/notification/NotificationController.test.ts +++ b/src/notification/NotificationController.test.ts @@ -36,9 +36,7 @@ function getRestrictedMessenger( never >({ name, - allowedActions: [ - 'NotificationController:show', - ], + allowedActions: ['NotificationController:show'], }) as NotificationControllerMessenger; } @@ -71,28 +69,7 @@ describe('NotificationController', () => { const messenger = getRestrictedMessenger(unrestricted); const showNativeNotification = jest.fn(); - new NotificationController({ - showNativeNotification, - messenger, - }); - - expect( - await unrestricted.call('NotificationController:show', origin, { - type: NotificationType.InApp, - message, - }), - ).toBeUndefined(); - expect(showNativeNotification).toHaveBeenCalledTimes(0); - const count = await unrestricted.call('NotificationController:getCount'); - expect(count).toBe(1); - }); - - it('action: NotificationController:getNotifications', async () => { - const unrestricted = getUnrestrictedMessenger(); - const messenger = getRestrictedMessenger(unrestricted); - - const showNativeNotification = jest.fn(); - new NotificationController({ + const controller = new NotificationController({ showNativeNotification, messenger, }); @@ -104,18 +81,8 @@ describe('NotificationController', () => { }), ).toBeUndefined(); expect(showNativeNotification).toHaveBeenCalledTimes(0); - const notifications = await unrestricted.call( - 'NotificationController:getNotifications', - ); + const notifications = Object.values(controller.state.notifications); expect(notifications).toHaveLength(1); - expect(notifications).toContainEqual({ - id: expect.any(String), - date: expect.any(Number), - origin, - read: false, - message, - type: NotificationType.InApp, - }); }); it('action: NotificationController:markViewed', async () => { @@ -123,7 +90,7 @@ describe('NotificationController', () => { const messenger = getRestrictedMessenger(unrestricted); const showNativeNotification = jest.fn(); - new NotificationController({ + const controller = new NotificationController({ showNativeNotification, messenger, }); @@ -135,23 +102,19 @@ describe('NotificationController', () => { }), ).toBeUndefined(); expect(showNativeNotification).toHaveBeenCalledTimes(0); - expect(await unrestricted.call('NotificationController:getCount')).toBe(1); - const notifications = await unrestricted.call( - 'NotificationController:getNotifications', - ); + const notifications = Object.values(controller.state.notifications); + expect(notifications).toHaveLength(1); await unrestricted.call('NotificationController:markRead', [ notifications[0].id, ]); - expect( - await unrestricted.call('NotificationController:getNotifications'), - ).toContainEqual({ ...notifications[0], read: true }); - - expect(await unrestricted.call('NotificationController:getCount')).toBe(1); + const newNotifications = Object.values(controller.state.notifications); + expect(newNotifications).toContainEqual({ + ...notifications[0], + read: true, + }); - expect( - await unrestricted.call('NotificationController:getUnreadCount'), - ).toBe(0); + expect(newNotifications).toHaveLength(1); }); it('action: NotificationController:dismiss', async () => { @@ -159,7 +122,7 @@ describe('NotificationController', () => { const messenger = getRestrictedMessenger(unrestricted); const showNativeNotification = jest.fn(); - new NotificationController({ + const controller = new NotificationController({ showNativeNotification, messenger, }); @@ -171,15 +134,13 @@ describe('NotificationController', () => { }), ).toBeUndefined(); expect(showNativeNotification).toHaveBeenCalledTimes(0); - expect(await unrestricted.call('NotificationController:getCount')).toBe(1); - const notifications = await unrestricted.call( - 'NotificationController:getNotifications', - ); + const notifications = Object.values(controller.state.notifications); + expect(notifications).toHaveLength(1); await unrestricted.call('NotificationController:dismiss', [ notifications[0].id, ]); - expect(await unrestricted.call('NotificationController:getCount')).toBe(0); + expect(Object.values(controller.state.notifications)).toHaveLength(0); }); it('uses showNativeNotification to show a notification', () => { diff --git a/src/notification/NotificationController.ts b/src/notification/NotificationController.ts index 80bb3059a8..3f70311ff1 100644 --- a/src/notification/NotificationController.ts +++ b/src/notification/NotificationController.ts @@ -67,29 +67,11 @@ export type MarkNotificationRead = { handler: NotificationController['markRead']; }; -export type GetNotifications = { - type: `${typeof name}:getNotifications`; - handler: NotificationController['getNotifications']; -}; - -export type GetNotificationCount = { - type: `${typeof name}:getCount`; - handler: NotificationController['getCount']; -}; - -export type GetUnreadNotificationCount = { - type: `${typeof name}:getUnreadCount`; - handler: NotificationController['getUnreadCount']; -}; - export type ControllerActions = | GetNotificationControllerState | ShowNotification | DismissNotification - | MarkNotificationRead - | GetNotifications - | GetNotificationCount - | GetUnreadNotificationCount; + | MarkNotificationRead; export type NotificationControllerMessenger = RestrictedControllerMessenger< typeof name, @@ -156,21 +138,6 @@ export class NotificationController extends BaseController< `${name}:markRead` as const, (ids: string[]) => this.markRead(ids), ); - - this.messagingSystem.registerActionHandler( - `${name}:getNotifications` as const, - () => this.getNotifications(), - ); - - this.messagingSystem.registerActionHandler( - `${name}:getCount` as const, - () => this.getCount(), - ); - - this.messagingSystem.registerActionHandler( - `${name}:getUnreadCount` as const, - () => this.getUnreadCount(), - ); } /** @@ -236,31 +203,4 @@ export class NotificationController extends BaseController< } }); } - - /** - * Gets the notifications. - * - * @returns The notifications - */ - getNotifications() { - return Object.values(this.state.notifications); - } - - /** - * Gets the current number of notifications. - * - * @returns The number of current notifications - */ - getCount() { - return this.getNotifications().length; - } - - /** - * Gets the current number of unread notifications. - * - * @returns The number of current notifications - */ - getUnreadCount() { - return this.getNotifications().filter((n) => !n.read).length; - } } From 92de8dcd537b0abed9f133c2bae40dfb468e054b Mon Sep 17 00:00:00 2001 From: Frederik Bolding Date: Tue, 19 Apr 2022 10:30:38 +0200 Subject: [PATCH 13/17] Add readDate --- src/notification/NotificationController.test.ts | 10 +++++++++- src/notification/NotificationController.ts | 10 +++++----- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/notification/NotificationController.test.ts b/src/notification/NotificationController.test.ts index e25364a2df..f0e82f1a83 100644 --- a/src/notification/NotificationController.test.ts +++ b/src/notification/NotificationController.test.ts @@ -83,6 +83,14 @@ describe('NotificationController', () => { expect(showNativeNotification).toHaveBeenCalledTimes(0); const notifications = Object.values(controller.state.notifications); expect(notifications).toHaveLength(1); + expect(notifications).toContainEqual({ + createdDate: expect.any(Number), + id: expect.any(String), + message, + origin, + readDate: null, + type: NotificationType.InApp, + }); }); it('action: NotificationController:markViewed', async () => { @@ -111,7 +119,7 @@ describe('NotificationController', () => { const newNotifications = Object.values(controller.state.notifications); expect(newNotifications).toContainEqual({ ...notifications[0], - read: true, + readDate: expect.any(Number), }); expect(newNotifications).toHaveLength(1); diff --git a/src/notification/NotificationController.ts b/src/notification/NotificationController.ts index 3f70311ff1..6ed4d778e0 100644 --- a/src/notification/NotificationController.ts +++ b/src/notification/NotificationController.ts @@ -18,8 +18,8 @@ export type Notification = { id: string; type: NotificationType; origin: string; - date: number; - read: boolean; + createdDate: number; + readDate: number | null; message: string; }; @@ -165,8 +165,8 @@ export class NotificationController extends BaseController< id, type: NotificationType.InApp, origin, - date: Date.now(), - read: false, + createdDate: Date.now(), + readDate: null, message, }; this.update((state) => { @@ -198,7 +198,7 @@ export class NotificationController extends BaseController< this.update((state) => { for (const id of ids) { if (hasProperty(state.notifications, id)) { - state.notifications[id].read = true; + state.notifications[id].readDate = Date.now(); } } }); From 852973b115085a24be0eddea6a5a71cb2b0d9c93 Mon Sep 17 00:00:00 2001 From: Frederik Bolding Date: Tue, 19 Apr 2022 10:51:53 +0200 Subject: [PATCH 14/17] Add some documentation --- src/notification/NotificationController.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/notification/NotificationController.ts b/src/notification/NotificationController.ts index 6ed4d778e0..89b3a5d298 100644 --- a/src/notification/NotificationController.ts +++ b/src/notification/NotificationController.ts @@ -14,6 +14,15 @@ export type NotificationControllerState = { notifications: Record; }; +/** + * @typedef Notification - Stores information about in-app notifications, to be shown in the UI + * @property id - A UUID that identifies the notification + * @property type - The notification type + * @property origin - The origin that requested the notification + * @property createdDate - The notification creation date in milliseconds elapsed since the UNIX epoch + * @property readDate - The notification read date in milliseconds elapsed since the UNIX epoch or null if unread + * @property message - The notification message + */ export type Notification = { id: string; type: NotificationType; From 1dc253f1e314edf90a351868d83f6cdd4b9c7751 Mon Sep 17 00:00:00 2001 From: Frederik Bolding Date: Tue, 19 Apr 2022 11:05:22 +0200 Subject: [PATCH 15/17] Expand test slightly --- .../NotificationController.test.ts | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/notification/NotificationController.test.ts b/src/notification/NotificationController.test.ts index f0e82f1a83..92c1660b13 100644 --- a/src/notification/NotificationController.test.ts +++ b/src/notification/NotificationController.test.ts @@ -112,9 +112,12 @@ describe('NotificationController', () => { expect(showNativeNotification).toHaveBeenCalledTimes(0); const notifications = Object.values(controller.state.notifications); expect(notifications).toHaveLength(1); - await unrestricted.call('NotificationController:markRead', [ - notifications[0].id, - ]); + expect( + await unrestricted.call('NotificationController:markRead', [ + notifications[0].id, + 'foo', + ]), + ).toBeUndefined(); const newNotifications = Object.values(controller.state.notifications); expect(newNotifications).toContainEqual({ @@ -144,9 +147,12 @@ describe('NotificationController', () => { expect(showNativeNotification).toHaveBeenCalledTimes(0); const notifications = Object.values(controller.state.notifications); expect(notifications).toHaveLength(1); - await unrestricted.call('NotificationController:dismiss', [ - notifications[0].id, - ]); + expect( + await unrestricted.call('NotificationController:dismiss', [ + notifications[0].id, + 'foo', + ]), + ).toBeUndefined(); expect(Object.values(controller.state.notifications)).toHaveLength(0); }); From 304b8c8f2920d8f1a50d95937927790d04dcaaa7 Mon Sep 17 00:00:00 2001 From: Frederik Bolding Date: Wed, 27 Apr 2022 13:25:32 +0200 Subject: [PATCH 16/17] Fix test type --- src/notification/NotificationController.test.ts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/notification/NotificationController.test.ts b/src/notification/NotificationController.test.ts index 92c1660b13..e0173c9d22 100644 --- a/src/notification/NotificationController.test.ts +++ b/src/notification/NotificationController.test.ts @@ -2,7 +2,6 @@ import { ControllerMessenger } from '../ControllerMessenger'; import { ControllerActions, NotificationController, - NotificationControllerMessenger, NotificationControllerStateChange, NotificationType, } from './NotificationController'; @@ -30,14 +29,9 @@ function getUnrestrictedMessenger() { function getRestrictedMessenger( controllerMessenger = getUnrestrictedMessenger(), ) { - return controllerMessenger.getRestricted< - typeof name, - ControllerActions['type'], - never - >({ + return controllerMessenger.getRestricted({ name, - allowedActions: ['NotificationController:show'], - }) as NotificationControllerMessenger; + }); } const origin = 'snap_test'; From 79a468b2177a56a2aef373be029e932c34346409 Mon Sep 17 00:00:00 2001 From: Frederik Bolding Date: Wed, 27 Apr 2022 13:32:10 +0200 Subject: [PATCH 17/17] Remove native notifications --- .../NotificationController.test.ts | 65 ++----------------- src/notification/NotificationController.ts | 45 +------------ 2 files changed, 7 insertions(+), 103 deletions(-) diff --git a/src/notification/NotificationController.test.ts b/src/notification/NotificationController.test.ts index e0173c9d22..353d333764 100644 --- a/src/notification/NotificationController.test.ts +++ b/src/notification/NotificationController.test.ts @@ -3,7 +3,6 @@ import { ControllerActions, NotificationController, NotificationControllerStateChange, - NotificationType, } from './NotificationController'; const name = 'NotificationController'; @@ -38,43 +37,17 @@ const origin = 'snap_test'; const message = 'foo'; describe('NotificationController', () => { - it('action: NotificationController:show native notifications', async () => { + it('action: NotificationController:show', async () => { const unrestricted = getUnrestrictedMessenger(); const messenger = getRestrictedMessenger(unrestricted); - const showNativeNotification = jest.fn(); - new NotificationController({ - showNativeNotification, - messenger, - }); - - expect( - await unrestricted.call('NotificationController:show', origin, { - type: NotificationType.Native, - message, - }), - ).toBeUndefined(); - expect(showNativeNotification).toHaveBeenCalledTimes(1); - expect(showNativeNotification).toHaveBeenCalledWith(origin, message); - }); - - it('action: NotificationController:show in-app notifications', async () => { - const unrestricted = getUnrestrictedMessenger(); - const messenger = getRestrictedMessenger(unrestricted); - - const showNativeNotification = jest.fn(); const controller = new NotificationController({ - showNativeNotification, messenger, }); expect( - await unrestricted.call('NotificationController:show', origin, { - type: NotificationType.InApp, - message, - }), + await unrestricted.call('NotificationController:show', origin, message), ).toBeUndefined(); - expect(showNativeNotification).toHaveBeenCalledTimes(0); const notifications = Object.values(controller.state.notifications); expect(notifications).toHaveLength(1); expect(notifications).toContainEqual({ @@ -83,7 +56,6 @@ describe('NotificationController', () => { message, origin, readDate: null, - type: NotificationType.InApp, }); }); @@ -91,19 +63,13 @@ describe('NotificationController', () => { const unrestricted = getUnrestrictedMessenger(); const messenger = getRestrictedMessenger(unrestricted); - const showNativeNotification = jest.fn(); const controller = new NotificationController({ - showNativeNotification, messenger, }); expect( - await unrestricted.call('NotificationController:show', origin, { - type: NotificationType.InApp, - message, - }), + await unrestricted.call('NotificationController:show', origin, message), ).toBeUndefined(); - expect(showNativeNotification).toHaveBeenCalledTimes(0); const notifications = Object.values(controller.state.notifications); expect(notifications).toHaveLength(1); expect( @@ -126,19 +92,13 @@ describe('NotificationController', () => { const unrestricted = getUnrestrictedMessenger(); const messenger = getRestrictedMessenger(unrestricted); - const showNativeNotification = jest.fn(); const controller = new NotificationController({ - showNativeNotification, messenger, }); expect( - await unrestricted.call('NotificationController:show', origin, { - type: NotificationType.InApp, - message, - }), + await unrestricted.call('NotificationController:show', origin, message), ).toBeUndefined(); - expect(showNativeNotification).toHaveBeenCalledTimes(0); const notifications = Object.values(controller.state.notifications); expect(notifications).toHaveLength(1); expect( @@ -150,21 +110,4 @@ describe('NotificationController', () => { expect(Object.values(controller.state.notifications)).toHaveLength(0); }); - - it('uses showNativeNotification to show a notification', () => { - const messenger = getRestrictedMessenger(); - - const showNativeNotification = jest.fn(); - const controller = new NotificationController({ - showNativeNotification, - messenger, - }); - expect( - controller.show(origin, { - type: NotificationType.Native, - message, - }), - ).toBeUndefined(); - expect(showNativeNotification).toHaveBeenCalledWith(origin, message); - }); }); diff --git a/src/notification/NotificationController.ts b/src/notification/NotificationController.ts index 89b3a5d298..5ff29d59ee 100644 --- a/src/notification/NotificationController.ts +++ b/src/notification/NotificationController.ts @@ -17,7 +17,6 @@ export type NotificationControllerState = { /** * @typedef Notification - Stores information about in-app notifications, to be shown in the UI * @property id - A UUID that identifies the notification - * @property type - The notification type * @property origin - The origin that requested the notification * @property createdDate - The notification creation date in milliseconds elapsed since the UNIX epoch * @property readDate - The notification read date in milliseconds elapsed since the UNIX epoch or null if unread @@ -25,30 +24,12 @@ export type NotificationControllerState = { */ export type Notification = { id: string; - type: NotificationType; origin: string; createdDate: number; readDate: number | null; message: string; }; -export enum NotificationType { - Native = 'native', - InApp = 'in-app', -} - -export interface NotificationArgs { - /** - * Enum type to determine notification type. - */ - type: NotificationType; - - /** - * A message to show on the notification. - */ - message: string; -} - const name = 'NotificationController'; export type NotificationControllerStateChange = { @@ -106,24 +87,19 @@ export class NotificationController extends BaseController< NotificationControllerState, NotificationControllerMessenger > { - private showNativeNotification; - /** * Creates a NotificationController instance. * * @param options - Constructor options. * @param options.messenger - A reference to the messaging system. * @param options.state - Initial state to set on this controller. - * @param options.showNativeNotification - Function that shows a native notification in the consumer */ constructor({ messenger, state, - showNativeNotification, }: { messenger: NotificationControllerMessenger; state?: Partial; - showNativeNotification: (origin: string, message: string) => void; }) { super({ name, @@ -131,11 +107,10 @@ export class NotificationController extends BaseController< messenger, state: { ...defaultState, ...state }, }); - this.showNativeNotification = showNativeNotification; this.messagingSystem.registerActionHandler( `${name}:show` as const, - (origin: string, args: NotificationArgs) => this.show(origin, args), + (origin: string, message: string) => this.show(origin, message), ); this.messagingSystem.registerActionHandler( @@ -153,26 +128,12 @@ export class NotificationController extends BaseController< * Shows a notification. * * @param origin - The origin trying to send a notification - * @param args - Notification arguments, containing the notification message etc. + * @param message - A message to show on the notification */ - show(origin: string, args: NotificationArgs) { - switch (args.type) { - case NotificationType.Native: - this.showNativeNotification(origin, args.message); - break; - case NotificationType.InApp: - this.add(origin, args.message); - break; - default: - throw new Error('Invalid notification type'); - } - } - - private add(origin: string, message: string) { + show(origin: string, message: string) { const id = nanoid(); const notification = { id, - type: NotificationType.InApp, origin, createdDate: Date.now(), readDate: null,