1
1
/**
2
- * Copyright 2020, 2022, Optimizely
2
+ * Copyright 2020, 2022, 2024, Optimizely
3
3
*
4
4
* Licensed under the Apache License, Version 2.0 (the "License");
5
5
* you may not use this file except in compliance with the License.
15
15
*/
16
16
import { LogHandler , ErrorHandler } from '../modules/logging' ;
17
17
import { objectValues } from '../utils/fns' ;
18
- import { NotificationListener , ListenerPayload } from '../shared_types' ;
19
18
20
19
import {
21
20
LOG_LEVEL ,
22
21
LOG_MESSAGES ,
23
- NOTIFICATION_TYPES ,
24
22
} from '../utils/enums' ;
25
23
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
+
26
29
const MODULE_NAME = 'NOTIFICATION_CENTER' ;
27
30
28
31
interface NotificationCenterOptions {
29
32
logger : LogHandler ;
30
33
errorHandler : ErrorHandler ;
31
34
}
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 ;
37
43
}
38
44
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 ;
41
50
}
42
51
43
52
/**
@@ -46,11 +55,13 @@ type NotificationListeners = {
46
55
* - ACTIVATE: An impression event will be sent to Optimizely.
47
56
* - TRACK a conversion event will be sent to Optimizely
48
57
*/
49
- export class NotificationCenter {
58
+ export class DefaultNotificationCenter implements NotificationCenter , NotificationSender {
50
59
private logger : LogHandler ;
51
60
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 ( ) ;
54
65
55
66
/**
56
67
* @constructor
@@ -61,13 +72,6 @@ export class NotificationCenter {
61
72
constructor ( options : NotificationCenterOptions ) {
62
73
this . logger = options . logger ;
63
74
this . errorHandler = options . errorHandler ;
64
- this . notificationListeners = { } ;
65
- objectValues ( NOTIFICATION_TYPES ) . forEach (
66
- ( notificationTypeEnum ) => {
67
- this . notificationListeners [ notificationTypeEnum ] = [ ] ;
68
- }
69
- ) ;
70
- this . listenerId = 1 ;
71
75
}
72
76
73
77
/**
@@ -80,47 +84,40 @@ export class NotificationCenter {
80
84
* can happen if the first argument is not a valid notification type, or if the same callback
81
85
* function was already added as a listener by a prior call to this function.
82
86
*/
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 ] >
86
90
) : 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 ) {
122
94
return - 1 ;
123
95
}
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
+ } ;
124
121
}
125
122
126
123
/**
@@ -130,103 +127,40 @@ export class NotificationCenter {
130
127
* otherwise.
131
128
*/
132
129
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 ;
165
134
}
166
-
167
- return false ;
135
+ return false
168
136
}
169
137
170
138
/**
171
139
* Removes all previously added notification listeners, for all notification types
172
140
*/
173
141
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 ( ) ;
184
143
}
185
144
186
145
/**
187
146
* 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
189
148
*/
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 ) ;
197
151
}
198
152
199
153
/**
200
154
* Fires notifications for the argument type. All registered callbacks for this type will be
201
155
* 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
203
157
* @param {Object } notificationData Will be passed to callbacks called
204
158
*/
205
- sendNotifications < T extends ListenerPayload > (
206
- notificationType : string ,
207
- notificationData ?: T
159
+ sendNotifications < N extends NotificationType > (
160
+ notificationType : N ,
161
+ notificationData : NotificationPayload [ N ]
208
162
) : 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 ) ;
230
164
}
231
165
}
232
166
@@ -235,12 +169,6 @@ export class NotificationCenter {
235
169
* @param {NotificationCenterOptions } options
236
170
* @returns {NotificationCenter } An instance of NotificationCenter
237
171
*/
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 ) ;
246
174
}
0 commit comments