import React, { PropTypes } from 'react' export const insertScriptHelper = (projectId) => { const el = document.querySelector('[data-optimizely-snippet]') if (!isReadyHelper() && !el) { const protocol = `${document.location.protocol}//` const scriptTag = document.createElement('script') scriptTag.type = 'text/javascript' scriptTag.async = true scriptTag.src = `${protocol}cdn.optimizely.com/js/${projectId}.js` scriptTag.setAttribute('data-optimizely-snippet', true) document.getElementsByTagName('head')[0].appendChild(scriptTag) } } export const triggerEventHelper = (eventName) => { try { typeof window.optimizely === 'object' && window.optimizely.push({ type: 'event', eventName: eventName, }) } catch (e) { throw Error('OptimizelyExperiment triggerEventHelper unexpected error!') } } export const isReadyHelper = () => { return typeof window.optimizely === 'object' && typeof window.optimizely.data === 'object' && typeof window.optimizely.allExperiments === 'object' } class OptimizelyExperiment extends React.Component { constructor (props) { super(props) window.optimizely = window.optimizely || [] window.optimizely.push({ type: 'log', level: 'info', }) this.onReadyRecallMs = 10 this.defaultState = { variantName: null, result: null, goalCompleted: false, } this.eventListeners = [] this.state = Object.assign({}, this.defaultState) this.mapVariantToState = this.mapVariantToState.bind(this) this.eventHandler = this.eventHandler.bind(this) } componentWillMount () { if (!isReadyHelper()) { this.insertScript() } } componentDidMount () { this.observeReady(this.mapVariantToState) } componentDidUpdate (prevProps, prevState) { if (isReadyHelper() && prevState.variantName !== this.state.variantName) { this.setResult() } if (prevState.goalCompleted !== this.state.goalCompleted) { this.removeAllEventListeners() } } componentWillUnmount () { this.removeAllEventListeners() } observeReady (cb) { const { allExperiments } = window.optimizely if (allExperiments && typeof allExperiments[this.props.experimentId] !== 'undefined') { this.triggerPageActivation() this.props.customEvents.forEach(({selector, eventType, eventName}) => this.attachEventHandler(selector, eventType, eventName)) if (typeof cb === 'function') cb() } else { const t = setTimeout(() => { clearTimeout(t) this.observeReady(cb) }, this.onReadyRecallMs) } } mapVariantToState () { const { experimentId } = this.props const { variationIdsMap, allVariations } = window.optimizely let variantName = 'Original' if (this.isExperimentActive(experimentId)) { const variationId = variationIdsMap[experimentId][0] // simple AB test, not multiple variantName = allVariations[variationId].name } this.setState(() => { // keep a reference for debugging this.setMiddlemanRefHelper({ variantName, }) return { variantName, } }) } isExperimentActive (experimentId) { const { activeExperiments } = window.optimizely return activeExperiments.find(v => v === experimentId) } insertScript () { const { projectId } = this.props insertScriptHelper(projectId) } runExperiment () { const { variantName } = this.state const { experimentsMap } = this.props let result = experimentsMap[variantName] || false if (typeof result === 'function') { result = result() } return result } setResult () { this.setState({ result: this.runExperiment(), }) } triggerPageActivation () { window.optimizely.push({ type: 'page', pageName: this.props.pageName, }) } attachEventHandler (selector, eventType, eventName) { if (Array.isArray(selector)) { return selector.forEach(v => this.attachEventHandler(v, eventType, eventName)) } const target = document.querySelector(selector) const eventHandler = this.eventHandler.bind(undefined, target, eventType, eventName) if (target) { target.addEventListener(eventType, eventHandler, true) this.eventListeners.push({ target, eventHandler, eventType, }) } } eventHandler (target, eventType, eventName) { this.setState(() => { triggerEventHelper(eventName) return { goalCompleted: true, } }) } removeAllEventListeners () { this.eventListeners.forEach(({target, eventType, eventHandler}) => this.removeEventListener(target, eventType, eventHandler)) } removeEventListener (target, eventType, eventHandler) { target.removeEventListener(eventType, eventHandler, true) } setMiddlemanRefHelper (data) { const propName = '__AB_TEST_STATE_REF__' window[propName] = Object.assign({}, window[propName], data) } render () { return this.state.result } } OptimizelyExperiment.propTypes = { projectId: PropTypes.string.isRequired, experimentId: PropTypes.string.isRequired, name: PropTypes.string.isRequired, experimentsMap: PropTypes.object.isRequired, pageName: PropTypes.string, customEvents: PropTypes.array, } export default OptimizelyExperiment