@crasman/google-optimize-experiment
Version:
Shared module for AB testing via Google Optimize Experiments.
142 lines (123 loc) • 4.11 kB
JavaScript
/**
* Registers an implementation callback for an experiment to Google Optimize JavaScript API
* and provides an option to fallback to default variant on timeout.
*
* API documentation:
* {@link https://support.google.com/optimize/answer/9059383}
*
* @class GoogleOptimizeExperiment
*
* @example
* const experiment = new GoogleOptimizeExperiment({
* callback: (variant) => variant.hidden = false,
* callbackTimeout: 1000,
* experimentId: 'JrXRqt5hRlCe29fqwiWkUg',
* variants: [...document.getElementsByClassName('variant')],
* });
*/
class GoogleOptimizeExperiment {
/**
* Creates an instance of GoogleOptimizeExperiment.
* @param {object} params - object containing configuration parameters
* @param {function(variant, variantIndex)} [params.callback=() => {}] - Function to call back with variant and its index.
* @param {string} [params.callbackEventName='optimize.callback'] - Name of the custom event that activates the experiment.
* @param {number} [params.callbackTimeout] - Time in milliseconds to timeout and to fall back to default variant.
* @param {string} params.experimentId - ID of experiment.
* @param {number} [params.fallbackVariantIndex=1] - Index of variant to fall back to on timeout.
* @param {HTMLElement[]} [params.variants=[]] - Variants to test.
* @memberof GoogleOptimizeExperiment
*/
constructor({
callback = () => {},
callbackEventName = 'optimize.callback',
callbackTimeout,
experimentId,
fallbackVariantIndex = 1,
variants = []
}) {
this.callback = callback;
this.callbackEventName = callbackEventName;
this.callbackTimeout = callbackTimeout;
this.experimentId = experimentId;
this.fallbackVariantIndex = fallbackVariantIndex;
this.variants = variants;
this.variantIndex = -1;
this.callbackWrapper = this.callbackWrapper.bind(this);
if (this.callbackTimeout) {
this.callbackTimeoutId = setTimeout(() => {
this.unregisterCallback();
this.variantIndex = this.fallbackVariantIndex;
this.callback(this.variantIndex, this.experimentId);
}, this.callbackTimeout);
}
this.registerCallback();
}
/**
* Gets active variant.
*
* @memberof GoogleOptimizeExperiment
*/
get variant() {
return this.variantIndex in this.variants ? this.variants[this.variantIndex] : null;
}
/**
* @memberof GoogleOptimizeExperiment
* @param {any} _ ignore
*/
set variant(_) {}
/**
* Handles callback from the API and calls the provided callback function with active variant and its index.
*
* @param {string} variantIndex - the selected variant
* @param {string} experimentId - the active experiment id
* @param {string} optimizeId - the active optimize account
* @memberof GoogleOptimizeExperiment
*/
callbackWrapper(variantIndex, experimentId, optimizeId) {
if (this.callbackTimeoutId) clearTimeout(this.callbackTimeoutId);
this.variantIndex = parseInt(variantIndex);
this.callback(this.variantIndex, experimentId, optimizeId);
}
/**
* Clears callback timeout and unregisters callback from the API.
*
* @memberof GoogleOptimizeExperiment
*/
cleanup() {
if (this.callbackTimeoutId) clearTimeout(this.callbackTimeoutId);
this.unregisterCallback();
}
/**
* Registers implementation callback for an experiment to the API.
*
* @memberof GoogleOptimizeExperiment
*/
registerCallback() {
gtag('event', this.callbackEventName, {
name: this.experimentId,
callback: this.callbackWrapper
});
}
/**
* Unregisters implementation callback for an experiment from the API.
*
* @memberof GoogleOptimizeExperiment
*/
unregisterCallback() {
gtag('event', this.callbackEventName, {
name: this.experimentId,
callback: this.callbackWrapper,
remove: true
});
}
}
/**
* Pushes arguments to data layer.
*/
function gtag() {
if (!('dataLayer' in window)) {
window.dataLayer = [];
}
window.dataLayer.push(arguments);
}
export { GoogleOptimizeExperiment };