UNPKG

vpaid-html5-client

Version:
1,005 lines (850 loc) 25.4 kB
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ 'use strict'; var METHODS = [ 'handshakeVersion', 'initAd', 'startAd', 'stopAd', 'skipAd', // VPAID 2.0 new method 'resizeAd', 'pauseAd', 'resumeAd', 'expandAd', 'collapseAd', 'subscribe', 'unsubscribe' ]; var EVENTS = [ 'AdLoaded', 'AdStarted', 'AdStopped', 'AdSkipped', 'AdSkippableStateChange', // VPAID 2.0 new event 'AdSizeChange', // VPAID 2.0 new event 'AdLinearChange', 'AdDurationChange', // VPAID 2.0 new event 'AdExpandedChange', 'AdRemainingTimeChange', // [Deprecated in 2.0] but will be still fired for backwards compatibility 'AdVolumeChange', 'AdImpression', 'AdVideoStart', 'AdVideoFirstQuartile', 'AdVideoMidpoint', 'AdVideoThirdQuartile', 'AdVideoComplete', 'AdClickThru', 'AdInteraction', // VPAID 2.0 new event 'AdUserAcceptInvitation', 'AdUserMinimize', 'AdUserClose', 'AdPaused', 'AdPlaying', 'AdLog', 'AdError' ]; var GETTERS = [ 'getAdLinear', 'getAdWidth', // VPAID 2.0 new getter 'getAdHeight', // VPAID 2.0 new getter 'getAdExpanded', 'getAdSkippableState', // VPAID 2.0 new getter 'getAdRemainingTime', 'getAdDuration', // VPAID 2.0 new getter 'getAdVolume', 'getAdCompanions', // VPAID 2.0 new getter 'getAdIcons' // VPAID 2.0 new getter ]; var SETTERS = [ 'setAdVolume' ]; /** * This callback is displayed as global member. The callback use nodejs error-first callback style * @callback NodeStyleCallback * @param {string|null} * @param {undefined|object} */ /** * IVPAIDAdUnit * * @class * * @param {object} creative * @param {HTMLElement} el * @param {HTMLVideoElement} video */ function IVPAIDAdUnit(creative, el, video) {} /** * handshakeVersion * * @param {string} VPAIDVersion * @param {nodeStyleCallback} callback */ IVPAIDAdUnit.prototype.handshakeVersion = function (VPAIDVersion, callback) {}; /** * initAd * * @param {number} width * @param {number} height * @param {string} viewMode can be 'normal', 'thumbnail' or 'fullscreen' * @param {number} desiredBitrate indicates the desired bitrate in kbps * @param {object} [creativeData] used for additional initialization data * @param {object} [environmentVars] used for passing implementation-specific of js version * @param {NodeStyleCallback} callback */ IVPAIDAdUnit.prototype.initAd = function(width, height, viewMode, desiredBitrate, creativeData, environmentVars, callback) {}; /** * startAd * * @param {nodeStyleCallback} callback */ IVPAIDAdUnit.prototype.startAd = function(callback) {}; /** * stopAd * * @param {nodeStyleCallback} callback */ IVPAIDAdUnit.prototype.stopAd = function(callback) {}; /** * skipAd * * @param {nodeStyleCallback} callback */ IVPAIDAdUnit.prototype.skipAd = function(callback) {}; /** * resizeAd * * @param {nodeStyleCallback} callback */ IVPAIDAdUnit.prototype.resizeAd = function(width, height, viewMode, callback) {}; /** * pauseAd * * @param {nodeStyleCallback} callback */ IVPAIDAdUnit.prototype.pauseAd = function(callback) {}; /** * resumeAd * * @param {nodeStyleCallback} callback */ IVPAIDAdUnit.prototype.resumeAd = function(callback) {}; /** * expandAd * * @param {nodeStyleCallback} callback */ IVPAIDAdUnit.prototype.expandAd = function(callback) {}; /** * collapseAd * * @param {nodeStyleCallback} callback */ IVPAIDAdUnit.prototype.collapseAd = function(callback) {}; /** * subscribe * * @param {string} event * @param {nodeStyleCallback} handler * @param {object} context */ IVPAIDAdUnit.prototype.subscribe = function(event, handler, context) {}; /** * startAd * * @param {string} event * @param {function} handler */ IVPAIDAdUnit.prototype.unsubscribe = function(event, handler) {}; /** * getAdLinear * * @param {nodeStyleCallback} callback */ IVPAIDAdUnit.prototype.getAdLinear = function(callback) {}; /** * getAdWidth * * @param {nodeStyleCallback} callback */ IVPAIDAdUnit.prototype.getAdWidth = function(callback) {}; /** * getAdHeight * * @param {nodeStyleCallback} callback */ IVPAIDAdUnit.prototype.getAdHeight = function(callback) {}; /** * getAdExpanded * * @param {nodeStyleCallback} callback */ IVPAIDAdUnit.prototype.getAdExpanded = function(callback) {}; /** * getAdSkippableState * * @param {nodeStyleCallback} callback */ IVPAIDAdUnit.prototype.getAdSkippableState = function(callback) {}; /** * getAdRemainingTime * * @param {nodeStyleCallback} callback */ IVPAIDAdUnit.prototype.getAdRemainingTime = function(callback) {}; /** * getAdDuration * * @param {nodeStyleCallback} callback */ IVPAIDAdUnit.prototype.getAdDuration = function(callback) {}; /** * getAdVolume * * @param {nodeStyleCallback} callback */ IVPAIDAdUnit.prototype.getAdVolume = function(callback) {}; /** * getAdCompanions * * @param {nodeStyleCallback} callback */ IVPAIDAdUnit.prototype.getAdCompanions = function(callback) {}; /** * getAdIcons * * @param {nodeStyleCallback} callback */ IVPAIDAdUnit.prototype.getAdIcons = function(callback) {}; /** * setAdVolume * * @param {number} volume * @param {nodeStyleCallback} callback */ IVPAIDAdUnit.prototype.setAdVolume = function(volume, callback) {}; addStaticToInterface(IVPAIDAdUnit, 'METHODS', METHODS); addStaticToInterface(IVPAIDAdUnit, 'GETTERS', GETTERS); addStaticToInterface(IVPAIDAdUnit, 'SETTERS', SETTERS); addStaticToInterface(IVPAIDAdUnit, 'EVENTS', EVENTS); var VPAID1_METHODS = METHODS.filter(function(method) { return ['skipAd'].indexOf(method) === -1; }); addStaticToInterface(IVPAIDAdUnit, 'checkVPAIDInterface', function checkVPAIDInterface (creative) { var result = VPAID1_METHODS.every(function(key) { return typeof creative[key] === 'function'; }); return result; }); module.exports = IVPAIDAdUnit; function addStaticToInterface(Interface, name, value) { Object.defineProperty(Interface, name, { writable: false, configurable: false, value: value }); } },{}],2:[function(require,module,exports){ 'use strict'; var IVPAIDAdUnit = require('./IVPAIDAdUnit'); var Subscriber = require('./subscriber'); var checkVPAIDInterface = IVPAIDAdUnit.checkVPAIDInterface; var utils = require('./utils'); var METHODS = IVPAIDAdUnit.METHODS; var ERROR = 'AdError'; var AD_CLICK = 'AdClickThru'; var FILTERED_EVENTS = IVPAIDAdUnit.EVENTS.filter(function (event) { return event != AD_CLICK; }); /** * This callback is displayed as global member. The callback use nodejs error-first callback style * @callback NodeStyleCallback * @param {string|null} * @param {undefined|object} */ /** * VPAIDAdUnit * @class * * @param VPAIDCreative * @param {HTMLElement} [el] this will be used in initAd environmentVars.slot if defined * @param {HTMLVideoElement} [video] this will be used in initAd environmentVars.videoSlot if defined */ function VPAIDAdUnit(VPAIDCreative, el, video, iframe) { this._isValid = checkVPAIDInterface(VPAIDCreative); if (this._isValid) { this._creative = VPAIDCreative; this._el = el; this._videoEl = video; this._iframe = iframe; this._subscribers = new Subscriber(); $addEventsSubscribers.call(this); } } VPAIDAdUnit.prototype = Object.create(IVPAIDAdUnit.prototype); /** * isValidVPAIDAd will return if the VPAIDCreative passed in constructor is valid or not * * @return {boolean} */ VPAIDAdUnit.prototype.isValidVPAIDAd = function isValidVPAIDAd() { return this._isValid; }; IVPAIDAdUnit.METHODS.forEach(function(method) { //NOTE: this methods arguments order are implemented differently from the spec var ignores = [ 'subscribe', 'unsubscribe', 'initAd' ]; if (ignores.indexOf(method) !== -1) return; VPAIDAdUnit.prototype[method] = function () { var ariaty = IVPAIDAdUnit.prototype[method].length; // TODO avoid leaking arguments // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#32-leaking-arguments var args = Array.prototype.slice.call(arguments); var callback = (ariaty === args.length) ? args.pop() : undefined; setTimeout(function () { var result, error = null; try { result = this._creative[method].apply(this._creative, args); } catch(e) { error = e; } callOrTriggerEvent(callback, this._subscribers, error, result); }.bind(this), 0); }; }); /** * initAd concreate implementation * * @param {number} width * @param {number} height * @param {string} viewMode can be 'normal', 'thumbnail' or 'fullscreen' * @param {number} desiredBitrate indicates the desired bitrate in kbps * @param {object} [creativeData] used for additional initialization data * @param {object} [environmentVars] used for passing implementation-specific of js version, if el & video was used in constructor slot & videoSlot will be added to the object * @param {NodeStyleCallback} callback */ VPAIDAdUnit.prototype.initAd = function initAd(width, height, viewMode, desiredBitrate, creativeData, environmentVars, callback) { creativeData = creativeData || {}; environmentVars = utils.extend({ slot: this._el, videoSlot: this._videoEl }, environmentVars || {}); setTimeout(function () { var error; try { this._creative.initAd(width, height, viewMode, desiredBitrate, creativeData, environmentVars); } catch (e) { error = e; } callOrTriggerEvent(callback, this._subscribers, error); }.bind(this), 0); }; /** * subscribe * * @param {string} event * @param {nodeStyleCallback} handler * @param {object} context */ VPAIDAdUnit.prototype.subscribe = function subscribe(event, handler, context) { this._subscribers.subscribe(handler, event, context); }; /** * unsubscribe * * @param {string} event * @param {nodeStyleCallback} handler */ VPAIDAdUnit.prototype.unsubscribe = function unsubscribe(event, handler) { this._subscribers.unsubscribe(handler, event); }; //alias VPAIDAdUnit.prototype.on = VPAIDAdUnit.prototype.subscribe; VPAIDAdUnit.prototype.off = VPAIDAdUnit.prototype.unsubscribe; IVPAIDAdUnit.GETTERS.forEach(function(getter) { VPAIDAdUnit.prototype[getter] = function (callback) { setTimeout(function () { var result, error = null; try { result = this._creative[getter](); } catch(e) { error = e; } callOrTriggerEvent(callback, this._subscribers, error, result); }.bind(this), 0); }; }); /** * setAdVolume * * @param volume * @param {nodeStyleCallback} callback */ VPAIDAdUnit.prototype.setAdVolume = function setAdVolume(volume, callback) { setTimeout(function () { var result, error = null; try { this._creative.setAdVolume(volume); result = this._creative.getAdVolume(); } catch(e) { error = e; } if (!error) { error = utils.validate(result === volume, 'failed to apply volume: ' + volume); } callOrTriggerEvent(callback, this._subscribers, error, result); }.bind(this), 0); }; VPAIDAdUnit.prototype._destroy = function destroy() { this.stopAd(); this._subscribers.unsubscribeAll(); }; function $addEventsSubscribers() { // some ads implement // so they only handle one subscriber // to handle this we create our one FILTERED_EVENTS.forEach(function (event) { this._creative.subscribe($trigger.bind(this, event), event); }.bind(this)); // map the click event to be an object instead of depending of the order of the arguments // and to be consistent with the flash this._creative.subscribe($clickThruHook.bind(this), AD_CLICK); // because we are adding the element inside the iframe // the user is not able to click in the video if (this._videoEl) { var documentElement = this._iframe.contentDocument.documentElement; var videoEl = this._videoEl; documentElement.addEventListener('click', function(e) { if (e.target === documentElement) { videoEl.click(); } }); } } function $clickThruHook(url, id, playerHandles) { this._subscribers.triggerSync(AD_CLICK, {url: url, id: id, playerHandles: playerHandles}); } function $trigger(event) { // TODO avoid leaking arguments // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#32-leaking-arguments this._subscribers.trigger(event, Array.prototype.slice(arguments, 1)); } function callOrTriggerEvent(callback, subscribers, error, result) { if (callback) { callback(error, result); } else if (error) { subscribers.trigger(ERROR, error); } } module.exports = VPAIDAdUnit; },{"./IVPAIDAdUnit":1,"./subscriber":4,"./utils":5}],3:[function(require,module,exports){ 'use strict'; var utils = require('./utils'); var unique = utils.unique('vpaidIframe'); var VPAIDAdUnit = require('./VPAIDAdUnit'); var defaultTemplate = '<!DOCTYPE html>' + '<html lang="en">' + '<head><meta charset="UTF-8"></head>' + '<body style="margin:0;padding:0"><div class="ad-element"></div>' + '<script type="text/javascript" src="{{iframeURL_JS}}"></script>' + '<script type="text/javascript">' + 'window.parent.postMessage(\'{"event": "ready", "id": "{{iframeID}}"}\', \'{{origin}}\');' + '</script>' + '</body>' + '</html>'; var AD_STOPPED = 'AdStopped'; /** * This callback is displayed as global member. The callback use nodejs error-first callback style * @callback NodeStyleCallback * @param {string|null} * @param {undefined|object} */ /** * VPAIDHTML5Client * @class * * @param {HTMLElement} el that will contain the iframe to load adUnit and a el to add to adUnit slot * @param {HTMLVideoElement} video default video element to be used by adUnit * @param {object} [templateConfig] template: html template to be used instead of the default, extraOptions: to be used when rendering the template * @param {object} [vpaidOptions] timeout: when loading adUnit */ function VPAIDHTML5Client(el, video, templateConfig, vpaidOptions) { templateConfig = templateConfig || {}; this._id = unique(); this._destroyed = false; this._frameContainer = utils.createElementInEl(el, 'div'); this._videoEl = video; this._vpaidOptions = vpaidOptions || {timeout: 10000}; this._templateConfig = { template: templateConfig.template || defaultTemplate, extraOptions: templateConfig.extraOptions || {} }; } /** * destroy * */ VPAIDHTML5Client.prototype.destroy = function destroy() { if (this._destroyed) { return; } this._destroyed = true; $unloadPreviousAdUnit.call(this); }; /** * isDestroyed * * @return {boolean} */ VPAIDHTML5Client.prototype.isDestroyed = function isDestroyed() { return this._destroyed; }; /** * loadAdUnit * * @param {string} adURL url of the js of the adUnit * @param {nodeStyleCallback} callback */ VPAIDHTML5Client.prototype.loadAdUnit = function loadAdUnit(adURL, callback) { $throwIfDestroyed.call(this); $unloadPreviousAdUnit.call(this); var that = this; var frame = utils.createIframeWithContent( this._frameContainer, this._templateConfig.template, utils.extend({ iframeURL_JS: adURL, iframeID: this.getID(), origin: getOrigin() }, this._templateConfig.extraOptions) ); this._frame = frame; this._onLoad = utils.callbackTimeout( this._vpaidOptions.timeout, onLoad.bind(this), onTimeout.bind(this) ); window.addEventListener('message', this._onLoad); function onLoad (e) { /*jshint validthis: false */ //don't clear timeout if (e.origin !== getOrigin()) return; var result = JSON.parse(e.data); //don't clear timeout if (result.id !== that.getID()) return; var adUnit, error, createAd; if (!that._frame.contentWindow) { error = 'the iframe is not anymore in the DOM tree'; } else { createAd = that._frame.contentWindow.getVPAIDAd; error = utils.validate(typeof createAd === 'function', 'the ad didn\'t return a function to create an ad'); } if (!error) { var adEl = that._frame.contentWindow.document.querySelector('.ad-element'); adUnit = new VPAIDAdUnit(createAd(), adEl, that._videoEl, that._frame); adUnit.subscribe(AD_STOPPED, $adDestroyed.bind(that)); error = utils.validate(adUnit.isValidVPAIDAd(), 'the add is not fully complaint with VPAID specification'); } that._adUnit = adUnit; $destroyLoadListener.call(that); callback(error, error ? null : adUnit); //clear timeout return true; } function onTimeout() { callback('timeout', null); } }; /** * unloadAdUnit * */ VPAIDHTML5Client.prototype.unloadAdUnit = function unloadAdUnit() { $unloadPreviousAdUnit.call(this); }; /** * getID will return the unique id * * @return {string} */ VPAIDHTML5Client.prototype.getID = function () { return this._id; }; /** * $removeEl * * @param {string} key */ function $removeEl(key) { var el = this[key]; if (el) { el.remove(); delete this[key]; } } function $adDestroyed() { $removeAdElements.call(this); delete this._adUnit; } function $unloadPreviousAdUnit() { $removeAdElements.call(this); $destroyAdUnit.call(this); } function $removeAdElements() { $removeEl.call(this, '_frame'); $destroyLoadListener.call(this); } /** * $destroyLoadListener * */ function $destroyLoadListener() { if (this._onLoad) { window.removeEventListener('message', this._onLoad); utils.clearCallbackTimeout(this._onLoad); delete this._onLoad; } } function $destroyAdUnit() { if (this._adUnit) { this._adUnit.stopAd(); delete this._adUnit; } } /** * $throwIfDestroyed * */ function $throwIfDestroyed() { if (this._destroyed) { throw new Error ('VPAIDHTML5Client already destroyed!'); } } function getOrigin() { if( window.location.origin ) { return window.location.origin; } else { return window.location.protocol + "//" + window.location.hostname + (window.location.port ? ':' + window.location.port: ''); } } module.exports = VPAIDHTML5Client; window.VPAIDHTML5Client = VPAIDHTML5Client; },{"./VPAIDAdUnit":2,"./utils":5}],4:[function(require,module,exports){ 'use strict'; function Subscriber() { this._subscribers = {}; } Subscriber.prototype.subscribe = function subscribe(handler, eventName, context) { if (!this.isHandlerAttached(handler, eventName)) { this.get(eventName).push({handler: handler, context: context, eventName: eventName}); } }; Subscriber.prototype.unsubscribe = function unsubscribe(handler, eventName) { this._subscribers[eventName] = this.get(eventName).filter(function (subscriber) { return handler !== subscriber.handler; }); }; Subscriber.prototype.unsubscribeAll = function unsubscribeAll() { this._subscribers = {}; }; Subscriber.prototype.trigger = function(eventName, data) { var that = this; var subscribers = this.get(eventName) .concat(this.get('*')); subscribers.forEach(function (subscriber) { setTimeout(function () { if (that.isHandlerAttached(subscriber.handler, subscriber.eventName)) { subscriber.handler.call(subscriber.context, data); } }, 0); }); }; Subscriber.prototype.triggerSync = function(eventName, data) { var subscribers = this.get(eventName) .concat(this.get('*')); subscribers.forEach(function (subscriber) { subscriber.handler.call(subscriber.context, data); }); }; Subscriber.prototype.get = function get(eventName) { if (!this._subscribers[eventName]) { this._subscribers[eventName] = []; } return this._subscribers[eventName]; }; Subscriber.prototype.isHandlerAttached = function isHandlerAttached(handler, eventName) { return this.get(eventName).some(function(subscriber) { return handler === subscriber.handler; }) }; module.exports = Subscriber; },{}],5:[function(require,module,exports){ 'use strict'; /** * noop a empty function */ function noop() {} /** * validate if is not validate will return an Error with the message * * @param {boolean} isValid * @param {string} message */ function validate(isValid, message) { return isValid ? null : new Error(message); } var timeouts = {}; /** * clearCallbackTimeout * * @param {function} func handler to remove */ function clearCallbackTimeout(func) { var timeout = timeouts[func]; if (timeout) { clearTimeout(timeout); delete timeouts[func]; } } /** * callbackTimeout if the onSuccess is not called and returns true in the timelimit then onTimeout will be called * * @param {number} timer * @param {function} onSuccess * @param {function} onTimeout */ function callbackTimeout(timer, onSuccess, onTimeout) { var callback, timeout; timeout = setTimeout(function () { onSuccess = noop; delete timeout[callback]; onTimeout(); }, timer); callback = function () { // TODO avoid leaking arguments // https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#32-leaking-arguments if (onSuccess.apply(this, arguments)) { clearCallbackTimeout(callback); } }; timeouts[callback] = timeout; return callback; } /** * createElementInEl * * @param {HTMLElement} parent * @param {string} tagName * @param {string} id */ function createElementInEl(parent, tagName, id) { var nEl = document.createElement(tagName); if (id) nEl.id = id; parent.appendChild(nEl); return nEl; } /** * createIframeWithContent * * @param {HTMLElement} parent * @param {string} template simple template using {{var}} * @param {object} data */ function createIframeWithContent(parent, template, data) { var iframe = createIframe(parent); if (!setIframeContent(iframe, simpleTemplate(template, data))) return; return iframe; } /** * createIframe * * @param {HTMLElement} parent * @param {string} url */ function createIframe(parent, url) { var nEl = document.createElement('iframe'); nEl.src = url || 'about:blank'; nEl.marginWidth = '0'; nEl.marginHeight = '0'; nEl.frameBorder = '0'; nEl.width = '100%'; nEl.height = '100%'; nEl.style.position = 'absolute'; nEl.style.left = '0'; nEl.style.top = '0'; nEl.style.margin = '0px'; nEl.style.padding = '0px'; nEl.style.border = 'none'; nEl.setAttribute('SCROLLING','NO'); parent.innerHTML = ''; parent.appendChild(nEl); return nEl; } /** * simpleTemplate * * @param {string} template * @param {object} data */ function simpleTemplate(template, data) { Object.keys(data).forEach(function (key) { var value = (typeof value === 'object') ? JSON.stringify(data[key]) : data[key]; template = template.replace(new RegExp('{{' + key + '}}', 'g'), value); }); return template; } /** * setIframeContent * * @param {HTMLIframeElement} iframeEl * @param content */ function setIframeContent(iframeEl, content) { var iframeDoc = iframeEl.contentWindow && iframeEl.contentWindow.document; if (!iframeDoc) return false; iframeDoc.write(content); return true; } /** * extend object with keys from another object * * @param {object} toExtend * @param {object} fromSource */ function extend(toExtend, fromSource) { Object.keys(fromSource).forEach(function(key) { toExtend[key] = fromSource[key]; }); return toExtend; } /** * unique will create a unique string everytime is called, sequentially and prefixed * * @param {string} prefix */ function unique(prefix) { var count = -1; return function () { return prefix + '_' + (++count); }; } module.exports = { noop: noop, validate: validate, clearCallbackTimeout: clearCallbackTimeout, callbackTimeout: callbackTimeout, createElementInEl: createElementInEl, createIframeWithContent: createIframeWithContent, createIframe: createIframe, simpleTemplate: simpleTemplate, setIframeContent: setIframeContent, extend: extend, unique: unique }; },{}]},{},[3]) //# sourceMappingURL=VPAIDHTML5Client.js.map