1var VAST = (function (exports) { 2 "use strict"; 3 4 var classCallCheck = function (instance, Constructor) { 5 if (!(instance instanceof Constructor)) { 6 throw new TypeError("Cannot call a class as a function"); 7 } 8 }; 9 10 var createClass = (function () { 11 function defineProperties(target, props) { 12 for (var i = 0; i < props.length; i++) { 13 var descriptor = props[i]; 14 descriptor.enumerable = descriptor.enumerable || false; 15 descriptor.configurable = true; 16 if ("value" in descriptor) descriptor.writable = true; 17 Object.defineProperty(target, descriptor.key, descriptor); 18 } 19 } 20 21 return function (Constructor, protoProps, staticProps) { 22 if (protoProps) defineProperties(Constructor.prototype, protoProps); 23 if (staticProps) defineProperties(Constructor, staticProps); 24 return Constructor; 25 }; 26 })(); 27 28 var inherits = function (subClass, superClass) { 29 if (typeof superClass !== "function" && superClass !== null) { 30 throw new TypeError( 31 "Super expression must either be null or a function, not " + 32 typeof superClass 33 ); 34 } 35 36 subClass.prototype = Object.create(superClass && superClass.prototype, { 37 constructor: { 38 value: subClass, 39 enumerable: false, 40 writable: true, 41 configurable: true, 42 }, 43 }); 44 if (superClass) 45 Object.setPrototypeOf 46 ? Object.setPrototypeOf(subClass, superClass) 47 : (subClass.__proto__ = superClass); 48 }; 49 50 var possibleConstructorReturn = function (self, call) { 51 if (!self) { 52 throw new ReferenceError( 53 "this hasn't been initialised - super() hasn't been called" 54 ); 55 } 56 57 return call && (typeof call === "object" || typeof call === "function") 58 ? call 59 : self; 60 }; 61 62 var Ad = function Ad() { 63 classCallCheck(this, Ad); 64 65 this.id = null; 66 this.sequence = null; 67 this.system = null; 68 this.title = null; 69 this.description = null; 70 this.advertiser = null; 71 this.pricing = null; 72 this.survey = null; 73 this.errorURLTemplates = []; 74 this.impressionURLTemplates = []; 75 this.creatives = []; 76 this.extensions = []; 77 }; 78 79 var AdExtension = function AdExtension() { 80 classCallCheck(this, AdExtension); 81 82 this.attributes = {}; 83 this.children = []; 84 }; 85 86 var AdExtensionChild = function AdExtensionChild() { 87 classCallCheck(this, AdExtensionChild); 88 89 this.name = null; 90 this.value = null; 91 this.attributes = {}; 92 }; 93 94 var CompanionAd = function CompanionAd() { 95 classCallCheck(this, CompanionAd); 96 97 this.id = null; 98 this.width = 0; 99 this.height = 0; 100 this.type = null; 101 this.staticResource = null; 102 this.htmlResource = null; 103 this.iframeResource = null; 104 this.altText = null; 105 this.companionClickThroughURLTemplate = null; 106 this.companionClickTrackingURLTemplates = []; 107 this.trackingEvents = {}; 108 }; 109 110 var Creative = function Creative() { 111 var creativeAttributes = 112 arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; 113 classCallCheck(this, Creative); 114 115 this.id = creativeAttributes.id || null; 116 this.adId = creativeAttributes.adId || null; 117 this.sequence = creativeAttributes.sequence || null; 118 this.apiFramework = creativeAttributes.apiFramework || null; 119 this.trackingEvents = {}; 120 }; 121 122 var CreativeCompanion = (function (_Creative) { 123 inherits(CreativeCompanion, _Creative); 124 125 function CreativeCompanion() { 126 var creativeAttributes = 127 arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; 128 classCallCheck(this, CreativeCompanion); 129 130 var _this = possibleConstructorReturn( 131 this, 132 ( 133 CreativeCompanion.__proto__ || 134 Object.getPrototypeOf(CreativeCompanion) 135 ).call(this, creativeAttributes) 136 ); 137 138 _this.type = "companion"; 139 _this.variations = []; 140 return _this; 141 } 142 143 return CreativeCompanion; 144 })(Creative); 145 146 function track(URLTemplates, variables, options) { 147 var URLs = resolveURLTemplates(URLTemplates, variables, options); 148 149 URLs.forEach(function (URL) { 150 if (typeof window !== "undefined" && window !== null) { 151 var i = new Image(); 152 i.src = URL; 153 } 154 }); 155 } 156 157 /** 158 * Replace the provided URLTemplates with the given values 159 * 160 * @param {Array} URLTemplates - An array of tracking url templates. 161 * @param {Object} [variables={}] - An optional Object of parameters to be used in the tracking calls. 162 * @param {Object} [options={}] - An optional Object of options to be used in the tracking calls. 163 */ 164 function resolveURLTemplates(URLTemplates) { 165 var variables = 166 arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 167 var options = 168 arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; 169 170 var URLs = []; 171 172 // Encode String variables, when given 173 if (variables["ASSETURI"]) { 174 variables["ASSETURI"] = encodeURIComponentRFC3986(variables["ASSETURI"]); 175 } 176 if (variables["CONTENTPLAYHEAD"]) {
177 variables["CONTENTPLAYHEAD"] = encodeURIComponentRFC3986( 178 variables["CONTENTPLAYHEAD"] 179 ); 180 } 181 182 // Set default value for invalid ERRORCODE 183 if ( 184 variables["ERRORCODE"] && 185 !options.isCustomCode && 186 !/^[0-9]{3}$/.test(variables["ERRORCODE"]) 187 ) { 188 variables["ERRORCODE"] = 900; 189 } 190 191 // Calc random/time based macros 192 variables["CACHEBUSTING"] = leftpad( 193 Math.round(Math.random() * 1.0e8).toString() 194 ); 195 variables["TIMESTAMP"] = encodeURIComponentRFC3986( 196 new Date().toISOString() 197 ); 198 199 // RANDOM/random is not defined in VAST 3/4 as a valid macro tho it's used by some adServer (Auditude) 200 variables["RANDOM"] = variables["random"] = variables["CACHEBUSTING"]; 201 202 for (var URLTemplateKey in URLTemplates) { 203 var resolveURL = URLTemplates[URLTemplateKey]; 204 205 if (typeof resolveURL !== "string") { 206 continue; 207 } 208 209 for (var key in variables) { 210 var value = variables[key]; 211 var macro1 = "[" + key + "]"; 212 var macro2 = "%%" + key + "%%"; 213 resolveURL = resolveURL.replace(macro1, value); 214 resolveURL = resolveURL.replace(macro2, value); 215 } 216 URLs.push(resolveURL); 217 } 218 219 return URLs; 220 } 221 222 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent 223 function encodeURIComponentRFC3986(str) { 224 return encodeURIComponent(str).replace(/[!'()*]/g, function (c) { 225 return "%" + c.charCodeAt(0).toString(16); 226 }); 227 } 228 229 function leftpad(str) { 230 if (str.length < 8) { 231 return ( 232 range(0, 8 - str.length, false) 233 .map(function () { 234 return "0"; 235 }) 236 .join("") + str 237 ); 238 } 239 return str; 240 } 241 242 function range(left, right, inclusive) { 243 var result = []; 244 var ascending = left < right; 245 var end = !inclusive ? right : ascending ? right + 1 : right - 1; 246 247 for (var i = left; ascending ? i < end : i > end; ascending ? i++ : i--) { 248 result.push(i); 249 } 250 return result; 251 } 252 253 function isNumeric(n) { 254 return !isNaN(parseFloat(n)) && isFinite(n); 255 } 256 257 function flatten(arr) { 258 return arr.reduce(function (flat, toFlatten) { 259 return flat.concat( 260 Array.isArray(toFlatten) ? flatten(toFlatten) : toFlatten 261 ); 262 }, []); 263 } 264 265 var util = { 266 track: track, 267 resolveURLTemplates: resolveURLTemplates, 268 encodeURIComponentRFC3986: encodeURIComponentRFC3986, 269 leftpad: leftpad, 270 range: range, 271 isNumeric: isNumeric, 272 flatten: flatten, 273 }; 274 275 /** 276 * This module provides support methods to the parsing classes. 277 */ 278 279 /** 280 * Returns the first element of the given node which nodeName matches the given name. 281 * @param {Object} node - The node to use to find a match. 282 * @param {String} name - The name to look for. 283 * @return {Object} 284 */ 285 function childByName(node, name) { 286 var childNodes = node.childNodes; 287 288 for (var childKey in childNodes) { 289 var child = childNodes[childKey]; 290 291 if (child.nodeName === name) { 292 return child; 293 } 294 } 295 } 296 297 /** 298 * Returns all the elements of the given node which nodeName match the given name. 299 * @param {any} node - The node to use to find the matches. 300 * @param {any} name - The name to look for. 301 * @return {Array} 302 */ 303 function childrenByName(node, name) { 304 var children = []; 305 var childNodes = node.childNodes; 306 307 for (var childKey in childNodes) { 308 var child = childNodes[childKey]; 309 310 if (child.nodeName === name) { 311 children.push(child); 312 } 313 } 314 return children; 315 } 316 317 /** 318 * Converts relative vastAdTagUri. 319 * @param {String} vastAdTagUrl - The url to resolve. 320 * @param {String} originalUrl - The original url. 321 * @return {String} 322 */ 323 function resolveVastAdTagURI(vastAdTagUrl, originalUrl) { 324 if (!originalUrl) { 325 return vastAdTagUrl; 326 } 327 328 if (vastAdTagUrl.indexOf("//") === 0) { 329 var _location = location, 330 protocol = _location.protocol; 331 332 return "" + protocol + vastAdTagUrl; 333 } 334 335 if (vastAdTagUrl.indexOf("://") === -1) { 336 // Resolve relative URLs (mainly for unit testing) 337 var baseURL = originalUrl.slice(0, originalUrl.lastIndexOf("/")); 338 return baseURL + "/" + vastAdTagUrl; 339 } 340 341 return vastAdTagUrl; 342 } 343 344 /** 345 * Converts a boolean string into a Boolean.
346 * @param {String} booleanString - The boolean string to convert. 347 * @return {Boolean} 348 */ 349 function parseBoolean(booleanString) { 350 return ["true", "TRUE", "1"].indexOf(booleanString) !== -1; 351 } 352 353 /** 354 * Parses a node text (for legacy support). 355 * @param {Object} node - The node to parse the text from. 356 * @return {String} 357 */ 358 function parseNodeText(node) { 359 return node && (node.textContent || node.text || "").trim(); 360 } 361 362 /** 363 * Copies an attribute from a node to another. 364 * @param {String} attributeName - The name of the attribute to clone. 365 * @param {Object} nodeSource - The source node to copy the attribute from. 366 * @param {Object} nodeDestination - The destination node to copy the attribute at. 367 */ 368 function copyNodeAttribute(attributeName, nodeSource, nodeDestination) { 369 var attributeValue = nodeSource.getAttribute(attributeName); 370 if (attributeValue) { 371 nodeDestination.setAttribute(attributeName, attributeValue); 372 } 373 } 374 375 /** 376 * Parses a String duration into a Number. 377 * @param {String} durationString - The dureation represented as a string. 378 * @return {Number} 379 */ 380 function parseDuration(durationString) { 381 if (durationString === null || typeof durationString === "undefined") { 382 return -1; 383 } 384 // Some VAST doesn't have an HH:MM:SS duration format but instead jus the number of seconds 385 if (util.isNumeric(durationString)) { 386 return parseInt(durationString); 387 } 388 389 var durationComponents = durationString.split(":"); 390 if (durationComponents.length !== 3) { 391 return -1; 392 } 393 394 var secondsAndMS = durationComponents[2].split("."); 395 var seconds = parseInt(secondsAndMS[0]); 396 if (secondsAndMS.length === 2) { 397 seconds += parseFloat("0." + secondsAndMS[1]); 398 } 399 400 var minutes = parseInt(durationComponents[1] * 60); 401 var hours = parseInt(durationComponents[0] * 60 * 60); 402 403 if ( 404 isNaN(hours) || 405 isNaN(minutes) || 406 isNaN(seconds) || 407 minutes > 60 * 60 || 408 seconds > 60 409 ) { 410 return -1; 411 } 412 return hours + minutes + seconds; 413 } 414 415 /** 416 * Splits an Array of ads into an Array of Arrays of ads. 417 * Each subarray contains either one ad or multiple ads (an AdPod) 418 * @param {Array} ads - An Array of ads to split 419 * @return {Array} 420 */ 421 function splitVAST(ads) { 422 var splittedVAST = []; 423 var lastAdPod = null; 424 425 ads.forEach(function (ad, i) { 426 if (ad.sequence) { 427 ad.sequence = parseInt(ad.sequence, 10); 428 } 429 // The current Ad may be the next Ad of an AdPod 430 if (ad.sequence > 1) { 431 var lastAd = ads[i - 1]; 432 // check if the current Ad is exactly the next one in the AdPod 433 if (lastAd && lastAd.sequence === ad.sequence - 1) { 434 lastAdPod && lastAdPod.push(ad); 435 return; 436 } 437 // If the ad had a sequence attribute but it was not part of a correctly formed 438 // AdPod, let's remove the sequence attribute 439 delete ad.sequence; 440 } 441 442 lastAdPod = [ad]; 443 splittedVAST.push(lastAdPod); 444 }); 445 446 return splittedVAST; 447 } 448 449 /** 450 * Merges the data between an unwrapped ad and his wrapper. 451 * @param {Ad} unwrappedAd - The 'unwrapped' Ad. 452 * @param {Ad} wrapper - The wrapper Ad. 453 * @return {void} 454 */ 455 function mergeWrapperAdData(unwrappedAd, wrapper) { 456 unwrappedAd.errorURLTemplates = wrapper.errorURLTemplates.concat( 457 unwrappedAd.errorURLTemplates 458 ); 459 unwrappedAd.impressionURLTemplates = wrapper.impressionURLTemplates.concat( 460 unwrappedAd.impressionURLTemplates 461 ); 462 unwrappedAd.extensions = wrapper.extensions.concat(unwrappedAd.extensions); 463 464 unwrappedAd.creatives.forEach(function (creative) { 465 if (wrapper.trackingEvents && wrapper.trackingEvents[creative.type]) {
466 for (var eventName in wrapper.trackingEvents[creative.type]) { 467 var urls = wrapper.trackingEvents[creative.type][eventName]; 468 if (!Array.isArray(creative.trackingEvents[eventName])) { 469 creative.trackingEvents[eventName] = []; 470 } 471 creative.trackingEvents[eventName] = creative.trackingEvents[ 472 eventName 473 ].concat(urls); 474 } 475 } 476 }); 477 478 if ( 479 wrapper.videoClickTrackingURLTemplates && 480 wrapper.videoClickTrackingURLTemplates.length 481 ) { 482 unwrappedAd.creatives.forEach(function (creative) { 483 if (creative.type === "linear") { 484 creative.videoClickTrackingURLTemplates = creative.videoClickTrackingURLTemplates.concat( 485 wrapper.videoClickTrackingURLTemplates 486 ); 487 } 488 }); 489 } 490 491 if ( 492 wrapper.videoCustomClickURLTemplates && 493 wrapper.videoCustomClickURLTemplates.length 494 ) { 495 unwrappedAd.creatives.forEach(function (creative) { 496 if (creative.type === "linear") { 497 creative.videoCustomClickURLTemplates = creative.videoCustomClickURLTemplates.concat( 498 wrapper.videoCustomClickURLTemplates 499 ); 500 } 501 }); 502 } 503 504 // VAST 2.0 support - Use Wrapper/linear/clickThrough when Inline/Linear/clickThrough is null 505 if (wrapper.videoClickThroughURLTemplate) { 506 unwrappedAd.creatives.forEach(function (creative) { 507 if ( 508 creative.type === "linear" && 509 (creative.videoClickThroughURLTemplate === null || 510 typeof creative.videoClickThroughURLTemplate === "undefined") 511 ) { 512 creative.videoClickThroughURLTemplate = 513 wrapper.videoClickThroughURLTemplate; 514 } 515 }); 516 } 517 } 518 519 var parserUtils = { 520 childByName: childByName, 521 childrenByName: childrenByName, 522 resolveVastAdTagURI: resolveVastAdTagURI, 523 parseBoolean: parseBoolean, 524 parseNodeText: parseNodeText, 525 copyNodeAttribute: copyNodeAttribute, 526 parseDuration: parseDuration, 527 splitVAST: splitVAST, 528 mergeWrapperAdData: mergeWrapperAdData, 529 }; 530 531 /** 532 * This module provides methods to parse a VAST CompanionAd Element. 533 */ 534 535 /** 536 * Parses a CompanionAd. 537 * @param {Object} creativeElement - The VAST CompanionAd element to parse. 538 * @param {Object} creativeAttributes - The attributes of the CompanionAd (optional). 539 * @return {CreativeCompanion} 540 */ 541 function parseCreativeCompanion(creativeElement, creativeAttributes) { 542 var creative = new CreativeCompanion(creativeAttributes); 543 544 parserUtils 545 .childrenByName(creativeElement, "Companion") 546 .forEach(function (companionResource) { 547 var companionAd = new CompanionAd(); 548 companionAd.id = companionResource.getAttribute("id") || null; 549 companionAd.width = companionResource.getAttribute("width"); 550 companionAd.height = companionResource.getAttribute("height"); 551 companionAd.companionClickTrackingURLTemplates = []; 552 553 parserUtils 554 .childrenByName(companionResource, "HTMLResource") 555 .forEach(function (htmlElement) { 556 companionAd.type = 557 htmlElement.getAttribute("creativeType") || "text/html"; 558 companionAd.htmlResource = parserUtils.parseNodeText(htmlElement); 559 }); 560 561 parserUtils 562 .childrenByName(companionResource, "IFrameResource") 563 .forEach(function (iframeElement) { 564 companionAd.type = iframeElement.getAttribute("creativeType") || 0; 565 companionAd.iframeResource = parserUtils.parseNodeText( 566 iframeElement 567 ); 568 }); 569 570 parserUtils 571 .childrenByName(companionResource, "StaticResource") 572 .forEach(function (staticElement) { 573 companionAd.type = staticElement.getAttribute("creativeType") || 0; 574 575 parserUtils 576 .childrenByName(companionResource, "AltText") 577 .forEach(function (child) { 578 companionAd.altText = parserUtils.parseNodeText(child); 579 }); 580 581 companionAd.staticResource = parserUtils.parseNodeText( 582 staticElement 583 ); 584 }); 585 586 parserUtils 587 .childrenByName(companionResource, "TrackingEvents") 588 .forEach(function (trackingEventsElement) { 589 parserUtils 590 .childrenByName(trackingEventsElement, "Tracking") 591 .forEach(function (trackingElement) { 592 var eventName = trackingElement.getAttribute("event"); 593 var trackingURLTemplate = parserUtils.parseNodeText( 594 trackingElement 595 ); 596 if (eventName && trackingURLTemplate) { 597 if (!Array.isArray(companionAd.trackingEvents[eventName])) { 598 companionAd.trackingEvents[eventName] = []; 599 } 600 companionAd.trackingEvents[eventName].push( 601 trackingURLTemplate 602 ); 603 } 604 }); 605 }); 606 607 parserUtils
608 .childrenByName(companionResource, "CompanionClickTracking") 609 .forEach(function (clickTrackingElement) { 610 companionAd.companionClickTrackingURLTemplates.push( 611 parserUtils.parseNodeText(clickTrackingElement) 612 ); 613 }); 614 615 companionAd.companionClickThroughURLTemplate = parserUtils.parseNodeText( 616 parserUtils.childByName(companionResource, "CompanionClickThrough") 617 ); 618 companionAd.companionClickTrackingURLTemplate = parserUtils.parseNodeText( 619 parserUtils.childByName(companionResource, "CompanionClickTracking") 620 ); 621 creative.variations.push(companionAd); 622 }); 623 624 return creative; 625 } 626 627 var CreativeLinear = (function (_Creative) { 628 inherits(CreativeLinear, _Creative); 629 630 function CreativeLinear() { 631 var creativeAttributes = 632 arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; 633 classCallCheck(this, CreativeLinear); 634 635 var _this = possibleConstructorReturn( 636 this, 637 ( 638 CreativeLinear.__proto__ || Object.getPrototypeOf(CreativeLinear) 639 ).call(this, creativeAttributes) 640 ); 641 642 _this.type = "linear"; 643 _this.duration = 0; 644 _this.skipDelay = null; 645 _this.mediaFiles = []; 646 _this.videoClickThroughURLTemplate = null; 647 _this.videoClickTrackingURLTemplates = []; 648 _this.videoCustomClickURLTemplates = []; 649 _this.adParameters = null; 650 _this.icons = []; 651 return _this; 652 } 653 654 return CreativeLinear; 655 })(Creative); 656 657 var Icon = function Icon() { 658 classCallCheck(this, Icon); 659 660 this.program = null; 661 this.height = 0; 662 this.width = 0; 663 this.xPosition = 0; 664 this.yPosition = 0; 665 this.apiFramework = null; 666 this.offset = null; 667 this.duration = 0; 668 this.type = null; 669 this.staticResource = null; 670 this.htmlResource = null; 671 this.iframeResource = null; 672 this.iconClickThroughURLTemplate = null; 673 this.iconClickTrackingURLTemplates = []; 674 this.iconViewTrackingURLTemplate = null; 675 }; 676 677 var MediaFile = function MediaFile() { 678 classCallCheck(this, MediaFile); 679 680 this.id = null; 681 this.fileURL = null; 682 this.deliveryType = "progressive"; 683 this.mimeType = null; 684 this.codec = null; 685 this.bitrate = 0; 686 this.minBitrate = 0; 687 this.maxBitrate = 0; 688 this.width = 0; 689 this.height = 0; 690 this.apiFramework = null; 691 this.scalable = null; 692 this.maintainAspectRatio = null; 693 }; 694 695 /** 696 * This module provides methods to parse a VAST Linear Element. 697 */ 698 699 /** 700 * Parses a Linear element. 701 * @param {Object} creativeElement - The VAST Linear element to parse. 702 * @param {any} creativeAttributes - The attributes of the Linear (optional). 703 * @return {CreativeLinear} 704 */ 705 function parseCreativeLinear(creativeElement, creativeAttributes) { 706 var offset = void 0; 707 var creative = new CreativeLinear(creativeAttributes); 708 709 creative.duration = parserUtils.parseDuration( 710 parserUtils.parseNodeText( 711 parserUtils.childByName(creativeElement, "Duration") 712 ) 713 ); 714 var skipOffset = creativeElement.getAttribute("skipoffset"); 715 716 if (typeof skipOffset === "undefined" || skipOffset === null) { 717 creative.skipDelay = null; 718 } else if ( 719 skipOffset.charAt(skipOffset.length - 1) === "%" && 720 creative.duration !== -1 721 ) { 722 var percent = parseInt(skipOffset, 10); 723 creative.skipDelay = creative.duration * (percent / 100); 724 } else { 725 creative.skipDelay = parserUtils.parseDuration(skipOffset); 726 } 727 728 var videoClicksElement = parserUtils.childByName( 729 creativeElement, 730 "VideoClicks" 731 ); 732 if (videoClicksElement) { 733 creative.videoClickThroughURLTemplate = parserUtils.parseNodeText( 734 parserUtils.childByName(videoClicksElement, "ClickThrough") 735 ); 736 737 parserUtils 738 .childrenByName(videoClicksElement, "ClickTracking") 739 .forEach(function (clickTrackingElement) { 740 creative.videoClickTrackingURLTemplates.push( 741 parserUtils.parseNodeText(clickTrackingElement) 742 ); 743 }); 744 745 parserUtils 746 .childrenByName(videoClicksElement, "CustomClick") 747 .forEach(function (customClickElement) { 748 creative.videoCustomClickURLTemplates.push( 749 parserUtils.parseNodeText(customClickElement) 750 ); 751 }); 752 } 753 754 var adParamsElement = parserUtils.childByName( 755 creativeElement, 756 "AdParameters" 757 ); 758 if (adParamsElement) { 759 creative.adParameters = parserUtils.parseNodeText(adParamsElement); 760 } 761 762 parserUtils 763 .childrenByName(creativeElement, "TrackingEvents") 764 .forEach(function (trackingEventsElement) { 765 parserUtils 766 .childrenByName(trackingEventsElement, "Tracking") 767 .forEach(function (trackingElement) { 768 var eventName = trackingElement.getAttribute("event"); 769 var trackingURLTemplate = parserUtils.parseNodeText( 770 trackingElement 771 ); 772 if (eventName && trackingURLTemplate) { 773 if (eventName === "progress") { 774 offset = trackingElement.getAttribute("offset"); 775 if (!offset) { 776 return; 777 } 778 if (offset.charAt(offset.length - 1) === "%") { 779 eventName = "progress-" + offset; 780 } else { 781 eventName = 782 "progress-" + Math.round(parserUtils.parseDuration(offset)); 783 } 784 } 785 786 if (!Array.isArray(creative.trackingEvents[eventName])) { 787 creative.trackingEvents[eventName] = []; 788 } 789 creative.trackingEvents[eventName].push(trackingURLTemplate); 790 } 791 }); 792 }); 793 794 parserUtils 795 .childrenByName(creativeElement, "MediaFiles") 796 .forEach(function (mediaFilesElement) { 797 parserUtils 798 .childrenByName(mediaFilesElement, "MediaFile") 799 .forEach(function (mediaFileElement) { 800 var mediaFile = new MediaFile(); 801 mediaFile.id = mediaFileElement.getAttribute("id"); 802 mediaFile.fileURL = parserUtils.parseNodeText(mediaFileElement); 803 mediaFile.deliveryType = mediaFileElement.getAttribute("delivery"); 804 mediaFile.codec = mediaFileElement.getAttribute("codec"); 805 mediaFile.mimeType = mediaFileElement.getAttribute("type"); 806 mediaFile.apiFramework = mediaFileElement.getAttribute( 807 "apiFramework" 808 ); 809 mediaFile.bitrate = parseInt( 810 mediaFileElement.getAttribute("bitrate") || 0 811 ); 812 mediaFile.minBitrate = parseInt( 813 mediaFileElement.getAttribute("minBitrate") || 0 814 ); 815 mediaFile.maxBitrate = parseInt( 816 mediaFileElement.getAttribute("maxBitrate") || 0 817 ); 818 mediaFile.width = parseInt( 819 mediaFileElement.getAttribute("width") || 0 820 ); 821 mediaFile.height = parseInt( 822 mediaFileElement.getAttribute("height") || 0 823 ); 824 825 var scalable = mediaFileElement.getAttribute("scalable"); 826 if (scalable && typeof scalable === "string") { 827 scalable = scalable.toLowerCase(); 828 if (scalable === "true") { 829 mediaFile.scalable = true; 830 }
830 else if (scalable === "false") { 831 mediaFile.scalable = false; 832 } 833 } 834 835 var maintainAspectRatio = mediaFileElement.getAttribute( 836 "maintainAspectRatio" 837 ); 838 if ( 839 maintainAspectRatio && 840 typeof maintainAspectRatio === "string" 841 ) { 842 maintainAspectRatio = maintainAspectRatio.toLowerCase(); 843 if (maintainAspectRatio === "true") { 844 mediaFile.maintainAspectRatio = true; 845 } else if (maintainAspectRatio === "false") { 846 mediaFile.maintainAspectRatio = false; 847 } 848 } 849 850 creative.mediaFiles.push(mediaFile); 851 }); 852 }); 853 854 var iconsElement = parserUtils.childByName(creativeElement, "Icons"); 855 if (iconsElement) { 856 parserUtils 857 .childrenByName(iconsElement, "Icon") 858 .forEach(function (iconElement) { 859 var icon = new Icon(); 860 icon.program = iconElement.getAttribute("program"); 861 icon.height = parseInt(iconElement.getAttribute("height") || 0); 862 icon.width = parseInt(iconElement.getAttribute("width") || 0); 863 icon.xPosition = parseXPosition( 864 iconElement.getAttribute("xPosition") 865 ); 866 icon.yPosition = parseYPosition( 867 iconElement.getAttribute("yPosition") 868 ); 869 icon.apiFramework = iconElement.getAttribute("apiFramework"); 870 icon.offset = parserUtils.parseDuration( 871 iconElement.getAttribute("offset") 872 ); 873 icon.duration = parserUtils.parseDuration( 874 iconElement.getAttribute("duration") 875 ); 876 877 parserUtils 878 .childrenByName(iconElement, "HTMLResource") 879 .forEach(function (htmlElement) { 880 icon.type = 881 htmlElement.getAttribute("creativeType") || "text/html"; 882 icon.htmlResource = parserUtils.parseNodeText(htmlElement); 883 }); 884 885 parserUtils 886 .childrenByName(iconElement, "IFrameResource") 887 .forEach(function (iframeElement) { 888 icon.type = iframeElement.getAttribute("creativeType") || 0; 889 icon.iframeResource = parserUtils.parseNodeText(iframeElement); 890 }); 891 892 parserUtils 893 .childrenByName(iconElement, "StaticResource") 894 .forEach(function (staticElement) { 895 icon.type = staticElement.getAttribute("creativeType") || 0; 896 icon.staticResource = parserUtils.parseNodeText(staticElement); 897 }); 898 899 var iconClicksElement = parserUtils.childByName( 900 iconElement, 901 "IconClicks" 902 ); 903 if (iconClicksElement) { 904 icon.iconClickThroughURLTemplate = parserUtils.parseNodeText( 905 parserUtils.childByName(iconClicksElement, "IconClickThrough") 906 ); 907 parserUtils 908 .childrenByName(iconClicksElement, "IconClickTracking") 909 .forEach(function (iconClickTrackingElement) { 910 icon.iconClickTrackingURLTemplates.push( 911 parserUtils.parseNodeText(iconClickTrackingElement) 912 ); 913 }); 914 } 915 916 icon.iconViewTrackingURLTemplate = parserUtils.parseNodeText( 917 parserUtils.childByName(iconElement, "IconViewTracking") 918 ); 919 920 creative.icons.push(icon); 921 }); 922 } 923 924 return creative; 925 } 926 927 /** 928 * Parses an horizontal position into a String ('left' or 'right') or into a Number. 929 * @param {String} xPosition - The x position to parse. 930 * @return {String|Number} 931 */ 932 function parseXPosition(xPosition) { 933 if (["left", "right"].indexOf(xPosition) !== -1) { 934 return xPosition; 935 } 936 937 return parseInt(xPosition || 0); 938 } 939 940 /** 941 * Parses an vertical position into a String ('top' or 'bottom') or into a Number. 942 * @param {String} yPosition - The x position to parse. 943 * @return {String|Number} 944 */ 945 function parseYPosition(yPosition) { 946 if (["top", "bottom"].indexOf(yPosition) !== -1) { 947 return yPosition; 948 } 949 950 return parseInt(yPosition || 0); 951 } 952 953 var CreativeNonLinear = (function (_Creative) { 954 inherits(CreativeNonLinear, _Creative); 955 956 function CreativeNonLinear() { 957 var creativeAttributes = 958 arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; 959 classCallCheck(this, CreativeNonLinear); 960 961 var _this = possibleConstructorReturn( 962 this, 963 ( 964 CreativeNonLinear.__proto__ || 965 Object.getPrototypeOf(CreativeNonLinear) 966 ).call(this, creativeAttributes) 967 ); 968 969 _this.type = "nonlinear"; 970 _this.variations = []; 971 return _this; 972 } 973 974 return CreativeNonLinear; 975 })(Creative); 976 977 var NonLinearAd = function NonLinearAd() { 978 classCallCheck(this, NonLinearAd); 979 980 this.id = null; 981 this.width = 0; 982 this.height = 0; 983 this.expandedWidth = 0; 984 this.expandedHeight = 0; 985 this.scalable = true; 986 this.maintainAspectRatio = true; 987 this.minSuggestedDuration = 0; 988 this.apiFramework = "static"; 989 this.type = null; 990 this.staticResource = null; 991 this.htmlResource = null; 992 this.iframeResource = null; 993 this.nonlinearClickThroughURLTemplate = null; 994 this.nonlinearClickTrackingURLTemplates = []; 995 this.adParameters = null; 996 }; 997 998 /** 999 * This module provides methods to parse a VAST NonLinear Element. 1000 */ 1001 1002 /** 1003 * Parses a NonLinear element. 1004 * @param {any} creativeElement - The VAST NonLinear element to parse. 1005 * @param {any} creativeAttributes - The attributes of the NonLinear (optional). 1006 * @return {CreativeNonLinear} 1007 */ 1008 function parseCreativeNonLinear(creativeElement, creativeAttributes) { 1009 var creative = new CreativeNonLinear(creativeAttributes); 1010 1011 parserUtils 1012 .childrenByName(creativeElement, "TrackingEvents") 1013 .forEach(function (trackingEventsElement) { 1014 var eventName = void 0, 1015 trackingURLTemplate = void 0; 1016 parserUtils 1017 .childrenByName(trackingEventsElement, "Tracking") 1018 .forEach(function (trackingElement) { 1019 eventName = trackingElement.getAttribute("event"); 1020 trackingURLTemplate = parserUtils.parseNodeText(trackingElement); 1021 1022 if (eventName && trackingURLTemplate) { 1023 if (!Array.isArray(creative.trackingEvents[eventName])) { 1024 creative.trackingEvents[eventName] = []; 1025 } 1026 creative.trackingEvents[eventName].push(trackingURLTemplate); 1027 } 1028 }); 1029 }); 1030 1031 parserUtils 1032 .childrenByName(creativeElement, "NonLinear")
1033 .forEach(function (nonlinearResource) { 1034 var nonlinearAd = new NonLinearAd(); 1035 nonlinearAd.id = nonlinearResource.getAttribute("id") || null; 1036 nonlinearAd.width = nonlinearResource.getAttribute("width"); 1037 nonlinearAd.height = nonlinearResource.getAttribute("height"); 1038 nonlinearAd.expandedWidth = nonlinearResource.getAttribute( 1039 "expandedWidth" 1040 ); 1041 nonlinearAd.expandedHeight = nonlinearResource.getAttribute( 1042 "expandedHeight" 1043 ); 1044 nonlinearAd.scalable = parserUtils.parseBoolean( 1045 nonlinearResource.getAttribute("scalable") 1046 ); 1047 nonlinearAd.maintainAspectRatio = parserUtils.parseBoolean( 1048 nonlinearResource.getAttribute("maintainAspectRatio") 1049 ); 1050 nonlinearAd.minSuggestedDuration = parserUtils.parseDuration( 1051 nonlinearResource.getAttribute("minSuggestedDuration") 1052 ); 1053 nonlinearAd.apiFramework = nonlinearResource.getAttribute( 1054 "apiFramework" 1055 ); 1056 1057 parserUtils 1058 .childrenByName(nonlinearResource, "HTMLResource") 1059 .forEach(function (htmlElement) { 1060 nonlinearAd.type = 1061 htmlElement.getAttribute("creativeType") || "text/html"; 1062 nonlinearAd.htmlResource = parserUtils.parseNodeText(htmlElement); 1063 }); 1064 1065 parserUtils 1066 .childrenByName(nonlinearResource, "IFrameResource") 1067 .forEach(function (iframeElement) { 1068 nonlinearAd.type = iframeElement.getAttribute("creativeType") || 0; 1069 nonlinearAd.iframeResource = parserUtils.parseNodeText( 1070 iframeElement 1071 ); 1072 }); 1073 1074 parserUtils 1075 .childrenByName(nonlinearResource, "StaticResource") 1076 .forEach(function (staticElement) { 1077 nonlinearAd.type = staticElement.getAttribute("creativeType") || 0; 1078 nonlinearAd.staticResource = parserUtils.parseNodeText( 1079 staticElement 1080 ); 1081 }); 1082 1083 var adParamsElement = parserUtils.childByName( 1084 nonlinearResource, 1085 "AdParameters" 1086 ); 1087 if (adParamsElement) { 1088 nonlinearAd.adParameters = parserUtils.parseNodeText(adParamsElement); 1089 } 1090 1091 nonlinearAd.nonlinearClickThroughURLTemplate = parserUtils.parseNodeText( 1092 parserUtils.childByName(nonlinearResource, "NonLinearClickThrough") 1093 ); 1094 parserUtils 1095 .childrenByName(nonlinearResource, "NonLinearClickTracking") 1096 .forEach(function (clickTrackingElement) { 1097 nonlinearAd.nonlinearClickTrackingURLTemplates.push( 1098 parserUtils.parseNodeText(clickTrackingElement) 1099 ); 1100 }); 1101 1102 creative.variations.push(nonlinearAd); 1103 }); 1104 1105 return creative; 1106 } 1107 1108 /** 1109 * This module provides methods to parse a VAST Ad Element. 1110 */ 1111 1112 /** 1113 * Parses an Ad element (can either be a Wrapper or an InLine). 1114 * @param {Object} adElement - The VAST Ad element to parse. 1115 * @return {Ad} 1116 */ 1117 function parseAd(adElement) { 1118 var childNodes = adElement.childNodes; 1119 1120 for (var adTypeElementKey in childNodes) { 1121 var adTypeElement = childNodes[adTypeElementKey]; 1122 1123 if (["Wrapper", "InLine"].indexOf(adTypeElement.nodeName) === -1) { 1124 continue; 1125 } 1126 1127 parserUtils.copyNodeAttribute("id", adElement, adTypeElement); 1128 parserUtils.copyNodeAttribute("sequence", adElement, adTypeElement); 1129 1130 if (adTypeElement.nodeName === "Wrapper") { 1131 return parseWrapper(adTypeElement); 1132 } else if (adTypeElement.nodeName === "InLine") { 1133 return parseInLine(adTypeElement); 1134 } 1135 } 1136 } 1137 1138 /** 1139 * Parses an Inline element. 1140 * @param {Object} inLineElement - The VAST Inline element to parse. 1141 * @return {Ad} 1142 */ 1143 function parseInLine(inLineElement) { 1144 var childNodes = inLineElement.childNodes; 1145 var ad = new Ad(); 1146 ad.id = inLineElement.getAttribute("id") || null; 1147 ad.sequence = inLineElement.getAttribute("sequence") || null; 1148 1149 for (var nodeKey in childNodes) { 1150 var node = childNodes[nodeKey]; 1151 1152 switch (node.nodeName) { 1153 case "Error": 1154 ad.errorURLTemplates.push(parserUtils.parseNodeText(node)); 1155 break; 1156 1157 case "Impression": 1158 ad.impressionURLTemplates.push(parserUtils.parseNodeText(node)); 1159 break; 1160 1161 case "Creatives": 1162 parserUtils 1163 .childrenByName(node, "Creative") 1164 .forEach(function (creativeElement) { 1165 var creativeAttributes = { 1166 id: creativeElement.getAttribute("id") || null, 1167 adId: parseCreativeAdIdAttribute(creativeElement), 1168 sequence: creativeElement.getAttribute("sequence") || null, 1169 apiFramework: 1170 creativeElement.getAttribute("apiFramework") || null, 1171 }; 1172 1173 for (var creativeTypeElementKey in creativeElement.childNodes) { 1174 var creativeTypeElement = 1175 creativeElement.childNodes[creativeTypeElementKey]; 1176 var parsedCreative = void 0; 1177 1178 switch (creativeTypeElement.nodeName) { 1179 case "Linear": 1180 parsedCreative = parseCreativeLinear( 1181 creativeTypeElement, 1182 creativeAttributes 1183 ); 1184 if (parsedCreative) { 1185 ad.creatives.push(parsedCreative); 1186 } 1187 break; 1188 case "NonLinearAds": 1189 parsedCreative = parseCreativeNonLinear( 1190 creativeTypeElement, 1191 creativeAttributes 1192 ); 1193 if (parsedCreative) { 1194 ad.creatives.push(parsedCreative); 1195 } 1196 break; 1197 case "CompanionAds": 1198 parsedCreative = parseCreativeCompanion( 1199 creativeTypeElement, 1200 creativeAttributes 1201 ); 1202 if (parsedCreative) { 1203 ad.creatives.push(parsedCreative); 1204 } 1205 break; 1206 } 1207 } 1208 }); 1209 break; 1210 1211 case "Extensions": 1212 parseExtensions( 1213 ad.extensions, 1214 parserUtils.childrenByName(node, "Extension") 1215 ); 1216 break; 1217 1218 case "AdSystem": 1219 ad.system = { 1220 value: parserUtils.parseNodeText(node), 1221 version: node.getAttribute("version") || null, 1222 }; 1223 break; 1224 1225 case "AdTitle": 1226 ad.title = parserUtils.parseNodeText(node); 1227 break; 1228 1229 case "Description": 1230 ad.description = parserUtils.parseNodeText(node); 1231 break; 1232 1233 case "Advertiser": 1234 ad.advertiser = parserUtils.parseNodeText(node); 1235 break; 1236 1237 case "Pricing": 1238 ad.pricing = { 1239 value: parserUtils.parseNodeText(node), 1240 model: node.getAttribute("model") || null, 1241 currency: node.getAttribute("currency") || null, 1242 }; 1243 break; 1244 1245 case "Survey": 1246 ad.survey = parserUtils.parseNodeText(node); 1247 break; 1248 } 1249 } 1250 1251 return ad; 1252 } 1253 1254 /** 1255 * Parses a Wrapper element without resolving the wrapped urls. 1256 * @param {Object} wrapperElement - The VAST Wrapper element to be parsed. 1257 * @return {Ad} 1258 */ 1259 function parseWrapper(wrapperElement) { 1260 var ad = parseInLine(wrapperElement); 1261 var wrapperURLElement = parserUtils.childByName( 1262 wrapperElement, 1263 "VASTAdTagURI" 1264 ); 1265 1266 if (wrapperURLElement) { 1267 ad.nextWrapperURL = parserUtils.parseNodeText(wrapperURLElement); 1268 } else { 1269 wrapperURLElement = parserUtils.childByName( 1270 wrapperElement, 1271 "VASTAdTagURL" 1272 ); 1273 1274 if (wrapperURLElement) { 1275 ad.nextWrapperURL = parserUtils.parseNodeText( 1276 parserUtils.childByName(wrapperURLElement, "URL") 1277 ); 1278 } 1279 } 1280
1281 ad.creatives.forEach(function (wrapperCreativeElement) { 1282 if (["linear", "nonlinear"].indexOf(wrapperCreativeElement.type) !== -1) { 1283 // TrackingEvents Linear / NonLinear 1284 if (wrapperCreativeElement.trackingEvents) { 1285 if (!ad.trackingEvents) { 1286 ad.trackingEvents = {}; 1287 } 1288 if (!ad.trackingEvents[wrapperCreativeElement.type]) { 1289 ad.trackingEvents[wrapperCreativeElement.type] = {}; 1290 } 1291 1292 var _loop = function _loop(eventName) { 1293 var urls = wrapperCreativeElement.trackingEvents[eventName]; 1294 if ( 1295 !Array.isArray( 1296 ad.trackingEvents[wrapperCreativeElement.type][eventName] 1297 ) 1298 ) { 1299 ad.trackingEvents[wrapperCreativeElement.type][eventName] = []; 1300 } 1301 urls.forEach(function (url) { 1302 ad.trackingEvents[wrapperCreativeElement.type][eventName].push( 1303 url 1304 ); 1305 }); 1306 }; 1307
1308 for (var eventName in wrapperCreativeElement.trackingEvents) { 1309 _loop(eventName); 1310 } 1311 } 1312 // ClickTracking 1313 if (wrapperCreativeElement.videoClickTrackingURLTemplates) { 1314 if (!Array.isArray(ad.videoClickTrackingURLTemplates)) { 1315 ad.videoClickTrackingURLTemplates = []; 1316 } // tmp property to save wrapper tracking URLs until they are merged 1317 wrapperCreativeElement.videoClickTrackingURLTemplates.forEach( 1318 function (item) { 1319 ad.videoClickTrackingURLTemplates.push(item); 1320 } 1321 ); 1322 } 1323 // ClickThrough 1324 if (wrapperCreativeElement.videoClickThroughURLTemplate) { 1325 ad.videoClickThroughURLTemplate = 1326 wrapperCreativeElement.videoClickThroughURLTemplate; 1327 } 1328 // CustomClick 1329 if (wrapperCreativeElement.videoCustomClickURLTemplates) { 1330 if (!Array.isArray(ad.videoCustomClickURLTemplates)) { 1331 ad.videoCustomClickURLTemplates = []; 1332 } // tmp property to save wrapper tracking URLs until they are merged 1333 wrapperCreativeElement.videoCustomClickURLTemplates.forEac
1333h(function ( 1334 item 1335 ) { 1336 ad.videoCustomClickURLTemplates.push(item); 1337 }); 1338 } 1339 } 1340 }); 1341 1342 if (ad.nextWrapperURL) { 1343 return ad; 1344 } 1345 } 1346 1347 /** 1348 * Parses an array of Extension elements. 1349 * @param {Array} collection - The array used to store the parsed extensions. 1350 * @param {Array} extensions - The array of extensions to parse. 1351 */ 1352 function parseExtensions(collection, extensions) { 1353 extensions.forEach(function (extNode) { 1354 var ext = new AdExtension(); 1355 var extNodeAttrs = extNode.attributes; 1356 var childNodes = extNode.childNodes; 1357 1358 if (extNode.attributes) { 1359 for (var extNodeAttrKey in extNodeAttrs) { 1360 var extNodeAttr = extNodeAttrs[extNodeAttrKey]; 1361 1362 if (extNodeAttr.nodeName && extNodeAttr.nodeValue) { 1363 ext.attributes[extNodeAttr.nodeName] = extNodeAttr.nodeValue; 1364 } 1365 } 1366 } 1367 1368 for (var childNodeKey in childNodes) { 1369 var childNode = childNodes[childNodeKey]; 1370 var txt = parserUtils.parseNodeText(childNode); 1371 1372 // ignore comments / empty value 1373 if (childNode.nodeName !== "#comment" && txt !== "") { 1374 var extChild = new AdExtensionChild(); 1375 extChild.name = childNode.nodeName; 1376 extChild.value = txt; 1377 1378 if (childNode.attributes) { 1379 var childNodeAttributes = childNode.attributes; 1380 1381 for (var extChildNodeAttrKey in childNodeAttributes) { 1382 var extChildNodeAttr = childNodeAttributes[extChildNodeAttrKey]; 1383 1384 extChild.attributes[extChildNodeAttr.nodeName] = 1385 extChildNodeAttr.nodeValue; 1386 } 1387 } 1388 1389 ext.children.push(extChild); 1390 } 1391 } 1392 1393 collection.push(ext); 1394 }); 1395 } 1396 1397 /** 1398 * Parses the creative adId Attribute. 1399 * @param {any} creativeElement - The creative element to retrieve the adId from. 1400 * @return {String|null} 1401 */ 1402 function parseCreativeAdIdAttribute(creativeElement) { 1403 return ( 1404 creativeElement.getAttribute("AdID") || // VAST 2 spec 1405 creativeElement.getAttribute("adID") || // VAST 3 spec 1406 creativeElement.getAttribute("adId") || // VAST 4 spec 1407 null 1408 ); 1409 } 1410 1411 var domain; 1412 1413 // This constructor is used to store event handlers. Instantiating this is 1414 // faster than explicitly calling `Object.create(null)` to get a "clean" empty 1415 // object (tested with v8 v4.9). 1416 function EventHandlers() {} 1417 EventHandlers.prototype = Object.create(null); 1418 1419 function EventEmitter() { 1420 EventEmitter.init.call(this); 1421 } 1422 1423 // nodejs oddity 1424 // require('events') === require('events').EventEmitter 1425 EventEmitter.EventEmitter = EventEmitter; 1426 1427 EventEmitter.usingDomains = false; 1428 1429 EventEmitter.prototype.domain = undefined; 1430 EventEmitter.prototype._events = undefined; 1431 EventEmitter.prototype._maxListeners = undefined; 1432 1433 // By default EventEmitters will print a warning if more than 10 listeners are 1434 // added to it. This is a useful default which helps finding memory leaks. 1435 EventEmitter.defaultMaxListeners = 10; 1436 1437 EventEmitter.init = function () { 1438 this.domain = null; 1439 if (EventEmitter.usingDomains) { 1440 // if there is an active domain, then attach to it. 1441 if (domain.active && !(this instanceof domain.Domain)) { 1442 this.domain = domain.active; 1443 } 1444 } 1445 1446 if (!this._events || this._events === Object.getPrototypeOf(this)._events) { 1447 this._events = new EventHandlers(); 1448 this._eventsCount = 0; 1449 } 1450 1451 this._maxListeners = this._maxListeners || undefined; 1452 }; 1453 1454 // Obviously not all Emitters should be limited to 10. This function allows 1455 // that to be increased. Set to zero for unlimited. 1456 EventEmitter.prototype.setMaxListeners = function setMaxListeners(n) { 1457 if (typeof n !== "number" || n < 0 || isNaN(n)) 1458 throw new TypeError('"n" argument must be a positive number'); 1459 this._maxListeners = n; 1460 return this; 1461 }; 1462 1463 function $getMaxListeners(that) { 1464 if (that._maxListeners === undefined) 1465 return EventEmitter.defaultMaxListeners; 1466 return that._maxListeners; 1467 } 1468 1469 EventEmitter.prototype.getMaxListeners = function getMaxListeners() { 1470 return $getMaxListeners(this); 1471 }; 1472 1473 // These standalone emit* functions are used to optimize calling of event 1474 // handlers for fast cases because emit() itself often has a variable number of 1475 // arguments and can be deoptimized because of that. These functions always have 1476 // the same number of arguments and thus do not get deoptimized, so the code 1477 // inside them can execute faster. 1478 function emitNone(handler, isFn, self) { 1479 if (isFn) handler.call(self); 1480 else { 1481 var len = handler.length; 1482 var listeners = arrayClone(handler, len); 1483 for (var i = 0; i < len; ++i) listeners[i].call(self); 1484 } 1485 } 1486 function emitOne(handler, isFn, self, arg1) { 1487 if (isFn) handler.call(self, arg1); 1488 else { 1489 var len = handler.length; 1490 var listeners = arrayClone(handler, len); 1491 for (var i = 0; i < len; ++i) listeners[i].call(self, arg1); 1492 } 1493 } 1494 function emitTwo(handler, isFn, self, arg1, arg2) { 1495 if (isFn) handler.call(self, arg1, arg2); 1496 else { 1497 var len = handler.length; 1498 var listeners = arrayClone(handler, len); 1499 for (var i = 0; i < len; ++i) listeners[i].call(self, arg1, arg2); 1500 } 1501 } 1502 function emitThree(handler, isFn, self, arg1, arg2, arg3) { 1503 if (isFn) handler.call(self, arg1, arg2, arg3); 1504 else { 1505 var len = handler.length; 1506 var listeners = arrayClone(handler, len); 1507 for (var i = 0; i < len; ++i) listeners[i].call(self, arg1, arg2, arg3); 1508 } 1509 } 1510 1511 function emitMany(handler, isFn, self, args) { 1512 if (isFn) handler.apply(self, args); 1513 else { 1514 var len = handler.length; 1515 var listeners = arrayClone(handler, len); 1516 for (var i = 0; i < len; ++i) listeners[i].apply(self, args); 1517 } 1518 } 1519 1520 EventEmitter.prototype.emit = function emit(type) { 1521 var er, handler, len, args, i, events, domain; 1522 var needDomainExit = false;
1523 var doError = type === "error"; 1524 1525 events = this._events; 1526 if (events) doError = doError && events.error == null; 1527 else if (!doError) return false; 1528 1529 domain = this.domain; 1530 1531 // If there is no 'error' event listener then throw. 1532 if (doError) { 1533 er = arguments[1]; 1534 if (domain) { 1535 if (!er) er = new Error('Uncaught, unspecified "error" event'); 1536 er.domainEmitter = this; 1537 er.domain = domain; 1538 er.domainThrown = false; 1539 domain.emit("error", er); 1540 } else if (er instanceof Error) { 1541 throw er; // Unhandled 'error' event 1542 } else { 1543 // At least give some kind of context to the user 1544 var err = new Error( 1545 'Uncaught, unspecified "error" event. (' + er + ")" 1546 ); 1547 err.context = er; 1548 throw err; 1549 } 1550 return false; 1551 } 1552 1553 handler = events[type]; 1554 1555 if (!handler) return false; 1556 1557 var isFn = typeof handler === "function"; 1558 len = arguments.length; 1559 switch (len) { 1560 // fast cases 1561 case 1: 1562 emitNone(handler, isFn, this); 1563 break; 1564 case 2: 1565 emitOne(handler, isFn, this, arguments[1]); 1566 break; 1567 case 3: 1568 emitTwo(handler, isFn, this, arguments[1], arguments[2]); 1569 break; 1570 case 4: 1571 emitThree( 1572 handler, 1573 isFn, 1574 this, 1575 arguments[1], 1576 arguments[2], 1577 arguments[3] 1578 ); 1579 break; 1580 // slower 1581 default: 1582 args = new Array(len - 1); 1583 for (i = 1; i < len; i++) args[i - 1] = arguments[i]; 1584 emitMany(handler, isFn, this, args); 1585 } 1586 1587 if (needDomainExit) domain.exit(); 1588 1589 return true; 1590 }; 1591 1592 function _addListener(target, type, listener, prepend) { 1593 var m; 1594 var events; 1595 var existing; 1596 1597 if (typeof listener !== "function") 1598 throw new TypeError('"listener" argument must be a function'); 1599 1600 events = target._events; 1601 if (!events) { 1602 events = target._events = new EventHandlers(); 1603 target._eventsCount = 0; 1604 } else { 1605 // To avoid recursion in the case that type === "newListener"! Before 1606 // adding it to the listeners, first emit "newListener". 1607 if (events.newListener) { 1608 target.emit( 1609 "newListener", 1610 type, 1611 listener.listener ? listener.listener : listener 1612 ); 1613 1614 // Re-assign `events` because a newListener handler could have caused the 1615 // this._events to be assigned to a new object 1616 events = target._events; 1617 } 1618 existing = events[type]; 1619 } 1620 1621 if (!existing) { 1622 // Optimize the case of one listener. Don't need the extra array object. 1623 existing = events[type] = listener; 1624 ++target._eventsCount; 1625 } else { 1626 if (typeof existing === "function") { 1627 // Adding the second element, need to change to array. 1628 existing = events[type] = prepend 1629 ? [listener, existing] 1630 : [existing, listener]; 1631 } else { 1632 // If we've already got an array, just append. 1633 if (prepend) { 1634 existing.unshift(listener); 1635 } else { 1636 existing.push(listener); 1637 } 1638 } 1639 1640 // Check for listener leak 1641 if (!existing.warned) { 1642 m = $getMaxListeners(target); 1643 if (m && m > 0 && existing.length > m) { 1644 existing.warned = true; 1645 var w = new Error( 1646 "Possible EventEmitter memory leak detected. " + 1647 existing.length + 1648 " " + 1649 type + 1650 " listeners added. " + 1651 "Use emitter.setMaxListeners() to increase limit" 1652 ); 1653 w.name = "MaxListenersExceededWarning"; 1654 w.emitter = target; 1655 w.type = type; 1656 w.count = existing.length; 1657 emitWarning(w); 1658 } 1659 } 1660 } 1661 1662 return target; 1663 } 1664 function emitWarning(e) { 1665 typeof console.warn === "function" ? console.warn(e) : console.log(e); 1666 } 1667 EventEmitter.prototype.addListener = function addListener(type, listener) { 1668 return _addListener(this, type, listener, false); 1669 }; 1670 1671 EventEmitter.prototype.on = EventEmitter.prototype.addListener; 1672 1673 EventEmitter.prototype.prependListener = function prependListener( 1674 type, 1675 listener 1676 ) { 1677 return _addListener(this, type, listener, true); 1678 }; 1679 1680 function _onceWrap(target, type, listener) { 1681 var fired = false;
1682 function g() { 1683 target.removeListener(type, g); 1684 if (!fired) { 1685 fired = true; 1686 listener.apply(target, arguments); 1687 } 1688 } 1689 g.listener = listener; 1690 return g; 1691 } 1692 1693 EventEmitter.prototype.once = function once(type, listener) { 1694 if (typeof listener !== "function") 1695 throw new TypeError('"listener" argument must be a function'); 1696 this.on(type, _onceWrap(this, type, listener)); 1697 return this; 1698 }; 1699 1700 EventEmitter.prototype.prependOnceListener = function prependOnceListener( 1701 type, 1702 listener 1703 ) { 1704 if (typeof listener !== "function") 1705 throw new TypeError('"listener" argument must be a function'); 1706 this.prependListener(type, _onceWrap(this, type, listener)); 1707 return this; 1708 }; 1709 1710 // emits a 'removeListener' event iff the listener was removed 1711 EventEmitter.prototype.removeListener = function removeListener( 1712 type, 1713 listener 1714 ) { 1715 var list, events, position, i, originalListener; 1716 1717 if (typeof listener !== "function") 1718 throw new TypeError('"listener" argument must be a function'); 1719 1720 events = this._events; 1721 if (!events) return this; 1722 1723 list = events[type]; 1724 if (!list) return this; 1725 1726 if (list === listener || (list.listener && list.listener === listener)) { 1727 if (--this._eventsCount === 0) this._events = new EventHandlers(); 1728 else { 1729 delete events[type]; 1730 if (events.removeListener) 1731 this.emit("removeListener", type, list.listener || listener); 1732 } 1733 } else if (typeof list !== "function") { 1734 position = -1; 1735 1736 for (i = list.length; i-- > 0; ) { 1737 if ( 1738 list[i] === listener || 1739 (list[i].listener && list[i].listener === listener) 1740 ) { 1741 originalListener = list[i].listener; 1742 position = i; 1743 break; 1744 } 1745 } 1746 1747 if (position < 0) return this; 1748 1749 if (list.length === 1) { 1750 list[0] = undefined; 1751 if (--this._eventsCount === 0) { 1752 this._events = new EventHandlers(); 1753 return this; 1754 } else { 1755 delete events[type]; 1756 } 1757 } else { 1758 spliceOne(list, position); 1759 } 1760 1761 if (events.removeListener) 1762 this.emit("removeListener", type, originalListener || listener); 1763 } 1764 1765 return this; 1766 }; 1767 1768 EventEmitter.prototype.removeAllListeners = function removeAllListeners( 1769 type 1770 ) { 1771 var listeners, events; 1772 1773 events = this._events; 1774 if (!events) return this; 1775 1776 // not listening for removeListener, no need to emit 1777 if (!events.removeListener) { 1778 if (arguments.length === 0) { 1779 this._events = new EventHandlers(); 1780 this._eventsCount = 0; 1781 } else if (events[type]) { 1782 if (--this._eventsCount === 0) this._events = new EventHandlers(); 1783 else delete events[type]; 1784 } 1785 return this; 1786 } 1787 1788 // emit removeListener for all listeners on all events 1789 if (arguments.length === 0) { 1790 var keys = Object.keys(events); 1791 for (var i = 0, key; i < keys.length; ++i) { 1792 key = keys[i]; 1793 if (key === "removeListener") continue; 1794 this.removeAllListeners(key); 1795 } 1796 this.removeAllListeners("removeListener"); 1797 this._events = new EventHandlers(); 1798 this._eventsCount = 0; 1799 return this; 1800 } 1801 1802 listeners = events[type]; 1803 1804 if (typeof listeners === "function") { 1805 this.removeListener(type, listeners); 1806 } else if (listeners) { 1807 // LIFO order 1808 do { 1809 this.removeListener(type, listeners[listeners.length - 1]); 1810 } while (listeners[0]); 1811 } 1812 1813 return this; 1814 }; 1815 1816 EventEmitter.prototype.listeners = function listeners(type) { 1817 var evlistener; 1818 var ret; 1819 var events = this._events; 1820 1821 if (!events) ret = []; 1822 else { 1823 evlistener = events[type]; 1824 if (!evlistener) ret = []; 1825 else if (typeof evlistener === "function") 1826 ret = [evlistener.listener || evlistener]; 1827 else ret = unwrapListeners(evlistener); 1828 } 1829 1830 return ret; 1831 }; 1832 1833 EventEmitter.listenerCount = function (emitter, type) { 1834 if (typeof emitter.listenerCount === "function") { 1835 return emitter.listenerCount(type); 1836 } else { 1837 return listenerCount.call(emitter, type); 1838 } 1839 }; 1840 1841 EventEmitter.prototype.listenerCount = listenerCount; 1842 function listenerCount(type) { 1843 var events = this._events; 1844 1845 if (events) { 1846 var evlistener = events[type]; 1847 1848 if (typeof evlistener === "function") { 1849 return 1; 1850 } else if (evlistener) { 1851 return evlistener.length; 1852 } 1853 } 1854 1855 return 0; 1856 } 1857 1858 EventEmitter.prototype.eventNames = function eventNames() { 1859 return this._eventsCount > 0 ? Reflect.ownKeys(this._events) : []; 1860 }; 1861 1862 // About 1.5x faster than the two-arg version of Array#splice(). 1863 function spliceOne(list, index) { 1864 for (var i = index, k = i + 1, n = list.length; k < n; i += 1, k += 1) 1865 list[i] = list[k]; 1866 list.pop(); 1867 } 1868 1869 function arrayClone(arr, i) { 1870 var copy = new Array(i); 1871 while (i--) copy[i] = arr[i]; 1872 return copy; 1873 } 1874 1875 function unwrapListeners(arr) { 1876 var ret = new Array(arr.length); 1877 for (var i = 0; i < ret.length; ++i) { 1878 ret[i] = arr[i].listener || arr[i]; 1879 } 1880 return ret; 1881 } 1882 1883 function xdr() { 1884 var request = void 0; 1885 if (window.XDomainRequest) { 1886 // eslint-disable-next-line no-undef 1887 request = new XDomainRequest(); 1888 } 1889 return request; 1890 } 1891 1892 function supported() { 1893 return !!xdr(); 1894 } 1895 1896 function get$1(url, options, cb) { 1897 var xmlDocument = 1898 typeof window.ActiveXObject === "function" 1899 ? new window.ActiveXObject("Microsoft.XMLDOM") 1900 : undefined; 1901 1902 if (xmlDocument) { 1903 xmlDocument.async = false;
1904 } else { 1905 return cb( 1906 new Error("FlashURLHandler: Microsoft.XMLDOM format not supported") 1907 ); 1908 } 1909 1910 var request = xdr(); 1911 request.open("GET", url); 1912 request.timeout = options.timeout || 0; 1913 request.withCredentials = options.withCredentials || false; 1914 request.send(); 1915 request.onprogress = function () {}; 1916 1917 request.onload = function () { 1918 xmlDocument.loadXML(request.responseText); 1919 cb(null, xmlDocument); 1920 }; 1921 } 1922 1923 var flashURLHandler = { 1924 get: get$1, 1925 supported: supported, 1926 }; 1927 1928 // This mock module is loaded in stead of the original NodeURLHandler module 1929 // when bundling the library for environments which are not node. 1930 // This allows us to avoid bundling useless node components and have a smaller build. 1931 function get$2(url, options, cb) { 1932 cb( 1933 new Error("Please bundle the library for node to use the node urlHandler") 1934 ); 1935 } 1936 1937 var nodeURLHandler = { 1938 get: get$2, 1939 }; 1940 1941 function xhr() { 1942 try { 1943 var request = new window.XMLHttpRequest(); 1944 if ("withCredentials" in request) { 1945 // check CORS support 1946 return request; 1947 } 1948 return null; 1949 } catch (err) { 1950 return null; 1951 } 1952 } 1953 1954 function supported$1() { 1955 return !!xhr(); 1956 } 1957 1958 function get$3(url, options, cb) { 1959 if (window.location.protocol === "https:" && url.indexOf("http://") === 0) { 1960 return cb(new Error("XHRURLHandler: Cannot go from HTTPS to HTTP.")); 1961 } 1962 1963 try { 1964 var request = xhr(); 1965 1966 request.open("GET", url); 1967 request.timeout = options.timeout || 0; 1968 request.withCredentials = options.withCredentials || false; 1969 request.overrideMimeType && request.overrideMimeType("text/xml"); 1970 request.onreadystatechange = function () { 1971 if (request.readyState === 4) { 1972 if (request.status === 200) { 1973 cb(null, request.responseXML); 1974 } else { 1975 cb(new Error("XHRURLHandler: " + request.statusText)); 1976 } 1977 } 1978 }; 1979 request.send(); 1980 } catch (error) { 1981 cb(new Error("XHRURLHandler: Unexpected error")); 1982 } 1983 } 1984 1985 var XHRURLHandler = { 1986 get: get$3, 1987 supported: supported$1, 1988 }; 1989 1990 function get$4(url, options, cb) { 1991 // Allow skip of the options param 1992 if (!cb) { 1993 if (typeof options === "function") { 1994 cb = options; 1995 } 1996 options = {}; 1997 } 1998 1999 if (typeof window === "undefined" || window === null) { 2000 return nodeURLHandler.get(url, options, cb); 2001 } else if (XHRURLHandler.supported()) { 2002 return XHRURLHandler.get(url, options, cb); 2003 } else if (flashURLHandler.supported()) { 2004 return flashURLHandler.get(url, options, cb); 2005 } 2006 return cb( 2007 new Error( 2008 "Current context is not supported by any of the default URLHandlers. Please provide a custom URLHandler" 2009 ) 2010 ); 2011 } 2012 2013 var urlHandler = { 2014 get: get$4, 2015 }; 2016 2017 var VASTResponse = function VASTResponse() { 2018 classCallCheck(this, VASTResponse); 2019 2020 this.ads = []; 2021 this.errorURLTemplates = []; 2022 this.version = null; 2023 }; 2024 2025 var DEFAULT_MAX_WRAPPER_DEPTH = 10; 2026 var DEFAULT_EVENT_DATA = { 2027 ERRORCODE: 900, 2028 extensions: [], 2029 }; 2030 2031 /** 2032 * This class provides methods to fetch and parse a VAST document. 2033 * @export 2034 * @class VASTParser 2035 * @extends EventEmitter 2036 */ 2037 var VASTParser = (function (_EventEmitter) { 2038 inherits(VASTParser, _EventEmitter); 2039 2040 /** 2041 * Creates an instance of VASTParser. 2042 * @constructor 2043 */ 2044 function VASTParser() { 2045 classCallCheck(this, VASTParser); 2046 2047 var _this = possibleConstructorReturn( 2048 this, 2049 (VASTParser.__proto__ || Object.getPrototypeOf(VASTParser)).call(this) 2050 ); 2051 2052 _this.remainingAds = []; 2053 _this.parentURLs = []; 2054 _this.errorURLTemplates = []; 2055 _this.rootErrorURLTemplates = []; 2056 _this.maxWrapperDepth = null; 2057 _this.URLTemplateFilters = []; 2058 _this.fetchingOptions = {}; 2059 return _this; 2060 } 2061 2062 /** 2063 * Adds a filter function to the array of filters which are called before fetching a VAST document. 2064 * @param {function} filter - The filter function to be added at the end of the array. 2065 * @return {void} 2066 */ 2067 2068 createClass(VASTParser, [ 2069 { 2070 key: "addURLTemplateFilter", 2071 value: function addURLTemplateFilter(filter) { 2072 if (typeof filter === "function") { 2073 this.URLTemplateFilters.push(filter); 2074 } 2075 }, 2076 2077 /** 2078 * Removes the last element of the url templates filters array. 2079 * @return {void} 2080 */ 2081 }, 2082 { 2083 key: "removeURLTemplateFilter", 2084 value: function removeURLTemplateFilter() { 2085 this.URLTemplateFilters.pop(); 2086 }, 2087 2088 /** 2089 * Returns the number of filters of the url templates filters array. 2090 * @return {Number} 2091 */ 2092 }, 2093 { 2094 key: "countURLTemplateFilters", 2095 value: function countURLTemplateFilters() { 2096 return this.URLTemplateFilters.length; 2097 }, 2098 2099 /** 2100 * Removes all the filter functions from the url templates filters array. 2101 * @return {void} 2102 */ 2103 }, 2104 { 2105 key: "clearURLTemplateFilters", 2106 value: function clearURLTemplateFilters() { 2107 this.URLTemplateFilters = []; 2108 }, 2109 2110 /** 2111 * Tracks the error provided in the errorCode parameter and emits a VAST-error event for the given error. 2112 * @param {Array} urlTemplates - An Array of url templates to use to make the tracking call. 2113 * @param {Object} errorCode - An Object containing the error data. 2114 * @param {Object} data - One (or more) Object containing additional data. 2115 * @emits VASTParser#VAST-error 2116 * @return {void} 2117 */ 2118 }, 2119 { 2120 key: "trackVastError", 2121 value: function trackVastError(urlTemplates, errorCode) { 2122 for ( 2123 var _len = arguments.length, 2124 data = Array(_len > 2 ? _len - 2 : 0), 2125 _key = 2; 2126 _key < _len; 2127 _key++ 2128 ) { 2129 data[_key - 2] = arguments[_key]; 2130 } 2131
2132 this.emit( 2133 "VAST-error", 2134 Object.assign.apply( 2135 Object, 2136 [{}, DEFAULT_EVENT_DATA, errorCode].concat(data) 2137 ) 2138 ); 2139 util.track(urlTemplates, errorCode); 2140 }, 2141 2142 /** 2143 * Returns an array of errorURLTemplates for the VAST being parsed. 2144 * @return {Array} 2145 */ 2146 }, 2147 { 2148 key: "getErrorURLTemplates", 2149 value: function getErrorURLTemplates() { 2150 return this.rootErrorURLTemplates.concat(this.errorURLTemplates); 2151 }, 2152 2153 /** 2154 * Fetches a VAST document for the given url. 2155 * Returns a Promise which resolves,rejects according to the result of the request. 2156 * @param {String} url - The url to request the VAST document. 2157 * @param {Number} wrapperDepth - how many times the current url has been wrapped 2158 * @param {String} originalUrl - url of original wrapper 2159 * @emits VASTParser#VAST-resolving 2160 * @emits VASTParser#VAST-resolved 2161 * @return {Promise} 2162 */ 2163 }, 2164 { 2165 key: "fetchVAST", 2166 value: function fetchVAST(url, wrapperDepth, originalUrl) { 2167 var _this2 = this; 2168 2169 return new Promise(function (resolve, reject) { 2170 // Process url with defined filter 2171 _this2.URLTemplateFilters.forEach(function (filter) { 2172 url = filter(url); 2173 }); 2174 2175 _this2.parentURLs.push(url); 2176 _this2.emit("VAST-resolving", { 2177 url: url, 2178 wrapperDepth: wrapperDepth, 2179 originalUrl: originalUrl, 2180 }); 2181 2182 _this2.urlHandler.get(url, _this2.fetchingOptions, function ( 2183 err, 2184 xml 2185 ) { 2186 _this2.emit("VAST-resolved", { url: url, error: err }); 2187 2188 if (err) { 2189 reject(err); 2190 } else { 2191 resolve(xml); 2192 } 2193 }); 2194 }); 2195 }, 2196 2197 /** 2198 * Inits the parsing properties of the class with the custom values provided as options. 2199 * @param {Object} options - The options to initialize a parsing sequence 2200 */ 2201 }, 2202 { 2203 key: "initParsingStatus", 2204 value: function initParsingStatus() { 2205 var options = 2206 arguments.length > 0 && arguments[0] !== undefined 2207 ? arguments[0] 2208 : {}; 2209 2210 this.rootURL = ""; 2211 this.remainingAds = []; 2212 this.parentURLs = []; 2213 this.errorURLTemplates = []; 2214 this.rootErrorURLTemplates = []; 2215 this.maxWrapperDepth = 2216 options.wrapperLimit || DEFAULT_MAX_WRAPPER_DEPTH; 2217 this.fetchingOptions = { 2218 timeout: options.timeout, 2219 withCredentials: options.withCredentials, 2220 }; 2221 2222 this.urlHandler = 2223 options.urlHandler || options.urlhandler || urlHandler; 2224 this.vastVersion = null; 2225 }, 2226 2227 /** 2228 * Resolves the next group of ads. If all is true resolves all the remaining ads. 2229 * @param {Boolean} all - If true all the remaining ads are resolved 2230 * @return {Promise} 2231 */ 2232 }, 2233 { 2234 key: "getRemainingAds", 2235 value: function getRemainingAds(all) { 2236 var _this3 = this; 2237 2238 if (this.remainingAds.length === 0) { 2239 return Promise.reject( 2240 new Error("No more ads are available for the given VAST") 2241 ); 2242 } 2243 2244 var ads = all 2245 ? util.flatten(this.remainingAds) 2246 : this.remainingAds.shift(); 2247 this.errorURLTemplates = []; 2248 this.parentURLs = []; 2249 2250 return this.resolveAds(ads, { 2251 wrapperDepth: 0, 2252 originalUrl: this.rootURL, 2253 }).then(function (resolvedAds) { 2254 return _this3.buildVASTResponse(resolvedAds); 2255 }); 2256 }, 2257 2258 /** 2259 * Fetches and parses a VAST for the given url. 2260 * Returns a Promise which resolves with a fully parsed VASTResponse or rejects with an Error. 2261 * @param {String} url - The url to request the VAST document. 2262 * @param {Object} options - An optional Object of parameters to be used in the parsing process. 2263 * @emits VASTParser#VAST-resolving 2264 * @emits VASTParser#VAST-resolved 2265 * @return {Promise} 2266 */ 2267 }, 2268 { 2269 key: "getAndParseVAST", 2270 value: function getAndParseVAST(url) { 2271 var _this4 = this; 2272 2273 var options = 2274 arguments.length > 1 && arguments[1] !== undefined 2275 ? arguments[1] 2276 : {}; 2277 2278 this.initParsingStatus(options); 2279 this.rootURL = url; 2280 2281 return this.fetchVAST(url).then(function (xml) { 2282 options.originalUrl = url; 2283 options.isRootVAST = true; 2284 2285 return _this4.parse(xml, options).then(function (ads) { 2286 return _this4.buildVASTResponse(ads); 2287 }); 2288 }); 2289 }, 2290 2291 /** 2292 * Parses the given xml Object into a VASTResponse. 2293 * Returns a Promise which resolves with a fully parsed VASTResponse or rejects with an Error. 2294 * @param {Object} vastXml - An object representing a vast xml document. 2295 * @param {Object} options - An optional Object of parameters to be used in the parsing process. 2296 * @emits VASTParser#VAST-resolving 2297 * @emits VASTParser#VAST-resolved 2298 * @return {Promise} 2299 */ 2300 }, 2301 { 2302 key: "parseVAST", 2303 value: function parseVAST(vastXml) { 2304 var _this5 = this; 2305 2306 var options = 2307 arguments.length > 1 && arguments[1] !== undefined 2308 ? arguments[1] 2309 : {}; 2310 2311 this.initParsingStatus(options); 2312 2313 options.isRootVAST = true; 2314 2315 return this.parse(vastXml, options).then(function (ads) { 2316 return _this5.buildVASTResponse(ads); 2317 }); 2318 }, 2319 2320 /** 2321 * Builds a VASTResponse which can be returned. 2322 * @param {Array} ads - An Array of unwrapped ads 2323 * @return {VASTResponse} 2324 */ 2325 }, 2326 { 2327 key: "buildVASTResponse", 2328 value: function buildVASTResponse(ads) { 2329 var response = new VASTResponse(); 2330 response.ads = ads; 2331 response.errorURLTemplates = this.getErrorURLTemplates(); 2332 response.version = this.vastVersion; 2333 this.completeWrapperResolving(response); 2334 2335 return response; 2336 }, 2337 2338 /** 2339 * Parses the given xml Object into an array of ads 2340 * Returns the array or throws an `Error` if an invali
2340d VAST XML is provided 2341 * @param {Object} vastXml - An object representing an xml document. 2342 * @param {Object} options - An optional Object of parameters to be used in the parsing process. 2343 * @return {Array} 2344 * @throws {Error} `vastXml` must be a valid VAST XMLDocument 2345 */ 2346 }, 2347 { 2348 key: "parseVastXml", 2349 value: function parseVastXml(vastXml, _ref) { 2350 var _ref$isRootVAST = _ref.isRootVAST, 2351 isRootVAST = 2352 _ref$isRootVAST === undefined ? false : _ref$isRootVAST; 2353 2354 // check if is a valid VAST document 2355 if ( 2356 !vastXml || 2357 !vastXml.documentElement || 2358 vastXml.documentElement.nodeName !== "VAST" 2359 ) { 2360 throw new Error("Invalid VAST XMLDocument"); 2361 } 2362 2363 var ads = []; 2364 var childNodes = vastXml.documentElement.childNodes; 2365 2366 /* Only parse the version of the Root VAST for now because we don't know yet how to 2367 handle some cases like multiple wrappers in the same vast 2368 */ 2369 if (isRootVAST) { 2370 var vastVersion = vastXml.documentElement.getAttribute("version"); 2371 if (vastVersion) this.vastVersion = vastVersion; 2372 } 2373 2374 // Fill the VASTResponse object with ads and errorURLTemplates 2375 for (var nodeKey in childNodes) { 2376 var node = childNodes[nodeKey]; 2377 2378 if (node.nodeName === "Error") { 2379 var errorURLTemplate = parserUtils.parseNodeText(node); 2380 2381 // Distinguish root VAST url templates from ad specific ones 2382 isRootVAST 2383 ? this.rootErrorURLTemplates.push(errorURLTemplate) 2384 : this.errorURLTemplates.push(errorURLTemplate); 2385 } 2386 2387 if (node.nodeName === "Ad") { 2388 var ad = parseAd(node); 2389 2390 if (ad) { 2391 ads.push(ad); 2392 } else { 2393 // VAST version of response not supported. 2394 this.trackVastError(this.getErrorURLTemplates(), { 2395 ERRORCODE: 101, 2396 }); 2397 } 2398 } 2399 } 2400 2401 return ads; 2402 }, 2403 2404 /** 2405 * Parses the given xml Object into an array of unwrapped ads. 2406 * Returns a Promise which resolves with the array or rejects with an error according to the result of the parsing. 2407 * @param {Object} vastXml - An object representing an xml document. 2408 * @param {Object} options - An optional Object of parameters to be used in the parsing process. 2409 * @emits VASTParser#VAST-resolving 2410 * @emits VASTParser#VAST-resolved 2411 * @return {Promise} 2412 */ 2413 }, 2414 { 2415 key: "parse", 2416 value: function parse(vastXml, _ref2) { 2417 var _ref2$resolveAll = _ref2.resolveAll, 2418 resolveAll = 2419 _ref2$resolveAll === undefined ? true : _ref2$resolveAll, 2420 _ref2$wrapperSequence = _ref2.wrapperSequence, 2421 wrapperSequence = 2422 _ref2$wrapperSequence === undefined 2423 ? null 2424 : _ref2$wrapperSequence, 2425 _ref2$originalUrl = _ref2.originalUrl, 2426 originalUrl = 2427 _ref2$originalUrl === undefined ? null : _ref2$originalUrl, 2428 _ref2$wrapperDepth = _ref2.wrapperDepth, 2429 wrapperDepth = 2430 _ref2$wrapperDepth === undefined ? 0 : _ref2$wrapperDepth, 2431 _ref2$isRootVAST = _ref2.isRootVAST, 2432 isRootVAST = 2433 _ref2$isRootVAST === undefined ? false : _ref2$isRootVAST; 2434 2435 var ads = []; 2436 try { 2437 ads = this.parseVastXml(vastXml, { isRootVAST: isRootVAST }); 2438 } catch (e) { 2439 return Promise.reject(e); 2440 } 2441 2442 var adsCount = ads.length; 2443 var lastAddedAd = ads[adsCount - 1]; 2444 // if in child nodes we have only one ads 2445 // and wrapperSequence is defined 2446 // and this ads doesn't already have sequence 2447 if ( 2448 adsCount === 1 && 2449 wrapperSequence !== undefined && 2450 wrapperSequence !== null && 2451 lastAddedAd && 2452 !lastAddedAd.sequence 2453 ) { 2454 lastAddedAd.sequence = wrapperSequence; 2455 } 2456 2457 // Split the VAST in case we don't want to resolve everything at the first time 2458 if (resolveAll === false) { 2459 this.remainingAds = parserUtils.splitVAST(ads); 2460 // Remove the first element from the remaining ads array, since we're going to resolve that element 2461 ads = this.remainingAds.shift(); 2462 } 2463 2464 return this.resolveAds(ads, { 2465 wrapperDepth: wrapperDepth, 2466 originalUrl: originalUrl, 2467 }); 2468 }, 2469 2470 /** 2471 * Resolves an Array of ads, recursively calling itself with the remaining ads if a no ad 2472 * response is returned for the given array. 2473 * @param {Array} ads - An array of ads to resolve 2474 * @param {Object} options - An options Object containing resolving parameters 2475 * @return {Promise} 2476 */ 2477 }, 2478 { 2479 key: "resolveAds", 2480 value: function resolveAds() { 2481 var _this6 = this; 2482 2483 var ads = 2484 arguments.length > 0 && arguments[0] !== undefined 2485 ? arguments[0] 2486 : []; 2487 var _ref3 = arguments[1]; 2488 var wrapperDepth = _ref3.wrapperDepth, 2489 originalUrl = _ref3.originalUrl; 2490 2491 var resolveWrappersPromises = []; 2492 2493 ads.forEach(function (ad) { 2494 var resolveWrappersPromise = _this6.resolveWrappers( 2495 ad, 2496 wrapperDepth, 2497 originalUrl 2498 ); 2499 2500 resolveWrappersPromises.push(resolveWrappersPromise); 2501 }); 2502 2503 return Promise.all(resolveWrappersPromises).then(function ( 2504 unwrappedAds 2505 ) { 2506 var resolvedAds = util.flatten(unwrappedAds); 2507 2508 if (!resolvedAds && _this6.remainingAds.length > 0) { 2509 var remainingAdsToResolve = _this6.remainingAds.shift(); 2510 2511 return _this6.resolveAds(remainingAdsToResolve, { 2512 wrapperDepth: wrapperDepth, 2513 originalUrl: originalUrl, 2514 }); 2515 } 2516 2517 return resolvedAds; 2518 }); 2519 }, 2520 2521 /** 2522 * Resolves the wrappers for the given ad in a recursive way. 2523 * Returns a Promise which resolves with the unwrapped ad or rejects with an error. 2524 * @param {Ad} ad - An ad to be unwrapped. 2525 * @param {Number} wrapperDepth - The reached depth in the wrapper resolving chain. 2526 * @param {String} originalUrl - The original vast url. 2527 * @return {Promise} 2528 */ 2529 }, 2530 { 2531 key: "resolveWrappers", 2532 value: function resolveWrappers(ad, wrapperDepth, originalUrl) { 2533 var _this7 = this; 2534 2535 return new Promise(function (resolve) { 2536 // Going one level deeper in the wrapper chain 2537 wrapperDepth++; 2538 // We already have a resolved VAST ad, no need to resolve wrapper 2539 if (!ad.nextWrapperURL) { 2540 delete ad.nextWrapperURL; 2541 return resolve(ad); 2542 } 2543 2544 if ( 2545 wrapperDepth >= _this7.maxWrapperDepth || 2546 _this7.parentURLs.indexOf(ad.nextWrapperURL) !== -1 2547 ) { 2548 // Wrapper limit reached, as defined by the video player. 2549 // Too many Wrapper responses have been received with no InLine response. 2550 ad.errorCode = 302; 2551 delete ad.nextWrapperURL; 2552 return resolve(ad); 2553 } 2554 2555 // Get full URL 2556 ad.nextWrapperURL = parserUtils.resolveVastAdTagURI( 2557 ad.nextWrapperURL, 2558 originalUrl 2559 ); 2560 2561 // sequence doesn't carry over in wrapper element 2562 var wrapperSequence = ad.sequence; 2563 originalUrl = ad.nextWrapperURL; 2564 2565 _this7 2566 .fetchVAST(ad.nextWrapperURL, wrapperDepth, originalUrl) 2567 .then(function (xml) { 2568 return _this7 2569 .parse(xml, { 2570 originalUrl: originalUrl, 2571 wrapperSequence: wrapperSequence, 2572 wrapperDepth: wrapperDepth, 2573 }) 2574 .then(function (unwrappedAds) { 2575 delete ad.nextWrapperURL; 2576 if (unwrappedAds.length === 0) { 2577 // No ads returned by the wrappedResponse, discard current <Ad><Wrapper> creatives 2578 ad.creatives = []; 2579 return resolve(ad); 2580 } 2581 2582 unwrappedAds.forEach(function (unwrappedAd) { 2583 if (unwrappedAd) { 2584 parserUtils.mergeWrapperAdData(unwrappedAd, ad); 2585 } 2586 }); 2587 2588 resolve(unwrappedAds); 2589 }); 2590 }) 2591 .catch(function (err) {
2592 // Timeout of VAST URI provided in Wrapper element, or of VAST URI provided in a subsequent Wrapper element. 2593 // (URI was either unavailable or reached a timeout as defined by the video player.) 2594 ad.errorCode = 301; 2595 ad.errorMessage = err.message; 2596 2597 resolve(ad); 2598 }); 2599 }); 2600 }, 2601 2602 /** 2603 * Takes care of handling errors when the wrappers are resolved. 2604 * @param {VASTResponse} vastResponse - A resolved VASTResponse. 2605 */ 2606 }, 2607 { 2608 key: "completeWrapperResolving", 2609 value: function completeWrapperResolving(vastResponse) { 2610 // We've to wait for all <Ad> elements to be parsed before handling error so we can: 2611 // - Send computed extensions data 2612 // - Ping all <Error> URIs defined across VAST files 2613 2614 // No Ad case - The parser never bump into an <Ad> element 2615 if (vastResponse.ads.length === 0) { 2616 this.trackVastError(vastResponse.errorURLTemplates, { 2617 ERRORCODE: 303, 2618 }); 2619 } else { 2620 for (var index = vastResponse.ads.length - 1; index >= 0; index--) { 2621 // - Error encountred while parsing 2622 // - No Creative case - The parser has dealt with soma <Ad><Wrapper> or/and an <Ad><Inline> elements 2623 // but no creative was found 2624 var ad = vastResponse.ads[index]; 2625 if (ad.errorCode || ad.creatives.length === 0) { 2626 this.trackVastError( 2627 ad.errorURLTemplates.concat(vastResponse.errorURLTemplates), 2628 { ERRORCODE: ad.errorCode || 303 }, 2629 { ERRORMESSAGE: ad.errorMessage || "" }, 2630 { extensions: ad.extensions }, 2631 { system: ad.system } 2632 ); 2633 vastResponse.ads.splice(index, 1); 2634 } 2635 } 2636 } 2637 }, 2638 }, 2639 ]); 2640 return VASTParser; 2641 })(EventEmitter); 2642 2643 var storage = null; 2644 2645 /** 2646 * This Object represents a default storage to be used in case no other storage is available. 2647 * @constant 2648 * @type {Object} 2649 */ 2650 var DEFAULT_STORAGE = { 2651 data: {}, 2652 length: 0, 2653 getItem: function getItem(key) { 2654 return this.data[key]; 2655 }, 2656 setItem: function setItem(key, value) { 2657 this.data[key] = value; 2658 this.length = Object.keys(this.data).length; 2659 }, 2660 removeItem: function removeItem(key) { 2661 delete this.data[key]; 2662 this.length = Object.keys(this.data).length; 2663 }, 2664 clear: function clear() { 2665 this.data = {}; 2666 this.length = 0; 2667 }, 2668 }; 2669 2670 /** 2671 * This class provides an wrapper interface to the a key-value storage. 2672 * It uses localStorage, sessionStorage or a custom storage if none of the two is available. 2673 * @export 2674 * @class Storage 2675 */ 2676 var Storage = (function () { 2677 /** 2678 * Creates an instance of Storage. 2679 * @constructor 2680 */ 2681 function Storage() { 2682 classCallCheck(this, Storage); 2683 2684 this.storage = this.initStorage(); 2685 } 2686 2687 /** 2688 * Provides a singleton instance of the wrapped storage. 2689 * @return {Object} 2690 */ 2691 2692 createClass(Storage, [ 2693 { 2694 key: "initStorage", 2695 value: function initStorage() { 2696 if (storage) { 2697 return storage; 2698 } 2699 2700 try { 2701 storage = 2702 typeof window !== "undefined" && window !== null 2703 ? window.localStorage || window.sessionStorage 2704 : null; 2705 } catch (storageError) { 2706 storage = null; 2707 } 2708 2709 if (!storage || this.isStorageDisabled(storage)) { 2710 storage = DEFAULT_STORAGE; 2711 storage.clear(); 2712 } 2713 2714 return storage; 2715 }, 2716 2717 /** 2718 * Check if storage is disabled (like in certain cases with private browsing). 2719 * In Safari (Mac + iOS) when private browsing is ON, localStorage is read only 2720 * http://spin.atomicobject.com/2013/01/23/ios-private-browsing-localstorage/ 2721 * @param {Object} testStorage - The storage to check. 2722 * @return {Boolean} 2723 */ 2724 }, 2725 { 2726 key: "isStorageDisabled", 2727 value: function isStorageDisabled(testStorage) { 2728 var testValue = "__VASTStorage__"; 2729 2730 try { 2731 testStorage.setItem(testValue, testValue); 2732 if (testStorage.getItem(testValue) !== testValue) { 2733 testStorage.removeItem(testValue); 2734 return true; 2735 } 2736 } catch (e) { 2737 return true; 2738 } 2739 2740 testStorage.removeItem(testValue); 2741 return false;
2742 }, 2743 2744 /** 2745 * Returns the value for the given key. If the key does not exist, null is returned. 2746 * @param {String} key - The key to retrieve the value. 2747 * @return {any} 2748 */ 2749 }, 2750 { 2751 key: "getItem", 2752 value: function getItem(key) { 2753 return this.storage.getItem(key); 2754 }, 2755 2756 /** 2757 * Adds or updates the value for the given key. 2758 * @param {String} key - The key to modify the value. 2759 * @param {any} value - The value to be associated with the key. 2760 * @return {any} 2761 */ 2762 }, 2763 { 2764 key: "setItem", 2765 value: function setItem(key, value) { 2766 return this.storage.setItem(key, value); 2767 }, 2768 2769 /** 2770 * Removes an item for the given key. 2771 * @param {String} key - The key to remove the value. 2772 * @return {any} 2773 */ 2774 }, 2775 { 2776 key: "removeItem", 2777 value: function removeItem(key) { 2778 return this.storage.removeItem(key); 2779 }, 2780 2781 /** 2782 * Removes all the items from the storage. 2783 */ 2784 }, 2785 { 2786 key: "clear", 2787 value: function clear() { 2788 return this.storage.clear(); 2789 }, 2790 }, 2791 ]); 2792 return Storage; 2793 })(); 2794 2795 /** 2796 * This class provides methods to fetch and parse a VAST document using VASTParser. 2797 * In addition it provides options to skip consecutive calls based on constraints. 2798 * @export 2799 * @class VASTClient 2800 */ 2801 var VASTClient = (function () { 2802 /** 2803 * Creates an instance of VASTClient. 2804 * @param {Number} cappingFreeLunch - The number of first calls to skip. 2805 * @param {Number} cappingMinimumTimeInterval - The minimum time interval between two consecutive calls. 2806 * @param {Storage} customStorage - A custom storage to use instead of the default one. 2807 * @constructor 2808 */ 2809 function VASTClient( 2810 cappingFreeLunch, 2811 cappingMinimumTimeInterval, 2812 customStorage 2813 ) { 2814 classCallCheck(this, VASTClient); 2815 2816 this.cappingFreeLunch = cappingFreeLunch || 0; 2817 this.cappingMinimumTimeInterval = cappingMinimumTimeInterval || 0; 2818 this.defaultOptions = { 2819 withCredentials: false, 2820 timeout: 0, 2821 }; 2822 this.vastParser = new VASTParser(); 2823 this.storage = customStorage || new Storage(); 2824 2825 // Init values if not already set 2826 if (this.lastSuccessfulAd === undefined) { 2827 this.lastSuccessfulAd = 0; 2828 } 2829 2830 if (this.totalCalls === undefined) { 2831 this.totalCalls = 0; 2832 } 2833 if (this.totalCallsTimeout === undefined) { 2834 this.totalCallsTimeout = 0; 2835 } 2836 } 2837 2838 createClass(VASTClient, [ 2839 { 2840 key: "getParser", 2841 value: function getParser() { 2842 return this.vastParser; 2843 }, 2844 }, 2845 { 2846 key: "hasRemainingAds", 2847 2848 /** 2849 * Returns a boolean indicating if there are more ads to resolve for the current parsing. 2850 * @return {Boolean} 2851 */ 2852 value: function hasRemainingAds() { 2853 return this.vastParser.remainingAds.length > 0; 2854 }, 2855 2856 /** 2857 * Resolves the next group of ads. If all is true resolves all the remaining ads. 2858 * @param {Boolean} all - If true all the remaining ads are resolved 2859 * @return {Promise} 2860 */ 2861 }, 2862 { 2863 key: "getNextAds", 2864 value: function getNextAds(all) { 2865 return this.vastParser.getRemainingAds(all); 2866 }, 2867 2868 /** 2869 * Gets a parsed VAST document for the given url, applying the skipping rules defined. 2870 * Returns a Promise which resolves with a fully parsed VASTResponse or rejects with an Error. 2871 * @param {String} url - The url to use to fecth the VAST document. 2872 * @param {Object} options - An optional Object of parameters to be applied in the process. 2873 * @return {Promise} 2874 */ 2875 }, 2876 { 2877 key: "get", 2878 value: function get$$1(url) { 2879 var _this = this; 2880 2881 var options = 2882 arguments.length > 1 && arguments[1] !== undefined 2883 ? arguments[1] 2884 : {}; 2885 2886 var now = Date.now(); 2887 options = Object.assign({}, this.defaultOptions, options); 2888 2889 // By default the client resolves only the first Ad or AdPod 2890 if (!options.hasOwnProperty("resolveAll")) { 2891 options.resolveAll = false;
2892 } 2893 2894 // Check totalCallsTimeout (first call + 1 hour), if older than now, 2895 // reset totalCalls number, by this way the client will be eligible again 2896 // for freelunch capping 2897 if (this.totalCallsTimeout < now) { 2898 this.totalCalls = 1; 2899 this.totalCallsTimeout = now + 60 * 60 * 1000; 2900 } else { 2901 this.totalCalls++; 2902 } 2903 2904 return new Promise(function (resolve, reject) { 2905 if (_this.cappingFreeLunch >= _this.totalCalls) { 2906 return reject( 2907 new Error( 2908 "VAST call canceled \u2013 FreeLunch capping not reached yet " + 2909 _this.totalCalls + 2910 "/" + 2911 _this.cappingFreeLunch 2912 ) 2913 ); 2914 } 2915 2916 var timeSinceLastCall = now - _this.lastSuccessfulAd; 2917 2918 // Check timeSinceLastCall to be a positive number. If not, this mean the 2919 // previous was made in the future. We reset lastSuccessfulAd value 2920 if (timeSinceLastCall < 0) { 2921 _this.lastSuccessfulAd = 0; 2922 } else if (timeSinceLastCall < _this.cappingMinimumTimeInterval) { 2923 return reject( 2924 new Error( 2925 "VAST call canceled \u2013 (" + 2926 _this.cappingMinimumTimeInterval + 2927 ")ms minimum interval reached" 2928 ) 2929 ); 2930 } 2931 2932 _this.vastParser 2933 .getAndParseVAST(url, options) 2934 .then(function (response) { 2935 return resolve(response); 2936 }) 2937 .catch(function (err) { 2938 return reject(err); 2939 }); 2940 }); 2941 }, 2942 }, 2943 { 2944 key: "lastSuccessfulAd", 2945 get: function get$$1() { 2946 return this.storage.getItem("vast-client-last-successful-ad"); 2947 }, 2948 set: function set$$1(value) { 2949 this.storage.setItem("vast-client-last-successful-ad", value); 2950 }, 2951 }, 2952 { 2953 key: "totalCalls", 2954 get: function get$$1() { 2955 return this.storage.getItem("vast-client-total-calls"); 2956 }, 2957 set: function set$$1(value) { 2958 this.storage.setItem("vast-client-total-calls", value); 2959 }, 2960 }, 2961 { 2962 key: "totalCallsTimeout", 2963 get: function get$$1() { 2964 return this.storage.getItem("vast-client-total-calls-timeout"); 2965 }, 2966 set: function set$$1(value) { 2967 this.storage.setItem("vast-client-total-calls-timeout", value); 2968 }, 2969 }, 2970 ]); 2971 return VASTClient; 2972 })(); 2973 2974 /** 2975 * The default skip delay used in case a custom one is not provided 2976 * @constant 2977 * @type {Number} 2978 */ 2979 var DEFAULT_SKIP_DELAY = -1; 2980 2981 /** 2982 * This class provides methods to track an ad execution. 2983 * 2984 * @export 2985 * @class VASTTracker 2986 * @extends EventEmitter 2987 */ 2988 var VASTTracker = (function (_EventEmitter) { 2989 inherits(VASTTracker, _EventEmitter); 2990 2991 /** 2992 * Creates an instance of VASTTracker. 2993 * 2994 * @param {VASTClient} client - An instance of VASTClient that can be updated by the tracker. [optional] 2995 * @param {Ad} ad - The ad to track. 2996 * @param {Creative} creative - The creative to track. 2997 * @param {CompanionAd|NonLinearAd} [variation=null] - An optional variation of the creative. 2998 * @constructor 2999 */ 3000 function VASTTracker(client, ad, creative) { 3001 var variation = 3002 arguments.length > 3 && arguments[3] !== undefined 3003 ? arguments[3] 3004 : null; 3005 classCallCheck(this, VASTTracker); 3006 3007 var _this = possibleConstructorReturn( 3008 this, 3009 (VASTTracker.__proto__ || Object.getPrototypeOf(VASTTracker)).call(this) 3010 ); 3011 3012 _this.ad = ad; 3013 _this.creative = creative; 3014 _this.variation = variation; 3015 _this.muted = false;
3016 _this.impressed = false; 3017 _this.skippable = false; 3018 _this.trackingEvents = {}; 3019 // We need to save the already triggered quartiles, in order to not trigger them again 3020 _this._alreadyTriggeredQuartiles = {}; 3021 // Tracker listeners should be notified with some events 3022 // no matter if there is a tracking URL or not 3023 _this.emitAlwaysEvents = [ 3024 "creativeView", 3025 "start", 3026 "firstQuartile", 3027 "midpoint", 3028 "thirdQuartile", 3029 "complete", 3030 "resume", 3031 "pause", 3032 "rewind", 3033 "skip", 3034 "closeLinear", 3035 "close", 3036 ]; 3037 3038 // Duplicate the creative's trackingEvents property so we can alter it 3039 for (var eventName in _this.creative.trackingEvents) { 3040 var events = _this.creative.trackingEvents[eventName]; 3041 _this.trackingEvents[eventName] = events.slice(0); 3042 } 3043 3044 // Nonlinear and companion creatives provide some tracking information at a variation level 3045 // While linear creatives provided that at a creative level. That's why we need to 3046 // differentiate how we retrieve some tracking information. 3047 if (_this.creative instanceof CreativeLinear) { 3048 _this._initLinearTracking(); 3049 } else { 3050 _this._initVariationTracking(); 3051 } 3052 3053 // If the tracker is associated with a client we add a listener to the start event 3054 // to update the lastSuccessfulAd property. 3055 if (client) { 3056 _this.on("start", function () { 3057 client.lastSuccessfulAd = Date.now(); 3058 }); 3059 } 3060 return _this; 3061 } 3062 3063 /** 3064 * Init the custom tracking options for linear creatives. 3065 * 3066 * @return {void} 3067 */ 3068 3069 createClass(VASTTracker, [ 3070 { 3071 key: "_initLinearTracking", 3072 value: function _initLinearTracking() { 3073 this.linear = true; 3074 this.skipDelay = this.creative.skipDelay; 3075 3076 this.setDuration(this.creative.duration); 3077 3078 this.clickThroughURLTemplate = this.creative.videoClickThroughURLTemplate; 3079 this.clickTrackingURLTemplates = this.creative.videoClickTrackingURLTemplates; 3080 }, 3081 3082 /** 3083 * Init the custom tracking options for nonlinear and companion creatives. 3084 * These options are provided in the variation Object. 3085 * 3086 * @return {void} 3087 */ 3088 }, 3089 { 3090 key: "_initVariationTracking", 3091 value: function _initVariationTracking() { 3092 this.linear = false; 3093 this.skipDelay = DEFAULT_SKIP_DELAY; 3094 3095 // If no variation has been provided there's nothing else to set 3096 if (!this.variation) { 3097 return; 3098 } 3099 3100 // Duplicate the variation's trackingEvents property so we can alter it 3101 for (var eventName in this.variation.trackingEvents) { 3102 var events = this.variation.trackingEvents[eventName]; 3103 3104 // If for the given eventName we already had some trackingEvents provided by the creative 3105 // we want to keep both the creative trackingEvents and the variation ones 3106 if (this.trackingEvents[eventName]) { 3107 this.trackingEvents[eventName] = this.trackingEvents[ 3108 eventName 3109 ].concat(events.slice(0)); 3110 } else { 3111 this.trackingEvents[eventName] = events.slice(0); 3112 } 3113 } 3114 3115 if (this.variation instanceof NonLinearAd) { 3116 this.clickThroughURLTemplate = this.variation.nonlinearClickThroughURLTemplate; 3117 this.clickTrackingURLTemplates = this.variation.nonlinearClickTrackingURLTemplates; 3118 this.setDuration(this.variation.minSuggestedDuration); 3119 } else if (this.variation instanceof CompanionAd) { 3120 this.clickThroughURLTemplate = this.variation.companionClickThroughURLTemplate; 3121 this.clickTrackingURLTemplates = this.variation.companionCl
3121ickTrackingURLTemplates; 3122 } 3123 }, 3124 3125 /** 3126 * Sets the duration of the ad and updates the quartiles based on that. 3127 * 3128 * @param {Number} duration - The duration of the ad. 3129 */ 3130 }, 3131 { 3132 key: "setDuration", 3133 value: function setDuration(duration) { 3134 this.assetDuration = duration; 3135 // beware of key names, theses are also used as event names 3136 this.quartiles = { 3137 firstQuartile: Math.round(25 * this.assetDuration) / 100, 3138 midpoint: Math.round(50 * this.assetDuration) / 100, 3139 thirdQuartile: Math.round(75 * this.assetDuration) / 100, 3140 }; 3141 }, 3142 3143 /** 3144 * Sets the duration of the ad and updates the quartiles based on that. 3145 * This is required for tracking time related events. 3146 * 3147 * @param {Number} progress - Current playback time in seconds. 3148 * @emits VASTTracker#start 3149 * @emits VASTTracker#skip-countdown 3150 * @emits VASTTracker#progress-[0-100]% 3151 * @emits VASTTracker#progress-[currentTime] 3152 * @emits VASTTracker#rewind 3153 * @emits VASTTracker#firstQuartile 3154 * @emits VASTTracker#midpoint 3155 * @emits VASTTracker#thirdQuartile 3156 */ 3157 }, 3158 { 3159 key: "setProgress", 3160 value: function setProgress(progress) { 3161 var _this2 = this; 3162 3163 var skipDelay = this.skipDelay || DEFAULT_SKIP_DELAY; 3164 3165 if (skipDelay !== -1 && !this.skippable) { 3166 if (skipDelay > progress) { 3167 this.emit("skip-countdown", skipDelay - progress); 3168 } else { 3169 this.skippable = true; 3170 this.emit("skip-countdown", 0); 3171 } 3172 } 3173 3174 if (this.assetDuration > 0) { 3175 var events = []; 3176 3177 if (progress > 0) { 3178 var percent = Math.round((progress / this.assetDuration) * 100); 3179 3180 events.push("start"); 3181 events.push("progress-" + percent + "%"); 3182 events.push("progress-" + Math.round(progress)); 3183 3184 for (var quartile in this.quartiles) { 3185 if ( 3186 this.isQuartileReached( 3187 quartile, 3188 this.quartiles[quartile], 3189 progress 3190 ) 3191 ) { 3192 events.push(quartile); 3193 this._alreadyTriggeredQuartiles[quartile] = true; 3194 } 3195 } 3196 } 3197 3198 events.forEach(function (eventName) { 3199 _this2.track(eventName, true); 3200 }); 3201 3202 if (progress < this.progress) { 3203 this.track("rewind"); 3204 } 3205 } 3206 3207 this.progress = progress; 3208 }, 3209 3210 /** 3211 * Checks if a quartile has been reached without have being triggered already. 3212 * 3213 * @param {String} quartile - Quartile name 3214 * @param {Number} time - Time offset, when this quartile is reached in seconds. 3215 * @param {Number} progress - Current progress of the ads in seconds. 3216 * 3217 * @return {Boolean} 3218 */ 3219 }, 3220 { 3221 key: "isQuartileReached", 3222 value: function isQuartileReached(quartile, time, progress) { 3223 var quartileReached = false; 3224 // if quartile time already reached and never triggered 3225 if (time <= progress && !this._alreadyTriggeredQuartiles[quartile]) { 3226 quartileReached = true; 3227 } 3228 return quartileReached; 3229 }, 3230 3231 /** 3232 * Updates the mute state and calls the mute/unmute tracking URLs. 3233 * 3234 * @param {Boolean} muted - Indicates if the video is muted or not. 3235 * @emits VASTTracker#mute 3236 * @emits VASTTracker#unmute 3237 */ 3238 }, 3239 { 3240 key: "setMuted", 3241 value: function setMuted(muted) { 3242 if (this.muted !== muted) { 3243 this.track(muted ? "mute" : "unmute"); 3244 } 3245 this.muted = muted; 3246 }, 3247 3248 /** 3249 * Update the pause state and call the resume/pause tracking URLs. 3250 * 3251 * @param {Boolean} paused - Indicates if the video is paused or not. 3252 * @emits VASTTracker#pause 3253 * @emits VASTTracker#resume 3254 */ 3255 }, 3256 { 3257 key: "setPaused", 3258 value: function setPaused(paused) { 3259 if (this.paused !== paused) { 3260 this.track(paused ? "pause" : "resume"); 3261 } 3262 this.paused = paused; 3263 }, 3264 3265 /** 3266 * Updates the fullscreen state and calls the fullscreen tracking URLs. 3267 * 3268 * @param {Boolean} fullscreen - Indicates if the video is in fulscreen mode or not. 3269 * @emits VASTTracker#fullscreen 3270 * @emits VASTTracker#exitFullscreen 3271 */ 3272 }, 3273 { 3274 key: "setFullscreen", 3275 value: function setFullscreen(fullscreen) { 3276 if (this.fullscreen !== fullscreen) { 3277 this.track(fullscreen ? "fullscreen" : "exitFullscreen"); 3278 } 3279 this.fullscreen = fullscreen; 3280 }, 3281 3282 /**
3283 * Updates the expand state and calls the expand/collapse tracking URLs. 3284 * 3285 * @param {Boolean} expanded - Indicates if the video is expanded or not. 3286 * @emits VASTTracker#expand 3287 * @emits VASTTracker#collapse 3288 */ 3289 }, 3290 { 3291 key: "setExpand", 3292 value: function setExpand(expanded) { 3293 if (this.expanded !== expanded) { 3294 this.track(expanded ? "expand" : "collapse"); 3295 } 3296 this.expanded = expanded; 3297 }, 3298 3299 /** 3300 * Must be called if you want to overwrite the <Linear> Skipoffset value. 3301 * This will init the skip countdown duration. Then, every time setProgress() is called, 3302 * it will decrease the countdown and emit a skip-countdown event with the remaining time. 3303 * Do not call this method if you want to keep the original Skipoffset value. 3304 * 3305 * @param {Number} duration - The time in seconds until the skip button is displayed. 3306 */ 3307 }, 3308 { 3309 key: "setSkipDelay", 3310 value: function setSkipDelay(duration) { 3311 if (typeof duration === "number") { 3312 this.skipDelay = duration; 3313 } 3314 }, 3315 3316 /** 3317 * Tracks an impression (can be called only once). 3318 * 3319 * @emits VASTTracker#creativeView 3320 */ 3321 }, 3322 { 3323 key: "trackImpression", 3324 value: function trackImpression() { 3325 if (!this.impressed) { 3326 this.impressed = true; 3327 this.trackURLs(this.ad.impressionURLTemplates); 3328 this.track("creativeView"); 3329 } 3330 }, 3331 3332 /** 3333 * Send a request to the URI provided by the VAST <Error> element. 3334 * If an [ERRORCODE] macro is included, it will be substitute with errorCode. 3335 * 3336 * @param {String} errorCode - Replaces [ERRORCODE] macro. [ERRORCODE] values are listed in the VAST specification. 3337 * @param {Boolean} [isCustomCode=false] - Flag to allow custom values on error code. 3338 */ 3339 }, 3340 { 3341 key: "errorWithCode", 3342 value: function errorWithCode(errorCode) { 3343 var isCustomCode = 3344 arguments.length > 1 && arguments[1] !== undefined 3345 ? arguments[1] 3346 : false; 3347 3348 this.trackURLs( 3349 this.ad.errorURLTemplates, 3350 { ERRORCODE: errorCode }, 3351 { isCustomCode: isCustomCode } 3352 ); 3353 }, 3354 3355 /** 3356 * Must be called when the user watched the linear creative until its end. 3357 * Calls the complete tracking URLs. 3358 * 3359 * @emits VASTTracker#complete 3360 */ 3361 }, 3362 { 3363 key: "complete", 3364 value: function complete() { 3365 this.track("complete"); 3366 }, 3367 3368 /** 3369 * Must be called when the player or the window is closed during the ad. 3370 * Calls the `closeLinear` (in VAST 3.0) and `close` tracking URLs. 3371 * 3372 * @emits VASTTracker#closeLinear 3373 * @emits VASTTracker#close 3374 */ 3375 }, 3376 { 3377 key: "close", 3378 value: function close() { 3379 this.track(this.linear ? "closeLinear" : "close"); 3380 }, 3381 3382 /** 3383 * Must be called when the skip button is clicked. Calls the skip tracking URLs. 3384 * 3385 * @emits VASTTracker#skip 3386 */ 3387 }, 3388 { 3389 key: "skip", 3390 value: function skip() { 3391 this.track("skip"); 3392 }, 3393 3394 /** 3395 * Must be called when the user clicks on the creative. 3396 * It calls the tracking URLs and emits a 'clickthrough' event with the resolved 3397 * clickthrough URL when done. 3398 * 3399 * @param {String} [fallbackClickThroughURL=null] - an optional clickThroughURL template that could be used as a fallback 3400 * @emits VASTTracker#clickthrough 3401 */ 3402 }, 3403 { 3404 key: "click", 3405 value: function click() { 3406 var fallbackClickThroughURL = 3407 arguments.length > 0 && arguments[0] !== undefined 3408 ? arguments[0] 3409 : null; 3410 3411 if ( 3412 this.clickTrackingURLTemplates && 3413 this.clickTrackingURLTemplates.length 3414 ) { 3415 this.trackURLs(this.clickTrackingURLTemplates); 3416 } 3417 3418 // Use the provided fallbackClickThroughURL as a fallback 3419 var clickThroughURLTemplate = 3420 this.clickThroughURLTemplate || fallbackClickThroughURL; 3421 3422 if (clickThroughURLTemplate) { 3423 var variables = this.linear 3424 ? { CONTENTPLAYHEAD: this.progressFormatted() } 3425 : {}; 3426 var clickThroughURL = util.resolveURLTemplates( 3427 [clickThroughURLTemplate], 3428 variables 3429 )[0]; 3430
3431 this.emit("clickthrough", clickThroughURL); 3432 } 3433 }, 3434 3435 /** 3436 * Calls the tracking URLs for the given eventName and emits the event. 3437 * 3438 * @param {String} eventName - The name of the event. 3439 * @param {Boolean} [once=false] - Boolean to define if the event has to be tracked only once. 3440 */ 3441 }, 3442 { 3443 key: "track", 3444 value: function track(eventName) { 3445 var once = 3446 arguments.length > 1 && arguments[1] !== undefined 3447 ? arguments[1] 3448 : false; 3449 3450 // closeLinear event was introduced in VAST 3.0 3451 // Fallback to vast 2.0 close event if necessary 3452 if ( 3453 eventName === "closeLinear" && 3454 !this.trackingEvents[eventName] && 3455 this.trackingEvents["close"] 3456 ) { 3457 eventName = "close"; 3458 } 3459 3460 var trackingURLTemplates = this.trackingEvents[eventName]; 3461 var isAlwaysEmitEvent = this.emitAlwaysEvents.indexOf(eventName) > -1; 3462 3463 if (trackingURLTemplates) { 3464 this.emit(eventName, ""); 3465 this.trackURLs(trackingURLTemplates); 3466 } else if (isAlwaysEmitEvent) { 3467 this.emit(eventName, ""); 3468 } 3469 3470 if (once) { 3471 delete this.trackingEvents[eventName]; 3472 if (isAlwaysEmitEvent) { 3473 this.emitAlwaysEvents.splice( 3474 this.emitAlwaysEvents.indexOf(eventName), 3475 1 3476 ); 3477 } 3478 } 3479 }, 3480 3481 /** 3482 * Calls the tracking urls templates with the given variables. 3483 * 3484 * @param {Array} URLTemplates - An array of tracking url templates. 3485 * @param {Object} [variables={}] - An optional Object of parameters to be used in the tracking calls. 3486 * @param {Object} [options={}] - An optional Object of options to be used in the tracking calls. 3487 */ 3488 }, 3489 { 3490 key: "trackURLs", 3491 value: function trackURLs(URLTemplates) { 3492 var variables = 3493 arguments.length > 1 && arguments[1] !== undefined 3494 ? arguments[1] 3495 : {}; 3496 var options = 3497 arguments.length > 2 && arguments[2] !== undefined 3498 ? arguments[2] 3499 : {}; 3500 3501 if (this.linear) { 3502 if ( 3503 this.creative && 3504 this.creative.mediaFiles && 3505 this.creative.mediaFiles[0] && 3506 this.creative.mediaFiles[0].fileURL 3507 ) { 3508 variables["ASSETURI"] = this.creative.mediaFiles[0].fileURL; 3509 } 3510 variables["CONTENTPLAYHEAD"] = this.progressFormatted(); 3511 } 3512 3513 util.track(URLTemplates, variables, options); 3514 }, 3515 3516 /** 3517 * Formats time progress in a readable string. 3518 * 3519 * @return {String} 3520 */ 3521 }, 3522 { 3523 key: "progressFormatted", 3524 value: function progressFormatted() { 3525 var seconds = parseInt(this.progress); 3526 var h = seconds / (60 * 60); 3527 if (h.length < 2) { 3528 h = "0" + h; 3529 } 3530 var m = (seconds / 60) % 60; 3531 if (m.length < 2) { 3532 m = "0" + m; 3533 } 3534 var s = seconds % 60; 3535 if (s.length < 2) { 3536 s = "0" + m; 3537 } 3538 var ms = parseInt((this.progress - seconds) * 100); 3539 return h + ":" + m + ":" + s + "." + ms; 3540 }, 3541 }, 3542 ]); 3543 return VASTTracker; 3544 })(EventEmitter); 3545 3546 exports.VASTClient = VASTClient; 3547 exports.VASTParser = VASTParser; 3548 exports.VASTTracker = VASTTracker; 3549 3550 return exports; 3551 })({});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.