react-native-vast-client
Version:
JavaScript VAST Client
1,418 lines (1,208 loc) • 104 kB
JavaScript
var VAST = (function (exports) {
'use strict';
var classCallCheck = function (instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
};
var createClass = function () {
function defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
return function (Constructor, protoProps, staticProps) {
if (protoProps) defineProperties(Constructor.prototype, protoProps);
if (staticProps) defineProperties(Constructor, staticProps);
return Constructor;
};
}();
var inherits = function (subClass, superClass) {
if (typeof superClass !== "function" && superClass !== null) {
throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
}
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass,
enumerable: false,
writable: true,
configurable: true
}
});
if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
};
var possibleConstructorReturn = function (self, call) {
if (!self) {
throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
}
return call && (typeof call === "object" || typeof call === "function") ? call : self;
};
var Ad = function Ad() {
classCallCheck(this, Ad);
this.id = null;
this.sequence = null;
this.system = null;
this.title = null;
this.description = null;
this.advertiser = null;
this.pricing = null;
this.survey = null;
this.errorURLTemplates = [];
this.impressionURLTemplates = [];
this.creatives = [];
this.extensions = [];
};
var AdExtension = function AdExtension() {
classCallCheck(this, AdExtension);
this.attributes = {};
this.children = [];
};
var AdExtensionChild = function AdExtensionChild() {
classCallCheck(this, AdExtensionChild);
this.name = null;
this.value = null;
this.attributes = {};
};
var CompanionAd = function CompanionAd() {
classCallCheck(this, CompanionAd);
this.id = null;
this.width = 0;
this.height = 0;
this.type = null;
this.staticResource = null;
this.htmlResource = null;
this.iframeResource = null;
this.altText = null;
this.companionClickThroughURLTemplate = null;
this.companionClickTrackingURLTemplates = [];
this.trackingEvents = {};
};
var Creative = function Creative() {
var creativeAttributes = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
classCallCheck(this, Creative);
this.id = creativeAttributes.id || null;
this.adId = creativeAttributes.adId || null;
this.sequence = creativeAttributes.sequence || null;
this.apiFramework = creativeAttributes.apiFramework || null;
this.trackingEvents = {};
};
var CreativeCompanion = function (_Creative) {
inherits(CreativeCompanion, _Creative);
function CreativeCompanion() {
var creativeAttributes = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
classCallCheck(this, CreativeCompanion);
var _this = possibleConstructorReturn(this, (CreativeCompanion.__proto__ || Object.getPrototypeOf(CreativeCompanion)).call(this, creativeAttributes));
_this.type = 'companion';
_this.variations = [];
return _this;
}
return CreativeCompanion;
}(Creative);
function track(URLTemplates, variables, options) {
var URLs = resolveURLTemplates(URLTemplates, variables, options);
URLs.forEach(function (URL) {
if (typeof window !== 'undefined' && window !== null) {
var i = new Image();
i.src = URL;
}
});
}
/**
* Replace the provided URLTemplates with the given values
*
* @param {Array} URLTemplates - An array of tracking url templates.
* @param {Object} [variables={}] - An optional Object of parameters to be used in the tracking calls.
* @param {Object} [options={}] - An optional Object of options to be used in the tracking calls.
*/
function resolveURLTemplates(URLTemplates) {
var variables = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
var URLs = [];
// Encode String variables, when given
if (variables['ASSETURI']) {
variables['ASSETURI'] = encodeURIComponentRFC3986(variables['ASSETURI']);
}
if (variables['CONTENTPLAYHEAD']) {
variables['CONTENTPLAYHEAD'] = encodeURIComponentRFC3986(variables['CONTENTPLAYHEAD']);
}
// Set default value for invalid ERRORCODE
if (variables['ERRORCODE'] && !options.isCustomCode && !/^[0-9]{3}$/.test(variables['ERRORCODE'])) {
variables['ERRORCODE'] = 900;
}
// Calc random/time based macros
variables['CACHEBUSTING'] = leftpad(Math.round(Math.random() * 1.0e8).toString());
variables['TIMESTAMP'] = encodeURIComponentRFC3986(new Date().toISOString());
// RANDOM/random is not defined in VAST 3/4 as a valid macro tho it's used by some adServer (Auditude)
variables['RANDOM'] = variables['random'] = variables['CACHEBUSTING'];
for (var URLTemplateKey in URLTemplates) {
var resolveURL = URLTemplates[URLTemplateKey];
if (typeof resolveURL !== 'string') {
continue;
}
for (var key in variables) {
var value = variables[key];
var macro1 = '[' + key + ']';
var macro2 = '%%' + key + '%%';
resolveURL = resolveURL.replace(macro1, value);
resolveURL = resolveURL.replace(macro2, value);
}
URLs.push(resolveURL);
}
return URLs;
}
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent
function encodeURIComponentRFC3986(str) {
return encodeURIComponent(str).replace(/[!'()*]/g, function (c) {
return '%' + c.charCodeAt(0).toString(16);
});
}
function leftpad(str) {
if (str.length < 8) {
return range(0, 8 - str.length, false).map(function () {
return '0';
}).join('') + str;
}
return str;
}
function range(left, right, inclusive) {
var result = [];
var ascending = left < right;
var end = !inclusive ? right : ascending ? right + 1 : right - 1;
for (var i = left; ascending ? i < end : i > end; ascending ? i++ : i--) {
result.push(i);
}
return result;
}
function isNumeric(n) {
return !isNaN(parseFloat(n)) && isFinite(n);
}
function flatten(arr) {
return arr.reduce(function (flat, toFlatten) {
return flat.concat(Array.isArray(toFlatten) ? flatten(toFlatten) : toFlatten);
}, []);
}
var util = {
track: track,
resolveURLTemplates: resolveURLTemplates,
encodeURIComponentRFC3986: encodeURIComponentRFC3986,
leftpad: leftpad,
range: range,
isNumeric: isNumeric,
flatten: flatten
};
/**
* This module provides support methods to the parsing classes.
*/
/**
* Returns the first element of the given node which nodeName matches the given name.
* @param {Object} node - The node to use to find a match.
* @param {String} name - The name to look for.
* @return {Object}
*/
function childByName(node, name) {
var childNodes = node.childNodes;
for (var childKey in childNodes) {
var child = childNodes[childKey];
if (child.nodeName === name) {
return child;
}
}
}
/**
* Returns all the elements of the given node which nodeName match the given name.
* @param {any} node - The node to use to find the matches.
* @param {any} name - The name to look for.
* @return {Array}
*/
function childrenByName(node, name) {
var children = [];
var childNodes = node.childNodes;
for (var childKey in childNodes) {
var child = childNodes[childKey];
if (child.nodeName === name) {
children.push(child);
}
}
return children;
}
/**
* Converts relative vastAdTagUri.
* @param {String} vastAdTagUrl - The url to resolve.
* @param {String} originalUrl - The original url.
* @return {String}
*/
function resolveVastAdTagURI(vastAdTagUrl, originalUrl) {
if (!originalUrl) {
return vastAdTagUrl;
}
if (vastAdTagUrl.indexOf('//') === 0) {
var _location = location,
protocol = _location.protocol;
return '' + protocol + vastAdTagUrl;
}
if (vastAdTagUrl.indexOf('://') === -1) {
// Resolve relative URLs (mainly for unit testing)
var baseURL = originalUrl.slice(0, originalUrl.lastIndexOf('/'));
return baseURL + '/' + vastAdTagUrl;
}
return vastAdTagUrl;
}
/**
* Converts a boolean string into a Boolean.
* @param {String} booleanString - The boolean string to convert.
* @return {Boolean}
*/
function parseBoolean(booleanString) {
return ['true', 'TRUE', '1'].indexOf(booleanString) !== -1;
}
/**
* Parses a node text (for legacy support).
* @param {Object} node - The node to parse the text from.
* @return {String}
*/
function parseNodeText(node) {
return node && (node.textContent || node.text || '').trim();
}
/**
* Copies an attribute from a node to another.
* @param {String} attributeName - The name of the attribute to clone.
* @param {Object} nodeSource - The source node to copy the attribute from.
* @param {Object} nodeDestination - The destination node to copy the attribute at.
*/
function copyNodeAttribute(attributeName, nodeSource, nodeDestination) {
var attributeValue = nodeSource.getAttribute(attributeName);
if (attributeValue) {
nodeDestination.setAttribute(attributeName, attributeValue);
}
}
/**
* Parses a String duration into a Number.
* @param {String} durationString - The dureation represented as a string.
* @return {Number}
*/
function parseDuration(durationString) {
if (durationString === null || typeof durationString === 'undefined') {
return -1;
}
// Some VAST doesn't have an HH:MM:SS duration format but instead jus the number of seconds
if (util.isNumeric(durationString)) {
return parseInt(durationString);
}
var durationComponents = durationString.split(':');
if (durationComponents.length !== 3) {
return -1;
}
var secondsAndMS = durationComponents[2].split('.');
var seconds = parseInt(secondsAndMS[0]);
if (secondsAndMS.length === 2) {
seconds += parseFloat('0.' + secondsAndMS[1]);
}
var minutes = parseInt(durationComponents[1] * 60);
var hours = parseInt(durationComponents[0] * 60 * 60);
if (isNaN(hours) || isNaN(minutes) || isNaN(seconds) || minutes > 60 * 60 || seconds > 60) {
return -1;
}
return hours + minutes + seconds;
}
/**
* Splits an Array of ads into an Array of Arrays of ads.
* Each subarray contains either one ad or multiple ads (an AdPod)
* @param {Array} ads - An Array of ads to split
* @return {Array}
*/
function splitVAST(ads) {
var splittedVAST = [];
var lastAdPod = null;
ads.forEach(function (ad, i) {
if (ad.sequence) {
ad.sequence = parseInt(ad.sequence, 10);
}
// The current Ad may be the next Ad of an AdPod
if (ad.sequence > 1) {
var lastAd = ads[i - 1];
// check if the current Ad is exactly the next one in the AdPod
if (lastAd && lastAd.sequence === ad.sequence - 1) {
lastAdPod && lastAdPod.push(ad);
return;
}
// If the ad had a sequence attribute but it was not part of a correctly formed
// AdPod, let's remove the sequence attribute
delete ad.sequence;
}
lastAdPod = [ad];
splittedVAST.push(lastAdPod);
});
return splittedVAST;
}
/**
* Merges the data between an unwrapped ad and his wrapper.
* @param {Ad} unwrappedAd - The 'unwrapped' Ad.
* @param {Ad} wrapper - The wrapper Ad.
* @return {void}
*/
function mergeWrapperAdData(unwrappedAd, wrapper) {
unwrappedAd.errorURLTemplates = wrapper.errorURLTemplates.concat(unwrappedAd.errorURLTemplates);
unwrappedAd.impressionURLTemplates = wrapper.impressionURLTemplates.concat(unwrappedAd.impressionURLTemplates);
unwrappedAd.extensions = wrapper.extensions.concat(unwrappedAd.extensions);
unwrappedAd.creatives.forEach(function (creative) {
if (wrapper.trackingEvents && wrapper.trackingEvents[creative.type]) {
for (var eventName in wrapper.trackingEvents[creative.type]) {
var urls = wrapper.trackingEvents[creative.type][eventName];
if (!Array.isArray(creative.trackingEvents[eventName])) {
creative.trackingEvents[eventName] = [];
}
creative.trackingEvents[eventName] = creative.trackingEvents[eventName].concat(urls);
}
}
});
if (wrapper.videoClickTrackingURLTemplates && wrapper.videoClickTrackingURLTemplates.length) {
unwrappedAd.creatives.forEach(function (creative) {
if (creative.type === 'linear') {
creative.videoClickTrackingURLTemplates = creative.videoClickTrackingURLTemplates.concat(wrapper.videoClickTrackingURLTemplates);
}
});
}
if (wrapper.videoCustomClickURLTemplates && wrapper.videoCustomClickURLTemplates.length) {
unwrappedAd.creatives.forEach(function (creative) {
if (creative.type === 'linear') {
creative.videoCustomClickURLTemplates = creative.videoCustomClickURLTemplates.concat(wrapper.videoCustomClickURLTemplates);
}
});
}
// VAST 2.0 support - Use Wrapper/linear/clickThrough when Inline/Linear/clickThrough is null
if (wrapper.videoClickThroughURLTemplate) {
unwrappedAd.creatives.forEach(function (creative) {
if (creative.type === 'linear' && (creative.videoClickThroughURLTemplate === null || typeof creative.videoClickThroughURLTemplate === 'undefined')) {
creative.videoClickThroughURLTemplate = wrapper.videoClickThroughURLTemplate;
}
});
}
}
var parserUtils = {
childByName: childByName,
childrenByName: childrenByName,
resolveVastAdTagURI: resolveVastAdTagURI,
parseBoolean: parseBoolean,
parseNodeText: parseNodeText,
copyNodeAttribute: copyNodeAttribute,
parseDuration: parseDuration,
splitVAST: splitVAST,
mergeWrapperAdData: mergeWrapperAdData
};
/**
* This module provides methods to parse a VAST CompanionAd Element.
*/
/**
* Parses a CompanionAd.
* @param {Object} creativeElement - The VAST CompanionAd element to parse.
* @param {Object} creativeAttributes - The attributes of the CompanionAd (optional).
* @return {CreativeCompanion}
*/
function parseCreativeCompanion(creativeElement, creativeAttributes) {
var creative = new CreativeCompanion(creativeAttributes);
parserUtils.childrenByName(creativeElement, 'Companion').forEach(function (companionResource) {
var companionAd = new CompanionAd();
companionAd.id = companionResource.getAttribute('id') || null;
companionAd.width = companionResource.getAttribute('width');
companionAd.height = companionResource.getAttribute('height');
companionAd.companionClickTrackingURLTemplates = [];
parserUtils.childrenByName(companionResource, 'HTMLResource').forEach(function (htmlElement) {
companionAd.type = htmlElement.getAttribute('creativeType') || 'text/html';
companionAd.htmlResource = parserUtils.parseNodeText(htmlElement);
});
parserUtils.childrenByName(companionResource, 'IFrameResource').forEach(function (iframeElement) {
companionAd.type = iframeElement.getAttribute('creativeType') || 0;
companionAd.iframeResource = parserUtils.parseNodeText(iframeElement);
});
parserUtils.childrenByName(companionResource, 'StaticResource').forEach(function (staticElement) {
companionAd.type = staticElement.getAttribute('creativeType') || 0;
parserUtils.childrenByName(companionResource, 'AltText').forEach(function (child) {
companionAd.altText = parserUtils.parseNodeText(child);
});
companionAd.staticResource = parserUtils.parseNodeText(staticElement);
});
parserUtils.childrenByName(companionResource, 'TrackingEvents').forEach(function (trackingEventsElement) {
parserUtils.childrenByName(trackingEventsElement, 'Tracking').forEach(function (trackingElement) {
var eventName = trackingElement.getAttribute('event');
var trackingURLTemplate = parserUtils.parseNodeText(trackingElement);
if (eventName && trackingURLTemplate) {
if (!Array.isArray(companionAd.trackingEvents[eventName])) {
companionAd.trackingEvents[eventName] = [];
}
companionAd.trackingEvents[eventName].push(trackingURLTemplate);
}
});
});
parserUtils.childrenByName(companionResource, 'CompanionClickTracking').forEach(function (clickTrackingElement) {
companionAd.companionClickTrackingURLTemplates.push(parserUtils.parseNodeText(clickTrackingElement));
});
companionAd.companionClickThroughURLTemplate = parserUtils.parseNodeText(parserUtils.childByName(companionResource, 'CompanionClickThrough'));
companionAd.companionClickTrackingURLTemplate = parserUtils.parseNodeText(parserUtils.childByName(companionResource, 'CompanionClickTracking'));
creative.variations.push(companionAd);
});
return creative;
}
var CreativeLinear = function (_Creative) {
inherits(CreativeLinear, _Creative);
function CreativeLinear() {
var creativeAttributes = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
classCallCheck(this, CreativeLinear);
var _this = possibleConstructorReturn(this, (CreativeLinear.__proto__ || Object.getPrototypeOf(CreativeLinear)).call(this, creativeAttributes));
_this.type = 'linear';
_this.duration = 0;
_this.skipDelay = null;
_this.mediaFiles = [];
_this.videoClickThroughURLTemplate = null;
_this.videoClickTrackingURLTemplates = [];
_this.videoCustomClickURLTemplates = [];
_this.adParameters = null;
_this.icons = [];
return _this;
}
return CreativeLinear;
}(Creative);
var Icon = function Icon() {
classCallCheck(this, Icon);
this.program = null;
this.height = 0;
this.width = 0;
this.xPosition = 0;
this.yPosition = 0;
this.apiFramework = null;
this.offset = null;
this.duration = 0;
this.type = null;
this.staticResource = null;
this.htmlResource = null;
this.iframeResource = null;
this.iconClickThroughURLTemplate = null;
this.iconClickTrackingURLTemplates = [];
this.iconViewTrackingURLTemplate = null;
};
var MediaFile = function MediaFile() {
classCallCheck(this, MediaFile);
this.id = null;
this.fileURL = null;
this.deliveryType = 'progressive';
this.mimeType = null;
this.codec = null;
this.bitrate = 0;
this.minBitrate = 0;
this.maxBitrate = 0;
this.width = 0;
this.height = 0;
this.apiFramework = null;
this.scalable = null;
this.maintainAspectRatio = null;
};
/**
* This module provides methods to parse a VAST Linear Element.
*/
/**
* Parses a Linear element.
* @param {Object} creativeElement - The VAST Linear element to parse.
* @param {any} creativeAttributes - The attributes of the Linear (optional).
* @return {CreativeLinear}
*/
function parseCreativeLinear(creativeElement, creativeAttributes) {
var offset = void 0;
var creative = new CreativeLinear(creativeAttributes);
creative.duration = parserUtils.parseDuration(parserUtils.parseNodeText(parserUtils.childByName(creativeElement, 'Duration')));
var skipOffset = creativeElement.getAttribute('skipoffset');
if (typeof skipOffset === 'undefined' || skipOffset === null) {
creative.skipDelay = null;
} else if (skipOffset.charAt(skipOffset.length - 1) === '%' && creative.duration !== -1) {
var percent = parseInt(skipOffset, 10);
creative.skipDelay = creative.duration * (percent / 100);
} else {
creative.skipDelay = parserUtils.parseDuration(skipOffset);
}
var videoClicksElement = parserUtils.childByName(creativeElement, 'VideoClicks');
if (videoClicksElement) {
creative.videoClickThroughURLTemplate = parserUtils.parseNodeText(parserUtils.childByName(videoClicksElement, 'ClickThrough'));
parserUtils.childrenByName(videoClicksElement, 'ClickTracking').forEach(function (clickTrackingElement) {
creative.videoClickTrackingURLTemplates.push(parserUtils.parseNodeText(clickTrackingElement));
});
parserUtils.childrenByName(videoClicksElement, 'CustomClick').forEach(function (customClickElement) {
creative.videoCustomClickURLTemplates.push(parserUtils.parseNodeText(customClickElement));
});
}
var adParamsElement = parserUtils.childByName(creativeElement, 'AdParameters');
if (adParamsElement) {
creative.adParameters = parserUtils.parseNodeText(adParamsElement);
}
parserUtils.childrenByName(creativeElement, 'TrackingEvents').forEach(function (trackingEventsElement) {
parserUtils.childrenByName(trackingEventsElement, 'Tracking').forEach(function (trackingElement) {
var eventName = trackingElement.getAttribute('event');
var trackingURLTemplate = parserUtils.parseNodeText(trackingElement);
if (eventName && trackingURLTemplate) {
if (eventName === 'progress') {
offset = trackingElement.getAttribute('offset');
if (!offset) {
return;
}
if (offset.charAt(offset.length - 1) === '%') {
eventName = 'progress-' + offset;
} else {
eventName = 'progress-' + Math.round(parserUtils.parseDuration(offset));
}
}
if (!Array.isArray(creative.trackingEvents[eventName])) {
creative.trackingEvents[eventName] = [];
}
creative.trackingEvents[eventName].push(trackingURLTemplate);
}
});
});
parserUtils.childrenByName(creativeElement, 'MediaFiles').forEach(function (mediaFilesElement) {
parserUtils.childrenByName(mediaFilesElement, 'MediaFile').forEach(function (mediaFileElement) {
var mediaFile = new MediaFile();
mediaFile.id = mediaFileElement.getAttribute('id');
mediaFile.fileURL = parserUtils.parseNodeText(mediaFileElement);
mediaFile.deliveryType = mediaFileElement.getAttribute('delivery');
mediaFile.codec = mediaFileElement.getAttribute('codec');
mediaFile.mimeType = mediaFileElement.getAttribute('type');
mediaFile.apiFramework = mediaFileElement.getAttribute('apiFramework');
mediaFile.bitrate = parseInt(mediaFileElement.getAttribute('bitrate') || 0);
mediaFile.minBitrate = parseInt(mediaFileElement.getAttribute('minBitrate') || 0);
mediaFile.maxBitrate = parseInt(mediaFileElement.getAttribute('maxBitrate') || 0);
mediaFile.width = parseInt(mediaFileElement.getAttribute('width') || 0);
mediaFile.height = parseInt(mediaFileElement.getAttribute('height') || 0);
var scalable = mediaFileElement.getAttribute('scalable');
if (scalable && typeof scalable === 'string') {
scalable = scalable.toLowerCase();
if (scalable === 'true') {
mediaFile.scalable = true;
} else if (scalable === 'false') {
mediaFile.scalable = false;
}
}
var maintainAspectRatio = mediaFileElement.getAttribute('maintainAspectRatio');
if (maintainAspectRatio && typeof maintainAspectRatio === 'string') {
maintainAspectRatio = maintainAspectRatio.toLowerCase();
if (maintainAspectRatio === 'true') {
mediaFile.maintainAspectRatio = true;
} else if (maintainAspectRatio === 'false') {
mediaFile.maintainAspectRatio = false;
}
}
creative.mediaFiles.push(mediaFile);
});
});
var iconsElement = parserUtils.childByName(creativeElement, 'Icons');
if (iconsElement) {
parserUtils.childrenByName(iconsElement, 'Icon').forEach(function (iconElement) {
var icon = new Icon();
icon.program = iconElement.getAttribute('program');
icon.height = parseInt(iconElement.getAttribute('height') || 0);
icon.width = parseInt(iconElement.getAttribute('width') || 0);
icon.xPosition = parseXPosition(iconElement.getAttribute('xPosition'));
icon.yPosition = parseYPosition(iconElement.getAttribute('yPosition'));
icon.apiFramework = iconElement.getAttribute('apiFramework');
icon.offset = parserUtils.parseDuration(iconElement.getAttribute('offset'));
icon.duration = parserUtils.parseDuration(iconElement.getAttribute('duration'));
parserUtils.childrenByName(iconElement, 'HTMLResource').forEach(function (htmlElement) {
icon.type = htmlElement.getAttribute('creativeType') || 'text/html';
icon.htmlResource = parserUtils.parseNodeText(htmlElement);
});
parserUtils.childrenByName(iconElement, 'IFrameResource').forEach(function (iframeElement) {
icon.type = iframeElement.getAttribute('creativeType') || 0;
icon.iframeResource = parserUtils.parseNodeText(iframeElement);
});
parserUtils.childrenByName(iconElement, 'StaticResource').forEach(function (staticElement) {
icon.type = staticElement.getAttribute('creativeType') || 0;
icon.staticResource = parserUtils.parseNodeText(staticElement);
});
var iconClicksElement = parserUtils.childByName(iconElement, 'IconClicks');
if (iconClicksElement) {
icon.iconClickThroughURLTemplate = parserUtils.parseNodeText(parserUtils.childByName(iconClicksElement, 'IconClickThrough'));
parserUtils.childrenByName(iconClicksElement, 'IconClickTracking').forEach(function (iconClickTrackingElement) {
icon.iconClickTrackingURLTemplates.push(parserUtils.parseNodeText(iconClickTrackingElement));
});
}
icon.iconViewTrackingURLTemplate = parserUtils.parseNodeText(parserUtils.childByName(iconElement, 'IconViewTracking'));
creative.icons.push(icon);
});
}
return creative;
}
/**
* Parses an horizontal position into a String ('left' or 'right') or into a Number.
* @param {String} xPosition - The x position to parse.
* @return {String|Number}
*/
function parseXPosition(xPosition) {
if (['left', 'right'].indexOf(xPosition) !== -1) {
return xPosition;
}
return parseInt(xPosition || 0);
}
/**
* Parses an vertical position into a String ('top' or 'bottom') or into a Number.
* @param {String} yPosition - The x position to parse.
* @return {String|Number}
*/
function parseYPosition(yPosition) {
if (['top', 'bottom'].indexOf(yPosition) !== -1) {
return yPosition;
}
return parseInt(yPosition || 0);
}
var CreativeNonLinear = function (_Creative) {
inherits(CreativeNonLinear, _Creative);
function CreativeNonLinear() {
var creativeAttributes = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
classCallCheck(this, CreativeNonLinear);
var _this = possibleConstructorReturn(this, (CreativeNonLinear.__proto__ || Object.getPrototypeOf(CreativeNonLinear)).call(this, creativeAttributes));
_this.type = 'nonlinear';
_this.variations = [];
return _this;
}
return CreativeNonLinear;
}(Creative);
var NonLinearAd = function NonLinearAd() {
classCallCheck(this, NonLinearAd);
this.id = null;
this.width = 0;
this.height = 0;
this.expandedWidth = 0;
this.expandedHeight = 0;
this.scalable = true;
this.maintainAspectRatio = true;
this.minSuggestedDuration = 0;
this.apiFramework = 'static';
this.type = null;
this.staticResource = null;
this.htmlResource = null;
this.iframeResource = null;
this.nonlinearClickThroughURLTemplate = null;
this.nonlinearClickTrackingURLTemplates = [];
this.adParameters = null;
};
/**
* This module provides methods to parse a VAST NonLinear Element.
*/
/**
* Parses a NonLinear element.
* @param {any} creativeElement - The VAST NonLinear element to parse.
* @param {any} creativeAttributes - The attributes of the NonLinear (optional).
* @return {CreativeNonLinear}
*/
function parseCreativeNonLinear(creativeElement, creativeAttributes) {
var creative = new CreativeNonLinear(creativeAttributes);
parserUtils.childrenByName(creativeElement, 'TrackingEvents').forEach(function (trackingEventsElement) {
var eventName = void 0,
trackingURLTemplate = void 0;
parserUtils.childrenByName(trackingEventsElement, 'Tracking').forEach(function (trackingElement) {
eventName = trackingElement.getAttribute('event');
trackingURLTemplate = parserUtils.parseNodeText(trackingElement);
if (eventName && trackingURLTemplate) {
if (!Array.isArray(creative.trackingEvents[eventName])) {
creative.trackingEvents[eventName] = [];
}
creative.trackingEvents[eventName].push(trackingURLTemplate);
}
});
});
parserUtils.childrenByName(creativeElement, 'NonLinear').forEach(function (nonlinearResource) {
var nonlinearAd = new NonLinearAd();
nonlinearAd.id = nonlinearResource.getAttribute('id') || null;
nonlinearAd.width = nonlinearResource.getAttribute('width');
nonlinearAd.height = nonlinearResource.getAttribute('height');
nonlinearAd.expandedWidth = nonlinearResource.getAttribute('expandedWidth');
nonlinearAd.expandedHeight = nonlinearResource.getAttribute('expandedHeight');
nonlinearAd.scalable = parserUtils.parseBoolean(nonlinearResource.getAttribute('scalable'));
nonlinearAd.maintainAspectRatio = parserUtils.parseBoolean(nonlinearResource.getAttribute('maintainAspectRatio'));
nonlinearAd.minSuggestedDuration = parserUtils.parseDuration(nonlinearResource.getAttribute('minSuggestedDuration'));
nonlinearAd.apiFramework = nonlinearResource.getAttribute('apiFramework');
parserUtils.childrenByName(nonlinearResource, 'HTMLResource').forEach(function (htmlElement) {
nonlinearAd.type = htmlElement.getAttribute('creativeType') || 'text/html';
nonlinearAd.htmlResource = parserUtils.parseNodeText(htmlElement);
});
parserUtils.childrenByName(nonlinearResource, 'IFrameResource').forEach(function (iframeElement) {
nonlinearAd.type = iframeElement.getAttribute('creativeType') || 0;
nonlinearAd.iframeResource = parserUtils.parseNodeText(iframeElement);
});
parserUtils.childrenByName(nonlinearResource, 'StaticResource').forEach(function (staticElement) {
nonlinearAd.type = staticElement.getAttribute('creativeType') || 0;
nonlinearAd.staticResource = parserUtils.parseNodeText(staticElement);
});
var adParamsElement = parserUtils.childByName(nonlinearResource, 'AdParameters');
if (adParamsElement) {
nonlinearAd.adParameters = parserUtils.parseNodeText(adParamsElement);
}
nonlinearAd.nonlinearClickThroughURLTemplate = parserUtils.parseNodeText(parserUtils.childByName(nonlinearResource, 'NonLinearClickThrough'));
parserUtils.childrenByName(nonlinearResource, 'NonLinearClickTracking').forEach(function (clickTrackingElement) {
nonlinearAd.nonlinearClickTrackingURLTemplates.push(parserUtils.parseNodeText(clickTrackingElement));
});
creative.variations.push(nonlinearAd);
});
return creative;
}
/**
* This module provides methods to parse a VAST Ad Element.
*/
/**
* Parses an Ad element (can either be a Wrapper or an InLine).
* @param {Object} adElement - The VAST Ad element to parse.
* @return {Ad}
*/
function parseAd(adElement) {
var childNodes = adElement.childNodes;
for (var adTypeElementKey in childNodes) {
var adTypeElement = childNodes[adTypeElementKey];
if (['Wrapper', 'InLine'].indexOf(adTypeElement.nodeName) === -1) {
continue;
}
parserUtils.copyNodeAttribute('id', adElement, adTypeElement);
parserUtils.copyNodeAttribute('sequence', adElement, adTypeElement);
if (adTypeElement.nodeName === 'Wrapper') {
return parseWrapper(adTypeElement);
} else if (adTypeElement.nodeName === 'InLine') {
return parseInLine(adTypeElement);
}
}
}
/**
* Parses an Inline element.
* @param {Object} inLineElement - The VAST Inline element to parse.
* @return {Ad}
*/
function parseInLine(inLineElement) {
var childNodes = inLineElement.childNodes;
var ad = new Ad();
ad.id = inLineElement.getAttribute('id') || null;
ad.sequence = inLineElement.getAttribute('sequence') || null;
for (var nodeKey in childNodes) {
var node = childNodes[nodeKey];
switch (node.nodeName) {
case 'Error':
ad.errorURLTemplates.push(parserUtils.parseNodeText(node));
break;
case 'Impression':
ad.impressionURLTemplates.push(parserUtils.parseNodeText(node));
break;
case 'Creatives':
parserUtils.childrenByName(node, 'Creative').forEach(function (creativeElement) {
var creativeAttributes = {
id: creativeElement.getAttribute('id') || null,
adId: parseCreativeAdIdAttribute(creativeElement),
sequence: creativeElement.getAttribute('sequence') || null,
apiFramework: creativeElement.getAttribute('apiFramework') || null
};
for (var creativeTypeElementKey in creativeElement.childNodes) {
var creativeTypeElement = creativeElement.childNodes[creativeTypeElementKey];
var parsedCreative = void 0;
switch (creativeTypeElement.nodeName) {
case 'Linear':
parsedCreative = parseCreativeLinear(creativeTypeElement, creativeAttributes);
if (parsedCreative) {
ad.creatives.push(parsedCreative);
}
break;
case 'NonLinearAds':
parsedCreative = parseCreativeNonLinear(creativeTypeElement, creativeAttributes);
if (parsedCreative) {
ad.creatives.push(parsedCreative);
}
break;
case 'CompanionAds':
parsedCreative = parseCreativeCompanion(creativeTypeElement, creativeAttributes);
if (parsedCreative) {
ad.creatives.push(parsedCreative);
}
break;
}
}
});
break;
case 'Extensions':
parseExtensions(ad.extensions, parserUtils.childrenByName(node, 'Extension'));
break;
case 'AdSystem':
ad.system = {
value: parserUtils.parseNodeText(node),
version: node.getAttribute('version') || null
};
break;
case 'AdTitle':
ad.title = parserUtils.parseNodeText(node);
break;
case 'Description':
ad.description = parserUtils.parseNodeText(node);
break;
case 'Advertiser':
ad.advertiser = parserUtils.parseNodeText(node);
break;
case 'Pricing':
ad.pricing = {
value: parserUtils.parseNodeText(node),
model: node.getAttribute('model') || null,
currency: node.getAttribute('currency') || null
};
break;
case 'Survey':
ad.survey = parserUtils.parseNodeText(node);
break;
}
}
return ad;
}
/**
* Parses a Wrapper element without resolving the wrapped urls.
* @param {Object} wrapperElement - The VAST Wrapper element to be parsed.
* @return {Ad}
*/
function parseWrapper(wrapperElement) {
var ad = parseInLine(wrapperElement);
var wrapperURLElement = parserUtils.childByName(wrapperElement, 'VASTAdTagURI');
if (wrapperURLElement) {
ad.nextWrapperURL = parserUtils.parseNodeText(wrapperURLElement);
} else {
wrapperURLElement = parserUtils.childByName(wrapperElement, 'VASTAdTagURL');
if (wrapperURLElement) {
ad.nextWrapperURL = parserUtils.parseNodeText(parserUtils.childByName(wrapperURLElement, 'URL'));
}
}
ad.creatives.forEach(function (wrapperCreativeElement) {
if (['linear', 'nonlinear'].indexOf(wrapperCreativeElement.type) !== -1) {
// TrackingEvents Linear / NonLinear
if (wrapperCreativeElement.trackingEvents) {
if (!ad.trackingEvents) {
ad.trackingEvents = {};
}
if (!ad.trackingEvents[wrapperCreativeElement.type]) {
ad.trackingEvents[wrapperCreativeElement.type] = {};
}
var _loop = function _loop(eventName) {
var urls = wrapperCreativeElement.trackingEvents[eventName];
if (!Array.isArray(ad.trackingEvents[wrapperCreativeElement.type][eventName])) {
ad.trackingEvents[wrapperCreativeElement.type][eventName] = [];
}
urls.forEach(function (url) {
ad.trackingEvents[wrapperCreativeElement.type][eventName].push(url);
});
};
for (var eventName in wrapperCreativeElement.trackingEvents) {
_loop(eventName);
}
}
// ClickTracking
if (wrapperCreativeElement.videoClickTrackingURLTemplates) {
if (!Array.isArray(ad.videoClickTrackingURLTemplates)) {
ad.videoClickTrackingURLTemplates = [];
} // tmp property to save wrapper tracking URLs until they are merged
wrapperCreativeElement.videoClickTrackingURLTemplates.forEach(function (item) {
ad.videoClickTrackingURLTemplates.push(item);
});
}
// ClickThrough
if (wrapperCreativeElement.videoClickThroughURLTemplate) {
ad.videoClickThroughURLTemplate = wrapperCreativeElement.videoClickThroughURLTemplate;
}
// CustomClick
if (wrapperCreativeElement.videoCustomClickURLTemplates) {
if (!Array.isArray(ad.videoCustomClickURLTemplates)) {
ad.videoCustomClickURLTemplates = [];
} // tmp property to save wrapper tracking URLs until they are merged
wrapperCreativeElement.videoCustomClickURLTemplates.forEach(function (item) {
ad.videoCustomClickURLTemplates.push(item);
});
}
}
});
if (ad.nextWrapperURL) {
return ad;
}
}
/**
* Parses an array of Extension elements.
* @param {Array} collection - The array used to store the parsed extensions.
* @param {Array} extensions - The array of extensions to parse.
*/
function parseExtensions(collection, extensions) {
extensions.forEach(function (extNode) {
var ext = new AdExtension();
var extNodeAttrs = extNode.attributes;
var childNodes = extNode.childNodes;
if (extNode.attributes) {
for (var extNodeAttrKey in extNodeAttrs) {
var extNodeAttr = extNodeAttrs[extNodeAttrKey];
if (extNodeAttr.nodeName && extNodeAttr.nodeValue) {
ext.attributes[extNodeAttr.nodeName] = extNodeAttr.nodeValue;
}
}
}
for (var childNodeKey in childNodes) {
var childNode = childNodes[childNodeKey];
var txt = parserUtils.parseNodeText(childNode);
// ignore comments / empty value
if (childNode.nodeName !== '#comment' && txt !== '') {
var extChild = new AdExtensionChild();
extChild.name = childNode.nodeName;
extChild.value = txt;
if (childNode.attributes) {
var childNodeAttributes = childNode.attributes;
for (var extChildNodeAttrKey in childNodeAttributes) {
var extChildNodeAttr = childNodeAttributes[extChildNodeAttrKey];
extChild.attributes[extChildNodeAttr.nodeName] = extChildNodeAttr.nodeValue;
}
}
ext.children.push(extChild);
}
}
collection.push(ext);
});
}
/**
* Parses the creative adId Attribute.
* @param {any} creativeElement - The creative element to retrieve the adId from.
* @return {String|null}
*/
function parseCreativeAdIdAttribute(creativeElement) {
return creativeElement.getAttribute('AdID') || // VAST 2 spec
creativeElement.getAttribute('adID') || // VAST 3 spec
creativeElement.getAttribute('adId') || // VAST 4 spec
null;
}
var domain;
// This constructor is used to store event handlers. Instantiating this is
// faster than explicitly calling `Object.create(null)` to get a "clean" empty
// object (tested with v8 v4.9).
function EventHandlers() {}
EventHandlers.prototype = Object.create(null);
function EventEmitter() {
EventEmitter.init.call(this);
}
// nodejs oddity
// require('events') === require('events').EventEmitter
EventEmitter.EventEmitter = EventEmitter;
EventEmitter.usingDomains = false;
EventEmitter.prototype.domain = undefined;
EventEmitter.prototype._events = undefined;
EventEmitter.prototype._maxListeners = undefined;
// By default EventEmitters will print a warning if more than 10 listeners are
// added to it. This is a useful default which helps finding memory leaks.
EventEmitter.defaultMaxListeners = 10;
EventEmitter.init = function() {
this.domain = null;
if (EventEmitter.usingDomains) {
// if there is an active domain, then attach to it.
if (domain.active && !(this instanceof domain.Domain)) {
this.domain = domain.active;
}
}
if (!this._events || this._events === Object.getPrototypeOf(this)._events) {
this._events = new EventHandlers();
this._eventsCount = 0;
}
this._maxListeners = this._maxListeners || undefined;
};
// Obviously not all Emitters should be limited to 10. This function allows
// that to be increased. Set to zero for unlimited.
EventEmitter.prototype.setMaxListeners = function setMaxListeners(n) {
if (typeof n !== 'number' || n < 0 || isNaN(n))
throw new TypeError('"n" argument must be a positive number');
this._maxListeners = n;
return this;
};
function $getMaxListeners(that) {
if (that._maxListeners === undefined)
return EventEmitter.defaultMaxListeners;
return that._maxListeners;
}
EventEmitter.prototype.getMaxListeners = function getMaxListeners() {
return $getMaxListeners(this);
};
// These standalone emit* functions are used to optimize calling of event
// handlers for fast cases because emit() itself often has a variable number of
// arguments and can be deoptimized because of that. These functions always have
// the same number of arguments and thus do not get deoptimized, so the code
// inside them can execute faster.
function emitNone(handler, isFn, self) {
if (isFn)
handler.call(self);
else {
var len = handler.length;
var listeners = arrayClone(handler, len);
for (var i = 0; i < len; ++i)
listeners[i].call(self);
}
}
function emitOne(handler, isFn, self, arg1) {
if (isFn)
handler.call(self, arg1);
else {
var len = handler.length;
var listeners = arrayClone(handler, len);
for (var i = 0; i < len; ++i)
listeners[i].call(self, arg1);
}
}
function emitTwo(handler, isFn, self, arg1, arg2) {
if (isFn)
handler.call(self, arg1, arg2);
else {
var len = handler.length;
var listeners = arrayClone(handler, len);
for (var i = 0; i < len; ++i)
listeners[i].call(self, arg1, arg2);
}
}
function emitThree(handler, isFn, self, arg1, arg2, arg3) {
if (isFn)
handler.call(self, arg1, arg2, arg3);
else {
var len = handler.length;
var listeners = arrayClone(handler, len);
for (var i = 0; i < len; ++i)
listeners[i].call(self, arg1, arg2, arg3);
}
}
function emitMany(handler, isFn, self, args) {
if (isFn)
handler.apply(self, args);
else {
var len = handler.length;
var listeners = arrayClone(handler, len);
for (var i = 0; i < len; ++i)
listeners[i].apply(self, args);
}
}
EventEmitter.prototype.emit = function emit(type) {
var er, handler, len, args, i, events, domain;
var needDomainExit = false;
var doError = (type === 'error');
events = this._events;
if (events)
doError = (doError && events.error == null);
else if (!doError)
return false;
domain = this.domain;
// If there is no 'error' event listener then throw.
if (doError) {
er = arguments[1];
if (domain) {
if (!er)
er = new Error('Uncaught, unspecified "error" event');
er.domainEmitter = this;
er.domain = domain;
er.domainThrown = false;
domain.emit('error', er);
} else if (er instanceof Error) {
throw er; // Unhandled 'error' event
} else {
// At least give some kind of context to the user
var err = new Error('Uncaught, unspecified "error" event. (' + er + ')');
err.context = er;
throw err;
}
return false;
}
handler = events[type];
if (!handler)
return false;
var isFn = typeof handler === 'function';
len = arguments.length;
switch (len) {
// fast cases
case 1:
emitNone(handler, isFn, this);
break;
case 2:
emitOne(handler, isFn, this, arguments[1]);
break;
case 3:
emitTwo(handler, isFn, this, arguments[1], arguments[2]);
break;
case 4:
emitThree(handler, isFn, this, arguments[1], arguments[2], arguments[3]);
break;
// slower
default:
args = new Array(len - 1);
for (i = 1; i < len; i++)
args[i - 1] = arguments[i];
emitMany(handler, isFn, this, args);
}
if (needDomainExit)
domain.exit();
return true;
};
function _addListener(target, type, listener, prepend) {
var m;
var events;
var existing;
if (typeof listener !== 'function')
throw new TypeError('"listener" argument must be a function');
events = target._events;
if (!events) {
events = target._events = new EventHandlers();
target._eventsCount = 0;
} else {
// To avoid recursion in the case that type === "newListener"! Before
// adding it to the listeners, first emit "newListener".
if (events.newListener) {
target.emit('newListener', type,
listener.listener ? listener.listener : listener);
// Re-assign `events` because a newListener handler could have caused the
// this._events to be assigned to a new object
events = target._events;
}
existing = events[type];
}
if (!existing) {
// Optimize the case of one listener. Don't need the extra array object.
existing = events[type] = listener;
++target._eventsCount;
} else {
if (typeof existing === 'function') {
// Adding the second element, need to change to array.
existing = events[type] = prepend ? [listener, existing] :
[existing, listener];
} else {
// If we've already got an array, just append.
if (prepend) {
existing.unshift(listener);
} else {
existing.push(listener);
}
}
// Check for listener leak
if (!existing.warned) {
m = $getMaxListeners(target);
if (m && m > 0 && existing.length > m) {
existing.warned = true;
var w = new Error('Possible EventEmitter memory leak detected. ' +
existing.length + ' ' + type + ' listeners added. ' +
'Use emitter.setMaxListeners() to increase limit');
w.name = 'MaxListenersExceededWarning';
w.emitter = target;
w.type = type;
w.count = existing.length;
emitWarning(w);
}
}
}
return target;
}
function emitWarning(e) {
typeof console.warn === 'function' ? console.warn(e) : console.log(e);
}
EventEmitter.prototype.addListener = function addListener(type, listener) {
return _addListener(this, type, listener, false);
};
EventEmitter.prototype.on = EventEmitter.prototype.addListener;
EventEmitter.prototype.prependListener =
function prependListener(type, listener) {
return _addListener(this, type, listener, true);
};
function _onceWrap(target, type, listener) {
var fired = false;
function g() {
target.removeListener(type, g);
if (!fired)