Skip to content
This repository was archived by the owner on Jan 11, 2024. It is now read-only.

Commit 5b286fa

Browse files
authored
Notification listeners (#98)
1 parent 8f33526 commit 5b286fa

File tree

4 files changed

+695
-0
lines changed

4 files changed

+695
-0
lines changed

lib/core/notification_center/index.js

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
/**
2+
* Copyright 2017, Optimizely
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
var enums = require('../../utils/enums');
18+
var fns = require('../../utils/fns');
19+
var sprintf = require('sprintf');
20+
21+
var LOG_LEVEL = enums.LOG_LEVEL;
22+
var LOG_MESSAGES = enums.LOG_MESSAGES;
23+
var MODULE_NAME = 'NOTIFICATION_CENTER';
24+
25+
/**
26+
* NotificationCenter allows registration and triggering of callback functions using
27+
* notification event types defined in NOTIFICATION_TYPES of utils/enums/index.js:
28+
* - ACTIVATE: An impression event will be sent to Optimizely.
29+
* - TRACK a conversion event will be sent to Optimizely
30+
* @constructor
31+
* @param {Object} options
32+
* @param {Object} options.logger An instance of a logger to log messages with
33+
* @returns {Object}
34+
*/
35+
function NotificationCenter(options) {
36+
this.logger = options.logger;
37+
this.__notificationListeners = {};
38+
fns.forOwn(enums.NOTIFICATION_TYPES, function(notificationTypeEnum) {
39+
this.__notificationListeners[notificationTypeEnum] = [];
40+
}.bind(this));
41+
this.__listenerId = 1;
42+
}
43+
44+
/**
45+
* Add a notification callback to the notification center
46+
* @param {string} notificationType One of the values from NOTIFICATION_TYPES in utils/enums/index.js
47+
* @param {Function} callback Function that will be called when the event is triggered
48+
* @returns {number} If the callback was successfully added, returns a listener ID which can be used
49+
* to remove the callback by calling removeNotificationListener. The ID is a number greater than 0.
50+
* If there was an error and the listener was not added, addNotificationListener returns -1. This
51+
* can happen if the first argument is not a valid notification type, or if the same callback
52+
* function was already added as a listener by a prior call to this function.
53+
*/
54+
NotificationCenter.prototype.addNotificationListener = function(notificationType, callback) {
55+
var isNotificationTypeValid = fns.values(enums.NOTIFICATION_TYPES)
56+
.indexOf(notificationType) > -1;
57+
if (!isNotificationTypeValid) {
58+
return -1;
59+
}
60+
61+
if (!this.__notificationListeners[notificationType]) {
62+
this.__notificationListeners[notificationType] = [];
63+
}
64+
65+
var callbackAlreadyAdded = false;
66+
fns.forEach(this.__notificationListeners[notificationType], function(listenerEntry) {
67+
if (listenerEntry.callback === callback) {
68+
callbackAlreadyAdded = true;
69+
return false;
70+
}
71+
});
72+
if (callbackAlreadyAdded) {
73+
return -1;
74+
}
75+
76+
this.__notificationListeners[notificationType].push({
77+
id: this.__listenerId,
78+
callback: callback,
79+
});
80+
81+
var returnId = this.__listenerId;
82+
this.__listenerId += 1;
83+
return returnId;
84+
};
85+
86+
/**
87+
* Remove a previously added notification callback
88+
* @param {number} listenerId ID of listener to be removed
89+
* @returns {boolean} Returns true if the listener was found and removed, and false
90+
* otherwise.
91+
*/
92+
NotificationCenter.prototype.removeNotificationListener = function(listenerId) {
93+
var indexToRemove;
94+
var typeToRemove;
95+
fns.forOwn(this.__notificationListeners, function(listenersForType, notificationType) {
96+
fns.forEach(listenersForType, function(listenerEntry, i) {
97+
if (listenerEntry.id === listenerId) {
98+
indexToRemove = i;
99+
typeToRemove = notificationType;
100+
return false;
101+
}
102+
});
103+
if (indexToRemove !== undefined && typeToRemove !== undefined) {
104+
return false;
105+
}
106+
});
107+
108+
if (indexToRemove !== undefined && typeToRemove !== undefined) {
109+
this.__notificationListeners[typeToRemove].splice(indexToRemove, 1);
110+
return true;
111+
}
112+
113+
return false;
114+
};
115+
116+
/**
117+
* Removes all previously added notification listeners, for all notification types
118+
*/
119+
NotificationCenter.prototype.clearAllNotificationListeners = function() {
120+
fns.forOwn(enums.NOTIFICATION_TYPES, function(notificationTypeEnum) {
121+
this.__notificationListeners[notificationTypeEnum] = [];
122+
}.bind(this));
123+
};
124+
125+
/**
126+
* Remove all previously added notification listeners for the argument type
127+
* @param {string} notificationType One of enums.NOTIFICATION_TYPES
128+
*/
129+
NotificationCenter.prototype.clearNotificationListeners = function(notificationType) {
130+
this.__notificationListeners[notificationType] = [];
131+
};
132+
133+
/**
134+
* Fires notifications for the argument type. All registered callbacks for this type will be
135+
* called. The notificationData object will be passed on to callbacks called.
136+
* @param {string} notificationType One of enums.NOTIFICATION_TYPES
137+
* @param {Object} notificationData Will be passed to callbacks called
138+
*/
139+
NotificationCenter.prototype.sendNotifications = function(notificationType, notificationData) {
140+
fns.forEach(this.__notificationListeners[notificationType], function(listenerEntry) {
141+
var callback = listenerEntry.callback;
142+
try {
143+
callback(notificationData);
144+
} catch (ex) {
145+
this.logger.log(LOG_LEVEL.ERROR, sprintf(LOG_MESSAGES.NOTIFICATION_LISTENER_EXCEPTION, MODULE_NAME, notificationType, ex.message));
146+
}
147+
}.bind(this));
148+
};
149+
150+
module.exports = {
151+
/**
152+
* Create an instance of NotificationCenter
153+
* @param {Object} options
154+
* @param {Object} options.logger An instance of a logger to log messages with
155+
* @returns {Object} An instance of NotificationCenter
156+
*/
157+
createNotificationCenter: function(options) {
158+
return new NotificationCenter(options);
159+
},
160+
};

lib/optimizely/index.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ var decisionService = require('../core/decision_service');
2020
var enums = require('../utils/enums');
2121
var eventBuilder = require('../core/event_builder/index.js');
2222
var eventTagsValidator = require('../utils/event_tags_validator');
23+
var notificationCenter = require('../core/notification_center');
2324
var projectConfig = require('../core/project_config');
2425
var projectConfigSchema = require('./project_config_schema');
2526
var sprintf = require('sprintf');
@@ -95,6 +96,10 @@ function Optimizely(config) {
9596
userProfileService: userProfileService,
9697
logger: this.logger,
9798
});
99+
100+
this.notificationCenter = notificationCenter.createNotificationCenter({
101+
logger: this.logger,
102+
});
98103
}
99104
}
100105

