UNPKG

react-native-vast-client

Version:
1,525 lines (1,287 loc) 86.7 kB
'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);