Skip to content

Commit 1be4665

Browse files
authored
feat (event processor/pending events dispatcher): In browser entry point, add event listener for pagehide or unload that calls optimizely.close (#347)
Summary: With event batching, optimizely.close() needs to be called to ensure that events are flushed before the user navigates away. This PR ensures that optimizely.close() is called when the page unloads by hooking into the 'pagehide' event or the 'unload' event. Test plan: Added a UMD bundle test. Manually tested.
1 parent 5f49770 commit 1be4665

File tree

3 files changed

+53
-11
lines changed

3 files changed

+53
-11
lines changed

packages/optimizely-sdk/lib/index.browser.js

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ var logger = logging.getLogger();
2929
logging.setLogHandler(loggerPlugin.createLogger());
3030
logging.setLogLevel(logging.LogLevel.INFO);
3131

32+
var MODULE_NAME = 'INDEX_BROWSER';
33+
3234
var DEFAULT_EVENT_BATCH_SIZE = 10;
3335
var DEFAULT_EVENT_FLUSH_INTERVAL = 1000; // Unit is ms, default is 1s
3436

@@ -105,16 +107,20 @@ module.exports = {
105107
eventDispatcher = config.eventDispatcher;
106108
}
107109

108-
config = fns.assignIn({
109-
clientEngine: enums.JAVASCRIPT_CLIENT_ENGINE,
110-
eventBatchSize: DEFAULT_EVENT_BATCH_SIZE,
111-
eventFlushInterval: DEFAULT_EVENT_FLUSH_INTERVAL,
112-
}, config, {
113-
eventDispatcher: eventDispatcher,
114-
// always get the OptimizelyLogger facade from logging
115-
logger: logger,
116-
errorHandler: logging.getErrorHandler(),
117-
});
110+
config = fns.assignIn(
111+
{
112+
clientEngine: enums.JAVASCRIPT_CLIENT_ENGINE,
113+
eventBatchSize: DEFAULT_EVENT_BATCH_SIZE,
114+
eventFlushInterval: DEFAULT_EVENT_FLUSH_INTERVAL,
115+
},
116+
config,
117+
{
118+
eventDispatcher: eventDispatcher,
119+
// always get the OptimizelyLogger facade from logging
120+
logger: logger,
121+
errorHandler: logging.getErrorHandler(),
122+
}
123+
);
118124

119125
if (!eventProcessorConfigValidator.validateEventBatchSize(config.eventBatchSize)) {
120126
logger.warn('Invalid eventBatchSize %s, defaulting to %s', config.eventBatchSize, DEFAULT_EVENT_BATCH_SIZE);
@@ -125,7 +131,24 @@ module.exports = {
125131
config.eventFlushInterval = DEFAULT_EVENT_FLUSH_INTERVAL;
126132
}
127133

128-
return new Optimizely(config);
134+
var optimizely = new Optimizely(config);
135+
136+
try {
137+
if (typeof window.addEventListener === 'function') {
138+
var unloadEvent = 'onpagehide' in window ? 'pagehide' : 'unload';
139+
window.addEventListener(
140+
unloadEvent,
141+
function() {
142+
optimizely.close();
143+
},
144+
false
145+
);
146+
}
147+
} catch (e) {
148+
logger.error(enums.LOG_MESSAGES.UNABLE_TO_ATTACH_UNLOAD, MODULE_NAME, e.message);
149+
}
150+
151+
return optimizely;
129152
} catch (e) {
130153
logger.error(e);
131154
return null;

packages/optimizely-sdk/lib/index.browser.umdtests.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
var configValidator = require('./utils/config_validator');
1717
var enums = require('./utils/enums');
1818
var logger = require('./plugins/logger');
19+
var Optimizely = require('./optimizely');
1920

2021
var packageJSON = require('../package.json');
2122
var eventDispatcher = require('./plugins/event_dispatcher/index.browser');
@@ -40,6 +41,7 @@ describe('javascript-sdk', function() {
4041
logToConsole: false,
4142
});
4243
sinon.stub(configValidator, 'validate');
44+
sinon.stub(Optimizely.prototype, 'close');
4345

4446
xhr = sinon.useFakeXMLHttpRequest();
4547
global.XMLHttpRequest = xhr;
@@ -52,14 +54,18 @@ describe('javascript-sdk', function() {
5254
sinon.spy(console, 'info');
5355
sinon.spy(console, 'warn');
5456
sinon.spy(console, 'error');
57+
58+
sinon.spy(window, 'addEventListener');
5559
});
5660

5761
afterEach(function() {
5862
console.log.restore();
5963
console.info.restore();
6064
console.warn.restore();
6165
console.error.restore();
66+
window.addEventListener.restore();
6267
configValidator.validate.restore();
68+
Optimizely.prototype.close.restore();
6369
xhr.restore();
6470
});
6571

@@ -292,6 +298,18 @@ describe('javascript-sdk', function() {
292298
var variation = optlyInstance.getVariation('testExperimentNotRunning', 'testUser');
293299
assert.strictEqual(variation, null);
294300
});
301+
302+
it('should hook into window `pagehide` event', function() {
303+
var optlyInstance = window.optimizelySdk.createInstance({
304+
datafile: testData.getTestProjectConfig(),
305+
errorHandler: fakeErrorHandler,
306+
eventDispatcher: eventDispatcher,
307+
logger: silentLogger,
308+
});
309+
310+
sinon.assert.calledOnce(window.addEventListener);
311+
sinon.assert.calledWith(window.addEventListener, sinon.match('pagehide').or(sinon.match('unload')));
312+
});
295313
});
296314
});
297315
});

packages/optimizely-sdk/lib/utils/enums/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ exports.LOG_MESSAGES = {
141141
UNKNOWN_MATCH_TYPE: '%s: Audience condition %s uses an unknown match type. You may need to upgrade to a newer release of the Optimizely SDK.',
142142
UPDATED_OPTIMIZELY_CONFIG: '%s: Updated Optimizely config to revision %s (project id %s)',
143143
OUT_OF_BOUNDS: '%s: Audience condition %s evaluated to UNKNOWN because the number value for user attribute "%s" is not in the range [-2^53, +2^53].',
144+
UNABLE_TO_ATTACH_UNLOAD: '%s: unable to bind optimizely.close() to page unload event: "%s"',
144145
};
145146

146147
exports.RESERVED_EVENT_KEYWORDS = {

0 commit comments

Comments
 (0)