11/**
2- * Copyright 2020, 2022, Optimizely
2+ * Copyright 2020, 2022, 2024, Optimizely
33 *
44 * Licensed under the Apache License, Version 2.0 (the "License");
55 * you may not use this file except in compliance with the License.
1515 */
1616import { LogHandler , ErrorHandler } from '../modules/logging' ;
1717import { objectValues } from '../utils/fns' ;
18- import { NotificationListener , ListenerPayload } from '../shared_types' ;
1918
2019import {
2120 LOG_LEVEL ,
2221 LOG_MESSAGES ,
23- NOTIFICATION_TYPES ,
2422} from '../utils/enums' ;
2523
24+ import { NOTIFICATION_TYPES } from './type' ;
25+ import { NotificationType , NotificationPayload } from './type' ;
26+ import { Consumer , Fn } from '../utils/type' ;
27+ import { EventEmitter } from '../utils/event_emitter/event_emitter' ;
28+
2629const MODULE_NAME = 'NOTIFICATION_CENTER' ;
2730
2831interface NotificationCenterOptions {
2932 logger : LogHandler ;
3033 errorHandler : ErrorHandler ;
3134}
32-
33- interface ListenerEntry {
34- id : number ;
35- // eslint-disable-next-line @typescript-eslint/no-explicit-any
36- callback : ( notificationData : any ) => void ;
35+ export interface NotificationCenter {
36+ addNotificationListener < N extends NotificationType > (
37+ notificationType : N ,
38+ callback : Consumer < NotificationPayload [ N ] >
39+ ) : number
40+ removeNotificationListener ( listenerId : number ) : boolean ;
41+ clearAllNotificationListeners ( ) : void ;
42+ clearNotificationListeners ( notificationType : NotificationType ) : void ;
3743}
3844
39- type NotificationListeners = {
40- [ key : string ] : ListenerEntry [ ] ;
45+ export interface NotificationSender {
46+ sendNotifications < N extends NotificationType > (
47+ notificationType : N ,
48+ notificationData : NotificationPayload [ N ]
49+ ) : void ;
4150}
4251
4352/**
@@ -46,11 +55,13 @@ type NotificationListeners = {
4655 * - ACTIVATE: An impression event will be sent to Optimizely.
4756 * - TRACK a conversion event will be sent to Optimizely
4857 */
49- export class NotificationCenter {
58+ export class DefaultNotificationCenter implements NotificationCenter , NotificationSender {
5059 private logger : LogHandler ;
5160 private errorHandler : ErrorHandler ;
52- private notificationListeners : NotificationListeners ;
53- private listenerId : number ;
61+
62+ private removerId = 1 ;
63+ private eventEmitter : EventEmitter < NotificationPayload > = new EventEmitter ( ) ;
64+ private removers : Map < number , Fn > = new Map ( ) ;
5465
5566 /**
5667 * @constructor
@@ -61,13 +72,6 @@ export class NotificationCenter {
6172 constructor ( options : NotificationCenterOptions ) {
6273 this . logger = options . logger ;
6374 this . errorHandler = options . errorHandler ;
64- this . notificationListeners = { } ;
65- objectValues ( NOTIFICATION_TYPES ) . forEach (
66- ( notificationTypeEnum ) => {
67- this . notificationListeners [ notificationTypeEnum ] = [ ] ;
68- }
69- ) ;
70- this . listenerId = 1 ;
7175 }
7276
7377 /**
@@ -80,47 +84,40 @@ export class NotificationCenter {
8084 * can happen if the first argument is not a valid notification type, or if the same callback
8185 * function was already added as a listener by a prior call to this function.
8286 */
83- addNotificationListener < T extends ListenerPayload > (
84- notificationType : string ,
85- callback : NotificationListener < T >
87+ addNotificationListener < N extends NotificationType > (
88+ notificationType : N ,
89+ callback : Consumer < NotificationPayload [ N ] >
8690 ) : number {
87- try {
88- const notificationTypeValues : string [ ] = objectValues ( NOTIFICATION_TYPES ) ;
89- const isNotificationTypeValid = notificationTypeValues . indexOf ( notificationType ) > - 1 ;
90- if ( ! isNotificationTypeValid ) {
91- return - 1 ;
92- }
93-
94- if ( ! this . notificationListeners [ notificationType ] ) {
95- this . notificationListeners [ notificationType ] = [ ] ;
96- }
97-
98- let callbackAlreadyAdded = false ;
99- ( this . notificationListeners [ notificationType ] || [ ] ) . forEach (
100- ( listenerEntry ) => {
101- if ( listenerEntry . callback === callback ) {
102- callbackAlreadyAdded = true ;
103- return ;
104- }
105- } ) ;
106-
107- if ( callbackAlreadyAdded ) {
108- return - 1 ;
109- }
110-
111- this . notificationListeners [ notificationType ] . push ( {
112- id : this . listenerId ,
113- callback : callback ,
114- } ) ;
115-
116- const returnId = this . listenerId ;
117- this . listenerId += 1 ;
118- return returnId ;
119- } catch ( e : any ) {
120- this . logger . log ( LOG_LEVEL . ERROR , e . message ) ;
121- this . errorHandler . handleError ( e ) ;
91+ const notificationTypeValues : string [ ] = objectValues ( NOTIFICATION_TYPES ) ;
92+ const isNotificationTypeValid = notificationTypeValues . indexOf ( notificationType ) > - 1 ;
93+ if ( ! isNotificationTypeValid ) {
12294 return - 1 ;
12395 }
96+
97+ const returnId = this . removerId ++ ;
98+ const remover = this . eventEmitter . on (
99+ notificationType , this . wrapWithErrorHandling ( notificationType , callback ) ) ;
100+ this . removers . set ( returnId , remover ) ;
101+ return returnId ;
102+ }
103+
104+ private wrapWithErrorHandling < N extends NotificationType > (
105+ notificationType : N ,
106+ callback : Consumer < NotificationPayload [ N ] >
107+ ) : Consumer < NotificationPayload [ N ] > {
108+ return ( notificationData : NotificationPayload [ N ] ) => {
109+ try {
110+ callback ( notificationData ) ;
111+ } catch ( ex : any ) {
112+ this . logger . log (
113+ LOG_LEVEL . ERROR ,
114+ LOG_MESSAGES . NOTIFICATION_LISTENER_EXCEPTION ,
115+ MODULE_NAME ,
116+ notificationType ,
117+ ex . message ,
118+ ) ;
119+ }
120+ } ;
124121 }
125122
126123 /**
@@ -130,103 +127,40 @@ export class NotificationCenter {
130127 * otherwise.
131128 */
132129 removeNotificationListener ( listenerId : number ) : boolean {
133- try {
134- let indexToRemove : number | undefined ;
135- let typeToRemove : string | undefined ;
136-
137- Object . keys ( this . notificationListeners ) . some (
138- ( notificationType ) => {
139- const listenersForType = this . notificationListeners [ notificationType ] ;
140- ( listenersForType || [ ] ) . every ( ( listenerEntry , i ) => {
141- if ( listenerEntry . id === listenerId ) {
142- indexToRemove = i ;
143- typeToRemove = notificationType ;
144- return false ;
145- }
146-
147- return true ;
148- } ) ;
149-
150- if ( indexToRemove !== undefined && typeToRemove !== undefined ) {
151- return true ;
152- }
153-
154- return false ;
155- }
156- ) ;
157-
158- if ( indexToRemove !== undefined && typeToRemove !== undefined ) {
159- this . notificationListeners [ typeToRemove ] . splice ( indexToRemove , 1 ) ;
160- return true ;
161- }
162- } catch ( e : any ) {
163- this . logger . log ( LOG_LEVEL . ERROR , e . message ) ;
164- this . errorHandler . handleError ( e ) ;
130+ const remover = this . removers . get ( listenerId ) ;
131+ if ( remover ) {
132+ remover ( ) ;
133+ return true ;
165134 }
166-
167- return false ;
135+ return false
168136 }
169137
170138 /**
171139 * Removes all previously added notification listeners, for all notification types
172140 */
173141 clearAllNotificationListeners ( ) : void {
174- try {
175- objectValues ( NOTIFICATION_TYPES ) . forEach (
176- ( notificationTypeEnum ) => {
177- this . notificationListeners [ notificationTypeEnum ] = [ ] ;
178- }
179- ) ;
180- } catch ( e : any ) {
181- this . logger . log ( LOG_LEVEL . ERROR , e . message ) ;
182- this . errorHandler . handleError ( e ) ;
183- }
142+ this . eventEmitter . removeAllListeners ( ) ;
184143 }
185144
186145 /**
187146 * Remove all previously added notification listeners for the argument type
188- * @param {NOTIFICATION_TYPES } notificationType One of NOTIFICATION_TYPES
147+ * @param {NotificationType } notificationType One of NotificationType
189148 */
190- clearNotificationListeners ( notificationType : NOTIFICATION_TYPES ) : void {
191- try {
192- this . notificationListeners [ notificationType ] = [ ] ;
193- } catch ( e : any ) {
194- this . logger . log ( LOG_LEVEL . ERROR , e . message ) ;
195- this . errorHandler . handleError ( e ) ;
196- }
149+ clearNotificationListeners ( notificationType : NotificationType ) : void {
150+ this . eventEmitter . removeListeners ( notificationType ) ;
197151 }
198152
199153 /**
200154 * Fires notifications for the argument type. All registered callbacks for this type will be
201155 * called. The notificationData object will be passed on to callbacks called.
202- * @param {string } notificationType One of NOTIFICATION_TYPES
156+ * @param {NotificationType } notificationType One of NotificationType
203157 * @param {Object } notificationData Will be passed to callbacks called
204158 */
205- sendNotifications < T extends ListenerPayload > (
206- notificationType : string ,
207- notificationData ?: T
159+ sendNotifications < N extends NotificationType > (
160+ notificationType : N ,
161+ notificationData : NotificationPayload [ N ]
208162 ) : void {
209- try {
210- ( this . notificationListeners [ notificationType ] || [ ] ) . forEach (
211- ( listenerEntry ) => {
212- const callback = listenerEntry . callback ;
213- try {
214- callback ( notificationData ) ;
215- } catch ( ex : any ) {
216- this . logger . log (
217- LOG_LEVEL . ERROR ,
218- LOG_MESSAGES . NOTIFICATION_LISTENER_EXCEPTION ,
219- MODULE_NAME ,
220- notificationType ,
221- ex . message ,
222- ) ;
223- }
224- }
225- ) ;
226- } catch ( e : any ) {
227- this . logger . log ( LOG_LEVEL . ERROR , e . message ) ;
228- this . errorHandler . handleError ( e ) ;
229- }
163+ this . eventEmitter . emit ( notificationType , notificationData ) ;
230164 }
231165}
232166
@@ -235,12 +169,6 @@ export class NotificationCenter {
235169 * @param {NotificationCenterOptions } options
236170 * @returns {NotificationCenter } An instance of NotificationCenter
237171 */
238- export function createNotificationCenter ( options : NotificationCenterOptions ) : NotificationCenter {
239- return new NotificationCenter ( options ) ;
240- }
241-
242- export interface NotificationSender {
243- // TODO[OASIS-6649]: Don't use any type
244- // eslint-disable-next-line @typescript-eslint/no-explicit-any
245- sendNotifications ( notificationType : NOTIFICATION_TYPES , notificationData ?: any ) : void
172+ export function createNotificationCenter ( options : NotificationCenterOptions ) : DefaultNotificationCenter {
173+ return new DefaultNotificationCenter ( options ) ;
246174}
0 commit comments