react-native-vast-client
Version:
JavaScript VAST Client
1,525 lines (1,287 loc) • 86.7 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var events = require('events');
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;
}
function xdr() {
var request = void 0;
if (window.XDomainRequest) {
// eslint-disable-next-line no-undef
request = new XDomainRequest();
}
return request;
}
function supported() {
return !!xdr();
}
function get$1(url, options, cb) {
var xmlDocument = typeof window.ActiveXObject === 'function' ? new window.ActiveXObject('Microsoft.XMLDOM') : undefined;
if (xmlDocument) {
xmlDocument.async = false;
} else {
return cb(new Error('FlashURLHandler: Microsoft.XMLDOM format not supported'));
}
var request = xdr();
request.open('GET', url);
request.timeout = options.timeout || 0;
request.withCredentials = options.withCredentials || false;
request.send();
request.onprogress = function () {};
request.onload = function () {
xmlDocument.loadXML(request.responseText);
cb(null, xmlDocument);
};
}
var flashURLHandler = {
get: get$1,
supported: supported
};
var uri = require('url');
var fs = require('fs');
var http = require('http');
var https = require('https');
var DOMParser = require('xmldom').DOMParser;
function get$2(url, options, cb) {
url = uri.parse(url);
var httpModule = url.protocol === 'https:' ? https : http;
if (url.protocol === 'file:') {
fs.readFile(url.pathname, 'utf8', function (err, data) {
if (err) {
return cb(err);
}
var xml = new DOMParser().parseFromString(data);
cb(null, xml);
});
} else {
var timing = void 0;
var data = '';
var timeoutWrapper = function timeoutWrapper(req) {
return function () {
return req.abort();
};
};
var req = httpModule.get(url.href, function (res) {
res.on('data', function (chunk) {
data += chunk;
clearTimeout(timing);
timing = setTimeout(fn, options.timeout || 120000);
});
res.on('end', function () {
clearTimeout(timing);
var xml = new DOMParser().parseFromString(data);
cb(null, xml);
});
});
req.on('error', function (err) {
clearTimeout(timing);
cb(err);
});
var fn = timeoutWrapper(req);
timing = setTimeout(fn, options.timeout || 120000);
}
}
var nodeURLHandler = {
get: get$2
};
function xhr() {
try {
var request = new window.XMLHttpRequest();
if ('withCredentials' in request) {
// check CORS support
return request;
}
return null;
} catch (err) {
return null;
}
}
function supported$1() {
return !!xhr();
}
function get$3(url, options, cb) {
if (window.location.protocol === 'https:' && url.indexOf('http://') === 0) {
return cb(new Error('XHRURLHandler: Cannot go from HTTPS to HTTP.'));
}
try {
var request = xhr();
request.open('GET', url);
request.timeout = options.timeout || 0;
request.withCredentials = options.withCredentials || false;
request.overrideMimeType && request.overrideMimeType('text/xml');
request.onreadystatechange = function () {
if (request.readyState === 4) {
if (request.status === 200) {
cb(null, request.responseXML);
} else {
cb(new Error('XHRURLHandler: ' + request.statusText));
}
}
};
request.send();
} catch (error) {
cb(new Error('XHRURLHandler: Unexpected error'));
}
}
var XHRURLHandler = {
get: get$3,
supported: supported$1
};
function get$4(url, options, cb) {
// Allow skip of the options param
if (!cb) {
if (typeof options === 'function') {
cb = options;
}
options = {};
}
if (typeof window === 'undefined' || window === null) {
return nodeURLHandler.get(url, options, cb);
} else if (XHRURLHandler.supported()) {
return XHRURLHandler.get(url, options, cb);
} else if (flashURLHandler.supported()) {
return flashURLHandler.get(url, options, cb);
}
return cb(new Error('Current context is not supported by any of the default URLHandlers. Please provide a custom URLHandler'));
}
var urlHandler = {
get: get$4
};
var VASTResponse = function VASTResponse() {
classCallCheck(this, VASTResponse);
this.ads = [];
this.errorURLTemplates = [];
this.version = null;
};
var DEFAULT_MAX_WRAPPER_DEPTH = 10;
var DEFAULT_EVENT_DATA = {
ERRORCODE: 900,
extensions: []
};
/**
* This class provides methods to fetch and parse a VAST document.
* @export
* @class VASTParser
* @extends EventEmitter
*/
var VASTParser = function (_EventEmitter) {
inherits(VASTParser, _EventEmitter);
/**
* Creates an instance of VASTParser.
* @constructor
*/
function VASTParser() {
classCallCheck(this, VASTParser);
var _this = possibleConstructorReturn(this, (VASTParser.__proto__ || Object.getPrototypeOf(VASTParser)).call(this));
_this.remainingAds = [];
_this.parentURLs = [];
_this.errorURLTemplates = [];
_this.rootErrorURLTemplates = [];
_this.maxWrapperDepth = null;
_this.URLTemplateFilters = [];
_this.fetchingOptions = {};
return _this;
}
/**
* Adds a filter function to the array of filters which are called before fetching a VAST document.
* @param {function} filter - The filter function to be added at the end of the array.
* @return {void}
*/
createClass(VASTParser, [{
key: 'addURLTemplateFilter',
value: function addURLTemplateFilter(filter) {
if (typeof filter === 'function') {
this.URLTemplateFilters.push(filter);
}
}
/**
* Removes the last element of the url templates filters array.
* @return {void}
*/
}, {
key: 'removeURLTemplateFilter',
value: function removeURLTemplateFilter() {
this.URLTemplateFilters.pop();
}
/**
* Returns the number of filters of the url templates filters array.
* @return {Number}
*/
}, {
key: 'countURLTemplateFilters',
value: function countURLTemplateFilters() {
return this.URLTemplateFilters.length;
}
/**
* Removes all the filter functions from the url templates filters array.
* @return {void}
*/
}, {
key: 'clearURLTemplateFilters',
value: function clearURLTemplateFilters() {
this.URLTemplateFilters = [];
}
/**
* Tracks the error provided in the errorCode parameter and emits a VAST-error event for the given error.
* @param {Array} urlTemplates - An Array of url templates to use to make the tracking call.
* @param {Object} errorCode - An Object containing the error data.
* @param {Object} data - One (or more) Object containing additional data.
* @emits VASTParser#VAST-error
* @return {void}
*/
}, {
key: 'trackVastError',
value: function trackVastError(urlTemplates, errorCode) {
for (var _len = arguments.length, data = Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {
data[_key - 2] = arguments[_key];
}
this.emit('VAST-error', Object.assign.apply(Object, [{}, DEFAULT_EVENT_DATA, errorCode].concat(data)));
util.track(urlTemplates, errorCode);
}
/**
* Returns an array of errorURLTemplates for the VAST being parsed.
* @return {Array}
*/
}, {
key: 'getErrorURLTemplates',
value: function getErrorURLTemplates() {
return this.rootErrorURLTemplates.concat(this.errorURLTemplates);
}
/**
* Fetches a VAST document for the given url.
* Returns a Promise which resolves,rejects according to the result of the request.
* @param {String} url - The url to request the VAST document.
* @param {Number} wrapperDepth - how many times the current url has been wrapped
* @param {String} originalUrl - url of original wrapper
* @emits VASTParser#VAST-resolving
* @emits VASTParser#VAST-resolved
* @return {Promise}
*/
}, {
key: 'fetchVAST',
value: function fetchVAST(url, wrapperDepth, originalUrl) {
var _this2 = this;
return new Promise(function (resolve, reject) {
// Process url with defined filter
_this2.URLTemplateFilters.forEach(function (filter) {
url = filter(url);
});
_this2.parentURLs.push(url);
_this2.emit('VAST-resolving', { url: url, wrapperDepth: wrapperDepth, originalUrl: originalUrl });
_this2.urlHandler.get(url, _this2.fetchingOptions, function (err, xml) {
_this2.emit('VAST-resolved', { url: url, error: err });
if (err) {
reject(err);
} else {
resolve(xml);
}
});
});
}
/**
* Inits the parsing properties of the class with the custom values provided as options.
* @param {Object} options - The options to initialize a parsing sequence
*/
}, {
key: 'initParsingStatus',
value: function initParsingStatus() {
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
this.rootURL = '';
this.remainingAds = [];
this.parentURLs = [];
this.errorURLTemplates = [];
this.rootErrorURLTemplates = [];
this.maxWrapperDepth = options.wrapperLimit || DEFAULT_MAX_WRAPPER_DEPTH;
this.fetchingOptions = {
timeout: options.timeout,
withCredentials: options.withCredentials
};
this.urlHandler = options.urlHandler || options.urlhandler || urlHandler;
this.vastVersion = null;
}
/**
* Resolves the next group of ads. If all is true resolves all the remaining ads.
* @param {Boolean} all - If true all the remaining ads are resolved
* @return {Promise}
*/
}, {
key: 'getRemainingAds',
value: function getRemainingAds(all) {
var _this3 = this;
if (this.remainingAds.length === 0) {
return Promise.reject(new Error('No more ads are available for the given VAST'));
}
var ads = all ? util.flatten(this.remainingAds) : this.remainingAds.shift();
this.errorURLTemplates = [];
this.parentURLs = [];
return this.resolveAds(ads, {
wrapperDepth: 0,
originalUrl: this.rootURL
}).then(function (resolvedAds) {
return _this3.buildVASTResponse(resolvedAds);