From c61c0b3327f44b7795005d268a05ce6c37a8cb60 Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Fri, 23 Jun 2023 15:05:06 +0200 Subject: [PATCH 01/95] [tools] modification of the rollup configuration --- rollup.config.js | 145 +++++++++++++++++++++++++++-------------------- 1 file changed, 84 insertions(+), 61 deletions(-) diff --git a/rollup.config.js b/rollup.config.js index eee0f840..dcd8aaf6 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -15,74 +15,97 @@ function onwarn(warning) { } } -function minify(config) { - const minifiedConfig = Object.assign({}, config); - minifiedConfig.output = Object.assign({}, config.output); - minifiedConfig.plugins = Object.assign([], config.plugins); +// Node configuration - const outputFile = minifiedConfig.output.file; - const extensionIndex = outputFile.lastIndexOf('.js'); - minifiedConfig.output.file = - outputFile.substr(0, extensionIndex) + - '.min' + - outputFile.substr(extensionIndex); +function createNodeConfig(filePath, minifiedOutput, notMinifiedOutput) { + let config = { + input: filePath, + output: [ + { + format: 'cjs', + file: `dist/node/${notMinifiedOutput}`, + }, + { + format: 'cjs', + file: `dist/node/min/${minifiedOutput}`, + plugins: [terser()], + }, + ], + plugins: [ + alias({ + entries: [ + { + find: './urlhandlers/mock_node_url_handler', + replacement: './urlhandlers/node_url_handler', + }, + ], + }), + resolve({ + preferBuiltins: true, + }), + babelPlugin, + ], + onwarn, + }; - minifiedConfig.plugins.push(terser()); - - return minifiedConfig; + return config; } -const browserConfig = { - input: 'src/index.js', - output: { - name: 'VAST', - format: 'umd', - file: 'dist/vast-client.js', - }, - plugins: [babelPlugin], -}; +// Browser configuration -const browserScriptConfig = { - input: 'src/index.js', - output: { - name: 'VAST', - format: 'iife', - file: 'dist/vast-client-browser.js', - }, - plugins: [babelPlugin], -}; +function createBrowserConfig(filePath, minifiedOutput, notMinifiedOutput) { + let config = { + input: filePath, + output: [ + { + format: 'es', + file: `dist/${notMinifiedOutput}`, + }, + { + format: 'es', + file: `dist/min/${minifiedOutput}`, + plugins: [terser()], + }, + ], + plugins: [babelPlugin], + }; -const nodeConfig = { - input: 'src/index.js', - output: { - format: 'cjs', - file: 'dist/vast-client-node.js', - }, - plugins: [ - alias({ - entries: [ - { - find: './urlhandlers/mock_node_url_handler', - replacement: './urlhandlers/node_url_handler', - }, - ], - }), - resolve({ - preferBuiltins: true, - }), - babelPlugin, - ], - onwarn, -}; + return config; +} export default [ - // Browser-friendly UMD build [package.json "browser"] - browserConfig, - minify(browserConfig), - + // Browser-friendly build [package.json "browser"] + createBrowserConfig( + 'src/parser/vast_parser.js', + 'vast-parser.min.js', + 'vast-parser.js' + ), + createBrowserConfig( + 'src/vast_tracker.js', + 'vast-tracker.min.js', + 'vast-tracker.js' + ), + createBrowserConfig('src/index.js', 'vast-client.min.js', 'vast-client.js'), + createBrowserConfig('src/vast_client.js', 'client.min.js', 'client.js'), // CommonJS build for Node usage [package.json "main"] - nodeConfig, - minify(nodeConfig), - - minify(browserScriptConfig), + createNodeConfig( + 'src/index.js', + 'vast-client-node.min.js', + 'vast-client-node.js' + ), + createNodeConfig( + 'src/parser/vast_parser.js', + 'vast-parser-node.min.js', + 'vast-parser-node.min.js' + ), + createNodeConfig( + 'src/vast_tracker.js', + 'vast-tracker-node.min.js', + 'vast-tracker-node.js' + ), + createNodeConfig( + 'src/vast_client.js', + 'client-node.min.js', + 'client-node.js' + ), ]; From b2f40c49967a55a975a5704f8c701187870318f2 Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Fri, 23 Jun 2023 15:16:58 +0200 Subject: [PATCH 02/95] [parser] Removing fectchVAST and getAndParseVAST methods from the vast_parser --- src/parser/vast_parser.js | 228 ++++++++++++++------------------------ 1 file changed, 82 insertions(+), 146 deletions(-) diff --git a/src/parser/vast_parser.js b/src/parser/vast_parser.js index 8dbf490e..c0fe25e1 100644 --- a/src/parser/vast_parser.js +++ b/src/parser/vast_parser.js @@ -1,10 +1,8 @@ import { parseAd } from './ad_parser'; import { EventEmitter } from '../util/event_emitter'; import { parserUtils } from './parser_utils'; -import { urlHandler } from '../url_handler'; import { util } from '../util/util'; import { createVASTResponse } from '../vast_response'; -import { DEFAULT_TIMEOUT } from '../urlhandlers/consts'; import { updateEstimatedBitrate, estimatedBitrate } from './bitrate'; const DEFAULT_MAX_WRAPPER_DEPTH = 10; @@ -26,50 +24,46 @@ export class VASTParser extends EventEmitter { */ constructor() { super(); - - this.remainingAds = []; - this.errorURLTemplates = []; - this.rootErrorURLTemplates = []; this.maxWrapperDepth = null; - this.URLTemplateFilters = []; - this.fetchingOptions = {}; - this.parsingOptions = {}; + // this.URLTemplateFilters = []; + // this.parsingOptions = {}; + this.fetchingMethod = null; } - /** - * Adds a filter function to the array of filters which are called before fetching a VAST document. - * @param {function} filter - The filter function to be added at the end of the array. - * @return {void} - */ - addURLTemplateFilter(filter) { - if (typeof filter === 'function') { - this.URLTemplateFilters.push(filter); - } - } - - /** - * Removes the last element of the url templates filters array. - * @return {void} - */ - removeURLTemplateFilter() { - this.URLTemplateFilters.pop(); - } - - /** - * Returns the number of filters of the url templates filters array. - * @return {Number} - */ - countURLTemplateFilters() { - return this.URLTemplateFilters.length; - } - - /** - * Removes all the filter functions from the url templates filters array. - * @return {void} - */ - clearURLTemplateFilters() { - this.URLTemplateFilters = []; - } + // /** + // * Adds a filter function to the array of filters which are called before fetching a VAST document. + // * @param {function} filter - The filter function to be added at the end of the array. + // * @return {void} + // */ + // addURLTemplateFilter(filter) { + // if (typeof filter === 'function') { + // this.URLTemplateFilters.push(filter); + // } + // } + + // /** + // * Removes the last element of the url templates filters array. + // * @return {void} + // */ + // removeURLTemplateFilter() { + // this.URLTemplateFilters.pop(); + // } + + // /** + // * Returns the number of filters of the url templates filters array. + // * @return {Number} + // */ + // countURLTemplateFilters() { + // return this.URLTemplateFilters.length; + // } + + // /** + // * Removes all the filter functions from the url templates filters array. + // * @return {void} + // */ + // clearURLTemplateFilters() { + // this.URLTemplateFilters = []; + // } /** * Tracks the error provided in the errorCode parameter and emits a VAST-error event for the given error. @@ -103,84 +97,29 @@ export class VASTParser extends EventEmitter { return estimatedBitrate; } - /** - * Fetches a VAST document for the given url. - * Returns a Promise which resolves,rejects according to the result of the request. - * @param {String} url - The url to request the VAST document. - * @param {Number} wrapperDepth - How many times the current url has been wrapped. - * @param {String} previousUrl - Url of the previous VAST. - * @param {Object} wrapperAd - Previously parsed ad node (Wrapper) related to this fetching. - * @emits VASTParser#VAST-resolving - * @emits VASTParser#VAST-resolved - * @return {Promise} - */ - fetchVAST(url, wrapperDepth = 0, previousUrl = null, wrapperAd = null) { - return new Promise((resolve, reject) => { - // Process url with defined filter - this.URLTemplateFilters.forEach((filter) => { - url = filter(url); - }); - - const timeBeforeGet = Date.now(); - this.emit('VAST-resolving', { - url, - previousUrl, - wrapperDepth, - maxWrapperDepth: this.maxWrapperDepth, - timeout: this.fetchingOptions.timeout, - wrapperAd, - }); - - this.urlHandler.get( - url, - this.fetchingOptions, - (error, xml, details = {}) => { - const deltaTime = Math.round(Date.now() - timeBeforeGet); - const info = Object.assign( - { - url, - previousUrl, - wrapperDepth, - error, - duration: deltaTime, - }, - details - ); - - this.emit('VAST-resolved', info); - - updateEstimatedBitrate(details.byteLength, deltaTime); - - if (error) { - reject(error); - } else { - resolve(xml); - } - } - ); - }); - } - /** * Inits the parsing properties of the class with the custom values provided as options. * @param {Object} options - The options to initialize a parsing sequence */ initParsingStatus(options = {}) { - this.errorURLTemplates = []; - this.fetchingOptions = { - timeout: options.timeout || DEFAULT_TIMEOUT, - withCredentials: options.withCredentials, - }; + console.log(options); this.maxWrapperDepth = options.wrapperLimit || DEFAULT_MAX_WRAPPER_DEPTH; this.parsingOptions = { allowMultipleAds: options.allowMultipleAds }; + this.resetParsingStatus(); + updateEstimatedBitrate(options.byteLength, options.requestDuration); + } + + /** + * Reset the parsing property of the class everytime a VAST is parsed + */ + resetParsingStatus() { + this.errorURLTemplates = []; + this.parentURLs = []; this.remainingAds = []; this.rootErrorURLTemplates = []; this.rootURL = ''; - this.urlHandler = options.urlHandler || options.urlhandler || urlHandler; this.vastVersion = null; - updateEstimatedBitrate(options.byteLength, options.requestDuration); } - /** * Resolves the next group of ads. If all is true resolves all the remaining ads. * @param {Boolean} all - If true all the remaining ads are resolved @@ -206,36 +145,6 @@ export class VASTParser extends EventEmitter { }); } - /** - * Fetches and parses a VAST for the given url. - * Returns a Promise which resolves with a fully parsed VASTResponse or rejects with an Error. - * @param {String} url - The url to request the VAST document. - * @param {Object} options - An optional Object of parameters to be used in the parsing process. - * @emits VASTParser#VAST-resolving - * @emits VASTParser#VAST-resolved - * @emits VASTParser#VAST-warning - * @return {Promise} - */ - getAndParseVAST(url, options = {}) { - this.initParsingStatus(options); - - this.URLTemplateFilters.forEach((filter) => { - url = filter(url); - }); - - this.rootURL = url; - - return this.fetchVAST(url).then((xml) => { - options.previousUrl = url; - options.isRootVAST = true; - options.url = url; - - return this.parse(xml, options).then((ads) => { - return this.buildVASTResponse(ads); - }); - }); - } - /** * Parses the given xml Object into a VASTResponse. * Returns a Promise which resolves with a fully parsed VASTResponse or rejects with an Error. @@ -316,7 +225,6 @@ export class VASTParser extends EventEmitter { if (isRootVAST) { if (vastVersion) this.vastVersion = vastVersion; } - // Fill the VASTResponse object with ads and errorURLTemplates for (const nodeKey in childNodes) { const node = childNodes[nodeKey]; @@ -389,6 +297,7 @@ export class VASTParser extends EventEmitter { allowMultipleAds, } = {} ) { + console.log(isRootVAST); let ads = []; // allowMultipleAds was introduced in VAST 3 as wrapper attribute // for retrocompatibility set it to true for vast pre-version 3 @@ -432,7 +341,11 @@ export class VASTParser extends EventEmitter { ads = this.remainingAds.shift(); } - return this.resolveAds(ads, { wrapperDepth, previousUrl, url }); + return this.resolveAds(ads, { + wrapperDepth, + previousUrl, + url, + }); } /** @@ -480,11 +393,24 @@ export class VASTParser extends EventEmitter { * @param {String} previousUrl - The previous vast url. * @return {Promise} */ - resolveWrappers(ad, wrapperDepth, previousUrl) { + resolveWrappers(adToUnWrap, wrapperDepth, previousUrl) { + // Copy ad from parameters to prevent altering given object outside of function scope + const ad = { ...adToUnWrap }; return new Promise((resolve) => { // Going one level deeper in the wrapper chain wrapperDepth++; + + // If you need to fectch a VAST document, or follow additional wrapper, + // you need to use the get method from de VASTClient, this method will use the fetchVAST method from the + // Fetcher class to fetch your document and set the fetchingMethod in case you want to fetch additional wrapper + + if (!this.fetchingMethod) { + ad.VASTAdTagURI = ad.nextWrapperURL; + delete ad.nextWrapperURL; + return resolve(ad); + } // We already have a resolved VAST ad, no need to resolve wrapper + if (!ad.nextWrapperURL) { delete ad.nextWrapperURL; return resolve(ad); @@ -503,9 +429,12 @@ export class VASTParser extends EventEmitter { previousUrl ); - this.URLTemplateFilters.forEach((filter) => { - ad.nextWrapperURL = filter(ad.nextWrapperURL); - }); + // TODO OK: move everything related to URLTemplateFilters to fetcher.js + // + update doc accordingly + // + filter already called in fetching method + // this.URLTemplateFilters.forEach((filter) => { + // ad.nextWrapperURL = filter(ad.nextWrapperURL); + // }); // If allowMultipleAds is set inside the parameter 'option' of public method // override the vast value by the one provided @@ -513,7 +442,14 @@ export class VASTParser extends EventEmitter { this.parsingOptions.allowMultipleAds ?? ad.allowMultipleAds; // sequence doesn't carry over in wrapper element const wrapperSequence = ad.sequence; - this.fetchVAST(ad.nextWrapperURL, wrapperDepth, previousUrl, ad) + + this.fetchingMethod( + ad.nextWrapperURL, + { wrapperDepth: 0, previousUrl: null, wrapperAd: null }, + this.maxWrapperDepth, + this.parentURLs, + this.emit.bind(this) + ) .then((xml) => { return this.parse(xml, { url: ad.nextWrapperURL, From 6a73db84643757fdd3e58377aa12c7c7bed62b7c Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Fri, 23 Jun 2023 15:27:06 +0200 Subject: [PATCH 03/95] adding new Fetcher class including the fetchVAST method --- src/fetcher.js | 130 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 src/fetcher.js diff --git a/src/fetcher.js b/src/fetcher.js new file mode 100644 index 00000000..4d626c4b --- /dev/null +++ b/src/fetcher.js @@ -0,0 +1,130 @@ +import { updateEstimatedBitrate } from './parser/bitrate'; +import { urlHandler } from './url_handler'; +import { DEFAULT_TIMEOUT } from './urlhandlers/consts'; + +/** + * This class provides a method to fetch a VAST document + * @exports + * @class Fetcher + */ + +export class Fetcher { + constructor() { + this.URLTemplateFilters = []; + } + + setOptions(options = {}) { + console.log(options); + this.urlHandler = options.urlHandler || options.urlhandler || urlHandler; + this.URLTemplateFilters = []; + this.fetchingOptions = { + timeout: options.timeout || DEFAULT_TIMEOUT, + withCredentials: options.withCredentials || false, + }; + } + + /** + * Adds a filter function to the array of filters which are called before fetching a VAST document. + * @param {function} filter - The filter function to be added at the end of the array. + * @return {void} + */ + addURLTemplateFilter(filter) { + if (typeof filter === 'function') { + this.URLTemplateFilters.push(filter); + } + } + + /** + * Removes the last element of the url templates filters array. + * @return {void} + */ + removeURLTemplateFilter() { + this.URLTemplateFilters.pop(); + } + + /** + * Returns the number of filters of the url templates filters array. + * @return {Number} + */ + countURLTemplateFilters() { + return this.URLTemplateFilters.length; + } + + /** + * Removes all the filter functions from the url templates filters array. + * @return {void} + */ + clearURLTemplateFilters() { + this.URLTemplateFilters = []; + } + + /** + * Fetches a VAST document for the given url. + * Returns a Promise which resolves,rejects according to the result of the request. + * @param {String} url - The url to request the VAST document. + * @param {Number} wrapperDepth - How many times the current url has been wrapped. + * @param {String} previousUrl - Url of the previous VAST. + * @param {Object} wrapperAd - Previously parsed ad node (Wrapper) related to this fetching. + * @param {Number} maxWrapperDepth - The maximum number of Wrapper that can be fetch + * @param {Array} parentUrls Array containing all the previous fetched URL + * @param {emitter} emitter - The function used to Emit event + * @emits VASTParser#VAST-resolving + * @emits VASTParser#VAST-resolved + * @return {Promise} + */ + + fetchVAST( + url, + { wrapperDepth = 0, previousUrl = null, wrapperAd = null } = {}, + maxWrapperDepth, + parentURLs, + emitter + ) { + return new Promise((resolve, reject) => { + const timeBeforeGet = Date.now(); + + // Process url with defined filter + this.URLTemplateFilters.forEach((filter) => { + url = filter(url); + }); + + this.rootURL = url; + + parentURLs.push(url); + + emitter('VAST-resolving', { + url, + previousUrl, + wrapperDepth, + maxWrapperDepth: maxWrapperDepth, + timeout: this.fetchingOptions.timeout, + wrapperAd, + }); + + this.urlHandler.get( + url, + this.fetchingOptions, + (error, xml, details = {}) => { + const deltaTime = Math.round(Date.now() - timeBeforeGet); + const info = { + url, + previousUrl, + wrapperDepth, + error, + duration: deltaTime, + ...details, + }; + + emitter('VAST-resolved', info); + updateEstimatedBitrate(details.byteLength, deltaTime); + + if (error) { + reject(error); + } else { + resolve(xml); + } + } + ); + }); + } +} From 2acf7daaf8a86faa919c26e22705e218aea13a64 Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Fri, 23 Jun 2023 15:38:23 +0200 Subject: [PATCH 04/95] [client] changing get method to use fetchVAST method from the Fetcher class instead of getAndParseVAST from the vast_parser --- src/vast_client.js | 72 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 63 insertions(+), 9 deletions(-) diff --git a/src/vast_client.js b/src/vast_client.js index 23dbb8f9..3f9ff451 100644 --- a/src/vast_client.js +++ b/src/vast_client.js @@ -1,5 +1,6 @@ import { Storage } from './util/storage'; import { VASTParser } from './parser/vast_parser'; +import { Fetcher } from './fetcher.js'; /** * This class provides methods to fetch and parse a VAST document using VASTParser. @@ -18,13 +19,9 @@ export class VASTClient { constructor(cappingFreeLunch, cappingMinimumTimeInterval, customStorage) { this.cappingFreeLunch = cappingFreeLunch || 0; this.cappingMinimumTimeInterval = cappingMinimumTimeInterval || 0; - this.defaultOptions = { - withCredentials: false, - timeout: 0, - }; this.vastParser = new VASTParser(); this.storage = customStorage || new Storage(); - + this.fetcher = new Fetcher(); // Init values if not already set if (this.lastSuccessfulAd === undefined) { this.lastSuccessfulAd = 0; @@ -38,6 +35,39 @@ export class VASTClient { } } + /** + * Adds a filter function to the array of filters which are called before fetching a VAST document. + * @param {function} filter - The filter function to be added at the end of the array. + * @return {void} + */ + addURLTemplateFilter(filter) { + this.fetcher.addURLTemplateFilter(filter); + } + + /** + * Removes the last element of the url templates filters array. + * @return {void} + */ + removeURLTemplateFilter() { + this.fetcher.removeURLTemplateFilter(); + } + + /** + * Returns the number of filters of the url templates filters array. + * @return {Number} + */ + countURLTemplateFilters() { + return this.fetcher.countURLTemplateFilters(); + } + + /** + * Removes all the filter functions from the url templates filters array. + * @return {void} + */ + clearURLTemplateFilters() { + this.fetcher.clearURLTemplateFilters(); + } + getParser() { return this.vastParser; } @@ -92,7 +122,7 @@ export class VASTClient { */ get(url, options = {}) { const now = Date.now(); - options = Object.assign({}, this.defaultOptions, options); + // options = Object.assign({}, this.defaultOptions, options); // By default the client resolves only the first Ad or AdPod if (!options.hasOwnProperty('resolveAll')) { @@ -132,9 +162,33 @@ export class VASTClient { ); } - this.vastParser - .getAndParseVAST(url, options) - .then((response) => resolve(response)) + // const fetcher = new Fetcher(options); + + this.vastParser.initParsingStatus(options); + this.fetcher.setOptions(options); + this.vastParser.fetchingMethod = this.fetcher.fetchVAST.bind( + this.fetcher + ); + + this.fetcher.rootURL = this.vastParser.rootURL; + + this.fetcher + .fetchVAST( + url, + { wrapperDepth: 0, previousUrl: null, wrapperAd: null }, + this.vastParser.maxWrapperDepth, + this.vastParser.parentURLs, + this.vastParser.emit.bind(this.vastParser) + ) + .then((xml) => { + options.previousUrl = url; + options.isRootVAST = true; + console.log(options); + return this.vastParser.parse(xml, options).then((resolvedAd) => { + const vastResponse = this.vastParser.buildVASTResponse(resolvedAd); + resolve(vastResponse); + }); + }) .catch((err) => reject(err)); }); } From 6ec4bce09efdb5bc7280744d0f0493ea98d8924f Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Fri, 23 Jun 2023 15:43:40 +0200 Subject: [PATCH 05/95] adding supported browsers list, modification of the entry point, and adding watch option when building the library --- package.json | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index d1bc0371..9ded5f50 100644 --- a/package.json +++ b/package.json @@ -19,15 +19,21 @@ "engines": { "node": ">=12.22.1" }, + "browserslist" : [ + "chrome >= 63", + "firefox >= 67", + "edge >= 79", + "safari >= 12.1" + ], "publishConfig": { "registry": "https://registry.npmjs.org" }, - "main": "dist/vast-client-node.min.js", - "browser": "dist/vast-client.min.js", + "main": "dist/node/min/vast-client-node.min.js", + "browser": "dist/min/vast-client.min.js", "scripts": { "performance": "node performance/performance_test.js", "prebuild": "rm -rf dist_old && mkdir dist_old && cp -a dist/. dist_old/", - "build": "rollup -c", + "build": "rollup -c --watch", "postbuild": "node bundle_size.js", "lint": "eslint .", "precommit": "eslint . --max-warnings 0 && pretty-quick --staged", From 4d360bc4af150e00aa022e47758958fe1c901ffb Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Fri, 23 Jun 2023 16:02:05 +0200 Subject: [PATCH 06/95] [parser] removing unnecessary comments --- src/parser/vast_parser.js | 44 --------------------------------------- 1 file changed, 44 deletions(-) diff --git a/src/parser/vast_parser.js b/src/parser/vast_parser.js index c0fe25e1..8fa4e7ae 100644 --- a/src/parser/vast_parser.js +++ b/src/parser/vast_parser.js @@ -25,46 +25,9 @@ export class VASTParser extends EventEmitter { constructor() { super(); this.maxWrapperDepth = null; - // this.URLTemplateFilters = []; - // this.parsingOptions = {}; this.fetchingMethod = null; } - // /** - // * Adds a filter function to the array of filters which are called before fetching a VAST document. - // * @param {function} filter - The filter function to be added at the end of the array. - // * @return {void} - // */ - // addURLTemplateFilter(filter) { - // if (typeof filter === 'function') { - // this.URLTemplateFilters.push(filter); - // } - // } - - // /** - // * Removes the last element of the url templates filters array. - // * @return {void} - // */ - // removeURLTemplateFilter() { - // this.URLTemplateFilters.pop(); - // } - - // /** - // * Returns the number of filters of the url templates filters array. - // * @return {Number} - // */ - // countURLTemplateFilters() { - // return this.URLTemplateFilters.length; - // } - - // /** - // * Removes all the filter functions from the url templates filters array. - // * @return {void} - // */ - // clearURLTemplateFilters() { - // this.URLTemplateFilters = []; - // } - /** * Tracks the error provided in the errorCode parameter and emits a VAST-error event for the given error. * @param {Array} urlTemplates - An Array of url templates to use to make the tracking call. @@ -429,13 +392,6 @@ export class VASTParser extends EventEmitter { previousUrl ); - // TODO OK: move everything related to URLTemplateFilters to fetcher.js - // + update doc accordingly - // + filter already called in fetching method - // this.URLTemplateFilters.forEach((filter) => { - // ad.nextWrapperURL = filter(ad.nextWrapperURL); - // }); - // If allowMultipleAds is set inside the parameter 'option' of public method // override the vast value by the one provided const allowMultipleAds = From 7e6f9f77ce04ef5d87f2f98a70d62b56977d23c3 Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Thu, 20 Jul 2023 15:16:50 +0200 Subject: [PATCH 07/95] [client] add parseVAST method to be able to perform fetching from a given xml --- src/fetcher.js | 1 - src/parser/vast_parser.js | 2 -- src/vast_client.js | 19 ++++++++++++------- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/fetcher.js b/src/fetcher.js index 4d626c4b..3b75d209 100644 --- a/src/fetcher.js +++ b/src/fetcher.js @@ -14,7 +14,6 @@ export class Fetcher { } setOptions(options = {}) { - console.log(options); this.urlHandler = options.urlHandler || options.urlhandler || urlHandler; this.URLTemplateFilters = []; this.fetchingOptions = { diff --git a/src/parser/vast_parser.js b/src/parser/vast_parser.js index 8fa4e7ae..d1730cbb 100644 --- a/src/parser/vast_parser.js +++ b/src/parser/vast_parser.js @@ -65,7 +65,6 @@ export class VASTParser extends EventEmitter { * @param {Object} options - The options to initialize a parsing sequence */ initParsingStatus(options = {}) { - console.log(options); this.maxWrapperDepth = options.wrapperLimit || DEFAULT_MAX_WRAPPER_DEPTH; this.parsingOptions = { allowMultipleAds: options.allowMultipleAds }; this.resetParsingStatus(); @@ -260,7 +259,6 @@ export class VASTParser extends EventEmitter { allowMultipleAds, } = {} ) { - console.log(isRootVAST); let ads = []; // allowMultipleAds was introduced in VAST 3 as wrapper attribute // for retrocompatibility set it to true for vast pre-version 3 diff --git a/src/vast_client.js b/src/vast_client.js index 3f9ff451..1a631204 100644 --- a/src/vast_client.js +++ b/src/vast_client.js @@ -22,6 +22,7 @@ export class VASTClient { this.vastParser = new VASTParser(); this.storage = customStorage || new Storage(); this.fetcher = new Fetcher(); + this.vastParser.fetchingMethod = this.fetcher.fetchVAST.bind(this.fetcher); // Init values if not already set if (this.lastSuccessfulAd === undefined) { this.lastSuccessfulAd = 0; @@ -113,6 +114,17 @@ export class VASTClient { return this.vastParser.getRemainingAds(all); } + /** + * Parses the given xml Object into a VASTResponse. + * Returns a Promise which resolves with a fully parsed VASTResponse or rejects with an Error. + * @param {*} xml - An object representing a vast xml document. + * @param {*} options - An optional Object of parameters to be used in the parsing and fetching process. + * @returns {Promise} + */ + parseVAST(xml, options = {}) { + this.fetcher.setOptions(options); + return this.vastParser.parseVAST(xml, options); + } /** * Gets a parsed VAST document for the given url, applying the skipping rules defined. * Returns a Promise which resolves with a fully parsed VASTResponse or rejects with an Error. @@ -162,14 +174,8 @@ export class VASTClient { ); } - // const fetcher = new Fetcher(options); - this.vastParser.initParsingStatus(options); this.fetcher.setOptions(options); - this.vastParser.fetchingMethod = this.fetcher.fetchVAST.bind( - this.fetcher - ); - this.fetcher.rootURL = this.vastParser.rootURL; this.fetcher @@ -183,7 +189,6 @@ export class VASTClient { .then((xml) => { options.previousUrl = url; options.isRootVAST = true; - console.log(options); return this.vastParser.parse(xml, options).then((resolvedAd) => { const vastResponse = this.vastParser.buildVASTResponse(resolvedAd); resolve(vastResponse); From 06f729a6056c36dc37a194306d75a65945b7c13c Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Wed, 26 Jul 2023 18:38:27 +0200 Subject: [PATCH 08/95] [test] tests for the modularization --- spec/fetcher.spec.js | 194 +++++++++ spec/vast_client.spec.js | 77 ++-- spec/vast_parser.spec.js | 896 +++++++++++++++++--------------------- src/fetcher.js | 6 +- src/parser/vast_parser.js | 4 +- src/vast_client.js | 2 +- 6 files changed, 631 insertions(+), 548 deletions(-) create mode 100644 spec/fetcher.spec.js diff --git a/spec/fetcher.spec.js b/spec/fetcher.spec.js new file mode 100644 index 00000000..664afad4 --- /dev/null +++ b/spec/fetcher.spec.js @@ -0,0 +1,194 @@ +import { Fetcher } from '../src/fetcher'; +import { VASTParser } from '../src/parser/vast_parser'; +import * as Bitrate from '../src/parser/bitrate'; +import { urlHandler } from '../src/url_handler'; +import { expect } from '@jest/globals'; + +describe('Fetcher', () => { + let fetcher, vastParser; + const xml = new DOMParser().parseFromString('', 'text/xml'); + const urlHandlerSuccess = { + get: (url, options, cb) => { + cb(null, xml, { byteLength: 1234, statusCode: 200 }); + }, + }; + + const urlHandlerFailure = { + get: (url, options, cb) => { + cb(new Error('timeout'), null, { statusCode: 408 }); + }, + }; + + beforeEach(() => { + fetcher = new Fetcher(); + vastParser = new VASTParser(); + jest.spyOn(vastParser, 'emit'); + }); + + describe('fetchVAST', () => { + const url = 'www.foo.foo'; + + describe('on resolved', () => { + beforeEach(() => { + fetcher.setOptions({ + wrapperLimit: 8, + urlHandler: urlHandlerSuccess, + }); + }); + + it('should applies url filters', () => { + fetcher.URLTemplateFilters = [(url) => url.replace('foo', 'bar')]; + let urlHandlerSpy = jest.spyOn(fetcher.urlHandler, 'get'); + const expectedUrl = 'www.bar.foo'; + + fetcher + .fetchVAST(url, {}, 5, () => {}) + .then(() => { + expect(urlHandlerSpy).toHaveBeenCalledWith( + expectedUrl, + expect.anything(), + expect.anything() + ); + }); + }); + + it('should emits VAST-resolving and VAST-resolved events with a filtered url', () => { + fetcher.URLTemplateFilters = [(url) => url.replace('foo', 'bar')]; + const expectedUrl = 'www.bar.foo'; + fetcher + .fetchVAST(url, {}, 4, vastParser.emit.bind(vastParser)) + .then(() => { + expect(vastParser.emit).toHaveBeenNthCalledWith( + 1, + 'VAST-resolving', + { + url: expectedUrl, + previousUrl: null, + wrapperDepth: 0, + maxWrapperDepth: 4, + timeout: 120000, + wrapperAd: null, + } + ); + + expect(vastParser.emit).toHaveBeenNthCalledWith( + 2, + 'VAST-resolved', + { + url: expectedUrl, + byteLength: 1234, + duration: expect.any(Number), + error: null, + statusCode: 200, + previousUrl: null, + wrapperDepth: 0, + } + ); + }); + }); + + it('should updates the estimated bitrate', () => { + jest.spyOn(Bitrate, 'updateEstimatedBitrate'); + return fetcher + .fetchVAST(url, {}, 5, () => {}) + .finally(() => { + expect(Bitrate.updateEstimatedBitrate).toHaveBeenCalledWith( + 1234, + expect.any(Number) + ); + }); + }); + + it('should resolves with xml', () => { + let result = fetcher.fetchVAST(url, {}, 5, () => {}); + expect(result).resolves.toEqual(xml); + }); + }); + + describe('on rejected', () => { + beforeEach(() => { + fetcher.setOptions({ + wrapperLimit: 5, + urlHandler: urlHandlerFailure, + }); + }); + + it('should applies url filters', () => { + fetcher.URLTemplateFilters = [(url) => url.replace('foo', 'bar')]; + let urlHandlerSpy = jest.spyOn(fetcher.urlHandler, 'get'); + const expectedUrl = 'www.bar.foo'; + + fetcher + .fetchVAST(url, {}, 5, () => {}) + .catch(() => { + expect(urlHandlerSpy).toHaveBeenCalledWith( + expectedUrl, + expect.anything(), + expect.anything() + ); + }); + }); + + it('should emits VAST-resolving and VAST-resolved events with a filtered url', () => { + fetcher.URLTemplateFilters = [(url) => url.replace('foo', 'bar')]; + const expectedUrl = 'www.bar.foo'; + + fetcher + .fetchVAST(url, {}, 4, vastParser.emit.bind(vastParser)) + .catch(() => { + expect(vastParser.emit).toHaveBeenNthCalledWith( + 1, + 'VAST-resolving', + { + url: expectedUrl, + previousUrl: null, + wrapperDepth: 0, + maxWrapperDepth: 4, + timeout: 120000, + wrapperAd: null, + } + ); + + expect(vastParser.emit).toHaveBeenNthCalledWith( + 2, + 'VAST-resolved', + { + url: expectedUrl, + duration: expect.any(Number), + error: new Error('timeout'), + statusCode: 408, + previousUrl: null, + wrapperDepth: 0, + } + ); + }); + }); + + it('should rejects with error', () => { + let result = fetcher.fetchVAST(url, {}, 5, () => {}); + expect(result).rejects.toEqual(new Error('timeout')); + }); + }); + }); + + describe('setOptions', () => { + it('should assign option to properties', () => { + const urlhandler = jest.fn(); + fetcher.setOptions({ + urlHandler: urlhandler, + timeout: 50000, + withCredentials: true, + }); + expect(fetcher.urlHandler).toEqual(urlhandler); + expect(fetcher.fetchingOptions.timeout).toEqual(50000); + expect(fetcher.fetchingOptions.withCredentials).toEqual(true); + }); + + it('should assign default option when no options are given', () => { + fetcher.setOptions(); + expect(fetcher.urlHandler).toEqual(urlHandler); + expect(fetcher.fetchingOptions.timeout).toEqual(120000); + expect(fetcher.fetchingOptions.withCredentials).toEqual(false); + }); + }); +}); diff --git a/spec/vast_client.spec.js b/spec/vast_client.spec.js index 93bdceb8..98f8cbe7 100644 --- a/spec/vast_client.spec.js +++ b/spec/vast_client.spec.js @@ -98,23 +98,23 @@ describe('VASTClient', () => { VastParser = VastClient.getParser(); }); - it('should call getAndParseVAST with correct options', () => { - jest - .spyOn(VastParser, 'getAndParseVAST') - .mockImplementation(() => Promise.resolve()); - return VastClient.get(wrapperMultipleAdsVastUrl, options).then(() => { - expect(VastParser.getAndParseVAST).toHaveBeenNthCalledWith( - 1, - wrapperMultipleAdsVastUrl, - { - urlhandler: nodeURLHandler, - resolveAll: false, - withCredentials: true, - timeout: 0, - } - ); - }); - }); + // it('should call getAndParseVAST with correct options', () => { + // jest + // .spyOn(VastParser, 'getAndParseVAST') + // .mockImplementation(() => Promise.resolve()); + // return VastClient.get(wrapperMultipleAdsVastUrl, options).then(() => { + // expect(VastParser.getAndParseVAST).toHaveBeenNthCalledWith( + // 1, + // wrapperMultipleAdsVastUrl, + // { + // urlhandler: nodeURLHandler, + // resolveAll: false, + // withCredentials: true, + // timeout: 0, + // } + // ); + // }); + // }); describe('with resolveAll set to false', () => { const optionsWithNoResolveAll = { ...options, resolveAll: false }; @@ -161,23 +161,36 @@ describe('VASTClient', () => { describe('getNextAds', () => { it('resolves all next ads if requested', () => { - return VastClient.getNextAds(true).then((res) => { - expect(res).toEqual({ - ads: expect.any(Array), - errorURLTemplates: [], - version: '3.0', - }); - expect(res.ads).toHaveLength(3); - }); + VastClient.get(wrapperMultipleAdsVastUrl, optionsWithNoResolveAll) + .then((res1) => { + VastClient.getNextAds(true) + .then((res) => { + expect(res).toEqual({ + ads: expect.any(Array), + errorURLTemplates: [], + version: '3.0', + }); + expect(res.ads).toHaveLength(3); + }) + .catch((err) => console.log(err)); + }) + .catch((err) => console.log(err)); }); it('resolves only next ad if requested', () => { - return VastClient.getNextAds(false).then((res) => { - expect(res).toEqual({ - ads: expect.any(Array), - errorURLTemplates: [], - version: '3.0', - }); - expect(res.ads).toHaveLength(2); + VastClient.get( + wrapperMultipleAdsVastUrl, + optionsWithNoResolveAll + ).then(() => { + VastClient.getNextAds(false) + .then((res) => { + expect(res).toEqual({ + ads: expect.any(Array), + errorURLTemplates: [], + version: '3.0', + }); + expect(res.ads).toHaveLength(2); + }) + .catch((err) => console.log(err)); }); }); }); diff --git a/spec/vast_parser.spec.js b/spec/vast_parser.spec.js index 0d763c28..66fae59a 100644 --- a/spec/vast_parser.spec.js +++ b/spec/vast_parser.spec.js @@ -4,6 +4,7 @@ import { urlFor, fetchXml } from './utils/utils'; import { util } from '../src/util/util'; import { parserUtils } from '../src/parser/parser_utils'; import * as Bitrate from '../src/parser/bitrate'; +import { Fetcher } from '../src/fetcher'; const xml = new DOMParser().parseFromString('', 'text/xml'); const urlHandlerSuccess = { @@ -71,147 +72,16 @@ describe('VASTParser', () => { jest.spyOn(VastParser, 'emit'); }); - describe('fetchVAST', () => { - describe('on resolved', () => { - beforeEach(() => { - VastParser.initParsingStatus({ - wrapperLimit: 8, - urlHandler: urlHandlerSuccess, - }); - }); - - it('applies url filters', (done) => { - VastParser.URLTemplateFilters = [(url) => url.replace('foo', 'bar')]; - - const urlHandlerSpy = jest.spyOn(VastParser.urlHandler, 'get'); - - VastParser.fetchVAST('www.foo.foo').then(() => { - expect(urlHandlerSpy).toHaveBeenCalledWith( - 'www.bar.foo', - expect.anything(), - expect.anything() - ); - done(); - }); - }); - - it('emits VAST-resolving and VAST-resolved events with filtered url', () => { - VastParser.URLTemplateFilters = [(url) => url.replace('foo', 'bar')]; - - return VastParser.fetchVAST( - 'www.foo.foo', - 2, - 'www.original.foo', - ad - ).finally(() => { - expect(VastParser.emit).toHaveBeenNthCalledWith(1, 'VAST-resolving', { - url: 'www.bar.foo', - previousUrl: 'www.original.foo', - wrapperDepth: 2, - maxWrapperDepth: 8, - timeout: 120000, - wrapperAd: ad, - }); - - expect(VastParser.emit).toHaveBeenNthCalledWith(2, 'VAST-resolved', { - url: 'www.bar.foo', - previousUrl: 'www.original.foo', - wrapperDepth: 2, - error: null, - duration: expect.any(Number), - byteLength: 1234, - statusCode: 200, - }); - }); - }); - - it('updates the estimated bitrate', () => { - jest.spyOn(Bitrate, 'updateEstimatedBitrate'); - - return VastParser.fetchVAST('www.foo.foo').finally(() => { - expect(Bitrate.updateEstimatedBitrate).toHaveBeenCalledWith( - 1234, - expect.any(Number) - ); - }); - }); - - it('resolves with xml', () => { - return expect( - VastParser.fetchVAST('www.foo.foo', 2, 'www.original.foo') - ).resolves.toEqual(xml); - }); - }); - - describe('on rejected', () => { - beforeEach(() => { - VastParser.initParsingStatus({ - wrapperLimit: 8, - urlHandler: urlHandlerFailure, - }); - }); - - it('applies url filters', () => { - VastParser.URLTemplateFilters = [(url) => url.replace('foo', 'bar')]; - - const urlHandlerSpy = jest.spyOn(VastParser.urlHandler, 'get'); - - return VastParser.fetchVAST('www.foo.foo').catch(() => { - expect(urlHandlerSpy).toHaveBeenCalledWith( - 'www.bar.foo', - expect.anything(), - expect.anything() - ); - }); - }); - - it('emits VAST-resolving and VAST-resolved events with filtered url', () => { - VastParser.URLTemplateFilters = [(url) => url.replace('foo', 'bar')]; - - return VastParser.fetchVAST( - 'www.foo.foo', - 2, - 'www.original.foo', - ad - ).catch(() => { - expect(VastParser.emit).toHaveBeenNthCalledWith(1, 'VAST-resolving', { - url: 'www.bar.foo', - previousUrl: 'www.original.foo', - wrapperDepth: 2, - maxWrapperDepth: 8, - timeout: 120000, - wrapperAd: ad, - }); - - expect(VastParser.emit).toHaveBeenNthCalledWith(2, 'VAST-resolved', { - url: 'www.bar.foo', - previousUrl: 'www.original.foo', - wrapperDepth: 2, - error: new Error('timeout'), - duration: expect.any(Number), - statusCode: 408, - }); - }); - }); - - it('rejects with error', () => { - return expect( - VastParser.fetchVAST('www.foo.foo', 2, 'www.original.foo') - ).rejects.toEqual(new Error('timeout')); - }); + describe('initParsingStatus', () => { + beforeEach(() => { + jest.spyOn(VastParser, 'resetParsingStatus'); }); - }); - describe('initParsingStatus', () => { it('assigns options to properties', () => { - const urlHandler = jest.fn(); jest.spyOn(Bitrate, 'updateEstimatedBitrate'); VastParser.initParsingStatus({ wrapperLimit: 5, - timeout: 1000, - withCredentials: true, - urlHandler, allowMultipleAds: true, byteLength: 1000, requestDuration: 200, @@ -222,14 +92,10 @@ describe('VASTParser', () => { expect(VastParser.errorURLTemplates).toEqual([]); expect(VastParser.rootErrorURLTemplates).toEqual([]); expect(VastParser.maxWrapperDepth).toBe(5); - expect(VastParser.fetchingOptions).toEqual({ - timeout: 1000, - withCredentials: true, - }); - expect(VastParser.urlHandler).toEqual(urlHandler); expect(VastParser.vastVersion).toBeNull(); expect(VastParser.parsingOptions).toEqual({ allowMultipleAds: true }); expect(Bitrate.updateEstimatedBitrate).toBeCalledWith(1000, 200); + expect(VastParser.resetParsingStatus).toHaveBeenCalled(); }); it('uses default values if no options are passed', () => { @@ -240,367 +106,364 @@ describe('VASTParser', () => { expect(VastParser.errorURLTemplates).toEqual([]); expect(VastParser.rootErrorURLTemplates).toEqual([]); expect(VastParser.maxWrapperDepth).toBe(10); - expect(VastParser.fetchingOptions).toEqual({ - timeout: 120000, - withCredentials: undefined, - }); expect(VastParser.vastVersion).toBeNull(); expect(VastParser.parsingOptions).toEqual({ allowMultipleAds: undefined, }); + expect(VastParser.resetParsingStatus).toHaveBeenCalled(); }); }); - describe('getAndParseVAST', () => { - beforeEach(() => { - jest.spyOn(VastParser, 'initParsingStatus'); - jest.spyOn(VastParser, 'fetchVAST'); - jest.spyOn(VastParser, 'parse'); - jest.spyOn(VastParser, 'buildVASTResponse'); - }); - - it('passes options to initParsingStatus and assigns rootUrl', () => { - VastParser.getAndParseVAST(wrapperAVastUrl, { - wrapperLimit: 8, - urlHandler: nodeURLHandler, - }); - - expect(VastParser.initParsingStatus).toHaveBeenCalledWith({ - wrapperLimit: 8, - urlHandler: nodeURLHandler, - }); - expect(VastParser.rootURL).toBe(wrapperAVastUrl); - }); - - describe('on success', () => { - it('calls fetchVast with correct params multiple times', () => { - return VastParser.getAndParseVAST(wrapperAVastUrl, { - wrapperLimit: 8, - urlHandler: nodeURLHandler, - }).finally(() => { - expect(VastParser.fetchVAST).toHaveBeenCalledTimes(4); - expect(VastParser.fetchVAST.mock.calls).toEqual( - expect.arrayContaining([ - [wrapperAVastUrl], - [wrapperBVastUrl, 1, wrapperAVastUrl, expect.any(Object)], - [inlineVpaidVastUrl, 1, wrapperAVastUrl, expect.any(Object)], - [inlineSampleVastUrl, 2, wrapperBVastUrl, expect.any(Object)], - [inlineSampleVastUrl, 2, wrapperBVastUrl, expect.any(Object)], - ]) - ); - }); - }); - - it('emits events in the right order', () => { - return VastParser.getAndParseVAST(wrapperAVastUrl, { - wrapperLimit: 8, - urlHandler: nodeURLHandler, - }).finally(() => { - expect(VastParser.emit).toHaveBeenCalledTimes(14); - expect(VastParser.emit.mock.calls).toEqual( - expect.arrayContaining([ - // WRAPPER A - [ - 'VAST-resolving', - { - url: wrapperAVastUrl, - previousUrl: null, - wrapperDepth: 0, - maxWrapperDepth: 8, - timeout: 120000, - wrapperAd: expect.any(Object), - }, - ], - [ - 'VAST-resolved', - { - url: wrapperAVastUrl, - previousUrl: null, - wrapperDepth: 0, - error: null, - duration: expect.any(Number), - byteLength: expect.any(Number), - }, - ], - [ - 'VAST-ad-parsed', - { - type: 'WRAPPER', - url: wrapperAVastUrl, - wrapperDepth: 0, - adIndex: 0, - vastVersion: '2.0', - }, - ], - [ - 'VAST-ad-parsed', - { - type: 'WRAPPER', - url: wrapperAVastUrl, - wrapperDepth: 0, - adIndex: 1, - vastVersion: '2.0', - }, - ], - // RESOLVING AD 1 (WRAPPER B) IN WRAPPER A - [ - 'VAST-resolving', - { - url: wrapperBVastUrl, - previousUrl: wrapperAVastUrl, - wrapperDepth: 1, - maxWrapperDepth: 8, - timeout: 120000, - wrapperAd: expect.any(Object), - }, - ], - // RESOLVING AD 2 (WRAPPER VPAID) IN WRAPPER A - [ - 'VAST-resolving', - { - url: inlineVpaidVastUrl, - previousUrl: wrapperAVastUrl, - wrapperDepth: 1, - maxWrapperDepth: 8, - timeout: 120000, - wrapperAd: expect.any(Object), - }, - ], - // AD 1 (WRAPPER B) IN WRAPPER A - [ - 'VAST-resolved', - { - url: wrapperBVastUrl, - previousUrl: wrapperAVastUrl, - wrapperDepth: 1, - error: null, - duration: expect.any(Number), - byteLength: expect.any(Number), - }, - ], - [ - 'VAST-ad-parsed', - { - type: 'WRAPPER', - url: wrapperBVastUrl, - wrapperDepth: 1, - adIndex: 0, - vastVersion: '2.0', - }, - ], - // AD 1 (WRAPPER SAMPLE) IN WRAPPER B - [ - 'VAST-resolving', - { - url: inlineSampleVastUrl, - previousUrl: wrapperBVastUrl, - wrapperDepth: 2, - maxWrapperDepth: 8, - timeout: 120000, - wrapperAd: expect.any(Object), - }, - ], - // AD 2 (WRAPPER VPAID) IN WRAPPER A - [ - 'VAST-resolved', - { - url: inlineVpaidVastUrl, - previousUrl: wrapperAVastUrl, - wrapperDepth: 1, - error: null, - duration: expect.any(Number), - byteLength: expect.any(Number), - }, - ], - [ - 'VAST-ad-parsed', - { - type: 'INLINE', - url: inlineVpaidVastUrl, - wrapperDepth: 1, - adIndex: 0, - vastVersion: '2.0', - }, - ], - // AD 1 (WRAPPER SAMPLE) IN WRAPPER B - [ - 'VAST-resolved', - { - url: inlineSampleVastUrl, - previousUrl: wrapperBVastUrl, - wrapperDepth: 2, - error: null, - duration: expect.any(Number), - byteLength: expect.any(Number), - }, - ], - [ - 'VAST-ad-parsed', - { - type: 'INLINE', - url: inlineSampleVastUrl, - wrapperDepth: 2, - adIndex: 0, - vastVersion: '2.1', - }, - ], - [ - 'VAST-ad-parsed', - { - type: 'INLINE', - url: inlineSampleVastUrl, - wrapperDepth: 2, - adIndex: 1, - vastVersion: '2.1', - }, - ], - ]) - ); - }); - }); - - it('calls parse with correct params multiple times', () => { - return VastParser.getAndParseVAST(wrapperAVastUrl, { - wrapperLimit: 8, - urlHandler: nodeURLHandler, - }).finally(() => { - expect(VastParser.parse).toHaveBeenCalledTimes(4); - expect(VastParser.parse.mock.calls).toEqual( - expect.arrayContaining([ - [ - jasmine.any(Object), - { - wrapperLimit: 8, - urlHandler: nodeURLHandler, - previousUrl: wrapperAVastUrl, - isRootVAST: true, - url: wrapperAVastUrl, - }, - ], - [ - jasmine.any(Object), - { - url: wrapperBVastUrl, - previousUrl: wrapperAVastUrl, - wrapperDepth: 1, - wrapperSequence: null, - allowMultipleAds: false, - followAdditionalWrappers: true, - }, - ], - [ - jasmine.any(Object), - { - url: inlineSampleVastUrl, - previousUrl: wrapperBVastUrl, - wrapperDepth: 2, - wrapperSequence: null, - allowMultipleAds: false, - followAdditionalWrappers: true, - }, - ], - [ - jasmine.any(Object), - { - url: inlineSampleVastUrl, - previousUrl: wrapperBVastUrl, - wrapperDepth: 2, - wrapperSequence: null, - allowMultipleAds: false, - followAdditionalWrappers: true, - }, - ], - ]) - ); - }); - }); - - it('calls buildVASTResponse with correct params one time', () => { - return VastParser.getAndParseVAST(wrapperAVastUrl, { - wrapperLimit: 8, - urlHandler: nodeURLHandler, - }).finally(() => { - expect(VastParser.buildVASTResponse).toBeCalledTimes(1); - }); - }); - }); - - describe('on failure', () => { - it('fails on bad fetch request', () => { - return VastParser.getAndParseVAST('badUrl', { - urlHandler: nodeURLHandler, - }) - .then(() => { - expect(true).toBeFalsy(); - }) - .catch((e) => { - expect(e).toBeTruthy(); - expect(VastParser.parse).not.toBeCalled(); - }); - }); - - describe('invalid VAST xml', () => { - it('when inline, rejects with error', () => { - return VastParser.getAndParseVAST(inlineInvalidVastUrl, { - urlHandler: nodeURLHandler, - }) - .then(() => { - expect(true).toBeFalsy(); - }) - .catch((e) => { - expect(e.message).toEqual('Invalid VAST XMLDocument'); - expect(VastParser.buildVASTResponse).not.toBeCalled(); - }); - }); - it('when wrapped, emits a VAST-error & track', (done) => { - const errorData = []; - const trackCalls = []; - VastParser.on('VAST-error', (data) => errorData.push(data)); - jest - .spyOn(util, 'track') - .mockImplementation((templates, variables) => { - trackCalls.push({ templates, variables }); - }); - - return VastParser.getAndParseVAST(wrapperInvalidVastUrl, { - urlHandler: nodeURLHandler, - }).then((res) => { - expect(res.ads).toHaveLength(0); - expect(errorData).toHaveLength(1); - expect(errorData[0]).toEqual({ - ERRORCODE: 301, - ERRORMESSAGE: 'Invalid VAST XMLDocument', - extensions: [ - { - attributes: {}, - name: 'Extension', - value: null, - children: [ - { - attributes: {}, - name: 'paramWrapperInvalidXmlfile', - value: 'valueWrapperInvalidXmlfile', - children: [], - }, - ], - }, - ], - system: { value: 'VAST', version: null }, - }); - - expect(trackCalls).toHaveLength(1); - expect(trackCalls[0]).toEqual({ - templates: [ - 'http://example.com/wrapper-invalid-xmlfile_wrapper-error', - ], - variables: { ERRORCODE: 301 }, - }); - done(); - }); - }); - }); - }); - }); + // describe('getAndParseVAST', () => { + // beforeEach(() => { + // jest.spyOn(VastParser, 'initParsingStatus'); + // jest.spyOn(VastParser, 'fetchVAST'); + // jest.spyOn(VastParser, 'parse'); + // jest.spyOn(VastParser, 'buildVASTResponse'); + // }); + + // it('passes options to initParsingStatus and assigns rootUrl', () => { + // VastParser.getAndParseVAST(wrapperAVastUrl, { + // wrapperLimit: 8, + // urlHandler: nodeURLHandler, + // }); + + // expect(VastParser.initParsingStatus).toHaveBeenCalledWith({ + // wrapperLimit: 8, + // urlHandler: nodeURLHandler, + // }); + // expect(VastParser.rootURL).toBe(wrapperAVastUrl); + // }); + + // describe('on success', () => { + // it('calls fetchVast with correct params multiple times', () => { + // return VastParser.getAndParseVAST(wrapperAVastUrl, { + // wrapperLimit: 8, + // urlHandler: nodeURLHandler, + // }).finally(() => { + // expect(VastParser.fetchVAST).toHaveBeenCalledTimes(4); + // expect(VastParser.fetchVAST.mock.calls).toEqual( + // expect.arrayContaining([ + // [wrapperAVastUrl], + // [wrapperBVastUrl, 1, wrapperAVastUrl, expect.any(Object)], + // [inlineVpaidVastUrl, 1, wrapperAVastUrl, expect.any(Object)], + // [inlineSampleVastUrl, 2, wrapperBVastUrl, expect.any(Object)], + // [inlineSampleVastUrl, 2, wrapperBVastUrl, expect.any(Object)], + // ]) + // ); + // }); + // }); + + // it('emits events in the right order', () => { + // return VastParser.getAndParseVAST(wrapperAVastUrl, { + // wrapperLimit: 8, + // urlHandler: nodeURLHandler, + // }).finally(() => { + // expect(VastParser.emit).toHaveBeenCalledTimes(14); + // expect(VastParser.emit.mock.calls).toEqual( + // expect.arrayContaining([ + // // WRAPPER A + // [ + // 'VAST-resolving', + // { + // url: wrapperAVastUrl, + // previousUrl: null, + // wrapperDepth: 0, + // maxWrapperDepth: 8, + // timeout: 120000, + // wrapperAd: expect.any(Object), + // }, + // ], + // [ + // 'VAST-resolved', + // { + // url: wrapperAVastUrl, + // previousUrl: null, + // wrapperDepth: 0, + // error: null, + // duration: expect.any(Number), + // byteLength: expect.any(Number), + // }, + // ], + // [ + // 'VAST-ad-parsed', + // { + // type: 'WRAPPER', + // url: wrapperAVastUrl, + // wrapperDepth: 0, + // adIndex: 0, + // vastVersion: '2.0', + // }, + // ], + // [ + // 'VAST-ad-parsed', + // { + // type: 'WRAPPER', + // url: wrapperAVastUrl, + // wrapperDepth: 0, + // adIndex: 1, + // vastVersion: '2.0', + // }, + // ], + // // RESOLVING AD 1 (WRAPPER B) IN WRAPPER A + // [ + // 'VAST-resolving', + // { + // url: wrapperBVastUrl, + // previousUrl: wrapperAVastUrl, + // wrapperDepth: 1, + // maxWrapperDepth: 8, + // timeout: 120000, + // wrapperAd: expect.any(Object), + // }, + // ], + // // RESOLVING AD 2 (WRAPPER VPAID) IN WRAPPER A + // [ + // 'VAST-resolving', + // { + // url: inlineVpaidVastUrl, + // previousUrl: wrapperAVastUrl, + // wrapperDepth: 1, + // maxWrapperDepth: 8, + // timeout: 120000, + // wrapperAd: expect.any(Object), + // }, + // ], + // // AD 1 (WRAPPER B) IN WRAPPER A + // [ + // 'VAST-resolved', + // { + // url: wrapperBVastUrl, + // previousUrl: wrapperAVastUrl, + // wrapperDepth: 1, + // error: null, + // duration: expect.any(Number), + // byteLength: expect.any(Number), + // }, + // ], + // [ + // 'VAST-ad-parsed', + // { + // type: 'WRAPPER', + // url: wrapperBVastUrl, + // wrapperDepth: 1, + // adIndex: 0, + // vastVersion: '2.0', + // }, + // ], + // // AD 1 (WRAPPER SAMPLE) IN WRAPPER B + // [ + // 'VAST-resolving', + // { + // url: inlineSampleVastUrl, + // previousUrl: wrapperBVastUrl, + // wrapperDepth: 2, + // maxWrapperDepth: 8, + // timeout: 120000, + // wrapperAd: expect.any(Object), + // }, + // ], + // // AD 2 (WRAPPER VPAID) IN WRAPPER A + // [ + // 'VAST-resolved', + // { + // url: inlineVpaidVastUrl, + // previousUrl: wrapperAVastUrl, + // wrapperDepth: 1, + // error: null, + // duration: expect.any(Number), + // byteLength: expect.any(Number), + // }, + // ], + // [ + // 'VAST-ad-parsed', + // { + // type: 'INLINE', + // url: inlineVpaidVastUrl, + // wrapperDepth: 1, + // adIndex: 0, + // vastVersion: '2.0', + // }, + // ], + // // AD 1 (WRAPPER SAMPLE) IN WRAPPER B + // [ + // 'VAST-resolved', + // { + // url: inlineSampleVastUrl, + // previousUrl: wrapperBVastUrl, + // wrapperDepth: 2, + // error: null, + // duration: expect.any(Number), + // byteLength: expect.any(Number), + // }, + // ], + // [ + // 'VAST-ad-parsed', + // { + // type: 'INLINE', + // url: inlineSampleVastUrl, + // wrapperDepth: 2, + // adIndex: 0, + // vastVersion: '2.1', + // }, + // ], + // [ + // 'VAST-ad-parsed', + // { + // type: 'INLINE', + // url: inlineSampleVastUrl, + // wrapperDepth: 2, + // adIndex: 1, + // vastVersion: '2.1', + // }, + // ], + // ]) + // ); + // }); + // }); + + // it('calls parse with correct params multiple times', () => { + // return VastParser.getAndParseVAST(wrapperAVastUrl, { + // wrapperLimit: 8, + // urlHandler: nodeURLHandler, + // }).finally(() => { + // expect(VastParser.parse).toHaveBeenCalledTimes(4); + // expect(VastParser.parse.mock.calls).toEqual( + // expect.arrayContaining([ + // [ + // jasmine.any(Object), + // { + // wrapperLimit: 8, + // urlHandler: nodeURLHandler, + // previousUrl: wrapperAVastUrl, + // isRootVAST: true, + // url: wrapperAVastUrl, + // }, + // ], + // [ + // jasmine.any(Object), + // { + // url: wrapperBVastUrl, + // previousUrl: wrapperAVastUrl, + // wrapperDepth: 1, + // wrapperSequence: null, + // allowMultipleAds: false, + // followAdditionalWrappers: true, + // }, + // ], + // [ + // jasmine.any(Object), + // { + // url: inlineSampleVastUrl, + // previousUrl: wrapperBVastUrl, + // wrapperDepth: 2, + // wrapperSequence: null, + // allowMultipleAds: false, + // followAdditionalWrappers: true, + // }, + // ], + // [ + // jasmine.any(Object), + // { + // url: inlineSampleVastUrl, + // previousUrl: wrapperBVastUrl, + // wrapperDepth: 2, + // wrapperSequence: null, + // allowMultipleAds: false, + // followAdditionalWrappers: true, + // }, + // ], + // ]) + // ); + // }); + // }); + + // it('calls buildVASTResponse with correct params one time', () => { + // return VastParser.getAndParseVAST(wrapperAVastUrl, { + // wrapperLimit: 8, + // urlHandler: nodeURLHandler, + // }).finally(() => { + // expect(VastParser.buildVASTResponse).toBeCalledTimes(1); + // }); + // }); + // }); + + // describe('on failure', () => { + // it('fails on bad fetch request', () => { + // return VastParser.getAndParseVAST('badUrl', { + // urlHandler: nodeURLHandler, + // }) + // .then(() => { + // expect(true).toBeFalsy(); + // }) + // .catch((e) => { + // expect(e).toBeTruthy(); + // expect(VastParser.parse).not.toBeCalled(); + // }); + // }); + + // describe('invalid VAST xml', () => { + // it('when inline, rejects with error', () => { + // return VastParser.getAndParseVAST(inlineInvalidVastUrl, { + // urlHandler: nodeURLHandler, + // }) + // .then(() => { + // expect(true).toBeFalsy(); + // }) + // .catch((e) => { + // expect(e.message).toEqual('Invalid VAST XMLDocument'); + // expect(VastParser.buildVASTResponse).not.toBeCalled(); + // }); + // }); + // it('when wrapped, emits a VAST-error & track', (done) => { + // const errorData = []; + // const trackCalls = []; + // VastParser.on('VAST-error', (data) => errorData.push(data)); + // jest + // .spyOn(util, 'track') + // .mockImplementation((templates, variables) => { + // trackCalls.push({ templates, variables }); + // }); + + // return VastParser.getAndParseVAST(wrapperInvalidVastUrl, { + // urlHandler: nodeURLHandler, + // }).then((res) => { + // expect(res.ads).toHaveLength(0); + // expect(errorData).toHaveLength(1); + // expect(errorData[0]).toEqual({ + // ERRORCODE: 301, + // ERRORMESSAGE: 'Invalid VAST XMLDocument', + // extensions: [ + // { + // attributes: {}, + // name: 'Extension', + // value: null, + // children: [ + // { + // attributes: {}, + // name: 'paramWrapperInvalidXmlfile', + // value: 'valueWrapperInvalidXmlfile', + // children: [], + // }, + // ], + // }, + // ], + // system: { value: 'VAST', version: null }, + // }); + + // expect(trackCalls).toHaveLength(1); + // expect(trackCalls[0]).toEqual({ + // templates: [ + // 'http://example.com/wrapper-invalid-xmlfile_wrapper-error', + // ], + // variables: { ERRORCODE: 301 }, + // }); + // done(); + // }); + // }); + // }); + // }); + // }); describe('parseVastXml', () => { it('handles invalid XML vast', () => { @@ -631,6 +494,8 @@ describe('VASTParser', () => { }); it('handles Error tag for root VAST', () => { + //initParsingStatus always will be called before parseVastXml + VastParser.initParsingStatus(); VastParser.parseVastXml(errorXml, { isRootVAST: true }); expect(VastParser.rootErrorURLTemplates).toEqual([ 'http://example.com/empty-no-ad', @@ -638,6 +503,7 @@ describe('VASTParser', () => { }); it('handles Error tag for not root VAST', () => { + VastParser.initParsingStatus(); VastParser.parseVastXml(errorXml, { isRootVAST: false }); expect(VastParser.errorURLTemplates).toEqual([ 'http://example.com/empty-no-ad', @@ -782,6 +648,7 @@ describe('VASTParser', () => { }); describe('resolveWrappers', () => { + let fetcher; const ad = { id: null, sequence: 1, @@ -803,6 +670,7 @@ describe('VASTParser', () => { }; beforeEach(() => { + fetcher = new Fetcher(); VastParser.previousUrl = wrapperAVastUrl; VastParser.initParsingStatus({ urlHandler: urlHandlerSuccess }); }); @@ -815,15 +683,24 @@ describe('VASTParser', () => { it('will add errorcode to resolved ad if parsing has reached maximum amount of unwrapping', () => { const adWithWrapper = { ...ad, nextWrapperURL: 'http://example.com/foo' }; + VastParser.fetchingMethod = () => {}; VastParser.maxWrapperDepth = 10; return VastParser.resolveWrappers(adWithWrapper, 10).then((res) => { - expect(res).toEqual({ ...ad, errorCode: 302 }); + expect(res).toEqual({ + ...ad, + errorCode: 302, + }); }); }); it('will successfully fetch the next wrapper url if it is provided', () => { const adWithWrapper = { ...ad, nextWrapperURL: wrapperBVastUrl }; - jest.spyOn(VastParser, 'fetchVAST'); + + jest.spyOn(fetcher, 'fetchVAST').mockImplementation(() => { + return Promise.resolve(expect.any(Object)); + }); + VastParser.fetchingMethod = fetcher.fetchVAST; + jest .spyOn(VastParser, 'parse') .mockImplementation(() => Promise.resolve([ad])); @@ -832,11 +709,11 @@ describe('VASTParser', () => { return VastParser.resolveWrappers(adWithWrapper, 0, wrapperAVastUrl).then( (res) => { - expect(VastParser.fetchVAST).toHaveBeenCalledWith( + expect(fetcher.fetchVAST).toHaveBeenCalledWith( wrapperBVastUrl, - 1, - wrapperAVastUrl, - adWithWrapper + { previousUrl: null, wrapperAd: null, wrapperDepth: 0 }, + VastParser.maxWrapperDepth, + expect.any(Function) ); expect(VastParser.parse).toHaveBeenCalledWith(expect.any(Object), { url: wrapperBVastUrl, @@ -851,20 +728,23 @@ describe('VASTParser', () => { }); it('will pass timeout error to ad if fetching next wrapper fails', () => { - VastParser.initParsingStatus({ urlHandler: urlHandlerFailure }); + fetcher.setOptions({ urlHandler: urlHandlerFailure }); const adWithWrapper = { ...ad, nextWrapperURL: wrapperBVastUrl }; - jest.spyOn(VastParser, 'fetchVAST'); + jest.spyOn(fetcher, 'fetchVAST').mockImplementation(() => { + return Promise.reject(new Error('timeout')); + }); + VastParser.fetchingMethod = fetcher.fetchVAST; jest.spyOn(VastParser, 'parse'); jest.spyOn(parserUtils, 'mergeWrapperAdData'); VastParser.maxWrapperDepth = 10; return VastParser.resolveWrappers(adWithWrapper, 0, wrapperAVastUrl).then( (res) => { - expect(VastParser.fetchVAST).toHaveBeenCalledWith( + expect(fetcher.fetchVAST).toHaveBeenCalledWith( wrapperBVastUrl, - 1, - wrapperAVastUrl, - adWithWrapper + { previousUrl: null, wrapperAd: null, wrapperDepth: 0 }, + VastParser.maxWrapperDepth, + expect.any(Function) ); expect(VastParser.parse).not.toHaveBeenCalled(); expect(parserUtils.mergeWrapperAdData).not.toBeCalled(); @@ -880,8 +760,9 @@ describe('VASTParser', () => { it('will take the allowMultipleAds value from the option', () => { jest - .spyOn(VastParser, 'fetchVAST') + .spyOn(fetcher, 'fetchVAST') .mockReturnValue(Promise.resolve('')); + VastParser.fetchingMethod = fetcher.fetchVAST; jest.spyOn(VastParser, 'parse').mockReturnValue(Promise.resolve()); const adWithWrapper = { @@ -903,8 +784,9 @@ describe('VASTParser', () => { it('will take the allowMultipleAds value from the ad if does not set in the option', () => { jest - .spyOn(VastParser, 'fetchVAST') + .spyOn(fetcher, 'fetchVAST') .mockReturnValue(Promise.resolve('')); + VastParser.fetchingMethod = fetcher.fetchVAST; jest.spyOn(VastParser, 'parse').mockReturnValue(Promise.resolve()); const expectedValue = { allowMultipleAds: true }; diff --git a/src/fetcher.js b/src/fetcher.js index 3b75d209..a4b558d4 100644 --- a/src/fetcher.js +++ b/src/fetcher.js @@ -15,7 +15,7 @@ export class Fetcher { setOptions(options = {}) { this.urlHandler = options.urlHandler || options.urlhandler || urlHandler; - this.URLTemplateFilters = []; + // this.URLTemplateFilters = []; this.fetchingOptions = { timeout: options.timeout || DEFAULT_TIMEOUT, withCredentials: options.withCredentials || false, @@ -65,7 +65,6 @@ export class Fetcher { * @param {String} previousUrl - Url of the previous VAST. * @param {Object} wrapperAd - Previously parsed ad node (Wrapper) related to this fetching. * @param {Number} maxWrapperDepth - The maximum number of Wrapper that can be fetch - * @param {Array} parentUrls Array containing all the previous fetched URL * @param {emitter} emitter - The function used to Emit event * @emits VASTParser#VAST-resolving * @emits VASTParser#VAST-resolved @@ -76,7 +75,6 @@ export class Fetcher { url, { wrapperDepth = 0, previousUrl = null, wrapperAd = null } = {}, maxWrapperDepth, - parentURLs, emitter ) { return new Promise((resolve, reject) => { @@ -89,8 +87,6 @@ export class Fetcher { this.rootURL = url; - parentURLs.push(url); - emitter('VAST-resolving', { url, previousUrl, diff --git a/src/parser/vast_parser.js b/src/parser/vast_parser.js index d1730cbb..8a224ab2 100644 --- a/src/parser/vast_parser.js +++ b/src/parser/vast_parser.js @@ -26,6 +26,7 @@ export class VASTParser extends EventEmitter { super(); this.maxWrapperDepth = null; this.fetchingMethod = null; + this.remainingAds = []; } /** @@ -76,8 +77,6 @@ export class VASTParser extends EventEmitter { */ resetParsingStatus() { this.errorURLTemplates = []; - this.parentURLs = []; - this.remainingAds = []; this.rootErrorURLTemplates = []; this.rootURL = ''; this.vastVersion = null; @@ -401,7 +400,6 @@ export class VASTParser extends EventEmitter { ad.nextWrapperURL, { wrapperDepth: 0, previousUrl: null, wrapperAd: null }, this.maxWrapperDepth, - this.parentURLs, this.emit.bind(this) ) .then((xml) => { diff --git a/src/vast_client.js b/src/vast_client.js index 1a631204..640b0bbe 100644 --- a/src/vast_client.js +++ b/src/vast_client.js @@ -183,12 +183,12 @@ export class VASTClient { url, { wrapperDepth: 0, previousUrl: null, wrapperAd: null }, this.vastParser.maxWrapperDepth, - this.vastParser.parentURLs, this.vastParser.emit.bind(this.vastParser) ) .then((xml) => { options.previousUrl = url; options.isRootVAST = true; + options.url = url; return this.vastParser.parse(xml, options).then((resolvedAd) => { const vastResponse = this.vastParser.buildVASTResponse(resolvedAd); resolve(vastResponse); From 3d303d6d3d322705f36e2a538680ec3734bd3070 Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Thu, 17 Aug 2023 11:40:55 +0200 Subject: [PATCH 09/95] [test] moving mocha tests to jest --- spec/fetcher.spec.js | 46 + spec/samples/empty-no-creative.xml | 19 + spec/samples/inline_trackers.js | 121 ++- spec/samples/wrapper-empty-no-creative.xml | 29 + spec/samples/wrapper-empty.xml | 38 + spec/samples/wrapper-legacy.xml | 36 + spec/samples/wrapper-unavailable-url.xml | 38 + spec/util.spec.js | 22 +- spec/vast_client.spec.js | 61 +- spec/vast_parser.spec.js | 703 +++++++------ spec/vast_trackers.spec.js | 396 +++++++- src/fetcher.js | 2 - src/parser/vast_parser.js | 7 +- src/vast_client.js | 1 - test/vast_parser.js | 1029 -------------------- test/vast_tracker.js | 661 ------------- 16 files changed, 1106 insertions(+), 2103 deletions(-) create mode 100644 spec/samples/empty-no-creative.xml create mode 100644 spec/samples/wrapper-empty-no-creative.xml create mode 100644 spec/samples/wrapper-empty.xml create mode 100644 spec/samples/wrapper-legacy.xml create mode 100644 spec/samples/wrapper-unavailable-url.xml delete mode 100644 test/vast_parser.js delete mode 100644 test/vast_tracker.js diff --git a/spec/fetcher.spec.js b/spec/fetcher.spec.js index 664afad4..739d207e 100644 --- a/spec/fetcher.spec.js +++ b/spec/fetcher.spec.js @@ -191,4 +191,50 @@ describe('Fetcher', () => { expect(fetcher.fetchingOptions.withCredentials).toEqual(false); }); }); + describe('URLtemplatefilter modifications', () => { + let filter = jest.fn(); + beforeEach(() => { + fetcher.URLTemplateFilters = [filter]; + }); + describe('addURLTemplateFilter', () => { + it('add given urlTemplateFilters to the fetcher instance', () => { + const myFilter = jest.fn(); + const anotherFilter = jest.fn(); + fetcher.addURLTemplateFilter(myFilter); + fetcher.addURLTemplateFilter(anotherFilter); + expect(fetcher.URLTemplateFilters).toEqual([ + filter, + myFilter, + anotherFilter, + ]); + }); + it('does not add given urlTemplateFilters when wrong type', () => { + fetcher.addURLTemplateFilter(1); + fetcher.addURLTemplateFilter('2'); + fetcher.addURLTemplateFilter(true); + fetcher.addURLTemplateFilter(); + expect(fetcher.URLTemplateFilters.length).toBe(1); + }); + }); + describe('clearURLTemplateFilters', () => { + it('should clear URLTemplateFilters array', () => { + fetcher.clearURLTemplateFilters(); + expect(fetcher.URLTemplateFilters).toEqual([]); + }); + }); + + describe('countURLTemplateFilters', () => { + it('should return the length of URLTemplateFilters array', () => { + let result = fetcher.countURLTemplateFilters(); + expect(result).toBe(1); + }); + }); + + describe('removeURLTemplateFilter', () => { + it('remove the last element of the URLTemplateFilters array', () => { + fetcher.removeURLTemplateFilter(); + expect(fetcher.URLTemplateFilters.length).toBe(0); + }); + }); + }); }); diff --git a/spec/samples/empty-no-creative.xml b/spec/samples/empty-no-creative.xml new file mode 100644 index 00000000..85a9a96e --- /dev/null +++ b/spec/samples/empty-no-creative.xml @@ -0,0 +1,19 @@ + + + + + No Ad Server + NO AD + + + + + + + + + + + + + diff --git a/spec/samples/inline_trackers.js b/spec/samples/inline_trackers.js index 4604615a..23f13cf7 100644 --- a/spec/samples/inline_trackers.js +++ b/spec/samples/inline_trackers.js @@ -69,7 +69,7 @@ export const inlineTrackersParsed = { apiFramework: null, type: 'linear', duration: 90.123, - skipDelay: null, + skipDelay: 3, mediaFiles: [ { id: null, @@ -170,6 +170,18 @@ export const inlineTrackersParsed = { midpoint: ['http://example.com/linear-midpoint'], complete: ['http://example.com/linear-complete'], start: ['http://example.com/linear-start'], + loaded: ['http://example.com/linear-loaded'], + complete: ['http://example.com/linear-complete'], + mute: ['http://example.com/linear-muted'], + unmute: ['http://example.com/linear-unmute'], + pause: ['http://example.com/linear-pause'], + resume: ['http://example.com/linear-resume'], + rewind: ['http://example.com/linear-rewind'], + skip: ['http://example.com/linear-skip'], + playerExpand: ['http://example.com/linear-playerExpant'], + playerCollapse: ['http://example.com/linear-playerCollapse'], + fullscreen: ['http://exemple.com/linear-fullscreen'], + exitFullscreen: ['http://exemple.com/linear-exitFullscreen'], firstQuartile: ['http://example.com/linear-firstQuartile'], close: ['http://example.com/linear-close'], thirdQuartile: ['http://example.com/linear-thirdQuartile'], @@ -200,6 +212,113 @@ export const inlineTrackersParsed = { }, ], }, + { + id: '5480', + adId: '2447226', + sequence: '1', + apiFramework: 'omid', + type: 'nonlinear', + variations: [ + { + id: null, + width: '350px', + height: '350px', + expandedWidth: '550px', + expandedHeight: '550px', + scalable: true, + maintainAspectRatio: true, + minSuggestedDuration: 10, + apiFramework: 'omid', + adType: 'nonLinearAd', + type: 'image/png', + staticResource: + 'https://mms.businesswire.com/media/20150623005446/en/473787/21/iab_tech_lab.jpg', + htmlResource: + '\n \n \n \n \n \n Document\n \n \n

titre

\n \n ', + iframeResource: 'lient vers html', + nonlinearClickThroughURLTemplate: 'https://iabtechlab.com', + nonlinearClickTrackingURLTemplates: [ + { + id: null, + url: 'https://example.com/tracking/clickTracking', + }, + ], + adParameters: 'campaign_id=1', + }, + ], + trackingEvents: { + progress: ['http://example.com/tracking/progress-10'], + firstQuartile: ['https://example.com/tracking/firstQuartile'], + midpoint: ['https://example.com/tracking/midpoint'], + thirdQuartile: ['https://example.com/tracking/thirdQuartile'], + complete: ['https://example.com/tracking/complete'], + }, + universalAdIds: [ + { + idRegistry: 'ad-id.org', + value: 'CNPA0484000H', + }, + ], + creativeExtensions: [ + { + name: 'CreativeExtension', + value: null, + attributes: { + type: 'application/javascript', + }, + children: [], + }, + ], + }, + { + id: '5480', + adId: '2447226', + sequence: '1', + apiFramework: null, + type: 'companion', + required: null, + variations: [ + { + id: '1232', + adType: 'companionAd', + width: '100', + height: '150', + assetWidth: '250', + assetHeight: '200', + expandedWidth: '350', + expandedHeight: '250', + apiFramework: null, + adSlotID: null, + pxratio: '1400', + renderingMode: 'default', + staticResources: [ + { + url: 'https://www.iab.com/wp-content/uploads/2014/09/iab-tech-lab-6-644x290.png', + creativeType: 'image/png', + }, + ], + htmlResources: [], + iframeResources: [], + adParameters: null, + xmlEncoded: null, + altText: null, + companionClickThroughURLTemplate: 'https://iabtechlab.com', + companionClickTrackingURLTemplates: [ + { + id: null, + url: 'https://example.com/tracking/clickTracking', + }, + ], + trackingEvents: {}, + }, + ], + universalAdIds: [ + { + idRegistry: 'Ad-ID', + value: '8465', + }, + ], + }, ], extensions: [ { diff --git a/spec/samples/wrapper-empty-no-creative.xml b/spec/samples/wrapper-empty-no-creative.xml new file mode 100644 index 00000000..5dfd2a1b --- /dev/null +++ b/spec/samples/wrapper-empty-no-creative.xml @@ -0,0 +1,29 @@ + + + + + VAST + empty-no-creative.xml + http://example.com/wrapper-no-creative_wrapper-error + http://example.com/wrapper-impression + + + + + + + + + + + + + + + + + + + + + diff --git a/spec/samples/wrapper-empty.xml b/spec/samples/wrapper-empty.xml new file mode 100644 index 00000000..bc32f984 --- /dev/null +++ b/spec/samples/wrapper-empty.xml @@ -0,0 +1,38 @@ + + + + + VAST + empty-no-ad.xml + http://example.com/wrapper-empty_wrapper-error + http://example.com/wrapper-impression + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spec/samples/wrapper-legacy.xml b/spec/samples/wrapper-legacy.xml new file mode 100644 index 00000000..82c95dfd --- /dev/null +++ b/spec/samples/wrapper-legacy.xml @@ -0,0 +1,36 @@ + + + + + + VAST + + + + http://example.com/wrapper-error + http://example.com/wrapper-impression + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spec/samples/wrapper-unavailable-url.xml b/spec/samples/wrapper-unavailable-url.xml new file mode 100644 index 00000000..6f3674ff --- /dev/null +++ b/spec/samples/wrapper-unavailable-url.xml @@ -0,0 +1,38 @@ + + + + + VAST + invalid-url + http://example.com/wrapper-invalid-xmlfile_wrapper-error + http://example.com/wrapper-invalid-xmlfile_impression + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spec/util.spec.js b/spec/util.spec.js index c2ad8049..b2c53117 100644 --- a/spec/util.spec.js +++ b/spec/util.spec.js @@ -12,8 +12,8 @@ describe('util', function () { const encodedPlayhead = encodeRFC3986(playhead); const encodedTimestamp = encodeRFC3986(now); - const resolve = (URLTemplate, macros) => - util.resolveURLTemplates([URLTemplate], macros)[0]; + const resolve = (URLTemplate, macros, options = {}) => + util.resolveURLTemplates([URLTemplate], macros, options)[0]; const realDateToISOString = Date.prototype.toISOString; @@ -25,6 +25,24 @@ describe('util', function () { Date.prototype.toISOString = realDateToISOString; }); + describe('ERRORCODE', () => { + it('should resolve ERRORCODE with 900 if ERRORCODE is invalid ', () => { + expect( + resolve('http://example.com/[ERRORCODE]', { ERRORCODE: 10001 }) + ).toBe('http://example.com/900'); + }); + + it('should have called error urls with custom code when enabled', () => { + expect( + resolve( + 'http://example.com/[ERRORCODE]', + { ERRORCODE: 10001 }, + { isCustomCode: true } + ) + ).toBe('http://example.com/10001'); + }); + }); + describe('assetURI', function () { it('should resolve assetURI', () => { expect( diff --git a/spec/vast_client.spec.js b/spec/vast_client.spec.js index 98f8cbe7..3a770f67 100644 --- a/spec/vast_client.spec.js +++ b/spec/vast_client.spec.js @@ -1,6 +1,8 @@ import { VASTClient } from '../src/vast_client'; import { nodeURLHandler } from '../src/urlhandlers/node_url_handler'; import { urlFor } from './utils/utils'; +import 'regenerator-runtime/runtime'; +import { Fetcher } from '../src/fetcher'; const wrapperMultipleAdsVastUrl = urlFor('wrapper-multiple-ads.xml'); const emptyVastUrl = urlFor('empty-no-ad.xml'); @@ -98,33 +100,16 @@ describe('VASTClient', () => { VastParser = VastClient.getParser(); }); - // it('should call getAndParseVAST with correct options', () => { - // jest - // .spyOn(VastParser, 'getAndParseVAST') - // .mockImplementation(() => Promise.resolve()); - // return VastClient.get(wrapperMultipleAdsVastUrl, options).then(() => { - // expect(VastParser.getAndParseVAST).toHaveBeenNthCalledWith( - // 1, - // wrapperMultipleAdsVastUrl, - // { - // urlhandler: nodeURLHandler, - // resolveAll: false, - // withCredentials: true, - // timeout: 0, - // } - // ); - // }); - // }); - describe('with resolveAll set to false', () => { - const optionsWithNoResolveAll = { ...options, resolveAll: false }; + const optionsWithNoResolveAll = { + ...options, + resolveAll: false, + }; let res; - beforeEach((done) => { - VastClient.get(wrapperMultipleAdsVastUrl, optionsWithNoResolveAll).then( - (results) => { - res = results; - done(); - } + beforeEach(async () => { + res = await VastClient.get( + wrapperMultipleAdsVastUrl, + optionsWithNoResolveAll ); }); it('returns first ad parsed', () => { @@ -160,23 +145,16 @@ describe('VASTClient', () => { }); describe('getNextAds', () => { - it('resolves all next ads if requested', () => { - VastClient.get(wrapperMultipleAdsVastUrl, optionsWithNoResolveAll) - .then((res1) => { - VastClient.getNextAds(true) - .then((res) => { - expect(res).toEqual({ - ads: expect.any(Array), - errorURLTemplates: [], - version: '3.0', - }); - expect(res.ads).toHaveLength(3); - }) - .catch((err) => console.log(err)); - }) - .catch((err) => console.log(err)); + it('resolves all next ads if requested', async () => { + let res = await VastClient.getNextAds(true); + expect(res).toEqual({ + ads: expect.any(Array), + errorURLTemplates: [], + version: '3.0', + }); + expect(res.ads).toHaveLength(3); }); - it('resolves only next ad if requested', () => { + it('resolves only next ad if requested', (done) => { VastClient.get( wrapperMultipleAdsVastUrl, optionsWithNoResolveAll @@ -191,6 +169,7 @@ describe('VASTClient', () => { expect(res.ads).toHaveLength(2); }) .catch((err) => console.log(err)); + done(); }); }); }); diff --git a/spec/vast_parser.spec.js b/spec/vast_parser.spec.js index 66fae59a..f07134da 100644 --- a/spec/vast_parser.spec.js +++ b/spec/vast_parser.spec.js @@ -5,8 +5,13 @@ import { util } from '../src/util/util'; import { parserUtils } from '../src/parser/parser_utils'; import * as Bitrate from '../src/parser/bitrate'; import { Fetcher } from '../src/fetcher'; +import { linearAd } from './samples/linear_ads'; +import { VASTClient } from '../src/vast_client'; -const xml = new DOMParser().parseFromString('', 'text/xml'); +const xml = new DOMParser().parseFromString( + `${linearAd}`, + 'text/xml' +); const urlHandlerSuccess = { get: (url, options, cb) => { cb(null, xml, { byteLength: 1234, statusCode: 200 }); @@ -49,7 +54,9 @@ const ad = { }; describe('VASTParser', () => { + let vastClient; let VastParser; + let fetcher; let inlineXml, invalidXml, errorXml, wrapperXml; beforeAll((done) => { @@ -69,6 +76,8 @@ describe('VASTParser', () => { beforeEach(() => { VastParser = new VASTParser(); + vastClient = new VASTClient(); + fetcher = new Fetcher(); jest.spyOn(VastParser, 'emit'); }); @@ -114,357 +123,6 @@ describe('VASTParser', () => { }); }); - // describe('getAndParseVAST', () => { - // beforeEach(() => { - // jest.spyOn(VastParser, 'initParsingStatus'); - // jest.spyOn(VastParser, 'fetchVAST'); - // jest.spyOn(VastParser, 'parse'); - // jest.spyOn(VastParser, 'buildVASTResponse'); - // }); - - // it('passes options to initParsingStatus and assigns rootUrl', () => { - // VastParser.getAndParseVAST(wrapperAVastUrl, { - // wrapperLimit: 8, - // urlHandler: nodeURLHandler, - // }); - - // expect(VastParser.initParsingStatus).toHaveBeenCalledWith({ - // wrapperLimit: 8, - // urlHandler: nodeURLHandler, - // }); - // expect(VastParser.rootURL).toBe(wrapperAVastUrl); - // }); - - // describe('on success', () => { - // it('calls fetchVast with correct params multiple times', () => { - // return VastParser.getAndParseVAST(wrapperAVastUrl, { - // wrapperLimit: 8, - // urlHandler: nodeURLHandler, - // }).finally(() => { - // expect(VastParser.fetchVAST).toHaveBeenCalledTimes(4); - // expect(VastParser.fetchVAST.mock.calls).toEqual( - // expect.arrayContaining([ - // [wrapperAVastUrl], - // [wrapperBVastUrl, 1, wrapperAVastUrl, expect.any(Object)], - // [inlineVpaidVastUrl, 1, wrapperAVastUrl, expect.any(Object)], - // [inlineSampleVastUrl, 2, wrapperBVastUrl, expect.any(Object)], - // [inlineSampleVastUrl, 2, wrapperBVastUrl, expect.any(Object)], - // ]) - // ); - // }); - // }); - - // it('emits events in the right order', () => { - // return VastParser.getAndParseVAST(wrapperAVastUrl, { - // wrapperLimit: 8, - // urlHandler: nodeURLHandler, - // }).finally(() => { - // expect(VastParser.emit).toHaveBeenCalledTimes(14); - // expect(VastParser.emit.mock.calls).toEqual( - // expect.arrayContaining([ - // // WRAPPER A - // [ - // 'VAST-resolving', - // { - // url: wrapperAVastUrl, - // previousUrl: null, - // wrapperDepth: 0, - // maxWrapperDepth: 8, - // timeout: 120000, - // wrapperAd: expect.any(Object), - // }, - // ], - // [ - // 'VAST-resolved', - // { - // url: wrapperAVastUrl, - // previousUrl: null, - // wrapperDepth: 0, - // error: null, - // duration: expect.any(Number), - // byteLength: expect.any(Number), - // }, - // ], - // [ - // 'VAST-ad-parsed', - // { - // type: 'WRAPPER', - // url: wrapperAVastUrl, - // wrapperDepth: 0, - // adIndex: 0, - // vastVersion: '2.0', - // }, - // ], - // [ - // 'VAST-ad-parsed', - // { - // type: 'WRAPPER', - // url: wrapperAVastUrl, - // wrapperDepth: 0, - // adIndex: 1, - // vastVersion: '2.0', - // }, - // ], - // // RESOLVING AD 1 (WRAPPER B) IN WRAPPER A - // [ - // 'VAST-resolving', - // { - // url: wrapperBVastUrl, - // previousUrl: wrapperAVastUrl, - // wrapperDepth: 1, - // maxWrapperDepth: 8, - // timeout: 120000, - // wrapperAd: expect.any(Object), - // }, - // ], - // // RESOLVING AD 2 (WRAPPER VPAID) IN WRAPPER A - // [ - // 'VAST-resolving', - // { - // url: inlineVpaidVastUrl, - // previousUrl: wrapperAVastUrl, - // wrapperDepth: 1, - // maxWrapperDepth: 8, - // timeout: 120000, - // wrapperAd: expect.any(Object), - // }, - // ], - // // AD 1 (WRAPPER B) IN WRAPPER A - // [ - // 'VAST-resolved', - // { - // url: wrapperBVastUrl, - // previousUrl: wrapperAVastUrl, - // wrapperDepth: 1, - // error: null, - // duration: expect.any(Number), - // byteLength: expect.any(Number), - // }, - // ], - // [ - // 'VAST-ad-parsed', - // { - // type: 'WRAPPER', - // url: wrapperBVastUrl, - // wrapperDepth: 1, - // adIndex: 0, - // vastVersion: '2.0', - // }, - // ], - // // AD 1 (WRAPPER SAMPLE) IN WRAPPER B - // [ - // 'VAST-resolving', - // { - // url: inlineSampleVastUrl, - // previousUrl: wrapperBVastUrl, - // wrapperDepth: 2, - // maxWrapperDepth: 8, - // timeout: 120000, - // wrapperAd: expect.any(Object), - // }, - // ], - // // AD 2 (WRAPPER VPAID) IN WRAPPER A - // [ - // 'VAST-resolved', - // { - // url: inlineVpaidVastUrl, - // previousUrl: wrapperAVastUrl, - // wrapperDepth: 1, - // error: null, - // duration: expect.any(Number), - // byteLength: expect.any(Number), - // }, - // ], - // [ - // 'VAST-ad-parsed', - // { - // type: 'INLINE', - // url: inlineVpaidVastUrl, - // wrapperDepth: 1, - // adIndex: 0, - // vastVersion: '2.0', - // }, - // ], - // // AD 1 (WRAPPER SAMPLE) IN WRAPPER B - // [ - // 'VAST-resolved', - // { - // url: inlineSampleVastUrl, - // previousUrl: wrapperBVastUrl, - // wrapperDepth: 2, - // error: null, - // duration: expect.any(Number), - // byteLength: expect.any(Number), - // }, - // ], - // [ - // 'VAST-ad-parsed', - // { - // type: 'INLINE', - // url: inlineSampleVastUrl, - // wrapperDepth: 2, - // adIndex: 0, - // vastVersion: '2.1', - // }, - // ], - // [ - // 'VAST-ad-parsed', - // { - // type: 'INLINE', - // url: inlineSampleVastUrl, - // wrapperDepth: 2, - // adIndex: 1, - // vastVersion: '2.1', - // }, - // ], - // ]) - // ); - // }); - // }); - - // it('calls parse with correct params multiple times', () => { - // return VastParser.getAndParseVAST(wrapperAVastUrl, { - // wrapperLimit: 8, - // urlHandler: nodeURLHandler, - // }).finally(() => { - // expect(VastParser.parse).toHaveBeenCalledTimes(4); - // expect(VastParser.parse.mock.calls).toEqual( - // expect.arrayContaining([ - // [ - // jasmine.any(Object), - // { - // wrapperLimit: 8, - // urlHandler: nodeURLHandler, - // previousUrl: wrapperAVastUrl, - // isRootVAST: true, - // url: wrapperAVastUrl, - // }, - // ], - // [ - // jasmine.any(Object), - // { - // url: wrapperBVastUrl, - // previousUrl: wrapperAVastUrl, - // wrapperDepth: 1, - // wrapperSequence: null, - // allowMultipleAds: false, - // followAdditionalWrappers: true, - // }, - // ], - // [ - // jasmine.any(Object), - // { - // url: inlineSampleVastUrl, - // previousUrl: wrapperBVastUrl, - // wrapperDepth: 2, - // wrapperSequence: null, - // allowMultipleAds: false, - // followAdditionalWrappers: true, - // }, - // ], - // [ - // jasmine.any(Object), - // { - // url: inlineSampleVastUrl, - // previousUrl: wrapperBVastUrl, - // wrapperDepth: 2, - // wrapperSequence: null, - // allowMultipleAds: false, - // followAdditionalWrappers: true, - // }, - // ], - // ]) - // ); - // }); - // }); - - // it('calls buildVASTResponse with correct params one time', () => { - // return VastParser.getAndParseVAST(wrapperAVastUrl, { - // wrapperLimit: 8, - // urlHandler: nodeURLHandler, - // }).finally(() => { - // expect(VastParser.buildVASTResponse).toBeCalledTimes(1); - // }); - // }); - // }); - - // describe('on failure', () => { - // it('fails on bad fetch request', () => { - // return VastParser.getAndParseVAST('badUrl', { - // urlHandler: nodeURLHandler, - // }) - // .then(() => { - // expect(true).toBeFalsy(); - // }) - // .catch((e) => { - // expect(e).toBeTruthy(); - // expect(VastParser.parse).not.toBeCalled(); - // }); - // }); - - // describe('invalid VAST xml', () => { - // it('when inline, rejects with error', () => { - // return VastParser.getAndParseVAST(inlineInvalidVastUrl, { - // urlHandler: nodeURLHandler, - // }) - // .then(() => { - // expect(true).toBeFalsy(); - // }) - // .catch((e) => { - // expect(e.message).toEqual('Invalid VAST XMLDocument'); - // expect(VastParser.buildVASTResponse).not.toBeCalled(); - // }); - // }); - // it('when wrapped, emits a VAST-error & track', (done) => { - // const errorData = []; - // const trackCalls = []; - // VastParser.on('VAST-error', (data) => errorData.push(data)); - // jest - // .spyOn(util, 'track') - // .mockImplementation((templates, variables) => { - // trackCalls.push({ templates, variables }); - // }); - - // return VastParser.getAndParseVAST(wrapperInvalidVastUrl, { - // urlHandler: nodeURLHandler, - // }).then((res) => { - // expect(res.ads).toHaveLength(0); - // expect(errorData).toHaveLength(1); - // expect(errorData[0]).toEqual({ - // ERRORCODE: 301, - // ERRORMESSAGE: 'Invalid VAST XMLDocument', - // extensions: [ - // { - // attributes: {}, - // name: 'Extension', - // value: null, - // children: [ - // { - // attributes: {}, - // name: 'paramWrapperInvalidXmlfile', - // value: 'valueWrapperInvalidXmlfile', - // children: [], - // }, - // ], - // }, - // ], - // system: { value: 'VAST', version: null }, - // }); - - // expect(trackCalls).toHaveLength(1); - // expect(trackCalls[0]).toEqual({ - // templates: [ - // 'http://example.com/wrapper-invalid-xmlfile_wrapper-error', - // ], - // variables: { ERRORCODE: 301 }, - // }); - // done(); - // }); - // }); - // }); - // }); - // }); - describe('parseVastXml', () => { it('handles invalid XML vast', () => { try { @@ -628,6 +286,345 @@ describe('VASTParser', () => { }); }); + describe('parseVAST', () => { + let options; + let res; + beforeEach(() => { + options = { + wrapperLimit: 5, + allowMultipleAds: true, + byteLength: 1234, + requestDuration: 12000, + }; + jest.spyOn(VastParser, 'initParsingStatus'); + jest + .spyOn(VastParser, 'parse') + .mockImplementation(() => Promise.resolve([linearAd])); + jest.spyOn(VastParser, 'buildVASTResponse').mockReturnValue({ + ads: [linearAd], + errorURLTemplates: [], + version: null, + }); + jest.spyOn(Bitrate, 'updateEstimatedBitrate'); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should return a VAST response object', (done) => { + VastParser.parseVAST(xml, options).then((response) => { + res = response; + expect(res).toEqual({ + ads: [linearAd], + errorURLTemplates: [], + version: null, + }); + expect(VastParser.initParsingStatus).toHaveBeenCalled(); + expect(VastParser.parse).toHaveBeenCalled(); + expect(VastParser.buildVASTResponse).toHaveBeenCalled(); + done(); + }); + }); + + it('should have set the options if given', (done) => { + VastParser.parseVAST(xml, options).then(() => { + expect(VastParser.maxWrapperDepth).toBe(5); + expect(VastParser.parsingOptions).toEqual({ allowMultipleAds: true }); + expect(Bitrate.updateEstimatedBitrate).toHaveBeenCalledWith( + 1234, + 12000 + ); + done(); + }); + }); + + it('should have set the default options if not given', (done) => { + VastParser.parseVAST(xml).then(() => { + expect(VastParser.maxWrapperDepth).toBe(10); + expect(VastParser.parsingOptions).toEqual({ + allowMultipleAds: undefined, + }); + expect(Bitrate.updateEstimatedBitrate).toHaveBeenCalledWith( + undefined, + undefined + ); + }); + done(); + }); + }); + + describe('Tracking', () => { + let trackCalls = null; + let dataTriggered = null; + const options = { + urlhandler: nodeURLHandler, + }; + + const dataFromGivenUrl = (url, options = {}) => { + fetcher.setOptions(options); + return new Promise((resolve, reject) => { + fetcher.urlHandler.get(url, {}, (err, xml) => { + if (err) { + return reject(err); + } + + for (let nodeKey in xml.documentElement.childNodes) { + const node = xml.documentElement.childNodes[nodeKey]; + + if (node.nodeName === 'Ad') { + for (let adNodeKey in node.childNodes) { + const adNode = node.childNodes[adNodeKey]; + + if (adNode.nodeName === 'Wrapper') { + for (let wrapperNodeKey in adNode.childNodes) { + const wrapperNode = adNode.childNodes[wrapperNodeKey]; + + if ( + wrapperNode.nodeName === 'VASTAdTagURI' || + wrapperNode.nodeName === 'VASTAdTagURL' + ) { + wrapperNode.textContent = urlFor( + parserUtils.parseNodeText(wrapperNode) + ); + break; + } + } + } + } + } + } + VastParser.fetchingMethod = fetcher.fetchVAST.bind(fetcher); + VastParser.parseVAST(xml, options).then((response) => { + resolve(response); + }); + }); + }); + }; + + beforeEach(() => { + VastParser.removeAllListeners(); + dataTriggered = []; + trackCalls = []; + + VastParser.on('VAST-error', (variables) => dataTriggered.push(variables)); + + util.track = (templates, variables) => { + trackCalls.push({ + templates, + variables, + }); + }; + }); + + describe('No Ad', () => { + it('should emits a VAST-error & track', (done) => { + let vast = new DOMParser().parseFromString( + `http://example.com/empty-no-ad`, + 'text/xml' + ); + VastParser.parseVAST(vast, options) + .then((response) => { + // Response doesn't have any ads + expect(response.ads).toEqual([]); + expect(dataTriggered.length).toBe(1); + // Error has been triggered + expect(dataTriggered.length).toBe(1); + expect(dataTriggered[0].ERRORCODE).toBe(303); + expect(dataTriggered[0].extensions).toEqual([]); + // Tracking has been done + expect(trackCalls.length).toBe(1); + expect(trackCalls[0].templates).toEqual([ + 'http://example.com/empty-no-ad', + ]); + expect(trackCalls[0].variables).toEqual({ ERRORCODE: 303 }); + done(); + }) + .catch((error) => { + console.error(error); + done(error); + }); + }); + + it('should emits VAST-error & track when wrapped', (done) => { + const url = urlFor('wrapper-empty.xml'); + dataFromGivenUrl(urlFor('wrapper-empty.xml'), options).then( + (response) => { + // Response doesn't have any ads + expect(response.ads).toEqual([]); + //error has been triggered + expect(dataTriggered.length).toBe(1); + expect(dataTriggered[0].ERRORCODE).toBe(303); + expect(dataTriggered[0].extensions[0].children[0].name).toBe( + 'paramWrapperEmptyNoAd' + ); + expect(dataTriggered[0].extensions[0].children[0].value).toBe( + 'valueWrapperEmptyNoAd' + ); + // TRacking has been done + expect(trackCalls.length).toBe(1); + expect(trackCalls[0].templates).toEqual([ + 'http://example.com/wrapper-empty_wrapper-error', + 'http://example.com/empty-no-ad', + ]); + expect(trackCalls[0].variables).toEqual({ ERRORCODE: 303 }); + done(); + } + ); + }); + }); + + describe('Ad with no creatives', () => { + it('should emits a VAST-error & track', (done) => { + dataFromGivenUrl(urlFor('empty-no-creative.xml'), options).then( + (response) => { + // Response doesn't have any ads + expect(response.ads).toEqual([]); + // Error has been triggered + expect(dataTriggered.length).toBe(1); + expect(dataTriggered[0].ERRORCODE).toBe(303); + expect(dataTriggered[0].extensions[0].children[0].name).toBe( + 'paramEmptyNoCreative' + ); + expect(dataTriggered[0].extensions[0].children[0].value).toBe( + 'valueEmptyNoCreative' + ); + // Tracking has been done; + expect(trackCalls.length).toBe(1); + expect(trackCalls[0].templates).toEqual([ + 'http://example.com/empty-no-creative_inline-error', + ]); + expect(trackCalls[0].variables).toEqual({ ERRORCODE: 303 }); + done(); + } + ); + }); + + it('should emits a VAST-ERROR & track when wrapped', (done) => { + dataFromGivenUrl(urlFor('wrapper-empty-no-creative.xml'), options).then( + (response) => { + // Response doesn't have any ads + expect(response.ads).toEqual([]); + // Error has been triggered + expect(dataTriggered.length).toBe(1); + expect(dataTriggered[0].ERRORCODE).toBe(303); + expect(dataTriggered[0].extensions[0].children[0].name).toBe( + 'paramWrapperEmptyNoCreative' + ); + expect(dataTriggered[0].extensions[0].children[0].value).toBe( + 'valueWrapperEmptyNoCreative' + ); + expect(dataTriggered[0].extensions[1].children[0].name).toBe( + 'paramEmptyNoCreative' + ); + expect(dataTriggered[0].extensions[1].children[0].value).toBe( + 'valueEmptyNoCreative' + ); + // Tracking has been done + expect(trackCalls.length).toBe(1); + expect(trackCalls[0].templates).toEqual([ + 'http://example.com/wrapper-no-creative_wrapper-error', + 'http://example.com/empty-no-creative_inline-error', + ]); + expect(trackCalls[0].variables).toEqual({ ERRORCODE: 303 }); + done(); + } + ); + }); + }); + + describe('Wrapper URL unavailable/timeout', () => { + it('sould emits a VAST-error and track', (done) => { + dataFromGivenUrl(urlFor('wrapper-unavailable-url.xml'), options).then( + (response) => { + // Response doesn't have any ads + expect(response.ads).toEqual([]); + // Error has been trigered + expect(dataTriggered.length).toBe(1); + expect(dataTriggered[0].ERRORCODE).toBe(301); + expect(dataTriggered[0].extensions[0].children[0].name).toBe( + 'paramWrapperInvalidXmlfile' + ); + expect(dataTriggered[0].extensions[0].children[0].value).toBe( + 'valueWrapperInvalidXmlfile' + ); + // Tracking has been done + expect(trackCalls.length).toBe(1); + expect(trackCalls[0].templates).toEqual([ + 'http://example.com/wrapper-invalid-xmlfile_wrapper-error', + ]); + expect(trackCalls[0].variables).toEqual({ ERRORCODE: 301 }); + done(); + } + ); + }); + }); + + describe('Wrapper limit reached', () => { + it('should emits a VAST-error & track', (done) => { + dataFromGivenUrl(urlFor('wrapper-b.xml'), { + ...options, + wrapperLimit: 1, + }).then((response) => { + // Response doesn't have any ads + expect(response.ads).toEqual([]); + // Error has been triggered + expect(dataTriggered.length).toBe(1); + expect(dataTriggered[0].ERRORCODE).toBe(302); + expect(dataTriggered[0].extensions.length).toBe(0); + // Tracking has been done + expect(trackCalls.length).toBe(1); + expect(trackCalls[0].templates).toEqual([ + 'http://example.com/wrapperB-error', + ]); + expect(trackCalls[0].variables).toEqual({ ERRORCODE: 302 }); + done(); + }); + }); + }); + describe('Legacy', () => { + let response = null; + + beforeEach((done) => { + VastParser.removeAllListeners(); + vastClient.get(urlFor('wrapper-legacy.xml'), options).then((res) => { + response = res; + done(); + }); + }); + describe('should correctly loads a wrapped ad, even with the VASTAdTagURL-Tag', () => { + it('should have found 1 ad', () => { + expect(response.ads.length).toBe(1); + }); + + it('should have returned a VAST response object', () => { + expect(response.ads.length).toBe(1); + expect(response).toHaveProperty('ads'); + expect(response).toHaveProperty('errorURLTemplates'); + expect(response).toHaveProperty('version'); + }); + + it('should have found 2 creatives', () => { + expect(response.ads[0].creatives.length).toBe(2); + }); + + it('should have parsed mediafile attribute', () => { + const mediafile = response.ads[0].creatives[1].mediaFiles[0]; + expect(mediafile.mimeType).toBe('video/mp4'); + expect(mediafile.width).toBe(400); + expect(mediafile.height).toBe(300); + expect(mediafile.fileURL).toBe( + 'https://iabtechlab.com/wp-content/uploads/2016/07/VAST-4.0-Short-Intro.mp4' + ); + expect(mediafile.bitrate).toBe(500); + expect(mediafile.minBitrate).toBe(360); + expect(mediafile.maxBitrate).toBe(1080); + expect(mediafile.scalable).toBe(true); + }); + }); + }); + }); + describe('resolveAds', () => { it('updates previousUrl value and calls resolveWrappers for each ad', () => { jest @@ -648,7 +645,6 @@ describe('VASTParser', () => { }); describe('resolveWrappers', () => { - let fetcher; const ad = { id: null, sequence: 1, @@ -670,7 +666,6 @@ describe('VASTParser', () => { }; beforeEach(() => { - fetcher = new Fetcher(); VastParser.previousUrl = wrapperAVastUrl; VastParser.initParsingStatus({ urlHandler: urlHandlerSuccess }); }); diff --git a/spec/vast_trackers.spec.js b/spec/vast_trackers.spec.js index 3def02c3..91087bdb 100644 --- a/spec/vast_trackers.spec.js +++ b/spec/vast_trackers.spec.js @@ -2,18 +2,19 @@ import { VASTClient } from '../src/vast_client'; import { VASTTracker } from '../src/vast_tracker'; import { inlineTrackersParsed } from '../spec/samples/inline_trackers'; import { util } from '../src/util/util'; +import { createCreativeLinear } from '../src/creative/creative_linear'; const vastClient = new VASTClient(); describe('VASTTracker', function () { let vastTracker = null; + let ad = inlineTrackersParsed.ads[0]; + let spyEmitter; + let spyTrackUrl; + let spyTrack; describe('#linear', () => { - let spyEmitter; - let spyTrackUrl; - let spyTrack; - let adTrackingUrls; - let ad; + let adTrackingUrls = ad.creatives[0].trackingEvents; const expectedMacros = { ASSETURI: 'http%3A%2F%2Fexample.com%2Flinear-asset.mp4', UNIVERSALADID: 'sample-registry%20000123%2Csample-registry-2%20000456', @@ -24,14 +25,79 @@ describe('VASTTracker', function () { }; beforeEach(() => { - ad = inlineTrackersParsed.ads[0]; - adTrackingUrls = ad.creatives[0].trackingEvents; vastTracker = new VASTTracker(vastClient, ad, ad.creatives[0]); spyEmitter = jest.spyOn(vastTracker, 'emit'); spyTrackUrl = jest.spyOn(vastTracker, 'trackURLs'); spyTrack = jest.spyOn(vastTracker, 'track'); }); + it('should have firstQuartile set', () => { + expect(vastTracker.quartiles.firstQuartile).toBe(22.53); + }); + + it('should have midpoint set', () => { + expect(vastTracker.quartiles.midpoint).toBe(45.06); + }); + + it('should have thirdQuartile set', () => { + expect(vastTracker.quartiles.thirdQuartile).toBe(67.59); + }); + + describe('#Track', () => { + Object.entries(adTrackingUrls).forEach(([event, url]) => { + it(`should call emit with ${event}`, () => { + vastTracker.track(event, { + macro: {}, + once: false, + }); + expect(spyEmitter).toHaveBeenCalledWith(event, { + trackingURLTemplates: url, + }); + }); + + it(`should call trackURLs with ${url} and emit with ${event} `, () => { + vastTracker.track(event, { + macro: {}, + once: false, + }); + expect(spyEmitter).toHaveBeenCalledWith(event, { + trackingURLTemplates: url, + }); + expect(spyTrackUrl).toHaveBeenCalledWith(url, {}); + }); + }); + + it('should call trackURLs with the right trackingURLTemplates and macros', () => { + vastTracker.track('adExpand', { + macros: { PLAYERSIZE: [200, 200] }, + once: false, + }); + expect(spyTrackUrl).toHaveBeenCalledWith( + ['http://example.com/linear-adExpand'], + { PLAYERSIZE: [200, 200] } + ); + }); + + it('should emit close when closeLinear is given ', () => { + vastTracker.track('closeLinear', { macro: {}, once: false }); + + expect(spyEmitter).toHaveBeenCalledWith('close', { + trackingURLTemplates: ['http://example.com/linear-close'], + }); + }); + + it('should emit event only once when once is true', () => { + vastTracker.track('start', { macro: {}, once: true }); + vastTracker.track('start', { macro: {}, once: true }); + expect(spyEmitter).toHaveBeenCalledTimes(1); + }); + + it('should delete event from trackingEvents when once is true', () => { + vastTracker.track('start', { macro: {}, once: true }); + expect(vastTracker.trackingEvents).not.toHaveProperty('start'); + }); + }); + describe('#click', () => { beforeEach(() => { vastTracker.setProgress(60 * 75 + 5.25); @@ -42,7 +108,6 @@ describe('VASTTracker', function () { 'clickthrough', 'http://example.com/linear-clickthrough_adplayhead:01%3A15%3A05.250' ); - expect(spyTrackUrl).toHaveBeenCalledWith( ad.creatives[0].videoClickTrackingURLTemplates, expectedMacros @@ -162,6 +227,56 @@ describe('VASTTracker', function () { }); }); + describe('#complete', () => { + it('should have sent complete event and trackers', () => { + vastTracker.complete(); + expect(spyTrack).toHaveBeenCalledWith('complete', expect.any(Object)); + }); + it('should be called multiple times', () => { + vastTracker.complete(); + vastTracker.complete(); + expect(spyTrack).toHaveBeenCalledWith('complete', expect.any(Object)); + expect(spyTrack).toHaveBeenCalledTimes(2); + }); + }); + + describe('#close', () => { + let a; + beforeEach(() => { + vastTracker.close(); + a = jest.spyOn(vastTracker, 'emit'); + }); + it('should have emit and track close event', () => { + expect(spyEmitter).toHaveBeenCalledWith('close', { + trackingURLTemplates: ['http://example.com/linear-close'], + }); + expect(spyTrack).toHaveBeenCalledWith( + 'closeLinear', + expect.any(Object) + ); + }); + }); + + describe('#skip', () => { + it('should emit and track skip event', () => { + vastTracker.skip(); + expect(spyEmitter).toHaveBeenCalledWith('skip', { + trackingURLTemplates: ['http://example.com/linear-skip'], + }); + expect(spyTrack).toHaveBeenCalledWith('skip', expect.any(Object)); + }); + }); + + describe('#loaded', () => { + it('should have emit and track loaded event', () => { + vastTracker.load(); + expect(spyEmitter).toHaveBeenCalledWith('loaded', { + trackingURLTemplates: ['http://example.com/linear-loaded'], + }); + expect(spyTrack).toHaveBeenCalledWith('loaded', expect.any(Object)); + }); + }); + describe('#adCollapse', () => { beforeEach(() => { vastTracker.adCollapse(expectedMacros); @@ -240,15 +355,71 @@ describe('VASTTracker', function () { vastTracker.assetDuration = 10; vastTracker.setProgress(5); }); + + it('should track start when set at 1', () => { + vastTracker.setProgress(1); + expect(spyTrack).toHaveBeenCalledWith('start', expect.any(Object)); + }); + + it('should send skip-countdown event', () => { + vastTracker.skipDelay = 5; + vastTracker.setProgress(6); + expect(spyEmitter).toHaveBeenCalledWith('skip-countdown', 0); + }); + + it('should track rewind when set to 2', () => { + vastTracker.setProgress(2); + expect(spyTrack).toHaveBeenCalledWith('rewind', expect.any(Object)); + }); + + it('should track firstQuartile', () => { + vastTracker.setProgress(23); + expect(spyTrack).toHaveBeenCalledWith( + 'firstQuartile', + expect.any(Object) + ); + }); + + it('should track progress-30', () => { + vastTracker.setProgress(30); + expect(spyTrack).toHaveBeenCalledWith( + 'progress-30', + expect.any(Object) + ); + }); + + it('should track midpoint', () => { + vastTracker.setProgress(46); + expect(spyTrack).toHaveBeenCalledWith('midpoint', expect.any(Object)); + }); + it('call track with progress-5', () => { expect(spyTrack).toHaveBeenCalledWith('progress-5', expect.anything()); }); + it('call track with progress-50%', () => { expect(spyTrack).toHaveBeenCalledWith( 'progress-50%', expect.anything() ); }); + + it('should track progress-60%', () => { + vastTracker.setProgress(54); + expect(spyTrack).toHaveBeenCalledWith( + 'progress-60%', + expect.any(Object) + ); + }); + + it('should track thirdQuartile', () => { + vastTracker.setProgress(68); + expect(spyTrack).toHaveBeenCalledWith( + 'thirdQuartile', + expect.any(Object) + ); + }); + it('should also calls track for previous missing percentages', () => { vastTracker.lastPercentage = 1; expect(spyTrack.mock.calls).toContainEqual( @@ -298,13 +469,93 @@ describe('VASTTracker', function () { }); }); + describe('#setPaused', () => { + it('should be paused and track pause event', () => { + vastTracker.setPaused(true); + expect(vastTracker.paused).toEqual(true); + expect(spyTrack).toHaveBeenCalledWith('pause', expect.any(Object)); + }); + + it('should be resumed and track resume event', () => { + vastTracker.setPaused(false); + expect(vastTracker.paused).toEqual(false); + expect(spyTrack).toHaveBeenCalledWith('resume', expect.any(Object)); + }); + + it('should not track any event', () => { + vastTracker.paused = false; + vastTracker.setPaused(false); + expect(vastTracker.paused).toEqual(false); + expect(spyEmitter).not.toHaveBeenCalled(); + }); + }); + + describe('#setFullScreen', () => { + it('should be in fullscreen mode and send fullscreen event', () => { + vastTracker.setFullscreen(true); + expect(vastTracker.fullscreen).toEqual(true); + expect(spyTrack).toHaveBeenCalledWith('fullscreen', expect.any(Object)); + }); + + it('shout be in exitFullscreen mode an send exitFullscreen event', () => { + vastTracker.setFullscreen(false); + expect(vastTracker.fullscreen).toEqual(false); + expect(spyTrack).toHaveBeenCalledWith( + 'exitFullscreen', + expect.any(Object) + ); + }); + + it('should not sent any event ', () => { + vastTracker.fullscreen = false; + vastTracker.setFullscreen(false); + expect(spyTrack).not.toHaveBeenCalled(); + }); + }); + + describe('#setExpand', () => { + it('should expand and send expand tracker', () => { + vastTracker.setExpand(true); + expect(vastTracker.expanded).toEqual(true); + expect(spyTrack).toHaveBeenCalledWith('expand', expect.any(Object)); + expect(spyTrack).toHaveBeenCalledWith( + 'playerExpand', + expect.any(Object) + ); + }); + + it('should collapse and send collapse tracker', () => { + vastTracker.setExpand(false); + expect(vastTracker.expanded).toEqual(false); + expect(spyTrack).toHaveBeenCalledWith('collapse', expect.any(Object)); + expect(spyTrack).toHaveBeenCalledWith( + 'playerCollapse', + expect.any(Object) + ); + }); + + it('should not track any event', () => { + vastTracker.expanded = false; + vastTracker.setExpand(false); + expect(spyTrack).not.toHaveBeenCalled(); + }); + }); + describe('#setSkipDelay', () => { + beforeEach(() => { + vastTracker.setSkipDelay(8); + }); it('should update skipDelay value to the given value', () => { const newSkipDelay = 123; vastTracker.skipDelay = null; vastTracker.setSkipDelay(newSkipDelay); expect(vastTracker.skipDelay).toEqual(123); }); + + it('should have skipDelay still set to 8', () => { + vastTracker.setSkipDelay('foo'); + expect(vastTracker.skipDelay).toBe(8); + }); }); describe('#trackImpression', () => { @@ -435,4 +686,133 @@ describe('VASTTracker', function () { }); }); }); + + describe('#companion', () => { + let variation = ad.creatives[2].variations[0]; + + beforeEach(() => { + vastTracker = new VASTTracker(vastClient, ad, ad.creatives[2], variation); + }); + + describe('#click', () => { + let spyEmit; + beforeEach(() => { + spyTrackUrl = jest.spyOn(vastTracker, 'trackURLs'); + spyEmit = jest.spyOn(vastTracker, 'emit'); + vastTracker.click(); + }); + it('shouls have sent clickthrough events with clickThough url', () => { + expect(spyEmit).toHaveBeenCalledWith( + 'clickthrough', + 'https://iabtechlab.com' + ); + }); + + it('should have sent clickTracking event', () => { + expect(spyTrackUrl).toHaveBeenCalledWith( + [{ id: null, url: 'https://example.com/tracking/clickTracking' }], + expect.any(Object) + ); + }); + }); + }); + + describe('#NonLinear', () => { + let variation = ad.creatives[1].variations[0]; + beforeEach(() => { + vastTracker = new VASTTracker(vastClient, ad, ad.creatives[1], variation); + }); + + it('shoutd correctly set the tracker duration', () => { + expect(vastTracker.assetDuration).toBe(10); + }); + + describe('click', () => { + let spyEmit; + beforeEach(() => { + spyEmit = jest.spyOn(vastTracker, 'emit'); + spyTrack = jest.spyOn(vastTracker, 'trackURLs'); + vastTracker.click(); + }); + it('should have sent clickthrough event with clickThrough url', () => { + expect(spyEmit).toHaveBeenCalledWith( + 'clickthrough', + 'https://iabtechlab.com' + ); + }); + + it('should have sent clicktracking event ', () => { + expect(spyTrack).toHaveBeenCalledWith( + [{ id: null, url: 'https://example.com/tracking/clickTracking' }], + expect.any(Object) + ); + }); + }); + }); + + describe('#clickthroughs', () => { + let spyEmit; + const fallbackClickThroughURL = 'http://example.com/fallback-clickthrough', + clickThroughURL = 'http://example.com/clickthrough'; + + describe('#VAST clichthrough with no fallback provided', () => { + beforeEach(() => { + const creative = createCreativeLinear(); + creative.videoClickThroughURLTemplate = clickThroughURL; + vastTracker = new VASTTracker(vastClient, {}, creative); + spyEmit = jest.spyOn(vastTracker, 'emit'); + vastTracker.click(); + }); + it('should have sent clickthrough event with VAST clickthrough url', () => { + expect(spyEmit).toHaveBeenCalledWith( + 'clickthrough', + 'http://example.com/clickthrough' + ); + }); + }); + + describe('#VAST clickthrough with fallback provided', () => { + beforeEach(() => { + const creative = createCreativeLinear(); + creative.videoClickThroughURLTemplate = clickThroughURL; + vastTracker = new VASTTracker(vastClient, {}, creative); + spyEmit = jest.spyOn(vastTracker, 'emit'); + vastTracker.click(fallbackClickThroughURL); + }); + + it('it should have sent clickthrough event with VAST clickTrhough url', () => { + expect(spyEmit).toHaveBeenCalledWith( + 'clickthrough', + 'http://example.com/clickthrough' + ); + }); + }); + + describe('#empty VAST clickThrough with no fallback provided', () => { + beforeEach(() => { + vastTracker = new VASTTracker(vastClient, {}, {}); + spyEmit = jest.spyOn(vastTracker, 'emit'); + vastTracker.click(); + }); + + it("shouldn't have sent any event", () => { + expect(spyEmit).not.toHaveBeenCalled(); + }); + }); + + describe('#empty VAST clickThrough with fallback provided', () => { + beforeEach(() => { + vastTracker = new VASTTracker(vastClient, {}, {}); + spyEmit = jest.spyOn(vastTracker, 'emit'); + vastTracker.click(fallbackClickThroughURL); + }); + + it('should have sent fallback clickthrough', () => { + expect(spyEmit).toHaveBeenCalledWith( + 'clickthrough', + 'http://example.com/fallback-clickthrough' + ); + }); + }); + }); }); diff --git a/src/fetcher.js b/src/fetcher.js index a4b558d4..33f7fb10 100644 --- a/src/fetcher.js +++ b/src/fetcher.js @@ -85,8 +85,6 @@ export class Fetcher { url = filter(url); }); - this.rootURL = url; - emitter('VAST-resolving', { url, previousUrl, diff --git a/src/parser/vast_parser.js b/src/parser/vast_parser.js index 8a224ab2..d8b440c7 100644 --- a/src/parser/vast_parser.js +++ b/src/parser/vast_parser.js @@ -68,6 +68,7 @@ export class VASTParser extends EventEmitter { initParsingStatus(options = {}) { this.maxWrapperDepth = options.wrapperLimit || DEFAULT_MAX_WRAPPER_DEPTH; this.parsingOptions = { allowMultipleAds: options.allowMultipleAds }; + this.rootURL = ''; this.resetParsingStatus(); updateEstimatedBitrate(options.byteLength, options.requestDuration); } @@ -78,7 +79,6 @@ export class VASTParser extends EventEmitter { resetParsingStatus() { this.errorURLTemplates = []; this.rootErrorURLTemplates = []; - this.rootURL = ''; this.vastVersion = null; } /** @@ -361,9 +361,8 @@ export class VASTParser extends EventEmitter { wrapperDepth++; // If you need to fectch a VAST document, or follow additional wrapper, - // you need to use the get method from de VASTClient, this method will use the fetchVAST method from the + // you need to use the get method from VASTClient, this method will use the fetchVAST method from the // Fetcher class to fetch your document and set the fetchingMethod in case you want to fetch additional wrapper - if (!this.fetchingMethod) { ad.VASTAdTagURI = ad.nextWrapperURL; delete ad.nextWrapperURL; @@ -403,6 +402,7 @@ export class VASTParser extends EventEmitter { this.emit.bind(this) ) .then((xml) => { + this.rootURL = ad.nextWrapperURL; return this.parse(xml, { url: ad.nextWrapperURL, previousUrl, @@ -432,7 +432,6 @@ export class VASTParser extends EventEmitter { // (URI was either unavailable or reached a timeout as defined by the video player.) ad.errorCode = 301; ad.errorMessage = err.message; - resolve(ad); }); }); diff --git a/src/vast_client.js b/src/vast_client.js index 640b0bbe..f4f881d9 100644 --- a/src/vast_client.js +++ b/src/vast_client.js @@ -176,7 +176,6 @@ export class VASTClient { this.vastParser.initParsingStatus(options); this.fetcher.setOptions(options); - this.fetcher.rootURL = this.vastParser.rootURL; this.fetcher .fetchVAST( diff --git a/test/vast_parser.js b/test/vast_parser.js deleted file mode 100644 index ff8c380f..00000000 --- a/test/vast_parser.js +++ /dev/null @@ -1,1029 +0,0 @@ -import path from 'path'; -import should from 'should'; -import sinon from 'sinon'; -import { VASTParser } from '../src/parser/vast_parser'; -import { nodeURLHandler } from '../src/urlhandlers/node_url_handler'; -import { parserUtils } from '../src/parser/parser_utils.js'; -import { util } from '../src/util/util'; - -const vastParser = new VASTParser(); - -const urlfor = (relpath) => - `file:///${path - .resolve(path.dirname(module.filename), 'vastfiles', relpath) - .replace(/\\/g, '/')}`; - -describe('VASTParser', function () { - describe('#getAndParseVAST', function () { - this.response = null; - let _response = null; - const options = { - urlhandler: nodeURLHandler, - }; - - before((done) => { - vastParser - .getAndParseVAST(urlfor('wrapper-notracking.xml'), options) - .then((response) => { - this.response = response; - _response = this.response; - done(); - }); - }); - - after(() => { - vastParser.removeAllListeners(); - vastParser.clearURLTemplateFilters(); - }); - - it('should have found 2 ads', () => { - this.response.ads.should.have.length(2); - }); - - it('should have returned a VAST response object', () => { - this.response.should.have.properties( - 'ads', - 'errorURLTemplates', - 'version' - ); - }); - - it('should have retrived root VAST version', () => { - this.response.version.should.eql('2.0'); - }); - - describe('#For the 1st ad', function () { - let ad1 = null; - - before(() => { - ad1 = _response.ads[0]; - }); - - after(() => { - ad1 = null; - }); - - it('should have retrieved Ad attributes', () => { - ad1.id.should.eql('ad_id_0001'); - ad1.sequence.should.eql('1'); - }); - - it('should have retrieved Ad sub-elements values', () => { - ad1.system.value.should.eql('AdServer'); - ad1.system.version.should.eql('2.0'); - ad1.title.should.eql('Ad title'); - ad1.advertiser.id.should.eql('advertiser-desc'); - ad1.advertiser.value.should.eql('Advertiser name'); - ad1.description.should.eql('Description text'); - ad1.pricing.value.should.eql('1.09'); - ad1.pricing.model.should.eql('CPM'); - ad1.pricing.currency.should.eql('USD'); - }); - - it('should have merged wrapped ad error URLs', () => { - ad1.errorURLTemplates.should.eql([ - 'http://example.com/wrapperNoTracking-error', - 'http://example.com/wrapperA-error', - 'http://example.com/wrapperB-error', - 'http://example.com/error_[ERRORCODE]', - ]); - }); - - it('should have merged impression URL templates', () => { - ad1.impressionURLTemplates.should.eql([ - { - id: null, - url: 'http://example.com/wrapperNoTracking-impression', - }, - { - id: 'wrapper-a-impression', - url: 'http://example.com/wrapperA-impression', - }, - { - id: 'wrapper-b-impression1', - url: 'http://example.com/wrapperB-impression1', - }, - { - id: 'wrapper-b-impression2', - url: 'http://example.com/wrapperB-impression2', - }, - { - id: 'sample-impression1', - url: 'http://example.com/impression1_asset:[ASSETURI]_[CACHEBUSTING]', - }, - { - id: 'sample-impression2', - url: 'http://example.com/impression2_[random]', - }, - { - id: 'sample-impression3', - url: 'http://example.com/impression3_[RANDOM]', - }, - ]); - }); - - it('should have 5 creatives', () => { - ad1.creatives.should.have.length(5); - }); - - it('should have 4 extensions', () => { - ad1.extensions.should.have.length(4); - }); - - it('should have 5 AdVerification URLs VAST 4.1', () => { - ad1.adVerifications.should.have.length(5); - }); - - it('validate second adVerification', () => { - const adVerification = ad1.adVerifications[1]; - adVerification.resource.should.eql('http://example.com/omid2'); - adVerification.vendor.should.eql('company2.com-omid'); - adVerification.browserOptional.should.eql(false); - adVerification.apiFramework.should.eql('omid'); - adVerification.parameters.should.eql('test-verification-parameter'); - adVerification.trackingEvents.should.have.keys( - 'verificationNotExecuted' - ); - adVerification.trackingEvents['verificationNotExecuted'].should.eql([ - 'http://example.com/verification-not-executed-JS', - ]); - }); - - it('validate third adVerification', () => { - const adVerification = ad1.adVerifications[2]; - adVerification.resource.should.eql('http://example.com/omid1.exe'); - adVerification.vendor.should.eql('company.daily.com-omid'); - adVerification.browserOptional.should.eql(false); - adVerification.apiFramework.should.eql('omid'); - adVerification.type.should.eql('executable'); - should.equal(adVerification.parameters, null); - adVerification.trackingEvents.should.have.keys( - 'verificationNotExecuted' - ); - adVerification.trackingEvents['verificationNotExecuted'].should.eql([ - 'http://example.com/verification-not-executed-EXE', - 'http://sample.com/verification-not-executed-EXE', - ]); - }); - - it('validate wrapper-b adVerification merging', () => { - const adVerification = ad1.adVerifications[3]; - adVerification.resource.should.eql( - 'https://verification-b.com/omid_verification.js' - ); - adVerification.vendor.should.eql('verification-b.com-omid'); - adVerification.browserOptional.should.eql(false); - adVerification.apiFramework.should.eql('omid'); - adVerification.parameters.should.eql( - 'parameterB1=valueB1¶meterB2=valueB2' - ); - adVerification.trackingEvents.should.not.have.keys( - 'verificationNotExecuted' - ); - }); - - it('validate wrapper-a adVerification merging', () => { - const adVerification = ad1.adVerifications[4]; - - adVerification.resource.should.eql( - 'https://verification-a.com/omid_verification.js' - ); - adVerification.vendor.should.eql('verification-a.com-omid'); - adVerification.browserOptional.should.eql(false); - adVerification.apiFramework.should.eql('omid'); - adVerification.parameters.should.eql( - 'parameterA1=valueA1¶meterA2=valueA2' - ); - adVerification.trackingEvents.should.have.keys( - 'verificationNotExecuted' - ); - adVerification.trackingEvents['verificationNotExecuted'].should.eql([ - 'http://verification-a.com/verification-A-not-executed-JS', - ]); - }); - - it('should not have trackingEvents property', () => { - should.equal(ad1.trackingEvents, undefined); - }); - - it('should not have videoClickTrackingURLTemplates property', () => { - should.equal(ad1.videoClickTrackingURLTemplates, undefined); - }); - - it('should not have videoClickThroughURLTemplate property', () => { - should.equal(ad1.videoClickThroughURLTemplate, undefined); - }); - - it('should not have videoCustomClickURLTemplates property', () => { - should.equal(ad1.videoCustomClickURLTemplates, undefined); - }); - - //Linear - describe('1st creative (Linear)', function () { - let linear = null; - - before(() => { - linear = _response.ads[0].creatives.filter( - (creative) => creative.id === 'id130984' - )[0]; - }); - - after(() => { - linear = null; - }); - - it('should have linear type', () => { - linear.type.should.equal('linear'); - }); - - it('should have an id', () => { - linear.id.should.equal('id130984'); - }); - - it('should have an adId', () => { - linear.adId.should.equal('adId345690'); - }); - - it('should have a sequence', () => { - linear.sequence.should.equal('1'); - }); - - it('should not have an apiFramework', () => { - should.equal(linear.apiFramework, null); - }); - - it('should have a duration of 90.123s', () => { - linear.duration.should.equal(90.123); - }); - - it('should have a universal ad id', () => { - linear.universalAdIds[0].idRegistry.should.equal('daily-motion-L1'); - linear.universalAdIds[0].value.should.equal('Linear-12345'); - }); - - it('should have creativeExtensions of length 3', () => { - linear.creativeExtensions.should.have.length(3); - }); - - it('should have parsed 1st creativeExtension properties', () => { - linear.creativeExtensions[0].attributes['type'].should.equal( - 'creativeExt1' - ); - linear.creativeExtensions[0].children.should.have.length(1); - linear.creativeExtensions[0].children[0].name.should.equal( - 'CreativeExecution' - ); - linear.creativeExtensions[0].children[0].value.should.equal('10.0'); - }); - - it('should have parsed 2nd creativeExtension properties', () => { - linear.creativeExtensions[1].attributes['type'].should.equal('Count'); - linear.creativeExtensions[1].value.should.equal('10'); - }); - - it('should have parsed 3rd creativeExtension properties', () => { - linear.creativeExtensions[2].value.should.equal('{ key: value }'); - }); - - it('should have 2 media file', () => { - linear.mediaFiles.should.have.length(2); - }); - - it('should have parsed 1st media file attributes', () => { - linear.mediaFiles[0].width.should.equal(512); - linear.mediaFiles[0].height.should.equal(288); - linear.mediaFiles[0].mimeType.should.equal('video/mp4'); - linear.mediaFiles[0].fileSize.should.equal(345670); - linear.mediaFiles[0].mediaType.should.equal('2D'); - linear.mediaFiles[0].fileURL.should.equal( - 'http://example.com/linear-asset.mp4' - ); - }); - - it('should have parsed 2nd media file attributes', () => { - linear.mediaFiles[1].width.should.equal(512); - linear.mediaFiles[1].height.should.equal(288); - linear.mediaFiles[1].mimeType.should.equal('application/javascript'); - linear.mediaFiles[1].mediaType.should.equal('3D'); - linear.mediaFiles[1].apiFramework.should.equal('VPAID'); - linear.mediaFiles[1].deliveryType.should.equal('progressive'); - linear.mediaFiles[1].fileURL.should.equal( - 'parser.js?adData=http%3A%2F%2Fad.com%2F%3Fcb%3D%5Btime%5D' - ); - }); - - it('should have parsed mezzanine file attributes', () => { - linear.mezzanine.delivery.should.equal('progressive'); - linear.mezzanine.type.should.equal('video/mp4'); - linear.mezzanine.width.should.equal(1080); - linear.mezzanine.height.should.equal(720); - linear.mezzanine.codec.should.equal('h264'); - linear.mezzanine.id.should.equal('mezzanine-id-165468451'); - linear.mezzanine.fileSize.should.equal(700); - linear.mezzanine.mediaType.should.equal('2D'); - linear.mezzanine.fileURL.should.equal( - 'http://example.com/linear-mezzanine.mp4' - ); - }); - - it('should have parsed interactivecreative file attributes', () => { - linear.interactiveCreativeFile.type.should.equal( - 'application/javascript' - ); - linear.interactiveCreativeFile.apiFramework.should.equal('simpleApp'); - linear.interactiveCreativeFile.variableDuration.should.equal(false); - linear.interactiveCreativeFile.fileURL.should.equal( - 'http://example.com/linear-interactive-creative.js' - ); - }); - - it('should have 4 closedcaption files', () => { - linear.closedCaptionFiles.should.have.length(4); - }); - - it('should have parsed 1st closedcaption file attributes', () => { - linear.closedCaptionFiles[0].type.should.equal('text/srt'); - linear.closedCaptionFiles[0].language.should.equal('en'); - linear.closedCaptionFiles[0].fileURL.should.equal( - 'https://mycdn.example.com/creatives/creative001.srt' - ); - }); - - it('should have parsed 2nd closedcaption file attributes', () => { - linear.closedCaptionFiles[1].type.should.equal('text/srt'); - linear.closedCaptionFiles[1].language.should.equal('fr'); - linear.closedCaptionFiles[1].fileURL.should.equal( - 'https://mycdn.example.com/creatives/creative001-1.srt' - ); - }); - - it('should have parsed 3rd closedcaption file attributes', () => { - linear.closedCaptionFiles[2].type.should.equal('text/vtt'); - linear.closedCaptionFiles[2].language.should.equal('zh-TW'); - linear.closedCaptionFiles[2].fileURL.should.equal( - 'https://mycdn.example.com/creatives/creative001.vtt' - ); - }); - - it('should have parsed 4th closedcaption file attributes', () => { - linear.closedCaptionFiles[3].type.should.equal( - 'application/ttml+xml' - ); - linear.closedCaptionFiles[3].language.should.equal('zh-CH'); - linear.closedCaptionFiles[3].fileURL.should.equal( - 'https://mycdn.example.com/creatives/creative001.ttml' - ); - }); - - it('should have 1 clickthrough URL template', () => { - linear.videoClickThroughURLTemplate.should.eql({ - id: 'click-through', - url: 'http://example.com/linear-clickthrough', - }); - }); - - it('should have 6 clicktracking URL templates', () => { - linear.videoClickTrackingURLTemplates.should.eql([ - { - id: 'video-click-1', - url: 'http://example.com/linear-clicktracking1_ts:[TIMESTAMP]', - }, - { - id: 'video-click-2', - url: 'http://example.com/linear-clicktracking2', - }, - { - id: 'WRAP', - url: 'http://example.com/wrapperB-linear-clicktracking', - }, - { - id: 'wrapper-video-click-1', - url: 'http://example.com/wrapperA-linear-clicktracking1', - }, - { - id: null, - url: 'http://example.com/wrapperA-linear-clicktracking2', - }, - { - id: 'wrapper-video-click-3', - url: 'http://example.com/wrapperA-linear-clicktracking3', - }, - ]); - }); - - it('should have 2 customclick URL templates', () => { - linear.videoCustomClickURLTemplates.should.eql([ - { - id: 'custom-click-1', - url: 'http://example.com/linear-customclick', - }, - { - id: '123', - url: 'http://example.com/wrapperA-linear-customclick', - }, - ]); - }); - - it('should have 8 tracking events', () => { - linear.trackingEvents.should.have.keys( - 'start', - 'close', - 'midpoint', - 'complete', - 'firstQuartile', - 'thirdQuartile', - 'progress-30', - 'progress-60%' - ); - }); - - it('should have 4 URLs for start event', () => { - linear.trackingEvents['start'].should.eql([ - 'http://example.com/linear-start', - 'http://example.com/wrapperB-linear-start', - 'http://example.com/wrapperA-linear-start1', - 'http://example.com/wrapperA-linear-start2', - ]); - }); - - it('should have 3 URLs for complete event', () => { - linear.trackingEvents['complete'].should.eql([ - 'http://example.com/linear-complete', - 'http://example.com/wrapperB-linear-complete', - 'http://example.com/wrapperA-linear-complete', - ]); - }); - - it('should have 3 URLs for progress-30 event VAST 3.0', () => { - linear.trackingEvents['progress-30'].should.eql([ - 'http://example.com/linear-progress-30sec', - 'http://example.com/wrapperB-linear-progress-30sec', - 'http://example.com/wrapperA-linear-progress-30sec', - ]); - }); - - it('should have 3 URLs for progress-60% event VAST 3.0', () => { - linear.trackingEvents['progress-60%'].should.eql([ - 'http://example.com/linear-progress-60%', - 'http://example.com/wrapperB-linear-progress-60%', - 'http://example.com/wrapperA-linear-progress-60%', - ]); - }); - - it('should have 3 URLs for progress-90% event VAST 3.0', () => { - linear.trackingEvents['progress-90%'].should.eql([ - 'http://example.com/wrapperA-linear-progress-90%', - ]); - }); - }); - }); - - describe('#For the 2nd ad', function () { - let ad2 = null; - - before(() => { - ad2 = _response.ads[1]; - }); - - after(() => { - ad2 = null; - }); - - it('should have retrieved Ad attributes', () => { - _response.ads[1].id.should.eql('ad_id_0002'); - }); - it("should have ignored the wrapper's sequence", () => { - should.equal(_response.ads[1].sequence, null); - }); - - it('should have retrieved Ad sub-elements values', () => { - ad2.system.value.should.eql('AdServer2'); - ad2.system.version.should.eql('2.1'); - ad2.title.should.eql('Ad title 2'); - should.equal(ad2.advertiser, null); - should.equal(ad2.description, null); - should.equal(ad2.pricing, null); - should.equal(ad2.survey, null); - }); - - it('should have merged error URLs', () => { - ad2.errorURLTemplates.should.eql([ - 'http://example.com/wrapperNoTracking-error', - 'http://example.com/wrapperA-error', - 'http://example.com/wrapperB-error', - ]); - }); - - it('should have merged impression URL templates', () => { - ad2.impressionURLTemplates.should.eql([ - { - id: null, - url: 'http://example.com/wrapperNoTracking-impression', - }, - { - id: 'wrapper-a-impression', - url: 'http://example.com/wrapperA-impression', - }, - { - id: 'wrapper-b-impression1', - url: 'http://example.com/wrapperB-impression1', - }, - { - id: 'wrapper-b-impression2', - url: 'http://example.com/wrapperB-impression2', - }, - { - id: 'sample-ad2-impression1', - url: 'http://example.com/impression1', - }, - ]); - }); - - it('should have 3 creative', () => { - ad2.creatives.should.have.length(3); - }); - - it('should have 1 extension (from the wrapper)', () => { - ad2.extensions.should.have.length(1); - }); - - //Linear - describe('1st creative (Linear)', function () { - let linear = null; - - before(() => { - linear = ad2.creatives.filter( - (creative) => creative.id === 'id873421' - )[0]; - }); - - after(() => { - linear = null; - }); - - it('should have linear type', () => { - linear.type.should.equal('linear'); - }); - - it('should have an id', () => { - linear.id.should.equal('id873421'); - }); - - it('should have an adId', () => { - linear.adId.should.equal('adId221144'); - }); - - it('should not have a sequence', () => { - should.equal(linear.sequence, null); - }); - - it('should have an apiFramework', () => { - linear.apiFramework.should.equal('VPAID'); - }); - - it('should have a duration of 30', () => { - linear.duration.should.equal(30); - }); - - it('should have a UniversalAdIds[0] with value=unknown and idRegistry=null', () => { - should.equal(linear.universalAdIds[0].value, 'Linear-id873421'); - should.equal(linear.universalAdIds[0].idRegistry, 'unknown'); - }); - - it('should have wrapper clickthrough URL', () => { - linear.videoClickThroughURLTemplate.should.eql({ - id: null, - url: 'http://example.com/wrapperB-linear-clickthrough', - }); - }); - - it('should have wrapper customclick URL template', () => { - linear.videoCustomClickURLTemplates.should.eql([ - { - id: '123', - url: 'http://example.com/wrapperA-linear-customclick', - }, - ]); - }); - - it('should have 5 clicktracking URL templates', () => { - linear.videoClickTrackingURLTemplates.should.eql([ - { - id: null, - url: 'http://example.com/linear-clicktracking', - }, - { - id: 'WRAP', - url: 'http://example.com/wrapperB-linear-clicktracking', - }, - { - id: 'wrapper-video-click-1', - url: 'http://example.com/wrapperA-linear-clicktracking1', - }, - { - id: null, - url: 'http://example.com/wrapperA-linear-clicktracking2', - }, - { - id: 'wrapper-video-click-3', - url: 'http://example.com/wrapperA-linear-clicktracking3', - }, - ]); - }); - }); - }); - - describe('#For the wrapper-1 ad', function () { - this.response = null; - this.templateFilterCalls = []; - - before((done) => { - vastParser.addURLTemplateFilter((url) => { - this.templateFilterCalls.push(url); - return url; - }); - vastParser - .getAndParseVAST(urlfor('wrapper-sequence.xml'), options) - .then((response) => { - this.response = response; - done(); - }); - }); - - it('should have called 4 times URLtemplateFilter ', () => { - this.templateFilterCalls.should.have.length(4); - this.templateFilterCalls.should.eql([ - urlfor('wrapper-sequence.xml'), - urlfor('wrapper-sequence.xml'), - urlfor('sample-wrapped.xml'), - urlfor('sample-wrapped.xml'), - ]); - }); - - it('should have carried sequence over from wrapper', () => { - this.response.ads[0].sequence.should.eql('1'); - }); - - it('should have default attributes value for wrapper', () => { - this.response.ads[0].followAdditionalWrappers.should.eql(true); - this.response.ads[0].allowMultipleAds.should.eql(false); - should.equal(this.response.ads[0].fallbackOnNoAd, null); - }); - }); - - describe('#VPAID', function () { - this.response = null; - - before((done) => { - this.wrapperSpy = sinon.spy(parserUtils, 'resolveVastAdTagURI'); - vastParser - .getAndParseVAST(urlfor('vpaid.xml'), options) - .then((response) => { - this.response = response; - done(); - }); - }); - - it('should not try to resolve wrappers', () => { - sinon.assert.notCalled(this.wrapperSpy); - }); - - it('should have apiFramework set', () => { - this.response.ads[0].creatives[0].mediaFiles[0].apiFramework.should.be.equal( - 'VPAID' - ); - }); - - it('should have duration set to -1', () => { - this.response.ads[0].creatives[0].duration.should.be.equal(-1); - }); - }); - - describe('#Ad Pod', function () { - this.response = null; - - before((done) => { - vastParser - .getAndParseVAST(urlfor('wrapper-ad-pod.xml'), options) - .then((response) => { - this.response = response; - done(); - }); - }); - - it('should have parsed 2 ads', () => { - this.response.ads.should.have.length(2); - }); - - it('should have maintened the sequence when resolving wrappers', () => { - this.response.ads[0].sequence.should.be.equal('1'); - this.response.ads[1].sequence.should.be.equal('2'); - }); - }); - }); - - describe('#parseVAST', function () { - const options = { - urlhandler: nodeURLHandler, - }; - this.response = null; - this.templateFilterCalls = []; - - before((done) => { - vastParser.addURLTemplateFilter((url) => { - this.templateFilterCalls.push(url); - return url; - }); - const url = urlfor('wrapper-notracking.xml'); - vastParser.urlHandler.get(url, {}, (err, xml) => { - // `VAST > Wrapper > VASTAdTagURI` in the VAST must be an absolute URL - for (let nodeKey in xml.documentElement.childNodes) { - const node = xml.documentElement.childNodes[nodeKey]; - - if (node.nodeName === 'Ad') { - for (let adNodeKey in node.childNodes) { - const adNode = node.childNodes[adNodeKey]; - - if (adNode.nodeName === 'Wrapper') { - for (let wrapperNodeKey in adNode.childNodes) { - const wrapperNode = adNode.childNodes[wrapperNodeKey]; - - if (wrapperNode.nodeName === 'VASTAdTagURI') { - wrapperNode.textContent = urlfor( - parserUtils.parseNodeText(wrapperNode) - ); - break; - } - } - } - } - } - } - - vastParser.parseVAST(xml, options).then((response) => { - this.response = response; - done(); - }); - }); - }); - - after(() => { - vastParser.clearURLTemplateFilters(); - }); - - it('should have 1 filter defined', () => { - vastParser.countURLTemplateFilters().should.equal(1); - }); - - it('should have called 6 times URLtemplateFilter ', () => { - this.templateFilterCalls.should.have.length(6); - this.templateFilterCalls.should.eql([ - urlfor('wrapper-a.xml'), - urlfor('wrapper-a.xml'), - urlfor('wrapper-b.xml'), - urlfor('wrapper-b.xml'), - urlfor('sample.xml'), - urlfor('sample.xml'), - ]); - }); - - it('should have found 2 ads', () => { - this.response.ads.should.have.length(2); - }); - - it('should have returned a VAST response object', () => { - this.response.should.have.properties( - 'ads', - 'errorURLTemplates', - 'version' - ); - }); - }); - - describe('#Tracking', function () { - let trackCalls = null; - let dataTriggered = null; - const options = { - urlhandler: nodeURLHandler, - }; - - beforeEach(() => { - vastParser.removeAllListeners(); - dataTriggered = []; - trackCalls = []; - - vastParser.on('VAST-error', (variables) => dataTriggered.push(variables)); - - util.track = (templates, variables) => { - trackCalls.push({ - templates, - variables, - }); - }; - }); - - describe('#No-Ad', function () { - it('emits a VAST-error & track', (done) => { - vastParser - .getAndParseVAST(urlfor('empty-no-ad.xml'), options) - .then((response) => { - // Response doesn't have any ads - response.ads.should.eql([]); - // Error has been triggered - dataTriggered.length.should.eql(1); - dataTriggered[0].ERRORCODE.should.eql(303); - dataTriggered[0].extensions.should.eql([]); - // Tracking has been done - trackCalls.length.should.eql(1); - trackCalls[0].templates.should.eql([ - 'http://example.com/empty-no-ad', - ]); - trackCalls[0].variables.should.eql({ ERRORCODE: 303 }); - done(); - }); - }); - - it('when wrapped, emits a VAST-error & track', (done) => { - vastParser - .getAndParseVAST(urlfor('wrapper-empty.xml'), options) - .then((response) => { - // Response doesn't have any ads - response.ads.should.eql([]); - // Error has been triggered - dataTriggered.length.should.eql(1); - dataTriggered[0].ERRORCODE.should.eql(303); - dataTriggered[0].extensions[0].children[0].name.should.eql( - 'paramWrapperEmptyNoAd' - ); - dataTriggered[0].extensions[0].children[0].value.should.eql( - 'valueWrapperEmptyNoAd' - ); - // Tracking has been done - trackCalls.length.should.eql(1); - trackCalls[0].templates.should.eql([ - 'http://example.com/wrapper-empty_wrapper-error', - 'http://example.com/empty-no-ad', - ]); - trackCalls[0].variables.should.eql({ ERRORCODE: 303 }); - done(); - }); - }); - }); - - describe('#Ad with no creatives', function () { - it('emits a VAST-error & track', (done) => { - vastParser - .getAndParseVAST(urlfor('empty-no-creative.xml'), options) - .then((response) => { - // Response doesn't have any ads - response.ads.should.eql([]); - // Error has been triggered - dataTriggered.length.should.eql(1); - dataTriggered[0].ERRORCODE.should.eql(303); - dataTriggered[0].extensions[0].children[0].name.should.eql( - 'paramEmptyNoCreative' - ); - dataTriggered[0].extensions[0].children[0].value.should.eql( - 'valueEmptyNoCreative' - ); - // Tracking has been done - trackCalls.length.should.eql(1); - trackCalls[0].templates.should.eql([ - 'http://example.com/empty-no-creative_inline-error', - ]); - trackCalls[0].variables.should.eql({ ERRORCODE: 303 }); - done(); - }); - }); - - it('when wrapped, emits a VAST-error & track', (done) => { - vastParser - .getAndParseVAST(urlfor('wrapper-empty-no-creative.xml'), options) - .then((response) => { - // Response doesn't have any ads - response.ads.should.eql([]); - // Error has been triggered - dataTriggered.length.should.eql(1); - dataTriggered[0].ERRORCODE.should.eql(303); - dataTriggered[0].extensions[0].children[0].name.should.eql( - 'paramWrapperEmptyNoCreative' - ); - dataTriggered[0].extensions[0].children[0].value.should.eql( - 'valueWrapperEmptyNoCreative' - ); - dataTriggered[0].extensions[1].children[0].name.should.eql( - 'paramEmptyNoCreative' - ); - dataTriggered[0].extensions[1].children[0].value.should.eql( - 'valueEmptyNoCreative' - ); - // Tracking has been done - trackCalls.length.should.eql(1); - trackCalls[0].templates.should.eql([ - 'http://example.com/wrapper-no-creative_wrapper-error', - 'http://example.com/empty-no-creative_inline-error', - ]); - trackCalls[0].variables.should.eql({ ERRORCODE: 303 }); - done(); - }); - }); - }); - - describe('#Wrapper URL unavailable/timeout', () => { - it('emits a VAST-error & track', (done) => { - vastParser - .getAndParseVAST(urlfor('wrapper-unavailable-url.xml'), options) - .then((response) => { - // Response doesn't have any ads - response.ads.should.eql([]); - // Error has been triggered - dataTriggered.length.should.eql(1); - dataTriggered[0].ERRORCODE.should.eql(301); - dataTriggered[0].extensions[0].children[0].name.should.eql( - 'paramWrapperInvalidXmlfile' - ); - dataTriggered[0].extensions[0].children[0].value.should.eql( - 'valueWrapperInvalidXmlfile' - ); - // Tracking has been done - trackCalls.length.should.eql(1); - trackCalls[0].templates.should.eql([ - 'http://example.com/wrapper-invalid-xmlfile_wrapper-error', - ]); - trackCalls[0].variables.should.eql({ ERRORCODE: 301 }); - done(); - }); - }); - }); - - describe('#Wrapper limit reached', () => { - it('emits a VAST-error & track', (done) => { - vastParser - .getAndParseVAST(urlfor('wrapper-a.xml'), { - wrapperLimit: 1, - urlhandler: nodeURLHandler, - }) - .then((response) => { - // Response doesn't have any ads - response.ads.should.eql([]); - // Error has been triggered - dataTriggered.length.should.eql(1); - dataTriggered[0].ERRORCODE.should.eql(302); - dataTriggered[0].extensions[0].children[0].name.should.eql( - 'extension_tag' - ); - dataTriggered[0].extensions[0].children[0].value.should.eql( - 'extension_value' - ); - // Tracking has been done - trackCalls.length.should.eql(1); - trackCalls[0].templates.should.eql([ - 'http://example.com/wrapperA-error', - ]); - trackCalls[0].variables.should.eql({ ERRORCODE: 302 }); - done(); - }); - }); - }); - }); - - describe('#legacy', function () { - const options = { - urlhandler: nodeURLHandler, - }; - - beforeEach(() => { - vastParser.removeAllListeners(); - }); - - it('correctly loads a wrapped ad, even with the VASTAdTagURL-Tag', (done) => { - vastParser - .getAndParseVAST(urlfor('wrapper-legacy.xml'), options) - .then((response) => { - it('should have found 1 ad', () => { - response.ads.should.have.length(1); - }); - - it('should have returned a VAST response object', () => { - response.should.have.properties( - 'ads', - 'errorURLTemplates', - 'version' - ); - }); - - // we just want to make sure that the sample.xml was loaded correctly - const linear = response.ads[0].creatives[0]; - it('should have parsed media file attributes', () => { - const mediaFile = linear.mediaFiles[0]; - mediaFile.width.should.equal(512); - mediaFile.height.should.equal(288); - mediaFile.mimeType.should.equal('video/mp4'); - mediaFile.fileURL.should.equal('http://example.com/asset.mp4'); - }); - - done(); - }); - }); - }); -}); diff --git a/test/vast_tracker.js b/test/vast_tracker.js deleted file mode 100644 index db5ad9d0..00000000 --- a/test/vast_tracker.js +++ /dev/null @@ -1,661 +0,0 @@ -import path from 'path'; -import sinon from 'sinon'; -import { VASTClient } from '../src/vast_client'; -import { VASTParser } from '../src/parser/vast_parser'; -import { VASTTracker } from '../src/vast_tracker'; -import { nodeURLHandler } from '../src/urlhandlers/node_url_handler'; -import { util } from '../src/util/util'; -import { createCreativeLinear } from '../src/creative/creative_linear'; - -const now = new Date(); -const vastParser = new VASTParser(); -const vastClient = new VASTClient(); -const options = { - urlhandler: nodeURLHandler, -}; - -const urlfor = (relpath) => - `file:///${path - .resolve(path.dirname(module.filename), 'vastfiles', relpath) - .replace(/\\/g, '/')}`; - -describe('VASTTracker', function () { - before(() => { - this.clock = sinon.useFakeTimers(now.getTime()); - }); - - after(() => { - this.clock.restore(); - }); - - describe('#constructor', function () { - this.Tracker = null; - let _eventsSent = []; - this.templateFilterCalls = []; - this.response = {}; - - before((done) => { - vastParser.addURLTemplateFilter((url) => { - this.templateFilterCalls.push(url); - return url; - }); - - vastParser - .getAndParseVAST(urlfor('wrapper-a.xml'), options) - .then((response) => { - this.response = response; - done(); - }); - }); - - after(() => { - vastParser.clearURLTemplateFilters(); - }); - - describe('#linear', () => { - before(() => { - // Init tracker - const ad = this.response.ads[0]; - const creative = ad.creatives.filter( - (creative) => creative.id === 'id130984' - )[0]; - this.Tracker = new VASTTracker(vastClient, ad, creative); - // Mock emit - this.Tracker.emit = (event) => { - _eventsSent.push(event); - }; - }); - - it('should have firstQuartile set', () => { - this.Tracker.quartiles.firstQuartile.should.equal(22.53); - }); - - it('should have midpoint set', () => { - this.Tracker.quartiles.midpoint.should.equal(45.06); - }); - - it('should have thirdQuartile set', () => { - this.Tracker.quartiles.thirdQuartile.should.equal(67.59); - }); - - it('should have skipDelay disabled', () => { - this.Tracker.skipDelay.should.equal(-1); - }); - - describe('#setProgress', () => { - beforeEach((done) => { - _eventsSent = []; - done(); - }); - - it('should send start event when set at 1', () => { - this.Tracker.setProgress(1); - _eventsSent.should.eql(['start']); - }); - - it('should send skip-countdown event', () => { - this.Tracker.skipDelay = 5; - this.Tracker.setProgress(6); - _eventsSent.should.eql(['skip-countdown']); - }); - - it('should send rewind event when set back at 5', () => { - this.Tracker.setProgress(5); - _eventsSent.should.eql(['rewind']); - }); - - it('should send firstQuartile event', () => { - this.Tracker.setProgress(23); - _eventsSent.should.eql(['firstQuartile']); - }); - - it('should send progress-30 event VAST 3.0', () => { - this.Tracker.setProgress(30); - _eventsSent.should.eql(['progress-30']); - }); - - it('should send midpoint event', () => { - this.Tracker.setProgress(46); - _eventsSent.should.eql(['midpoint']); - }); - - it('should send progress-60% event VAST 3.0', () => { - this.Tracker.setProgress(54); - _eventsSent.should.eql(['progress-60%']); - }); - - it('should send thirdQuartile event', () => { - this.Tracker.setProgress(68); - _eventsSent.should.eql(['thirdQuartile']); - }); - }); - - describe('#setMuted', () => { - before((done) => { - _eventsSent = []; - this.Tracker.trackingEvents['mute'] = 'http://example.com/muted'; - this.Tracker.trackingEvents['unmute'] = 'http://example.com/muted'; - this.Tracker.setMuted(true); - done(); - }); - - it('should be muted', () => { - this.Tracker.muted.should.eql(true); - }); - - it('should send muted event', () => { - _eventsSent.should.eql(['mute']); - }); - - it('should be unmuted', () => { - _eventsSent = []; - this.Tracker.setMuted(false); - this.Tracker.muted.should.eql(false); - }); - - it('should send unmuted event', () => { - _eventsSent.should.eql(['unmute']); - }); - - it('should send no event', () => { - _eventsSent = []; - this.Tracker.setMuted(false); - _eventsSent.should.eql([]); - }); - }); - - describe('#setPaused', () => { - before((done) => { - _eventsSent = []; - this.Tracker.setPaused(true); - done(); - }); - - it('should be paused', () => { - this.Tracker.paused.should.eql(true); - }); - - it('should send pause event', () => { - _eventsSent.should.eql(['pause']); - }); - - it('should be resumed', () => { - _eventsSent = []; - this.Tracker.setPaused(false); - this.Tracker.paused.should.eql(false); - }); - - it('should send resume event', () => { - _eventsSent.should.eql(['resume']); - }); - - it('should send no event', () => { - _eventsSent = []; - this.Tracker.setPaused(false); - _eventsSent.should.eql([]); - }); - }); - - describe('#setFullscreen', () => { - before((done) => { - _eventsSent = []; - this.Tracker.trackingEvents['fullscreen'] = - 'http://example.com/fullscreen'; - this.Tracker.trackingEvents['exitFullscreen'] = - 'http://example.com/exitFullscreen'; - this.Tracker.setFullscreen(true); - done(); - }); - - it('should be in fullscreen mode', () => { - this.Tracker.fullscreen.should.eql(true); - }); - - it('should send fullscreen event', () => { - _eventsSent.should.eql(['fullscreen']); - }); - - it('should be in exitFullscreen mode', () => { - _eventsSent = []; - this.Tracker.setFullscreen(false); - this.Tracker.fullscreen.should.eql(false); - }); - - it('should send exitFullscreen event', () => { - _eventsSent.should.eql(['exitFullscreen']); - }); - - it('should send no event', () => { - _eventsSent = []; - this.Tracker.setFullscreen(false); - _eventsSent.should.eql([]); - }); - }); - - describe('#setExpand', () => { - before((done) => { - _eventsSent = []; - this.Tracker.trackingEvents['expand'] = 'http://example.com/expand'; - this.Tracker.trackingEvents['playerExpand'] = - this.Tracker.trackingEvents['expand']; - this.Tracker.trackingEvents['collapse'] = - 'http://example.com/collapse'; - this.Tracker.trackingEvents['playerCollapse'] = - this.Tracker.trackingEvents['collapse']; - this.Tracker.setExpand(true); - done(); - }); - - it('should be in expanded mode', () => { - this.Tracker.expanded.should.eql(true); - }); - - it('should send expand event', () => { - _eventsSent.should.eql(['expand', 'playerExpand']); - }); - - it('should be in collapsed mode', () => { - _eventsSent = []; - this.Tracker.setExpand(false); - this.Tracker.expanded.should.eql(false); - }); - - it('should send collapse event', () => { - _eventsSent.should.eql(['collapse', 'playerCollapse']); - }); - - it('should send no event', () => { - _eventsSent = []; - this.Tracker.setExpand(false); - _eventsSent.should.eql([]); - }); - }); - - describe('#setSkipDelay', () => { - it('should have skipDelay set to 3', () => { - this.Tracker.setSkipDelay(3); - this.Tracker.skipDelay.should.eql(3); - }); - - it('should have skipDelay still set to 3', () => { - this.Tracker.setSkipDelay('blabla'); - this.Tracker.skipDelay.should.eql(3); - }); - }); - - describe('#error', () => { - before(() => { - util.track = function (URLTemplates, variables, options) { - _eventsSent.push( - this.resolveURLTemplates(URLTemplates, variables, options) - ); - }; - }); - beforeEach(() => { - _eventsSent = []; - }); - - it('should have called error urls with right code', () => { - this.Tracker.error({ ERRORCODE: 405 }); - _eventsSent[0].should.eql([ - 'http://example.com/wrapperA-error', - 'http://example.com/wrapperB-error', - 'http://example.com/error_405', - ]); - }); - - it('should have called error urls with 900 if unknown code', () => { - this.Tracker.error({ ERRORCODE: 10001 }); - _eventsSent[0].should.eql([ - 'http://example.com/wrapperA-error', - 'http://example.com/wrapperB-error', - 'http://example.com/error_900', - ]); - }); - - it('should have called error urls with custom code when enabled', () => { - this.Tracker.error({ ERRORCODE: 10001 }, true); - _eventsSent[0].should.eql([ - 'http://example.com/wrapperA-error', - 'http://example.com/wrapperB-error', - 'http://example.com/error_10001', - ]); - }); - }); - - describe('#complete', () => { - before((done) => { - _eventsSent = []; - this.Tracker.complete(); - done(); - }); - - it('should have sent complete event and urls', () => { - _eventsSent.should.eql([ - 'complete', - [ - 'http://example.com/linear-complete', - 'http://example.com/wrapperB-linear-complete', - 'http://example.com/wrapperA-linear-complete', - ], - ]); - }); - - it('should be called multiples times', () => { - _eventsSent = []; - this.Tracker.complete(); - _eventsSent.should.eql([ - 'complete', - [ - 'http://example.com/linear-complete', - 'http://example.com/wrapperB-linear-complete', - 'http://example.com/wrapperA-linear-complete', - ], - ]); - }); - }); - - describe('#close', () => { - before((done) => { - _eventsSent = []; - this.Tracker.close(); - done(); - }); - - it('should have sent close event and urls VAST 2.0', () => { - _eventsSent.should.eql([ - 'close', - ['http://example.com/linear-close'], - ]); - }); - - it('should have sent closeLinear event and urls VAST 3.0', () => { - _eventsSent = []; - this.Tracker.trackingEvents['closeLinear'] = [ - 'http://example.com/closelinear', - ]; - delete this.Tracker.trackingEvents['close']; - this.Tracker.close(); - _eventsSent.should.eql([ - 'closeLinear', - ['http://example.com/closelinear'], - ]); - }); - }); - - describe('#skip', () => { - before((done) => { - _eventsSent = []; - this.Tracker.skip(); - done(); - }); - - it('should have sent skip event', () => { - _eventsSent.should.eql(['skip']); - }); - }); - - describe('#load', () => { - it('should have sent load event', () => { - _eventsSent = []; - this.Tracker.trackingEvents['loaded'] = ['http://example.com/loaded']; - this.Tracker.load(); - _eventsSent.should.eql(['loaded', ['http://example.com/loaded']]); - }); - }); - - describe('#click', () => { - before((done) => { - _eventsSent = []; - util.track = function (URLTemplates, variables) { - _eventsSent.push(this.resolveURLTemplates(URLTemplates, variables)); - }; - this.Tracker.click(); - done(); - }); - - it('should have sent clicktracking events', () => { - const ISOTimeStamp = util.encodeURIComponentRFC3986( - new Date().toISOString() - ); - _eventsSent[0].should.eql([ - `http://example.com/linear-clicktracking1_ts:${ISOTimeStamp}`, - 'http://example.com/linear-clicktracking2', - 'http://example.com/wrapperB-linear-clicktracking', - 'http://example.com/wrapperA-linear-clicktracking1', - 'http://example.com/wrapperA-linear-clicktracking2', - 'http://example.com/wrapperA-linear-clicktracking3', - ]); - }); - - it('should have sent clickthrough event', () => { - _eventsSent[1].should.eql('clickthrough'); - }); - }); - }); - - describe('#linear seek ads', () => { - before(() => { - // Init tracker - const ad = this.response.ads[0]; - const creative = ad.creatives.filter( - (creative) => creative.id === 'id130984' - )[0]; - this.Tracker = new VASTTracker(vastClient, ad, creative); - // Mock emit - this.Tracker.emit = (event) => { - _eventsSent.push(event); - }; - }); - - describe('#setProgress seek ads IOS', () => { - beforeEach((done) => { - _eventsSent = []; - done(); - }); - - it('should send thirdQuartile event', () => { - this.Tracker.setProgress(68); - _eventsSent.should.containEql('start'); - _eventsSent.should.containEql('firstQuartile'); - _eventsSent.should.containEql('midpoint'); - _eventsSent.should.containEql('thirdQuartile'); - }); - }); - }); - - describe('#companion', () => { - before(() => { - // Init tracker - const ad = this.response.ads[0]; - const creative = ad.creatives.filter( - (creative) => creative.id === 'id130985' - )[0]; - const variation = creative.variations[0]; - this.Tracker = new VASTTracker(vastClient, ad, creative, variation); - // Mock emit - this.Tracker.emit = (event, ...args) => { - _eventsSent.push({ event, args }); - }; - }); - - describe('#click', () => { - before((done) => { - _eventsSent = []; - util.track = function (URLTemplates, variables) { - _eventsSent.push(this.resolveURLTemplates(URLTemplates, variables)); - }; - this.Tracker.click(); - done(); - }); - - it('should have sent clicktracking events', () => { - const ISOTimeStamp = util.encodeURIComponentRFC3986( - new Date().toISOString() - ); - _eventsSent[0].should.eql([ - 'http://example.com/companion1-clicktracking-first', - 'http://example.com/companion1-clicktracking-second', - 'http://example.com/wrapperB-companion1-click-tracking', - 'http://example.com/wrapperA-companion1-click-tracking', - ]); - }); - - it('should have sent clickthrough event with clickThrough url', () => { - _eventsSent[1].event.should.eql('clickthrough'); - _eventsSent[1].args.should.eql([ - 'http://example.com/companion1-clickthrough', - ]); - }); - }); - }); - - describe('#nonlinear', () => { - before(() => { - // Init tracker - const ad = this.response.ads[0]; - const creative = ad.creatives.filter( - (creative) => creative.id === 'id130986' - )[0]; - const variation = creative.variations[0]; - this.Tracker = new VASTTracker(vastClient, ad, creative, variation); - // Mock emit - this.Tracker.emit = (event, ...args) => { - _eventsSent.push({ event, args }); - }; - }); - - it('should correctly set the tracker duration', () => { - this.Tracker.assetDuration.should.eql(100); - }); - - describe('#click', () => { - before((done) => { - _eventsSent = []; - util.track = function (URLTemplates, variables) { - _eventsSent.push(this.resolveURLTemplates(URLTemplates, variables)); - }; - this.Tracker.click(); - done(); - }); - - it('should have sent clicktracking events', () => { - const ISOTimeStamp = util.encodeURIComponentRFC3986( - new Date().toISOString() - ); - _eventsSent[0].should.eql([ - 'http://example.com/nonlinear-clicktracking-1', - 'http://example.com/nonlinear-clicktracking-2', - ]); - }); - - it('should have sent clickthrough event with clickThrough url', () => { - _eventsSent[1].event.should.eql('clickthrough'); - _eventsSent[1].args.should.eql([ - 'http://example.com/nonlinear-clickthrough', - ]); - }); - }); - }); - }); - - describe('#clickthroughs', () => { - const fallbackClickThroughURL = 'http://example.com/fallback-clickthrough', - clickThroughURL = 'http://example.com/clickthrough'; - - describe('#VAST clickThrough with no fallback provided', () => { - const eventsSent = []; - before(() => { - // Init tracker - const creative = createCreativeLinear(); - creative.videoClickThroughURLTemplate = clickThroughURL; - const tracker = new VASTTracker(vastClient, {}, creative); - // Mock emit - tracker.emit = (event, ...args) => { - eventsSent.push({ event, args }); - }; - tracker.click(); - }); - it('should have sent clickthrough event with VAST clickThrough url', () => { - eventsSent[0].event.should.eql('clickthrough'); - eventsSent[0].args.should.eql([clickThroughURL]); - }); - }); - - describe('#VAST clickThrough with fallback provided', () => { - const eventsSent = []; - before(() => { - // Init tracker - const creative = createCreativeLinear(); - creative.videoClickThroughURLTemplate = clickThroughURL; - const tracker = new VASTTracker(vastClient, {}, creative); - // Mock emit - tracker.emit = (event, ...args) => { - eventsSent.push({ event, args }); - }; - tracker.click(fallbackClickThroughURL); - }); - it('should have sent clickthrough event with VAST clickThrough url', () => { - eventsSent[0].event.should.eql('clickthrough'); - eventsSent[0].args.should.eql([clickThroughURL]); - }); - }); - - describe('#empty VAST clickThrough with no fallback provided', () => { - const eventsSent = []; - before(() => { - // Init tracker - const tracker = new VASTTracker(vastClient, {}, {}); - // Mock emit - tracker.emit = (event, ...args) => { - eventsSent.push({ event, args }); - }; - tracker.click(); - }); - it("shouldn't have sent any event", () => { - eventsSent.should.have.length(0); - }); - }); - - describe('#empty VAST clickThrough with fallback provided', () => { - const eventsSent = []; - before(() => { - // Init tracker - const tracker = new VASTTracker(vastClient, {}, {}); - // Mock emit - tracker.emit = (event, ...args) => { - eventsSent.push({ event, args }); - }; - tracker.click(fallbackClickThroughURL); - }); - it('should have sent fallback clickthrough', () => { - eventsSent[0].event.should.eql('clickthrough'); - eventsSent[0].args.should.eql([fallbackClickThroughURL]); - }); - }); - }); - - describe('#Tracked pixels events', () => { - const eventsSent = []; - const creativeTrackingUrls = ['http://creativeView']; - - before(() => { - // Init tracker - const creative = createCreativeLinear(); - creative.trackingEvents.creativeView = creativeTrackingUrls; - - const tracker = new VASTTracker(null, {}, creative); - // Mock emit - tracker.emit = (event, ...args) => { - eventsSent.push({ event, args }); - }; - tracker.trackImpression(); - }); - - it('should contain trackingUrls info in the event payload', () => { - eventsSent[0].args[0].trackingURLTemplates.should.eql( - creativeTrackingUrls - ); - }); - }); -}); From f6229fb356a71736b117e9332c44fe57303a012a Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Thu, 17 Aug 2023 11:52:33 +0200 Subject: [PATCH 10/95] removing mocha command from package.json --- package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index 9ded5f50..9f0bf727 100644 --- a/package.json +++ b/package.json @@ -37,8 +37,7 @@ "postbuild": "node bundle_size.js", "lint": "eslint .", "precommit": "eslint . --max-warnings 0 && pretty-quick --staged", - "test": "mocha --require @babel/register && jest", - "jest": "jest" + "test": "jest" }, "devDependencies": { "@babel/core": "^7.16.12", From 8ccc8d570ef2a87121efdc09c5bdeeebb9794f20 Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Fri, 25 Aug 2023 16:43:33 +0200 Subject: [PATCH 11/95] [tools] Removing mocha --- package-lock.json | 599 ---------------------------------------------- package.json | 3 +- 2 files changed, 1 insertion(+), 601 deletions(-) diff --git a/package-lock.json b/package-lock.json index f80b6769..9480b4d0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,7 +27,6 @@ "eslint-plugin-import": "^2.25.4", "husky": "^8.0.3", "jest": "^26.6.3", - "mocha": "^10.0.0", "path": "^0.12.7", "prettier": "^2.5.1", "pretty-quick": "^3.1.3", @@ -4360,15 +4359,6 @@ "node": ">=0.10.0" } }, - "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -4397,12 +4387,6 @@ "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", "dev": true }, - "node_modules/browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true - }, "node_modules/browserslist": { "version": "4.21.10", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.10.tgz", @@ -4583,33 +4567,6 @@ "node": ">=10" } }, - "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, "node_modules/chownr": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", @@ -5079,15 +5036,6 @@ "node": ">=8" } }, - "node_modules/diff": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, "node_modules/diff-sequences": { "version": "26.6.2", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz", @@ -6149,15 +6097,6 @@ "node": ">=8" } }, - "node_modules/flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true, - "bin": { - "flat": "cli.js" - } - }, "node_modules/flat-cache": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", @@ -6674,15 +6613,6 @@ "node": ">=0.10.0" } }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, - "bin": { - "he": "bin/he" - } - }, "node_modules/home-or-tmp": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", @@ -6931,18 +6861,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/is-boolean-object": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", @@ -7163,15 +7081,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", @@ -7290,18 +7199,6 @@ "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", "dev": true }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-weakref": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", @@ -9283,92 +9180,6 @@ "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", "dev": true }, - "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-symbols/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/log-symbols/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/log-symbols/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/log-symbols/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/log-symbols/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/log-symbols/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/lolex": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/lolex/-/lolex-1.6.0.tgz", @@ -9604,317 +9415,6 @@ "mkdirp": "bin/cmd.js" } }, - "node_modules/mocha": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", - "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", - "dev": true, - "dependencies": { - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.5.3", - "debug": "4.3.4", - "diff": "5.0.0", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "7.2.0", - "he": "1.2.0", - "js-yaml": "4.1.0", - "log-symbols": "4.1.0", - "minimatch": "5.0.1", - "ms": "2.1.3", - "nanoid": "3.3.3", - "serialize-javascript": "6.0.0", - "strip-json-comments": "3.1.1", - "supports-color": "8.1.1", - "workerpool": "6.2.1", - "yargs": "16.2.0", - "yargs-parser": "20.2.4", - "yargs-unparser": "2.0.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha.js" - }, - "engines": { - "node": ">= 14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mochajs" - } - }, - "node_modules/mocha/node_modules/ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/mocha/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/mocha/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/mocha/node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/mocha/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/mocha/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/mocha/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/mocha/node_modules/glob/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/mocha/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/mocha/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/mocha/node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/minimatch": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", - "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mocha/node_modules/minimatch/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/mocha/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "node_modules/mocha/node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/mocha/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/mocha/node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/mocha/node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/mri": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", @@ -9952,18 +9452,6 @@ "integrity": "sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==", "dev": true }, - "node_modules/nanoid": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", - "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", - "dev": true, - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, "node_modules/nanomatch": { "version": "1.2.13", "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", @@ -11027,18 +10515,6 @@ "node": ">= 6" } }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -11740,15 +11216,6 @@ "semver": "bin/semver.js" } }, - "node_modules/serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", - "dev": true, - "dependencies": { - "randombytes": "^2.1.0" - } - }, "node_modules/set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", @@ -13441,12 +12908,6 @@ "string-width": "^1.0.2 || 2 || 3 || 4" } }, - "node_modules/workerpool": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", - "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", - "dev": true - }, "node_modules/wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", @@ -13579,54 +13040,6 @@ "node": ">=8" } }, - "node_modules/yargs-parser": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-unparser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", - "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", - "dev": true, - "dependencies": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0", - "flat": "^5.0.2", - "is-plain-obj": "^2.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-unparser/node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yargs-unparser/node_modules/decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/yargs/node_modules/yargs-parser": { "version": "18.1.3", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", @@ -13639,18 +13052,6 @@ "engines": { "node": ">=6" } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } } } } diff --git a/package.json b/package.json index 9f0bf727..1963edac 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "engines": { "node": ">=12.22.1" }, - "browserslist" : [ + "browserslist": [ "chrome >= 63", "firefox >= 67", "edge >= 79", @@ -55,7 +55,6 @@ "eslint-plugin-import": "^2.25.4", "husky": "^8.0.3", "jest": "^26.6.3", - "mocha": "^10.0.0", "path": "^0.12.7", "prettier": "^2.5.1", "pretty-quick": "^3.1.3", From 3c23dafe57d18a81ed06e37ad29d20151c20123e Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Fri, 25 Aug 2023 16:44:31 +0200 Subject: [PATCH 12/95] Removing unnecessary comment --- src/fetcher.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/fetcher.js b/src/fetcher.js index 33f7fb10..86bf4d42 100644 --- a/src/fetcher.js +++ b/src/fetcher.js @@ -15,7 +15,6 @@ export class Fetcher { setOptions(options = {}) { this.urlHandler = options.urlHandler || options.urlhandler || urlHandler; - // this.URLTemplateFilters = []; this.fetchingOptions = { timeout: options.timeout || DEFAULT_TIMEOUT, withCredentials: options.withCredentials || false, From f169bc557aec7bd783708d3d68d5d2bf2d3cec99 Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Tue, 29 Aug 2023 12:34:13 +0200 Subject: [PATCH 13/95] [parser] checking Inline first to prevent VASTAdTagURI to be in the final inline object --- src/parser/vast_parser.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/parser/vast_parser.js b/src/parser/vast_parser.js index d8b440c7..a63ad500 100644 --- a/src/parser/vast_parser.js +++ b/src/parser/vast_parser.js @@ -360,6 +360,13 @@ export class VASTParser extends EventEmitter { // Going one level deeper in the wrapper chain wrapperDepth++; + // We already have a resolved VAST ad, no need to resolve wrapper + + if (!ad.nextWrapperURL) { + delete ad.nextWrapperURL; + return resolve(ad); + } + // If you need to fectch a VAST document, or follow additional wrapper, // you need to use the get method from VASTClient, this method will use the fetchVAST method from the // Fetcher class to fetch your document and set the fetchingMethod in case you want to fetch additional wrapper @@ -368,12 +375,7 @@ export class VASTParser extends EventEmitter { delete ad.nextWrapperURL; return resolve(ad); } - // We already have a resolved VAST ad, no need to resolve wrapper - if (!ad.nextWrapperURL) { - delete ad.nextWrapperURL; - return resolve(ad); - } if (wrapperDepth >= this.maxWrapperDepth) { // Wrapper limit reached, as defined by the video player. // Too many Wrapper responses have been received with no InLine response. @@ -402,7 +404,6 @@ export class VASTParser extends EventEmitter { this.emit.bind(this) ) .then((xml) => { - this.rootURL = ad.nextWrapperURL; return this.parse(xml, { url: ad.nextWrapperURL, previousUrl, From f5a55752df60a6f18878883ee906758d6ac7c5e3 Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Tue, 29 Aug 2023 12:38:03 +0200 Subject: [PATCH 14/95] [client] rootURL should be set when the first VAST is fetch --- src/vast_client.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vast_client.js b/src/vast_client.js index f4f881d9..9990bac6 100644 --- a/src/vast_client.js +++ b/src/vast_client.js @@ -176,6 +176,7 @@ export class VASTClient { this.vastParser.initParsingStatus(options); this.fetcher.setOptions(options); + this.vastParser.rootURL = url; this.fetcher .fetchVAST( From 06be203d70b72edb632986cae0803ae26cf424f3 Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Wed, 30 Aug 2023 13:51:31 +0200 Subject: [PATCH 15/95] [tools] updating rollup config since there is no need to have one bundle per module --- package.json | 4 ++-- rollup.config.js | 32 +++----------------------------- 2 files changed, 5 insertions(+), 31 deletions(-) diff --git a/package.json b/package.json index 1963edac..329e04a1 100644 --- a/package.json +++ b/package.json @@ -28,8 +28,8 @@ "publishConfig": { "registry": "https://registry.npmjs.org" }, - "main": "dist/node/min/vast-client-node.min.js", - "browser": "dist/min/vast-client.min.js", + "main": "dist/vast-client-node.min.js", + "browser": "dist/vast-client.min.js", "scripts": { "performance": "node performance/performance_test.js", "prebuild": "rm -rf dist_old && mkdir dist_old && cp -a dist/. dist_old/", diff --git a/rollup.config.js b/rollup.config.js index dcd8aaf6..9a8df337 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -23,11 +23,11 @@ function createNodeConfig(filePath, minifiedOutput, notMinifiedOutput) { output: [ { format: 'cjs', - file: `dist/node/${notMinifiedOutput}`, + file: `dist/${notMinifiedOutput}`, }, { format: 'cjs', - file: `dist/node/min/${minifiedOutput}`, + file: `dist/${minifiedOutput}`, plugins: [terser()], }, ], @@ -63,7 +63,7 @@ function createBrowserConfig(filePath, minifiedOutput, notMinifiedOutput) { }, { format: 'es', - file: `dist/min/${minifiedOutput}`, + file: `dist/${minifiedOutput}`, plugins: [terser()], }, ], @@ -75,37 +75,11 @@ function createBrowserConfig(filePath, minifiedOutput, notMinifiedOutput) { export default [ // Browser-friendly build [package.json "browser"] - createBrowserConfig( - 'src/parser/vast_parser.js', - 'vast-parser.min.js', - 'vast-parser.js' - ), - createBrowserConfig( - 'src/vast_tracker.js', - 'vast-tracker.min.js', - 'vast-tracker.js' - ), createBrowserConfig('src/index.js', 'vast-client.min.js', 'vast-client.js'), - createBrowserConfig('src/vast_client.js', 'client.min.js', 'client.js'), // CommonJS build for Node usage [package.json "main"] createNodeConfig( 'src/index.js', 'vast-client-node.min.js', 'vast-client-node.js' ), - createNodeConfig( - 'src/parser/vast_parser.js', - 'vast-parser-node.min.js', - 'vast-parser-node.min.js' - ), - createNodeConfig( - 'src/vast_tracker.js', - 'vast-tracker-node.min.js', - 'vast-tracker-node.js' - ), - createNodeConfig( - 'src/vast_client.js', - 'client-node.min.js', - 'client-node.js' - ), ]; From be2907f0f077fdaf10be84bd7748313bf7f5afff Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Thu, 31 Aug 2023 10:58:23 +0200 Subject: [PATCH 16/95] [docs] WIP/modifying documentations for modularization --- docs/api/vast-client.md | 121 +++++++++++++++++++++++++++++++ docs/api/vast-parser.md | 120 +------------------------------ docs/fetcher.md | 155 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 277 insertions(+), 119 deletions(-) create mode 100644 docs/fetcher.md diff --git a/docs/api/vast-client.md b/docs/api/vast-client.md index 1914029d..2cc40a4d 100644 --- a/docs/api/vast-client.md +++ b/docs/api/vast-client.md @@ -99,6 +99,74 @@ Instance of a class which implements the `Storage` interface. Should be set up o ## Public Methods 💚 +### addURLTemplateFilter(filter) + +Adds a filter function to the array of filters which are called before fetching a VAST document. + +#### Parameters + +- **`filter: function`** - The filter function to be added at the end of the array + +#### Example + +```Javascript +vastClient.addURLTemplateFilter( vastUrl => { + return url.replace('[DOMAIN]', 'mywebsite.com') +}); + +/* +For a VASTAdTagURI defined as : +http://example.dailymotion.com/vast.php?domain=[DOMAIN] +HTTP request will be: +http://example.dailymotion.com/vast.php?domain=mywebsite.com +*/ +``` + +### removeURLTemplateFilter() + +Removes the last element of the url templates filters array. + +#### Example + +```Javascript +const replaceDomain = () => { + return url.replace('[DOMAIN]', 'mywebsite.com') +}; + +vastClient.addURLTemplateFilter(replaceDomain); +// ... +vastClient.removeURLTemplateFilter(replaceDomain); +// [DOMAIN] placeholder is no longer replaced +``` +### countURLTemplateFilters() + +Returns the number of filters of the url templates filters array. + +#### Example + +```Javascript +vastClient.addURLTemplateFilter( vastUrl => { + return url.replace('[DOMAIN]', 'mywebsite.com') +}); + +vastClient.countUrlTemplateFilters(); +// returns 1 +``` + +### clearURLTemplateFilters() + +Removes all the filter functions from the url templates filters array. + +#### Example + +```Javascript +vastClient.addURLTemplateFilter( vastUrl => { + return url.replace('[DOMAIN]', 'mywebsite.com') +}); + +vastClient.clearUrlTemplateFilters(); +// [DOMAIN] placeholder is no longer replaced +``` ### get(url, options): Promise Gets a parsed VAST document for the given url, applying the skipping rules defined (`cappingFreeLunch` and `cappingMinimumTimeInterval`). @@ -147,6 +215,59 @@ vastClient.get('http://example.dailymotion.com/vast.xml', options) // Deal with the error }); ``` +### parseVAST(vastXml, options) + +Parses the given xml Object into a [VASTResponse](https://github.com/dailymotion/vast-client-js/blob/master/docs/api/class-reference.md#vastresponse). +Returns a `Promise` which either resolves with the fully parsed `VASTResponse` or rejects with an `Error`. + +#### Parameters + +- **`vastXml: Object`** - An object representing an xml document +- **`options: Object`** - An optional Object of parameters to be used in the parsing process + - `timeout: Number` - A custom timeout for the possible wrapper resolving requests (default `120000`) + - `withCredentials: Boolean` - A boolean to enable the withCredentials options for the XHR URLHandler (default `false`) + - `wrapperLimit: Number` - A number of Wrapper responses that can be received with no InLine response (default `10`) + - `urlHandler: URLHandler` - Custom urlhandler to be used instead of the default ones [`urlhandlers`](../../src/urlhandlers) + - `urlhandler: URLHandler` - Fulfills the same purpose as `urlHandler`, which is the preferred parameter to use + - `allowMultipleAds: Boolean` - A boolean value that identifies whether multiple ads are allowed in the requested VAST response. This will override any value of allowMultipleAds attribute set in the VAST + - `followAdditionalWrappers: Boolean` - A boolean value that identifies whether subsequent Wrappers after a requested VAST response is allowed. This will override any value of followAdditionalWrappers attribute set in the VAST + - `requestDuration: Number` - The fetching time of the XML in ms. Provide it with byteLength to have a more accurate estimated bitrate. + - `byteLength: Number`- The size of the request in bytes. Provide it with requestDuration to have a more accurate estimated bitrate. + +#### Events emitted + +- **`VAST-resolved`** +- **`VAST-resolving`** +- **`VAST-warning`** + +#### Example + +```Javascript +const vastXml = (new window.DOMParser()).parseFromString(xmlStr, "text/xml"); + +vastParser.parseVAST(vastXml) + .then(res => { + // Do something with the parsed VAST response + }) + .catch(err => { + // Deal with the error + }); + +// Or with some options +const options = { + timeout: 5000, + withCredentials: true, + wrapperLimit: 7 +} +vastParser.parseVAST(vastXml, options) + .then(res => { + // Do something with the parsed VAST response + }) + .catch(err => { + // Deal with the error + }); +``` + #### How does resolveAll work diff --git a/docs/api/vast-parser.md b/docs/api/vast-parser.md index 1d09a98d..6a1b32b8 100644 --- a/docs/api/vast-parser.md +++ b/docs/api/vast-parser.md @@ -122,7 +122,7 @@ Event is triggered when `parseVastXml` function is called, when an Ad tag has be - `adIndex: Number|undefined` ```Javascript -vastParser.on('VAST-resolving', ({ url, wrapperDepth, previousUrl }) => { +vastParser.on('VAST-ad-parsed', ({ url, wrapperDepth, type,adIndex }) => { // Access to the info }); ``` @@ -136,75 +136,6 @@ Instance of the support class `URLHandler`, is used to make the requests. ## Public Methods 💚 -### addURLTemplateFilter(filter) - -Adds a filter function to the array of filters which are called before fetching a VAST document. - -#### Parameters - -- **`filter: function`** - The filter function to be added at the end of the array - -#### Example - -```Javascript -vastParser.addURLTemplateFilter( vastUrl => { - return url.replace('[DOMAIN]', 'mywebsite.com') -}); - -/* -For a VASTAdTagURI defined as : -http://example.dailymotion.com/vast.php?domain=[DOMAIN] -HTTP request will be: -http://example.dailymotion.com/vast.php?domain=mywebsite.com -*/ -``` - -### removeURLTemplateFilter() - -Removes the last element of the url templates filters array. - -#### Example - -```Javascript -const replaceDomain = () => { - return url.replace('[DOMAIN]', 'mywebsite.com') -}; - -vastParser.addURLTemplateFilter(replaceDomain); -// ... -vastParser.removeURLTemplateFilter(replaceDomain); -// [DOMAIN] placeholder is no longer replaced -``` - -### countURLTemplateFilters() - -Returns the number of filters of the url templates filters array. - -#### Example - -```Javascript -vastParser.addURLTemplateFilter( vastUrl => { - return url.replace('[DOMAIN]', 'mywebsite.com') -}); - -vastParser.countUrlTemplateFilters(); -// returns 1 -``` - -### clearURLTemplateFilters() - -Removes all the filter functions from the url templates filters array. - -#### Example - -```Javascript -vastParser.addURLTemplateFilter( vastUrl => { - return url.replace('[DOMAIN]', 'mywebsite.com') -}); - -vastParser.clearUrlTemplateFilters(); -// [DOMAIN] placeholder is no longer replaced -``` ### trackVastError(urlTemplates, errorCode, ...data) @@ -232,55 +163,6 @@ Fetches a VAST document for the given url. Returns a `Promise` which resolves wi - **`VAST-resolved`** - **`VAST-resolving`** -### getAndParseVAST(url, options = {}) - -Fetches and parses a VAST for the given url. -Returns a `Promise` which either resolves with the fully parsed [`VASTResponse`](https://github.com/dailymotion/vast-client-js/blob/master/docs/api/class-reference.md#vastresponse) or rejects with an `Error`. - -#### Parameters - -- **`url: String`** - The url to request the VAST document -- **`options: Object`** - An optional Object of parameters to be used in the request - - `timeout: Number` - A custom timeout for the requests (default `120000`) - - `withCredentials: Boolean` - A boolean to enable the withCredentials options for the XHR URLHandler (default `false`) - - `wrapperLimit: Number` - A number of Wrapper responses that can be received with no InLine response (default `10`) - - `urlHandler: URLHandler` - Custom urlhandler to be used instead of the default ones [`urlhandlers`](../../src/urlhandlers) - - `urlhandler: URLHandler` - Fulfills the same purpose as `urlHandler`, which is the preferred parameter to use - - `allowMultipleAds: Boolean` - A boolean value that identifies whether multiple ads are allowed in the requested VAST response. This will override any value of allowMultipleAds attribute set in the VAST - - `followAdditionalWrappers: Boolean` - A boolean value that identifies whether subsequent Wrappers after a requested VAST response is allowed. This will override any value of followAdditionalWrappers attribute set in the VAST - -#### Events emitted - -- **`VAST-resolved`** -- **`VAST-resolving`** -- **`VAST-warning`** - -#### Example - -```Javascript -vastParser.getAndParseVAST('http://example.dailymotion.com/vast.xml') - .then(res => { - // Do something with the parsed VAST response - }) - .catch(err => { - // Deal with the error - }); - -// With some options -const options = { - timeout: 5000, - withCredentials: true, - wrapperLimit: 7 -} -vastParser.getAndParseVAST('http://example.dailymotion.com/vast.xml', options) - .then(res => { - // Do something with the parsed VAST response - }) - .catch(err => { - // Deal with the error - }); -``` - ### parseVAST(vastXml, options) Parses the given xml Object into a [VASTResponse](https://github.com/dailymotion/vast-client-js/blob/master/docs/api/class-reference.md#vastresponse). diff --git a/docs/fetcher.md b/docs/fetcher.md new file mode 100644 index 00000000..ab6764e3 --- /dev/null +++ b/docs/fetcher.md @@ -0,0 +1,155 @@ +# Fetcher + +The `Fetcher` class provides a method to manage the fetching. + +Fetcher is not intended to be use directly. + +This documentation is provided in order to make the fecthing internal logic clearer. It should not be considered as part of the class public API. + +- [Constructor](#constructor) +- [Events](#events) +- [Properties](#properties) +- [Methods](#private-methods) + +## Contructor + +The constructor signature is: + +```Javascript +constructor() +``` + +## Events + +### VAST-resolving + +Event is triggered when `fetchVAST` function is called, before the fetching started. It carries the following data: + +- `url: String` +- `previousUrl: String|Null` +- `wrapperDepth: Number` +- `maxWrapperDepth: Number` +- `timeout: Number` +- `wrapperAd: Ad` + +```Javascript +vastParser.on('VAST-resolving', ({ url, wrapperDepth, previousUrl }) => { + // Access to the info +}); +``` + +### VAST-resolved + +Event is triggered when `fetchVAST` function is called, after the fetching was done. It carries the following data: + +- `url: String` +- `previousUrl: String|Null` +- `wrapperDepth: Number` +- `error: Error|Null` +- `duration: Number` +- `byteLength: Number|undefined` +- `statusCode: Number|undefined` + +```Javascript +vastParser.on('VAST-resolved', ({ url, error }) => { + // Access to the info +}); +``` +## Private Methods :warning: + +These methods documentation is provided in order to make the parser internal logic clearer. It should not be considered as part of the class public API. + +### fetchVAST(url, {wrapperDepth = 0, previousUrl = null, wrapperAd = null} = {}, maxWrapperDepth, emitter) + +Fetches a VAST document for the given url. Returns a `Promise` which resolves with the fetched xml or rejects with an error, according to the result of the request. + +#### Parameters + +- **`url: String`** - The url to request the VAST document +- **`wrapperDepth: Number`** - Number of wrappers that have occurred +- **`previousUrl: String`** - The url of the previous VAST +- **`wrapperAd: Ad`** - Previously parsed ad node (Wrapper) related to this fetching. +- **`maxWrapperDepth`** - The maximum number of Wrapper that can be fetch; +- **`emitter`** - The function used to Emit event + +#### Events emitted + +- **`VAST-resolved`** +- **`VAST-resolving`** + + +### addURLTemplateFilter(filter) + +Adds a filter function to the array of filters which are called before fetching a VAST document. + +#### Parameters + +- **`filter: function`** - The filter function to be added at the end of the array + +#### Example + +```Javascript +fetcher.addURLTemplateFilter( vastUrl => { + return url.replace('[DOMAIN]', 'mywebsite.com') +}); + +/* +For a VASTAdTagURI defined as : +http://example.dailymotion.com/vast.php?domain=[DOMAIN] +HTTP request will be: +http://example.dailymotion.com/vast.php?domain=mywebsite.com +*/ +``` + +### removeURLTemplateFilter() + +Removes the last element of the url templates filters array. + +#### Example + +```Javascript +const replaceDomain = () => { + return url.replace('[DOMAIN]', 'mywebsite.com') +}; + +fetcher.addURLTemplateFilter(replaceDomain); +// ... +fetcher.removeURLTemplateFilter(replaceDomain); +// [DOMAIN] placeholder is no longer replaced +``` +### countURLTemplateFilters() + +Returns the number of filters of the url templates filters array. + +#### Example + +```Javascript +fetcher.addURLTemplateFilter( vastUrl => { + return url.replace('[DOMAIN]', 'mywebsite.com') +}); + +fetcher.countUrlTemplateFilters(); +// returns 1 +``` + +### clearURLTemplateFilters() + +Removes all the filter functions from the url templates filters array. + +#### Example + +```Javascript +fetcher.addURLTemplateFilter( vastUrl => { + return url.replace('[DOMAIN]', 'mywebsite.com') +}); + +fetcher.clearUrlTemplateFilters(); +// [DOMAIN] placeholder is no longer replaced +``` + + + + + + + From 2c665d21ead7a60db240494a4d44a164c2d24d2e Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Thu, 31 Aug 2023 15:33:08 +0200 Subject: [PATCH 17/95] [docs] WIP/ updating documentations --- docs/{ => api}/fetcher.md | 7 +++++++ docs/api/vast-client.md | 3 +++ docs/api/vast-parser.md | 17 +++-------------- 3 files changed, 13 insertions(+), 14 deletions(-) rename docs/{ => api}/fetcher.md (96%) diff --git a/docs/fetcher.md b/docs/api/fetcher.md similarity index 96% rename from docs/fetcher.md rename to docs/api/fetcher.md index ab6764e3..03bd7418 100644 --- a/docs/fetcher.md +++ b/docs/api/fetcher.md @@ -55,6 +55,13 @@ vastParser.on('VAST-resolved', ({ url, error }) => { // Access to the info }); ``` +## Properties + +### urlHandler: URLHandler + +Instance of the support class `URLHandler`, is used to make the requests. + + ## Private Methods :warning: These methods documentation is provided in order to make the parser internal logic clearer. It should not be considered as part of the class public API. diff --git a/docs/api/vast-client.md b/docs/api/vast-client.md index 2cc40a4d..f1031bdf 100644 --- a/docs/api/vast-client.md +++ b/docs/api/vast-client.md @@ -220,6 +220,9 @@ vastClient.get('http://example.dailymotion.com/vast.xml', options) Parses the given xml Object into a [VASTResponse](https://github.com/dailymotion/vast-client-js/blob/master/docs/api/class-reference.md#vastresponse). Returns a `Promise` which either resolves with the fully parsed `VASTResponse` or rejects with an `Error`. +By using this method, you will be able to perform fetching in case you want to parse and unwrappe a wrapper. +If you just need to parse an inline VAST or you want to parse the first VAST document encountered, you should use the `parseVAST` method from the `VASTParser`. + #### Parameters - **`vastXml: Object`** - An object representing an xml document diff --git a/docs/api/vast-parser.md b/docs/api/vast-parser.md index 6a1b32b8..9a82f6e1 100644 --- a/docs/api/vast-parser.md +++ b/docs/api/vast-parser.md @@ -127,13 +127,6 @@ vastParser.on('VAST-ad-parsed', ({ url, wrapperDepth, type,adIndex }) => { }); ``` - -## Properties - -### urlHandler: URLHandler - -Instance of the support class `URLHandler`, is used to make the requests. - ## Public Methods 💚 @@ -168,18 +161,14 @@ Fetches a VAST document for the given url. Returns a `Promise` which resolves wi Parses the given xml Object into a [VASTResponse](https://github.com/dailymotion/vast-client-js/blob/master/docs/api/class-reference.md#vastresponse). Returns a `Promise` which either resolves with the fully parsed `VASTResponse` or rejects with an `Error`. +This method will not proceed to any fetching, the final response will only contain the first VAST encountered. +If you need to parse and unwrapper a wrapper, you should use the `parseVAST` method from the `VASTClient`. + #### Parameters - **`vastXml: Object`** - An object representing an xml document - **`options: Object`** - An optional Object of parameters to be used in the parsing process - - `timeout: Number` - A custom timeout for the possible wrapper resolving requests (default `120000`) - - `withCredentials: Boolean` - A boolean to enable the withCredentials options for the XHR URLHandler (default `false`) - - `wrapperLimit: Number` - A number of Wrapper responses that can be received with no InLine response (default `10`) - - `urlHandler: URLHandler` - Custom urlhandler to be used instead of the default ones [`urlhandlers`](../../src/urlhandlers) - - `urlhandler: URLHandler` - Fulfills the same purpose as `urlHandler`, which is the preferred parameter to use - `allowMultipleAds: Boolean` - A boolean value that identifies whether multiple ads are allowed in the requested VAST response. This will override any value of allowMultipleAds attribute set in the VAST - - `followAdditionalWrappers: Boolean` - A boolean value that identifies whether subsequent Wrappers after a requested VAST response is allowed. This will override any value of followAdditionalWrappers attribute set in the VAST - - `requestDuration: Number` - The fetching time of the XML in ms. Provide it with byteLength to have a more accurate estimated bitrate. - `byteLength: Number`- The size of the request in bytes. Provide it with requestDuration to have a more accurate estimated bitrate. #### Events emitted From 41ae3a738ea473ee58d5994ff5bbc6dbc53f637c Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Thu, 31 Aug 2023 16:19:10 +0200 Subject: [PATCH 18/95] [docs] removing unnecessary documentation --- docs/api/fetcher.md | 162 -------------------------------------------- 1 file changed, 162 deletions(-) delete mode 100644 docs/api/fetcher.md diff --git a/docs/api/fetcher.md b/docs/api/fetcher.md deleted file mode 100644 index 03bd7418..00000000 --- a/docs/api/fetcher.md +++ /dev/null @@ -1,162 +0,0 @@ -# Fetcher - -The `Fetcher` class provides a method to manage the fetching. - -Fetcher is not intended to be use directly. - -This documentation is provided in order to make the fecthing internal logic clearer. It should not be considered as part of the class public API. - -- [Constructor](#constructor) -- [Events](#events) -- [Properties](#properties) -- [Methods](#private-methods) - -## Contructor - -The constructor signature is: - -```Javascript -constructor() -``` - -## Events - -### VAST-resolving - -Event is triggered when `fetchVAST` function is called, before the fetching started. It carries the following data: - -- `url: String` -- `previousUrl: String|Null` -- `wrapperDepth: Number` -- `maxWrapperDepth: Number` -- `timeout: Number` -- `wrapperAd: Ad` - -```Javascript -vastParser.on('VAST-resolving', ({ url, wrapperDepth, previousUrl }) => { - // Access to the info -}); -``` - -### VAST-resolved - -Event is triggered when `fetchVAST` function is called, after the fetching was done. It carries the following data: - -- `url: String` -- `previousUrl: String|Null` -- `wrapperDepth: Number` -- `error: Error|Null` -- `duration: Number` -- `byteLength: Number|undefined` -- `statusCode: Number|undefined` - -```Javascript -vastParser.on('VAST-resolved', ({ url, error }) => { - // Access to the info -}); -``` -## Properties - -### urlHandler: URLHandler - -Instance of the support class `URLHandler`, is used to make the requests. - - -## Private Methods :warning: - -These methods documentation is provided in order to make the parser internal logic clearer. It should not be considered as part of the class public API. - -### fetchVAST(url, {wrapperDepth = 0, previousUrl = null, wrapperAd = null} = {}, maxWrapperDepth, emitter) - -Fetches a VAST document for the given url. Returns a `Promise` which resolves with the fetched xml or rejects with an error, according to the result of the request. - -#### Parameters - -- **`url: String`** - The url to request the VAST document -- **`wrapperDepth: Number`** - Number of wrappers that have occurred -- **`previousUrl: String`** - The url of the previous VAST -- **`wrapperAd: Ad`** - Previously parsed ad node (Wrapper) related to this fetching. -- **`maxWrapperDepth`** - The maximum number of Wrapper that can be fetch; -- **`emitter`** - The function used to Emit event - -#### Events emitted - -- **`VAST-resolved`** -- **`VAST-resolving`** - - -### addURLTemplateFilter(filter) - -Adds a filter function to the array of filters which are called before fetching a VAST document. - -#### Parameters - -- **`filter: function`** - The filter function to be added at the end of the array - -#### Example - -```Javascript -fetcher.addURLTemplateFilter( vastUrl => { - return url.replace('[DOMAIN]', 'mywebsite.com') -}); - -/* -For a VASTAdTagURI defined as : -http://example.dailymotion.com/vast.php?domain=[DOMAIN] -HTTP request will be: -http://example.dailymotion.com/vast.php?domain=mywebsite.com -*/ -``` - -### removeURLTemplateFilter() - -Removes the last element of the url templates filters array. - -#### Example - -```Javascript -const replaceDomain = () => { - return url.replace('[DOMAIN]', 'mywebsite.com') -}; - -fetcher.addURLTemplateFilter(replaceDomain); -// ... -fetcher.removeURLTemplateFilter(replaceDomain); -// [DOMAIN] placeholder is no longer replaced -``` -### countURLTemplateFilters() - -Returns the number of filters of the url templates filters array. - -#### Example - -```Javascript -fetcher.addURLTemplateFilter( vastUrl => { - return url.replace('[DOMAIN]', 'mywebsite.com') -}); - -fetcher.countUrlTemplateFilters(); -// returns 1 -``` - -### clearURLTemplateFilters() - -Removes all the filter functions from the url templates filters array. - -#### Example - -```Javascript -fetcher.addURLTemplateFilter( vastUrl => { - return url.replace('[DOMAIN]', 'mywebsite.com') -}); - -fetcher.clearUrlTemplateFilters(); -// [DOMAIN] placeholder is no longer replaced -``` - - - - - - - From 33c34ef2702946691fb17c5f495454016a9a9977 Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Thu, 31 Aug 2023 17:43:14 +0200 Subject: [PATCH 19/95] [docs] updating docs --- README.md | 18 +++++++++++++++++- docs/api/vast-client.md | 2 +- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index af2173d7..350e3806 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ Then import the components you need. ### VASTClient -If you need to fetch and parse VAST documents, you can use the **VASTClient**: +If you need to fetch and parse VAST documents, you can use the `get` method from the **VASTClient**: ```javascript import { VASTClient } from '@dailymotion/vast-client' @@ -47,9 +47,25 @@ vastClient.get('https://www.examplevast.com/vast.xml') In addition to fetching and parsing a VAST resource, **VASTClient** provides options to filter a sequence of calls based on count and time of execution, together with the possibility to track URLs using **VASTTracker**. +If you need to directy parse a VAST XML and follow a wrappers chain, you can use the `VASTClient` just like so : + +```javascript +import { VASTClient } from '@dailymotion/vast-client' + +const vastClient = new VASTClient(); + +vastClient.parseVAST(vastXml) + .then(parsedVAST => { + // Do something with the parsed VAST response + }) + .catch(err => { + // Deal with the error + }); +``` ### VASTParser To directly parse a VAST XML you can use the **VASTParser**: +The VASTParser will not proceed to any fetching, the final response will only contain the first VAST encountered. ```Javascript import { VASTParser } from '@dailymotion/vast-client' diff --git a/docs/api/vast-client.md b/docs/api/vast-client.md index f1031bdf..dab4ae24 100644 --- a/docs/api/vast-client.md +++ b/docs/api/vast-client.md @@ -220,7 +220,7 @@ vastClient.get('http://example.dailymotion.com/vast.xml', options) Parses the given xml Object into a [VASTResponse](https://github.com/dailymotion/vast-client-js/blob/master/docs/api/class-reference.md#vastresponse). Returns a `Promise` which either resolves with the fully parsed `VASTResponse` or rejects with an `Error`. -By using this method, you will be able to perform fetching in case you want to parse and unwrappe a wrapper. +By using this method, you will be able to perform fetching in case you want to parse a VAST document and follow a wrappers chain. If you just need to parse an inline VAST or you want to parse the first VAST document encountered, you should use the `parseVAST` method from the `VASTParser`. #### Parameters From ae5f7ce444c4142b1760613dd1c6c5ab7bdee2fd Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Fri, 1 Sep 2023 15:04:21 +0200 Subject: [PATCH 20/95] [docs] updating documentations --- docs/api/vast-client.md | 4 +++- docs/api/vast-parser.md | 8 ++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/docs/api/vast-client.md b/docs/api/vast-client.md index dab4ae24..b0f54074 100644 --- a/docs/api/vast-client.md +++ b/docs/api/vast-client.md @@ -2,6 +2,8 @@ The `VASTClient` class provides a client to manage the fetching and parsing of VAST documents. +`VASTClient` provides a methods to fetch a VAST resource because of his ability to resolving the wrapper chain (recursive fetch and parse) by using the `VASTParser` parsing methods. + - [Constructor](#constructor) - [Properties](#properties) - [Methods](#methods) @@ -221,7 +223,7 @@ Parses the given xml Object into a [VASTResponse](https://github.com/dailymotion Returns a `Promise` which either resolves with the fully parsed `VASTResponse` or rejects with an `Error`. By using this method, you will be able to perform fetching in case you want to parse a VAST document and follow a wrappers chain. -If you just need to parse an inline VAST or you want to parse the first VAST document encountered, you should use the `parseVAST` method from the `VASTParser`. +If you just need to parse an inline VAST or you want to parse the first VAST document encountered, you should use the **parseVAST** method from the **VASTParser**. #### Parameters diff --git a/docs/api/vast-parser.md b/docs/api/vast-parser.md index 9a82f6e1..ce476c3a 100644 --- a/docs/api/vast-parser.md +++ b/docs/api/vast-parser.md @@ -1,13 +1,9 @@ # VASTParser -The `VASTParser` class provides a parser to manage the fetching ([`getAndParseVAST`](#getandparse) method) and direct parsing ([`parseVAST`](#parse) method) of VAST documents. - -The behavior of this component may be confused with the one of `VASTClient`, since they both provide a way to fetch and parse a VAST document. +The `VASTParser` class provides a parser to manage the parsing ([`parseVAST`](#parse) method) of VAST documents. `VASTClient` has to be intended as the preferred way to manage a sequence of VAST requests on a higher level, while `VASTParser` offers a set of method to follow in more detail the parsing process. -`VASTParser` provides methods to fetch a VAST resource because of his ability to resolving the wrapper chain (recursive fetch and parse). - Use an instance of this class directly only if you don't need any control on multiple calls, otherwise access it through an instance of `VASTClient`. - [Constructor](#constructor) @@ -162,7 +158,7 @@ Parses the given xml Object into a [VASTResponse](https://github.com/dailymotion Returns a `Promise` which either resolves with the fully parsed `VASTResponse` or rejects with an `Error`. This method will not proceed to any fetching, the final response will only contain the first VAST encountered. -If you need to parse and unwrapper a wrapper, you should use the `parseVAST` method from the `VASTClient`. +If you need to parse and follow a wrappers chain, you should use the **parseVAST** method from the **VASTClient**. #### Parameters From 5f7ade13a4cce330f515a556656fc18ca98df95e Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Fri, 1 Sep 2023 15:05:21 +0200 Subject: [PATCH 21/95] [docs] updating README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 350e3806..4108c3db 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ vastClient.get('https://www.examplevast.com/vast.xml') In addition to fetching and parsing a VAST resource, **VASTClient** provides options to filter a sequence of calls based on count and time of execution, together with the possibility to track URLs using **VASTTracker**. -If you need to directy parse a VAST XML and follow a wrappers chain, you can use the `VASTClient` just like so : +If you need to directy parse a VAST XML and follow a wrappers chain, you can use the **VASTClient** just like so : ```javascript import { VASTClient } from '@dailymotion/vast-client' @@ -65,7 +65,7 @@ vastClient.parseVAST(vastXml) ### VASTParser To directly parse a VAST XML you can use the **VASTParser**: -The VASTParser will not proceed to any fetching, the final response will only contain the first VAST encountered. +The **VASTParser** will not proceed to any fetching, the final response will only contain the first VAST encountered. ```Javascript import { VASTParser } from '@dailymotion/vast-client' From a26ab518fadbaa5e92f2cf34f6fe63e05581fcdb Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Mon, 4 Sep 2023 12:03:58 +0200 Subject: [PATCH 22/95] [docs] updating README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4108c3db..f8e3bce1 100644 --- a/README.md +++ b/README.md @@ -126,7 +126,7 @@ const vastTracker = new VASTTracker(); #### Browser script -A pre-bundled version of VAST Client JS is available: [`vast-client-browser.min.js`](dist/vast-client-browser.min.js) [minified]. +A pre-bundled version of VAST Client JS is available: [`vast-client.min.js`](dist/vast-client.min.js) [minified]. You can add the script directly to your page and access the library's components through the `VAST` object. From fb4830c933113e4addb26109493ea6cb4b2f2c73 Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Mon, 4 Sep 2023 13:35:53 +0200 Subject: [PATCH 23/95] [docs] updating readme --- README.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index f8e3bce1..d15aebdf 100644 --- a/README.md +++ b/README.md @@ -128,16 +128,18 @@ const vastTracker = new VASTTracker(); A pre-bundled version of VAST Client JS is available: [`vast-client.min.js`](dist/vast-client.min.js) [minified]. -You can add the script directly to your page and access the library's components through the `VAST` object. +If you want to use this file, add it to your project and make sure that your main file is in type module as below. ```html - + ``` ```javascript -const vastClient = new VAST.VASTClient(); -const vastParser = new VAST.VASTParser(); -const vastTracker = new VAST.VASTTracker(); +import {VASTClient, VASTParser, VASTTracker} from "vast-client.min.js" + +const vastClient = new VASTClient(); +const vastParser = new VASTParser(); +const vastTracker = new VASTTracker(); ``` #### Node From 9cd9f7b91859896a333628494fde79ea7e918199 Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Tue, 12 Sep 2023 12:30:17 +0200 Subject: [PATCH 24/95] [test] updating vast version in tests files --- spec/samples/empty-no-ad.xml | 2 +- spec/samples/empty-no-creative.xml | 2 +- spec/samples/inline-linear.xml | 7 ++++--- spec/samples/inline_missing_values.js | 6 +++--- spec/samples/inline_trackers.js | 2 +- spec/samples/sample.xml | 16 ++++++++-------- spec/samples/vpaid.xml | 2 +- spec/samples/vpaid2.xml | 2 +- spec/samples/wrapper-a.xml | 6 +++--- spec/samples/wrapper-attributes-multiple-ads.xml | 2 +- spec/samples/wrapper-b.xml | 2 +- spec/samples/wrapper-empty-no-creative.xml | 4 ++-- spec/samples/wrapper-empty.xml | 2 +- spec/samples/wrapper-invalid-xmlfile.xml | 4 ++-- spec/samples/wrapper-multiple-ads.xml | 2 +- spec/samples/wrapper-unavailable-url.xml | 4 ++-- spec/samples/wrapper_missing_values.js | 2 +- 17 files changed, 34 insertions(+), 33 deletions(-) diff --git a/spec/samples/empty-no-ad.xml b/spec/samples/empty-no-ad.xml index 8290be0d..e3a38558 100644 --- a/spec/samples/empty-no-ad.xml +++ b/spec/samples/empty-no-ad.xml @@ -1,4 +1,4 @@ - + diff --git a/spec/samples/empty-no-creative.xml b/spec/samples/empty-no-creative.xml index 85a9a96e..c03ebeb7 100644 --- a/spec/samples/empty-no-creative.xml +++ b/spec/samples/empty-no-creative.xml @@ -1,5 +1,5 @@ - + No Ad Server diff --git a/spec/samples/inline-linear.xml b/spec/samples/inline-linear.xml index f599a1fe..1777a117 100644 --- a/spec/samples/inline-linear.xml +++ b/spec/samples/inline-linear.xml @@ -1,4 +1,5 @@ - + iabtechlab @@ -17,14 +18,14 @@ 00:00:16 - http://example.com/tracking/start + http://example.com/tracking/start http://example.com/tracking/firstQuartile http://example.com/tracking/midpoint http://example.com/tracking/thirdQuartile http://example.com/tracking/complete - + diff --git a/spec/samples/inline_missing_values.js b/spec/samples/inline_missing_values.js index f2da9f68..1ee01d1f 100644 --- a/spec/samples/inline_missing_values.js +++ b/spec/samples/inline_missing_values.js @@ -9,7 +9,7 @@ const missingRequiredSubElements = ` `; const childMissingRequiredSubElements = ` - + @@ -48,7 +48,7 @@ const childMissingRequiredSubElements = ` `; const missingRequiredAttributes = ` - + Test sample ad @@ -184,7 +184,7 @@ const missingRequiredAttributes = ` `; const emptyRequiredValues = ` - + diff --git a/spec/samples/inline_trackers.js b/spec/samples/inline_trackers.js index 23f13cf7..afb530d9 100644 --- a/spec/samples/inline_trackers.js +++ b/spec/samples/inline_trackers.js @@ -232,7 +232,7 @@ export const inlineTrackersParsed = { adType: 'nonLinearAd', type: 'image/png', staticResource: - 'https://mms.businesswire.com/media/20150623005446/en/473787/21/iab_tech_lab.jpg', + 'https://www.iab.com/wp-content/uploads/2014/09/iab-tech-lab-6-644x290.png', htmlResource: '\n \n \n \n \n \n Document\n \n \n

titre

\n \n ', iframeResource: 'lient vers html', diff --git a/spec/samples/sample.xml b/spec/samples/sample.xml index 2fc1c140..a41a9a8d 100644 --- a/spec/samples/sample.xml +++ b/spec/samples/sample.xml @@ -1,12 +1,12 @@ - + - + - + @@ -28,7 +28,7 @@
- + 00:01:30.123 @@ -65,7 +65,7 @@ - + @@ -99,7 +99,7 @@ - + @@ -137,12 +137,12 @@
- + - + 30 diff --git a/spec/samples/vpaid.xml b/spec/samples/vpaid.xml index 1f1df59b..19b6c4e0 100644 --- a/spec/samples/vpaid.xml +++ b/spec/samples/vpaid.xml @@ -1,5 +1,5 @@ - + LiveRail diff --git a/spec/samples/vpaid2.xml b/spec/samples/vpaid2.xml index f554b066..65c6d829 100644 --- a/spec/samples/vpaid2.xml +++ b/spec/samples/vpaid2.xml @@ -1,4 +1,4 @@ - + 2.0 diff --git a/spec/samples/wrapper-a.xml b/spec/samples/wrapper-a.xml index b5172ec3..f271c911 100644 --- a/spec/samples/wrapper-a.xml +++ b/spec/samples/wrapper-a.xml @@ -1,5 +1,5 @@ - + VAST @@ -89,10 +89,10 @@ - + - + diff --git a/spec/samples/wrapper-attributes-multiple-ads.xml b/spec/samples/wrapper-attributes-multiple-ads.xml index 1efd8ffc..ad3d4ae7 100644 --- a/spec/samples/wrapper-attributes-multiple-ads.xml +++ b/spec/samples/wrapper-attributes-multiple-ads.xml @@ -1,4 +1,4 @@ - + iabtechlab diff --git a/spec/samples/wrapper-b.xml b/spec/samples/wrapper-b.xml index 13b42643..6f9e5b54 100644 --- a/spec/samples/wrapper-b.xml +++ b/spec/samples/wrapper-b.xml @@ -1,5 +1,5 @@ - + VAST diff --git a/spec/samples/wrapper-empty-no-creative.xml b/spec/samples/wrapper-empty-no-creative.xml index 5dfd2a1b..a04f82ce 100644 --- a/spec/samples/wrapper-empty-no-creative.xml +++ b/spec/samples/wrapper-empty-no-creative.xml @@ -1,5 +1,5 @@ - + VAST @@ -21,7 +21,7 @@ - + diff --git a/spec/samples/wrapper-empty.xml b/spec/samples/wrapper-empty.xml index bc32f984..ae9cdf90 100644 --- a/spec/samples/wrapper-empty.xml +++ b/spec/samples/wrapper-empty.xml @@ -1,5 +1,5 @@ - + VAST diff --git a/spec/samples/wrapper-invalid-xmlfile.xml b/spec/samples/wrapper-invalid-xmlfile.xml index 3a271a32..625599f1 100644 --- a/spec/samples/wrapper-invalid-xmlfile.xml +++ b/spec/samples/wrapper-invalid-xmlfile.xml @@ -1,5 +1,5 @@ - + VAST @@ -30,7 +30,7 @@ - + diff --git a/spec/samples/wrapper-multiple-ads.xml b/spec/samples/wrapper-multiple-ads.xml index 52dd2136..81554858 100644 --- a/spec/samples/wrapper-multiple-ads.xml +++ b/spec/samples/wrapper-multiple-ads.xml @@ -1,4 +1,4 @@ - + iabtechlab diff --git a/spec/samples/wrapper-unavailable-url.xml b/spec/samples/wrapper-unavailable-url.xml index 6f3674ff..2a348273 100644 --- a/spec/samples/wrapper-unavailable-url.xml +++ b/spec/samples/wrapper-unavailable-url.xml @@ -1,5 +1,5 @@ - + VAST @@ -30,7 +30,7 @@ - + diff --git a/spec/samples/wrapper_missing_values.js b/spec/samples/wrapper_missing_values.js index f5224914..fb7e01ee 100644 --- a/spec/samples/wrapper_missing_values.js +++ b/spec/samples/wrapper_missing_values.js @@ -1,5 +1,5 @@ export const vastMissingWrapperValues = ` - + VAST From 5409cb67ee57dc56e8ff8a1f5c6ecc13f1bbf203 Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Tue, 12 Sep 2023 12:35:50 +0200 Subject: [PATCH 25/95] [test] updating tests --- spec/fetcher.spec.js | 141 +++++++++++++++++++------------------ spec/util.spec.js | 28 ++++---- spec/vast_client.spec.js | 41 +++++------ spec/vast_parser.spec.js | 66 ++++++++--------- spec/vast_trackers.spec.js | 8 +-- 5 files changed, 134 insertions(+), 150 deletions(-) diff --git a/spec/fetcher.spec.js b/spec/fetcher.spec.js index 739d207e..0cf25860 100644 --- a/spec/fetcher.spec.js +++ b/spec/fetcher.spec.js @@ -3,10 +3,12 @@ import { VASTParser } from '../src/parser/vast_parser'; import * as Bitrate from '../src/parser/bitrate'; import { urlHandler } from '../src/url_handler'; import { expect } from '@jest/globals'; +import { getNodesFromXml } from './utils/utils'; describe('Fetcher', () => { - let fetcher, vastParser; - const xml = new DOMParser().parseFromString('', 'text/xml'); + let fetcher, vastParser, mockEmit; + + const xml = getNodesFromXml(''); const urlHandlerSuccess = { get: (url, options, cb) => { cb(null, xml, { byteLength: 1234, statusCode: 200 }); @@ -21,8 +23,7 @@ describe('Fetcher', () => { beforeEach(() => { fetcher = new Fetcher(); - vastParser = new VASTParser(); - jest.spyOn(vastParser, 'emit'); + mockEmit = jest.fn(); }); describe('fetchVAST', () => { @@ -42,7 +43,7 @@ describe('Fetcher', () => { const expectedUrl = 'www.bar.foo'; fetcher - .fetchVAST(url, {}, 5, () => {}) + .fetchVAST({ url: url, maxWrapperDepth: 5, emitter: () => {} }) .then(() => { expect(urlHandlerSpy).toHaveBeenCalledWith( expectedUrl, @@ -56,42 +57,38 @@ describe('Fetcher', () => { fetcher.URLTemplateFilters = [(url) => url.replace('foo', 'bar')]; const expectedUrl = 'www.bar.foo'; fetcher - .fetchVAST(url, {}, 4, vastParser.emit.bind(vastParser)) + .fetchVAST({ + url: url, + maxWrapperDepth: 4, + emitter: mockEmit, + }) .then(() => { - expect(vastParser.emit).toHaveBeenNthCalledWith( - 1, - 'VAST-resolving', - { - url: expectedUrl, - previousUrl: null, - wrapperDepth: 0, - maxWrapperDepth: 4, - timeout: 120000, - wrapperAd: null, - } - ); - - expect(vastParser.emit).toHaveBeenNthCalledWith( - 2, - 'VAST-resolved', - { - url: expectedUrl, - byteLength: 1234, - duration: expect.any(Number), - error: null, - statusCode: 200, - previousUrl: null, - wrapperDepth: 0, - } - ); + expect(mockEmit).toHaveBeenNthCalledWith(1, 'VAST-resolving', { + url: expectedUrl, + previousUrl: null, + wrapperDepth: 0, + maxWrapperDepth: 4, + timeout: 120000, + wrapperAd: null, + }); + + expect(mockEmit).toHaveBeenNthCalledWith(2, 'VAST-resolved', { + url: expectedUrl, + byteLength: 1234, + duration: expect.any(Number), + error: null, + statusCode: 200, + previousUrl: null, + wrapperDepth: 0, + }); }); }); it('should updates the estimated bitrate', () => { jest.spyOn(Bitrate, 'updateEstimatedBitrate'); - return fetcher - .fetchVAST(url, {}, 5, () => {}) - .finally(() => { + fetcher + .fetchVAST({ url: url, maxWrapperDepth: 5, emitter: () => {} }) + .then(() => { expect(Bitrate.updateEstimatedBitrate).toHaveBeenCalledWith( 1234, expect.any(Number) @@ -100,8 +97,12 @@ describe('Fetcher', () => { }); it('should resolves with xml', () => { - let result = fetcher.fetchVAST(url, {}, 5, () => {}); - expect(result).resolves.toEqual(xml); + let result = fetcher.fetchVAST({ + url: url, + maxWrapperDepth: 5, + emitter: () => {}, + }); + return expect(result).resolves.toEqual(xml); }); }); @@ -119,7 +120,7 @@ describe('Fetcher', () => { const expectedUrl = 'www.bar.foo'; fetcher - .fetchVAST(url, {}, 5, () => {}) + .fetchVAST({ url: url, maxWrapperDepth: 5, emitter: () => {} }) .catch(() => { expect(urlHandlerSpy).toHaveBeenCalledWith( expectedUrl, @@ -134,52 +135,52 @@ describe('Fetcher', () => { const expectedUrl = 'www.bar.foo'; fetcher - .fetchVAST(url, {}, 4, vastParser.emit.bind(vastParser)) + .fetchVAST({ + url: url, + maxWrapperDepth: 4, + emitter: mockEmit, + }) .catch(() => { - expect(vastParser.emit).toHaveBeenNthCalledWith( - 1, - 'VAST-resolving', - { - url: expectedUrl, - previousUrl: null, - wrapperDepth: 0, - maxWrapperDepth: 4, - timeout: 120000, - wrapperAd: null, - } - ); - - expect(vastParser.emit).toHaveBeenNthCalledWith( - 2, - 'VAST-resolved', - { - url: expectedUrl, - duration: expect.any(Number), - error: new Error('timeout'), - statusCode: 408, - previousUrl: null, - wrapperDepth: 0, - } - ); + expect(mockEmit).toHaveBeenNthCalledWith(1, 'VAST-resolving', { + url: expectedUrl, + previousUrl: null, + wrapperDepth: 0, + maxWrapperDepth: 4, + timeout: 120000, + wrapperAd: null, + }); + + expect(mockEmit).toHaveBeenNthCalledWith(2, 'VAST-resolved', { + url: expectedUrl, + duration: expect.any(Number), + error: new Error('timeout'), + statusCode: 408, + previousUrl: null, + wrapperDepth: 0, + }); }); }); it('should rejects with error', () => { - let result = fetcher.fetchVAST(url, {}, 5, () => {}); - expect(result).rejects.toEqual(new Error('timeout')); + let result = fetcher.fetchVAST({ + url: url, + maxWrapperDepth: 5, + emitter: () => {}, + }); + return expect(result).rejects.toEqual(new Error('timeout')); }); }); }); describe('setOptions', () => { - it('should assign option to properties', () => { - const urlhandler = jest.fn(); + it('should assign options to properties', () => { + const customUrlHandler = jest.fn(); fetcher.setOptions({ - urlHandler: urlhandler, + urlHandler: customUrlHandler, timeout: 50000, withCredentials: true, }); - expect(fetcher.urlHandler).toEqual(urlhandler); + expect(fetcher.urlHandler).toEqual(customUrlHandler); expect(fetcher.fetchingOptions.timeout).toEqual(50000); expect(fetcher.fetchingOptions.withCredentials).toEqual(true); }); diff --git a/spec/util.spec.js b/spec/util.spec.js index b2c53117..f8b51f8d 100644 --- a/spec/util.spec.js +++ b/spec/util.spec.js @@ -25,22 +25,20 @@ describe('util', function () { Date.prototype.toISOString = realDateToISOString; }); - describe('ERRORCODE', () => { - it('should resolve ERRORCODE with 900 if ERRORCODE is invalid ', () => { - expect( - resolve('http://example.com/[ERRORCODE]', { ERRORCODE: 10001 }) - ).toBe('http://example.com/900'); - }); + it('should replace ERRORCODE with 900 if ERRORCODE is invalid ', () => { + expect( + resolve('http://example.com/[ERRORCODE]', { ERRORCODE: 10001 }) + ).toBe('http://example.com/900'); + }); - it('should have called error urls with custom code when enabled', () => { - expect( - resolve( - 'http://example.com/[ERRORCODE]', - { ERRORCODE: 10001 }, - { isCustomCode: true } - ) - ).toBe('http://example.com/10001'); - }); + it('should have called error urls with custom code when enabled', () => { + expect( + resolve( + 'http://example.com/[ERRORCODE]', + { ERRORCODE: 10001 }, + { isCustomCode: true } + ) + ).toBe('http://example.com/10001'); }); describe('assetURI', function () { diff --git a/spec/vast_client.spec.js b/spec/vast_client.spec.js index 3a770f67..bfab2d61 100644 --- a/spec/vast_client.spec.js +++ b/spec/vast_client.spec.js @@ -1,8 +1,6 @@ import { VASTClient } from '../src/vast_client'; import { nodeURLHandler } from '../src/urlhandlers/node_url_handler'; import { urlFor } from './utils/utils'; -import 'regenerator-runtime/runtime'; -import { Fetcher } from '../src/fetcher'; const wrapperMultipleAdsVastUrl = urlFor('wrapper-multiple-ads.xml'); const emptyVastUrl = urlFor('empty-no-ad.xml'); @@ -29,7 +27,7 @@ describe('VASTClient', () => { expect(res).toEqual({ ads: expect.any(Array), errorURLTemplates: [], - version: '3.0', + version: '4.3', }); expect(VastClient.totalCalls).toBe('2'); }); @@ -44,7 +42,7 @@ describe('VASTClient', () => { expect(res).toEqual({ ads: expect.any(Array), errorURLTemplates: [], - version: '3.0', + version: '4.3', }); expect(VastClient.totalCalls).toBe('3'); }); @@ -116,7 +114,7 @@ describe('VASTClient', () => { expect(res).toEqual({ ads: expect.any(Array), errorURLTemplates: [], - version: '3.0', + version: '4.3', }); expect(res.ads).toHaveLength(2); }); @@ -134,7 +132,7 @@ describe('VASTClient', () => { expect(result).toEqual({ ads: [], errorURLTemplates: ['http://example.com/empty-no-ad'], - version: '2.0', + version: '4.3', }); } ); @@ -150,27 +148,26 @@ describe('VASTClient', () => { expect(res).toEqual({ ads: expect.any(Array), errorURLTemplates: [], - version: '3.0', + version: '4.3', }); expect(res.ads).toHaveLength(3); }); - it('resolves only next ad if requested', (done) => { - VastClient.get( + + it('resolves only next ad if requested', async () => { + let a = await VastClient.get( wrapperMultipleAdsVastUrl, optionsWithNoResolveAll - ).then(() => { - VastClient.getNextAds(false) - .then((res) => { - expect(res).toEqual({ - ads: expect.any(Array), - errorURLTemplates: [], - version: '3.0', - }); - expect(res.ads).toHaveLength(2); - }) - .catch((err) => console.log(err)); - done(); + ); + + let res = await VastClient.getNextAds(false); + console.log('ress', a); + + expect(res).toEqual({ + ads: expect.any(Array), + errorURLTemplates: [], + version: '4.3', }); + expect(res.ads).toHaveLength(2); }); }); }); @@ -190,7 +187,7 @@ describe('VASTClient', () => { expect(res).toEqual({ ads: expect.any(Array), errorURLTemplates: [], - version: '3.0', + version: '4.3', }); expect(res.ads).toHaveLength(5); }); diff --git a/spec/vast_parser.spec.js b/spec/vast_parser.spec.js index f07134da..8e2ef2a3 100644 --- a/spec/vast_parser.spec.js +++ b/spec/vast_parser.spec.js @@ -1,6 +1,6 @@ import { VASTParser } from '../src/parser/vast_parser'; import { nodeURLHandler } from '../src/urlhandlers/node_url_handler'; -import { urlFor, fetchXml } from './utils/utils'; +import { urlFor, fetchXml, getNodesFromXml } from './utils/utils'; import { util } from '../src/util/util'; import { parserUtils } from '../src/parser/parser_utils'; import * as Bitrate from '../src/parser/bitrate'; @@ -8,10 +8,7 @@ import { Fetcher } from '../src/fetcher'; import { linearAd } from './samples/linear_ads'; import { VASTClient } from '../src/vast_client'; -const xml = new DOMParser().parseFromString( - `${linearAd}`, - 'text/xml' -); +const xml = getNodesFromXml(`${linearAd}`, 'text/xml'); const urlHandlerSuccess = { get: (url, options, cb) => { cb(null, xml, { byteLength: 1234, statusCode: 200 }); @@ -148,12 +145,12 @@ describe('VASTParser', () => { url: inlineSampleVastUrl, wrapperDepth: 0, }); - expect(VastParser.vastVersion).toBe('2.1'); + expect(VastParser.vastVersion).toBe('4.3'); }); it('handles Error tag for root VAST', () => { //initParsingStatus always will be called before parseVastXml - VastParser.initParsingStatus(); + VastParser.rootErrorURLTemplates = []; VastParser.parseVastXml(errorXml, { isRootVAST: true }); expect(VastParser.rootErrorURLTemplates).toEqual([ 'http://example.com/empty-no-ad', @@ -186,7 +183,7 @@ describe('VASTParser', () => { type: 'INLINE', url: inlineSampleVastUrl, wrapperDepth: 0, - vastVersion: '2.1', + vastVersion: '4.3', }, ], [ @@ -196,7 +193,7 @@ describe('VASTParser', () => { type: 'INLINE', url: inlineSampleVastUrl, wrapperDepth: 0, - vastVersion: '2.1', + vastVersion: '4.3', }, ], ]); @@ -299,7 +296,7 @@ describe('VASTParser', () => { jest.spyOn(VastParser, 'initParsingStatus'); jest .spyOn(VastParser, 'parse') - .mockImplementation(() => Promise.resolve([linearAd])); + .mockReturnValue(Promise.resolve([linearAd])); jest.spyOn(VastParser, 'buildVASTResponse').mockReturnValue({ ads: [linearAd], errorURLTemplates: [], @@ -314,8 +311,7 @@ describe('VASTParser', () => { it('should return a VAST response object', (done) => { VastParser.parseVAST(xml, options).then((response) => { - res = response; - expect(res).toEqual({ + expect(response).toEqual({ ads: [linearAd], errorURLTemplates: [], version: null, @@ -345,10 +341,6 @@ describe('VASTParser', () => { expect(VastParser.parsingOptions).toEqual({ allowMultipleAds: undefined, }); - expect(Bitrate.updateEstimatedBitrate).toHaveBeenCalledWith( - undefined, - undefined - ); }); done(); }); @@ -394,7 +386,7 @@ describe('VASTParser', () => { } } } - VastParser.fetchingMethod = fetcher.fetchVAST.bind(fetcher); + VastParser.fetchingCallback = fetcher.fetchVAST.bind(fetcher); VastParser.parseVAST(xml, options).then((response) => { resolve(response); }); @@ -629,7 +621,7 @@ describe('VASTParser', () => { it('updates previousUrl value and calls resolveWrappers for each ad', () => { jest .spyOn(VastParser, 'resolveWrappers') - .mockImplementation(() => Promise.resolve(['ad1', 'ad2'])); + .mockReturnValue(Promise.resolve(['ad1', 'ad2'])); return VastParser.resolveAds(['ad1', 'ad2'], { wrapperDepth: 1, previousUrl: wrapperBVastUrl, @@ -678,7 +670,7 @@ describe('VASTParser', () => { it('will add errorcode to resolved ad if parsing has reached maximum amount of unwrapping', () => { const adWithWrapper = { ...ad, nextWrapperURL: 'http://example.com/foo' }; - VastParser.fetchingMethod = () => {}; + VastParser.fetchingCallback = () => {}; VastParser.maxWrapperDepth = 10; return VastParser.resolveWrappers(adWithWrapper, 10).then((res) => { expect(res).toEqual({ @@ -691,10 +683,10 @@ describe('VASTParser', () => { it('will successfully fetch the next wrapper url if it is provided', () => { const adWithWrapper = { ...ad, nextWrapperURL: wrapperBVastUrl }; - jest.spyOn(fetcher, 'fetchVAST').mockImplementation(() => { - return Promise.resolve(expect.any(Object)); - }); - VastParser.fetchingMethod = fetcher.fetchVAST; + jest + .spyOn(fetcher, 'fetchVAST') + .mockReturnValue(Promise.resolve(expect.any(Object))); + VastParser.fetchingCallback = fetcher.fetchVAST; jest .spyOn(VastParser, 'parse') @@ -704,12 +696,11 @@ describe('VASTParser', () => { return VastParser.resolveWrappers(adWithWrapper, 0, wrapperAVastUrl).then( (res) => { - expect(fetcher.fetchVAST).toHaveBeenCalledWith( - wrapperBVastUrl, - { previousUrl: null, wrapperAd: null, wrapperDepth: 0 }, - VastParser.maxWrapperDepth, - expect.any(Function) - ); + expect(fetcher.fetchVAST).toHaveBeenCalledWith({ + url: wrapperBVastUrl, + maxWrapperDepth: VastParser.maxWrapperDepth, + emitter: expect.any(Function), + }); expect(VastParser.parse).toHaveBeenCalledWith(expect.any(Object), { url: wrapperBVastUrl, previousUrl: wrapperAVastUrl, @@ -728,19 +719,18 @@ describe('VASTParser', () => { jest.spyOn(fetcher, 'fetchVAST').mockImplementation(() => { return Promise.reject(new Error('timeout')); }); - VastParser.fetchingMethod = fetcher.fetchVAST; + VastParser.fetchingCallback = fetcher.fetchVAST; jest.spyOn(VastParser, 'parse'); jest.spyOn(parserUtils, 'mergeWrapperAdData'); VastParser.maxWrapperDepth = 10; return VastParser.resolveWrappers(adWithWrapper, 0, wrapperAVastUrl).then( (res) => { - expect(fetcher.fetchVAST).toHaveBeenCalledWith( - wrapperBVastUrl, - { previousUrl: null, wrapperAd: null, wrapperDepth: 0 }, - VastParser.maxWrapperDepth, - expect.any(Function) - ); + expect(fetcher.fetchVAST).toHaveBeenCalledWith({ + url: wrapperBVastUrl, + maxWrapperDepth: VastParser.maxWrapperDepth, + emitter: expect.any(Function), + }); expect(VastParser.parse).not.toHaveBeenCalled(); expect(parserUtils.mergeWrapperAdData).not.toBeCalled(); expect(res).toEqual( @@ -757,7 +747,7 @@ describe('VASTParser', () => { jest .spyOn(fetcher, 'fetchVAST') .mockReturnValue(Promise.resolve('')); - VastParser.fetchingMethod = fetcher.fetchVAST; + VastParser.fetchingCallback = fetcher.fetchVAST; jest.spyOn(VastParser, 'parse').mockReturnValue(Promise.resolve()); const adWithWrapper = { @@ -781,7 +771,7 @@ describe('VASTParser', () => { jest .spyOn(fetcher, 'fetchVAST') .mockReturnValue(Promise.resolve('')); - VastParser.fetchingMethod = fetcher.fetchVAST; + VastParser.fetchingCallback = fetcher.fetchVAST; jest.spyOn(VastParser, 'parse').mockReturnValue(Promise.resolve()); const expectedValue = { allowMultipleAds: true }; diff --git a/spec/vast_trackers.spec.js b/spec/vast_trackers.spec.js index 91087bdb..31d20afa 100644 --- a/spec/vast_trackers.spec.js +++ b/spec/vast_trackers.spec.js @@ -734,7 +734,7 @@ describe('VASTTracker', function () { spyTrack = jest.spyOn(vastTracker, 'trackURLs'); vastTracker.click(); }); - it('should have sent clickthrough event with clickThrough url', () => { + it('should have emit clickthrough event with clickThrough url', () => { expect(spyEmit).toHaveBeenCalledWith( 'clickthrough', 'https://iabtechlab.com' @@ -755,15 +755,13 @@ describe('VASTTracker', function () { const fallbackClickThroughURL = 'http://example.com/fallback-clickthrough', clickThroughURL = 'http://example.com/clickthrough'; - describe('#VAST clichthrough with no fallback provided', () => { - beforeEach(() => { + describe('#VAST clickthrough with no fallback provided', () => { + it('should have sent clickthrough event with VAST clickthrough url', () => { const creative = createCreativeLinear(); creative.videoClickThroughURLTemplate = clickThroughURL; vastTracker = new VASTTracker(vastClient, {}, creative); spyEmit = jest.spyOn(vastTracker, 'emit'); vastTracker.click(); - }); - it('should have sent clickthrough event with VAST clickthrough url', () => { expect(spyEmit).toHaveBeenCalledWith( 'clickthrough', 'http://example.com/clickthrough' From 33e0e7951523b0eae72b480f1eb879bc9ceadf60 Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Tue, 12 Sep 2023 12:37:16 +0200 Subject: [PATCH 26/95] [client] updating get method --- src/vast_client.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/vast_client.js b/src/vast_client.js index 9990bac6..2a1ef914 100644 --- a/src/vast_client.js +++ b/src/vast_client.js @@ -22,7 +22,9 @@ export class VASTClient { this.vastParser = new VASTParser(); this.storage = customStorage || new Storage(); this.fetcher = new Fetcher(); - this.vastParser.fetchingMethod = this.fetcher.fetchVAST.bind(this.fetcher); + this.vastParser.fetchingCallback = this.fetcher.fetchVAST.bind( + this.fetcher + ); // Init values if not already set if (this.lastSuccessfulAd === undefined) { this.lastSuccessfulAd = 0; @@ -117,8 +119,8 @@ export class VASTClient { /** * Parses the given xml Object into a VASTResponse. * Returns a Promise which resolves with a fully parsed VASTResponse or rejects with an Error. - * @param {*} xml - An object representing a vast xml document. - * @param {*} options - An optional Object of parameters to be used in the parsing and fetching process. + * @param {Object} xml - An object representing a vast xml document. + * @param {Object} options - An optional Object of parameters to be used in the parsing and fetching process. * @returns {Promise} */ parseVAST(xml, options = {}) { @@ -134,7 +136,6 @@ export class VASTClient { */ get(url, options = {}) { const now = Date.now(); - // options = Object.assign({}, this.defaultOptions, options); // By default the client resolves only the first Ad or AdPod if (!options.hasOwnProperty('resolveAll')) { @@ -179,12 +180,11 @@ export class VASTClient { this.vastParser.rootURL = url; this.fetcher - .fetchVAST( + .fetchVAST({ url, - { wrapperDepth: 0, previousUrl: null, wrapperAd: null }, - this.vastParser.maxWrapperDepth, - this.vastParser.emit.bind(this.vastParser) - ) + emitter: this.vastParser.emit.bind(this.vastParser), + maxWrapperDepth: this.vastParser.maxWrapperDepth, + }) .then((xml) => { options.previousUrl = url; options.isRootVAST = true; From 1dbcc80998ca9237d477f21586978aab08dd139d Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Tue, 12 Sep 2023 12:38:41 +0200 Subject: [PATCH 27/95] [parser] updating fetchingCallback --- src/parser/vast_parser.js | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/parser/vast_parser.js b/src/parser/vast_parser.js index a63ad500..3ce74c9e 100644 --- a/src/parser/vast_parser.js +++ b/src/parser/vast_parser.js @@ -25,7 +25,7 @@ export class VASTParser extends EventEmitter { constructor() { super(); this.maxWrapperDepth = null; - this.fetchingMethod = null; + this.fetchingCallback = null; this.remainingAds = []; } @@ -367,10 +367,7 @@ export class VASTParser extends EventEmitter { return resolve(ad); } - // If you need to fectch a VAST document, or follow additional wrapper, - // you need to use the get method from VASTClient, this method will use the fetchVAST method from the - // Fetcher class to fetch your document and set the fetchingMethod in case you want to fetch additional wrapper - if (!this.fetchingMethod) { + if (!this.fetchingCallback) { ad.VASTAdTagURI = ad.nextWrapperURL; delete ad.nextWrapperURL; return resolve(ad); @@ -397,12 +394,11 @@ export class VASTParser extends EventEmitter { // sequence doesn't carry over in wrapper element const wrapperSequence = ad.sequence; - this.fetchingMethod( - ad.nextWrapperURL, - { wrapperDepth: 0, previousUrl: null, wrapperAd: null }, - this.maxWrapperDepth, - this.emit.bind(this) - ) + this.fetchingCallback({ + url: ad.nextWrapperURL, + emitter: this.emit.bind(this), + maxWrapperDepth: this.maxWrapperDepth, + }) .then((xml) => { return this.parse(xml, { url: ad.nextWrapperURL, From 0ca5915e78b30b7e3e4ce762179919b67fa66c73 Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Tue, 12 Sep 2023 12:40:30 +0200 Subject: [PATCH 28/95] [tools] adding new command including --watch --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 329e04a1..33bc5974 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,8 @@ "scripts": { "performance": "node performance/performance_test.js", "prebuild": "rm -rf dist_old && mkdir dist_old && cp -a dist/. dist_old/", - "build": "rollup -c --watch", + "build": "rollup -c", + "watch": "rollup -c --watch", "postbuild": "node bundle_size.js", "lint": "eslint .", "precommit": "eslint . --max-warnings 0 && pretty-quick --staged", From a129967dc9611bc69f4f607315e5623236a58683 Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Tue, 12 Sep 2023 12:42:42 +0200 Subject: [PATCH 29/95] [tools] replacing functions by arrow functions --- rollup.config.js | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/rollup.config.js b/rollup.config.js index 9a8df337..6cca005d 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -15,9 +15,7 @@ function onwarn(warning) { } } -// Node configuration - -function createNodeConfig(filePath, minifiedOutput, notMinifiedOutput) { +const createNodeConfig = (filePath, minifiedOutput, notMinifiedOutput) => { let config = { input: filePath, output: [ @@ -49,11 +47,9 @@ function createNodeConfig(filePath, minifiedOutput, notMinifiedOutput) { }; return config; -} +}; -// Browser configuration - -function createBrowserConfig(filePath, minifiedOutput, notMinifiedOutput) { +const createBrowserConfig = (filePath, minifiedOutput, notMinifiedOutput) => { let config = { input: filePath, output: [ @@ -71,7 +67,7 @@ function createBrowserConfig(filePath, minifiedOutput, notMinifiedOutput) { }; return config; -} +}; export default [ // Browser-friendly build [package.json "browser"] From f49be0cc1afa775fbbf634163987139af7257c53 Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Tue, 12 Sep 2023 12:43:27 +0200 Subject: [PATCH 30/95] [docs] updating documentations --- README.md | 7 +- docs/api/vast-client.md | 137 ++++++++++++++++++++-------------------- docs/api/vast-parser.md | 2 + 3 files changed, 75 insertions(+), 71 deletions(-) diff --git a/README.md b/README.md index d15aebdf..477d9177 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ vastClient.get('https://www.examplevast.com/vast.xml') In addition to fetching and parsing a VAST resource, **VASTClient** provides options to filter a sequence of calls based on count and time of execution, together with the possibility to track URLs using **VASTTracker**. -If you need to directy parse a VAST XML and follow a wrappers chain, you can use the **VASTClient** just like so : +If you need to directly parse a VAST XML and also follow any wrappers chain, you can use the `parseVAST` method from the **VASTClient** : ```javascript import { VASTClient } from '@dailymotion/vast-client' @@ -65,7 +65,7 @@ vastClient.parseVAST(vastXml) ### VASTParser To directly parse a VAST XML you can use the **VASTParser**: -The **VASTParser** will not proceed to any fetching, the final response will only contain the first VAST encountered. +The **VASTParser** will make no fetching, the final response will only contain the first VAST encountered. ```Javascript import { VASTParser } from '@dailymotion/vast-client' @@ -128,7 +128,8 @@ const vastTracker = new VASTTracker(); A pre-bundled version of VAST Client JS is available: [`vast-client.min.js`](dist/vast-client.min.js) [minified]. -If you want to use this file, add it to your project and make sure that your main file is in type module as below. +If you want to use this file, download it place it your project at your prefered location. +Then, make sure that your main file is in type module as below. ```html diff --git a/docs/api/vast-client.md b/docs/api/vast-client.md index b0f54074..3b2f6398 100644 --- a/docs/api/vast-client.md +++ b/docs/api/vast-client.md @@ -101,74 +101,6 @@ Instance of a class which implements the `Storage` interface. Should be set up o ## Public Methods 💚 -### addURLTemplateFilter(filter) - -Adds a filter function to the array of filters which are called before fetching a VAST document. - -#### Parameters - -- **`filter: function`** - The filter function to be added at the end of the array - -#### Example - -```Javascript -vastClient.addURLTemplateFilter( vastUrl => { - return url.replace('[DOMAIN]', 'mywebsite.com') -}); - -/* -For a VASTAdTagURI defined as : -http://example.dailymotion.com/vast.php?domain=[DOMAIN] -HTTP request will be: -http://example.dailymotion.com/vast.php?domain=mywebsite.com -*/ -``` - -### removeURLTemplateFilter() - -Removes the last element of the url templates filters array. - -#### Example - -```Javascript -const replaceDomain = () => { - return url.replace('[DOMAIN]', 'mywebsite.com') -}; - -vastClient.addURLTemplateFilter(replaceDomain); -// ... -vastClient.removeURLTemplateFilter(replaceDomain); -// [DOMAIN] placeholder is no longer replaced -``` -### countURLTemplateFilters() - -Returns the number of filters of the url templates filters array. - -#### Example - -```Javascript -vastClient.addURLTemplateFilter( vastUrl => { - return url.replace('[DOMAIN]', 'mywebsite.com') -}); - -vastClient.countUrlTemplateFilters(); -// returns 1 -``` - -### clearURLTemplateFilters() - -Removes all the filter functions from the url templates filters array. - -#### Example - -```Javascript -vastClient.addURLTemplateFilter( vastUrl => { - return url.replace('[DOMAIN]', 'mywebsite.com') -}); - -vastClient.clearUrlTemplateFilters(); -// [DOMAIN] placeholder is no longer replaced -``` ### get(url, options): Promise Gets a parsed VAST document for the given url, applying the skipping rules defined (`cappingFreeLunch` and `cappingMinimumTimeInterval`). @@ -430,3 +362,72 @@ const vastParser = vastClient.getParser(); // Clear the url template filters used vastParser.clearUrlTemplateFilters(); ``` + +### addURLTemplateFilter(filter) + +Adds a filter function to the array of filters which are called before fetching a VAST document. + +#### Parameters + +- **`filter: function`** - The filter function to be added at the end of the array + +#### Example + +```Javascript +vastClient.addURLTemplateFilter( vastUrl => { + return url.replace('[DOMAIN]', 'mywebsite.com') +}); + +/* +For a VASTAdTagURI defined as : +http://example.dailymotion.com/vast.php?domain=[DOMAIN] +HTTP request will be: +http://example.dailymotion.com/vast.php?domain=mywebsite.com +*/ +``` + +### removeURLTemplateFilter() + +Removes the last element of the url templates filters array. + +#### Example + +```Javascript +const replaceDomain = () => { + return url.replace('[DOMAIN]', 'mywebsite.com') +}; + +vastClient.addURLTemplateFilter(replaceDomain); +// ... +vastClient.removeURLTemplateFilter(replaceDomain); +// [DOMAIN] placeholder is no longer replaced +``` +### countURLTemplateFilters() + +Returns the number of filters of the url templates filters array. + +#### Example + +```Javascript +vastClient.addURLTemplateFilter( vastUrl => { + return url.replace('[DOMAIN]', 'mywebsite.com') +}); + +vastClient.countUrlTemplateFilters(); +// returns 1 +``` + +### clearURLTemplateFilters() + +Removes all the filter functions from the url templates filters array. + +#### Example + +```Javascript +vastClient.addURLTemplateFilter( vastUrl => { + return url.replace('[DOMAIN]', 'mywebsite.com') +}); + +vastClient.clearUrlTemplateFilters(); +// [DOMAIN] placeholder is no longer replaced +``` diff --git a/docs/api/vast-parser.md b/docs/api/vast-parser.md index ce476c3a..b25c96b1 100644 --- a/docs/api/vast-parser.md +++ b/docs/api/vast-parser.md @@ -166,6 +166,8 @@ If you need to parse and follow a wrappers chain, you should use the **parseVAST - **`options: Object`** - An optional Object of parameters to be used in the parsing process - `allowMultipleAds: Boolean` - A boolean value that identifies whether multiple ads are allowed in the requested VAST response. This will override any value of allowMultipleAds attribute set in the VAST - `byteLength: Number`- The size of the request in bytes. Provide it with requestDuration to have a more accurate estimated bitrate. + - `resolveAll: Boolean` - Allows you to parse all the ads contained in the VAST or to parse them ad by ad or adPod by adPod (default `true`) + #### Events emitted From 8a431205b959654fc9985319c0afe9a5bcd64316 Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Tue, 12 Sep 2023 12:44:46 +0200 Subject: [PATCH 31/95] updating fetchVAST method --- src/fetcher.js | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/fetcher.js b/src/fetcher.js index 86bf4d42..79b82e37 100644 --- a/src/fetcher.js +++ b/src/fetcher.js @@ -13,6 +13,10 @@ export class Fetcher { this.URLTemplateFilters = []; } + /** + * Inits the fetching properties of the class with the custom values provided as options + * @param {Object} options - The options to initialize before fetching + */ setOptions(options = {}) { this.urlHandler = options.urlHandler || options.urlhandler || urlHandler; this.fetchingOptions = { @@ -69,13 +73,14 @@ export class Fetcher { * @emits VASTParser#VAST-resolved * @return {Promise} */ - - fetchVAST( + fetchVAST({ url, - { wrapperDepth = 0, previousUrl = null, wrapperAd = null } = {}, maxWrapperDepth, - emitter - ) { + emitter, + wrapperDepth = 0, + previousUrl = null, + wrapperAd = null, + }) { return new Promise((resolve, reject) => { const timeBeforeGet = Date.now(); @@ -88,7 +93,7 @@ export class Fetcher { url, previousUrl, wrapperDepth, - maxWrapperDepth: maxWrapperDepth, + maxWrapperDepth, timeout: this.fetchingOptions.timeout, wrapperAd, }); From a54fc7678c80e6d1e29d8894fbf4daa0a7f4af28 Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Tue, 12 Sep 2023 12:45:26 +0200 Subject: [PATCH 32/95] [test] removing console.log --- spec/vast_client.spec.js | 1 - 1 file changed, 1 deletion(-) diff --git a/spec/vast_client.spec.js b/spec/vast_client.spec.js index bfab2d61..e6eec158 100644 --- a/spec/vast_client.spec.js +++ b/spec/vast_client.spec.js @@ -160,7 +160,6 @@ describe('VASTClient', () => { ); let res = await VastClient.getNextAds(false); - console.log('ress', a); expect(res).toEqual({ ads: expect.any(Array), From 7a1b823d6365676dd9222f4154db9ad90fdd653b Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Tue, 12 Sep 2023 12:53:46 +0200 Subject: [PATCH 33/95] [tests] removing unnecessary files related to mocha --- test/mocha.opts | 2 - test/vastfiles/empty-no-ad.xml | 5 - test/vastfiles/empty-no-creative.xml | 19 -- test/vastfiles/inline-linear.xml | 53 ----- test/vastfiles/sample-wrapped.xml | 32 --- test/vastfiles/sample.xml | 202 ------------------- test/vastfiles/vpaid.xml | 33 --- test/vastfiles/wrapper-a.xml | 90 --------- test/vastfiles/wrapper-ad-pod.xml | 46 ----- test/vastfiles/wrapper-b.xml | 69 ------- test/vastfiles/wrapper-empty-no-creative.xml | 29 --- test/vastfiles/wrapper-empty.xml | 38 ---- test/vastfiles/wrapper-legacy.xml | 34 ---- test/vastfiles/wrapper-multiple-ads.xml | 90 --------- test/vastfiles/wrapper-notracking.xml | 11 - test/vastfiles/wrapper-sequence.xml | 47 ----- test/vastfiles/wrapper-unavailable-url.xml | 38 ---- 17 files changed, 838 deletions(-) delete mode 100644 test/mocha.opts delete mode 100644 test/vastfiles/empty-no-ad.xml delete mode 100644 test/vastfiles/empty-no-creative.xml delete mode 100644 test/vastfiles/inline-linear.xml delete mode 100644 test/vastfiles/sample-wrapped.xml delete mode 100644 test/vastfiles/sample.xml delete mode 100644 test/vastfiles/vpaid.xml delete mode 100644 test/vastfiles/wrapper-a.xml delete mode 100644 test/vastfiles/wrapper-ad-pod.xml delete mode 100644 test/vastfiles/wrapper-b.xml delete mode 100644 test/vastfiles/wrapper-empty-no-creative.xml delete mode 100644 test/vastfiles/wrapper-empty.xml delete mode 100644 test/vastfiles/wrapper-legacy.xml delete mode 100644 test/vastfiles/wrapper-multiple-ads.xml delete mode 100644 test/vastfiles/wrapper-notracking.xml delete mode 100644 test/vastfiles/wrapper-sequence.xml delete mode 100644 test/vastfiles/wrapper-unavailable-url.xml diff --git a/test/mocha.opts b/test/mocha.opts deleted file mode 100644 index cfbf2d8e..00000000 --- a/test/mocha.opts +++ /dev/null @@ -1,2 +0,0 @@ ---require @babel/register ---reporter spec test/*.js diff --git a/test/vastfiles/empty-no-ad.xml b/test/vastfiles/empty-no-ad.xml deleted file mode 100644 index f5c80e5a..00000000 --- a/test/vastfiles/empty-no-ad.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/test/vastfiles/empty-no-creative.xml b/test/vastfiles/empty-no-creative.xml deleted file mode 100644 index 85a9a96e..00000000 --- a/test/vastfiles/empty-no-creative.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - No Ad Server - NO AD - - - - - - - - - - - - - diff --git a/test/vastfiles/inline-linear.xml b/test/vastfiles/inline-linear.xml deleted file mode 100644 index f599a1fe..00000000 --- a/test/vastfiles/inline-linear.xml +++ /dev/null @@ -1,53 +0,0 @@ - - - - iabtechlab - iabtechlab video ad - - - - - - http://example.com/error - - http://example.com/track/impression - - - - - 00:00:16 - - http://example.com/tracking/start - http://example.com/tracking/firstQuartile - http://example.com/tracking/midpoint - http://example.com/tracking/thirdQuartile - http://example.com/tracking/complete - - - - - - - http://iabtechlab.com - - - - - - - - - - - - - - - - - - - - - - diff --git a/test/vastfiles/sample-wrapped.xml b/test/vastfiles/sample-wrapped.xml deleted file mode 100644 index bb3cd576..00000000 --- a/test/vastfiles/sample-wrapped.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - AdServer - - - - - - - - 00:00:22 - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/test/vastfiles/sample.xml b/test/vastfiles/sample.xml deleted file mode 100644 index 1742c531..00000000 --- a/test/vastfiles/sample.xml +++ /dev/null @@ -1,202 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Linear-12345 - Linear-5678 - - - - - - - - - - - { key: value } - - - - 00:01:30.123 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - http://www.example.com/companion2-clickthrough - - - - - Some call to action HTML! - ]]> - - - http://www.example.com/companion3-clickthrough - - - - - - NonLinear-12345 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - { foo: bar } - - - - - - - - - - - - Linear-id873421 - - 30 - - - - - - - - - - - - diff --git a/test/vastfiles/vpaid.xml b/test/vastfiles/vpaid.xml deleted file mode 100644 index fde56f7f..00000000 --- a/test/vastfiles/vpaid.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - LiveRail - VPAID Ad Manager - - - - - - - - - - - - - - ]]> - - - ]]> - - - ]]> - - - - - - - diff --git a/test/vastfiles/wrapper-a.xml b/test/vastfiles/wrapper-a.xml deleted file mode 100644 index b9563396..00000000 --- a/test/vastfiles/wrapper-a.xml +++ /dev/null @@ -1,90 +0,0 @@ - - - - - VAST - - - - - - - - - - - - - - - - - - - - - - - - - - - - http://example.com/wrapperA-companion2-click-thru - - - - - - - - - - - - http://www.example.com/wrapperA-nonlinear-click-thru - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/test/vastfiles/wrapper-ad-pod.xml b/test/vastfiles/wrapper-ad-pod.xml deleted file mode 100644 index a14ef6bb..00000000 --- a/test/vastfiles/wrapper-ad-pod.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - iabtechlab - - http://example.com/error - http://example.com/track/impression - - - - - - - - - - - - - - - - - - - iabtechlab - - http://example.com/error - http://example.com/track/impression - - - - - - - - - - - - - - - - - diff --git a/test/vastfiles/wrapper-b.xml b/test/vastfiles/wrapper-b.xml deleted file mode 100644 index 15be8b34..00000000 --- a/test/vastfiles/wrapper-b.xml +++ /dev/null @@ -1,69 +0,0 @@ - - - - - VAST - - - - - - - - - - - - - - - - - - - - - - - - - - http://example.com/wrapperB-companion2-click-thru - - - - - - - - - - - - http://www.example.com/wrapperB-nonlinear-click-thru - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/test/vastfiles/wrapper-empty-no-creative.xml b/test/vastfiles/wrapper-empty-no-creative.xml deleted file mode 100644 index 5dfd2a1b..00000000 --- a/test/vastfiles/wrapper-empty-no-creative.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - VAST - empty-no-creative.xml - http://example.com/wrapper-no-creative_wrapper-error - http://example.com/wrapper-impression - - - - - - - - - - - - - - - - - - - - - diff --git a/test/vastfiles/wrapper-empty.xml b/test/vastfiles/wrapper-empty.xml deleted file mode 100644 index 94b95992..00000000 --- a/test/vastfiles/wrapper-empty.xml +++ /dev/null @@ -1,38 +0,0 @@ - - - - - VAST - empty-no-ad.xml - http://example.com/wrapper-empty_wrapper-error - http://example.com/wrapper-impression - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/test/vastfiles/wrapper-legacy.xml b/test/vastfiles/wrapper-legacy.xml deleted file mode 100644 index a4772251..00000000 --- a/test/vastfiles/wrapper-legacy.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - VAST - - http://example.com/wrapper-error - http://example.com/wrapper-impression - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/test/vastfiles/wrapper-multiple-ads.xml b/test/vastfiles/wrapper-multiple-ads.xml deleted file mode 100644 index 5a116334..00000000 --- a/test/vastfiles/wrapper-multiple-ads.xml +++ /dev/null @@ -1,90 +0,0 @@ - - - - iabtechlab - - http://example.com/error - http://example.com/track/impression - - - - - - - - - - - - - - - - - - - iabtechlab - - http://example.com/error - http://example.com/track/impression - - - - - - - - - - - - - - - - - - - iabtechlab - - http://example.com/error - http://example.com/track/impression - - - - - - - - - - - - - - - - - - - iabtechlab - - http://example.com/error - http://example.com/track/impression - - - - - - - - - - - - - - - - - diff --git a/test/vastfiles/wrapper-notracking.xml b/test/vastfiles/wrapper-notracking.xml deleted file mode 100644 index a7e0d7cc..00000000 --- a/test/vastfiles/wrapper-notracking.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - VAST - wrapper-a.xml - http://example.com/wrapperNoTracking-error - http://example.com/wrapperNoTracking-impression - - - diff --git a/test/vastfiles/wrapper-sequence.xml b/test/vastfiles/wrapper-sequence.xml deleted file mode 100644 index d83615d0..00000000 --- a/test/vastfiles/wrapper-sequence.xml +++ /dev/null @@ -1,47 +0,0 @@ - - - - - VAST - sample-wrapped.xml - http://example.com/wrapper-error - http://example.com/wrapper-impression - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - unknown - - - - - diff --git a/test/vastfiles/wrapper-unavailable-url.xml b/test/vastfiles/wrapper-unavailable-url.xml deleted file mode 100644 index 6f3674ff..00000000 --- a/test/vastfiles/wrapper-unavailable-url.xml +++ /dev/null @@ -1,38 +0,0 @@ - - - - - VAST - invalid-url - http://example.com/wrapper-invalid-xmlfile_wrapper-error - http://example.com/wrapper-invalid-xmlfile_impression - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From 4c31d606b60c808241850b6324ae97688f120093 Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Wed, 13 Sep 2023 11:58:32 +0200 Subject: [PATCH 34/95] minor code update --- spec/fetcher.spec.js | 1 + spec/vast_parser.spec.js | 44 +++++++++++++++++++--------------------- 2 files changed, 22 insertions(+), 23 deletions(-) diff --git a/spec/fetcher.spec.js b/spec/fetcher.spec.js index 0cf25860..12b175b4 100644 --- a/spec/fetcher.spec.js +++ b/spec/fetcher.spec.js @@ -192,6 +192,7 @@ describe('Fetcher', () => { expect(fetcher.fetchingOptions.withCredentials).toEqual(false); }); }); + describe('URLtemplatefilter modifications', () => { let filter = jest.fn(); beforeEach(() => { diff --git a/spec/vast_parser.spec.js b/spec/vast_parser.spec.js index 8e2ef2a3..d8201544 100644 --- a/spec/vast_parser.spec.js +++ b/spec/vast_parser.spec.js @@ -440,29 +440,27 @@ describe('VASTParser', () => { it('should emits VAST-error & track when wrapped', (done) => { const url = urlFor('wrapper-empty.xml'); - dataFromGivenUrl(urlFor('wrapper-empty.xml'), options).then( - (response) => { - // Response doesn't have any ads - expect(response.ads).toEqual([]); - //error has been triggered - expect(dataTriggered.length).toBe(1); - expect(dataTriggered[0].ERRORCODE).toBe(303); - expect(dataTriggered[0].extensions[0].children[0].name).toBe( - 'paramWrapperEmptyNoAd' - ); - expect(dataTriggered[0].extensions[0].children[0].value).toBe( - 'valueWrapperEmptyNoAd' - ); - // TRacking has been done - expect(trackCalls.length).toBe(1); - expect(trackCalls[0].templates).toEqual([ - 'http://example.com/wrapper-empty_wrapper-error', - 'http://example.com/empty-no-ad', - ]); - expect(trackCalls[0].variables).toEqual({ ERRORCODE: 303 }); - done(); - } - ); + dataFromGivenUrl(url, options).then((response) => { + // Response doesn't have any ads + expect(response.ads).toEqual([]); + //error has been triggered + expect(dataTriggered.length).toBe(1); + expect(dataTriggered[0].ERRORCODE).toBe(303); + expect(dataTriggered[0].extensions[0].children[0].name).toBe( + 'paramWrapperEmptyNoAd' + ); + expect(dataTriggered[0].extensions[0].children[0].value).toBe( + 'valueWrapperEmptyNoAd' + ); + // TRacking has been done + expect(trackCalls.length).toBe(1); + expect(trackCalls[0].templates).toEqual([ + 'http://example.com/wrapper-empty_wrapper-error', + 'http://example.com/empty-no-ad', + ]); + expect(trackCalls[0].variables).toEqual({ ERRORCODE: 303 }); + done(); + }); }); }); From 2b2869f840e5630220875b6a5369928115bda64a Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Wed, 4 Oct 2023 14:35:01 +0200 Subject: [PATCH 35/95] modifying fetchVAST jsdoc --- src/fetcher.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fetcher.js b/src/fetcher.js index 79b82e37..0f8b86d9 100644 --- a/src/fetcher.js +++ b/src/fetcher.js @@ -68,7 +68,7 @@ export class Fetcher { * @param {String} previousUrl - Url of the previous VAST. * @param {Object} wrapperAd - Previously parsed ad node (Wrapper) related to this fetching. * @param {Number} maxWrapperDepth - The maximum number of Wrapper that can be fetch - * @param {emitter} emitter - The function used to Emit event + * @param {Function} emitter - The function used to Emit event * @emits VASTParser#VAST-resolving * @emits VASTParser#VAST-resolved * @return {Promise} From ab8dba0e31e35a4b74f9091d0793cd24961cd7bf Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Wed, 4 Oct 2023 14:36:31 +0200 Subject: [PATCH 36/95] [test] removing duplicate complete key --- spec/samples/inline_trackers.js | 1 - 1 file changed, 1 deletion(-) diff --git a/spec/samples/inline_trackers.js b/spec/samples/inline_trackers.js index afb530d9..88f72a15 100644 --- a/spec/samples/inline_trackers.js +++ b/spec/samples/inline_trackers.js @@ -171,7 +171,6 @@ export const inlineTrackersParsed = { complete: ['http://example.com/linear-complete'], start: ['http://example.com/linear-start'], loaded: ['http://example.com/linear-loaded'], - complete: ['http://example.com/linear-complete'], mute: ['http://example.com/linear-muted'], unmute: ['http://example.com/linear-unmute'], pause: ['http://example.com/linear-pause'], From a8bd5023e03474c10691b87fe568fabf638db51d Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Wed, 4 Oct 2023 14:40:52 +0200 Subject: [PATCH 37/95] [test] removing unused variables --- spec/vast_client.spec.js | 8 +++----- spec/vast_parser.spec.js | 5 ++--- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/spec/vast_client.spec.js b/spec/vast_client.spec.js index e6eec158..08f64d89 100644 --- a/spec/vast_client.spec.js +++ b/spec/vast_client.spec.js @@ -91,11 +91,9 @@ describe('VASTClient', () => { describe('get', () => { let VastClient; - let VastParser; beforeEach(() => { VastClient = new VASTClient(); - VastParser = VastClient.getParser(); }); describe('with resolveAll set to false', () => { @@ -144,7 +142,7 @@ describe('VASTClient', () => { describe('getNextAds', () => { it('resolves all next ads if requested', async () => { - let res = await VastClient.getNextAds(true); + const res = await VastClient.getNextAds(true); expect(res).toEqual({ ads: expect.any(Array), errorURLTemplates: [], @@ -154,12 +152,12 @@ describe('VASTClient', () => { }); it('resolves only next ad if requested', async () => { - let a = await VastClient.get( + await VastClient.get( wrapperMultipleAdsVastUrl, optionsWithNoResolveAll ); - let res = await VastClient.getNextAds(false); + const res = await VastClient.getNextAds(false); expect(res).toEqual({ ads: expect.any(Array), diff --git a/spec/vast_parser.spec.js b/spec/vast_parser.spec.js index d8201544..5c23cfc5 100644 --- a/spec/vast_parser.spec.js +++ b/spec/vast_parser.spec.js @@ -285,7 +285,6 @@ describe('VASTParser', () => { describe('parseVAST', () => { let options; - let res; beforeEach(() => { options = { wrapperLimit: 5, @@ -350,7 +349,7 @@ describe('VASTParser', () => { let trackCalls = null; let dataTriggered = null; const options = { - urlhandler: nodeURLHandler, + urlHandler: nodeURLHandler, }; const dataFromGivenUrl = (url, options = {}) => { @@ -670,7 +669,7 @@ describe('VASTParser', () => { const adWithWrapper = { ...ad, nextWrapperURL: 'http://example.com/foo' }; VastParser.fetchingCallback = () => {}; VastParser.maxWrapperDepth = 10; - return VastParser.resolveWrappers(adWithWrapper, 10).then((res) => { + return VastParser.resolveWrappers(adWithWrapper, 10, null).then((res) => { expect(res).toEqual({ ...ad, errorCode: 302, From 74b3e1c09a06efe693f0c6c1687c48113c275b1a Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Wed, 4 Oct 2023 14:42:29 +0200 Subject: [PATCH 38/95] [doc] updating documentation --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 477d9177..1e80a52e 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ This library provides three components: For the full API documentation go [here](#api). For the full Class reference go [here](https://github.com/dailymotion/vast-client-js/blob/master/docs/api/class-reference.md) -Complies with the [VAST 4.2 specification](https://iabtechlab.com/wp-content/uploads/2019/06/VAST_4.2_final_june26.pdf) provided by the [Interactive Advertising Bureau (IAB)](https://www.iab.com/). +Complies with the [VAST 4.3 specification](https://iabtechlab.com/wp-content/uploads/2022/09/VAST_4.3.pdf) provided by the [Interactive Advertising Bureau (IAB)](https://www.iab.com/). ## Get Started From b41edc3f4942a9e3abefa85c68ac1a3a6816d222 Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Wed, 4 Oct 2023 14:45:42 +0200 Subject: [PATCH 39/95] [test] correcting typo error --- spec/samples/inline_trackers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/samples/inline_trackers.js b/spec/samples/inline_trackers.js index 88f72a15..bb674431 100644 --- a/spec/samples/inline_trackers.js +++ b/spec/samples/inline_trackers.js @@ -234,7 +234,7 @@ export const inlineTrackersParsed = { 'https://www.iab.com/wp-content/uploads/2014/09/iab-tech-lab-6-644x290.png', htmlResource: '\n \n \n \n \n \n Document\n \n \n

titre

\n \n ', - iframeResource: 'lient vers html', + iframeResource: 'liens vers html', nonlinearClickThroughURLTemplate: 'https://iabtechlab.com', nonlinearClickTrackingURLTemplates: [ { From 531ad8aa7c4a5f97b10510eade9781ba517ee5fa Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Fri, 6 Oct 2023 17:57:23 +0200 Subject: [PATCH 40/95] [docs] updating documentations --- README.md | 8 ++++---- docs/api/vast-client.md | 4 +--- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 1e80a52e..24e07e31 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ vastClient.get('https://www.examplevast.com/vast.xml') In addition to fetching and parsing a VAST resource, **VASTClient** provides options to filter a sequence of calls based on count and time of execution, together with the possibility to track URLs using **VASTTracker**. -If you need to directly parse a VAST XML and also follow any wrappers chain, you can use the `parseVAST` method from the **VASTClient** : +If you need to directly parse a VAST XML and also follow any wrappers chain, you can use the `parseVAST` method from the **VASTClient** : ```javascript import { VASTClient } from '@dailymotion/vast-client' @@ -128,13 +128,13 @@ const vastTracker = new VASTTracker(); A pre-bundled version of VAST Client JS is available: [`vast-client.min.js`](dist/vast-client.min.js) [minified]. -If you want to use this file, download it place it your project at your prefered location. -Then, make sure that your main file is in type module as below. +To use it, either host it on your CDN or locally in your project. If you're using a script tag make sure to set the type property to module like below. +_your index.html_ ```html ``` - +_main.js_ ```javascript import {VASTClient, VASTParser, VASTTracker} from "vast-client.min.js" diff --git a/docs/api/vast-client.md b/docs/api/vast-client.md index 3b2f6398..622afedf 100644 --- a/docs/api/vast-client.md +++ b/docs/api/vast-client.md @@ -1,8 +1,6 @@ # VASTClient -The `VASTClient` class provides a client to manage the fetching and parsing of VAST documents. - -`VASTClient` provides a methods to fetch a VAST resource because of his ability to resolving the wrapper chain (recursive fetch and parse) by using the `VASTParser` parsing methods. +The `VASTClient` class provides a client to manage the fetching and parsing of VAST documents. It provides methods for fetching VAST resources by resolving the wrapper chain (recursive fetching and parsing) by using the `VASTParser` parsing methods. - [Constructor](#constructor) - [Properties](#properties) From 24b2c68c2ae5fdc5e1c4932f936470e5d827b6a7 Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Fri, 6 Oct 2023 18:02:47 +0200 Subject: [PATCH 41/95] fix typo and update formatting --- spec/samples/inline_trackers.js | 2 +- src/fetcher.js | 2 +- src/parser/vast_parser.js | 2 -- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/spec/samples/inline_trackers.js b/spec/samples/inline_trackers.js index bb674431..d88cbf4a 100644 --- a/spec/samples/inline_trackers.js +++ b/spec/samples/inline_trackers.js @@ -234,7 +234,7 @@ export const inlineTrackersParsed = { 'https://www.iab.com/wp-content/uploads/2014/09/iab-tech-lab-6-644x290.png', htmlResource: '\n \n \n \n \n \n Document\n \n \n

titre

\n \n ', - iframeResource: 'liens vers html', + iframeResource: 'http://example.com/htmlresourcefile.html', nonlinearClickThroughURLTemplate: 'https://iabtechlab.com', nonlinearClickTrackingURLTemplates: [ { diff --git a/src/fetcher.js b/src/fetcher.js index 0f8b86d9..1dd0f449 100644 --- a/src/fetcher.js +++ b/src/fetcher.js @@ -63,7 +63,7 @@ export class Fetcher { /** * Fetches a VAST document for the given url. * Returns a Promise which resolves,rejects according to the result of the request. - * @param {String} url - The url to request the VAST document. + * @param {String} url - The url to request the VAST document. * @param {Number} wrapperDepth - How many times the current url has been wrapped. * @param {String} previousUrl - Url of the previous VAST. * @param {Object} wrapperAd - Previously parsed ad node (Wrapper) related to this fetching. diff --git a/src/parser/vast_parser.js b/src/parser/vast_parser.js index 3ce74c9e..b8ce6dfa 100644 --- a/src/parser/vast_parser.js +++ b/src/parser/vast_parser.js @@ -359,9 +359,7 @@ export class VASTParser extends EventEmitter { return new Promise((resolve) => { // Going one level deeper in the wrapper chain wrapperDepth++; - // We already have a resolved VAST ad, no need to resolve wrapper - if (!ad.nextWrapperURL) { delete ad.nextWrapperURL; return resolve(ad); From b68037bb21a75b199c3f292ebd3c5a7e1b4ab54e Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Mon, 6 Nov 2023 18:12:15 +0100 Subject: [PATCH 42/95] [client] Pass fetcher as argument of VASTParser --- src/vast_client.js | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/vast_client.js b/src/vast_client.js index 2a1ef914..8e037c15 100644 --- a/src/vast_client.js +++ b/src/vast_client.js @@ -19,12 +19,9 @@ export class VASTClient { constructor(cappingFreeLunch, cappingMinimumTimeInterval, customStorage) { this.cappingFreeLunch = cappingFreeLunch || 0; this.cappingMinimumTimeInterval = cappingMinimumTimeInterval || 0; - this.vastParser = new VASTParser(); - this.storage = customStorage || new Storage(); this.fetcher = new Fetcher(); - this.vastParser.fetchingCallback = this.fetcher.fetchVAST.bind( - this.fetcher - ); + this.vastParser = new VASTParser({ fetcher: this.fetcher }); + this.storage = customStorage || new Storage(); // Init values if not already set if (this.lastSuccessfulAd === undefined) { this.lastSuccessfulAd = 0; @@ -51,8 +48,8 @@ export class VASTClient { * Removes the last element of the url templates filters array. * @return {void} */ - removeURLTemplateFilter() { - this.fetcher.removeURLTemplateFilter(); + removeLastURLTemplateFilter() { + this.fetcher.removeLastURLTemplateFilter(); } /** From c8b4f2c313b1c75a0d8cab9b8a1209c1aa6c1c26 Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Mon, 6 Nov 2023 18:20:17 +0100 Subject: [PATCH 43/95] [parser] add fetcher in the constructor --- src/parser/vast_parser.js | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/parser/vast_parser.js b/src/parser/vast_parser.js index b8ce6dfa..2afaf7e6 100644 --- a/src/parser/vast_parser.js +++ b/src/parser/vast_parser.js @@ -22,11 +22,11 @@ export class VASTParser extends EventEmitter { * Creates an instance of VASTParser. * @constructor */ - constructor() { + constructor({ fetcher } = {}) { super(); this.maxWrapperDepth = null; - this.fetchingCallback = null; this.remainingAds = []; + this.fetcher = fetcher || null; } /** @@ -348,7 +348,7 @@ export class VASTParser extends EventEmitter { /** * Resolves the wrappers for the given ad in a recursive way. * Returns a Promise which resolves with the unwrapped ad or rejects with an error. - * @param {Object} ad - An ad object to be unwrapped. + * @param {Object} adToUnWrap - An ad object to be unwrapped. * @param {Number} wrapperDepth - The reached depth in the wrapper resolving chain. * @param {String} previousUrl - The previous vast url. * @return {Promise} @@ -365,7 +365,7 @@ export class VASTParser extends EventEmitter { return resolve(ad); } - if (!this.fetchingCallback) { + if (!this.fetcher) { ad.VASTAdTagURI = ad.nextWrapperURL; delete ad.nextWrapperURL; return resolve(ad); @@ -392,11 +392,12 @@ export class VASTParser extends EventEmitter { // sequence doesn't carry over in wrapper element const wrapperSequence = ad.sequence; - this.fetchingCallback({ - url: ad.nextWrapperURL, - emitter: this.emit.bind(this), - maxWrapperDepth: this.maxWrapperDepth, - }) + this.fetcher + .fetchVAST({ + url: ad.nextWrapperURL, + emitter: this.emit.bind(this), + maxWrapperDepth: this.maxWrapperDepth, + }) .then((xml) => { return this.parse(xml, { url: ad.nextWrapperURL, From 04c582f72383d967532b423ca729a9c92cd7c14c Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Mon, 6 Nov 2023 18:23:01 +0100 Subject: [PATCH 44/95] updated comments and variables names --- src/fetcher.js | 39 +++++++++++++++++---------------------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/src/fetcher.js b/src/fetcher.js index 1dd0f449..a1fb6ebf 100644 --- a/src/fetcher.js +++ b/src/fetcher.js @@ -28,7 +28,6 @@ export class Fetcher { /** * Adds a filter function to the array of filters which are called before fetching a VAST document. * @param {function} filter - The filter function to be added at the end of the array. - * @return {void} */ addURLTemplateFilter(filter) { if (typeof filter === 'function') { @@ -37,15 +36,14 @@ export class Fetcher { } /** - * Removes the last element of the url templates filters array. - * @return {void} + * Removes the latest URL template filter added. */ - removeURLTemplateFilter() { + removeLastURLTemplateFilter() { this.URLTemplateFilters.pop(); } /** - * Returns the number of filters of the url templates filters array. + * Returns the number of URL template filters added. * @return {Number} */ countURLTemplateFilters() { @@ -53,8 +51,7 @@ export class Fetcher { } /** - * Removes all the filter functions from the url templates filters array. - * @return {void} + * Removes all the URL template filters added. */ clearURLTemplateFilters() { this.URLTemplateFilters = []; @@ -62,15 +59,13 @@ export class Fetcher { /** * Fetches a VAST document for the given url. - * Returns a Promise which resolves,rejects according to the result of the request. - * @param {String} url - The url to request the VAST document. - * @param {Number} wrapperDepth - How many times the current url has been wrapped. - * @param {String} previousUrl - Url of the previous VAST. - * @param {Object} wrapperAd - Previously parsed ad node (Wrapper) related to this fetching. - * @param {Number} maxWrapperDepth - The maximum number of Wrapper that can be fetch - * @param {Function} emitter - The function used to Emit event - * @emits VASTParser#VAST-resolving - * @emits VASTParser#VAST-resolved + * @param {Object} params + * @param {String} params.url - The url to request the VAST document. + * @param {Number} params.wrapperDepth - How many times the current url has been wrapped. + * @param {(String | null)} params.previousUrl - Url of the previous VAST. + * @param {Object} params.wrapperAd - Previously parsed ad node (Wrapper) related to this fetching. + * @param {Number} params.maxWrapperDepth - The maximum number of Wrapper that can be fetch + * @param {Function} params.emitter - The function used to Emit event * @return {Promise} */ fetchVAST({ @@ -102,18 +97,18 @@ export class Fetcher { url, this.fetchingOptions, (error, xml, details = {}) => { - const deltaTime = Math.round(Date.now() - timeBeforeGet); - const info = { + const requestDuration = Math.round(Date.now() - timeBeforeGet); + + emitter('VAST-resolved', { url, previousUrl, wrapperDepth, error, - duration: deltaTime, + duration: requestDuration, ...details, - }; + }); - emitter('VAST-resolved', info); - updateEstimatedBitrate(details.byteLength, deltaTime); + updateEstimatedBitrate(details.byteLength, requestDuration); if (error) { reject(error); From 56ecbcc1c4abd4f507e111243eec58a41f3fdca6 Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Mon, 6 Nov 2023 18:23:47 +0100 Subject: [PATCH 45/95] [test] update tests cases --- spec/fetcher.spec.js | 4 ++-- spec/vast_parser.spec.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/fetcher.spec.js b/spec/fetcher.spec.js index 12b175b4..24429f78 100644 --- a/spec/fetcher.spec.js +++ b/spec/fetcher.spec.js @@ -232,9 +232,9 @@ describe('Fetcher', () => { }); }); - describe('removeURLTemplateFilter', () => { + describe('removeLastUrlTemplateFilter', () => { it('remove the last element of the URLTemplateFilters array', () => { - fetcher.removeURLTemplateFilter(); + fetcher.removeLastURLTemplateFilter(); expect(fetcher.URLTemplateFilters.length).toBe(0); }); }); diff --git a/spec/vast_parser.spec.js b/spec/vast_parser.spec.js index 5c23cfc5..fbcf18af 100644 --- a/spec/vast_parser.spec.js +++ b/spec/vast_parser.spec.js @@ -72,9 +72,9 @@ describe('VASTParser', () => { }); beforeEach(() => { - VastParser = new VASTParser(); vastClient = new VASTClient(); fetcher = new Fetcher(); + VastParser = new VASTParser({ fetcher }); jest.spyOn(VastParser, 'emit'); }); From 11e0876a6fdd7cfb37c4d158678d3b12861f8334 Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Tue, 7 Nov 2023 16:28:13 +0100 Subject: [PATCH 46/95] minor code update --- src/fetcher.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fetcher.js b/src/fetcher.js index a1fb6ebf..261b523f 100644 --- a/src/fetcher.js +++ b/src/fetcher.js @@ -21,7 +21,7 @@ export class Fetcher { this.urlHandler = options.urlHandler || options.urlhandler || urlHandler; this.fetchingOptions = { timeout: options.timeout || DEFAULT_TIMEOUT, - withCredentials: options.withCredentials || false, + withCredentials: Boolean(options.withCredentials), }; } From 83f54643099e5f6d8cff8deaf9722391f28a29c3 Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Tue, 7 Nov 2023 16:32:38 +0100 Subject: [PATCH 47/95] remove unnecessary file in dist folder --- dist/vast-client-browser.min.js | 1 - 1 file changed, 1 deletion(-) delete mode 100644 dist/vast-client-browser.min.js diff --git a/dist/vast-client-browser.min.js b/dist/vast-client-browser.min.js deleted file mode 100644 index e0c492a9..00000000 --- a/dist/vast-client-browser.min.js +++ /dev/null @@ -1 +0,0 @@ -var VAST=function(e){"use strict";function t(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);t&&(i=i.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,i)}return r}function r(e){for(var r=1;re.length)&&(t=e.length);for(var r=0,i=new Array(t);r0&&void 0!==arguments[0]?arguments[0]:{};return{id:e.id||null,adId:e.adId||null,sequence:e.sequence||null,apiFramework:e.apiFramework||null,universalAdIds:[],creativeExtensions:[]}}var g=["ADCATEGORIES","ADCOUNT","ADPLAYHEAD","ADSERVINGID","ADTYPE","APIFRAMEWORKS","APPBUNDLE","ASSETURI","BLOCKEDADCATEGORIES","BREAKMAXADLENGTH","BREAKMAXADS","BREAKMAXDURATION","BREAKMINADLENGTH","BREAKMINDURATION","BREAKPOSITION","CLICKPOS","CLICKTYPE","CLIENTUA","CONTENTID","CONTENTPLAYHEAD","CONTENTURI","DEVICEIP","DEVICEUA","DOMAIN","EXTENSIONS","GDPRCONSENT","IFA","IFATYPE","INVENTORYSTATE","LATLONG","LIMITADTRACKING","MEDIAMIME","MEDIAPLAYHEAD","OMIDPARTNER","PAGEURL","PLACEMENTTYPE","PLAYERCAPABILITIES","PLAYERSIZE","PLAYERSTATE","PODSEQUENCE","REGULATIONS","SERVERSIDE","SERVERUA","TRANSACTIONID","UNIVERSALADID","VASTVERSIONS","VERIFICATIONVENDORS"];function y(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},i=[],n=A(e);for(var a in!t.ERRORCODE||r.isCustomCode||/^[0-9]{3}$/.test(t.ERRORCODE)||(t.ERRORCODE=900),t.CACHEBUSTING=w(Math.round(1e8*Math.random())),t.TIMESTAMP=(new Date).toISOString(),t.RANDOM=t.random=t.CACHEBUSTING,t)t[a]=N(t[a]);for(var o in n){var s=n[o];"string"==typeof s&&i.push(T(s,t))}return i}function T(e,t){var r=(e=k(e,t)).match(/[^[\]]+(?=])/g);if(!r)return e;var i=r.filter((function(e){return g.indexOf(e)>-1}));return 0===i.length?e:k(e,i=i.reduce((function(e,t){return e[t]=-1,e}),{}))}function k(e,t){var r=e;for(var i in t){var n=t[i];r=r.replace(new RegExp("(?:\\[|%%)(".concat(i,")(?:\\]|%%)"),"g"),n)}return r}function A(e){return Array.isArray(e)?e.map((function(e){return e&&e.hasOwnProperty("url")?e.url:e})):e}function b(e){return/^(https?:\/\/|\/\/)/.test(e)}function R(e,t){for(var r=0;r1&&void 0!==arguments[1]?arguments[1]:8;return e.toString().padStart(t,"0")}var L={track:function(e,t,r){y(e,t,r).forEach((function(e){"undefined"!=typeof window&&null!==window&&((new Image).src=e)}))},resolveURLTemplates:y,extractURLsFromTemplates:A,filterValidUrlTemplates:function(e){return Array.isArray(e)?e.filter((function(e){return b(e.hasOwnProperty("url")?e.url:e)})):b(e)},containsTemplateObject:R,isTemplateObjectEqual:E,encodeURIComponentRFC3986:N,replaceUrlMacros:T,isNumeric:function(e){return!isNaN(parseFloat(e))&&isFinite(e)},flatten:function e(t){return t.reduce((function(t,r){return t.concat(Array.isArray(r)?e(r):r)}),[])},joinArrayOfUniqueTemplateObjs:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[],t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:[],r=Array.isArray(e)?e:[],i=Array.isArray(t)?t:[];return r.concat(i).reduce((function(e,t){return R(t,e)||e.push(t),e}),[])},isValidTimeValue:function(e){return Number.isFinite(e)&&e>=-2},addLeadingZeros:w};function U(e){return-1!==["true","TRUE","True","1"].indexOf(e)}function C(e){if(null==e)return-1;if(L.isNumeric(e))return parseInt(e);var t=e.split(":");if(3!==t.length)return-1;var r=t[2].split("."),i=parseInt(r[0]);2===r.length&&(i+=parseFloat("0.".concat(r[1])));var n=parseInt(60*t[1]),a=parseInt(60*t[0]*60);return isNaN(a)||isNaN(n)||isNaN(i)||n>3600||i>60?-1:a+n+i}var I={childByName:function(e,t){var r=e.childNodes;for(var i in r){var n=r[i];if(n.nodeName===t)return n}},childrenByName:function(e,t){var r=[],i=e.childNodes;for(var n in i){var a=i[n];a.nodeName===t&&r.push(a)}return r},resolveVastAdTagURI:function(e,t){if(!t)return e;if(0===e.indexOf("//")){var r=location.protocol;return"".concat(r).concat(e)}if(-1===e.indexOf("://")){var i=t.slice(0,t.lastIndexOf("/"));return"".concat(i,"/").concat(e)}return e},parseBoolean:U,parseNodeText:function(e){return e&&(e.textContent||e.text||"").trim()},copyNodeAttribute:function(e,t,r){var i=t.getAttribute(e);i&&r.setAttribute(e,i)},parseAttributes:function(e){for(var t=e.attributes,r={},i=0;i1){var a=e[n-1];if(a&&a.sequence===i.sequence-1)return void(r&&r.push(i));delete i.sequence}r=[i],t.push(r)})),t},assignAttributes:function(e,t){if(e)for(var r in e){var i=e[r];if(i.nodeName&&i.nodeValue&&t.hasOwnProperty(i.nodeName)){var n=i.nodeValue;"boolean"==typeof t[i.nodeName]&&(n=U(n)),t[i.nodeName]=n}}},mergeWrapperAdData:function(e,t){var r;e.errorURLTemplates=t.errorURLTemplates.concat(e.errorURLTemplates),e.impressionURLTemplates=t.impressionURLTemplates.concat(e.impressionURLTemplates),e.extensions=t.extensions.concat(e.extensions),t.viewableImpression.length>0&&(e.viewableImpression=[].concat(h(e.viewableImpression),h(t.viewableImpression))),e.followAdditionalWrappers=t.followAdditionalWrappers,e.allowMultipleAds=t.allowMultipleAds,e.fallbackOnNoAd=t.fallbackOnNoAd;var i=(t.creatives||[]).filter((function(e){return e&&"companion"===e.type})),n=i.reduce((function(e,t){return(t.variations||[]).forEach((function(t){(t.companionClickTrackingURLTemplates||[]).forEach((function(t){L.containsTemplateObject(t,e)||e.push(t)}))})),e}),[]);e.creatives=i.concat(e.creatives);var a=t.videoClickTrackingURLTemplates&&t.videoClickTrackingURLTemplates.length,o=t.videoCustomClickURLTemplates&&t.videoCustomClickURLTemplates.length;if(e.creatives.forEach((function(e){if(t.trackingEvents&&t.trackingEvents[e.type])for(var r in t.trackingEvents[e.type]){var i=t.trackingEvents[e.type][r];Array.isArray(e.trackingEvents[r])||(e.trackingEvents[r]=[]),e.trackingEvents[r]=e.trackingEvents[r].concat(i)}"linear"===e.type&&(a&&(e.videoClickTrackingURLTemplates=e.videoClickTrackingURLTemplates.concat(t.videoClickTrackingURLTemplates)),o&&(e.videoCustomClickURLTemplates=e.videoCustomClickURLTemplates.concat(t.videoCustomClickURLTemplates)),!t.videoClickThroughURLTemplate||null!==e.videoClickThroughURLTemplate&&void 0!==e.videoClickThroughURLTemplate||(e.videoClickThroughURLTemplate=t.videoClickThroughURLTemplate)),"companion"===e.type&&n.length&&(e.variations||[]).forEach((function(e){e.companionClickTrackingURLTemplates=L.joinArrayOfUniqueTemplateObjs(e.companionClickTrackingURLTemplates,n)}))})),t.adVerifications&&(e.adVerifications=e.adVerifications.concat(t.adVerifications)),t.blockedAdCategories&&(e.blockedAdCategories=e.blockedAdCategories.concat(t.blockedAdCategories)),null!==(r=t.creatives)&&void 0!==r&&r.length){var s=t.creatives.filter((function(e){var t;return(null===(t=e.icons)||void 0===t?void 0:t.length)&&!e.mediaFiles.length}));s.length&&(e.creatives=e.creatives.concat(s))}}};function x(e,t){var r=function(){var e=m(arguments.length>0&&void 0!==arguments[0]?arguments[0]:{});return{id:e.id,adId:e.adId,sequence:e.sequence,apiFramework:e.apiFramework,type:"companion",required:null,variations:[]}}(t);return r.required=e.getAttribute("required")||null,r.variations=I.childrenByName(e,"Companion").map((function(e){var t=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return{id:e.id||null,adType:"companionAd",width:e.width||0,height:e.height||0,assetWidth:e.assetWidth||null,assetHeight:e.assetHeight||null,expandedWidth:e.expandedWidth||null,expandedHeight:e.expandedHeight||null,apiFramework:e.apiFramework||null,adSlotId:e.adSlotId||null,pxratio:e.pxratio||"1",renderingMode:e.renderingMode||"default",staticResources:[],htmlResources:[],iframeResources:[],adParameters:null,altText:null,companionClickThroughURLTemplate:null,companionClickTrackingURLTemplates:[],trackingEvents:{}}}(I.parseAttributes(e));t.htmlResources=I.childrenByName(e,"HTMLResource").reduce((function(e,t){var r=I.parseNodeText(t);return r?e.concat(r):e}),[]),t.iframeResources=I.childrenByName(e,"IFrameResource").reduce((function(e,t){var r=I.parseNodeText(t);return r?e.concat(r):e}),[]),t.staticResources=I.childrenByName(e,"StaticResource").reduce((function(e,t){var r=I.parseNodeText(t);return r?e.concat({url:r,creativeType:t.getAttribute("creativeType")||null}):e}),[]),t.altText=I.parseNodeText(I.childByName(e,"AltText"))||null;var r=I.childByName(e,"TrackingEvents");r&&I.childrenByName(r,"Tracking").forEach((function(e){var r=e.getAttribute("event"),i=I.parseNodeText(e);r&&i&&(Array.isArray(t.trackingEvents[r])||(t.trackingEvents[r]=[]),t.trackingEvents[r].push(i))})),t.companionClickTrackingURLTemplates=I.childrenByName(e,"CompanionClickTracking").map((function(e){return{id:e.getAttribute("id")||null,url:I.parseNodeText(e)}})),t.companionClickThroughURLTemplate=I.parseNodeText(I.childByName(e,"CompanionClickThrough"))||null;var i=I.childByName(e,"AdParameters");return i&&(t.adParameters={value:I.parseNodeText(i),xmlEncoded:i.getAttribute("xmlEncoded")||null}),t})),r}function S(e,t){var r,i=function(){var e=m(arguments.length>0&&void 0!==arguments[0]?arguments[0]:{});return{id:e.id,adId:e.adId,sequence:e.sequence,apiFramework:e.apiFramework,type:"linear",duration:0,skipDelay:null,mediaFiles:[],mezzanine:null,interactiveCreativeFile:null,closedCaptionFiles:[],videoClickThroughURLTemplate:null,videoClickTrackingURLTemplates:[],videoCustomClickURLTemplates:[],adParameters:null,icons:[],trackingEvents:{}}}(t);i.duration=I.parseDuration(I.parseNodeText(I.childByName(e,"Duration")));var n=e.getAttribute("skipoffset");if(null==n)i.skipDelay=null;else if("%"===n.charAt(n.length-1)&&-1!==i.duration){var a=parseInt(n,10);i.skipDelay=i.duration*(a/100)}else i.skipDelay=I.parseDuration(n);var o=I.childByName(e,"VideoClicks");if(o){var s=I.childByName(o,"ClickThrough");i.videoClickThroughURLTemplate=s?{id:s.getAttribute("id")||null,url:I.parseNodeText(s)}:null,I.childrenByName(o,"ClickTracking").forEach((function(e){i.videoClickTrackingURLTemplates.push({id:e.getAttribute("id")||null,url:I.parseNodeText(e)})})),I.childrenByName(o,"CustomClick").forEach((function(e){i.videoCustomClickURLTemplates.push({id:e.getAttribute("id")||null,url:I.parseNodeText(e)})}))}var l=I.childByName(e,"AdParameters");l&&(i.adParameters={value:I.parseNodeText(l),xmlEncoded:l.getAttribute("xmlEncoded")||null}),I.childrenByName(e,"TrackingEvents").forEach((function(e){I.childrenByName(e,"Tracking").forEach((function(e){var t=e.getAttribute("event"),n=I.parseNodeText(e);if(t&&n){if("progress"===t){if(!(r=e.getAttribute("offset")))return;t="%"===r.charAt(r.length-1)?"progress-".concat(r):"progress-".concat(Math.round(I.parseDuration(r)))}Array.isArray(i.trackingEvents[t])||(i.trackingEvents[t]=[]),i.trackingEvents[t].push(n)}}))})),I.childrenByName(e,"MediaFiles").forEach((function(e){I.childrenByName(e,"MediaFile").forEach((function(e){i.mediaFiles.push(function(e){var t={id:null,fileURL:null,fileSize:0,deliveryType:"progressive",mimeType:null,mediaType:null,codec:null,bitrate:0,minBitrate:0,maxBitrate:0,width:0,height:0,apiFramework:null,scalable:null,maintainAspectRatio:null};t.id=e.getAttribute("id"),t.fileURL=I.parseNodeText(e),t.deliveryType=e.getAttribute("delivery"),t.codec=e.getAttribute("codec"),t.mimeType=e.getAttribute("type"),t.mediaType=e.getAttribute("mediaType")||"2D",t.apiFramework=e.getAttribute("apiFramework"),t.fileSize=parseInt(e.getAttribute("fileSize")||0),t.bitrate=parseInt(e.getAttribute("bitrate")||0),t.minBitrate=parseInt(e.getAttribute("minBitrate")||0),t.maxBitrate=parseInt(e.getAttribute("maxBitrate")||0),t.width=parseInt(e.getAttribute("width")||0),t.height=parseInt(e.getAttribute("height")||0);var r=e.getAttribute("scalable");r&&"string"==typeof r&&(t.scalable=I.parseBoolean(r));var i=e.getAttribute("maintainAspectRatio");i&&"string"==typeof i&&(t.maintainAspectRatio=I.parseBoolean(i));return t}(e))}));var t=I.childByName(e,"InteractiveCreativeFile");t&&(i.interactiveCreativeFile=function(e){var t=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return{type:e.type||null,apiFramework:e.apiFramework||null,variableDuration:I.parseBoolean(e.variableDuration),fileURL:null}}(I.parseAttributes(e));return t.fileURL=I.parseNodeText(e),t}(t));var r=I.childByName(e,"ClosedCaptionFiles");r&&I.childrenByName(r,"ClosedCaptionFile").forEach((function(e){var t=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return{type:e.type||null,language:e.language||null,fileURL:null}}(I.parseAttributes(e));t.fileURL=I.parseNodeText(e),i.closedCaptionFiles.push(t)}));var n,a,o,s=I.childByName(e,"Mezzanine"),l=(n=s,a={},o=!1,["delivery","type","width","height"].forEach((function(e){n&&n.getAttribute(e)?a[e]=n.getAttribute(e):o=!0})),o?null:a);if(l){var c={id:null,fileURL:null,delivery:null,codec:null,type:null,width:0,height:0,fileSize:0,mediaType:"2D"};c.id=s.getAttribute("id"),c.fileURL=I.parseNodeText(s),c.delivery=l.delivery,c.codec=s.getAttribute("codec"),c.type=l.type,c.width=parseInt(l.width,10),c.height=parseInt(l.height,10),c.fileSize=parseInt(s.getAttribute("fileSize"),10),c.mediaType=s.getAttribute("mediaType")||"2D",i.mezzanine=c}}));var c=I.childByName(e,"Icons");return c&&I.childrenByName(c,"Icon").forEach((function(e){i.icons.push(function(e){var t={program:null,height:0,width:0,xPosition:0,yPosition:0,apiFramework:null,offset:null,duration:0,type:null,staticResource:null,htmlResource:null,iframeResource:null,pxratio:"1",iconClickThroughURLTemplate:null,iconClickTrackingURLTemplates:[],iconViewTrackingURLTemplate:null,iconClickFallbackImages:[]};t.program=e.getAttribute("program"),t.height=parseInt(e.getAttribute("height")||0),t.width=parseInt(e.getAttribute("width")||0),t.xPosition=function(e){if(-1!==["left","right"].indexOf(e))return e;return parseInt(e||0)}(e.getAttribute("xPosition")),t.yPosition=function(e){if(-1!==["top","bottom"].indexOf(e))return e;return parseInt(e||0)}(e.getAttribute("yPosition")),t.apiFramework=e.getAttribute("apiFramework"),t.pxratio=e.getAttribute("pxratio")||"1",t.offset=I.parseDuration(e.getAttribute("offset")),t.duration=I.parseDuration(e.getAttribute("duration")),I.childrenByName(e,"HTMLResource").forEach((function(e){t.type=e.getAttribute("creativeType")||"text/html",t.htmlResource=I.parseNodeText(e)})),I.childrenByName(e,"IFrameResource").forEach((function(e){t.type=e.getAttribute("creativeType")||0,t.iframeResource=I.parseNodeText(e)})),I.childrenByName(e,"StaticResource").forEach((function(e){t.type=e.getAttribute("creativeType")||0,t.staticResource=I.parseNodeText(e)}));var r=I.childByName(e,"IconClicks");if(r){t.iconClickThroughURLTemplate=I.parseNodeText(I.childByName(r,"IconClickThrough")),I.childrenByName(r,"IconClickTracking").forEach((function(e){t.iconClickTrackingURLTemplates.push({id:e.getAttribute("id")||null,url:I.parseNodeText(e)})}));var i=I.childByName(r,"IconClickFallbackImages");i&&I.childrenByName(i,"IconClickFallbackImage").forEach((function(e){t.iconClickFallbackImages.push({url:I.parseNodeText(e)||null,width:e.getAttribute("width")||null,height:e.getAttribute("height")||null})}))}return t.iconViewTrackingURLTemplate=I.parseNodeText(I.childByName(e,"IconViewTracking")),t}(e))})),i}function O(e,t){var r=function(){var e=m(arguments.length>0&&void 0!==arguments[0]?arguments[0]:{});return{id:e.id,adId:e.adId,sequence:e.sequence,apiFramework:e.apiFramework,type:"nonlinear",variations:[],trackingEvents:{}}}(t);return I.childrenByName(e,"TrackingEvents").forEach((function(e){var t,i;I.childrenByName(e,"Tracking").forEach((function(e){t=e.getAttribute("event"),i=I.parseNodeText(e),t&&i&&(Array.isArray(r.trackingEvents[t])||(r.trackingEvents[t]=[]),r.trackingEvents[t].push(i))}))})),I.childrenByName(e,"NonLinear").forEach((function(e){var t={id:null,width:0,height:0,expandedWidth:0,expandedHeight:0,scalable:!0,maintainAspectRatio:!0,minSuggestedDuration:0,apiFramework:"static",adType:"nonLinearAd",type:null,staticResource:null,htmlResource:null,iframeResource:null,nonlinearClickThroughURLTemplate:null,nonlinearClickTrackingURLTemplates:[],adParameters:null};t.id=e.getAttribute("id")||null,t.width=e.getAttribute("width"),t.height=e.getAttribute("height"),t.expandedWidth=e.getAttribute("expandedWidth"),t.expandedHeight=e.getAttribute("expandedHeight"),t.scalable=I.parseBoolean(e.getAttribute("scalable")),t.maintainAspectRatio=I.parseBoolean(e.getAttribute("maintainAspectRatio")),t.minSuggestedDuration=I.parseDuration(e.getAttribute("minSuggestedDuration")),t.apiFramework=e.getAttribute("apiFramework"),I.childrenByName(e,"HTMLResource").forEach((function(e){t.type=e.getAttribute("creativeType")||"text/html",t.htmlResource=I.parseNodeText(e)})),I.childrenByName(e,"IFrameResource").forEach((function(e){t.type=e.getAttribute("creativeType")||0,t.iframeResource=I.parseNodeText(e)})),I.childrenByName(e,"StaticResource").forEach((function(e){t.type=e.getAttribute("creativeType")||0,t.staticResource=I.parseNodeText(e)}));var i=I.childByName(e,"AdParameters");i&&(t.adParameters={value:I.parseNodeText(i),xmlEncoded:i.getAttribute("xmlEncoded")||null}),t.nonlinearClickThroughURLTemplate=I.parseNodeText(I.childByName(e,"NonLinearClickThrough")),I.childrenByName(e,"NonLinearClickTracking").forEach((function(e){t.nonlinearClickTrackingURLTemplates.push({id:e.getAttribute("id")||null,url:I.parseNodeText(e)})})),r.variations.push(t)})),r}function D(e){var t=[];return e.forEach((function(e){var r=V(e);r&&t.push(r)})),t}function V(e){if("#comment"===e.nodeName)return null;var t,r={name:null,value:null,attributes:{},children:[]},i=e.attributes,n=e.childNodes;if(r.name=e.nodeName,e.attributes)for(var a in i)if(i.hasOwnProperty(a)){var o=i[a];o.nodeName&&o.nodeValue&&(r.attributes[o.nodeName]=o.nodeValue)}for(var s in n)if(n.hasOwnProperty(s)){var l=V(n[s]);l&&r.children.push(l)}if(0===r.children.length||1===r.children.length&&["#cdata-section","#text"].indexOf(r.children[0].name)>=0){var c=I.parseNodeText(e);""!==c&&(r.value=c),r.children=[]}return null===(t=r).value&&0===Object.keys(t.attributes).length&&0===t.children.length?null:r}function P(e){var t=[];return e.forEach((function(e){var r,i={id:e.getAttribute("id")||null,adId:B(e),sequence:e.getAttribute("sequence")||null,apiFramework:e.getAttribute("apiFramework")||null},n=[];I.childrenByName(e,"UniversalAdId").forEach((function(e){var t={idRegistry:e.getAttribute("idRegistry")||"unknown",value:I.parseNodeText(e)};n.push(t)}));var a=I.childByName(e,"CreativeExtensions");for(var o in a&&(r=D(I.childrenByName(a,"CreativeExtension"))),e.childNodes){var s=e.childNodes[o],l=void 0;switch(s.nodeName){case"Linear":l=S(s,i);break;case"NonLinearAds":l=O(s,i);break;case"CompanionAds":l=x(s,i)}l&&(n&&(l.universalAdIds=n),r&&(l.creativeExtensions=r),t.push(l))}})),t}function B(e){return e.getAttribute("AdID")||e.getAttribute("adID")||e.getAttribute("adId")||null}var F={Wrapper:{subElements:["VASTAdTagURI","Impression"]},BlockedAdCategories:{attributes:["authority"]},InLine:{subElements:["AdSystem","AdTitle","Impression","AdServingId","Creatives"]},Category:{attributes:["authority"]},Pricing:{attributes:["model","currency"]},Verification:{oneOfinLineResources:["JavaScriptResource","ExecutableResource"],attributes:["vendor"]},UniversalAdId:{attributes:["idRegistry"]},JavaScriptResource:{attributes:["apiFramework","browserOptional"]},ExecutableResource:{attributes:["apiFramework","type"]},Tracking:{attributes:["event"]},Creatives:{subElements:["Creative"]},Creative:{subElements:["UniversalAdId"]},Linear:{subElements:["MediaFiles","Duration"]},MediaFiles:{subElements:["MediaFile"]},MediaFile:{attributes:["delivery","type","width","height"]},Mezzanine:{attributes:["delivery","type","width","height"]},NonLinear:{oneOfinLineResources:["StaticResource","IFrameResource","HTMLResource"],attributes:["width","height"]},Companion:{oneOfinLineResources:["StaticResource","IFrameResource","HTMLResource"],attributes:["width","height"]},StaticResource:{attributes:["creativeType"]},Icons:{subElements:["Icon"]},Icon:{oneOfinLineResources:["StaticResource","IFrameResource","HTMLResource"]}};function M(e,t){if(F[e.nodeName]&&F[e.nodeName].attributes){var r=F[e.nodeName].attributes.filter((function(t){return!e.getAttribute(t)}));r.length>0&&q({name:e.nodeName,parentName:e.parentNode.nodeName,attributes:r},t)}}function j(e,t,r){var i=F[e.nodeName],n=!r&&"Wrapper"!==e.nodeName;if(i&&!n){if(i.subElements){var a=i.subElements.filter((function(t){return!I.childByName(e,t)}));a.length>0&&q({name:e.nodeName,parentName:e.parentNode.nodeName,subElements:a},t)}if(r&&i.oneOfinLineResources)i.oneOfinLineResources.some((function(t){return I.childByName(e,t)}))||q({name:e.nodeName,parentName:e.parentNode.nodeName,oneOfResources:i.oneOfinLineResources},t)}}function W(e){return e.children&&0!==e.children.length}function q(e,t){var r=e.name,i=e.parentName,n=e.attributes,a=e.subElements,o=e.oneOfResources,s="Element '".concat(r,"'");t("VAST-warning",{message:s+=n?" missing required attribute(s) '".concat(n.join(", "),"' "):a?" missing required sub element(s) '".concat(a.join(", "),"' "):o?" must provide one of the following '".concat(o.join(", "),"' "):" is empty",parentElement:i,specVersion:4.1})}var H={verifyRequiredValues:function e(t,r,i){if(t&&t.nodeName)if("InLine"===t.nodeName&&(i=!0),M(t,r),W(t)){j(t,r,i);for(var n=0;n2&&void 0!==arguments[2]?arguments[2]:{},i=r.allowMultipleAds,n=r.followAdditionalWrappers,a=e.childNodes;for(var o in a){var s=a[o];if(-1!==["Wrapper","InLine"].indexOf(s.nodeName)&&("Wrapper"!==s.nodeName||!1!==n)){if(I.copyNodeAttribute("id",e,s),I.copyNodeAttribute("sequence",e,s),I.copyNodeAttribute("adType",e,s),"Wrapper"===s.nodeName)return{ad:Y(s,t),type:"WRAPPER"};if("InLine"===s.nodeName)return{ad:G(s,t,{allowMultipleAds:i}),type:"INLINE"}}}}function G(e,t){return!1===(arguments.length>2&&void 0!==arguments[2]?arguments[2]:{}).allowMultipleAds&&e.getAttribute("sequence")?null:z(e,t)}function z(e,t){var r=[];t&&H.verifyRequiredValues(e,t);var i,n,a=e.childNodes,o=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return{id:e.id||null,sequence:e.sequence||null,adType:e.adType||null,adServingId:null,categories:[],expires:null,viewableImpression:[],system:null,title:null,description:null,advertiser:null,pricing:null,survey:null,errorURLTemplates:[],impressionURLTemplates:[],creatives:[],extensions:[],adVerifications:[],blockedAdCategories:[],followAdditionalWrappers:!0,allowMultipleAds:!1,fallbackOnNoAd:null}}(I.parseAttributes(e));for(var s in a){var l=a[s];switch(l.nodeName){case"Error":o.errorURLTemplates.push(I.parseNodeText(l));break;case"Impression":o.impressionURLTemplates.push({id:l.getAttribute("id")||null,url:I.parseNodeText(l)});break;case"Creatives":o.creatives=P(I.childrenByName(l,"Creative"));break;case"Extensions":var c=I.childrenByName(l,"Extension");o.extensions=D(c),o.adVerifications.length||(r=K(c));break;case"AdVerifications":o.adVerifications=X(I.childrenByName(l,"Verification"));break;case"AdSystem":o.system={value:I.parseNodeText(l),version:l.getAttribute("version")||null};break;case"AdTitle":o.title=I.parseNodeText(l);break;case"AdServingId":o.adServingId=I.parseNodeText(l);break;case"Category":o.categories.push({authority:l.getAttribute("authority")||null,value:I.parseNodeText(l)});break;case"Expires":o.expires=parseInt(I.parseNodeText(l),10);break;case"ViewableImpression":o.viewableImpression.push((n=void 0,n=function(e,t){var r=I.parseNodeText(t);return r&&e.push(r),e},{id:(i=l).getAttribute("id")||null,viewable:I.childrenByName(i,"Viewable").reduce(n,[]),notViewable:I.childrenByName(i,"NotViewable").reduce(n,[]),viewUndetermined:I.childrenByName(i,"ViewUndetermined").reduce(n,[])}));break;case"Description":o.description=I.parseNodeText(l);break;case"Advertiser":o.advertiser={id:l.getAttribute("id")||null,value:I.parseNodeText(l)};break;case"Pricing":o.pricing={value:I.parseNodeText(l),model:l.getAttribute("model")||null,currency:l.getAttribute("currency")||null};break;case"Survey":o.survey={value:I.parseNodeText(l),type:l.getAttribute("type")||null};break;case"BlockedAdCategories":o.blockedAdCategories.push({authority:l.getAttribute("authority")||null,value:I.parseNodeText(l)})}}return r.length&&(o.adVerifications=o.adVerifications.concat(r)),o}function Y(e,t){var r=z(e,t),i=e.getAttribute("followAdditionalWrappers"),n=e.getAttribute("allowMultipleAds"),a=e.getAttribute("fallbackOnNoAd");r.followAdditionalWrappers=!i||I.parseBoolean(i),r.allowMultipleAds=!!n&&I.parseBoolean(n),r.fallbackOnNoAd=a?I.parseBoolean(a):null;var o=I.childByName(e,"VASTAdTagURI");if(o?r.nextWrapperURL=I.parseNodeText(o):(o=I.childByName(e,"VASTAdTagURL"))&&(r.nextWrapperURL=I.parseNodeText(I.childByName(o,"URL"))),r.creatives.forEach((function(e){if(-1!==["linear","nonlinear"].indexOf(e.type)){if(e.trackingEvents){r.trackingEvents||(r.trackingEvents={}),r.trackingEvents[e.type]||(r.trackingEvents[e.type]={});var t=function(t){var i=e.trackingEvents[t];Array.isArray(r.trackingEvents[e.type][t])||(r.trackingEvents[e.type][t]=[]),i.forEach((function(i){r.trackingEvents[e.type][t].push(i)}))};for(var i in e.trackingEvents)t(i)}e.videoClickTrackingURLTemplates&&(Array.isArray(r.videoClickTrackingURLTemplates)||(r.videoClickTrackingURLTemplates=[]),e.videoClickTrackingURLTemplates.forEach((function(e){r.videoClickTrackingURLTemplates.push(e)}))),e.videoClickThroughURLTemplate&&(r.videoClickThroughURLTemplate=e.videoClickThroughURLTemplate),e.videoCustomClickURLTemplates&&(Array.isArray(r.videoCustomClickURLTemplates)||(r.videoCustomClickURLTemplates=[]),e.videoCustomClickURLTemplates.forEach((function(e){r.videoCustomClickURLTemplates.push(e)})))}})),r.nextWrapperURL)return r}function X(e){var t=[];return e.forEach((function(e){var r={resource:null,vendor:null,browserOptional:!1,apiFramework:null,type:null,parameters:null,trackingEvents:{}},i=e.childNodes;for(var n in I.assignAttributes(e.attributes,r),i){var a=i[n];switch(a.nodeName){case"JavaScriptResource":case"ExecutableResource":r.resource=I.parseNodeText(a),I.assignAttributes(a.attributes,r);break;case"VerificationParameters":r.parameters=I.parseNodeText(a)}}var o=I.childByName(e,"TrackingEvents");o&&I.childrenByName(o,"Tracking").forEach((function(e){var t=e.getAttribute("event"),i=I.parseNodeText(e);t&&i&&(Array.isArray(r.trackingEvents[t])||(r.trackingEvents[t]=[]),r.trackingEvents[t].push(i))})),t.push(r)})),t}function K(e){var t=null,r=[];return e.some((function(e){return t=I.childByName(e,"AdVerifications")})),t&&(r=X(I.childrenByName(t,"Verification"))),r}var Q=function(){function e(){n(this,e),this._handlers=[]}return o(e,[{key:"on",value:function(e,t){if("function"!=typeof t)throw new TypeError("The handler argument must be of type Function. Received type ".concat(i(t)));if(!e)throw new TypeError("The event argument must be of type String. Received type ".concat(i(e)));return this._handlers.push({event:e,handler:t}),this}},{key:"once",value:function(e,t){return this.on(e,function(e,t,r){var i={fired:!1,wrapFn:void 0};function n(){i.fired||(e.off(t,i.wrapFn),i.fired=!0,r.bind(e).apply(void 0,arguments))}return i.wrapFn=n,n}(this,e,t))}},{key:"off",value:function(e,t){return this._handlers=this._handlers.filter((function(r){return r.event!==e||r.handler!==t})),this}},{key:"emit",value:function(e){for(var t=arguments.length,r=new Array(t>1?t-1:0),i=1;i2?r-2:0),n=2;n1&&void 0!==arguments[1]?arguments[1]:0,i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null,n=arguments.length>3&&void 0!==arguments[3]?arguments[3]:null;return new Promise((function(a,o){t.URLTemplateFilters.forEach((function(t){e=t(e)}));var s=Date.now();t.emit("VAST-resolving",{url:e,previousUrl:i,wrapperDepth:r,maxWrapperDepth:t.maxWrapperDepth,timeout:t.fetchingOptions.timeout,wrapperAd:n}),t.urlHandler.get(e,t.fetchingOptions,(function(n,l){var c=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},u=Math.round(Date.now()-s),d=Object.assign({url:e,previousUrl:i,wrapperDepth:r,error:n,duration:u},c);t.emit("VAST-resolved",d),ae(c.byteLength,u),n?o(n):a(l)}))}))}},{key:"initParsingStatus",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};this.errorURLTemplates=[],this.fetchingOptions={timeout:e.timeout||J,withCredentials:e.withCredentials},this.maxWrapperDepth=e.wrapperLimit||10,this.parsingOptions={allowMultipleAds:e.allowMultipleAds},this.remainingAds=[],this.rootErrorURLTemplates=[],this.rootURL="",this.urlHandler=e.urlHandler||e.urlhandler||re,this.vastVersion=null,ae(e.byteLength,e.requestDuration)}},{key:"getRemainingAds",value:function(e){var t=this;if(0===this.remainingAds.length)return Promise.reject(new Error("No more ads are available for the given VAST"));var r=e?L.flatten(this.remainingAds):this.remainingAds.shift();return this.errorURLTemplates=[],this.resolveAds(r,{wrapperDepth:0,url:this.rootURL}).then((function(e){return t.buildVASTResponse(e)}))}},{key:"getAndParseVAST",value:function(e){var t=this,r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return this.initParsingStatus(r),this.URLTemplateFilters.forEach((function(t){e=t(e)})),this.rootURL=e,this.fetchVAST(e).then((function(i){return r.previousUrl=e,r.isRootVAST=!0,r.url=e,t.parse(i,r).then((function(e){return t.buildVASTResponse(e)}))}))}},{key:"parseVAST",value:function(e){var t=this,r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return this.initParsingStatus(r),r.isRootVAST=!0,this.parse(e,r).then((function(e){return t.buildVASTResponse(e)}))}},{key:"buildVASTResponse",value:function(e){var t,r={ads:(t={ads:e,errorURLTemplates:this.getErrorURLTemplates(),version:this.vastVersion}).ads||[],errorURLTemplates:t.errorURLTemplates||[],version:t.version||null};return this.completeWrapperResolving(r),r}},{key:"parseVastXml",value:function(e,t){var r=t.isRootVAST,i=void 0!==r&&r,n=t.url,a=void 0===n?null:n,o=t.wrapperDepth,s=void 0===o?0:o,l=t.allowMultipleAds,c=t.followAdditionalWrappers;if(!e||!e.documentElement||"VAST"!==e.documentElement.nodeName)throw this.emit("VAST-ad-parsed",{type:"ERROR",url:a,wrapperDepth:s}),new Error("Invalid VAST XMLDocument");var u=[],d=e.documentElement.childNodes,p=e.documentElement.getAttribute("version");for(var h in i&&p&&(this.vastVersion=p),d){var v=d[h];if("Error"===v.nodeName){var f=I.parseNodeText(v);i?this.rootErrorURLTemplates.push(f):this.errorURLTemplates.push(f)}else if("Ad"===v.nodeName){if(this.vastVersion&&parseFloat(this.vastVersion)<3)l=!0;else if(!1===l&&u.length>1)break;var m=_(v,this.emit.bind(this),{allowMultipleAds:l,followAdditionalWrappers:c});m.ad?(u.push(m.ad),this.emit("VAST-ad-parsed",{type:m.type,url:a,wrapperDepth:s,adIndex:u.length-1,vastVersion:p})):this.trackVastError(this.getErrorURLTemplates(),{ERRORCODE:101})}}return u}},{key:"parse",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},r=t.url,i=void 0===r?null:r,n=t.resolveAll,a=void 0===n||n,o=t.wrapperSequence,s=void 0===o?null:o,l=t.previousUrl,c=void 0===l?null:l,u=t.wrapperDepth,d=void 0===u?0:u,p=t.isRootVAST,h=void 0!==p&&p,v=t.followAdditionalWrappers,f=t.allowMultipleAds,m=[];this.vastVersion&&parseFloat(this.vastVersion)<3&&h&&(f=!0);try{m=this.parseVastXml(e,{isRootVAST:h,url:i,wrapperDepth:d,allowMultipleAds:f,followAdditionalWrappers:v})}catch(e){return Promise.reject(e)}return 1===m.length&&null!=s&&(m[0].sequence=s),!1===a&&(this.remainingAds=I.splitVAST(m),m=this.remainingAds.shift()),this.resolveAds(m,{wrapperDepth:d,previousUrl:c,url:i})}},{key:"resolveAds",value:function(){var e=this,t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[],r=arguments.length>1?arguments[1]:void 0,i=r.wrapperDepth,n=r.previousUrl,a=r.url,o=[];return n=a,t.forEach((function(t){var r=e.resolveWrappers(t,i,n);o.push(r)})),Promise.all(o).then((function(t){var r=L.flatten(t);if(!r&&e.remainingAds.length>0){var o=e.remainingAds.shift();return e.resolveAds(o,{wrapperDepth:i,previousUrl:n,url:a})}return r}))}},{key:"resolveWrappers",value:function(e,t,r){var i=this;return new Promise((function(n){var a;if(t++,!e.nextWrapperURL)return delete e.nextWrapperURL,n(e);if(t>=i.maxWrapperDepth)return e.errorCode=302,delete e.nextWrapperURL,n(e);e.nextWrapperURL=I.resolveVastAdTagURI(e.nextWrapperURL,r),i.URLTemplateFilters.forEach((function(t){e.nextWrapperURL=t(e.nextWrapperURL)}));var o=null!==(a=i.parsingOptions.allowMultipleAds)&&void 0!==a?a:e.allowMultipleAds,s=e.sequence;i.fetchVAST(e.nextWrapperURL,t,r,e).then((function(a){return i.parse(a,{url:e.nextWrapperURL,previousUrl:r,wrapperSequence:s,wrapperDepth:t,followAdditionalWrappers:e.followAdditionalWrappers,allowMultipleAds:o}).then((function(t){if(delete e.nextWrapperURL,0===t.length)return e.creatives=[],n(e);t.forEach((function(t){t&&I.mergeWrapperAdData(t,e)})),n(t)}))})).catch((function(t){e.errorCode=301,e.errorMessage=t.message,n(e)}))}))}},{key:"completeWrapperResolving",value:function(e){if(0===e.ads.length)this.trackVastError(e.errorURLTemplates,{ERRORCODE:303});else for(var t=e.ads.length-1;t>=0;t--){var r=e.ads[t];(r.errorCode||0===r.creatives.length)&&(this.trackVastError(r.errorURLTemplates.concat(e.errorURLTemplates),{ERRORCODE:r.errorCode||303},{ERRORMESSAGE:r.errorMessage||""},{extensions:r.extensions},{system:r.system}),e.ads.splice(t,1))}}}]),r}(Q),le=null,ce={data:{},length:0,getItem:function(e){return this.data[e]},setItem:function(e,t){this.data[e]=t,this.length=Object.keys(this.data).length},removeItem:function(e){delete this.data[e],this.length=Object.keys(this.data).length},clear:function(){this.data={},this.length=0}},ue=function(){function e(){n(this,e),this.storage=this.initStorage()}return o(e,[{key:"initStorage",value:function(){if(le)return le;try{le="undefined"!=typeof window&&null!==window?window.localStorage||window.sessionStorage:null}catch(e){le=null}return le&&!this.isStorageDisabled(le)||(le=ce).clear(),le}},{key:"isStorageDisabled",value:function(e){var t="__VASTStorage__";try{if(e.setItem(t,t),e.getItem(t)!==t)return e.removeItem(t),!0}catch(e){return!0}return e.removeItem(t),!1}},{key:"getItem",value:function(e){return this.storage.getItem(e)}},{key:"setItem",value:function(e,t){return this.storage.setItem(e,t)}},{key:"removeItem",value:function(e){return this.storage.removeItem(e)}},{key:"clear",value:function(){return this.storage.clear()}}]),e}(),de=function(){function e(t,r,i){n(this,e),this.cappingFreeLunch=t||0,this.cappingMinimumTimeInterval=r||0,this.defaultOptions={withCredentials:!1,timeout:0},this.vastParser=new se,this.storage=i||new ue,void 0===this.lastSuccessfulAd&&(this.lastSuccessfulAd=0),void 0===this.totalCalls&&(this.totalCalls=0),void 0===this.totalCallsTimeout&&(this.totalCallsTimeout=0)}return o(e,[{key:"getParser",value:function(){return this.vastParser}},{key:"lastSuccessfulAd",get:function(){return this.storage.getItem("vast-client-last-successful-ad")},set:function(e){this.storage.setItem("vast-client-last-successful-ad",e)}},{key:"totalCalls",get:function(){return this.storage.getItem("vast-client-total-calls")},set:function(e){this.storage.setItem("vast-client-total-calls",e)}},{key:"totalCallsTimeout",get:function(){return this.storage.getItem("vast-client-total-calls-timeout")},set:function(e){this.storage.setItem("vast-client-total-calls-timeout",e)}},{key:"hasRemainingAds",value:function(){return this.vastParser.remainingAds.length>0}},{key:"getNextAds",value:function(e){return this.vastParser.getRemainingAds(e)}},{key:"get",value:function(e){var t=this,r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},i=Date.now();return(r=Object.assign({},this.defaultOptions,r)).hasOwnProperty("resolveAll")||(r.resolveAll=!1),this.totalCallsTimeout=t.totalCalls)return a(new Error("VAST call canceled – FreeLunch capping not reached yet ".concat(t.totalCalls,"/").concat(t.cappingFreeLunch)));var o=i-t.lastSuccessfulAd;if(o<0)t.lastSuccessfulAd=0;else if(o3&&void 0!==arguments[3]?arguments[3]:null,l=arguments.length>4&&void 0!==arguments[4]&&arguments[4];for(var c in n(this,a),(o=t.call(this)).ad=r,o.creative=i,o.variation=s,o.muted=l,o.impressed=!1,o.skippable=!1,o.trackingEvents={},o.lastPercentage=0,o._alreadyTriggeredQuartiles={},o.emitAlwaysEvents=["creativeView","start","firstQuartile","midpoint","thirdQuartile","complete","resume","pause","rewind","skip","closeLinear","close"],o.creative.trackingEvents){var u=o.creative.trackingEvents[c];o.trackingEvents[c]=u.slice(0)}return!function(e){return"linear"===e.type}(o.creative)?o._initVariationTracking():o._initLinearTracking(),e&&o.on("start",(function(){e.lastSuccessfulAd=Date.now()})),o}return o(a,[{key:"_initLinearTracking",value:function(){this.linear=!0,this.skipDelay=this.creative.skipDelay,this.setDuration(this.creative.duration),this.clickThroughURLTemplate=this.creative.videoClickThroughURLTemplate,this.clickTrackingURLTemplates=this.creative.videoClickTrackingURLTemplates}},{key:"_initVariationTracking",value:function(){if(this.linear=!1,this.skipDelay=-1,this.variation){for(var e in this.variation.trackingEvents){var t=this.variation.trackingEvents[e];this.trackingEvents[e]?this.trackingEvents[e]=this.trackingEvents[e].concat(t.slice(0)):this.trackingEvents[e]=t.slice(0)}"nonLinearAd"===this.variation.adType?(this.clickThroughURLTemplate=this.variation.nonlinearClickThroughURLTemplate,this.clickTrackingURLTemplates=this.variation.nonlinearClickTrackingURLTemplates,this.setDuration(this.variation.minSuggestedDuration)):function(e){return"companionAd"===e.adType}(this.variation)&&(this.clickThroughURLTemplate=this.variation.companionClickThroughURLTemplate,this.clickTrackingURLTemplates=this.variation.companionClickTrackingURLTemplates)}}},{key:"setDuration",value:function(e){L.isValidTimeValue(e)&&(this.assetDuration=e,this.quartiles={firstQuartile:Math.round(25*this.assetDuration)/100,midpoint:Math.round(50*this.assetDuration)/100,thirdQuartile:Math.round(75*this.assetDuration)/100})}},{key:"setProgress",value:function(e){var t=this,r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};if(L.isValidTimeValue(e)&&"object"===i(r)){var n=this.skipDelay||-1;if(-1===n||this.skippable||(n>e?this.emit("skip-countdown",n-e):(this.skippable=!0,this.emit("skip-countdown",0))),this.assetDuration>0){var a=Math.round(e/this.assetDuration*100),o=[];if(e>0){o.push("start");for(var s=this.lastPercentage;s1&&void 0!==arguments[1]?arguments[1]:{};"boolean"==typeof e&&"object"===i(t)&&(this.muted!==e&&this.track(e?"mute":"unmute",{macros:t}),this.muted=e)}},{key:"setPaused",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};"boolean"==typeof e&&"object"===i(t)&&(this.paused!==e&&this.track(e?"pause":"resume",{macros:t}),this.paused=e)}},{key:"setFullscreen",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};"boolean"==typeof e&&"object"===i(t)&&(this.fullscreen!==e&&this.track(e?"fullscreen":"exitFullscreen",{macros:t}),this.fullscreen=e)}},{key:"setExpand",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};"boolean"==typeof e&&"object"===i(t)&&(this.expanded!==e&&(this.track(e?"expand":"collapse",{macros:t}),this.track(e?"playerExpand":"playerCollapse",{macros:t})),this.expanded=e)}},{key:"setSkipDelay",value:function(e){L.isValidTimeValue(e)&&(this.skipDelay=e)}},{key:"trackImpression",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};"object"===i(e)&&(this.impressed||(this.impressed=!0,this.trackURLs(this.ad.impressionURLTemplates,e),this.track("creativeView",{macros:e})))}},{key:"trackViewableImpression",value:function(){var e=this,t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};"object"===i(t)&&this.ad.viewableImpression.forEach((function(r){e.trackURLs(r.viewable,t)}))}},{key:"trackNotViewableImpression",value:function(){var e=this,t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};"object"===i(t)&&this.ad.viewableImpression.forEach((function(r){e.trackURLs(r.notViewable,t)}))}},{key:"trackUndeterminedImpression",value:function(){var e=this,t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};"object"===i(t)&&this.ad.viewableImpression.forEach((function(r){e.trackURLs(r.viewUndetermined,t)}))}},{key:"error",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=arguments.length>1&&void 0!==arguments[1]&&arguments[1];"object"===i(e)&&"boolean"==typeof t&&this.trackURLs(this.ad.errorURLTemplates,e,{isCustomCode:t})}},{key:"errorWithCode",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]&&arguments[1];"string"==typeof e&&"boolean"==typeof t&&(this.error({ERRORCODE:e},t),console.log("The method errorWithCode is deprecated, please use vast tracker error method instead"))}},{key:"complete",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};"object"===i(e)&&this.track("complete",{macros:e})}},{key:"notUsed",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};"object"===i(e)&&(this.track("notUsed",{macros:e}),this.trackingEvents=[])}},{key:"otherAdInteraction",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};"object"===i(e)&&this.track("otherAdInteraction",{macros:e})}},{key:"acceptInvitation",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};"object"===i(e)&&this.track("acceptInvitation",{macros:e})}},{key:"adExpand",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};"object"===i(e)&&this.track("adExpand",{macros:e})}},{key:"adCollapse",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};"object"===i(e)&&this.track("adCollapse",{macros:e})}},{key:"minimize",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};"object"===i(e)&&this.track("minimize",{macros:e})}},{key:"verificationNotExecuted",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};if("string"==typeof e&&"object"===i(t)){if(!this.ad||!this.ad.adVerifications||!this.ad.adVerifications.length)throw new Error("No adVerifications provided");if(!e)throw new Error("No vendor provided, unable to find associated verificationNotExecuted");var r=this.ad.adVerifications.find((function(t){return t.vendor===e}));if(!r)throw new Error("No associated verification element found for vendor: ".concat(e));var n=r.trackingEvents;if(n&&n.verificationNotExecuted){var a=n.verificationNotExecuted;this.trackURLs(a,t),this.emit("verificationNotExecuted",{trackingURLTemplates:a})}}}},{key:"overlayViewDuration",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};"string"==typeof e&&"object"===i(t)&&(t.ADPLAYHEAD=e,this.track("overlayViewDuration",{macros:t}))}},{key:"close",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};"object"===i(e)&&this.track(this.linear?"closeLinear":"close",{macros:e})}},{key:"skip",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};"object"===i(e)&&this.track("skip",{macros:e})}},{key:"load",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};"object"===i(e)&&this.track("loaded",{macros:e})}},{key:"click",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null,t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};if((null===e||"string"==typeof e)&&"object"===i(t)){this.clickTrackingURLTemplates&&this.clickTrackingURLTemplates.length&&this.trackURLs(this.clickTrackingURLTemplates,t);var n=this.clickThroughURLTemplate||e,a=r({},t);if(n){this.progress&&(a.ADPLAYHEAD=this.progressFormatted());var o=L.resolveURLTemplates([n],a)[0];this.emit("clickthrough",o)}}}},{key:"track",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},r=t.macros,n=void 0===r?{}:r,a=t.once,o=void 0!==a&&a;if("object"===i(n)){"closeLinear"===e&&!this.trackingEvents[e]&&this.trackingEvents.close&&(e="close");var s=this.trackingEvents[e],l=this.emitAlwaysEvents.indexOf(e)>-1;s?(this.emit(e,{trackingURLTemplates:s}),this.trackURLs(s,n)):l&&this.emit(e,null),o&&(delete this.trackingEvents[e],l&&this.emitAlwaysEvents.splice(this.emitAlwaysEvents.indexOf(e),1))}}},{key:"trackURLs",value:function(e){var t,i=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},a=L.filterValidUrlTemplates(e),o=r({},i);this.linear&&(this.creative&&this.creative.mediaFiles&&this.creative.mediaFiles[0]&&this.creative.mediaFiles[0].fileURL&&(o.ASSETURI=this.creative.mediaFiles[0].fileURL),this.progress&&(o.ADPLAYHEAD=this.progressFormatted())),null!==(t=this.creative)&&void 0!==t&&null!==(t=t.universalAdIds)&&void 0!==t&&t.length&&(o.UNIVERSALADID=this.creative.universalAdIds.map((function(e){return e.idRegistry.concat(" ",e.value)})).join(",")),this.ad&&(this.ad.sequence&&(o.PODSEQUENCE=this.ad.sequence),this.ad.adType&&(o.ADTYPE=this.ad.adType),this.ad.adServingId&&(o.ADSERVINGID=this.ad.adServingId),this.ad.categories&&this.ad.categories.length&&(o.ADCATEGORIES=this.ad.categories.map((function(e){return e.value})).join(",")),this.ad.blockedAdCategories&&this.ad.blockedAdCategories.length&&(o.BLOCKEDADCATEGORIES=this.ad.blockedAdCategories)),L.track(a,o,n)}},{key:"convertToTimecode",value:function(e){if(!L.isValidTimeValue(e))return"";var t=1e3*e,r=Math.floor(t/36e5),i=Math.floor(t/6e4%60),n=Math.floor(t/1e3%60),a=Math.floor(t%1e3);return"".concat(L.addLeadingZeros(r,2),":").concat(L.addLeadingZeros(i,2),":").concat(L.addLeadingZeros(n,2),".").concat(L.addLeadingZeros(a,3))}},{key:"progressFormatted",value:function(){return this.convertToTimecode(this.progress)}}]),a}(Q);return e.VASTClient=de,e.VASTParser=se,e.VASTTracker=pe,e.parseDuration=C,Object.defineProperty(e,"__esModule",{value:!0}),e}({}); From 83fae5b42532fc2e2a9f71f79b948ab61f102ac4 Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Fri, 8 Sep 2023 12:23:03 +0200 Subject: [PATCH 48/95] wip/upgrating urlhandler --- src/fetcher.js | 61 ++++++------ src/urlhandlers/xhr_url_handler.js | 151 ++++++++++++++++++++--------- 2 files changed, 133 insertions(+), 79 deletions(-) diff --git a/src/fetcher.js b/src/fetcher.js index 261b523f..61cd1466 100644 --- a/src/fetcher.js +++ b/src/fetcher.js @@ -1,5 +1,5 @@ import { updateEstimatedBitrate } from './parser/bitrate'; -import { urlHandler } from './url_handler'; +import { urlHandler } from './urlhandlers/xhr_url_handler'; import { DEFAULT_TIMEOUT } from './urlhandlers/consts'; /** @@ -76,7 +76,7 @@ export class Fetcher { previousUrl = null, wrapperAd = null, }) { - return new Promise((resolve, reject) => { + return new Promise(async (resolve, reject) => { const timeBeforeGet = Date.now(); // Process url with defined filter @@ -84,39 +84,38 @@ export class Fetcher { url = filter(url); }); - emitter('VAST-resolving', { - url, - previousUrl, - wrapperDepth, - maxWrapperDepth, - timeout: this.fetchingOptions.timeout, - wrapperAd, - }); - - this.urlHandler.get( - url, - this.fetchingOptions, - (error, xml, details = {}) => { - const requestDuration = Math.round(Date.now() - timeBeforeGet); + try { + emitter('VAST-resolving', { + url, + previousUrl, + wrapperDepth, + maxWrapperDepth: maxWrapperDepth, + timeout: this.fetchingOptions.timeout, + wrapperAd, + }); - emitter('VAST-resolved', { - url, - previousUrl, - wrapperDepth, - error, - duration: requestDuration, - ...details, - }); + let data = await this.urlHandler.get(url, this.fetchingOptions); + const requestDuration = Math.round(Date.now() - timeBeforeGet); - updateEstimatedBitrate(details.byteLength, requestDuration); + emitter('VAST-resolved', { + url, + previousUrl, + wrapperDepth, + error: data?.error || null, + duration: requestDuration, + statusCode: data?.statusCode || null, + ...data?.details, + }); + updateEstimatedBitrate(data?.details?.byteLength, requestDuration); - if (error) { - reject(error); - } else { - resolve(xml); - } + if (data.error) { + reject(data.error); + } else { + resolve(data.xml); } - ); + } catch (error) { + console.error(error); + } }); } } diff --git a/src/urlhandlers/xhr_url_handler.js b/src/urlhandlers/xhr_url_handler.js index 10b5fad4..2a098631 100644 --- a/src/urlhandlers/xhr_url_handler.js +++ b/src/urlhandlers/xhr_url_handler.js @@ -1,67 +1,122 @@ import { DEFAULT_TIMEOUT } from './consts'; -function xhr() { - try { - const request = new window.XMLHttpRequest(); - if ('withCredentials' in request) { - // check CORS support - return request; - } - return null; - } catch (err) { - return null; - } -} +// function supported() { +// return !!xhr(); +// } -function supported() { - return !!xhr(); -} +// // si succes +// function handleLoad(request, cb) { +// if (request.status === 200) { +// cb(null, request.responseXML, { +// byteLength: request.response.length, +// statusCode: request.status, +// }); +// } else { +// handleFail(request, cb, false); +// } +// } -function handleLoad(request, cb) { - if (request.status === 200) { - cb(null, request.responseXML, { - byteLength: request.response.length, - statusCode: request.status, - }); - } else { - handleFail(request, cb, false); - } -} +// // si foireux +// function handleFail(request, cb, isTimeout) { +// const statusCode = !isTimeout ? request.status : 408; // Request timeout +// const msg = isTimeout +// ? `XHRURLHandler: Request timed out after ${request.timeout} ms (${statusCode})` +// : `XHRURLHandler: ${request.statusText} (${statusCode})`; + +// cb(new Error(msg), null, { statusCode }); +// } + +// function get(url, options, cb) { +// if (window.location.protocol === 'https:' && url.indexOf('http://') === 0) { +// return cb(new Error('XHRURLHandler: Cannot go from HTTPS to HTTP.')); +// } + +// try { +// const request = xhr(); -function handleFail(request, cb, isTimeout) { - const statusCode = !isTimeout ? request.status : 408; // Request timeout - const msg = isTimeout - ? `XHRURLHandler: Request timed out after ${request.timeout} ms (${statusCode})` - : `XHRURLHandler: ${request.statusText} (${statusCode})`; +// request.open('GET', url); +// request.timeout = options.timeout || DEFAULT_TIMEOUT; +// request.withCredentials = options.withCredentials || false; +// request.overrideMimeType && request.overrideMimeType('text/xml'); +// request.onload = () => handleLoad(request, cb); +// request.onerror = () => handleFail(request, cb, false); +// request.onabort = () => handleFail(request, cb, false); +// request.ontimeout = () => handleFail(request, cb, true); - cb(new Error(msg), null, { statusCode }); +// request.send(); +// } catch (error) { +// cb(new Error('XHRURLHandler: Unexpected error')); +// } +// } + +function isBrowserEnvironment() { + return typeof window !== 'undefined'; } -function get(url, options, cb) { - if (window.location.protocol === 'https:' && url.indexOf('http://') === 0) { - return cb(new Error('XHRURLHandler: Cannot go from HTTPS to HTTP.')); +/** + * handlingResponse + * @param {*} request + * @returns + */ +async function handleResponse(request) { + let details = {}; + const textXml = await request.text(); + + details.byteLength = textXml.length; + details.statusCode = request.status; + + const parser = isBrowserEnvironment() ? new DOMParser() : new nodeDOMParser(); + const xml = parser.parseFromString(textXml, 'text/xml'); + return { xml, details }; +} +/** + * Error Handling + * @param {*} request + * @returns + */ +function handleError(request) { + if (isBrowserEnvironment()) { + if ( + window.location.protocol === 'http:' && + request.url.indexOf('https://') === 0 + ) { + return 'XHRURLHandler: Cannot go from HTTPS to HTTP.'; + } } + if (request.status !== 200 || !request.ok) { + return `XHRURLHandler: ${request.statusText} (${request.status})`; + } + return null; +} +async function get(url, options) { try { - const request = xhr(); + ///// timeout + const controller = new AbortController(); + const timer = setTimeout(() => { + controller.abort(); + throw new Error( + `XHRURLHandler: Request timed out after ${options.timeout} ms (408)` + ); + }, options.timeout); + ///// - request.open('GET', url); - request.timeout = options.timeout || DEFAULT_TIMEOUT; - request.withCredentials = options.withCredentials || false; - request.overrideMimeType && request.overrideMimeType('text/xml'); + const request = await fetch(url, { ...options, signal: controller.signal }); + clearTimeout(timer); - request.onload = () => handleLoad(request, cb); - request.onerror = () => handleFail(request, cb, false); - request.onabort = () => handleFail(request, cb, false); - request.ontimeout = () => handleFail(request, cb, true); - - request.send(); + const error = handleError(request); + if (error) { + return { error: new Error(error) }; + } + return handleResponse(request); } catch (error) { - cb(new Error('XHRURLHandler: Unexpected error')); + return { + error, + statusCode: error.name === 'AbortError' ? 408 : undefined, + }; } } -export const XHRURLHandler = { +export const urlHandler = { get, - supported, }; From 78eb7511c37018a3a24a518d08e31d77f7d4d381 Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Wed, 13 Sep 2023 17:59:37 +0200 Subject: [PATCH 49/95] [tools] modifying rollup configuration for node --- rollup.config.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/rollup.config.js b/rollup.config.js index 6cca005d..28d5d40b 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -30,14 +30,6 @@ const createNodeConfig = (filePath, minifiedOutput, notMinifiedOutput) => { }, ], plugins: [ - alias({ - entries: [ - { - find: './urlhandlers/mock_node_url_handler', - replacement: './urlhandlers/node_url_handler', - }, - ], - }), resolve({ preferBuiltins: true, }), From 554792cb81335270929354cac2b44757ef3b9963 Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Wed, 13 Sep 2023 18:05:36 +0200 Subject: [PATCH 50/95] upgrade urlhandler from XMLHttpRequest to fetch --- src/urlhandlers/xhr_url_handler.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/urlhandlers/xhr_url_handler.js b/src/urlhandlers/xhr_url_handler.js index 2a098631..5d1475a9 100644 --- a/src/urlhandlers/xhr_url_handler.js +++ b/src/urlhandlers/xhr_url_handler.js @@ -101,18 +101,22 @@ async function get(url, options) { }, options.timeout); ///// - const request = await fetch(url, { ...options, signal: controller.signal }); + const request = await fetch(url, { + ...options, + signal: controller.signal, + credentials: options.withCredentials ? 'include' : 'omit', + }); clearTimeout(timer); const error = handleError(request); if (error) { - return { error: new Error(error) }; + return { error: new Error(error), statusCode: request.status }; } return handleResponse(request); } catch (error) { return { error, - statusCode: error.name === 'AbortError' ? 408 : undefined, + statusCode: error.name === 'AbortError' ? 408 : null, }; } } From fa2fe1ef53aeba69c78771791f93c378659121a5 Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Wed, 13 Sep 2023 18:06:26 +0200 Subject: [PATCH 51/95] [test] updating tests for new fetching method --- spec/fetcher.spec.js | 17 +++--- spec/vast_client.spec.js | 120 +++++++++++++++++++++------------------ 2 files changed, 72 insertions(+), 65 deletions(-) diff --git a/spec/fetcher.spec.js b/spec/fetcher.spec.js index 24429f78..32dde438 100644 --- a/spec/fetcher.spec.js +++ b/spec/fetcher.spec.js @@ -1,7 +1,7 @@ import { Fetcher } from '../src/fetcher'; import { VASTParser } from '../src/parser/vast_parser'; import * as Bitrate from '../src/parser/bitrate'; -import { urlHandler } from '../src/url_handler'; +import { urlHandler } from '../src/urlhandlers/xhr_url_handler'; import { expect } from '@jest/globals'; import { getNodesFromXml } from './utils/utils'; @@ -10,15 +10,15 @@ describe('Fetcher', () => { const xml = getNodesFromXml(''); const urlHandlerSuccess = { - get: (url, options, cb) => { - cb(null, xml, { byteLength: 1234, statusCode: 200 }); - }, + get: (url, option) => + new Promise((resolve, _) => + resolve({ xml, details: { byteLength: 1234, statusCode: 200 } }) + ), }; const urlHandlerFailure = { - get: (url, options, cb) => { - cb(new Error('timeout'), null, { statusCode: 408 }); - }, + get: (url, option) => + new Promise((_, reject) => reject({ error: new Error('error') })), }; beforeEach(() => { @@ -47,8 +47,7 @@ describe('Fetcher', () => { .then(() => { expect(urlHandlerSpy).toHaveBeenCalledWith( expectedUrl, - expect.anything(), - expect.anything() + expect.any(Object) ); }); }); diff --git a/spec/vast_client.spec.js b/spec/vast_client.spec.js index 08f64d89..7c2acd11 100644 --- a/spec/vast_client.spec.js +++ b/spec/vast_client.spec.js @@ -1,12 +1,27 @@ import { VASTClient } from '../src/vast_client'; -import { nodeURLHandler } from '../src/urlhandlers/node_url_handler'; -import { urlFor } from './utils/utils'; - -const wrapperMultipleAdsVastUrl = urlFor('wrapper-multiple-ads.xml'); -const emptyVastUrl = urlFor('empty-no-ad.xml'); +import 'regenerator-runtime/runtime'; +import { readFile } from 'fs/promises'; +import { afterAll } from '@jest/globals'; + +const wrapperMultipleAdsVastUrl = './spec/samples/wrapper-multiple-ads.xml'; +const emptyVastUrl = './spec/samples/empty-no-ad.xml'; + +const nodeUrlHandler = { + get: async (file) => { + try { + const response = await readFile(file, 'utf-8'); + return { + xml: new DOMParser().parseFromString(response, 'text/xml'), + details: { ByteLength: 1234, statusCode: 200 }, + }; + } catch (err) { + return { error: err, statusCode: 400 }; + } + }, +}; const options = { - urlhandler: nodeURLHandler, + urlHandler: nodeUrlHandler, withCredentials: true, }; @@ -14,6 +29,10 @@ describe('VASTClient', () => { describe('when cappingFreeLunch is set to 1', () => { const VastClient = new VASTClient(1, undefined, undefined); + afterAll(() => { + VastClient.storage.clear(); + }); + it('ignores the first call and updates total calls', () => { return expect( VastClient.get(wrapperMultipleAdsVastUrl, options) @@ -22,47 +41,40 @@ describe('VASTClient', () => { ); }); - it('succeeds with the second call and updates total calls', () => { - return VastClient.get(wrapperMultipleAdsVastUrl, options).then((res) => { - expect(res).toEqual({ - ads: expect.any(Array), - errorURLTemplates: [], - version: '4.3', - }); - expect(VastClient.totalCalls).toBe('2'); + it('succeeds with the second call and updates total calls', async () => { + const res = await VastClient.get(wrapperMultipleAdsVastUrl, options); + expect(res).toEqual({ + ads: expect.any(Array), + errorURLTemplates: [], + version: '4.3', }); + expect(VastClient.totalCalls).toBe('2'); }); }); describe('when cappingMinimumTimeInterval is set to 30 seconds', () => { const VastClient = new VASTClient(0, 30000, undefined); - it('succeeds with the first call and updates total calls', () => { - return VastClient.get(wrapperMultipleAdsVastUrl, options).then((res) => { - expect(res).toEqual({ - ads: expect.any(Array), - errorURLTemplates: [], - version: '4.3', - }); - expect(VastClient.totalCalls).toBe('3'); + it('succeeds with the first call and updates total calls', async () => { + const res = await VastClient.get(wrapperMultipleAdsVastUrl, options); + expect(res).toEqual({ + ads: expect.any(Array), + errorURLTemplates: [], + version: '4.3', }); + expect(VastClient.totalCalls).toBe('1'); }); - it('should ignore the second call sent under 30 seconds', () => { - return VastClient.get(wrapperMultipleAdsVastUrl, options).then(() => { + it('should ignore the second call sent under 30 seconds', async () => { + try { + await VastClient.get(wrapperMultipleAdsVastUrl, options); jest.spyOn(Date, 'now').mockImplementation(() => 0); - return VastClient.get(wrapperMultipleAdsVastUrl, options) - .then(() => { - expect(true).toBeFalsy(); - }) - .catch((e) => { - expect(e).toEqual( - new Error( - 'VAST call canceled – (30000)ms minimum interval reached' - ) - ); - }); - }); + await VastClient.get(wrapperMultipleAdsVastUrl, options); + } catch (error) { + expect(error).toEqual( + new Error('VAST call canceled – (30000)ms minimum interval reached') + ); + } }); }); @@ -124,16 +136,16 @@ describe('VASTClient', () => { ]); }); - it('handles empty ads correctly', () => { - return VastClient.get(emptyVastUrl, optionsWithNoResolveAll).then( - (result) => { - expect(result).toEqual({ - ads: [], - errorURLTemplates: ['http://example.com/empty-no-ad'], - version: '4.3', - }); - } + it('handles empty ads correctly', async () => { + const response = await VastClient.get( + emptyVastUrl, + optionsWithNoResolveAll ); + expect(response).toEqual({ + ads: [], + errorURLTemplates: ['http://example.com/empty-no-ad'], + version: '4.3', + }); }); it('returns true for hasRemainingAds', () => { @@ -156,15 +168,13 @@ describe('VASTClient', () => { wrapperMultipleAdsVastUrl, optionsWithNoResolveAll ); - - const res = await VastClient.getNextAds(false); - - expect(res).toEqual({ + const response = await VastClient.getNextAds(false); + expect(response).toEqual({ ads: expect.any(Array), errorURLTemplates: [], version: '4.3', }); - expect(res.ads).toHaveLength(2); + expect(response.ads).toHaveLength(2); }); }); }); @@ -172,12 +182,10 @@ describe('VASTClient', () => { describe('with resolveAll set to true', () => { const optionsWithResolveAll = { ...options, resolveAll: true }; let res; - beforeEach((done) => { - VastClient.get(wrapperMultipleAdsVastUrl, optionsWithResolveAll).then( - (results) => { - res = results; - done(); - } + beforeEach(async () => { + res = await VastClient.get( + wrapperMultipleAdsVastUrl, + optionsWithResolveAll ); }); it('returns all ads parsed', () => { From 59647657a29e960ee9708fd3019599ef7a6f1423 Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Mon, 18 Sep 2023 18:27:13 +0200 Subject: [PATCH 52/95] removing file related to node urlhandler and modifications for new urlhandler --- src/url_handler.js | 27 ------ src/urlhandlers/mock_node_url_handler.js | 12 --- src/urlhandlers/node_url_handler.js | 66 -------------- src/urlhandlers/xhr_url_handler.js | 110 ++++++++--------------- 4 files changed, 35 insertions(+), 180 deletions(-) delete mode 100644 src/url_handler.js delete mode 100644 src/urlhandlers/mock_node_url_handler.js delete mode 100644 src/urlhandlers/node_url_handler.js diff --git a/src/url_handler.js b/src/url_handler.js deleted file mode 100644 index 7cbce34a..00000000 --- a/src/url_handler.js +++ /dev/null @@ -1,27 +0,0 @@ -import { nodeURLHandler } from './urlhandlers/mock_node_url_handler'; -import { XHRURLHandler } from './urlhandlers/xhr_url_handler'; - -function get(url, options, cb) { - // Allow skip of the options param - if (!cb) { - if (typeof options === 'function') { - cb = options; - } - options = {}; - } - - if (typeof window === 'undefined' || window === null) { - return nodeURLHandler.get(url, options, cb); - } else if (XHRURLHandler.supported()) { - return XHRURLHandler.get(url, options, cb); - } - return cb( - new Error( - 'Current context is not supported by any of the default URLHandlers. Please provide a custom URLHandler' - ) - ); -} - -export const urlHandler = { - get, -}; diff --git a/src/urlhandlers/mock_node_url_handler.js b/src/urlhandlers/mock_node_url_handler.js deleted file mode 100644 index 8f01ccd6..00000000 --- a/src/urlhandlers/mock_node_url_handler.js +++ /dev/null @@ -1,12 +0,0 @@ -// This mock module is loaded in stead of the original NodeURLHandler module -// when bundling the library for environments which are not node. -// This allows us to avoid bundling useless node components and have a smaller build. -function get(url, options, cb) { - cb( - new Error('Please bundle the library for node to use the node urlHandler') - ); -} - -export const nodeURLHandler = { - get, -}; diff --git a/src/urlhandlers/node_url_handler.js b/src/urlhandlers/node_url_handler.js deleted file mode 100644 index d9cdb408..00000000 --- a/src/urlhandlers/node_url_handler.js +++ /dev/null @@ -1,66 +0,0 @@ -import { DEFAULT_TIMEOUT } from './consts'; - -const uri = require('url'); -const fs = require('fs'); -const http = require('http'); -const https = require('https'); -const DOMParser = require('@xmldom/xmldom').DOMParser; - -function get(url, options, cb) { - url = uri.parse(url); - const httpModule = url.protocol === 'https:' ? https : http; - if (url.protocol === 'file:') { - fs.readFile(uri.fileURLToPath(url.href), 'utf8', function (err, data) { - if (err) { - return cb(err); - } - const xml = new DOMParser().parseFromString(data); - cb(null, xml, { byteLength: Buffer.from(data).byteLength }); - }); - } else { - let timeoutId; - let data = ''; - const timeout = options.timeout || DEFAULT_TIMEOUT; - - const req = httpModule.get(url.href, function (res) { - res.on('data', function (chunk) { - data += chunk; - clearTimeout(timeoutId); - timeoutId = startTimeout(); - }); - res.on('end', function () { - clearTimeout(timeoutId); - const xml = new DOMParser().parseFromString(data); - cb(null, xml, { - byteLength: Buffer.from(data).byteLength, - statusCode: res.statusCode, - }); - }); - }); - - req.on('error', function (err) { - clearTimeout(timeoutId); - - if (req.aborted) { - cb( - new Error(`NodeURLHandler: Request timed out after ${timeout} ms.`), - null, - { - statusCode: 408, // Request timeout - } - ); - } else { - cb(err); - } - }); - - const startTimeout = () => { - return setTimeout(() => req.abort(), timeout); - }; - timeoutId = startTimeout(); - } -} - -export const nodeURLHandler = { - get, -}; diff --git a/src/urlhandlers/xhr_url_handler.js b/src/urlhandlers/xhr_url_handler.js index 5d1475a9..50212a8d 100644 --- a/src/urlhandlers/xhr_url_handler.js +++ b/src/urlhandlers/xhr_url_handler.js @@ -1,118 +1,78 @@ import { DEFAULT_TIMEOUT } from './consts'; +const nodeDOMParser = require('@xmldom/xmldom').DOMParser; -// function supported() { -// return !!xhr(); -// } - -// // si succes -// function handleLoad(request, cb) { -// if (request.status === 200) { -// cb(null, request.responseXML, { -// byteLength: request.response.length, -// statusCode: request.status, -// }); -// } else { -// handleFail(request, cb, false); -// } -// } - -// // si foireux -// function handleFail(request, cb, isTimeout) { -// const statusCode = !isTimeout ? request.status : 408; // Request timeout -// const msg = isTimeout -// ? `XHRURLHandler: Request timed out after ${request.timeout} ms (${statusCode})` -// : `XHRURLHandler: ${request.statusText} (${statusCode})`; - -// cb(new Error(msg), null, { statusCode }); -// } - -// function get(url, options, cb) { -// if (window.location.protocol === 'https:' && url.indexOf('http://') === 0) { -// return cb(new Error('XHRURLHandler: Cannot go from HTTPS to HTTP.')); -// } - -// try { -// const request = xhr(); - -// request.open('GET', url); -// request.timeout = options.timeout || DEFAULT_TIMEOUT; -// request.withCredentials = options.withCredentials || false; -// request.overrideMimeType && request.overrideMimeType('text/xml'); -// request.onload = () => handleLoad(request, cb); -// request.onerror = () => handleFail(request, cb, false); -// request.onabort = () => handleFail(request, cb, false); -// request.ontimeout = () => handleFail(request, cb, true); - -// request.send(); -// } catch (error) { -// cb(new Error('XHRURLHandler: Unexpected error')); -// } -// } - +/** + * Check if we are in a browser environment + * @returns {Boolean} + */ function isBrowserEnvironment() { return typeof window !== 'undefined'; } /** - * handlingResponse - * @param {*} request - * @returns + * Return an object containing an XML document. + * in addition to the byteLength and the statusCode of the response. + * @param {Object} response the response of the fetch request. + * @returns {Object} */ -async function handleResponse(request) { - let details = {}; - const textXml = await request.text(); - - details.byteLength = textXml.length; - details.statusCode = request.status; +async function handleResponse(response) { + const textXml = await response.text(); const parser = isBrowserEnvironment() ? new DOMParser() : new nodeDOMParser(); const xml = parser.parseFromString(textXml, 'text/xml'); - return { xml, details }; + return { + xml, + details: { byteLength: textXml.length, statusCode: response.status }, + }; } + /** - * Error Handling - * @param {*} request - * @returns + * Return a custom message if an error occured + * @param {Object} response The response of fetch request + * @returns {String | null} */ -function handleError(request) { +function handleError(response) { if (isBrowserEnvironment()) { if ( window.location.protocol === 'http:' && - request.url.indexOf('https://') === 0 + response.url.indexOf('https://') === 0 ) { - return 'XHRURLHandler: Cannot go from HTTPS to HTTP.'; + return 'URLHandler: Cannot go from HTTPS to HTTP.'; } } - if (request.status !== 200 || !request.ok) { - return `XHRURLHandler: ${request.statusText} (${request.status})`; + + if (response.status !== 200 || !response.ok) { + return `URLHandler: ${response.statusText} (${response.status})`; } return null; } async function get(url, options) { try { - ///// timeout + // fetch does not have "timeout" option, we are using AbortController + // to abort the request if it reach the timeout. const controller = new AbortController(); const timer = setTimeout(() => { controller.abort(); throw new Error( - `XHRURLHandler: Request timed out after ${options.timeout} ms (408)` + `URLHandler: Request timed out after ${ + options.timeout || DEFAULT_TIMEOUT + } ms (408)` ); - }, options.timeout); - ///// + }, options.timeout || DEFAULT_TIMEOUT); - const request = await fetch(url, { + const response = await fetch(url, { ...options, signal: controller.signal, credentials: options.withCredentials ? 'include' : 'omit', }); clearTimeout(timer); - const error = handleError(request); + const error = handleError(response); if (error) { - return { error: new Error(error), statusCode: request.status }; + return { error: new Error(error), statusCode: response.status }; } - return handleResponse(request); + return handleResponse(response); } catch (error) { return { error, From 4b7f2a621664559a98e143da0448f59f678ecead Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Mon, 18 Sep 2023 18:41:03 +0200 Subject: [PATCH 53/95] [test] modifying tests for new urlHandler --- spec/utils/utils.js | 9 - spec/vast_client.spec.js | 2 +- spec/vast_parser.spec.js | 405 +++++++++++++++++++-------------------- 3 files changed, 196 insertions(+), 220 deletions(-) diff --git a/spec/utils/utils.js b/spec/utils/utils.js index 15b41c46..61c1e484 100644 --- a/spec/utils/utils.js +++ b/spec/utils/utils.js @@ -1,5 +1,4 @@ import path from 'path'; -import { nodeURLHandler } from '../../src/urlhandlers/node_url_handler'; export const getNodesFromXml = (xmlString) => { const parser = new DOMParser(); @@ -12,11 +11,3 @@ export const urlFor = (fileName) => { .replace(/\\/g, '/'); return `file:///${filePath}`; }; - -export const fetchXml = (url, options = {}) => { - return new Promise((resolve) => { - nodeURLHandler.get(url, options, (error, xml, details) => { - resolve({ error, xml, details }); - }); - }); -}; diff --git a/spec/vast_client.spec.js b/spec/vast_client.spec.js index 7c2acd11..91dc1bbe 100644 --- a/spec/vast_client.spec.js +++ b/spec/vast_client.spec.js @@ -12,7 +12,7 @@ const nodeUrlHandler = { const response = await readFile(file, 'utf-8'); return { xml: new DOMParser().parseFromString(response, 'text/xml'), - details: { ByteLength: 1234, statusCode: 200 }, + details: { byteLength: 1234, statusCode: 200 }, }; } catch (err) { return { error: err, statusCode: 400 }; diff --git a/spec/vast_parser.spec.js b/spec/vast_parser.spec.js index fbcf18af..244f9c34 100644 --- a/spec/vast_parser.spec.js +++ b/spec/vast_parser.spec.js @@ -1,22 +1,36 @@ import { VASTParser } from '../src/parser/vast_parser'; -import { nodeURLHandler } from '../src/urlhandlers/node_url_handler'; -import { urlFor, fetchXml, getNodesFromXml } from './utils/utils'; +import { urlFor } from './utils/utils'; import { util } from '../src/util/util'; import { parserUtils } from '../src/parser/parser_utils'; import * as Bitrate from '../src/parser/bitrate'; import { Fetcher } from '../src/fetcher'; import { linearAd } from './samples/linear_ads'; import { VASTClient } from '../src/vast_client'; +import { readFile } from 'fs/promises'; const xml = getNodesFromXml(`${linearAd}`, 'text/xml'); const urlHandlerSuccess = { - get: (url, options, cb) => { - cb(null, xml, { byteLength: 1234, statusCode: 200 }); - }, + get: (url, option) => + new Promise((resolve, _) => + resolve({ xml, details: { byteLength: 1234, statusCode: 200 } }) + ), }; + const urlHandlerFailure = { - get: (url, options, cb) => { - cb(new Error('timeout'), null, { statusCode: 408 }); + get: (url, option) => + new Promise((_, reject) => reject({ error: new Error('error') })), +}; +const nodeUrlHandler = { + get: async (file) => { + try { + const response = await readFile(file, 'utf-8'); + return { + xml: new DOMParser().parseFromString(response, 'text/xml'), + details: { byteLength: 1234, statusCode: 200 }, + }; + } catch (err) { + return { error: err, statusCode: 400 }; + } }, }; @@ -56,19 +70,13 @@ describe('VASTParser', () => { let fetcher; let inlineXml, invalidXml, errorXml, wrapperXml; - beforeAll((done) => { - return Promise.all([ - fetchXml(inlineInvalidVastUrl), - fetchXml(inlineSampleVastUrl), - fetchXml(vastWithErrorUrl), - fetchXml(wrapperWithAttributesVastUrl), - ]).then(([invalid, inline, error, wrapper]) => { - invalidXml = invalid.xml; - inlineXml = inline.xml; - errorXml = error.xml; - wrapperXml = wrapper.xml; - done(); - }); + beforeAll(async () => { + inlineXml = await nodeUrlHandler.get('./spec/samples/sample.xml'); + errorXml = await nodeUrlHandler.get('./spec/samples/empty-no-ad.xml'); + wrapperXml = await nodeUrlHandler.get( + './spec/samples/wrapper-attributes-multiple-ads.xml' + ); + invalidXml = await nodeUrlHandler.get('./spec/samples/invalid-xmlfile.xml'); }); beforeEach(() => { @@ -123,7 +131,7 @@ describe('VASTParser', () => { describe('parseVastXml', () => { it('handles invalid XML vast', () => { try { - VastParser.parseVastXml(invalidXml, { + VastParser.parseVastXml(invalidXml.xml, { isRootVAST: true, url: inlineInvalidVastUrl, wrapperDepth: 0, @@ -140,7 +148,7 @@ describe('VASTParser', () => { }); it('gets vast version from original vast', () => { - VastParser.parseVastXml(inlineXml, { + VastParser.parseVastXml(inlineXml.xml, { isRootVAST: true, url: inlineSampleVastUrl, wrapperDepth: 0, @@ -159,14 +167,14 @@ describe('VASTParser', () => { it('handles Error tag for not root VAST', () => { VastParser.initParsingStatus(); - VastParser.parseVastXml(errorXml, { isRootVAST: false }); + VastParser.parseVastXml(errorXml.xml, { isRootVAST: false }); expect(VastParser.errorURLTemplates).toEqual([ 'http://example.com/empty-no-ad', ]); }); it('handles Ad tag', () => { - const ads = VastParser.parseVastXml(inlineXml, { + const ads = VastParser.parseVastXml(inlineXml.xml, { isRootVAST: true, url: inlineSampleVastUrl, wrapperDepth: 0, @@ -174,29 +182,20 @@ describe('VASTParser', () => { }); expect(ads).toHaveLength(2); - expect(VastParser.emit).toHaveBeenCalledTimes(2); - expect(VastParser.emit.mock.calls).toEqual([ - [ - 'VAST-ad-parsed', - { - adIndex: 0, - type: 'INLINE', - url: inlineSampleVastUrl, - wrapperDepth: 0, - vastVersion: '4.3', - }, - ], - [ - 'VAST-ad-parsed', - { - adIndex: 1, - type: 'INLINE', - url: inlineSampleVastUrl, - wrapperDepth: 0, - vastVersion: '4.3', - }, - ], - ]); + expect(VastParser.emit).toHaveBeenCalledWith('VAST-ad-parsed', { + adIndex: 0, + type: 'INLINE', + url: inlineSampleVastUrl, + wrapperDepth: 0, + vastVersion: '2.1', + }); + expect(VastParser.emit).toHaveBeenCalledWith('VAST-ad-parsed', { + adIndex: 1, + type: 'INLINE', + url: inlineSampleVastUrl, + wrapperDepth: 0, + vastVersion: '2.1', + }); }); }); @@ -204,7 +203,7 @@ describe('VASTParser', () => { it('calls parseVastXml with passed options', () => { jest.spyOn(VastParser, 'parseVastXml'); - return VastParser.parse(inlineXml, { + return VastParser.parse(inlineXml.xml, { url: inlineSampleVastUrl, previousUrl: wrapperBVastUrl, resolveAll: true, @@ -212,7 +211,7 @@ describe('VASTParser', () => { wrapperDepth: 0, isRootVAST: true, }).then(() => { - expect(VastParser.parseVastXml).toHaveBeenCalledWith(inlineXml, { + expect(VastParser.parseVastXml).toHaveBeenCalledWith(inlineXml.xml, { isRootVAST: true, url: inlineSampleVastUrl, wrapperDepth: 0, @@ -223,7 +222,7 @@ describe('VASTParser', () => { it('rejects if parsing xml failed', () => { jest.spyOn(VastParser, 'parseVastXml'); - return VastParser.parse(invalidXml, { + return VastParser.parse(invalidXml.xml, { url: inlineInvalidVastUrl, previousUrl: wrapperBVastUrl, resolveAll: true, @@ -240,7 +239,7 @@ describe('VASTParser', () => { }); it('resolves first ad and saves remaining ads if resolveAll is false', () => { - return VastParser.parse(inlineXml, { + return VastParser.parse(inlineXml.xml, { url: inlineSampleVastUrl, previousUrl: wrapperBVastUrl, resolveAll: false, @@ -255,7 +254,7 @@ describe('VASTParser', () => { }); it('parse wrapper sub elements based on allowMultipleAds and followAdditionalWrappers values', () => { - return VastParser.parse(wrapperXml).then((ads) => { + return VastParser.parse(wrapperXml.xml).then((ads) => { expect(ads.length).toEqual(4); }); }); @@ -349,48 +348,7 @@ describe('VASTParser', () => { let trackCalls = null; let dataTriggered = null; const options = { - urlHandler: nodeURLHandler, - }; - - const dataFromGivenUrl = (url, options = {}) => { - fetcher.setOptions(options); - return new Promise((resolve, reject) => { - fetcher.urlHandler.get(url, {}, (err, xml) => { - if (err) { - return reject(err); - } - - for (let nodeKey in xml.documentElement.childNodes) { - const node = xml.documentElement.childNodes[nodeKey]; - - if (node.nodeName === 'Ad') { - for (let adNodeKey in node.childNodes) { - const adNode = node.childNodes[adNodeKey]; - - if (adNode.nodeName === 'Wrapper') { - for (let wrapperNodeKey in adNode.childNodes) { - const wrapperNode = adNode.childNodes[wrapperNodeKey]; - - if ( - wrapperNode.nodeName === 'VASTAdTagURI' || - wrapperNode.nodeName === 'VASTAdTagURL' - ) { - wrapperNode.textContent = urlFor( - parserUtils.parseNodeText(wrapperNode) - ); - break; - } - } - } - } - } - } - VastParser.fetchingCallback = fetcher.fetchVAST.bind(fetcher); - VastParser.parseVAST(xml, options).then((response) => { - resolve(response); - }); - }); - }); + urlhandler: nodeUrlHandler, }; beforeEach(() => { @@ -437,138 +395,163 @@ describe('VASTParser', () => { }); }); - it('should emits VAST-error & track when wrapped', (done) => { - const url = urlFor('wrapper-empty.xml'); - dataFromGivenUrl(url, options).then((response) => { - // Response doesn't have any ads - expect(response.ads).toEqual([]); - //error has been triggered - expect(dataTriggered.length).toBe(1); - expect(dataTriggered[0].ERRORCODE).toBe(303); - expect(dataTriggered[0].extensions[0].children[0].name).toBe( - 'paramWrapperEmptyNoAd' - ); - expect(dataTriggered[0].extensions[0].children[0].value).toBe( - 'valueWrapperEmptyNoAd' - ); - // TRacking has been done - expect(trackCalls.length).toBe(1); - expect(trackCalls[0].templates).toEqual([ - 'http://example.com/wrapper-empty_wrapper-error', - 'http://example.com/empty-no-ad', - ]); - expect(trackCalls[0].variables).toEqual({ ERRORCODE: 303 }); - done(); + it('should emits VAST-error & track when wrapped', async () => { + const url = './spec/samples/wrapper-empty.xml'; + const response = await nodeUrlHandler.get( + './spec/samples/wrapper-empty.xml' + ); + + fetcher.setOptions({ ...options, url: url, previousUrl: url }); + VastParser.fetchingMethod = fetcher.fetchVAST.bind(fetcher); + + const vastXML = await VastParser.parseVAST(response.xml, { + url: url, + previousUrl: url, }); + // Response doesn't have any ads + + expect(vastXML.ads).toEqual([]); + // error has been triggered + expect(dataTriggered.length).toBe(1); + expect(dataTriggered[0].ERRORCODE).toBe(303); + expect(dataTriggered[0].extensions[0].children[0].name).toBe( + 'paramWrapperEmptyNoAd' + ); + expect(dataTriggered[0].extensions[0].children[0].value).toBe( + 'valueWrapperEmptyNoAd' + ); + // TRacking has been done + expect(trackCalls.length).toBe(1); + expect(trackCalls[0].templates).toEqual([ + 'http://example.com/wrapper-empty_wrapper-error', + 'http://example.com/empty-no-ad', + ]); + expect(trackCalls[0].variables).toEqual({ ERRORCODE: 303 }); }); }); describe('Ad with no creatives', () => { - it('should emits a VAST-error & track', (done) => { - dataFromGivenUrl(urlFor('empty-no-creative.xml'), options).then( - (response) => { - // Response doesn't have any ads - expect(response.ads).toEqual([]); - // Error has been triggered - expect(dataTriggered.length).toBe(1); - expect(dataTriggered[0].ERRORCODE).toBe(303); - expect(dataTriggered[0].extensions[0].children[0].name).toBe( - 'paramEmptyNoCreative' - ); - expect(dataTriggered[0].extensions[0].children[0].value).toBe( - 'valueEmptyNoCreative' - ); - // Tracking has been done; - expect(trackCalls.length).toBe(1); - expect(trackCalls[0].templates).toEqual([ - 'http://example.com/empty-no-creative_inline-error', - ]); - expect(trackCalls[0].variables).toEqual({ ERRORCODE: 303 }); - done(); - } + it('should emits a VAST-error & track', async () => { + const url = './spec/samples/empty-no-creative.xml'; + const response = await nodeUrlHandler.get(url); + + const vastXML = await VastParser.parseVAST(response.xml); + // Response doesn't have any ads + expect(vastXML.ads).toEqual([]); + // Error has been triggered + expect(dataTriggered.length).toBe(1); + expect(dataTriggered[0].ERRORCODE).toBe(303); + expect(dataTriggered[0].extensions[0].children[0].name).toBe( + 'paramEmptyNoCreative' + ); + expect(dataTriggered[0].extensions[0].children[0].value).toBe( + 'valueEmptyNoCreative' ); + // Tracking has been done; + expect(trackCalls.length).toBe(1); + expect(trackCalls[0].templates).toEqual([ + 'http://example.com/empty-no-creative_inline-error', + ]); + expect(trackCalls[0].variables).toEqual({ ERRORCODE: 303 }); }); - it('should emits a VAST-ERROR & track when wrapped', (done) => { - dataFromGivenUrl(urlFor('wrapper-empty-no-creative.xml'), options).then( - (response) => { - // Response doesn't have any ads - expect(response.ads).toEqual([]); - // Error has been triggered - expect(dataTriggered.length).toBe(1); - expect(dataTriggered[0].ERRORCODE).toBe(303); - expect(dataTriggered[0].extensions[0].children[0].name).toBe( - 'paramWrapperEmptyNoCreative' - ); - expect(dataTriggered[0].extensions[0].children[0].value).toBe( - 'valueWrapperEmptyNoCreative' - ); - expect(dataTriggered[0].extensions[1].children[0].name).toBe( - 'paramEmptyNoCreative' - ); - expect(dataTriggered[0].extensions[1].children[0].value).toBe( - 'valueEmptyNoCreative' - ); - // Tracking has been done - expect(trackCalls.length).toBe(1); - expect(trackCalls[0].templates).toEqual([ - 'http://example.com/wrapper-no-creative_wrapper-error', - 'http://example.com/empty-no-creative_inline-error', - ]); - expect(trackCalls[0].variables).toEqual({ ERRORCODE: 303 }); - done(); - } + it('should emits a VAST-ERROR & track when wrapped', async () => { + const url = './spec/samples/wrapper-empty-no-creative.xml'; + const response = await nodeUrlHandler.get(url); + + fetcher.setOptions({ ...options, url: url, previousUrl: url }); + VastParser.fetchingMethod = fetcher.fetchVAST.bind(fetcher); + + const vastXML = await VastParser.parseVAST(response.xml, { + url: url, + previousUrl: url, + }); + // Response doesn't have any ads + expect(vastXML.ads).toEqual([]); + // Error has been triggered + expect(dataTriggered.length).toBe(1); + expect(dataTriggered[0].ERRORCODE).toBe(303); + expect(dataTriggered[0].extensions[0].children[0].name).toBe( + 'paramWrapperEmptyNoCreative' + ); + expect(dataTriggered[0].extensions[0].children[0].value).toBe( + 'valueWrapperEmptyNoCreative' + ); + expect(dataTriggered[0].extensions[1].children[0].name).toBe( + 'paramEmptyNoCreative' ); + expect(dataTriggered[0].extensions[1].children[0].value).toBe( + 'valueEmptyNoCreative' + ); + // Tracking has been done + expect(trackCalls.length).toBe(1); + expect(trackCalls[0].templates).toEqual([ + 'http://example.com/wrapper-no-creative_wrapper-error', + 'http://example.com/empty-no-creative_inline-error', + ]); + expect(trackCalls[0].variables).toEqual({ ERRORCODE: 303 }); }); }); describe('Wrapper URL unavailable/timeout', () => { - it('sould emits a VAST-error and track', (done) => { - dataFromGivenUrl(urlFor('wrapper-unavailable-url.xml'), options).then( - (response) => { - // Response doesn't have any ads - expect(response.ads).toEqual([]); - // Error has been trigered - expect(dataTriggered.length).toBe(1); - expect(dataTriggered[0].ERRORCODE).toBe(301); - expect(dataTriggered[0].extensions[0].children[0].name).toBe( - 'paramWrapperInvalidXmlfile' - ); - expect(dataTriggered[0].extensions[0].children[0].value).toBe( - 'valueWrapperInvalidXmlfile' - ); - // Tracking has been done - expect(trackCalls.length).toBe(1); - expect(trackCalls[0].templates).toEqual([ - 'http://example.com/wrapper-invalid-xmlfile_wrapper-error', - ]); - expect(trackCalls[0].variables).toEqual({ ERRORCODE: 301 }); - done(); - } + it('sould emits a VAST-error and track', async () => { + const url = './spec/samples/wrapper-unavailable-url.xml'; + + const response = await nodeUrlHandler.get(url); + + fetcher.setOptions({ ...options, url: url, previousUrl: url }); + VastParser.fetchingMethod = fetcher.fetchVAST.bind(fetcher); + + const vastXML = await VastParser.parseVAST(response.xml, { + url: url, + previousUrl: url, + }); + // Response doesn't have any ads + expect(vastXML.ads).toEqual([]); + // Error has been trigered + expect(dataTriggered.length).toBe(1); + expect(dataTriggered[0].ERRORCODE).toBe(301); + expect(dataTriggered[0].extensions[0].children[0].name).toBe( + 'paramWrapperInvalidXmlfile' + ); + expect(dataTriggered[0].extensions[0].children[0].value).toBe( + 'valueWrapperInvalidXmlfile' ); + // Tracking has been done + expect(trackCalls.length).toBe(1); + expect(trackCalls[0].templates).toEqual([ + 'http://example.com/wrapper-invalid-xmlfile_wrapper-error', + ]); + expect(trackCalls[0].variables).toEqual({ ERRORCODE: 301 }); }); }); describe('Wrapper limit reached', () => { - it('should emits a VAST-error & track', (done) => { - dataFromGivenUrl(urlFor('wrapper-b.xml'), { - ...options, + it('should emits a VAST-error & track', async () => { + const url = './spec/samples/wrapper-b.xml'; + + const response = await nodeUrlHandler.get(url); + + fetcher.setOptions({ ...options, url: url, previousUrl: url }); + VastParser.fetchingMethod = fetcher.fetchVAST.bind(fetcher); + + const vastXML = await VastParser.parseVAST(response.xml, { + url: url, + previousUrl: url, wrapperLimit: 1, - }).then((response) => { - // Response doesn't have any ads - expect(response.ads).toEqual([]); - // Error has been triggered - expect(dataTriggered.length).toBe(1); - expect(dataTriggered[0].ERRORCODE).toBe(302); - expect(dataTriggered[0].extensions.length).toBe(0); - // Tracking has been done - expect(trackCalls.length).toBe(1); - expect(trackCalls[0].templates).toEqual([ - 'http://example.com/wrapperB-error', - ]); - expect(trackCalls[0].variables).toEqual({ ERRORCODE: 302 }); - done(); }); + // Response doesn't have any ads + expect(vastXML.ads).toEqual([]); + // Error has been triggered + expect(dataTriggered.length).toBe(1); + expect(dataTriggered[0].ERRORCODE).toBe(302); + expect(dataTriggered[0].extensions.length).toBe(0); + // Tracking has been done + expect(trackCalls.length).toBe(1); + expect(trackCalls[0].templates).toEqual([ + 'http://example.com/wrapperB-error', + ]); + expect(trackCalls[0].variables).toEqual({ ERRORCODE: 302 }); }); }); describe('Legacy', () => { @@ -576,10 +559,12 @@ describe('VASTParser', () => { beforeEach((done) => { VastParser.removeAllListeners(); - vastClient.get(urlFor('wrapper-legacy.xml'), options).then((res) => { - response = res; - done(); - }); + vastClient + .get('./spec/samples/wrapper-legacy.xml', options) + .then((res) => { + response = res; + done(); + }); }); describe('should correctly loads a wrapped ad, even with the VASTAdTagURL-Tag', () => { it('should have found 1 ad', () => { From 03d2545c52f23e96660b6d647b4b4eccec460072 Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Mon, 18 Sep 2023 18:44:20 +0200 Subject: [PATCH 54/95] [tools] removing unused import --- rollup.config.js | 1 - 1 file changed, 1 deletion(-) diff --git a/rollup.config.js b/rollup.config.js index 28d5d40b..e1c15832 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -1,6 +1,5 @@ import babel from '@rollup/plugin-babel'; import { terser } from 'rollup-plugin-terser'; -import alias from '@rollup/plugin-alias'; import resolve from '@rollup/plugin-node-resolve'; const babelPlugin = babel({ From 0d1786b98f7e899c70780be16fcd1b24b8c17333 Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Tue, 19 Sep 2023 18:06:14 +0200 Subject: [PATCH 55/95] [test] correcting vast sample and test --- spec/samples/sample.xml | 6 ++++++ spec/vast_parser.spec.js | 1 + 2 files changed, 7 insertions(+) diff --git a/spec/samples/sample.xml b/spec/samples/sample.xml index a41a9a8d..e13a9079 100644 --- a/spec/samples/sample.xml +++ b/spec/samples/sample.xml @@ -2,6 +2,7 @@ + a532d16d-4d7f-4440-bd29-2ec0e693fc80 @@ -29,6 +30,7 @@ + Linear-12345 00:01:30.123 @@ -66,6 +68,7 @@ + Linear-12345 @@ -98,6 +101,7 @@ + Linear-12345 @@ -137,11 +141,13 @@ + a532d16d-4d7f-4440-bd29-2ec0e693fc80 + Linear-12345 30 diff --git a/spec/vast_parser.spec.js b/spec/vast_parser.spec.js index 244f9c34..f0fbd46d 100644 --- a/spec/vast_parser.spec.js +++ b/spec/vast_parser.spec.js @@ -182,6 +182,7 @@ describe('VASTParser', () => { }); expect(ads).toHaveLength(2); + expect(VastParser.emit).toHaveBeenCalledTimes(2); expect(VastParser.emit).toHaveBeenCalledWith('VAST-ad-parsed', { adIndex: 0, type: 'INLINE', From 3822db7cf7ad2c6300acfdac4862cdbfaca13272 Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Mon, 25 Sep 2023 15:20:17 +0200 Subject: [PATCH 56/95] fixing error handling --- src/urlhandlers/xhr_url_handler.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/urlhandlers/xhr_url_handler.js b/src/urlhandlers/xhr_url_handler.js index 50212a8d..76280f3a 100644 --- a/src/urlhandlers/xhr_url_handler.js +++ b/src/urlhandlers/xhr_url_handler.js @@ -34,8 +34,8 @@ async function handleResponse(response) { function handleError(response) { if (isBrowserEnvironment()) { if ( - window.location.protocol === 'http:' && - response.url.indexOf('https://') === 0 + window.location.protocol === 'https:' && + response.url.indexOf('http://') === 0 ) { return 'URLHandler: Cannot go from HTTPS to HTTP.'; } From 63cdae9da65f195554fa1b551816cbd925abe2bb Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Wed, 4 Oct 2023 18:05:19 +0200 Subject: [PATCH 57/95] [test] fixing failing test --- spec/fetcher.spec.js | 3 ++- spec/vast_parser.spec.js | 16 ++++++++-------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/spec/fetcher.spec.js b/spec/fetcher.spec.js index 32dde438..2707c5bd 100644 --- a/spec/fetcher.spec.js +++ b/spec/fetcher.spec.js @@ -4,6 +4,7 @@ import * as Bitrate from '../src/parser/bitrate'; import { urlHandler } from '../src/urlhandlers/xhr_url_handler'; import { expect } from '@jest/globals'; import { getNodesFromXml } from './utils/utils'; +import { async } from 'regenerator-runtime'; describe('Fetcher', () => { let fetcher, vastParser, mockEmit; @@ -166,7 +167,7 @@ describe('Fetcher', () => { maxWrapperDepth: 5, emitter: () => {}, }); - return expect(result).rejects.toEqual(new Error('timeout')); + expect(result).rejects.toEqual(new Error('error')); }); }); }); diff --git a/spec/vast_parser.spec.js b/spec/vast_parser.spec.js index f0fbd46d..110a500e 100644 --- a/spec/vast_parser.spec.js +++ b/spec/vast_parser.spec.js @@ -1,5 +1,5 @@ import { VASTParser } from '../src/parser/vast_parser'; -import { urlFor } from './utils/utils'; +import { urlFor, getNodesFromXml } from './utils/utils'; import { util } from '../src/util/util'; import { parserUtils } from '../src/parser/parser_utils'; import * as Bitrate from '../src/parser/bitrate'; @@ -159,7 +159,7 @@ describe('VASTParser', () => { it('handles Error tag for root VAST', () => { //initParsingStatus always will be called before parseVastXml VastParser.rootErrorURLTemplates = []; - VastParser.parseVastXml(errorXml, { isRootVAST: true }); + VastParser.parseVastXml(errorXml.xml, { isRootVAST: true }); expect(VastParser.rootErrorURLTemplates).toEqual([ 'http://example.com/empty-no-ad', ]); @@ -188,14 +188,14 @@ describe('VASTParser', () => { type: 'INLINE', url: inlineSampleVastUrl, wrapperDepth: 0, - vastVersion: '2.1', + vastVersion: '4.3', }); expect(VastParser.emit).toHaveBeenCalledWith('VAST-ad-parsed', { adIndex: 1, type: 'INLINE', url: inlineSampleVastUrl, wrapperDepth: 0, - vastVersion: '2.1', + vastVersion: '4.3', }); }); }); @@ -403,7 +403,7 @@ describe('VASTParser', () => { ); fetcher.setOptions({ ...options, url: url, previousUrl: url }); - VastParser.fetchingMethod = fetcher.fetchVAST.bind(fetcher); + VastParser.fetchingCallback = fetcher.fetchVAST.bind(fetcher); const vastXML = await VastParser.parseVAST(response.xml, { url: url, @@ -461,7 +461,7 @@ describe('VASTParser', () => { const response = await nodeUrlHandler.get(url); fetcher.setOptions({ ...options, url: url, previousUrl: url }); - VastParser.fetchingMethod = fetcher.fetchVAST.bind(fetcher); + VastParser.fetchingCallback = fetcher.fetchVAST.bind(fetcher); const vastXML = await VastParser.parseVAST(response.xml, { url: url, @@ -501,7 +501,7 @@ describe('VASTParser', () => { const response = await nodeUrlHandler.get(url); fetcher.setOptions({ ...options, url: url, previousUrl: url }); - VastParser.fetchingMethod = fetcher.fetchVAST.bind(fetcher); + VastParser.fetchingCallback = fetcher.fetchVAST.bind(fetcher); const vastXML = await VastParser.parseVAST(response.xml, { url: url, @@ -534,7 +534,7 @@ describe('VASTParser', () => { const response = await nodeUrlHandler.get(url); fetcher.setOptions({ ...options, url: url, previousUrl: url }); - VastParser.fetchingMethod = fetcher.fetchVAST.bind(fetcher); + VastParser.fetchingCallback = fetcher.fetchVAST.bind(fetcher); const vastXML = await VastParser.parseVAST(response.xml, { url: url, From f6bd0021f6e5faab07db6d8981f8bd9fae58cffd Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Thu, 5 Oct 2023 09:50:11 +0200 Subject: [PATCH 58/95] [test] fix inconsistent test --- spec/fetcher.spec.js | 15 +++++++++------ spec/vast_client.spec.js | 1 - 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/spec/fetcher.spec.js b/spec/fetcher.spec.js index 2707c5bd..3ab1cd16 100644 --- a/spec/fetcher.spec.js +++ b/spec/fetcher.spec.js @@ -162,12 +162,15 @@ describe('Fetcher', () => { }); it('should rejects with error', () => { - let result = fetcher.fetchVAST({ - url: url, - maxWrapperDepth: 5, - emitter: () => {}, - }); - expect(result).rejects.toEqual(new Error('error')); + let result = fetcher + .fetchVAST({ + url: url, + maxWrapperDepth: 5, + emitter: () => {}, + }) + .catch(() => { + return expect(result).rejects.toEqual('error'); + }); }); }); }); diff --git a/spec/vast_client.spec.js b/spec/vast_client.spec.js index 91dc1bbe..22634a34 100644 --- a/spec/vast_client.spec.js +++ b/spec/vast_client.spec.js @@ -1,5 +1,4 @@ import { VASTClient } from '../src/vast_client'; -import 'regenerator-runtime/runtime'; import { readFile } from 'fs/promises'; import { afterAll } from '@jest/globals'; From d2e9bde147edec04d34487b92ef16d419ad2e869 Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Fri, 22 Sep 2023 15:34:49 +0200 Subject: [PATCH 59/95] [parser] Improving code readability --- src/parser/ad_parser.js | 87 ++++++++++++++++++++------------------ src/parser/parser_utils.js | 61 +++++++++----------------- 2 files changed, 66 insertions(+), 82 deletions(-) diff --git a/src/parser/ad_parser.js b/src/parser/ad_parser.js index ffce4f25..91e8a94d 100644 --- a/src/parser/ad_parser.js +++ b/src/parser/ad_parser.js @@ -22,30 +22,25 @@ export function parseAd( emit, { allowMultipleAds, followAdditionalWrappers } = {} ) { - const childNodes = adElement.childNodes; + const childNodes = Array.from(adElement.childNodes); - for (const adTypeElementKey in childNodes) { - const adTypeElement = childNodes[adTypeElementKey]; - - if (['Wrapper', 'InLine'].indexOf(adTypeElement.nodeName) === -1) { - continue; - } + const filteredChildNodes = childNodes + .filter((childNode) => ['Wrapper', 'InLine'].includes(childNode.nodeName)) + .filter( + (adType) => + !(adType.nodeName !== 'Wrapper' && followAdditionalWrappers === false) + ); - if ( - adTypeElement.nodeName === 'Wrapper' && - followAdditionalWrappers === false - ) { - continue; - } + for (const node of filteredChildNodes) { + parserUtils.copyNodeAttribute('id', adElement, node); + parserUtils.copyNodeAttribute('sequence', adElement, node); + parserUtils.copyNodeAttribute('adType', adElement, node); - parserUtils.copyNodeAttribute('id', adElement, adTypeElement); - parserUtils.copyNodeAttribute('sequence', adElement, adTypeElement); - parserUtils.copyNodeAttribute('adType', adElement, adTypeElement); - if (adTypeElement.nodeName === 'Wrapper') { - return { ad: parseWrapper(adTypeElement, emit), type: 'WRAPPER' }; - } else if (adTypeElement.nodeName === 'InLine') { + if (node.nodeName === 'Wrapper') { + return { ad: parseWrapper(node, emit), type: 'WRAPPER' }; + } else if (node.nodeName === 'InLine') { return { - ad: parseInLine(adTypeElement, emit, { allowMultipleAds }), + ad: parseInLine(node, emit, { allowMultipleAds }), type: 'INLINE', }; } @@ -84,11 +79,10 @@ function parseAdElement(adTypeElement, emit) { parserVerification.verifyRequiredValues(adTypeElement, emit); } - const childNodes = adTypeElement.childNodes; + const childNodes = Array.from(adTypeElement.childNodes); const ad = createAd(parserUtils.parseAttributes(adTypeElement)); - for (const nodeKey in childNodes) { - const node = childNodes[nodeKey]; + childNodes.forEach((node) => { switch (node.nodeName) { case 'Error': ad.errorURLTemplates.push(parserUtils.parseNodeText(node)); @@ -191,7 +185,7 @@ function parseAdElement(adTypeElement, emit) { }); break; } - } + }); if (adVerificationsFromExtensions.length) { ad.adVerifications = ad.adVerifications.concat( @@ -246,7 +240,7 @@ function parseWrapper(wrapperElement, emit) { } ad.creatives.forEach((wrapperCreativeElement) => { - if (['linear', 'nonlinear'].indexOf(wrapperCreativeElement.type) !== -1) { + if (['linear', 'nonlinear'].includes(wrapperCreativeElement.type)) { // TrackingEvents Linear / NonLinear if (wrapperCreativeElement.trackingEvents) { if (!ad.trackingEvents) { @@ -255,8 +249,10 @@ function parseWrapper(wrapperElement, emit) { if (!ad.trackingEvents[wrapperCreativeElement.type]) { ad.trackingEvents[wrapperCreativeElement.type] = {}; } - for (const eventName in wrapperCreativeElement.trackingEvents) { - const urls = wrapperCreativeElement.trackingEvents[eventName]; + + for (const [eventName, urls] of Object.entries( + wrapperCreativeElement.trackingEvents + )) { if ( !Array.isArray( ad.trackingEvents[wrapperCreativeElement.type][eventName] @@ -312,23 +308,30 @@ export function _parseAdVerifications(verifications) { verifications.forEach((verificationNode) => { const verification = createAdVerification(); - const childNodes = verificationNode.childNodes; + const childNodes = Array.from(verificationNode.childNodes); parserUtils.assignAttributes(verificationNode.attributes, verification); - for (const nodeKey in childNodes) { - const node = childNodes[nodeKey]; - - switch (node.nodeName) { - case 'JavaScriptResource': - case 'ExecutableResource': - verification.resource = parserUtils.parseNodeText(node); - parserUtils.assignAttributes(node.attributes, verification); - break; - case 'VerificationParameters': - verification.parameters = parserUtils.parseNodeText(node); - break; - } - } + + childNodes + .filter(({ nodeName }) => + [ + 'JavaScriptResource', + 'ExecutableResource', + 'VerificationParameters', + ].includes(nodeName) + ) + .forEach(({ nodeName, textContent, attributes }) => { + switch (nodeName) { + case 'JavaScriptResource': + case 'ExecutableResource': + verification.resource = textContent.trim(); + parserUtils.assignAttributes(attributes, verification); + break; + case 'VerificationParameters': + verification.parameters = parserUtils.parseNodeText(textContent); + break; + } + }); const trackingEventsElement = parserUtils.childByName( verificationNode, diff --git a/src/parser/parser_utils.js b/src/parser/parser_utils.js index a4dd1a0a..1330bf8a 100644 --- a/src/parser/parser_utils.js +++ b/src/parser/parser_utils.js @@ -11,15 +11,8 @@ import { util } from '../util/util'; * @return {Object|undefined} */ function childByName(node, name) { - const childNodes = node.childNodes; - - for (const childKey in childNodes) { - const child = childNodes[childKey]; - - if (child.nodeName === name) { - return child; - } - } + const childNodes = Array.from(node.childNodes); + return childNodes.find((childNode) => childNode.nodeName === name); } /** @@ -29,17 +22,8 @@ function childByName(node, name) { * @return {Array} */ function childrenByName(node, name) { - const children = []; - const childNodes = node.childNodes; - - for (const childKey in childNodes) { - const child = childNodes[childKey]; - - if (child.nodeName === name) { - children.push(child); - } - } - return children; + const childNodes = Array.from(node.childNodes); + return childNodes.filter((childNode) => childNode.nodeName === name); } /** @@ -53,12 +37,12 @@ function resolveVastAdTagURI(vastAdTagUrl, originalUrl) { return vastAdTagUrl; } - if (vastAdTagUrl.indexOf('//') === 0) { + if (vastAdTagUrl.startsWith('//')) { const { protocol } = location; return `${protocol}${vastAdTagUrl}`; } - if (vastAdTagUrl.indexOf('://') === -1) { + if (!vastAdTagUrl.includes('://')) { // Resolve relative URLs (mainly for unit testing) const baseURL = originalUrl.slice(0, originalUrl.lastIndexOf('/')); return `${baseURL}/${vastAdTagUrl}`; @@ -73,7 +57,7 @@ function resolveVastAdTagURI(vastAdTagUrl, originalUrl) { * @return {Boolean} */ function parseBoolean(booleanString) { - return ['true', 'TRUE', 'True', '1'].indexOf(booleanString) !== -1; + return ['true', 'TRUE', 'True', '1'].includes(booleanString); } /** @@ -105,12 +89,12 @@ function copyNodeAttribute(attributeName, nodeSource, nodeDestination) { * @returns {Object} */ function parseAttributes(element) { - const nodeAttributes = element.attributes; - const attributes = {}; - for (let i = 0; i < nodeAttributes.length; i++) { - attributes[nodeAttributes[i].nodeName] = nodeAttributes[i].nodeValue; - } - return attributes; + const nodeAttributes = Array.from(element.attributes); + + return nodeAttributes.reduce((acc, nodeAttribute) => { + acc[nodeAttribute.nodeName] = nodeAttribute.nodeValue; + return acc; + }, {}); } /** @@ -194,22 +178,19 @@ function splitVAST(ads) { */ function assignAttributes(attributes, verificationObject) { if (attributes) { - for (const attrKey in attributes) { - const attribute = attributes[attrKey]; - + Array.from(attributes).forEach(({ nodeName, nodeValue }) => { if ( - attribute.nodeName && - attribute.nodeValue && - verificationObject.hasOwnProperty(attribute.nodeName) + nodeName && + nodeValue && + verificationObject.hasOwnProperty(nodeName) ) { - let value = attribute.nodeValue; - - if (typeof verificationObject[attribute.nodeName] === 'boolean') { + let value = nodeValue; + if (typeof verificationObject[nodeName] === 'boolean') { value = parseBoolean(value); } - verificationObject[attribute.nodeName] = value; + verificationObject[nodeName] = value; } - } + }); } } From d62cde29b7f6d1f9b867badaa7e419579040513b Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Thu, 5 Oct 2023 11:48:09 +0200 Subject: [PATCH 60/95] [parser] fix unparsed VerificationParameters --- src/parser/ad_parser.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/parser/ad_parser.js b/src/parser/ad_parser.js index 91e8a94d..26799d8c 100644 --- a/src/parser/ad_parser.js +++ b/src/parser/ad_parser.js @@ -328,7 +328,7 @@ export function _parseAdVerifications(verifications) { parserUtils.assignAttributes(attributes, verification); break; case 'VerificationParameters': - verification.parameters = parserUtils.parseNodeText(textContent); + verification.parameters = textContent.trim(); break; } }); From be09e552507549cf097499734831e0c3cb558959 Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Thu, 5 Oct 2023 12:26:18 +0200 Subject: [PATCH 61/95] [client] refactor constructor default values --- src/vast_client.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/vast_client.js b/src/vast_client.js index 8e037c15..8fe94621 100644 --- a/src/vast_client.js +++ b/src/vast_client.js @@ -16,12 +16,16 @@ export class VASTClient { * @param {Storage} customStorage - A custom storage to use instead of the default one. * @constructor */ - constructor(cappingFreeLunch, cappingMinimumTimeInterval, customStorage) { - this.cappingFreeLunch = cappingFreeLunch || 0; - this.cappingMinimumTimeInterval = cappingMinimumTimeInterval || 0; + constructor( + cappingFreeLunch = 0, + cappingMinimumTimeInterval = 0, + customStorage = new Storage() + ) { + this.cappingFreeLunch = cappingFreeLunch; + this.cappingMinimumTimeInterval = cappingMinimumTimeInterval; this.fetcher = new Fetcher(); this.vastParser = new VASTParser({ fetcher: this.fetcher }); - this.storage = customStorage || new Storage(); + this.storage = customStorage; // Init values if not already set if (this.lastSuccessfulAd === undefined) { this.lastSuccessfulAd = 0; From a3a74d24bf1da3045efc8a3498261636c951c777 Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Wed, 4 Oct 2023 10:26:19 +0200 Subject: [PATCH 62/95] [test] adding tests for new error logs --- spec/util.spec.js | 44 +++++ spec/vast_trackers.spec.js | 363 ++++++++++++++++++++++++++++++++++--- 2 files changed, 383 insertions(+), 24 deletions(-) diff --git a/spec/util.spec.js b/spec/util.spec.js index f8b51f8d..7fa41025 100644 --- a/spec/util.spec.js +++ b/spec/util.spec.js @@ -374,4 +374,48 @@ describe('util', function () { expect(replacedUrlMacros).toEqual('http://test.com?bp=-1'); }); }); + describe('#filterUrlTemplates', function () { + it('should filtered valid and unvalid urls', () => { + const urlsToFilter = [ + { + id: 'sample-impression1', + url: './example.com', + }, + { + id: 'sample-impression2', + url: '../example.com', + }, + { + id: 'sample-impression3', + url: 'https://example.com', + }, + { + id: 'sample-impression4', + url: 'http://example.com', + }, + { + id: 'sample-impression5', + url: '//example.com', + }, + ]; + + const { validUrls, unvalidUrls } = util.filterUrlTemplates(urlsToFilter); + expect(validUrls.length).toBe(3); + expect(unvalidUrls.length).toBe(2); + }); + }); + + describe('#isValidUrls', function () { + it('should return true if the url is valid', () => { + const url = 'http://example.com'; + const validUrl = util.isValidUrl(url); + expect(validUrl).toBe(true); + }); + + it('should return false if the url is not valid', () => { + const url = 'example.com'; + const validUrl = util.isValidUrl(url); + expect(validUrl).toBe(false); + }); + }); }); diff --git a/spec/vast_trackers.spec.js b/spec/vast_trackers.spec.js index 31d20afa..7d8e5468 100644 --- a/spec/vast_trackers.spec.js +++ b/spec/vast_trackers.spec.js @@ -22,6 +22,7 @@ describe('VASTTracker', function () { ADSERVINGID: 'z292x16y-3d7f-6440-bd29-2ec0f153fc89', ADTYPE: 'video', ADCATEGORIES: 'Category-A%2CCategory-B%2CCategory-C', + BLOCKEDADCATEGORIES: 'blockedAdCategory', }; beforeEach(() => { @@ -43,6 +44,58 @@ describe('VASTTracker', function () { expect(vastTracker.quartiles.thirdQuartile).toBe(67.59); }); + describe('#trackURLs', () => { + let spyTrackUtil; + + beforeEach(() => { + spyTrackUtil = jest.spyOn(util, 'track'); + }); + + it('should call track with the expected macros', () => { + vastTracker.trackURLs([{ id: 'valid-url', url: 'http://example.com' }]); + expect(spyTrackUtil).toHaveBeenCalledWith( + ['http://example.com'], + expect.objectContaining(expectedMacros), + expect.any(Object) + ); + }); + + it('should call track with the expected macros if progress is defined', () => { + vastTracker.progress = 12; + vastTracker.trackURLs([{ id: 'valid-url', url: 'http://example.com' }]); + expect(spyTrackUtil).toHaveBeenCalledWith( + ['http://example.com'], + expect.objectContaining({ + ...expectedMacros, + ADPLAYHEAD: '00%3A00%3A12.000', + }), + expect.any(Object) + ); + }); + + it('should call track without expected macro if ad or creative is not defined', () => { + vastTracker.creative = null; + vastTracker.ad = null; + vastTracker.trackURLs([{ id: 'valid-url', url: 'http://example.com' }]); + + expect(spyTrackUtil).toHaveBeenCalledWith( + ['http://example.com'], + expect.not.objectContaining(expectedMacros), + expect.any(Object) + ); + }); + + it('should emit TRACKER-error if invalid urls are provided', () => { + const urlTemplates = [{ id: 'invalid-url', url: 'example.com' }]; + vastTracker.trackURLs(urlTemplates); + + expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { + unvalidUrls: ['example.com'], + message: 'Provided urls are malformed', + }); + }); + }); + describe('#Track', () => { Object.entries(adTrackingUrls).forEach(([event, url]) => { it(`should call emit with ${event}`, () => { @@ -96,6 +149,14 @@ describe('VASTTracker', function () { vastTracker.track('start', { macro: {}, once: true }); expect(vastTracker.trackingEvents).not.toHaveProperty('start'); }); + + it('should emit TRACKER-error if macros is not valid', () => { + vastTracker.track('start', { macros: 'foo', once: false }); + expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { + message: 'track macros parameter has the wrong type', + parameter: 'foo', + }); + }); }); describe('#click', () => { @@ -132,6 +193,13 @@ describe('VASTTracker', function () { expectedMacros ); }); + it('should emit TRACKER-error if macros is not valid', () => { + vastTracker.minimize('foo'); + expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { + message: 'minimize parameter has to wrong type', + parameter: 'foo', + }); + }); }); describe('#verificationNotExecuted', () => { @@ -143,6 +211,23 @@ describe('VASTTracker', function () { ad.adVerifications[0].trackingEvents.verificationNotExecuted; vastTracker.verificationNotExecuted(vendor, reasonMacro); }); + + it('should emit TRACKER-error if vendor is not valid', () => { + vastTracker.verificationNotExecuted(1); + expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { + message: 'verificationNotExecuted parameters have to wrong type', + parameters: { vendor: 1, macros: {} }, + }); + }); + + it('should emit TRACKER-error if macro is not valid', () => { + vastTracker.verificationNotExecuted('vendor', 'foo'); + expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { + message: 'verificationNotExecuted parameters have to wrong type', + parameters: { vendor: 'vendor', macros: 'foo' }, + }); + }); + it('should be defined', () => { expect(verificationUrl).toBeDefined(); }); @@ -189,6 +274,13 @@ describe('VASTTracker', function () { expectedMacros ); }); + it('should emit TRACKER-error if macros is not valid', () => { + vastTracker.otherAdInteraction('foo'); + expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { + message: 'otherAdInteraction parameter has to wrong type', + parameter: 'foo', + }); + }); }); describe('#acceptInvitation', () => { @@ -207,6 +299,13 @@ describe('VASTTracker', function () { expectedMacros ); }); + it('should emit TRACKER-error if macros is not valid', () => { + vastTracker.acceptInvitation('foo'); + expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { + message: 'acceptInvitation parameter has to wrong type', + parameter: 'foo', + }); + }); }); describe('#adExpand', () => { @@ -225,6 +324,13 @@ describe('VASTTracker', function () { expectedMacros ); }); + it('should emit TRACKER-error if macros is not valid', () => { + vastTracker.adExpand('foo'); + expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { + message: 'adExpand parameter has to wrong type', + parameter: 'foo', + }); + }); }); describe('#complete', () => { @@ -238,13 +344,19 @@ describe('VASTTracker', function () { expect(spyTrack).toHaveBeenCalledWith('complete', expect.any(Object)); expect(spyTrack).toHaveBeenCalledTimes(2); }); + + it('should emit TRACKER-error if macros is not valid', () => { + vastTracker.complete('foo'); + expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { + message: 'complete parameter has to wrong type', + parameter: 'foo', + }); + }); }); describe('#close', () => { - let a; beforeEach(() => { vastTracker.close(); - a = jest.spyOn(vastTracker, 'emit'); }); it('should have emit and track close event', () => { expect(spyEmitter).toHaveBeenCalledWith('close', { @@ -255,6 +367,13 @@ describe('VASTTracker', function () { expect.any(Object) ); }); + it('should emit TRACKER-error if macros is not valid', () => { + vastTracker.close('foo'); + expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { + message: 'close parameter has the wrong type', + parameter: 'foo', + }); + }); }); describe('#skip', () => { @@ -265,6 +384,13 @@ describe('VASTTracker', function () { }); expect(spyTrack).toHaveBeenCalledWith('skip', expect.any(Object)); }); + it('should emit TRACKER-error if macros is not valid', () => { + vastTracker.skip('foo'); + expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { + message: 'skip parameter has the wrong type', + parameter: 'foo', + }); + }); }); describe('#loaded', () => { @@ -275,6 +401,13 @@ describe('VASTTracker', function () { }); expect(spyTrack).toHaveBeenCalledWith('loaded', expect.any(Object)); }); + it('should emit TRACKER-error if macros is not valid', () => { + vastTracker.load('foo'); + expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { + message: 'load parameter has the wrong type', + parameter: 'foo', + }); + }); }); describe('#adCollapse', () => { @@ -293,6 +426,13 @@ describe('VASTTracker', function () { expectedMacros ); }); + it('should emit TRACKER-error if macros is not valid', () => { + vastTracker.adCollapse('foo'); + expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { + message: 'adCollapse parameter has to wrong type', + parameter: 'foo', + }); + }); }); describe('#overlayViewDuration', () => { @@ -320,6 +460,20 @@ describe('VASTTracker', function () { } ); }); + it('should emit TRACKER-error if formattedDuration is not valid', () => { + vastTracker.overlayViewDuration(1); + expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { + message: 'overlayViewDuration parameters have the wrong type', + parameters: { formattedDuration: 1, macros: {} }, + }); + }); + it('should emit TRACKER-error if macros is not valid', () => { + vastTracker.overlayViewDuration('00:00:12', 'foo'); + expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { + message: 'overlayViewDuration parameters have the wrong type', + parameters: { formattedDuration: '00:00:12', macros: 'foo' }, + }); + }); }); describe('#notUsed', () => { @@ -339,6 +493,14 @@ describe('VASTTracker', function () { ); expect(spyEmitter).toHaveBeenCalledTimes(1); }); + + it('should emit TRACKER-error if macros is not valid', () => { + vastTracker.notUsed('foo'); + expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { + message: 'notUsed parameter has to wrong type', + parameter: 'foo', + }); + }); }); describe('#setDuration', () => { @@ -348,6 +510,14 @@ describe('VASTTracker', function () { vastTracker.setDuration(newDuration); expect(vastTracker.assetDuration).toEqual(123); }); + + it('should emit TRACKER-error if duration is not valid', () => { + vastTracker.setDuration('foo'); + expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { + message: 'the duration provided is not valid', + duration: 'foo', + }); + }); }); describe('#setProgress', () => { @@ -428,6 +598,22 @@ describe('VASTTracker', function () { ['progress-4%', expect.anything()] ); }); + + it('should emit TRACKER-error if progress is unvalid', () => { + vastTracker.setProgress('foo'); + expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { + message: 'setProgress parameters has the wrong type.', + parameter: { progress: 'foo', macros: {} }, + }); + }); + + it('should emit TRACKER-error if macros is unvalid', () => { + vastTracker.setProgress(12, 'foo'); + expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { + message: 'setProgress parameters has the wrong type.', + parameter: { progress: 12, macros: 'foo' }, + }); + }); }); describe('#isQuartileReached', () => { @@ -467,6 +653,22 @@ describe('VASTTracker', function () { expect(spyTrack).not.toHaveBeenCalled(); expect(vastTracker.muted).toEqual(false); }); + + it('should emit TRACKER-error if muted is not valid', () => { + vastTracker.setMuted('foo'); + expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { + message: 'setMuted parameters have the wrong type', + parameters: { muted: 'foo', macros: {} }, + }); + }); + + it('should emit TRACKER-error if macros is not valid', () => { + vastTracker.setMuted(true, 'foo'); + expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { + message: 'setMuted parameters have the wrong type', + parameters: { muted: true, macros: 'foo' }, + }); + }); }); describe('#setPaused', () => { @@ -488,6 +690,22 @@ describe('VASTTracker', function () { expect(vastTracker.paused).toEqual(false); expect(spyEmitter).not.toHaveBeenCalled(); }); + + it('should emit TRACKER-error if paused is not valid', () => { + vastTracker.setPaused('foo'); + expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { + message: 'setPaused parameters have the wrong type', + parameters: { paused: 'foo', macros: {} }, + }); + }); + + it('should emit TRACKER-error if macros is not valid', () => { + vastTracker.setPaused(true, 'foo'); + expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { + message: 'setPaused parameters have the wrong type', + parameters: { paused: true, macros: 'foo' }, + }); + }); }); describe('#setFullScreen', () => { @@ -511,6 +729,22 @@ describe('VASTTracker', function () { vastTracker.setFullscreen(false); expect(spyTrack).not.toHaveBeenCalled(); }); + + it('should emit TRACKER-error if fullscreen is not valid', () => { + vastTracker.setFullscreen('foo'); + expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { + message: 'setFullScreen parameters have the wrong type', + parameters: { fullscreen: 'foo', macros: {} }, + }); + }); + + it('should emit TRACKER-error if macros is not valid', () => { + vastTracker.setFullscreen(true, 'foo'); + expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { + message: 'setFullScreen parameters have the wrong type', + parameters: { fullscreen: true, macros: 'foo' }, + }); + }); }); describe('#setExpand', () => { @@ -539,6 +773,22 @@ describe('VASTTracker', function () { vastTracker.setExpand(false); expect(spyTrack).not.toHaveBeenCalled(); }); + + it('should emit TRACKER-error if expanded is not valid', () => { + vastTracker.setExpand('foo'); + expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { + message: 'setExpand parameters have the wrong type', + parameters: { expanded: 'foo', macros: {} }, + }); + }); + + it('should emit TRACKER-error if macros is not valid', () => { + vastTracker.setExpand(true, 'foo'); + expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { + message: 'setExpand parameters have the wrong type', + parameters: { expanded: true, macros: 'foo' }, + }); + }); }); describe('#setSkipDelay', () => { @@ -556,6 +806,14 @@ describe('VASTTracker', function () { vastTracker.setSkipDelay('foo'); expect(vastTracker.skipDelay).toBe(8); }); + + it('should emit TRACKER-error if duration is not valid', () => { + vastTracker.setSkipDelay('foo'); + expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { + message: 'setSkipDelay parameter does not have a valid value', + parameter: 'foo', + }); + }); }); describe('#trackImpression', () => { @@ -590,28 +848,12 @@ describe('VASTTracker', function () { expect(spyTrack).not.toHaveBeenCalledTimes(2); }); - it('should skip invalid urls', () => { - const expectedUrlTemplates = [ - { - id: 'sample-impression1', - url: 'http://example.com/impression1_asset:[ASSETURI]_[CACHEBUSTING]', - }, - { - id: 'sample-impression2', - url: 'http://example.com/impression2_[random]', - }, - { - id: 'sample-impression3', - url: '//example.com/impression3_[RANDOM]', - }, - ]; - const spyUtilTrack = jest.spyOn(util, 'track'); - vastTracker.trackURLs(ad.impressionURLTemplates); - expect(spyUtilTrack).toHaveBeenCalledWith( - expectedUrlTemplates, - expect.anything(), - expect.anything() - ); + it('should emit TRACKER-error if macros is not valid', () => { + vastTracker.trackImpression('foo'); + expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { + message: 'trackImpression parameter has the wrong type', + parameter: 'foo', + }); }); }); @@ -628,6 +870,14 @@ describe('VASTTracker', function () { macros ); }); + + it('should emit TRACKER-error if macros is not valid ', () => { + vastTracker.trackViewableImpression('foo'); + expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { + message: 'trackViewableImpression parameter has the wrong type', + parameter: 'foo', + }); + }); }); describe('#trackNotViewableImpression', () => { @@ -641,6 +891,14 @@ describe('VASTTracker', function () { macros ); }); + + it('should emit TRACKER-error if macros is not valid ', () => { + vastTracker.trackNotViewableImpression('foo'); + expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { + message: 'trackNotViewableImpression parameter has the wrong type', + parameter: 'foo', + }); + }); }); describe('#trackUndeterminedImpression', () => { @@ -654,6 +912,14 @@ describe('VASTTracker', function () { macros ); }); + + it('should emit TRACKER-error if macros is not valid ', () => { + vastTracker.trackUndeterminedImpression('foo'); + expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { + message: 'trackUndeterminedImpression parameter has the wrong type', + parameter: 'foo', + }); + }); }); }); @@ -675,6 +941,22 @@ describe('VASTTracker', function () { { isCustomCode: false } ); }); + + it('should emit TRACKER-error if macros is not valid', () => { + vastTracker.error('foo'); + expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { + message: 'error parameters have the wrong type', + parameters: { macros: 'foo', isCustomCode: false }, + }); + }); + + it('should emit TRACKER-error if isCustomCode is not valid', () => { + vastTracker.error({}, 'foo'); + expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { + message: 'error parameters have the wrong type', + parameters: { macros: {}, isCustomCode: 'foo' }, + }); + }); }); describe('#errorWithCode', () => { @@ -684,6 +966,20 @@ describe('VASTTracker', function () { vastTracker.errorWithCode('1234', true); expect(spyError).toHaveBeenCalledWith({ ERRORCODE: '1234' }, true); }); + it('should emit TRACKER-error if errorCode is not valid', () => { + vastTracker.errorWithCode(1); + expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { + message: 'ErrorWithCode parameters have the wrong type', + parameters: { errorCode: 1, isCustomCode: false }, + }); + }); + it('should emit TRACKER-error if macros is not valid', () => { + vastTracker.errorWithCode('303', 'foo'); + expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { + message: 'ErrorWithCode parameters have the wrong type', + parameters: { errorCode: '303', isCustomCode: 'foo' }, + }); + }); }); }); @@ -714,6 +1010,25 @@ describe('VASTTracker', function () { expect.any(Object) ); }); + + it('should emit TRACKER-error if fallbackClickThroughURL is not valid', () => { + vastTracker.click(1); + expect(spyEmit).toHaveBeenCalledWith('TRACKER-error', { + message: 'click parameters have the wrong type', + parameters: { fallbackClickThroughURL: 1, macros: {} }, + }); + }); + + it('should emit TRACKER-error if macros is not valid', () => { + vastTracker.click('https://fallbackurl.com', 'foo'); + expect(spyEmit).toHaveBeenCalledWith('TRACKER-error', { + message: 'click parameters have the wrong type', + parameters: { + fallbackClickThroughURL: 'https://fallbackurl.com', + macros: 'foo', + }, + }); + }); }); }); From 817a8ac66d598978a9270f8cf714ee686c7116c9 Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Wed, 4 Oct 2023 10:33:50 +0200 Subject: [PATCH 63/95] [tracker] adding error logs in public methods --- src/vast_tracker.js | 128 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 125 insertions(+), 3 deletions(-) diff --git a/src/vast_tracker.js b/src/vast_tracker.js index c6a8a8dc..a475f60d 100644 --- a/src/vast_tracker.js +++ b/src/vast_tracker.js @@ -151,6 +151,10 @@ export class VASTTracker extends EventEmitter { setDuration(duration) { // check if duration is a valid time input if (!util.isValidTimeValue(duration)) { + this.emit('TRACKER-error', { + message: 'the duration provided is not valid', + duration, + }); return; } this.assetDuration = duration; @@ -180,8 +184,14 @@ export class VASTTracker extends EventEmitter { setProgress(progress, macros = {}) { // check if progress is a valid time input if (!util.isValidTimeValue(progress) || typeof macros !== 'object') { + this.emit('TRACKER-error', { + message: 'setProgress parameters has the wrong type.', + parameter: { progress, macros }, + }); + return; } + const skipDelay = this.skipDelay || DEFAULT_SKIP_DELAY; if (skipDelay !== -1 && !this.skippable) { @@ -252,8 +262,13 @@ export class VASTTracker extends EventEmitter { */ setMuted(muted, macros = {}) { if (typeof muted !== 'boolean' || typeof macros !== 'object') { + this.emit('TRACKER-error', { + message: 'setMuted parameters have the wrong type', + parameters: { muted, macros }, + }); return; } + if (this.muted !== muted) { this.track(muted ? 'mute' : 'unmute', { macros }); } @@ -270,6 +285,10 @@ export class VASTTracker extends EventEmitter { */ setPaused(paused, macros = {}) { if (typeof paused !== 'boolean' || typeof macros !== 'object') { + this.emit('TRACKER-error', { + message: 'setPaused parameters have the wrong type', + parameters: { paused, macros }, + }); return; } if (this.paused !== paused) { @@ -288,6 +307,10 @@ export class VASTTracker extends EventEmitter { */ setFullscreen(fullscreen, macros = {}) { if (typeof fullscreen !== 'boolean' || typeof macros !== 'object') { + this.emit('TRACKER-error', { + message: 'setFullScreen parameters have the wrong type', + parameters: { fullscreen, macros }, + }); return; } if (this.fullscreen !== fullscreen) { @@ -308,8 +331,13 @@ export class VASTTracker extends EventEmitter { */ setExpand(expanded, macros = {}) { if (typeof expanded !== 'boolean' || typeof macros !== 'object') { + this.emit('TRACKER-error', { + message: 'setExpand parameters have the wrong type', + parameters: { expanded, macros }, + }); return; } + if (this.expanded !== expanded) { this.track(expanded ? 'expand' : 'collapse', { macros }); this.track(expanded ? 'playerExpand' : 'playerCollapse', { macros }); @@ -327,6 +355,10 @@ export class VASTTracker extends EventEmitter { */ setSkipDelay(duration) { if (!util.isValidTimeValue(duration)) { + this.emit('TRACKER-error', { + message: 'setSkipDelay parameter does not have a valid value', + parameter: duration, + }); return; } this.skipDelay = duration; @@ -339,6 +371,10 @@ export class VASTTracker extends EventEmitter { */ trackImpression(macros = {}) { if (typeof macros !== 'object') { + this.emit('TRACKER-error', { + message: 'trackImpression parameter has the wrong type', + parameter: macros, + }); return; } if (!this.impressed) { @@ -354,6 +390,10 @@ export class VASTTracker extends EventEmitter { */ trackViewableImpression(macros = {}) { if (typeof macros !== 'object') { + this.emit('TRACKER-error', { + message: 'trackViewableImpression parameter has the wrong type', + parameter: macros, + }); return; } this.ad.viewableImpression.forEach((impression) => { @@ -368,6 +408,10 @@ export class VASTTracker extends EventEmitter { trackNotViewableImpression(macros = {}) { if (typeof macros !== 'object') { + this.emit('TRACKER-error', { + message: 'trackNotViewableImpression parameter has the wrong type', + parameter: macros, + }); return; } @@ -382,6 +426,10 @@ export class VASTTracker extends EventEmitter { */ trackUndeterminedImpression(macros = {}) { if (typeof macros !== 'object') { + this.emit('TRACKER-error', { + message: 'trackUndeterminedImpression parameter has the wrong type', + parameter: macros, + }); return; } @@ -397,6 +445,10 @@ export class VASTTracker extends EventEmitter { */ error(macros = {}, isCustomCode = false) { if (typeof macros !== 'object' || typeof isCustomCode !== 'boolean') { + this.emit('TRACKER-error', { + message: 'error parameters have the wrong type', + parameters: { macros, isCustomCode }, + }); return; } this.trackURLs(this.ad.errorURLTemplates, macros, { isCustomCode }); @@ -411,6 +463,10 @@ export class VASTTracker extends EventEmitter { */ errorWithCode(errorCode, isCustomCode = false) { if (typeof errorCode !== 'string' || typeof isCustomCode !== 'boolean') { + this.emit('TRACKER-error', { + message: 'ErrorWithCode parameters have the wrong type', + parameters: { errorCode, isCustomCode }, + }); return; } this.error({ ERRORCODE: errorCode }, isCustomCode); @@ -429,6 +485,10 @@ export class VASTTracker extends EventEmitter { */ complete(macros = {}) { if (typeof macros !== 'object') { + this.emit('TRACKER-error', { + message: 'complete parameter has to wrong type', + parameter: macros, + }); return; } this.track('complete', { macros }); @@ -444,6 +504,10 @@ export class VASTTracker extends EventEmitter { */ notUsed(macros = {}) { if (typeof macros !== 'object') { + this.emit('TRACKER-error', { + message: 'notUsed parameter has to wrong type', + parameter: macros, + }); return; } this.track('notUsed', { macros }); @@ -461,6 +525,10 @@ export class VASTTracker extends EventEmitter { */ otherAdInteraction(macros = {}) { if (typeof macros !== 'object') { + this.emit('TRACKER-error', { + message: 'otherAdInteraction parameter has to wrong type', + parameter: macros, + }); return; } this.track('otherAdInteraction', { macros }); @@ -478,6 +546,10 @@ export class VASTTracker extends EventEmitter { */ acceptInvitation(macros = {}) { if (typeof macros !== 'object') { + this.emit('TRACKER-error', { + message: 'acceptInvitation parameter has to wrong type', + parameter: macros, + }); return; } this.track('acceptInvitation', { macros }); @@ -492,6 +564,10 @@ export class VASTTracker extends EventEmitter { */ adExpand(macros = {}) { if (typeof macros !== 'object') { + this.emit('TRACKER-error', { + message: 'adExpand parameter has to wrong type', + parameter: macros, + }); return; } this.track('adExpand', { macros }); @@ -506,6 +582,10 @@ export class VASTTracker extends EventEmitter { */ adCollapse(macros = {}) { if (typeof macros !== 'object') { + this.emit('TRACKER-error', { + message: 'adCollapse parameter has to wrong type', + parameter: macros, + }); return; } this.track('adCollapse', { macros }); @@ -520,6 +600,10 @@ export class VASTTracker extends EventEmitter { */ minimize(macros = {}) { if (typeof macros !== 'object') { + this.emit('TRACKER-error', { + message: 'minimize parameter has to wrong type', + parameter: macros, + }); return; } this.track('minimize', { macros }); @@ -536,6 +620,10 @@ export class VASTTracker extends EventEmitter { */ verificationNotExecuted(vendor, macros = {}) { if (typeof vendor !== 'string' || typeof macros !== 'object') { + this.emit('TRACKER-error', { + message: 'verificationNotExecuted parameters have to wrong type', + parameters: { vendor, macros }, + }); return; } if ( @@ -586,6 +674,10 @@ export class VASTTracker extends EventEmitter { */ overlayViewDuration(formattedDuration, macros = {}) { if (typeof formattedDuration !== 'string' || typeof macros !== 'object') { + this.emit('TRACKER-error', { + message: 'overlayViewDuration parameters have the wrong type', + parameters: { formattedDuration, macros }, + }); return; } macros['ADPLAYHEAD'] = formattedDuration; @@ -602,6 +694,10 @@ export class VASTTracker extends EventEmitter { */ close(macros = {}) { if (typeof macros !== 'object') { + this.emit('TRACKER-error', { + message: 'close parameter has the wrong type', + parameter: macros, + }); return; } this.track(this.linear ? 'closeLinear' : 'close', { macros }); @@ -615,6 +711,10 @@ export class VASTTracker extends EventEmitter { */ skip(macros = {}) { if (typeof macros !== 'object') { + this.emit('TRACKER-error', { + message: 'skip parameter has the wrong type', + parameter: macros, + }); return; } this.track('skip', { macros }); @@ -630,6 +730,10 @@ export class VASTTracker extends EventEmitter { */ load(macros = {}) { if (typeof macros !== 'object') { + this.emit('TRACKER-error', { + message: 'load parameter has the wrong type', + parameter: macros, + }); return; } this.track('loaded', { macros }); @@ -650,6 +754,10 @@ export class VASTTracker extends EventEmitter { typeof fallbackClickThroughURL !== 'string') || typeof macros !== 'object' ) { + this.emit('TRACKER-error', { + message: 'click parameters have the wrong type', + parameters: { fallbackClickThroughURL, macros }, + }); return; } if ( @@ -689,6 +797,10 @@ export class VASTTracker extends EventEmitter { */ track(eventName, { macros = {}, once = false } = {}) { if (typeof macros !== 'object') { + this.emit('TRACKER-error', { + message: 'track macros parameter has the wrong type', + parameter: macros, + }); return; } // closeLinear event was introduced in VAST 3.0 @@ -730,7 +842,15 @@ export class VASTTracker extends EventEmitter { * @param {Object} [options={}] - An optional Object of options to be used in the tracking calls. */ trackURLs(URLTemplates, macros = {}, options = {}) { - const validUrlTemplates = util.filterValidUrlTemplates(URLTemplates); + const { validUrls, unvalidUrls } = util.filterUrlTemplates(URLTemplates); + + if (unvalidUrls.length) { + this.emit('TRACKER-error', { + unvalidUrls, + message: 'Provided urls are malformed', + }); + } + //Avoid mutating the object received in parameters. const givenMacros = { ...macros }; if (this.linear) { @@ -771,11 +891,13 @@ export class VASTTracker extends EventEmitter { .join(','); } if (this.ad.blockedAdCategories && this.ad.blockedAdCategories.length) { - givenMacros['BLOCKEDADCATEGORIES'] = this.ad.blockedAdCategories; + givenMacros['BLOCKEDADCATEGORIES'] = this.ad.blockedAdCategories + .map((blockedCategorie) => blockedCategorie.value) + .join(','); } } - util.track(validUrlTemplates, givenMacros, options); + util.track(validUrls, givenMacros, options); } /** From d65171db77347133663142d2cc4c4e7223a167f6 Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Wed, 4 Oct 2023 10:38:52 +0200 Subject: [PATCH 64/95] [util] filtering url to return arrays of valid and unvalid url --- src/util/util.js | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/src/util/util.js b/src/util/util.js index dfdc01f3..48e65cd1 100644 --- a/src/util/util.js +++ b/src/util/util.js @@ -125,22 +125,34 @@ function extractURLsFromTemplates(URLTemplates) { } /** - * Filter URLTemplates elements to keep only valid and safe URL templates. + * Filter URLTemplates elements . * To be valid, urls should: * - have the same protocol as the client * or * - be protocol-relative urls * + * Returns an object with two arrays + * - validUrls : An array of valid URLs + * - invalidUrls: An array of invalid URLs + * * @param {Array} URLTemplates - A Array of string/object containing urls templates. + * @returns {Object} + * */ -function filterValidUrlTemplates(URLTemplates) { - if (Array.isArray(URLTemplates)) { - return URLTemplates.filter(urlTemplate => { - const url = urlTemplate.hasOwnProperty('url') ? urlTemplate.url : urlTemplate; - return isValidUrl(url); - }) - } - return isValidUrl(URLTemplates); +function filterUrlTemplates(URLTemplates) { + return URLTemplates.reduce( + (acc, urlTemplate) => { + const url = urlTemplate.hasOwnProperty('url') + ? urlTemplate.url + : urlTemplate; + + let urlVerification = isValidUrl(url); + + urlVerification ? acc.validUrls.push(url) : acc.unvalidUrls.push(url); + return acc; + }, + { validUrls: [], unvalidUrls: [] } + ); } function isValidUrl(url) { @@ -251,14 +263,14 @@ function joinArrayOfUniqueTemplateObjs(arr1 = [], arr2 = []) { * @return {Boolean} */ function isValidTimeValue(time) { - return Number.isFinite(time) && time >= -2 + return Number.isFinite(time) && time >= -2; } export const util = { track, resolveURLTemplates, extractURLsFromTemplates, - filterValidUrlTemplates, + filterUrlTemplates, containsTemplateObject, isTemplateObjectEqual, encodeURIComponentRFC3986, @@ -268,4 +280,5 @@ export const util = { joinArrayOfUniqueTemplateObjs, isValidTimeValue, addLeadingZeros, + isValidUrl, }; From 412b1efa13e5bd7573088b92d2aaa308d82ea941 Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Wed, 4 Oct 2023 10:50:55 +0200 Subject: [PATCH 65/95] [parser] adding error logs in case Wrapper or InLine node is malformed --- src/parser/ad_parser.js | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/parser/ad_parser.js b/src/parser/ad_parser.js index 26799d8c..390f6bff 100644 --- a/src/parser/ad_parser.js +++ b/src/parser/ad_parser.js @@ -25,10 +25,15 @@ export function parseAd( const childNodes = Array.from(adElement.childNodes); const filteredChildNodes = childNodes - .filter((childNode) => ['Wrapper', 'InLine'].includes(childNode.nodeName)) + .filter((childNode) => + ['inline', 'wrapper'].includes(childNode.nodeName.toLowerCase()) + ) .filter( (adType) => - !(adType.nodeName !== 'Wrapper' && followAdditionalWrappers === false) + !( + adType.nodeName.toLowerCase() === 'wrapper' && + followAdditionalWrappers === false + ) ); for (const node of filteredChildNodes) { @@ -43,6 +48,16 @@ export function parseAd( ad: parseInLine(node, emit, { allowMultipleAds }), type: 'INLINE', }; + } else { + const wrongNode = node.nodeName.toLowerCase(); + const message = + wrongNode === 'inline' + ? `<${node.nodeName}> must be written ` + : `<${node.nodeName}> must be written `; + emit('VAST-warning', { + message, + wrongNode: node, + }); } } } From 311201440ebac04b9827fb9a374d0c1f7d1cf7a2 Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Wed, 4 Oct 2023 10:52:33 +0200 Subject: [PATCH 66/95] [test] adding blockedAdCategories node to a sample file --- spec/samples/inline_trackers.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/spec/samples/inline_trackers.js b/spec/samples/inline_trackers.js index d88cbf4a..144d3385 100644 --- a/spec/samples/inline_trackers.js +++ b/spec/samples/inline_trackers.js @@ -41,6 +41,12 @@ export const inlineTrackersParsed = { value: 'Category-C', }, ], + blockedAdCategories: [ + { + authority: 'https://www.example.com/categoryauthority', + value: 'blockedAdCategory', + }, + ], survey: 'http://example.com/survey', errorURLTemplates: ['http://example.com/error_[ERRORCODE]'], impressionURLTemplates: [ From e6b6354cfdffdadcacd7e2e5e19af9cb9303e7d1 Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Wed, 24 Jan 2024 14:52:39 +0100 Subject: [PATCH 67/95] fix bug where wrapper where not parsed if creatives node was missing or empty using VASTParser --- src/parser/vast_parser.js | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/parser/vast_parser.js b/src/parser/vast_parser.js index 2afaf7e6..3b56aab7 100644 --- a/src/parser/vast_parser.js +++ b/src/parser/vast_parser.js @@ -452,14 +452,18 @@ export class VASTParser extends EventEmitter { // but no creative was found const ad = vastResponse.ads[index]; if (ad.errorCode || ad.creatives.length === 0) { - this.trackVastError( - ad.errorURLTemplates.concat(vastResponse.errorURLTemplates), - { ERRORCODE: ad.errorCode || 303 }, - { ERRORMESSAGE: ad.errorMessage || '' }, - { extensions: ad.extensions }, - { system: ad.system } - ); - vastResponse.ads.splice(index, 1); + // If VASTAdTagURI is in the vastResponse, it means we are dealing with a Wrapper when using parseVAST from the VASTParser. + // In that case, we dont want to modify the vastResponse since the creatives node is not required in a wrapper. + if (!ad.VASTAdTagURI) { + this.trackVastError( + ad.errorURLTemplates.concat(vastResponse.errorURLTemplates), + { ERRORCODE: ad.errorCode || 303 }, + { ERRORMESSAGE: ad.errorMessage || '' }, + { extensions: ad.extensions }, + { system: ad.system } + ); + vastResponse.ads.splice(index, 1); + } } } } From af7bcc0fce0a00003481b7737dff13d413957d10 Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Thu, 25 Jan 2024 11:17:29 +0100 Subject: [PATCH 68/95] correct typos errors --- src/vast_tracker.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/vast_tracker.js b/src/vast_tracker.js index a475f60d..14b840cb 100644 --- a/src/vast_tracker.js +++ b/src/vast_tracker.js @@ -486,7 +486,7 @@ export class VASTTracker extends EventEmitter { complete(macros = {}) { if (typeof macros !== 'object') { this.emit('TRACKER-error', { - message: 'complete parameter has to wrong type', + message: 'complete parameter has the wrong type', parameter: macros, }); return; @@ -505,7 +505,7 @@ export class VASTTracker extends EventEmitter { notUsed(macros = {}) { if (typeof macros !== 'object') { this.emit('TRACKER-error', { - message: 'notUsed parameter has to wrong type', + message: 'notUsed parameter has the wrong type', parameter: macros, }); return; @@ -526,7 +526,7 @@ export class VASTTracker extends EventEmitter { otherAdInteraction(macros = {}) { if (typeof macros !== 'object') { this.emit('TRACKER-error', { - message: 'otherAdInteraction parameter has to wrong type', + message: 'otherAdInteraction parameter has the wrong type', parameter: macros, }); return; @@ -547,7 +547,7 @@ export class VASTTracker extends EventEmitter { acceptInvitation(macros = {}) { if (typeof macros !== 'object') { this.emit('TRACKER-error', { - message: 'acceptInvitation parameter has to wrong type', + message: 'acceptInvitation parameter has the wrong type', parameter: macros, }); return; @@ -565,7 +565,7 @@ export class VASTTracker extends EventEmitter { adExpand(macros = {}) { if (typeof macros !== 'object') { this.emit('TRACKER-error', { - message: 'adExpand parameter has to wrong type', + message: 'adExpand parameter has the wrong type', parameter: macros, }); return; @@ -583,7 +583,7 @@ export class VASTTracker extends EventEmitter { adCollapse(macros = {}) { if (typeof macros !== 'object') { this.emit('TRACKER-error', { - message: 'adCollapse parameter has to wrong type', + message: 'adCollapse parameter has the wrong type', parameter: macros, }); return; @@ -601,7 +601,7 @@ export class VASTTracker extends EventEmitter { minimize(macros = {}) { if (typeof macros !== 'object') { this.emit('TRACKER-error', { - message: 'minimize parameter has to wrong type', + message: 'minimize parameter has the wrong type', parameter: macros, }); return; From c5940414ac625826f380189a2aa31259b1754e8f Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Thu, 25 Jan 2024 17:34:51 +0100 Subject: [PATCH 69/95] adding raw xml to the object returned by the urlhandler --- src/urlhandlers/xhr_url_handler.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/urlhandlers/xhr_url_handler.js b/src/urlhandlers/xhr_url_handler.js index 76280f3a..b1ee8d2a 100644 --- a/src/urlhandlers/xhr_url_handler.js +++ b/src/urlhandlers/xhr_url_handler.js @@ -22,7 +22,11 @@ async function handleResponse(response) { const xml = parser.parseFromString(textXml, 'text/xml'); return { xml, - details: { byteLength: textXml.length, statusCode: response.status }, + details: { + byteLength: textXml.length, + statusCode: response.status, + rawXml: textXml, + }, }; } From bf0e460fbb7e218b9129bb29584048fa017c0111 Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Fri, 26 Jan 2024 17:15:12 +0100 Subject: [PATCH 70/95] [test] fix inconsistent test --- spec/fetcher.spec.js | 4 +--- spec/vast_parser.spec.js | 26 +++++--------------------- spec/vast_trackers.spec.js | 14 +++++++------- 3 files changed, 13 insertions(+), 31 deletions(-) diff --git a/spec/fetcher.spec.js b/spec/fetcher.spec.js index 3ab1cd16..5b41bb4a 100644 --- a/spec/fetcher.spec.js +++ b/spec/fetcher.spec.js @@ -4,11 +4,9 @@ import * as Bitrate from '../src/parser/bitrate'; import { urlHandler } from '../src/urlhandlers/xhr_url_handler'; import { expect } from '@jest/globals'; import { getNodesFromXml } from './utils/utils'; -import { async } from 'regenerator-runtime'; describe('Fetcher', () => { - let fetcher, vastParser, mockEmit; - + let fetcher, mockEmit; const xml = getNodesFromXml(''); const urlHandlerSuccess = { get: (url, option) => diff --git a/spec/vast_parser.spec.js b/spec/vast_parser.spec.js index 110a500e..4c5828a8 100644 --- a/spec/vast_parser.spec.js +++ b/spec/vast_parser.spec.js @@ -9,17 +9,7 @@ import { VASTClient } from '../src/vast_client'; import { readFile } from 'fs/promises'; const xml = getNodesFromXml(`${linearAd}`, 'text/xml'); -const urlHandlerSuccess = { - get: (url, option) => - new Promise((resolve, _) => - resolve({ xml, details: { byteLength: 1234, statusCode: 200 } }) - ), -}; -const urlHandlerFailure = { - get: (url, option) => - new Promise((_, reject) => reject({ error: new Error('error') })), -}; const nodeUrlHandler = { get: async (file) => { try { @@ -136,7 +126,6 @@ describe('VASTParser', () => { url: inlineInvalidVastUrl, wrapperDepth: 0, }); - expect(true).toBeFalsy(); } catch (e) { expect(e.message).toBe('Invalid VAST XMLDocument'); expect(VastParser.emit).toHaveBeenLastCalledWith('VAST-ad-parsed', { @@ -230,13 +219,9 @@ describe('VASTParser', () => { wrapperSequence: 1, wrapperDepth: 0, isRootVAST: true, - }) - .then(() => { - expect(true).toBeFalsy(); - }) - .catch((e) => { - expect(e.message).toBe('Invalid VAST XMLDocument'); - }); + }).catch((e) => { + expect(e.message).toBe('Invalid VAST XMLDocument'); + }); }); it('resolves first ad and saves remaining ads if resolveAll is false', () => { @@ -373,7 +358,7 @@ describe('VASTParser', () => { `http://example.com/empty-no-ad`, 'text/xml' ); - VastParser.parseVAST(vast, options) + VastParser.parseVAST(vast) .then((response) => { // Response doesn't have any ads expect(response.ads).toEqual([]); @@ -642,7 +627,7 @@ describe('VASTParser', () => { beforeEach(() => { VastParser.previousUrl = wrapperAVastUrl; - VastParser.initParsingStatus({ urlHandler: urlHandlerSuccess }); + VastParser.initParsingStatus(); }); it('resolves with ad if there is no more wrappers', () => { @@ -697,7 +682,6 @@ describe('VASTParser', () => { }); it('will pass timeout error to ad if fetching next wrapper fails', () => { - fetcher.setOptions({ urlHandler: urlHandlerFailure }); const adWithWrapper = { ...ad, nextWrapperURL: wrapperBVastUrl }; jest.spyOn(fetcher, 'fetchVAST').mockImplementation(() => { return Promise.reject(new Error('timeout')); diff --git a/spec/vast_trackers.spec.js b/spec/vast_trackers.spec.js index 7d8e5468..405f5e54 100644 --- a/spec/vast_trackers.spec.js +++ b/spec/vast_trackers.spec.js @@ -196,7 +196,7 @@ describe('VASTTracker', function () { it('should emit TRACKER-error if macros is not valid', () => { vastTracker.minimize('foo'); expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { - message: 'minimize parameter has to wrong type', + message: 'minimize parameter has the wrong type', parameter: 'foo', }); }); @@ -277,7 +277,7 @@ describe('VASTTracker', function () { it('should emit TRACKER-error if macros is not valid', () => { vastTracker.otherAdInteraction('foo'); expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { - message: 'otherAdInteraction parameter has to wrong type', + message: 'otherAdInteraction parameter has the wrong type', parameter: 'foo', }); }); @@ -302,7 +302,7 @@ describe('VASTTracker', function () { it('should emit TRACKER-error if macros is not valid', () => { vastTracker.acceptInvitation('foo'); expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { - message: 'acceptInvitation parameter has to wrong type', + message: 'acceptInvitation parameter has the wrong type', parameter: 'foo', }); }); @@ -327,7 +327,7 @@ describe('VASTTracker', function () { it('should emit TRACKER-error if macros is not valid', () => { vastTracker.adExpand('foo'); expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { - message: 'adExpand parameter has to wrong type', + message: 'adExpand parameter has the wrong type', parameter: 'foo', }); }); @@ -348,7 +348,7 @@ describe('VASTTracker', function () { it('should emit TRACKER-error if macros is not valid', () => { vastTracker.complete('foo'); expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { - message: 'complete parameter has to wrong type', + message: 'complete parameter has the wrong type', parameter: 'foo', }); }); @@ -429,7 +429,7 @@ describe('VASTTracker', function () { it('should emit TRACKER-error if macros is not valid', () => { vastTracker.adCollapse('foo'); expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { - message: 'adCollapse parameter has to wrong type', + message: 'adCollapse parameter has the wrong type', parameter: 'foo', }); }); @@ -497,7 +497,7 @@ describe('VASTTracker', function () { it('should emit TRACKER-error if macros is not valid', () => { vastTracker.notUsed('foo'); expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { - message: 'notUsed parameter has to wrong type', + message: 'notUsed parameter has the wrong type', parameter: 'foo', }); }); From d470fd7bb20314002402f6dd0aed94f51f310283 Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Mon, 18 Mar 2024 12:45:08 +0100 Subject: [PATCH 71/95] [test] fix comments related to tests --- spec/fetcher.spec.js | 40 +++++--- spec/util.spec.js | 12 +-- spec/vast_client.spec.js | 1 - spec/vast_parser.spec.js | 2 +- spec/vast_trackers.spec.js | 192 +++++++++++++++---------------------- 5 files changed, 110 insertions(+), 137 deletions(-) diff --git a/spec/fetcher.spec.js b/spec/fetcher.spec.js index 5b41bb4a..7c2f3fbf 100644 --- a/spec/fetcher.spec.js +++ b/spec/fetcher.spec.js @@ -1,7 +1,7 @@ -import { Fetcher } from '../src/fetcher'; +import { Fetcher } from '../src/fetcher/fetcher'; import { VASTParser } from '../src/parser/vast_parser'; import * as Bitrate from '../src/parser/bitrate'; -import { urlHandler } from '../src/urlhandlers/xhr_url_handler'; +import { urlHandler } from '../src/fetcher/url_handler'; import { expect } from '@jest/globals'; import { getNodesFromXml } from './utils/utils'; @@ -17,7 +17,9 @@ describe('Fetcher', () => { const urlHandlerFailure = { get: (url, option) => - new Promise((_, reject) => reject({ error: new Error('error') })), + new Promise((_, reject) => + reject({ error: new Error('AbortError'), statusCode: 408 }) + ), }; beforeEach(() => { @@ -122,13 +124,21 @@ describe('Fetcher', () => { .catch(() => { expect(urlHandlerSpy).toHaveBeenCalledWith( expectedUrl, - expect.anything(), expect.anything() ); }); }); it('should emits VAST-resolving and VAST-resolved events with a filtered url', () => { + fetcher.setOptions({}); + + // We need to mock global.fetch since jest does not handle it. + global.fetch = jest.fn().mockResolvedValue({ + ok: false, + status: 500, + statusText: 'internal server error', + }); + fetcher.URLTemplateFilters = [(url) => url.replace('foo', 'bar')]; const expectedUrl = 'www.bar.foo'; @@ -151,8 +161,8 @@ describe('Fetcher', () => { expect(mockEmit).toHaveBeenNthCalledWith(2, 'VAST-resolved', { url: expectedUrl, duration: expect.any(Number), - error: new Error('timeout'), - statusCode: 408, + error: expect.any(Error), + statusCode: 500, previousUrl: null, wrapperDepth: 0, }); @@ -160,15 +170,15 @@ describe('Fetcher', () => { }); it('should rejects with error', () => { - let result = fetcher - .fetchVAST({ - url: url, - maxWrapperDepth: 5, - emitter: () => {}, - }) - .catch(() => { - return expect(result).rejects.toEqual('error'); - }); + let result = fetcher.fetchVAST({ + url: url, + maxWrapperDepth: 5, + emitter: () => {}, + }); + return expect(result).rejects.toEqual({ + error: new Error('AbortError'), + statusCode: 408, + }); }); }); }); diff --git a/spec/util.spec.js b/spec/util.spec.js index 7fa41025..b101c537 100644 --- a/spec/util.spec.js +++ b/spec/util.spec.js @@ -375,7 +375,7 @@ describe('util', function () { }); }); describe('#filterUrlTemplates', function () { - it('should filtered valid and unvalid urls', () => { + it('should filtered valid and invalid urls', () => { const urlsToFilter = [ { id: 'sample-impression1', @@ -399,13 +399,13 @@ describe('util', function () { }, ]; - const { validUrls, unvalidUrls } = util.filterUrlTemplates(urlsToFilter); + const { validUrls, invalidUrls } = util.filterUrlTemplates(urlsToFilter); expect(validUrls.length).toBe(3); - expect(unvalidUrls.length).toBe(2); + expect(invalidUrls.length).toBe(2); }); }); - describe('#isValidUrls', function () { + describe('#isValidUrl', function () { it('should return true if the url is valid', () => { const url = 'http://example.com'; const validUrl = util.isValidUrl(url); @@ -414,8 +414,8 @@ describe('util', function () { it('should return false if the url is not valid', () => { const url = 'example.com'; - const validUrl = util.isValidUrl(url); - expect(validUrl).toBe(false); + const isValidUrl = util.isValidUrl(url); + expect(isValidUrl).toBe(false); }); }); }); diff --git a/spec/vast_client.spec.js b/spec/vast_client.spec.js index 22634a34..6283c8bf 100644 --- a/spec/vast_client.spec.js +++ b/spec/vast_client.spec.js @@ -1,6 +1,5 @@ import { VASTClient } from '../src/vast_client'; import { readFile } from 'fs/promises'; -import { afterAll } from '@jest/globals'; const wrapperMultipleAdsVastUrl = './spec/samples/wrapper-multiple-ads.xml'; const emptyVastUrl = './spec/samples/empty-no-ad.xml'; diff --git a/spec/vast_parser.spec.js b/spec/vast_parser.spec.js index 4c5828a8..98433aa2 100644 --- a/spec/vast_parser.spec.js +++ b/spec/vast_parser.spec.js @@ -3,7 +3,7 @@ import { urlFor, getNodesFromXml } from './utils/utils'; import { util } from '../src/util/util'; import { parserUtils } from '../src/parser/parser_utils'; import * as Bitrate from '../src/parser/bitrate'; -import { Fetcher } from '../src/fetcher'; +import { Fetcher } from '../src/fetcher/fetcher'; import { linearAd } from './samples/linear_ads'; import { VASTClient } from '../src/vast_client'; import { readFile } from 'fs/promises'; diff --git a/spec/vast_trackers.spec.js b/spec/vast_trackers.spec.js index 405f5e54..ff2dd5d0 100644 --- a/spec/vast_trackers.spec.js +++ b/spec/vast_trackers.spec.js @@ -12,6 +12,7 @@ describe('VASTTracker', function () { let spyEmitter; let spyTrackUrl; let spyTrack; + const wrongTrackerValue = 'foo'; describe('#linear', () => { let adTrackingUrls = ad.creatives[0].trackingEvents; @@ -90,8 +91,7 @@ describe('VASTTracker', function () { vastTracker.trackURLs(urlTemplates); expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { - unvalidUrls: ['example.com'], - message: 'Provided urls are malformed', + message: 'Provided urls are malformed. url: example.com', }); }); }); @@ -151,10 +151,9 @@ describe('VASTTracker', function () { }); it('should emit TRACKER-error if macros is not valid', () => { - vastTracker.track('start', { macros: 'foo', once: false }); + vastTracker.track('start', { macros: wrongTrackerValue, once: false }); expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { - message: 'track macros parameter has the wrong type', - parameter: 'foo', + message: `track given macros has the wrong type. macros: ${wrongTrackerValue}`, }); }); }); @@ -194,10 +193,9 @@ describe('VASTTracker', function () { ); }); it('should emit TRACKER-error if macros is not valid', () => { - vastTracker.minimize('foo'); + vastTracker.minimize(wrongTrackerValue); expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { - message: 'minimize parameter has the wrong type', - parameter: 'foo', + message: `minimize given macros has the wrong type. macros: ${wrongTrackerValue}`, }); }); }); @@ -215,16 +213,15 @@ describe('VASTTracker', function () { it('should emit TRACKER-error if vendor is not valid', () => { vastTracker.verificationNotExecuted(1); expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { - message: 'verificationNotExecuted parameters have to wrong type', - parameters: { vendor: 1, macros: {} }, + message: + 'One given verificationNotExecuted parameter has to wrong type. vendor: 1, macros: {}', }); }); it('should emit TRACKER-error if macro is not valid', () => { - vastTracker.verificationNotExecuted('vendor', 'foo'); + vastTracker.verificationNotExecuted('vendor', wrongTrackerValue); expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { - message: 'verificationNotExecuted parameters have to wrong type', - parameters: { vendor: 'vendor', macros: 'foo' }, + message: `One given verificationNotExecuted parameter has to wrong type. vendor: vendor, macros: ${wrongTrackerValue}`, }); }); @@ -275,10 +272,9 @@ describe('VASTTracker', function () { ); }); it('should emit TRACKER-error if macros is not valid', () => { - vastTracker.otherAdInteraction('foo'); + vastTracker.otherAdInteraction(wrongTrackerValue); expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { - message: 'otherAdInteraction parameter has the wrong type', - parameter: 'foo', + message: `otherAdInteraction given macros has the wrong type. macros: ${wrongTrackerValue}`, }); }); }); @@ -300,10 +296,9 @@ describe('VASTTracker', function () { ); }); it('should emit TRACKER-error if macros is not valid', () => { - vastTracker.acceptInvitation('foo'); + vastTracker.acceptInvitation(wrongTrackerValue); expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { - message: 'acceptInvitation parameter has the wrong type', - parameter: 'foo', + message: `acceptInvitation given macros has the wrong type. macros: ${wrongTrackerValue}`, }); }); }); @@ -325,10 +320,9 @@ describe('VASTTracker', function () { ); }); it('should emit TRACKER-error if macros is not valid', () => { - vastTracker.adExpand('foo'); + vastTracker.adExpand(wrongTrackerValue); expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { - message: 'adExpand parameter has the wrong type', - parameter: 'foo', + message: `adExpand given macros has the wrong type. macros: ${wrongTrackerValue}`, }); }); }); @@ -346,10 +340,9 @@ describe('VASTTracker', function () { }); it('should emit TRACKER-error if macros is not valid', () => { - vastTracker.complete('foo'); + vastTracker.complete(wrongTrackerValue); expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { - message: 'complete parameter has the wrong type', - parameter: 'foo', + message: `complete given macros has the wrong type. macros: ${wrongTrackerValue}`, }); }); }); @@ -368,10 +361,9 @@ describe('VASTTracker', function () { ); }); it('should emit TRACKER-error if macros is not valid', () => { - vastTracker.close('foo'); + vastTracker.close(wrongTrackerValue); expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { - message: 'close parameter has the wrong type', - parameter: 'foo', + message: `close given macros has the wrong type. macros: ${wrongTrackerValue}`, }); }); }); @@ -385,10 +377,9 @@ describe('VASTTracker', function () { expect(spyTrack).toHaveBeenCalledWith('skip', expect.any(Object)); }); it('should emit TRACKER-error if macros is not valid', () => { - vastTracker.skip('foo'); + vastTracker.skip(wrongTrackerValue); expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { - message: 'skip parameter has the wrong type', - parameter: 'foo', + message: `skip given macros has the wrong type. macros: ${wrongTrackerValue}`, }); }); }); @@ -402,10 +393,9 @@ describe('VASTTracker', function () { expect(spyTrack).toHaveBeenCalledWith('loaded', expect.any(Object)); }); it('should emit TRACKER-error if macros is not valid', () => { - vastTracker.load('foo'); + vastTracker.load(wrongTrackerValue); expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { - message: 'load parameter has the wrong type', - parameter: 'foo', + message: `load given macros has the wrong type. macros: ${wrongTrackerValue}`, }); }); }); @@ -427,10 +417,9 @@ describe('VASTTracker', function () { ); }); it('should emit TRACKER-error if macros is not valid', () => { - vastTracker.adCollapse('foo'); + vastTracker.adCollapse(wrongTrackerValue); expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { - message: 'adCollapse parameter has the wrong type', - parameter: 'foo', + message: `adCollapse given macros has the wrong type. macros: ${wrongTrackerValue}`, }); }); }); @@ -463,15 +452,14 @@ describe('VASTTracker', function () { it('should emit TRACKER-error if formattedDuration is not valid', () => { vastTracker.overlayViewDuration(1); expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { - message: 'overlayViewDuration parameters have the wrong type', - parameters: { formattedDuration: 1, macros: {} }, + message: + 'One given overlayViewDuration parameters has the wrong type. formattedDuration: 1, macros: {}', }); }); it('should emit TRACKER-error if macros is not valid', () => { - vastTracker.overlayViewDuration('00:00:12', 'foo'); + vastTracker.overlayViewDuration('00:00:12', wrongTrackerValue); expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { - message: 'overlayViewDuration parameters have the wrong type', - parameters: { formattedDuration: '00:00:12', macros: 'foo' }, + message: `One given overlayViewDuration parameters has the wrong type. formattedDuration: 00:00:12, macros: ${wrongTrackerValue}`, }); }); }); @@ -495,10 +483,9 @@ describe('VASTTracker', function () { }); it('should emit TRACKER-error if macros is not valid', () => { - vastTracker.notUsed('foo'); + vastTracker.notUsed(wrongTrackerValue); expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { - message: 'notUsed parameter has the wrong type', - parameter: 'foo', + message: `notUsed given macros has the wrong type. macros: ${wrongTrackerValue}`, }); }); }); @@ -512,10 +499,9 @@ describe('VASTTracker', function () { }); it('should emit TRACKER-error if duration is not valid', () => { - vastTracker.setDuration('foo'); + vastTracker.setDuration(wrongTrackerValue); expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { - message: 'the duration provided is not valid', - duration: 'foo', + message: `the duration provided is not valid. duration: ${wrongTrackerValue}`, }); }); }); @@ -599,19 +585,17 @@ describe('VASTTracker', function () { ); }); - it('should emit TRACKER-error if progress is unvalid', () => { - vastTracker.setProgress('foo'); + it('should emit TRACKER-error if progress is invalid', () => { + vastTracker.setProgress(wrongTrackerValue); expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { - message: 'setProgress parameters has the wrong type.', - parameter: { progress: 'foo', macros: {} }, + message: `One given setProgress parameter has the wrong type. progress: ${wrongTrackerValue}, macros: {}`, }); }); - it('should emit TRACKER-error if macros is unvalid', () => { - vastTracker.setProgress(12, 'foo'); + it('should emit TRACKER-error if macros is invalid', () => { + vastTracker.setProgress(12, wrongTrackerValue); expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { - message: 'setProgress parameters has the wrong type.', - parameter: { progress: 12, macros: 'foo' }, + message: `One given setProgress parameter has the wrong type. progress: 12, macros: ${wrongTrackerValue}`, }); }); }); @@ -655,18 +639,16 @@ describe('VASTTracker', function () { }); it('should emit TRACKER-error if muted is not valid', () => { - vastTracker.setMuted('foo'); + vastTracker.setMuted(wrongTrackerValue); expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { - message: 'setMuted parameters have the wrong type', - parameters: { muted: 'foo', macros: {} }, + message: `One given setMuted parameter has the wrong type. muted: ${wrongTrackerValue}, macros: {}`, }); }); it('should emit TRACKER-error if macros is not valid', () => { - vastTracker.setMuted(true, 'foo'); + vastTracker.setMuted(true, wrongTrackerValue); expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { - message: 'setMuted parameters have the wrong type', - parameters: { muted: true, macros: 'foo' }, + message: `One given setMuted parameter has the wrong type. muted: true, macros: ${wrongTrackerValue}`, }); }); }); @@ -692,18 +674,16 @@ describe('VASTTracker', function () { }); it('should emit TRACKER-error if paused is not valid', () => { - vastTracker.setPaused('foo'); + vastTracker.setPaused(wrongTrackerValue); expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { - message: 'setPaused parameters have the wrong type', - parameters: { paused: 'foo', macros: {} }, + message: `One given setPaused parameter has the wrong type. paused: ${wrongTrackerValue}, macros: {}`, }); }); it('should emit TRACKER-error if macros is not valid', () => { - vastTracker.setPaused(true, 'foo'); + vastTracker.setPaused(true, wrongTrackerValue); expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { - message: 'setPaused parameters have the wrong type', - parameters: { paused: true, macros: 'foo' }, + message: `One given setPaused parameter has the wrong type. paused: true, macros: ${wrongTrackerValue}`, }); }); }); @@ -731,18 +711,16 @@ describe('VASTTracker', function () { }); it('should emit TRACKER-error if fullscreen is not valid', () => { - vastTracker.setFullscreen('foo'); + vastTracker.setFullscreen(wrongTrackerValue); expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { - message: 'setFullScreen parameters have the wrong type', - parameters: { fullscreen: 'foo', macros: {} }, + message: `One given setFullScreen parameter has the wrong type. fullscreen: ${wrongTrackerValue}, macros: {}`, }); }); it('should emit TRACKER-error if macros is not valid', () => { - vastTracker.setFullscreen(true, 'foo'); + vastTracker.setFullscreen(true, wrongTrackerValue); expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { - message: 'setFullScreen parameters have the wrong type', - parameters: { fullscreen: true, macros: 'foo' }, + message: `One given setFullScreen parameter has the wrong type. fullscreen: true, macros: ${wrongTrackerValue}`, }); }); }); @@ -775,18 +753,16 @@ describe('VASTTracker', function () { }); it('should emit TRACKER-error if expanded is not valid', () => { - vastTracker.setExpand('foo'); + vastTracker.setExpand(wrongTrackerValue); expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { - message: 'setExpand parameters have the wrong type', - parameters: { expanded: 'foo', macros: {} }, + message: `One given setExpand parameter has the wrong type. expanded: ${wrongTrackerValue}, macros: {}`, }); }); it('should emit TRACKER-error if macros is not valid', () => { - vastTracker.setExpand(true, 'foo'); + vastTracker.setExpand(true, wrongTrackerValue); expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { - message: 'setExpand parameters have the wrong type', - parameters: { expanded: true, macros: 'foo' }, + message: `One given setExpand parameter has the wrong type. expanded: true, macros: ${wrongTrackerValue}`, }); }); }); @@ -808,10 +784,9 @@ describe('VASTTracker', function () { }); it('should emit TRACKER-error if duration is not valid', () => { - vastTracker.setSkipDelay('foo'); + vastTracker.setSkipDelay(wrongTrackerValue); expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { - message: 'setSkipDelay parameter does not have a valid value', - parameter: 'foo', + message: `setSkipDelay parameter does not have a valid value. duration: ${wrongTrackerValue}`, }); }); }); @@ -849,10 +824,9 @@ describe('VASTTracker', function () { }); it('should emit TRACKER-error if macros is not valid', () => { - vastTracker.trackImpression('foo'); + vastTracker.trackImpression(wrongTrackerValue); expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { - message: 'trackImpression parameter has the wrong type', - parameter: 'foo', + message: `trackImpression parameter has the wrong type. macros: ${wrongTrackerValue}`, }); }); }); @@ -872,10 +846,9 @@ describe('VASTTracker', function () { }); it('should emit TRACKER-error if macros is not valid ', () => { - vastTracker.trackViewableImpression('foo'); + vastTracker.trackViewableImpression(wrongTrackerValue); expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { - message: 'trackViewableImpression parameter has the wrong type', - parameter: 'foo', + message: `trackViewableImpression given macros has the wrong type. macros: ${wrongTrackerValue}`, }); }); }); @@ -893,10 +866,9 @@ describe('VASTTracker', function () { }); it('should emit TRACKER-error if macros is not valid ', () => { - vastTracker.trackNotViewableImpression('foo'); + vastTracker.trackNotViewableImpression(wrongTrackerValue); expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { - message: 'trackNotViewableImpression parameter has the wrong type', - parameter: 'foo', + message: `trackNotViewableImpression given macros has the wrong type. macros: ${wrongTrackerValue}`, }); }); }); @@ -914,10 +886,9 @@ describe('VASTTracker', function () { }); it('should emit TRACKER-error if macros is not valid ', () => { - vastTracker.trackUndeterminedImpression('foo'); + vastTracker.trackUndeterminedImpression(wrongTrackerValue); expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { - message: 'trackUndeterminedImpression parameter has the wrong type', - parameter: 'foo', + message: `trackUndeterminedImpression given macros has the wrong type. macros: ${wrongTrackerValue}`, }); }); }); @@ -943,18 +914,16 @@ describe('VASTTracker', function () { }); it('should emit TRACKER-error if macros is not valid', () => { - vastTracker.error('foo'); + vastTracker.error(wrongTrackerValue); expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { - message: 'error parameters have the wrong type', - parameters: { macros: 'foo', isCustomCode: false }, + message: `One given error parameter has the wrong type. macros: ${wrongTrackerValue}, isCustomCode: false`, }); }); it('should emit TRACKER-error if isCustomCode is not valid', () => { - vastTracker.error({}, 'foo'); + vastTracker.error({}, wrongTrackerValue); expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { - message: 'error parameters have the wrong type', - parameters: { macros: {}, isCustomCode: 'foo' }, + message: `One given error parameter has the wrong type. macros: {}, isCustomCode: ${wrongTrackerValue}`, }); }); }); @@ -969,15 +938,14 @@ describe('VASTTracker', function () { it('should emit TRACKER-error if errorCode is not valid', () => { vastTracker.errorWithCode(1); expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { - message: 'ErrorWithCode parameters have the wrong type', - parameters: { errorCode: 1, isCustomCode: false }, + message: + 'One given errorWithCode parameter has the wrong type. errorCode: 1, isCustomCode: false', }); }); it('should emit TRACKER-error if macros is not valid', () => { - vastTracker.errorWithCode('303', 'foo'); + vastTracker.errorWithCode('303', wrongTrackerValue); expect(spyEmitter).toHaveBeenCalledWith('TRACKER-error', { - message: 'ErrorWithCode parameters have the wrong type', - parameters: { errorCode: '303', isCustomCode: 'foo' }, + message: `One given errorWithCode parameter has the wrong type. errorCode: 303, isCustomCode: ${wrongTrackerValue}`, }); }); }); @@ -1014,19 +982,15 @@ describe('VASTTracker', function () { it('should emit TRACKER-error if fallbackClickThroughURL is not valid', () => { vastTracker.click(1); expect(spyEmit).toHaveBeenCalledWith('TRACKER-error', { - message: 'click parameters have the wrong type', - parameters: { fallbackClickThroughURL: 1, macros: {} }, + message: + 'One given click parameter has the wrong type. fallbackClickThroughURL: 1, macros: {}', }); }); it('should emit TRACKER-error if macros is not valid', () => { - vastTracker.click('https://fallbackurl.com', 'foo'); + vastTracker.click('https://fallbackurl.com', wrongTrackerValue); expect(spyEmit).toHaveBeenCalledWith('TRACKER-error', { - message: 'click parameters have the wrong type', - parameters: { - fallbackClickThroughURL: 'https://fallbackurl.com', - macros: 'foo', - }, + message: `One given click parameter has the wrong type. fallbackClickThroughURL: https://fallbackurl.com, macros: ${wrongTrackerValue}`, }); }); }); From c359caded78c1a758cc5e0eb2f12706db86b8ac6 Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Mon, 18 Mar 2024 12:48:25 +0100 Subject: [PATCH 72/95] [parser] fix comments related to the vastparser --- src/parser/ad_parser.js | 68 ++++++++++++++++----------------------- src/parser/vast_parser.js | 20 ++++++------ 2 files changed, 37 insertions(+), 51 deletions(-) diff --git a/src/parser/ad_parser.js b/src/parser/ad_parser.js index 390f6bff..b0fce2f1 100644 --- a/src/parser/ad_parser.js +++ b/src/parser/ad_parser.js @@ -24,17 +24,13 @@ export function parseAd( ) { const childNodes = Array.from(adElement.childNodes); - const filteredChildNodes = childNodes - .filter((childNode) => - ['inline', 'wrapper'].includes(childNode.nodeName.toLowerCase()) - ) - .filter( - (adType) => - !( - adType.nodeName.toLowerCase() === 'wrapper' && - followAdditionalWrappers === false - ) + const filteredChildNodes = childNodes.filter((childNode) => { + const childNodeToLowerCase = childNode.nodeName.toLowerCase(); + return ( + childNodeToLowerCase === 'inline' || + (followAdditionalWrappers !== false && childNodeToLowerCase === 'wrapper') ); + }); for (const node of filteredChildNodes) { parserUtils.copyNodeAttribute('id', adElement, node); @@ -48,17 +44,17 @@ export function parseAd( ad: parseInLine(node, emit, { allowMultipleAds }), type: 'INLINE', }; - } else { - const wrongNode = node.nodeName.toLowerCase(); - const message = - wrongNode === 'inline' - ? `<${node.nodeName}> must be written ` - : `<${node.nodeName}> must be written `; - emit('VAST-warning', { - message, - wrongNode: node, - }); } + + const wrongNode = node.nodeName.toLowerCase(); + const message = + wrongNode === 'inline' + ? `<${node.nodeName}> must be written ` + : `<${node.nodeName}> must be written `; + emit('VAST-warning', { + message, + wrongNode: node, + }); } } @@ -327,26 +323,18 @@ export function _parseAdVerifications(verifications) { parserUtils.assignAttributes(verificationNode.attributes, verification); - childNodes - .filter(({ nodeName }) => - [ - 'JavaScriptResource', - 'ExecutableResource', - 'VerificationParameters', - ].includes(nodeName) - ) - .forEach(({ nodeName, textContent, attributes }) => { - switch (nodeName) { - case 'JavaScriptResource': - case 'ExecutableResource': - verification.resource = textContent.trim(); - parserUtils.assignAttributes(attributes, verification); - break; - case 'VerificationParameters': - verification.parameters = textContent.trim(); - break; - } - }); + childNodes.forEach(({ nodeName, textContent, attributes }) => { + switch (nodeName) { + case 'JavaScriptResource': + case 'ExecutableResource': + verification.resource = textContent.trim(); + parserUtils.assignAttributes(attributes, verification); + break; + case 'VerificationParameters': + verification.parameters = textContent.trim(); + break; + } + }); const trackingEventsElement = parserUtils.childByName( verificationNode, diff --git a/src/parser/vast_parser.js b/src/parser/vast_parser.js index 3b56aab7..2142142b 100644 --- a/src/parser/vast_parser.js +++ b/src/parser/vast_parser.js @@ -451,19 +451,17 @@ export class VASTParser extends EventEmitter { // - No Creative case - The parser has dealt with soma or/and an elements // but no creative was found const ad = vastResponse.ads[index]; - if (ad.errorCode || ad.creatives.length === 0) { + if ((ad.errorCode || ad.creatives.length === 0) && !ad.VASTAdTagURI) { // If VASTAdTagURI is in the vastResponse, it means we are dealing with a Wrapper when using parseVAST from the VASTParser. // In that case, we dont want to modify the vastResponse since the creatives node is not required in a wrapper. - if (!ad.VASTAdTagURI) { - this.trackVastError( - ad.errorURLTemplates.concat(vastResponse.errorURLTemplates), - { ERRORCODE: ad.errorCode || 303 }, - { ERRORMESSAGE: ad.errorMessage || '' }, - { extensions: ad.extensions }, - { system: ad.system } - ); - vastResponse.ads.splice(index, 1); - } + this.trackVastError( + ad.errorURLTemplates.concat(vastResponse.errorURLTemplates), + { ERRORCODE: ad.errorCode || 303 }, + { ERRORMESSAGE: ad.errorMessage || '' }, + { extensions: ad.extensions }, + { system: ad.system } + ); + vastResponse.ads.splice(index, 1); } } } From 904e794330fa5363e6e6d94fb58faabc9a684ed3 Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Mon, 18 Mar 2024 12:49:57 +0100 Subject: [PATCH 73/95] create folder for fetching related file --- src/{urlhandlers => fetcher}/consts.js | 0 src/{ => fetcher}/fetcher.js | 8 ++--- .../url_handler.js} | 32 +++++++++---------- 3 files changed, 19 insertions(+), 21 deletions(-) rename src/{urlhandlers => fetcher}/consts.js (100%) rename src/{ => fetcher}/fetcher.js (94%) rename src/{urlhandlers/xhr_url_handler.js => fetcher/url_handler.js} (78%) diff --git a/src/urlhandlers/consts.js b/src/fetcher/consts.js similarity index 100% rename from src/urlhandlers/consts.js rename to src/fetcher/consts.js diff --git a/src/fetcher.js b/src/fetcher/fetcher.js similarity index 94% rename from src/fetcher.js rename to src/fetcher/fetcher.js index 61cd1466..eeffbd70 100644 --- a/src/fetcher.js +++ b/src/fetcher/fetcher.js @@ -1,6 +1,6 @@ -import { updateEstimatedBitrate } from './parser/bitrate'; -import { urlHandler } from './urlhandlers/xhr_url_handler'; -import { DEFAULT_TIMEOUT } from './urlhandlers/consts'; +import { updateEstimatedBitrate } from '../parser/bitrate'; +import { urlHandler } from './url_handler'; +import { DEFAULT_TIMEOUT } from './consts'; /** * This class provides a method to fetch a VAST document @@ -114,7 +114,7 @@ export class Fetcher { resolve(data.xml); } } catch (error) { - console.error(error); + reject(error); } }); } diff --git a/src/urlhandlers/xhr_url_handler.js b/src/fetcher/url_handler.js similarity index 78% rename from src/urlhandlers/xhr_url_handler.js rename to src/fetcher/url_handler.js index b1ee8d2a..e1b0b272 100644 --- a/src/urlhandlers/xhr_url_handler.js +++ b/src/fetcher/url_handler.js @@ -1,13 +1,5 @@ import { DEFAULT_TIMEOUT } from './consts'; -const nodeDOMParser = require('@xmldom/xmldom').DOMParser; - -/** - * Check if we are in a browser environment - * @returns {Boolean} - */ -function isBrowserEnvironment() { - return typeof window !== 'undefined'; -} +import { util } from '../util/util'; /** * Return an object containing an XML document. @@ -17,8 +9,15 @@ function isBrowserEnvironment() { */ async function handleResponse(response) { const textXml = await response.text(); + let parser; + + if (!util.isBrowserEnvironment()) { + const xmlDom = await import('@xmldom/xmldom'); + parser = new xmlDom.DOMParser(); + } else { + parser = new DOMParser(); + } - const parser = isBrowserEnvironment() ? new DOMParser() : new nodeDOMParser(); const xml = parser.parseFromString(textXml, 'text/xml'); return { xml, @@ -36,13 +35,12 @@ async function handleResponse(response) { * @returns {String | null} */ function handleError(response) { - if (isBrowserEnvironment()) { - if ( - window.location.protocol === 'https:' && - response.url.indexOf('http://') === 0 - ) { - return 'URLHandler: Cannot go from HTTPS to HTTP.'; - } + if ( + util.isBrowserEnvironment() && + window.location.protocol === 'https:' && + response.url.includes('http://') + ) { + return 'URLHandler: Cannot go from HTTPS to HTTP.'; } if (response.status !== 200 || !response.ok) { From 49f2e8aacc9500ab147aa6e0dd5db0bbcfdcdefe Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Mon, 18 Mar 2024 12:51:18 +0100 Subject: [PATCH 74/95] [tracker] fix comments related to vasttracker --- src/vast_tracker.js | 106 ++++++++++++++++++++------------------------ 1 file changed, 48 insertions(+), 58 deletions(-) diff --git a/src/vast_tracker.js b/src/vast_tracker.js index 14b840cb..72916d49 100644 --- a/src/vast_tracker.js +++ b/src/vast_tracker.js @@ -152,8 +152,7 @@ export class VASTTracker extends EventEmitter { // check if duration is a valid time input if (!util.isValidTimeValue(duration)) { this.emit('TRACKER-error', { - message: 'the duration provided is not valid', - duration, + message: `the duration provided is not valid. duration: ${duration}`, }); return; } @@ -185,8 +184,9 @@ export class VASTTracker extends EventEmitter { // check if progress is a valid time input if (!util.isValidTimeValue(progress) || typeof macros !== 'object') { this.emit('TRACKER-error', { - message: 'setProgress parameters has the wrong type.', - parameter: { progress, macros }, + message: `One given setProgress parameter has the wrong type. progress: ${progress}, macros: ${util.formatMacrosValues( + macros + )}`, }); return; @@ -263,8 +263,9 @@ export class VASTTracker extends EventEmitter { setMuted(muted, macros = {}) { if (typeof muted !== 'boolean' || typeof macros !== 'object') { this.emit('TRACKER-error', { - message: 'setMuted parameters have the wrong type', - parameters: { muted, macros }, + message: `One given setMuted parameter has the wrong type. muted: ${muted}, macros: ${util.formatMacrosValues( + macros + )}`, }); return; } @@ -286,8 +287,9 @@ export class VASTTracker extends EventEmitter { setPaused(paused, macros = {}) { if (typeof paused !== 'boolean' || typeof macros !== 'object') { this.emit('TRACKER-error', { - message: 'setPaused parameters have the wrong type', - parameters: { paused, macros }, + message: `One given setPaused parameter has the wrong type. paused: ${paused}, macros: ${util.formatMacrosValues( + macros + )}`, }); return; } @@ -308,8 +310,9 @@ export class VASTTracker extends EventEmitter { setFullscreen(fullscreen, macros = {}) { if (typeof fullscreen !== 'boolean' || typeof macros !== 'object') { this.emit('TRACKER-error', { - message: 'setFullScreen parameters have the wrong type', - parameters: { fullscreen, macros }, + message: `One given setFullScreen parameter has the wrong type. fullscreen: ${fullscreen}, macros: ${util.formatMacrosValues( + macros + )}`, }); return; } @@ -332,8 +335,9 @@ export class VASTTracker extends EventEmitter { setExpand(expanded, macros = {}) { if (typeof expanded !== 'boolean' || typeof macros !== 'object') { this.emit('TRACKER-error', { - message: 'setExpand parameters have the wrong type', - parameters: { expanded, macros }, + message: `One given setExpand parameter has the wrong type. expanded: ${expanded}, macros: ${util.formatMacrosValues( + macros + )}`, }); return; } @@ -356,8 +360,7 @@ export class VASTTracker extends EventEmitter { setSkipDelay(duration) { if (!util.isValidTimeValue(duration)) { this.emit('TRACKER-error', { - message: 'setSkipDelay parameter does not have a valid value', - parameter: duration, + message: `setSkipDelay parameter does not have a valid value. duration: ${duration}`, }); return; } @@ -372,8 +375,7 @@ export class VASTTracker extends EventEmitter { trackImpression(macros = {}) { if (typeof macros !== 'object') { this.emit('TRACKER-error', { - message: 'trackImpression parameter has the wrong type', - parameter: macros, + message: `trackImpression parameter has the wrong type. macros: ${macros}`, }); return; } @@ -391,8 +393,7 @@ export class VASTTracker extends EventEmitter { trackViewableImpression(macros = {}) { if (typeof macros !== 'object') { this.emit('TRACKER-error', { - message: 'trackViewableImpression parameter has the wrong type', - parameter: macros, + message: `trackViewableImpression given macros has the wrong type. macros: ${macros}`, }); return; } @@ -409,8 +410,7 @@ export class VASTTracker extends EventEmitter { trackNotViewableImpression(macros = {}) { if (typeof macros !== 'object') { this.emit('TRACKER-error', { - message: 'trackNotViewableImpression parameter has the wrong type', - parameter: macros, + message: `trackNotViewableImpression given macros has the wrong type. macros: ${macros}`, }); return; } @@ -427,8 +427,7 @@ export class VASTTracker extends EventEmitter { trackUndeterminedImpression(macros = {}) { if (typeof macros !== 'object') { this.emit('TRACKER-error', { - message: 'trackUndeterminedImpression parameter has the wrong type', - parameter: macros, + message: `trackUndeterminedImpression given macros has the wrong type. macros: ${macros}`, }); return; } @@ -446,8 +445,9 @@ export class VASTTracker extends EventEmitter { error(macros = {}, isCustomCode = false) { if (typeof macros !== 'object' || typeof isCustomCode !== 'boolean') { this.emit('TRACKER-error', { - message: 'error parameters have the wrong type', - parameters: { macros, isCustomCode }, + message: `One given error parameter has the wrong type. macros: ${util.formatMacrosValues( + macros + )}, isCustomCode: ${isCustomCode}`, }); return; } @@ -464,8 +464,7 @@ export class VASTTracker extends EventEmitter { errorWithCode(errorCode, isCustomCode = false) { if (typeof errorCode !== 'string' || typeof isCustomCode !== 'boolean') { this.emit('TRACKER-error', { - message: 'ErrorWithCode parameters have the wrong type', - parameters: { errorCode, isCustomCode }, + message: `One given errorWithCode parameter has the wrong type. errorCode: ${errorCode}, isCustomCode: ${isCustomCode}`, }); return; } @@ -486,8 +485,7 @@ export class VASTTracker extends EventEmitter { complete(macros = {}) { if (typeof macros !== 'object') { this.emit('TRACKER-error', { - message: 'complete parameter has the wrong type', - parameter: macros, + message: `complete given macros has the wrong type. macros: ${macros}`, }); return; } @@ -505,8 +503,7 @@ export class VASTTracker extends EventEmitter { notUsed(macros = {}) { if (typeof macros !== 'object') { this.emit('TRACKER-error', { - message: 'notUsed parameter has the wrong type', - parameter: macros, + message: `notUsed given macros has the wrong type. macros: ${macros}`, }); return; } @@ -526,8 +523,7 @@ export class VASTTracker extends EventEmitter { otherAdInteraction(macros = {}) { if (typeof macros !== 'object') { this.emit('TRACKER-error', { - message: 'otherAdInteraction parameter has the wrong type', - parameter: macros, + message: `otherAdInteraction given macros has the wrong type. macros: ${macros}`, }); return; } @@ -547,8 +543,7 @@ export class VASTTracker extends EventEmitter { acceptInvitation(macros = {}) { if (typeof macros !== 'object') { this.emit('TRACKER-error', { - message: 'acceptInvitation parameter has the wrong type', - parameter: macros, + message: `acceptInvitation given macros has the wrong type. macros: ${macros}`, }); return; } @@ -565,8 +560,7 @@ export class VASTTracker extends EventEmitter { adExpand(macros = {}) { if (typeof macros !== 'object') { this.emit('TRACKER-error', { - message: 'adExpand parameter has the wrong type', - parameter: macros, + message: `adExpand given macros has the wrong type. macros: ${macros}`, }); return; } @@ -583,8 +577,7 @@ export class VASTTracker extends EventEmitter { adCollapse(macros = {}) { if (typeof macros !== 'object') { this.emit('TRACKER-error', { - message: 'adCollapse parameter has the wrong type', - parameter: macros, + message: `adCollapse given macros has the wrong type. macros: ${macros}`, }); return; } @@ -601,8 +594,7 @@ export class VASTTracker extends EventEmitter { minimize(macros = {}) { if (typeof macros !== 'object') { this.emit('TRACKER-error', { - message: 'minimize parameter has the wrong type', - parameter: macros, + message: `minimize given macros has the wrong type. macros: ${macros}`, }); return; } @@ -621,8 +613,9 @@ export class VASTTracker extends EventEmitter { verificationNotExecuted(vendor, macros = {}) { if (typeof vendor !== 'string' || typeof macros !== 'object') { this.emit('TRACKER-error', { - message: 'verificationNotExecuted parameters have to wrong type', - parameters: { vendor, macros }, + message: `One given verificationNotExecuted parameter has to wrong type. vendor: ${vendor}, macros: ${util.formatMacrosValues( + macros + )}`, }); return; } @@ -675,8 +668,9 @@ export class VASTTracker extends EventEmitter { overlayViewDuration(formattedDuration, macros = {}) { if (typeof formattedDuration !== 'string' || typeof macros !== 'object') { this.emit('TRACKER-error', { - message: 'overlayViewDuration parameters have the wrong type', - parameters: { formattedDuration, macros }, + message: `One given overlayViewDuration parameters has the wrong type. formattedDuration: ${formattedDuration}, macros: ${util.formatMacrosValues( + macros + )}`, }); return; } @@ -695,8 +689,7 @@ export class VASTTracker extends EventEmitter { close(macros = {}) { if (typeof macros !== 'object') { this.emit('TRACKER-error', { - message: 'close parameter has the wrong type', - parameter: macros, + message: `close given macros has the wrong type. macros: ${macros}`, }); return; } @@ -712,8 +705,7 @@ export class VASTTracker extends EventEmitter { skip(macros = {}) { if (typeof macros !== 'object') { this.emit('TRACKER-error', { - message: 'skip parameter has the wrong type', - parameter: macros, + message: `skip given macros has the wrong type. macros: ${macros}`, }); return; } @@ -731,8 +723,7 @@ export class VASTTracker extends EventEmitter { load(macros = {}) { if (typeof macros !== 'object') { this.emit('TRACKER-error', { - message: 'load parameter has the wrong type', - parameter: macros, + message: `load given macros has the wrong type. macros: ${macros}`, }); return; } @@ -755,8 +746,9 @@ export class VASTTracker extends EventEmitter { typeof macros !== 'object' ) { this.emit('TRACKER-error', { - message: 'click parameters have the wrong type', - parameters: { fallbackClickThroughURL, macros }, + message: `One given click parameter has the wrong type. fallbackClickThroughURL: ${fallbackClickThroughURL}, macros: ${util.formatMacrosValues( + macros + )}`, }); return; } @@ -798,8 +790,7 @@ export class VASTTracker extends EventEmitter { track(eventName, { macros = {}, once = false } = {}) { if (typeof macros !== 'object') { this.emit('TRACKER-error', { - message: 'track macros parameter has the wrong type', - parameter: macros, + message: `track given macros has the wrong type. macros: ${macros}`, }); return; } @@ -842,12 +833,11 @@ export class VASTTracker extends EventEmitter { * @param {Object} [options={}] - An optional Object of options to be used in the tracking calls. */ trackURLs(URLTemplates, macros = {}, options = {}) { - const { validUrls, unvalidUrls } = util.filterUrlTemplates(URLTemplates); + const { validUrls, invalidUrls } = util.filterUrlTemplates(URLTemplates); - if (unvalidUrls.length) { + if (invalidUrls.length) { this.emit('TRACKER-error', { - unvalidUrls, - message: 'Provided urls are malformed', + message: `Provided urls are malformed. url: ${invalidUrls}`, }); } From c51abd45eec716b74dd7343a26937cecbf0e4e79 Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Mon, 18 Mar 2024 12:53:27 +0100 Subject: [PATCH 75/95] [utils] add two utils function to check browser environment and format macros values --- src/util/util.js | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/util/util.js b/src/util/util.js index 48e65cd1..4c1676f7 100644 --- a/src/util/util.js +++ b/src/util/util.js @@ -142,16 +142,12 @@ function extractURLsFromTemplates(URLTemplates) { function filterUrlTemplates(URLTemplates) { return URLTemplates.reduce( (acc, urlTemplate) => { - const url = urlTemplate.hasOwnProperty('url') - ? urlTemplate.url - : urlTemplate; + const url = urlTemplate.url || urlTemplate; - let urlVerification = isValidUrl(url); - - urlVerification ? acc.validUrls.push(url) : acc.unvalidUrls.push(url); + isValidUrl(url) ? acc.validUrls.push(url) : acc.invalidUrls.push(url); return acc; }, - { validUrls: [], unvalidUrls: [] } + { validUrls: [], invalidUrls: [] } ); } @@ -266,6 +262,18 @@ function isValidTimeValue(time) { return Number.isFinite(time) && time >= -2; } +/** + * Check if we are in a browser environment + * @returns {Boolean} + */ +function isBrowserEnvironment() { + return typeof window !== 'undefined'; +} + +function formatMacrosValues(macros) { + return typeof macros !== 'object' ? macros : JSON.stringify(macros); +} + export const util = { track, resolveURLTemplates, @@ -281,4 +289,6 @@ export const util = { isValidTimeValue, addLeadingZeros, isValidUrl, + isBrowserEnvironment, + formatMacrosValues, }; From b3b682212ac634ffba45f6af8c7d2574f12bff47 Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Mon, 18 Mar 2024 12:54:39 +0100 Subject: [PATCH 76/95] [client] fix import --- src/vast_client.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vast_client.js b/src/vast_client.js index 8fe94621..219db68c 100644 --- a/src/vast_client.js +++ b/src/vast_client.js @@ -1,6 +1,6 @@ import { Storage } from './util/storage'; import { VASTParser } from './parser/vast_parser'; -import { Fetcher } from './fetcher.js'; +import { Fetcher } from './fetcher/fetcher.js'; /** * This class provides methods to fetch and parse a VAST document using VASTParser. From 074067eccebf938caced15e8e7b10ff8a32b3e2e Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Mon, 18 Mar 2024 12:55:36 +0100 Subject: [PATCH 77/95] [doc] add doc for new error tracking --- docs/api/vast-tracker.md | 57 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/docs/api/vast-tracker.md b/docs/api/vast-tracker.md index e708b278..28e29a32 100644 --- a/docs/api/vast-tracker.md +++ b/docs/api/vast-tracker.md @@ -49,14 +49,25 @@ const vastTracker = new VASTTracker(null, ad, creative); ## Events + + `VASTTracker` extends a custom [`EventEmitter`](https://github.com/dailymotion/vast-client-js/blob/master/docs/api/event-emitter.md), therefore is possible to add event listeners like this: + ```Javascript vastTracker.on('exitFullscreen', () => { // Deal with the event }); ``` +In addition, you can add a specific event listener to listen to any errors. + +```Javascript +vastTracker.on('TRACKER-error', ({message}) => { + console.log(message) +}); +``` + ### Events list | **Event** | **Description** | **Payload** | @@ -181,6 +192,9 @@ Pass `isCustomCode` as true only if you want to use a custom code for the `[ERRO - **`macros : Object `** - An optional Object containing macros and their values to be used and replaced in the tracking calls. - **`isCustomCode: Boolean`** - Flag to allow custom values on error code. +#### Event emitted +- **`TRACKER-error`** + #### Example ```Javascript @@ -206,6 +220,9 @@ Sends a request to the URI provided by the VAST `` element. If an `[ERROR - **`errorCode: String`** - Replaces `[ERRORCODE]` macro. `[ERRORCODE]` values are listed in the VAST specification - **`isCustomCode: Boolean`** - Flag to allow custom values on error code. +#### Event emitted +- **`TRACKER-error`** + #### Example ```Javascript @@ -228,6 +245,7 @@ Must be called when the player considers that it has loaded and buffered the cre #### Events emitted - **`loaded`** +- **`TRACKER-error`** #### Example @@ -253,6 +271,7 @@ Must be called when the user watched the linear creative until its end. Calls th #### Events emitted - **`complete`** +- **`TRACKER-error`** #### Example @@ -280,6 +299,7 @@ Must be called when the user clicks on the creative. Calls the tracking URLs. #### Events emitted - **`clickthrough`** +- **`TRACKER-error`** #### Example @@ -307,6 +327,7 @@ Must be called when the player or the window is closed during the ad. Calls the - **`closeLinear`** - **`close`** +- **`TRACKER-error`** #### Example @@ -338,6 +359,7 @@ Must be called when the skip button is clicked. Calls the skip tracking URLs. #### Events emitted - **`skip`** +- **`TRACKER-error`** #### Example @@ -360,6 +382,9 @@ Sets the duration of the ad and updates the quartiles based on that. - **`duration: Number`** - The duration of the ad +#### Event Emitted +- **`TRACKER-error`** + ### setExpand(expanded, macros) Updates the expand state and calls the expand/collapse as well as playerExpand/playerCollapse for VAST 4.1. tracking URLs. @@ -375,6 +400,7 @@ Updates the expand state and calls the expand/collapse as well as playerExpand/p - **`playerExpand`** - **`collapse`** - **`playerCollapse`** +- **`TRACKER-error`** #### Example @@ -413,6 +439,7 @@ Updates the fullscreen state and calls the fullscreen tracking URLs. - **`fullscreen`** - **`exitFullscreen`** +- **`TRACKER-error`** #### Example @@ -448,6 +475,7 @@ Updates the mute state and calls the mute/unmute tracking URLs. - **`mute`** - **`unmute`** +- **`TRACKER-error`** #### Example @@ -479,6 +507,7 @@ Update the pause state and call the resume/pause tracking URLs. - **`pause`** - **`resume`** +- **`TRACKER-error`** #### Example @@ -515,6 +544,7 @@ Sets the duration of the ad and updates the quartiles based on that. This is req - **`midpoint`** - **`firstQuartile`** - **`thirdQuartile`** +- **`TRACKER-error`** #### Example @@ -539,6 +569,10 @@ Do not call this method if you want to keep the original `Skipoffset` value. - **`duration: Number`** - The time in seconds until the skip button is displayed +#### Event emitted + +- **`TRACKER-error`** + #### Example ```Javascript @@ -560,6 +594,7 @@ Reports the impression URI. Can only be called once. Will report the following U #### Events emitted - **`creativeView`** +- **`TRACKER-error`** #### Example @@ -585,6 +620,10 @@ This method should be used when the ad meets criteria for Viewable impression as - - **`macros: Object`** - Optional parameter. Object containing macros and their values to be replaced. Macros must be supported by VAST specification. +#### Event emitted + +- **`TRACKER-error`** + #### Example @@ -607,6 +646,10 @@ This method should be used when the ad meets criteria for NotViewable impression - - **`macros: Object`** - Optional parameter. Object containing macros and their values to be replaced. Macros must be supported by VAST specification. +#### Event emitted + +- **`TRACKER-error`** + #### Example @@ -629,6 +672,10 @@ This method should be used when the ad meets criteria for ViewUndetermined impre - - **`macros: Object`** - Optional parameter. Object containing macros and their values to be replaced. Macros must be supported by VAST specification. +#### Event emitted + +- **`TRACKER-error`** + #### Example @@ -656,6 +703,7 @@ Calls the notUsed tracking URLs. #### Events emitted - **`notUsed`** +- **`TRACKER-error`** #### Example @@ -683,6 +731,7 @@ Calls the otherAdInteraction tracking URLs. #### Events emitted - **`otherAdInteraction`** +- **`TRACKER-error`** #### Example @@ -711,6 +760,7 @@ Calls the acceptInvitation tracking URLs. #### Events emitted - **`acceptInvitation`** +- **`TRACKER-error`** #### Example @@ -738,6 +788,7 @@ Calls the adExpand tracking URLs. #### Events emitted - **`adExpand`** +- **`TRACKER-error`** #### Example @@ -765,6 +816,7 @@ Calls the adCollapse tracking URLs. #### Events emitted - **`adCollapse`** +- **`TRACKER-error`** #### Example @@ -794,6 +846,7 @@ Calls the minimize tracking URLs. #### Events emitted - **`minimize`** +- **`TRACKER-error`** #### Example @@ -836,6 +889,7 @@ Calls the overlayViewDuration tracking URLs. #### Events emitted - **`overlayViewDuration`** +- **`TRACKER-error`** #### Example @@ -861,6 +915,7 @@ Calls the verificationNotExecuted trackings URLs. #### Events emitted - **`verificationNotExecuted`** +- **`TRACKER-error`** #### Example @@ -886,6 +941,8 @@ Calls the tracking URLs for the given eventName and emits the event. #### Events emitted - **`given eventName`** +- **`TRACKER-error`** + #### Example From cf254d25059c76c5b0a7dba82328046b7a2699be Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Mon, 18 Mar 2024 13:00:27 +0100 Subject: [PATCH 78/95] [tool] fix rollup configuration to handle dynamic imports and fix getFileSize to exclude directory --- bundle_size.js | 7 ++++++- rollup.config.js | 50 ++++++++++++++++++++++++++++++++---------------- 2 files changed, 40 insertions(+), 17 deletions(-) diff --git a/bundle_size.js b/bundle_size.js index c64cc36b..a9cd3808 100644 --- a/bundle_size.js +++ b/bundle_size.js @@ -6,7 +6,12 @@ const oldDistPath = './dist_old'; const getFileSizes = (folderPath) => { return fs.readdirSync(folderPath).reduce((res, fileName) => { - res[fileName] = fs.readFileSync(path.join(folderPath, fileName)).byteLength; + const stats = fs.statSync(`${folderPath}/${fileName}`); + if (stats.isFile()) { + res[fileName] = fs.readFileSync( + path.join(folderPath, fileName) + ).byteLength; + } return res; }, {}); }; diff --git a/rollup.config.js b/rollup.config.js index e1c15832..bb4661c5 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -15,19 +15,8 @@ function onwarn(warning) { } const createNodeConfig = (filePath, minifiedOutput, notMinifiedOutput) => { - let config = { + const baseConfig = { input: filePath, - output: [ - { - format: 'cjs', - file: `dist/${notMinifiedOutput}`, - }, - { - format: 'cjs', - file: `dist/${minifiedOutput}`, - plugins: [terser()], - }, - ], plugins: [ resolve({ preferBuiltins: true, @@ -37,7 +26,34 @@ const createNodeConfig = (filePath, minifiedOutput, notMinifiedOutput) => { onwarn, }; - return config; + const nonMinifiedConfig = { + ...baseConfig, + output: { + format: 'cjs', + dir: 'dist', + entryFileNames: `${notMinifiedOutput}`, + chunkFileNames: 'chunks/[name]-[hash].js', + manualChunks: { + xmldom: ['@xmldom/xmldom'], + }, + }, + }; + + const minifiedConfig = { + ...baseConfig, + output: { + format: 'cjs', + dir: 'dist', + entryFileNames: `${minifiedOutput}`, + chunkFileNames: 'chunks/[name]-[hash].min.js', + manualChunks: { + xmldom: ['@xmldom/xmldom'], + }, + }, + plugins: [...baseConfig.plugins, terser()], + }; + + return [nonMinifiedConfig, minifiedConfig]; }; const createBrowserConfig = (filePath, minifiedOutput, notMinifiedOutput) => { @@ -46,11 +62,13 @@ const createBrowserConfig = (filePath, minifiedOutput, notMinifiedOutput) => { output: [ { format: 'es', - file: `dist/${notMinifiedOutput}`, + dir: 'dist', + entryFileNames: `${notMinifiedOutput}`, }, { format: 'es', - file: `dist/${minifiedOutput}`, + dir: 'dist', + entryFileNames: `${minifiedOutput}`, plugins: [terser()], }, ], @@ -64,7 +82,7 @@ export default [ // Browser-friendly build [package.json "browser"] createBrowserConfig('src/index.js', 'vast-client.min.js', 'vast-client.js'), // CommonJS build for Node usage [package.json "main"] - createNodeConfig( + ...createNodeConfig( 'src/index.js', 'vast-client-node.min.js', 'vast-client-node.js' From bbd2fc4da0fa469a1ea4aa0d1c657bb7cb6c097d Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Mon, 18 Mar 2024 13:10:43 +0100 Subject: [PATCH 79/95] [parser] fix comments related to parseWrapper function --- src/parser/ad_parser.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/parser/ad_parser.js b/src/parser/ad_parser.js index b0fce2f1..7082eda1 100644 --- a/src/parser/ad_parser.js +++ b/src/parser/ad_parser.js @@ -261,9 +261,8 @@ function parseWrapper(wrapperElement, emit) { ad.trackingEvents[wrapperCreativeElement.type] = {}; } - for (const [eventName, urls] of Object.entries( - wrapperCreativeElement.trackingEvents - )) { + for (const eventName in wrapperCreativeElement.trackingEvents) { + const urls = wrapperCreativeElement.trackingEvents[eventName]; if ( !Array.isArray( ad.trackingEvents[wrapperCreativeElement.type][eventName] From fefb556d2159d2066271ec9441272a9faad58e3c Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Fri, 22 Mar 2024 18:04:27 +0100 Subject: [PATCH 80/95] fix comments --- bundle_size.js | 4 +--- docs/api/vast-tracker.md | 7 ++++++- rollup.config.js | 27 +++++++++++++-------------- src/fetcher/url_handler.js | 1 - 4 files changed, 20 insertions(+), 19 deletions(-) diff --git a/bundle_size.js b/bundle_size.js index a9cd3808..7cceb113 100644 --- a/bundle_size.js +++ b/bundle_size.js @@ -8,9 +8,7 @@ const getFileSizes = (folderPath) => { return fs.readdirSync(folderPath).reduce((res, fileName) => { const stats = fs.statSync(`${folderPath}/${fileName}`); if (stats.isFile()) { - res[fileName] = fs.readFileSync( - path.join(folderPath, fileName) - ).byteLength; + res[fileName] = stats.size; } return res; }, {}); diff --git a/docs/api/vast-tracker.md b/docs/api/vast-tracker.md index 28e29a32..cef3b753 100644 --- a/docs/api/vast-tracker.md +++ b/docs/api/vast-tracker.md @@ -50,7 +50,6 @@ const vastTracker = new VASTTracker(null, ad, creative); ## Events - `VASTTracker` extends a custom [`EventEmitter`](https://github.com/dailymotion/vast-client-js/blob/master/docs/api/event-emitter.md), therefore is possible to add event listeners like this: @@ -62,6 +61,9 @@ vastTracker.on('exitFullscreen', () => { In addition, you can add a specific event listener to listen to any errors. +##### TRACKER-error + +- `message: String` ```Javascript vastTracker.on('TRACKER-error', ({message}) => { console.log(message) @@ -193,6 +195,7 @@ Pass `isCustomCode` as true only if you want to use a custom code for the `[ERRO - **`isCustomCode: Boolean`** - Flag to allow custom values on error code. #### Event emitted + - **`TRACKER-error`** #### Example @@ -221,6 +224,7 @@ Sends a request to the URI provided by the VAST `` element. If an `[ERROR - **`isCustomCode: Boolean`** - Flag to allow custom values on error code. #### Event emitted + - **`TRACKER-error`** #### Example @@ -383,6 +387,7 @@ Sets the duration of the ad and updates the quartiles based on that. - **`duration: Number`** - The duration of the ad #### Event Emitted + - **`TRACKER-error`** ### setExpand(expanded, macros) diff --git a/rollup.config.js b/rollup.config.js index bb4661c5..01f05672 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -17,6 +17,13 @@ function onwarn(warning) { const createNodeConfig = (filePath, minifiedOutput, notMinifiedOutput) => { const baseConfig = { input: filePath, + output: { + format: 'cjs', + dir: 'dist', + manualChunks: { + xmldom: ['@xmldom/xmldom'], + }, + }, plugins: [ resolve({ preferBuiltins: true, @@ -29,26 +36,18 @@ const createNodeConfig = (filePath, minifiedOutput, notMinifiedOutput) => { const nonMinifiedConfig = { ...baseConfig, output: { - format: 'cjs', - dir: 'dist', - entryFileNames: `${notMinifiedOutput}`, + ...baseConfig.output, + entryFileNames: notMinifiedOutput, chunkFileNames: 'chunks/[name]-[hash].js', - manualChunks: { - xmldom: ['@xmldom/xmldom'], - }, }, }; const minifiedConfig = { ...baseConfig, output: { - format: 'cjs', - dir: 'dist', - entryFileNames: `${minifiedOutput}`, + ...baseConfig.output, + entryFileNames: minifiedOutput, chunkFileNames: 'chunks/[name]-[hash].min.js', - manualChunks: { - xmldom: ['@xmldom/xmldom'], - }, }, plugins: [...baseConfig.plugins, terser()], }; @@ -63,12 +62,12 @@ const createBrowserConfig = (filePath, minifiedOutput, notMinifiedOutput) => { { format: 'es', dir: 'dist', - entryFileNames: `${notMinifiedOutput}`, + entryFileNames: notMinifiedOutput, }, { format: 'es', dir: 'dist', - entryFileNames: `${minifiedOutput}`, + entryFileNames: minifiedOutput, plugins: [terser()], }, ], diff --git a/src/fetcher/url_handler.js b/src/fetcher/url_handler.js index e1b0b272..f04a43cb 100644 --- a/src/fetcher/url_handler.js +++ b/src/fetcher/url_handler.js @@ -36,7 +36,6 @@ async function handleResponse(response) { */ function handleError(response) { if ( - util.isBrowserEnvironment() && window.location.protocol === 'https:' && response.url.includes('http://') ) { From 89a6b39f77b569053c6a45b0a69d9013ae0b010f Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Tue, 9 Apr 2024 14:45:19 +0200 Subject: [PATCH 81/95] add a full example with a player using the vast-client --- examples/src/index.html | 29 ++++ examples/src/index.js | 11 ++ examples/src/styles.css | 9 ++ examples/src/utils.js | 327 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 376 insertions(+) create mode 100644 examples/src/index.html create mode 100644 examples/src/index.js create mode 100644 examples/src/styles.css create mode 100644 examples/src/utils.js diff --git a/examples/src/index.html b/examples/src/index.html new file mode 100644 index 00000000..79bbaf18 --- /dev/null +++ b/examples/src/index.html @@ -0,0 +1,29 @@ + + + + + + + + Document + + +

This page aim to demonstrate how to use the vast-client.js library and implement a simple preroll using a html5 video tag

+
+
+ +
+

Companion ad slot

+
+ +
+
+ + + diff --git a/examples/src/index.js b/examples/src/index.js new file mode 100644 index 00000000..9d10c210 --- /dev/null +++ b/examples/src/index.js @@ -0,0 +1,11 @@ +import { playPreroll } from './utils'; + +const videoPlayer = document.getElementById('myVideo'); +let prerollPlayed = false; + +videoPlayer.addEventListener('play', () => { + if (!prerollPlayed) { + playPreroll(); + prerollPlayed = true; + } +}); diff --git a/examples/src/styles.css b/examples/src/styles.css new file mode 100644 index 00000000..e18fdaa0 --- /dev/null +++ b/examples/src/styles.css @@ -0,0 +1,9 @@ +body { + display: flex; + align-items: center; + flex-direction: column; +} + +.hidden { + display: none; +} diff --git a/examples/src/utils.js b/examples/src/utils.js new file mode 100644 index 00000000..19301d63 --- /dev/null +++ b/examples/src/utils.js @@ -0,0 +1,327 @@ +import { VASTClient, VASTTracker } from '@dailymotion/vast-client'; +const mainContent = + 'https://dmplayer.storage.googleapis.com/tech_test/midnight_sun_720p.mp4'; +const videoPlayer = document.getElementById('myVideo'); +const variationImg = document.getElementById('variationImg'); +const variationContainer = document.getElementById('variationContainer'); + +//vastTracker for linear ads +let vastTracker = null; +//vastTracker for companion ads +let companionVastTracker = null; + +/** + * Get the first valid creative from an ad + */ +export const getCreative = (ad) => { + if (!ad.creatives) { + return null; + } + const validCreative = ad.creatives.find((creative) => creative.mediaFiles); + return validCreative || null; +}; + +/** + * Get the URL of the first media file in the creative + */ + +export const getMediaFileUrl = (creative) => { + const mediaFiles = creative.mediaFiles; + if (!mediaFiles.length) { + return null; + } + const mediafile = mediaFiles.find((mediaFile) => mediaFile.fileURL); + return mediafile.fileURL || null; +}; + +/** + * @returns a promise that resolves with the parsed VAST + */ + +export const parsePreroll = async () => { + const VAST = 'https://run.mocky.io/v3/e4c75f91-c6fd-4425-ad2b-f346fe13bfb2'; + + let vastClient = new VASTClient(); + try { + return await vastClient.get(VAST); + } catch (error) { + console.error(error); + } +}; + +/** + * play a preroll + */ + +export const playPreroll = async () => { + const parsedVAST = await parsePreroll(); + // here we a using the first ad from the parsed vast that contain creatives, + // however, this logic can depend on you player implementation logic + const validAd = parsedVAST.ads.find((ads) => ads.creatives.length > 0); + if (!validAd) { + return; + } + + const creative = getCreative(validAd); + const mediaFileUrl = getMediaFileUrl(creative); + const variationData = getVariationData(validAd); + const companionCreative = getCreativeCompanion(validAd); + + videoPlayer.src = mediaFileUrl; + videoPlayer.play(); + + // listen the the en ended event to stop the ad and remove trackers + videoPlayer.addEventListener('ended', (e) => { + stopAd(); + }); + + //once we have the parsed vast we can instanciate the vastTracker + vastTracker = new VASTTracker(null, validAd, creative); + + //if we have a companion ad we instanciate the companionVastTracker + if (variationData.variation) { + companionVastTracker = new VASTTracker( + null, + validAd, + companionCreative, + variationData.variation + ); + } + + displayVariation(variationData); + initTrackers(); +}; + +/** + * Stop the ad and remove all EventListeners + */ + +export const stopAd = () => { + videoPlayer.src = mainContent; + videoPlayer.load(); + videoPlayer.play(); + variationContainer.classList.add('hidden'); + removeTrackers(videoPlayer); +}; + +/** + * attach all the EventListeners to the videoPlayer + */ +export const initTrackers = () => { + videoPlayer.addEventListener('volumechange', handleMuteTrackers); + videoPlayer.addEventListener('click', handleClickTrackers); + videoPlayer.addEventListener('canplay', handleImpressionTrackers); + videoPlayer.addEventListener('canplay', handleCompanionImpressionTrackers); + videoPlayer.addEventListener('timeupdate', handleTimeUpdateTrackers); + videoPlayer.addEventListener('ended', handleCompleteTrackers); + videoPlayer.addEventListener('play', handleResumeTrackers); + videoPlayer.addEventListener('pause', handlePauseTrackers); + videoPlayer.addEventListener('error', handleErrorTrackers); + //listener on the variation container to detect the click an fire the tracker + variationImg.addEventListener('click', handleCompanionClickTrackers); +}; + +/** + * removes all the EventListeners from the videoPlayer + */ +export const removeTrackers = () => { + videoPlayer.removeEventListener('volumechange', handleMuteTrackers); + videoPlayer.removeEventListener('click', handleClickTrackers); + videoPlayer.removeEventListener('canplay', handleImpressionTrackers); + videoPlayer.removeEventListener('canplay', handleCompanionImpressionTrackers); + videoPlayer.removeEventListener('timeupdate', handleTimeUpdateTrackers); + videoPlayer.removeEventListener('play', handleResumeTrackers); + videoPlayer.removeEventListener('pause', handlePauseTrackers); + videoPlayer.removeEventListener('ended', handleCompleteTrackers); + videoPlayer.removeEventListener('error', handleErrorTrackers); + variationImg.removeEventListener('click', handleCompanionClickTrackers); +}; + +/** + * Send mute and unmute trackers using the vastTracker + */ +const handleMuteTrackers = (e) => { + if (!vastTracker) { + return; + } + + const muted = e.target.muted; + vastTracker.setMuted(muted); +}; + +/** + * Send click trackers using the vastTracker + */ +const handleClickTrackers = (e) => { + if (!vastTracker) { + return; + } + + // The values for the CLICKPOS macros depends on your player implementations logic + vastTracker.click(null, { CLICKPOS: [e.clientX, e.clientY] }); + vastTracker.on('clickthrough', (url) => { + window.open(url, '_blank'); + }); +}; + +/** + * Send companion click trackers using the vastTracker + */ +const handleCompanionClickTrackers = (e) => { + if (!companionVastTracker) { + return; + } + companionVastTracker.click(); + companionVastTracker.on('clickthrough', (url) => { + window.open(url, '_blank'); + }); +}; + +/** + * Send impression trackers for linear ads using the vastTracker + */ +const handleImpressionTrackers = () => { + if (!vastTracker) { + return; + } + vastTracker.trackImpression(); +}; + +/** + * Send impression trackers for companion ads using the vastTracker + */ +const handleCompanionImpressionTrackers = () => { + if (!companionVastTracker) { + return; + } + companionVastTracker.trackImpression(); +}; + +/** + * Send complete tracker using the vastTracker + */ +const handleCompleteTrackers = () => { + if (!vastTracker) { + return; + } + vastTracker.complete(); +}; + +/** + * Send pause tracker using the vastTracker + */ + +const handlePauseTrackers = () => { + if (!vastTracker) { + return; + } + vastTracker.setPaused(true); +}; + +/** + * Send resume tracker using the vastTracker + */ +const handleResumeTrackers = () => { + if (!vastTracker) { + return; + } + vastTracker.setPaused(false); +}; + +/** + * Send progress tracker(firstQuartile, midpoint...) using the vastTracker + */ +const handleTimeUpdateTrackers = () => { + if (!vastTracker) { + return; + } + vastTracker.setProgress(videoPlayer.currentTime); +}; + +/** + * Send error trackers using the vastTracker + */ + +const handleErrorTrackers = () => { + if (!vastTracker) { + return; + } + vastTracker.error(); +}; + +/////////////////////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////COMPANION HANDLING//////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * @returns the first valid companion creative + */ +const getCreativeCompanion = (ad) => { + if (!ad.creatives) { + return null; + } + const validCreative = ad.creatives.find( + (creative) => creative.type === 'companion' + ); + + if (!validCreative) { + return; + } + + return validCreative; +}; + +/** + * @returns an Object containing the image url and the variation + */ + +const getVariationData = (ad) => { + if (!ad.creatives) { + return null; + } + const validCreative = getCreativeCompanion(ad); + + if (!validCreative.variations.length) { + return null; + } + + return { + imgurl: getVariationUrl(validCreative.variations[0]), + variation: validCreative.variations[0], + }; +}; + +/** + * @returns the image url of the variation to display + */ + +const getVariationUrl = (variation) => { + if (!variation.staticResources.length) { + return null; + } + const variationImage = variation.staticResources[0].url; + return variationImage; +}; + +const displayVariation = (variationData) => { + const { imgurl, variation } = variationData; + if (!variation) { + return; + } + variationImg.src = imgurl; + variationImg.style.height = variation.assetHeight + 'px'; + variationImg.style.assetWIdth = variation.assetWidth + 'px'; + variationContainer.style.width = variation.width + 'px'; + variationContainer.style.height = variation.height + 'px'; + variationImg.style.maxWidth = variation.expandedWidth + 'px'; + variationImg.style.maxHeight = variation.expandedHeight + 'px'; +}; + +let prerollPlayed = false; + +videoPlayer.addEventListener('play', () => { + if (!prerollPlayed) { + playPreroll(); + prerollPlayed = true; + } +}); From cb49bb5b326d978e5053f9791b1ffaaf7ea4dc36 Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Tue, 9 Apr 2024 14:52:43 +0200 Subject: [PATCH 82/95] adding package.json --- examples/src/package.json | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 examples/src/package.json diff --git a/examples/src/package.json b/examples/src/package.json new file mode 100644 index 00000000..3b19315d --- /dev/null +++ b/examples/src/package.json @@ -0,0 +1,21 @@ +{ + "name": "javascript", + "version": "1.0.0", + "description": "The JavaScript template", + "scripts": { + "start": "parcel ./src/index.html", + "build": "parcel build ./src/index.html" + }, + "devDependencies": { + "parcel": "^2.0.0", + "babel-eslint": "^10.1.0", + "eslint": "^7.2.0" + }, + "keywords": [ + "css", + "javascript" + ], + "dependencies": { + "@dailymotion/vast-client": "5.0.0" + } +} \ No newline at end of file From 4d343b7a82b0588ee767ee8233038bf13028762e Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Tue, 9 Apr 2024 18:07:30 +0200 Subject: [PATCH 83/95] update readme + VAST url --- README.md | 2 ++ examples/src/utils.js | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 24e07e31..3a7bcc2f 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,8 @@ This library provides three components: For the full API documentation go [here](#api). For the full Class reference go [here](https://github.com/dailymotion/vast-client-js/blob/master/docs/api/class-reference.md) +To explore a practical example of how the vast client can be implemented, please visit this [link](https://codesandbox.io/p/sandbox/github/dailymotion/vast-client-js/tree/adding-vast-client-use-case-examples/examples/src?file=%2Findex.js). + Complies with the [VAST 4.3 specification](https://iabtechlab.com/wp-content/uploads/2022/09/VAST_4.3.pdf) provided by the [Interactive Advertising Bureau (IAB)](https://www.iab.com/). ## Get Started diff --git a/examples/src/utils.js b/examples/src/utils.js index 19301d63..0d4e8c5a 100644 --- a/examples/src/utils.js +++ b/examples/src/utils.js @@ -39,11 +39,11 @@ export const getMediaFileUrl = (creative) => { */ export const parsePreroll = async () => { - const VAST = 'https://run.mocky.io/v3/e4c75f91-c6fd-4425-ad2b-f346fe13bfb2'; + const VAST = 'https://statics.dmcdn.net/h/html/vast/simple-inline.xml'; let vastClient = new VASTClient(); try { - return await vastClient.get(VAST); + return vastClient.get(VAST); } catch (error) { console.error(error); } From 0ce269749f0911a09ce5dd580c0a6b1713d21309 Mon Sep 17 00:00:00 2001 From: Raphael Levin Date: Fri, 12 Apr 2024 15:44:28 +0200 Subject: [PATCH 84/95] fix comments --- examples/package.json | 22 +++++++++++++++ examples/src/index.html | 6 ++--- examples/src/package.json | 21 --------------- examples/src/styles.css | 6 +---- examples/src/utils.js | 56 ++++++++++++--------------------------- 5 files changed, 43 insertions(+), 68 deletions(-) create mode 100644 examples/package.json delete mode 100644 examples/src/package.json diff --git a/examples/package.json b/examples/package.json new file mode 100644 index 00000000..7858dea3 --- /dev/null +++ b/examples/package.json @@ -0,0 +1,22 @@ +{ + "name": "javascript", + "version": "1.0.0", + "description": "The JavaScript template", + "scripts": { + "start": "parcel ./src/index.html", + "build": "parcel build ./src/index.html" + }, + "devDependencies": { + "parcel": "^2.0.0", + "babel-eslint": "^10.1.0", + "eslint": "^7.2.0" + }, + "keywords": [ + "css", + "javascript" + ], + "dependencies": { + "@dailymotion/vast-client": "5.0.0" + } + } + \ No newline at end of file diff --git a/examples/src/index.html b/examples/src/index.html index 79bbaf18..91f7e351 100644 --- a/examples/src/index.html +++ b/examples/src/index.html @@ -8,8 +8,8 @@ Document -

This page aim to demonstrate how to use the vast-client.js library and implement a simple preroll using a html5 video tag

-
+
+

This page aim to demonstrate how to use the vast-client.js library and implement a simple preroll using a html5 video tag