@@ -153,6 +158,23 @@ Optimizely.prototype.activate = function(experimentKey, userId, attributes) {
153158
}.bind(this);
154159

155160
this.__dispatchEvent(impressionEvent, eventDispatcherCallback);
161+
162+
var experiment = this.configObj.experimentKeyMap[experimentKey];
163+
var variation;
164+
if (experiment && experiment.variationKeyMap) {
165+
variation = experiment.variationKeyMap[variationKey];
166+
}
167+
this.notificationCenter.sendNotifications(
168+
enums.NOTIFICATION_TYPES.ACTIVATE,
169+
{
170+
experiment: experiment,
171+
userId: userId,
172+
attributes: attributes,
173+
variation: variation,
174+
logEvent: impressionEvent
175+
}
176+
);
177+
156178
return variationKey;
157179
} catch (ex) {
158180
this.logger.log(LOG_LEVEL.ERROR, ex.message);
@@ -228,6 +250,17 @@ Optimizely.prototype.track = function(eventKey, userId, attributes, eventTags) {
228250
}.bind(this);
229251

230252
this.__dispatchEvent(conversionEvent, eventDispatcherCallback);
253+
254+
this.notificationCenter.sendNotifications(
255+
enums.NOTIFICATION_TYPES.TRACK,
256+
{
257+
eventKey: eventKey,
258+
userId: userId,
259+
attributes: attributes,
260+
eventTags: eventTags,
261+
logEvent: conversionEvent
262+
}
263+
);
231264
} catch (ex) {
232265
this.logger.log(LOG_LEVEL.ERROR, ex.message);
233266
var failedTrackLogMessage = sprintf(LOG_MESSAGES.NOT_TRACKING_USER, MODULE_NAME, userId);

0 commit comments

Comments
 (0)