├── .codeclimate.yml ├── .editorconfig ├── .eslintrc.js ├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── bin ├── videojs_5.vast.vpaid.js ├── videojs_5.vast.vpaid.min.js └── videojs_5.vast.vpaid.min.js.map ├── build ├── BuildTaskDoc.js ├── COLORS.js ├── build.js ├── ci-test.js ├── config.js └── test.js ├── dev_embed └── scripts │ └── videojs_5.vast.vpaid.js ├── dist ├── styles │ └── bc_vpaid_vast_mo.css ├── videojs_5.vast.vpaid.js └── videojs_5.vast.vpaid.min.js ├── gulpfile.js ├── karma.conf.js ├── license-bc-mol.txt ├── package.json ├── scripts └── release ├── src ├── scripts │ ├── ads │ │ ├── icon │ │ │ └── IconIntegrator.js │ │ ├── vast │ │ │ ├── Ad.js │ │ │ ├── Category.js │ │ │ ├── Companion.js │ │ │ ├── Creative.js │ │ │ ├── Icon.js │ │ │ ├── InLine.js │ │ │ ├── InteractiveCreativeFile.js │ │ │ ├── Linear.js │ │ │ ├── MediaFile.js │ │ │ ├── TrackingEvent.js │ │ │ ├── UniversalAdId.js │ │ │ ├── VASTClient.js │ │ │ ├── VASTError.js │ │ │ ├── VASTIntegrator.js │ │ │ ├── VASTResponse.js │ │ │ ├── VASTTracker.js │ │ │ ├── Verification.js │ │ │ ├── VideoClicks.js │ │ │ ├── ViewableImpression.js │ │ │ ├── Wrapper.js │ │ │ ├── parsers.js │ │ │ └── vastUtil.js │ │ └── vpaid │ │ │ ├── VPAIDAdUnitWrapper.js │ │ │ ├── VPAIDHTML5Tech.js │ │ │ └── VPAIDIntegrator.js │ ├── plugin │ │ ├── components │ │ │ ├── black-poster.js │ │ │ ├── black-poster_4.js │ │ │ └── black-poster_5.js │ │ └── videojs.vast.vpaid.js │ ├── utils │ │ ├── anVideoViewability.js │ │ ├── async.js │ │ ├── consoleLogger.js │ │ ├── dom.js │ │ ├── http.js │ │ ├── mimetypes.js │ │ ├── playerUtils.js │ │ ├── urlUtils.js │ │ ├── utilityFunctions.js │ │ └── xml.js │ └── videojs_5.vast.vpaid.js └── styles │ ├── ads-label.scss │ ├── black-poster.scss │ ├── videojs.vast.scss │ ├── videojs.vast.vpaid.scss │ └── videojs.vpaid.scss ├── test ├── ads │ ├── icon │ │ └── IconIntegrator.spec.js │ ├── vast │ │ ├── Ad.spec.js │ │ ├── Companion.spec.js │ │ ├── Creative.spec.js │ │ ├── Icon.spec.js │ │ ├── Inline.spec.js │ │ ├── Linear.spec.js │ │ ├── MediaFile.spec.js │ │ ├── TrackingEvent.spec.js │ │ ├── VASTClient.spec.js │ │ ├── VASTIntegrator.spec.js │ │ ├── VASTResponse.spec.js │ │ ├── VASTTracker.spec.js │ │ ├── VideoClicks.spec.js │ │ ├── Wrapper.spec.js │ │ └── vastUtil.spec.js │ └── vpaid │ │ ├── VPAIDAdUnitWrapper.spec.js │ │ ├── VPAIDHTML5Tech.spec.js │ │ └── VPAIDIntegrator.spec.js ├── plugin │ └── videojs.vast.spec.js ├── test-utils.css ├── test-utils.js └── utils │ ├── async.spec.js │ ├── dom.spec.js │ ├── http.spec.js │ ├── playerUtils.spec.js │ ├── urlUtils.spec.js │ ├── utilityFunctions.spec.js │ └── xml │ ├── xml-vast.spec.js │ └── xml.spec.js ├── webpack.common.js ├── webpack.dev.js ├── webpack.prod.js └── webpack.properties.js /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | languages: 2 | Ruby: false 3 | JavaScript: true 4 | PHP: false 5 | Python: false 6 | exclude_paths: 7 | - "bin/*" 8 | - "build/*" 9 | - "lib/*" 10 | - "src/utils/pollyfill.js" 11 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.{js,css,json}] 2 | indent_style = space 3 | indent_size = 2 4 | 5 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "browser": true 4 | }, 5 | "extends": "standard", 6 | "parserOptions": { 7 | "ecmaVersion": 5, 8 | "sourceType": "module", 9 | "ecmaFeatures": { 10 | "jsx": false 11 | }, 12 | }, 13 | "rules": { 14 | "no-unused-vars": "off", 15 | "comma-dangle": "off", 16 | "semi": "off", 17 | "space-before-function-paren": 2, 18 | "padding-line-between-statements": "off", 19 | "padded-blocks": "off", 20 | 21 | "camelcase": "off", 22 | "eqeqeq": "off", 23 | "no-return-assign": "off", 24 | "no-throw-literal": "off", 25 | "no-undef": "off", 26 | "no-useless-escape": "off", 27 | 28 | "no-mixed-spaces-and-tabs": "off", 29 | "indent": "off", 30 | "no-tabs": "off", 31 | "brace-style": "off", 32 | "standard/no-callback-literal": "off", 33 | "one-var": "off", 34 | "no-extra-boolean-cast": "off", 35 | "new-cap": "off", 36 | "handle-callback-err": 'off', 37 | "no-extend-native": "off", 38 | "no-multiple-empty-lines": "off", 39 | "no-debugger": "off", 40 | "no-inline-comments": "off", 41 | "no-multi-spaces": "off" 42 | } 43 | }; 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | bower_components/ 4 | dev/ 5 | .idea/ 6 | coverage/ 7 | *.swp 8 | *.swo 9 | .publish/ -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prebid/videojs-mailonline-plugin/50df36f02ef33aa6a6d73f509a0a357e05c878c3/.npmignore -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.12.2 4 | before_install: 5 | - npm install -g bower 6 | - npm install -g codeclimate-test-reporter 7 | - export CHROME_BIN=chromium-browser 8 | - export DISPLAY=:99.0 9 | - sh -e /etc/init.d/xvfb start 10 | install: 11 | - npm install 12 | - bower install 13 | after_success: 14 | - ./scripts/release 15 | branches: 16 | except: 17 | - RELEASE 18 | addons: 19 | code_climate: 20 | repo_token: 21 | secure: Fng7h8a5o8ti8w0lRjCjmpdI/YYQK1qRMfUFmev6oKzOGOATyMP4yJ7V3yuf+QPP5kAO23/gHkoQvKsbL7IyvHlwAqo4fHbwWHMF6ktURtxkRcs59P5kJunRht/C4Hyx9ZSmahHxv0WsmbxRGQ2dQJrgF2m71H7KrBXLwv+s+YVVAtEc6jGqVlVaalmrS4g2lWUQtRLcxSmFvqjigBDo4h5H2vqH1P0Qk1Xm8h7NTp3dq3c86l2hnzQFgfNJL0PjJPm/UKslwGoo35OQxejGvffVN9KV8ZgA0Ql52Zu3/UvdEvbzRv6QNxU6rJwjbfgKYD0oNGmKxRPo68aETtefRAju/sgY53HZP7Z0oZTGHQsBMrfQ2cJSopIetPWPw6sfd5WvFS4W0MPoAEVw8zcvUvBA4HZAHUP4y/k8FvYWDC/8G2YYXTeTEb1ATym0l7a67e8eWgvFC6fuOXLcVc49RftQOksbDTpnqOaqtWJxtw9WVkNFCSE1jXFCibxtaKAAIU05TCdypk4ita30dDJ2FFPP4wp/wmXaogj4rhCXlCPQx3em+Mpqr3yqEnt73dNQRMFEyfxubDMopodyUMbCdHv0CUqDpu173494wkO6wVg7rBnJqqAHXEJMI6bh5QtBa6MtmgOqVESaNcnxTaRAJ22hlhnIbVwp9Pl6uP0tMv8= 22 | after_script: 23 | - codeclimate-test-reporter < coverage/lcov.info 24 | deploy: 25 | provider: npm 26 | email: "table1.mailonline@gmail.com" 27 | api_key: 28 | secure: UJJjbQRho1Q4XVAz8jXVbVmhQlbsZLGTz9kFHzniMRtbiMvs3w5gv/OKqpSqyl1reuv3JYOGB63mM8wjZ/N8QxDNZH8KAnA1jU2zd0vZCoXNVvcYdF8yspDACh+styBJU2tsfa92SfDr7D6wlXYfG3IZtiBfR69puc4n17DSytibZmINrjf6oyO0bezUma6GENo6GxBaOX+KpcPVqU/BlYyEntyJnfrp/1zTPlmH3LOJCjdErJ4XuG3IsjnS/Lk9jNFZu0a/Yep+pVrCSB3Rjb/0I3+NEtcP8pjhdn9kn6RzaJ56RIpm2+7bNmy7dWmNYkWLryuwHem5VAS54EyQf2dE5NsTMQwSo2aTOSouLObJNF7AWXfFhQkxzP75/P7L7WCf5yWkwy5P9LQj1YFWU6RPhVDCBY6XYeRa/pTbFoFiYRqq4w7UOss9I38DPctrfUijhHCcqow3qgFFH2vcpufTERTrk4KjpcAvKdlzX1qQoz6VYoJa1274Uxlg2p1/MjvPSW0UbBqNgi8tYUDGJkmu1jLSexK0Sih+wGwJe9vDbgF/dpBQubgCMjfz2siWSvIdvl+DoIUiU1TaosVGDX3Cuesredp7bzFlZmOeMoGemuGoBcZ+UFxyUBA3elxzP2B1Y/JxJJi5tXbcINCclKzK0wMbO6jJrXmZvomlYFk= 29 | on: 30 | tags: true 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 MailOnline 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /build/BuildTaskDoc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function BuildTaskDoc (name, description, order) { 4 | this.name = name; 5 | this.description = description; 6 | this.order = order || 100; 7 | }; 8 | -------------------------------------------------------------------------------- /build/COLORS.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var colors = require('colors'); 4 | 5 | colors.setTheme({ 6 | silly: 'rainbow', 7 | input: 'grey', 8 | verbose: 'cyan', 9 | prompt: 'grey', 10 | info: 'green', 11 | data: 'grey', 12 | help: 'cyan', 13 | warn: 'yellow', 14 | debug: 'blue', 15 | error: 'red' 16 | }); 17 | 18 | module.exports = colors; 19 | -------------------------------------------------------------------------------- /build/build.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var clone = require('gulp-clone'); 4 | var cssnano = require('gulp-cssnano'); 5 | var gulp = require('gulp'); 6 | var gulpif = require('gulp-if'); 7 | var lazypipe = require('lazypipe'); 8 | var path = require('path'); 9 | var rename = require('gulp-rename'); 10 | var runSequence = require('run-sequence'); 11 | var sass = require('gulp-sass'); 12 | var size = require('gulp-size'); 13 | var sourcemaps = require('gulp-sourcemaps'); 14 | 15 | var webpack = require('webpack'); 16 | var gutil = require('gutil'); 17 | 18 | var webpack_config_dev = require('../webpack.dev.js')(null, null); 19 | var webpack_config_prod = require('../webpack.prod.js')(null, null); 20 | 21 | var BuildTaskDoc = require('./BuildTaskDoc'); 22 | var config = require('./config'); 23 | 24 | var devPath = path.join(config.DEV, '/'); 25 | var distPath = path.join(config.DIST, '/'); 26 | var isProduction = config.env === 'production'; 27 | 28 | gulp.task('build', function (done) { 29 | runSequence( 30 | 'build-prod', 31 | 'build-styles', 32 | 'test', 33 | function (error) { 34 | if (error) { 35 | console.log(error.message.red); 36 | } else { 37 | console.log('BUILD FINISHED SUCCESSFULLY'.green); 38 | } 39 | done(error); 40 | }); 41 | }); 42 | 43 | 44 | var getWebpackCallback = function getWebpackCallback (done) { 45 | return function (err, stats) { 46 | if (err) { 47 | throw new gutil.PluginError('webpack', err); 48 | } 49 | else { 50 | gutil.log('=================== Webpack Build Report ===================\n', stats.toString()); 51 | } 52 | done(); 53 | }; 54 | }; 55 | 56 | gulp.task('build-dev', function (done) { 57 | webpack(webpack_config_dev, getWebpackCallback(done)); 58 | }); 59 | 60 | gulp.task('build-prod', function (done) { 61 | webpack(webpack_config_prod, getWebpackCallback(done)); 62 | }); 63 | 64 | gulp.task('build-styles', function () { 65 | 66 | var entryFile = path.join('src/styles', 'videojs.vast.vpaid.scss'); 67 | var destPath = path.join(devPath, 'styles'); 68 | 69 | return gulp.src(entryFile) 70 | .pipe(sourcemaps.init()) 71 | .pipe(sass() 72 | .on('error', sass.logError)) 73 | .pipe(sourcemaps.write()) 74 | .pipe(rename(function (path) { 75 | path.basename = 'bc_vpaid_vast_mo'; 76 | })) 77 | .pipe(gulp.dest(destPath)) 78 | .pipe(size({showFiles: true, title: '[Styles]'})); 79 | }); 80 | 81 | module.exports = new BuildTaskDoc('build', 'This task builds the plugin and runs karma tests', 1); 82 | -------------------------------------------------------------------------------- /build/ci-test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var gulp = require('gulp'); 4 | var Server = require('karma').Server; 5 | var runSequence = require('run-sequence'); 6 | var path = require('path'); 7 | 8 | var config = require('./config'); 9 | var BuildTaskDoc = require('./BuildTaskDoc'); 10 | 11 | /** 12 | * Run test once and exit 13 | */ 14 | 15 | var testTasks = []; 16 | config.versions.forEach(function (version) { 17 | 18 | var testTask = 'ci-test-videojs_' + version; 19 | 20 | gulp.task(testTask, function (done) { 21 | 22 | new Server({ 23 | configFile: path.join(__dirname, '/../karma.conf.js'), 24 | files: config.testFiles(version), 25 | autoWatch: false, 26 | singleRun: true, 27 | browsers: ['Chrome_travis_ci'], 28 | reporters: ['spec', 'coverage'], 29 | customLaunchers: { 30 | Chrome_travis_ci: { 31 | base: 'Chrome', 32 | flags: ['--no-sandbox'] 33 | } 34 | }, 35 | coverageReporter: { 36 | reporters: [ 37 | { 38 | type: 'text', 39 | dir: 'coverage/', 40 | file: 'coverage.txt' 41 | }, 42 | { 43 | type: 'html', 44 | dir: 'coverage/' 45 | }, 46 | { 47 | type: 'lcovonly', 48 | dir: 'coverage/', 49 | subdir: '.' 50 | }, 51 | {type: 'text-summary'} 52 | ] 53 | } 54 | }, function (error) { 55 | done(error); 56 | }).start(); 57 | }); 58 | testTasks.push(testTask); 59 | }); 60 | 61 | 62 | gulp.task('ci-test', function (done) { 63 | 64 | testTasks.push(function (error) { 65 | if (error) { 66 | console.log(error.message.red); 67 | } else { 68 | console.log('TEST FINISHED SUCCESSFULLY'.green); 69 | } 70 | done(error); 71 | }); 72 | runSequence.apply(this, testTasks); 73 | 74 | }); 75 | 76 | module.exports = new BuildTaskDoc('ci-test', 'Starts karma test and generates test code coverage, to be used by CI Server', 2); 77 | -------------------------------------------------------------------------------- /build/config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | var parseArgs = require('minimist'); 5 | 6 | var knownOptions = { 7 | string: 'env', 8 | default: {env: process.env.NODE_ENV || 'development'} 9 | }; 10 | var options = parseArgs(process.argv.slice(2), knownOptions); 11 | 12 | module.exports = { 13 | 14 | versions: ['5'], 15 | 16 | options: options, 17 | env: options.env, 18 | git: { 19 | remoteUrl: process.env.GH_TOKEN ? 'https://' + process.env.GH_TOKEN + '@github.com/MailOnline/videojs-vast-vpaid' : 'origin' 20 | }, 21 | 22 | DIST: path.normalize('__dirname/../dist'), 23 | DEV: path.normalize('__dirname/../dist'), 24 | 25 | testFiles: function testFiles (videojsVersion) { 26 | var dependencies = []; 27 | videojsVersion = videojsVersion || '5'; 28 | 29 | // We add videojs 30 | dependencies.push('node_modules/video.js/dist/video.js'); 31 | return dependencies.concat([ 32 | 'test/test-utils.css', 33 | 'test/**/*.spec.js' 34 | ]); 35 | } 36 | 37 | }; 38 | 39 | 40 | -------------------------------------------------------------------------------- /build/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var gulp = require('gulp'); 4 | var Server = require('karma').Server; 5 | var runSequence = require('run-sequence'); 6 | var path = require('path'); 7 | 8 | var config = require('./config'); 9 | var BuildTaskDoc = require('./BuildTaskDoc'); 10 | 11 | /** 12 | * Run test once and exit 13 | */ 14 | 15 | var testTasks = []; 16 | config.versions.forEach(function (version) { 17 | 18 | var testTask = 'test-videojs_' + version; 19 | 20 | gulp.task(testTask, function (done) { 21 | 22 | var autoWatch = !!config.options['watch']; 23 | var singleRun = !autoWatch; 24 | // singleRun = false; 25 | 26 | new Server({ 27 | configFile: path.join(__dirname, '/../karma.conf.js'), 28 | files: config.testFiles(version), 29 | autoWatch: autoWatch, 30 | singleRun: singleRun 31 | }, function (error) { 32 | done(error); 33 | }).start(); 34 | }); 35 | testTasks.push(testTask); 36 | }); 37 | 38 | gulp.task('test', function (done) { 39 | 40 | testTasks.push(function (error) { 41 | if (error) { 42 | console.log(error.message.red); 43 | } else { 44 | console.log('TEST FINISHED SUCCESSFULLY'.green); 45 | } 46 | done(error); 47 | }); 48 | 49 | runSequence.apply(this, testTasks); 50 | 51 | }); 52 | 53 | module.exports = new BuildTaskDoc('test', 'Starts karma and test the player', 3); 54 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var requireDir = require('require-dir'); 2 | var asciify = require('asciify'); 3 | var gulp = require('gulp'); 4 | var async = require('async'); 5 | var Table = require('cli-table'); 6 | var BuildTaskDoc = require('./build/BuildTaskDoc'); 7 | 8 | // Init colors 9 | require('./build/COLORS'); 10 | 11 | // We retrieve the build-tasks 12 | var buildTasksMap = requireDir('./build'); 13 | 14 | 15 | gulp.task('default', function (finishTask) { 16 | async.series([ 17 | printWelcomeMessage, 18 | printBanner, 19 | printTasksHelpTable 20 | ], 21 | finishTask 22 | ); 23 | 24 | /** Local functions **/ 25 | 26 | function printWelcomeMessage (done) { 27 | console.log("Welcome to MailOnline's new".info); 28 | done(); 29 | } 30 | 31 | function printBanner (done) { 32 | asciify('Videojs Vast Vpaid', function (err, res) { 33 | console.log(res.help); 34 | done(); 35 | }); 36 | } 37 | 38 | function printTasksHelpTable (done) { 39 | var table = new Table({ 40 | head: ['Name', 'Description'], 41 | colWidths: [25, 80], 42 | chars: { 43 | 'top': '═', 44 | 'top-mid': '╤', 45 | 'top-left': '╔', 46 | 'top-right': '╗', 47 | 'bottom': '═', 48 | 'bottom-mid': '╧', 49 | 'bottom-left': '╚', 50 | 'bottom-right': '╝', 51 | 'left': '║', 52 | 'left-mid': '╟', 53 | 'mid': '─', 54 | 'mid-mid': '┼', 55 | 'right': '║', 56 | 'right-mid': '╢', 57 | 'middle': '│' 58 | } 59 | }); 60 | 61 | var buildTasks = getBuildTasks(buildTasksMap); 62 | 63 | buildTasks.forEach(function (task) { 64 | table.push([task.name, task.description]); 65 | }); 66 | 67 | console.log('###### Below, you have the list of all the available build tasks ########'); 68 | console.log(table.toString()); 69 | console.log(' '); 70 | done(); 71 | } 72 | 73 | function getBuildTasks (tasksDir) { 74 | 75 | var tasks = Object.keys(tasksDir) 76 | .map(function mapTasks (taskName) { 77 | return tasksDir[taskName]; 78 | }) 79 | .filter(function filterTasks (task) { 80 | return task instanceof BuildTaskDoc; 81 | }) 82 | .sort(function compareTasks (a, b) { 83 | return a.order > b.order ? 1 : -1; 84 | }); 85 | 86 | return tasks; 87 | } 88 | 89 | }); 90 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | var istanbul = require('browserify-istanbul'); 2 | var config = require('./build/config'); 3 | 4 | module.exports = function (karma) { 5 | karma.set({ 6 | /** 7 | * From where to look for files, starting with the location of this file. 8 | */ 9 | basePath: './', 10 | 11 | files: config.testFiles(), 12 | 13 | /** 14 | * This is the list of file patterns to load into the browser during testing. 15 | */ 16 | frameworks: ['browserify', 'mocha', 'chai-sinon'], 17 | 18 | preprocessors: { 19 | 'src/**/*.js': ['coverage'], 20 | 'test/**/*.js': ['browserify'] 21 | }, 22 | browserify: { 23 | paths: ['src/scripts'], 24 | debug: true, 25 | transform: [ 26 | istanbul({ 27 | // NOTE: Once we got full ES6 there is a problem in Karma/Istanbul please look https://github.com/karma-runner/karma-coverage/issues/157#issuecomment-160555004 28 | ignore: ['**/node_modules/**', '**/test/**'], 29 | }) ] 30 | }, 31 | logLevel: 'LOG_INFO', 32 | // logLevel: 'LOG_DEBUG', // Use this level for full test details 33 | /** 34 | * How to report, by default. 35 | */ 36 | reporters: ['progress', 'coverage'], 37 | 38 | // enable / disable colors in the output (reporters and logs) 39 | colors: true, 40 | 41 | /** 42 | * On which port should the browser connect, on which port is the test runner 43 | * operating, and what is the URL path for the browser to use. 44 | */ 45 | port: 9018, 46 | runnerPort: 9100, 47 | urlRoot: '/', 48 | 49 | /** 50 | * Enable file watching by default. 51 | */ 52 | autoWatch: true, 53 | singleRun: false, 54 | /** 55 | * The list of browsers to launch to test on. This includes only "Firefox" by 56 | * default, but other browser names include: 57 | * Chrome, ChromeCanary, Firefox, Opera, Safari, PhantomJS 58 | * 59 | * Note that you can also use the executable name of the browser, like "chromium" 60 | * or "firefox", but that these vary based on your operating system. 61 | * 62 | * You may also leave this blank and manually navigate your browser to 63 | * http://localhost:9018/ when you're running tests. The window/tab can be left 64 | * open and the tests will automatically occur there during the build. This has 65 | * the aesthetic advantage of not launching a browser every time you save. 66 | */ 67 | browsers: [ 68 | 'Chrome' 69 | ], 70 | 71 | coverageReporter: { 72 | includeAllSources: true, 73 | dir: 'coverage/', 74 | reporters: [ 75 | { type: 'html' }, 76 | { type: 'text-summary' } 77 | ] 78 | } 79 | }); 80 | }; 81 | -------------------------------------------------------------------------------- /license-bc-mol.txt: -------------------------------------------------------------------------------- 1 | This code contains videojs-vast-vpaid(Copyright (c) 2015 MailOnline) which is licensed under the MIT License. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@appnexus/videojs-vast-vpaid", 3 | "version": "1.3.18", 4 | "authors": [ 5 | "Carlos Serrano " 6 | ], 7 | "description": "VAST plugin to use with video.js", 8 | "main": "bin", 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/prebid/videojs-mailonline-plugin.git" 12 | }, 13 | "publishConfig": { 14 | "registry": "https://artifactory.prod.adnxs.net/artifactory/api/npm/npm/" 15 | }, 16 | "keywords": [ 17 | "vpaid", 18 | "html5", 19 | "flash", 20 | "vast", 21 | "videojs", 22 | "js", 23 | "video", 24 | "iab" 25 | ], 26 | "scripts": { 27 | "test": "gulp ci-test", 28 | "build-dev": "webpack --config webpack.dev.js", 29 | "build-prod": "webpack --config webpack.prod.js" 30 | }, 31 | "license": "MIT", 32 | "devDependencies": { 33 | "asciify": "^1.3.5", 34 | "async": "^1.2.1", 35 | "babel-preset-es2015": "^6.3.13", 36 | "browserify-istanbul": "^0.2.1", 37 | "chai": "^3.4.1", 38 | "clean-webpack-plugin": "^2.0.1", 39 | "cli-table": "^0.3.1", 40 | "colors": "^1.3.2", 41 | "eslint": "^5.15.1", 42 | "eslint-config-standard": "^11.0.0", 43 | "eslint-loader": "^2.1.2", 44 | "eslint-plugin-import": "^2.14.0", 45 | "eslint-plugin-node": "^6.0.1", 46 | "eslint-plugin-promise": "^3.8.0", 47 | "eslint-plugin-standard": "^3.0.1", 48 | "eslint-stylish-config": "0.0.2", 49 | "express": "^4.16.4", 50 | "gulp": "^3.9.0", 51 | "gulp-bump": "^1.0.0", 52 | "gulp-clone": "^1.1.4", 53 | "gulp-cssnano": "^2.1.3", 54 | "gulp-gh-pages": "^0.5.2", 55 | "gulp-git": "^1.2.4", 56 | "gulp-if": "^2.0.0", 57 | "gulp-rename": "^1.4.0", 58 | "gulp-sass": "^2.1.1", 59 | "gulp-size": "^2.0.0", 60 | "gulp-sourcemaps": "^1.12.1", 61 | "gulp-supervisor": "^0.1.2", 62 | "gulp-template": "^3.0.0", 63 | "gulp-uglify": "^1.2.0", 64 | "gulp-util": "^3.0.5", 65 | "gutil": "^1.6.4", 66 | "jshint": "^2.9.6", 67 | "jshint-stylish": "^2.1.0", 68 | "karma": "^0.13.19", 69 | "karma-browserify": "^4.4.2", 70 | "karma-chai-sinon": "0.1.5", 71 | "karma-chrome-launcher": "^0.2.2", 72 | "karma-coverage": "^0.5.3", 73 | "karma-firefox-launcher": "^0.1.6", 74 | "karma-mocha": "0.2.1", 75 | "karma-safari-launcher": "0.1.1", 76 | "karma-spec-reporter": "^0.0.23", 77 | "lazypipe": "^1.0.1", 78 | "minimist": "^1.1.1", 79 | "mocha": "^2.2.5", 80 | "request": "^2.88.0", 81 | "require-dir": "^0.3.0", 82 | "run-sequence": "^1.1.0", 83 | "sinon": "1.14.1", 84 | "sinon-chai": "^2.8.0", 85 | "string-replace-webpack-plugin": "^0.1.3", 86 | "video.js": "^5.19.2", 87 | "webpack": "^4.29.6", 88 | "webpack-cli": "^3.2.3", 89 | "webpack-merge": "^4.2.1", 90 | "webpack-strip-block": "^0.2.0" 91 | }, 92 | "dependencies": { 93 | "vpaid-html5-client": "git://github.com/MailOnline/VPAIDHTML5Client.git" 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /scripts/release: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e #Quit as soon as there is an error or exit 4 | 5 | if [ "$TRAVIS" = "true" ] && [ "$TRAVIS_PULL_REQUEST" = "false" ]; then 6 | if [ -n "$USER_NAME" ]; then 7 | # inside this git repo we'll pretend to be a new user 8 | git config --global user.name "$USER_NAME" 9 | git config --global user.email "$USER_MAIL" 10 | fi 11 | 12 | if [ \( "$TRAVIS_BRANCH" = "patch" \) -o \( "$TRAVIS_BRANCH" = "minor" \) -o \( "$TRAVIS_BRANCH" = "major" \) ]; then 13 | gulp build --env production 14 | original_dir=`pwd` 15 | rm -rf /var/tmp/release 16 | mkdir -p /var/tmp/release 17 | cd /var/tmp/release 18 | git clone --branch RELEASE -q --single-branch --depth 1 "https://$GH_TOKEN@github.com/MailOnline/videojs-vast-vpaid" . 19 | old_version=`cat bower.json | grep version | awk -F\" '{print $4}'` 20 | rsync -a --del --exclude=.git --exclude=.publish $original_dir/ ./ 21 | gulp bump --version $old_version --type $TRAVIS_BRANCH 22 | git add -A . 23 | new_version=`cat bower.json | grep version | awk -F\" '{print $4}'` 24 | git commit -m "[RELEASE] build and release $new_version" 25 | git tag $new_version 26 | if git push && git push --tags ; then 27 | # do nothing - the demo component was removed from this project 28 | else 29 | echo "Push failed probably due to concurrent trigger of release build" 30 | fi 31 | cd $original_dir 32 | fi 33 | fi 34 | -------------------------------------------------------------------------------- /src/scripts/ads/vast/Ad.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var InLine = require('./InLine'); 4 | var Wrapper = require('./Wrapper'); 5 | 6 | function Ad (adJTree) { 7 | if (!(this instanceof Ad)) { 8 | return new Ad(adJTree); 9 | } 10 | this.initialize(adJTree); 11 | } 12 | 13 | Ad.prototype.initialize = function (adJTree) { 14 | this.id = adJTree.attr('id'); 15 | this.sequence = adJTree.attr('sequence'); 16 | if (window.mol_vastVersion === 4) { 17 | this.conditionalAd = adJTree.attr('conditionalAd'); 18 | } 19 | 20 | if (adJTree.inLine) { 21 | this.inLine = new InLine(adJTree.inLine); 22 | } 23 | 24 | if (adJTree.wrapper) { 25 | this.wrapper = new Wrapper(adJTree.wrapper); 26 | } 27 | }; 28 | 29 | module.exports = Ad; 30 | -------------------------------------------------------------------------------- /src/scripts/ads/vast/Category.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var xml = require('../../utils/xml'); 4 | var utilities = require('../../utils/utilityFunctions'); 5 | 6 | function Category (categoryJTree) { 7 | if (!(this instanceof Category)) { 8 | return new Category(categoryJTree); 9 | } 10 | 11 | this.authority = categoryJTree.attr('authority'); 12 | this.category = xml.keyValue(categoryJTree); 13 | } 14 | 15 | Category.parseCategories = function parseCategoties (categoriesJTree) { 16 | var categories = []; 17 | var categoriesData; 18 | if (categories) { 19 | categoriesData = utilities.isArray(categoriesJTree) ? categoriesJTree : [categoriesJTree]; 20 | categoriesData.forEach(function (category) { 21 | var cat = new Category(category); 22 | if (cat.category && cat.category.length > 0 && cat.authority && cat.authority.length > 0) { 23 | categories.push(new Category(category)); 24 | } 25 | }); 26 | } 27 | return categories; 28 | }; 29 | 30 | module.exports = Category; 31 | -------------------------------------------------------------------------------- /src/scripts/ads/vast/Companion.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var TrackingEvent = require('./TrackingEvent'); 4 | 5 | var utilities = require('../../utils/utilityFunctions'); 6 | 7 | var xml = require('../../utils/xml'); 8 | 9 | var logger = require('../../utils/consoleLogger'); 10 | 11 | 12 | function Companion (companionJTree) { 13 | if (!(this instanceof Companion)) { 14 | return new Companion(companionJTree); 15 | } 16 | 17 | logger.info(' found companion ad'); 18 | logger.debug(' companionJTree:', companionJTree); 19 | 20 | // Required Elements 21 | this.creativeType = xml.attr(companionJTree.staticResource, 'creativeType'); 22 | this.staticResource = xml.keyValue(companionJTree.staticResource); 23 | 24 | logger.info(' creativeType: ' + this.creativeType); 25 | logger.info(' staticResource: ' + this.staticResource); 26 | 27 | // Weird bug when the JXON tree is built it doesn't handle casing properly in this situation... 28 | var htmlResource = null; 29 | if (xml.keyValue(companionJTree.HTMLResource)) { 30 | htmlResource = xml.keyValue(companionJTree.HTMLResource); 31 | } else if (xml.keyValue(companionJTree.hTMLResource)) { 32 | htmlResource = xml.keyValue(companionJTree.hTMLResource); 33 | } 34 | 35 | if (htmlResource !== null) 36 | { 37 | logger.info(' found html resource', htmlResource); 38 | } 39 | 40 | this.htmlResource = htmlResource; 41 | 42 | var iframeResource = null; 43 | if (xml.keyValue(companionJTree.IFrameResource)) { 44 | iframeResource = xml.keyValue(companionJTree.IFrameResource); 45 | } else if (xml.keyValue(companionJTree.iFrameresource)) { 46 | iframeResource = xml.keyValue(companionJTree.iFrameresource); 47 | } 48 | 49 | if (iframeResource !== null) 50 | { 51 | logger.info(' found iframe resource', iframeResource); 52 | } 53 | 54 | this.iframeResource = iframeResource; 55 | 56 | // Optional fields 57 | this.id = xml.attr(companionJTree, 'id'); 58 | this.width = xml.attr(companionJTree, 'width'); 59 | this.height = xml.attr(companionJTree, 'height'); 60 | this.assetWidth = xml.attr(companionJTree, 'assetWidth'); 61 | this.assetHeight = xml.attr(companionJTree, 'assetHeight'); 62 | this.expandedWidth = xml.attr(companionJTree, 'expandedWidth'); 63 | this.expandedHeight = xml.attr(companionJTree, 'expandedHeight'); 64 | this.apiFramework = xml.attr(companionJTree, 'apiFramework'); 65 | this.adSlotID = xml.attr(companionJTree, 'adSlotID'); 66 | this.companionClickThrough = xml.keyValue(companionJTree.companionClickThrough); 67 | this.trackingEvents = parseTrackingEvents(companionJTree.trackingEvents && companionJTree.trackingEvents.tracking); 68 | 69 | if (window.mol_vastVersion === 4) { 70 | this.pxratio = xml.attr(companionJTree, 'pxratio'); 71 | } 72 | 73 | logger.info(' companionClickThrough: ' + this.companionClickThrough); 74 | 75 | 76 | // **** Local Functions **** // 77 | function parseTrackingEvents (trackingEvents) { 78 | var trackings = []; 79 | if (utilities.isDefined(trackingEvents)) { 80 | trackingEvents = utilities.isArray(trackingEvents) ? trackingEvents : [trackingEvents]; 81 | trackingEvents.forEach(function (trackingData) { 82 | trackings.push(new TrackingEvent(trackingData)); 83 | }); 84 | } 85 | return trackings; 86 | } 87 | } 88 | 89 | module.exports = Companion; 90 | -------------------------------------------------------------------------------- /src/scripts/ads/vast/Creative.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Linear = require('./Linear'); 4 | var Companion = require('./Companion'); 5 | var UniversalAdId = require('./UniversalAdId'); 6 | var utilities = require('../../utils/utilityFunctions'); 7 | 8 | function Creative (creativeJTree) { 9 | if (!(this instanceof Creative)) { 10 | return new Creative(creativeJTree); 11 | } 12 | 13 | this.id = creativeJTree.attr('id'); 14 | this.sequence = creativeJTree.attr('sequence'); 15 | this.adId = creativeJTree.attr('adId'); 16 | this.apiFramework = creativeJTree.attr('apiFramework'); 17 | 18 | if (creativeJTree.linear) { 19 | this.linear = new Linear(creativeJTree.linear); 20 | } 21 | 22 | if (creativeJTree.companionAds) { 23 | var companions = []; 24 | var companionAds = creativeJTree.companionAds && creativeJTree.companionAds.companion; 25 | if (utilities.isDefined(companionAds)) { 26 | companionAds = utilities.isArray(companionAds) ? companionAds : [companionAds]; 27 | companionAds.forEach(function (companionData) { 28 | companions.push(new Companion(companionData)); 29 | }); 30 | } 31 | this.companionAds = companions; 32 | } 33 | 34 | if (window.mol_vastVersion === 4) { 35 | this.universalAdId = new UniversalAdId(creativeJTree.universalAdId); 36 | } 37 | } 38 | 39 | /** 40 | * Returns true if the browser supports at the creative. 41 | */ 42 | Creative.prototype.isSupported = function () { 43 | if (this.linear) { 44 | return this.linear.isSupported(); 45 | } 46 | 47 | return true; 48 | }; 49 | 50 | Creative.parseCreatives = function parseCreatives (creativesJTree) { 51 | var creatives = []; 52 | var creativesData; 53 | if (utilities.isDefined(creativesJTree) && utilities.isDefined(creativesJTree.creative)) { 54 | creativesData = utilities.isArray(creativesJTree.creative) ? creativesJTree.creative : [creativesJTree.creative]; 55 | creativesData.forEach(function (creative) { 56 | creatives.push(new Creative(creative)); 57 | }); 58 | } 59 | return creatives; 60 | }; 61 | 62 | module.exports = Creative; 63 | -------------------------------------------------------------------------------- /src/scripts/ads/vast/Icon.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var utilities = require('../../utils/utilityFunctions'); 4 | var xml = require('../../utils/xml'); 5 | var parsers = require('./parsers'); 6 | 7 | var attributesList = [ 8 | 'program', 9 | 'width', 10 | 'height', 11 | 'xPosition', 12 | 'yPosition', 13 | 'duration', 14 | 'offset', 15 | 'apiFramework', 16 | 'pxratio' 17 | ]; 18 | 19 | function Icon (iconJTree) { 20 | if (!(this instanceof Icon)) { 21 | return new Icon(iconJTree); 22 | } 23 | 24 | // Attributes 25 | for (var x = 0; x < attributesList.length; x++) { 26 | var attribute = attributesList[x]; 27 | this[attribute] = iconJTree.attr(attribute); 28 | } 29 | if (this.duration) { 30 | this.duration = parsers.duration(this.duration); 31 | } 32 | if (this.offset) { 33 | this.offset = parsers.duration(this.offset); 34 | } 35 | 36 | // Required Elements 37 | this.creativeType = xml.attr(iconJTree.staticResource, 'creativeType'); 38 | this.staticResource = xml.keyValue(iconJTree.staticResource); 39 | 40 | var htmlResource = null; 41 | if (xml.keyValue(iconJTree.HTMLResource)) { 42 | htmlResource = xml.keyValue(iconJTree.HTMLResource); 43 | } else if (xml.keyValue(iconJTree.hTMLResource)) { 44 | htmlResource = xml.keyValue(iconJTree.hTMLResource); 45 | } 46 | 47 | this.htmlResource = htmlResource; 48 | 49 | var iframeResource = null; 50 | if (xml.keyValue(iconJTree.IFrameResource)) { 51 | iframeResource = xml.keyValue(iconJTree.IFrameResource); 52 | } else if (xml.keyValue(iconJTree.iFrameResource)) { 53 | iframeResource = xml.keyValue(iconJTree.iFrameResource); 54 | } 55 | 56 | this.iframeResource = iframeResource; 57 | 58 | this.iconViewTrackings = parseTrackings(iconJTree.iconViewTracking); 59 | 60 | if (iconJTree.iconClicks) { 61 | this.iconClickThrough = xml.keyValue(iconJTree.iconClicks.iconClickThrough); 62 | this.iconClickTrackings = parseTrackings(iconJTree.iconClicks.iconClickTracking); 63 | } 64 | 65 | // Local functions 66 | function parseTrackings (trackings) { 67 | if (trackings) { 68 | trackings = utilities.isArray(trackings) ? trackings : [trackings]; 69 | return utilities.transformArray(trackings, function (trackings) { 70 | if (utilities.isNotEmptyString(trackings.keyValue)) { 71 | return trackings.keyValue; 72 | } 73 | return undefined; 74 | }); 75 | } 76 | return []; 77 | } 78 | } 79 | 80 | module.exports = Icon; 81 | -------------------------------------------------------------------------------- /src/scripts/ads/vast/InLine.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var vastUtil = require('./vastUtil'); 4 | var Creative = require('./Creative'); 5 | var Category = require('./Category'); 6 | var ViewableImpression = require('./ViewableImpression'); 7 | var Verification = require('./Verification'); 8 | 9 | var utilities = require('../../utils/utilityFunctions'); 10 | var xml = require('../../utils/xml'); 11 | 12 | function InLine (inlineJTree) { 13 | if (!(this instanceof InLine)) { 14 | return new InLine(inlineJTree); 15 | } 16 | 17 | // Required Fields 18 | this.adTitle = xml.keyValue(inlineJTree.adTitle); 19 | this.adSystem = xml.keyValue(inlineJTree.adSystem); 20 | this.impressions = vastUtil.parseImpressions(inlineJTree.impression); 21 | this.creatives = Creative.parseCreatives(inlineJTree.creatives); 22 | 23 | // Optional Fields 24 | this.description = xml.keyValue(inlineJTree.description); 25 | this.advertiser = xml.keyValue(inlineJTree.advertiser); 26 | this.surveys = parseSurveys(inlineJTree.survey); 27 | // this.error = xml.keyValue(inlineJTree.error); 28 | this.errors = vastUtil.parseErrors(inlineJTree.error); 29 | this.pricing = xml.keyValue(inlineJTree.pricing); 30 | this.extensions = inlineJTree.extensions; 31 | if (this.extensions) { 32 | this.moat = parseMoat(this.extensions); 33 | } 34 | if (window.mol_vastVersion === 4) { 35 | if (inlineJTree.category) { 36 | this.categories = Category.parseCategories(inlineJTree.category); 37 | } 38 | if (inlineJTree.viewableImpression) { 39 | this.viewableImpression = new ViewableImpression(inlineJTree.viewableImpression); 40 | } 41 | if (inlineJTree.adVerifications) { 42 | this.adVerifications = Verification.parseAdVerifications(inlineJTree.adVerifications); 43 | } 44 | } 45 | 46 | // **** Local Functions **** // 47 | function parseSurveys (inlineSurveys) { 48 | if (inlineSurveys) { 49 | return utilities.transformArray(utilities.isArray(inlineSurveys) ? inlineSurveys : [inlineSurveys], function (survey) { 50 | if (utilities.isNotEmptyString(survey.keyValue)) { 51 | return { 52 | uri: survey.keyValue, 53 | type: survey.attr('type') 54 | }; 55 | } 56 | 57 | return undefined; 58 | }); 59 | } 60 | return []; 61 | } 62 | 63 | function parseMoat (extensions) { 64 | var getMoatConfigData = function (arrExtentions) { 65 | for (var i = 0; i < arrExtentions.length; i++) { 66 | var extension = arrExtentions[i].extension; 67 | if (extension && extension.moat) { 68 | var moat = {}; 69 | var value = xml.keyValue(extension.moat); 70 | var arr = value.split(';'); 71 | for (var j = 0; j < arr.length; j++) { 72 | var params = arr[j].split('='); 73 | if (params && params.length === 2) { 74 | moat[params[0]] = params[1]; 75 | } 76 | } 77 | return moat; 78 | } 79 | } 80 | return undefined; 81 | }; 82 | if (extensions) { 83 | return getMoatConfigData(utilities.isArray(extensions) ? extensions : [extensions]); 84 | } 85 | return undefined; 86 | } 87 | } 88 | 89 | 90 | /** 91 | * Returns true if the browser supports all the creatives. 92 | */ 93 | InLine.prototype.isSupported = function () { 94 | var i, len; 95 | 96 | if (this.creatives.length === 0) { 97 | return false; 98 | } 99 | 100 | for (i = 0, len = this.creatives.length; i < len; i += 1) { 101 | if (!this.creatives[i].isSupported()) { 102 | return false; 103 | } 104 | } 105 | return true; 106 | }; 107 | 108 | module.exports = InLine; 109 | -------------------------------------------------------------------------------- /src/scripts/ads/vast/InteractiveCreativeFile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var xml = require('../../utils/xml'); 4 | var utilities = require('../../utils/utilityFunctions'); 5 | 6 | function InteractiveCreativeFile (interactiveCreativeFileJTree) { 7 | if (!(this instanceof InteractiveCreativeFile)) { 8 | return new InteractiveCreativeFile(interactiveCreativeFileJTree); 9 | } 10 | 11 | this.type = interactiveCreativeFileJTree.attr('type'); 12 | this.apiFramework = interactiveCreativeFileJTree.attr('apiFramework'); 13 | this.src = xml.keyValue(interactiveCreativeFileJTree); 14 | } 15 | 16 | InteractiveCreativeFile.parseInteractiveCreativeFiles = function parseInteractiveCreativeFiles (icfsJTree) { 17 | var interactiveCreativeFiles = []; 18 | var interactiveCreativeFilesData; 19 | if (utilities.isDefined(icfsJTree)) { 20 | interactiveCreativeFilesData = utilities.isArray(icfsJTree) ? icfsJTree : [icfsJTree]; 21 | interactiveCreativeFilesData.forEach(function (fileJTree) { 22 | interactiveCreativeFiles.push(new InteractiveCreativeFile(fileJTree)); 23 | }); 24 | } 25 | return interactiveCreativeFiles; 26 | }; 27 | 28 | module.exports = InteractiveCreativeFile; 29 | -------------------------------------------------------------------------------- /src/scripts/ads/vast/Linear.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var TrackingEvent = require('./TrackingEvent'); 4 | var MediaFile = require('./MediaFile'); 5 | var VideoClicks = require('./VideoClicks'); 6 | var InteractiveCreativeFile = require('./InteractiveCreativeFile'); 7 | var Icon = require('./Icon'); 8 | 9 | var utilities = require('../../utils/utilityFunctions'); 10 | var parsers = require('./parsers'); 11 | 12 | var xml = require('../../utils/xml'); 13 | 14 | 15 | function Linear (linearJTree) { 16 | if (!(this instanceof Linear)) { 17 | return new Linear(linearJTree); 18 | } 19 | 20 | // Required Elements 21 | this.duration = parsers.duration(xml.keyValue(linearJTree.duration)); 22 | this.mediaFiles = parseMediaFiles(linearJTree.mediaFiles && linearJTree.mediaFiles.mediaFile); 23 | 24 | // Optional fields 25 | this.trackingEvents = parseTrackingEvents(linearJTree.trackingEvents && linearJTree.trackingEvents.tracking, this.duration); 26 | this.skipoffset = parsers.offset(xml.attr(linearJTree, 'skipoffset'), this.duration); 27 | 28 | // adjust skipoffset to publisher settings 29 | if (this.duration && window._molSettings && window._molSettings.skippable) { 30 | if (window._molSettings.skippable.enabled) { 31 | if (this.duration >= window._molSettings.skippable.videoThreshold) { 32 | this.skipoffset = window._molSettings.skippable.videoOffset; 33 | } 34 | else { 35 | this.skipoffset = null; 36 | } 37 | } 38 | else { 39 | this.skipoffset = null; 40 | } 41 | } 42 | 43 | if (linearJTree.videoClicks) { 44 | this.videoClicks = new VideoClicks(linearJTree.videoClicks); 45 | } 46 | 47 | if (linearJTree.adParameters) { 48 | this.adParameters = xml.keyValue(linearJTree.adParameters); 49 | 50 | if (xml.attr(linearJTree.adParameters, 'xmlEncoded')) { 51 | this.adParameters = xml.decode(this.adParameters); 52 | } 53 | } 54 | 55 | if (window.mol_vastVersion === 4) { 56 | if (linearJTree.mediaFiles && linearJTree.mediaFiles.mezzanine) { 57 | this.mezzanine = xml.keyValue(linearJTree.mediaFiles.mezzanine); 58 | } 59 | if (linearJTree.mediaFiles && linearJTree.mediaFiles.interactiveCreativeFile) { 60 | this.interactiveCreativeFiles = InteractiveCreativeFile.parseInteractiveCreativeFiles(linearJTree.mediaFiles.interactiveCreativeFile); 61 | } 62 | } 63 | 64 | if (linearJTree.icons) { 65 | this.icons = parseIcons(linearJTree.icons && linearJTree.icons.icon); 66 | } 67 | 68 | // **** Local Functions **** // 69 | function parseTrackingEvents (trackingEvents, duration) { 70 | var trackings = []; 71 | if (utilities.isDefined(trackingEvents)) { 72 | trackingEvents = utilities.isArray(trackingEvents) ? trackingEvents : [trackingEvents]; 73 | trackingEvents.forEach(function (trackingData) { 74 | trackings.push(new TrackingEvent(trackingData, duration)); 75 | }); 76 | } 77 | return trackings; 78 | } 79 | 80 | function parseMediaFiles (mediaFilesJxonTree) { 81 | var mediaFiles = []; 82 | if (utilities.isDefined(mediaFilesJxonTree)) { 83 | mediaFilesJxonTree = utilities.isArray(mediaFilesJxonTree) ? mediaFilesJxonTree : [mediaFilesJxonTree]; 84 | 85 | mediaFilesJxonTree.forEach(function (mfData) { 86 | mediaFiles.push(new MediaFile(mfData)); 87 | }); 88 | } 89 | return mediaFiles; 90 | } 91 | 92 | function parseIcons (iconsJxonTree) { 93 | var icons = []; 94 | if (utilities.isDefined(iconsJxonTree)) { 95 | iconsJxonTree = utilities.isArray(iconsJxonTree) ? iconsJxonTree : [iconsJxonTree]; 96 | 97 | iconsJxonTree.forEach(function (iconData) { 98 | icons.push(new Icon(iconData)); 99 | }); 100 | } 101 | return icons; 102 | } 103 | } 104 | 105 | /** 106 | * Must return true if at least one of the MediaFiles' type is supported 107 | */ 108 | Linear.prototype.isSupported = function () { 109 | var i, len; 110 | for (i = 0, len = this.mediaFiles.length; i < len; i += 1) { 111 | if (this.mediaFiles[i].isSupported()) { 112 | return true; 113 | } 114 | } 115 | 116 | return false; 117 | }; 118 | 119 | module.exports = Linear; 120 | -------------------------------------------------------------------------------- /src/scripts/ads/vast/MediaFile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var xml = require('../../utils/xml'); 4 | var vastUtil = require('./vastUtil'); 5 | 6 | var attributesList = [ 7 | // Required attributes 8 | 'delivery', 9 | 'type', 10 | 'width', 11 | 'height', 12 | // Optional attributes 13 | 'codec', 14 | 'id', 15 | 'bitrate', 16 | 'minBitrate', 17 | 'maxBitrate', 18 | 'scalable', 19 | 'maintainAspectRatio', 20 | 'apiFramework' 21 | ]; 22 | 23 | function MediaFile (mediaFileJTree) { 24 | if (!(this instanceof MediaFile)) { 25 | return new MediaFile(mediaFileJTree); 26 | } 27 | 28 | // Required attributes 29 | this.src = xml.keyValue(mediaFileJTree); 30 | 31 | for (var x = 0; x < attributesList.length; x++) { 32 | var attribute = attributesList[x]; 33 | this[attribute] = mediaFileJTree.attr(attribute); 34 | } 35 | } 36 | 37 | MediaFile.prototype.isSupported = function () { 38 | if (vastUtil.isVPAID(this)) { 39 | return !!vastUtil.findSupportedVPAIDTech(this.type); 40 | } 41 | 42 | if (this.type === 'video/x-flv') { 43 | return false; 44 | } 45 | 46 | return true; 47 | }; 48 | 49 | module.exports = MediaFile; 50 | -------------------------------------------------------------------------------- /src/scripts/ads/vast/TrackingEvent.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var parsers = require('./parsers'); 4 | 5 | var xml = require('../../utils/xml'); 6 | 7 | function TrackingEvent (trackingJTree, duration) { 8 | if (!(this instanceof TrackingEvent)) { 9 | return new TrackingEvent(trackingJTree, duration); 10 | } 11 | 12 | this.name = trackingJTree.attr('event'); 13 | this.uri = xml.keyValue(trackingJTree); 14 | 15 | if (this.name === 'progress') { 16 | this.offset = parsers.offset(trackingJTree.attr('offset'), duration); 17 | } 18 | } 19 | 20 | module.exports = TrackingEvent; 21 | -------------------------------------------------------------------------------- /src/scripts/ads/vast/UniversalAdId.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function UniversalAdId (universalAdIdJTree) { 4 | if (!(this instanceof UniversalAdId)) { 5 | return new UniversalAdId(universalAdIdJTree); 6 | } 7 | 8 | if (universalAdIdJTree) { 9 | this.idRegistry = universalAdIdJTree.attr('idRegistry'); 10 | this.idValue = universalAdIdJTree.attr('idValue'); 11 | } 12 | else { 13 | this.idRegistry = 'unknown'; 14 | this.idValue = 'unknown'; 15 | } 16 | } 17 | 18 | module.exports = UniversalAdId; 19 | -------------------------------------------------------------------------------- /src/scripts/ads/vast/VASTError.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function VASTError (message, code) { 4 | this.message = 'VAST Error: ' + (message || ''); 5 | if (code) { 6 | this.code = code; 7 | } 8 | } 9 | 10 | VASTError.prototype = new Error(); 11 | VASTError.prototype.name = 'VAST Error'; 12 | 13 | module.exports = VASTError; 14 | -------------------------------------------------------------------------------- /src/scripts/ads/vast/VASTResponse.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Ad = require('./Ad'); 4 | var VideoClicks = require('./VideoClicks'); 5 | var Linear = require('./Linear'); 6 | var InLine = require('./InLine'); 7 | var Wrapper = require('./Wrapper'); 8 | 9 | var utilities = require('../../utils/utilityFunctions'); 10 | // var xml = require('../../utils/xml'); 11 | 12 | window.InLine__A = InLine; 13 | function VASTResponse () { 14 | if (!(this instanceof VASTResponse)) { 15 | return new VASTResponse(); 16 | } 17 | 18 | this._linearAdded = false; 19 | this.vastVersion = undefined; 20 | this.ads = []; 21 | this.errorURLMacros = []; 22 | this.impressions = []; 23 | this.clickTrackings = []; 24 | this.customClicks = []; 25 | this.trackingEvents = {}; 26 | this.mediaFiles = []; 27 | this.clickThrough = undefined; 28 | this.adTitle = ''; 29 | this.duration = undefined; 30 | this.skipoffset = undefined; 31 | this.icons = []; 32 | } 33 | 34 | VASTResponse.prototype.addAd = function (ad) { 35 | var inLine, wrapper; 36 | if (ad instanceof Ad) { 37 | inLine = ad.inLine; 38 | wrapper = ad.wrapper; 39 | 40 | this.ads.push(ad); 41 | 42 | if (inLine) { 43 | this._addInLine(inLine); 44 | } 45 | 46 | if (wrapper) { 47 | this._addWrapper(wrapper); 48 | } 49 | } 50 | }; 51 | 52 | /* 53 | VASTResponse.prototype._addErrorTrackUrl = function (error) { 54 | var errorURL = error instanceof xml.JXONTree ? xml.keyValue(error) : error; 55 | if (errorURL) { 56 | this.errorURLMacros.push(errorURL); 57 | } 58 | }; 59 | */ 60 | VASTResponse.prototype._addErrorTrackUrls = function (errors) { 61 | utilities.isArray(errors) && appendToArray(this.errorURLMacros, errors); 62 | }; 63 | 64 | VASTResponse.prototype._addImpressions = function (impressions) { 65 | utilities.isArray(impressions) && appendToArray(this.impressions, impressions); 66 | }; 67 | 68 | VASTResponse.prototype._addClickThrough = function (clickThrough) { 69 | if (utilities.isNotEmptyString(clickThrough)) { 70 | this.clickThrough = clickThrough; 71 | } 72 | }; 73 | 74 | VASTResponse.prototype._addClickTrackings = function (clickTrackings) { 75 | utilities.isArray(clickTrackings) && appendToArray(this.clickTrackings, clickTrackings); 76 | }; 77 | 78 | VASTResponse.prototype._addCustomClicks = function (customClicks) { 79 | utilities.isArray(customClicks) && appendToArray(this.customClicks, customClicks); 80 | }; 81 | 82 | VASTResponse.prototype._addTrackingEvents = function (trackingEvents) { 83 | var eventsMap = this.trackingEvents; 84 | 85 | if (trackingEvents) { 86 | trackingEvents = utilities.isArray(trackingEvents) ? trackingEvents : [trackingEvents]; 87 | trackingEvents.forEach(function (trackingEvent) { 88 | if (!eventsMap[trackingEvent.name]) { 89 | eventsMap[trackingEvent.name] = []; 90 | } 91 | eventsMap[trackingEvent.name].push(trackingEvent); 92 | }); 93 | } 94 | }; 95 | 96 | VASTResponse.prototype._addTitle = function (title) { 97 | if (utilities.isNotEmptyString(title)) { 98 | this.adTitle = title; 99 | } 100 | }; 101 | 102 | VASTResponse.prototype._addDuration = function (duration) { 103 | if (utilities.isNumber(duration)) { 104 | this.duration = duration; 105 | } 106 | }; 107 | 108 | VASTResponse.prototype._addVideoClicks = function (videoClicks) { 109 | if (videoClicks instanceof VideoClicks) { 110 | this._addClickThrough(videoClicks.clickThrough); 111 | this._addClickTrackings(videoClicks.clickTrackings); 112 | this._addCustomClicks(videoClicks.customClicks); 113 | } 114 | }; 115 | 116 | VASTResponse.prototype._addMediaFiles = function (mediaFiles) { 117 | utilities.isArray(mediaFiles) && appendToArray(this.mediaFiles, mediaFiles); 118 | }; 119 | 120 | VASTResponse.prototype._addSkipoffset = function (offset) { 121 | if (window._molSettings && window._molSettings.skippable) { 122 | if (window._molSettings.skippable.enabled) { 123 | if (!this.duration || !window._molSettings.skippable.videoThreshold || this.duration >= window._molSettings.skippable.videoThreshold) { 124 | this.skipoffset = window._molSettings.skippable.videoOffset; 125 | } 126 | } 127 | } 128 | else if (offset) { 129 | this.skipoffset = offset; 130 | } 131 | }; 132 | 133 | VASTResponse.prototype._addAdParameters = function (adParameters) { 134 | if (adParameters) { 135 | this.adParameters = adParameters; 136 | } 137 | }; 138 | 139 | VASTResponse.prototype._addIcons = function (icons) { 140 | utilities.isArray(icons) && appendToArray(this.icons, icons); 141 | }; 142 | 143 | VASTResponse.prototype._addLinear = function (linear) { 144 | if (linear instanceof Linear) { 145 | this._addDuration(linear.duration); 146 | this._addTrackingEvents(linear.trackingEvents); 147 | this._addVideoClicks(linear.videoClicks); 148 | this._addMediaFiles(linear.mediaFiles); 149 | this._addSkipoffset(linear.skipoffset); 150 | this._addAdParameters(linear.adParameters); 151 | this._addIcons(linear.icons); 152 | this._linearAdded = true; 153 | } 154 | }; 155 | 156 | VASTResponse.prototype._addInLine = function (inLine) { 157 | var that = this; 158 | 159 | if (inLine instanceof InLine) { 160 | this._addTitle(inLine.adTitle); 161 | this._addErrorTrackUrls(inLine.errors); 162 | this._addImpressions(inLine.impressions); 163 | 164 | inLine.creatives.forEach(function (creative) { 165 | if (creative.linear) { 166 | that._addLinear(creative.linear); 167 | } 168 | }); 169 | } 170 | }; 171 | 172 | VASTResponse.prototype._addWrapper = function (wrapper) { 173 | var that = this; 174 | 175 | if (wrapper instanceof Wrapper) { 176 | this._addErrorTrackUrls(wrapper.errors); 177 | this._addImpressions(wrapper.impressions); 178 | 179 | wrapper.creatives.forEach(function (creative) { 180 | var linear = creative.linear; 181 | if (linear) { 182 | that._addVideoClicks(linear.videoClicks); 183 | that.clickThrough = undefined;// We ensure that no clickThrough has been added 184 | that._addTrackingEvents(linear.trackingEvents); 185 | that._addIcons(linear.icons); 186 | } 187 | }); 188 | } 189 | }; 190 | 191 | VASTResponse.prototype.hasLinear = function () { 192 | return this._linearAdded; 193 | }; 194 | 195 | function appendToArray (array, items) { 196 | items.forEach(function (item) { 197 | if (item) { 198 | array.push(item); 199 | } 200 | }); 201 | } 202 | 203 | module.exports = VASTResponse; 204 | 205 | -------------------------------------------------------------------------------- /src/scripts/ads/vast/VASTTracker.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var VASTError = require('./VASTError'); 4 | var VASTResponse = require('./VASTResponse'); 5 | var vastUtil = require('./vastUtil'); 6 | var utilities = require('../../utils/utilityFunctions'); 7 | 8 | function VASTTracker (assetURI, vastResponse) { 9 | if (!(this instanceof VASTTracker)) { 10 | return new VASTTracker(assetURI, vastResponse); 11 | } 12 | 13 | this.sanityCheck(assetURI, vastResponse); 14 | this.initialize(assetURI, vastResponse); 15 | 16 | } 17 | 18 | VASTTracker.prototype.initialize = function (assetURI, vastResponse) { 19 | this.response = vastResponse; 20 | this.assetURI = assetURI; 21 | this.progress = 0; 22 | this.quartiles = { 23 | firstQuartile: {tracked: false, time: Math.round(25 * vastResponse.duration) / 100}, 24 | midpoint: {tracked: false, time: Math.round(50 * vastResponse.duration) / 100}, 25 | thirdQuartile: {tracked: false, time: Math.round(75 * vastResponse.duration) / 100} 26 | }; 27 | }; 28 | 29 | VASTTracker.prototype.sanityCheck = function (assetURI, vastResponse) { 30 | if (!utilities.isString(assetURI) || utilities.isEmptyString(assetURI)) { 31 | throw new VASTError('on VASTTracker constructor, missing required the URI of the ad asset being played'); 32 | } 33 | 34 | if (!(vastResponse instanceof VASTResponse)) { 35 | throw new VASTError('on VASTTracker constructor, missing required VAST response'); 36 | } 37 | }; 38 | 39 | VASTTracker.prototype.trackURLs = function trackURLs (urls, variables) { 40 | if (utilities.isArray(urls) && urls.length > 0) { 41 | variables = utilities.extend({ 42 | ASSETURI: this.assetURI, 43 | CONTENTPLAYHEAD: vastUtil.formatProgress(this.progress) 44 | }, variables || {}); 45 | 46 | vastUtil.track(urls, variables); 47 | } 48 | }; 49 | 50 | VASTTracker.prototype.trackEvent = function trackEvent (eventName, trackOnce) { 51 | if (this.response.trackingEvents[eventName] && window._molSettings.viewabilityTracking) { 52 | window._molSettings.viewabilityTracking.invokeEvent(eventName); 53 | } 54 | this.trackURLs(getEventUris(this.response.trackingEvents[eventName])); 55 | if (trackOnce) { 56 | this.response.trackingEvents[eventName] = undefined; 57 | } 58 | 59 | // **** Local Function **** // 60 | function getEventUris (trackingEvents) { 61 | var uris; 62 | 63 | if (trackingEvents) { 64 | uris = []; 65 | trackingEvents.forEach(function (event) { 66 | if (!event.uri) { 67 | return; 68 | } 69 | 70 | uris.push(event.uri); 71 | }); 72 | } 73 | return uris; 74 | } 75 | }; 76 | 77 | VASTTracker.prototype.trackProgress = function trackProgress (newProgressInMs) { 78 | var that = this; 79 | var events = []; 80 | var ONCE = true; 81 | var ALWAYS = false; 82 | var trackingEvents = this.response.trackingEvents; 83 | 84 | if (utilities.isNumber(newProgressInMs)) { 85 | addTrackEvent('start', ONCE, newProgressInMs > 0); 86 | addTrackEvent('rewind', ALWAYS, hasRewound(this.progress, newProgressInMs)); 87 | addQuartileEvents(newProgressInMs); 88 | trackProgressEvents(newProgressInMs); 89 | trackEvents(); 90 | this.progress = newProgressInMs; 91 | } 92 | 93 | // **** Local Function **** // 94 | function hasRewound (currentProgress, newProgress) { 95 | var REWIND_THRESHOLD = 3000; // IOS video clock is very unreliable and we need a 3 seconds threshold to ensure that there was a rewind an that it was on purpose. 96 | return currentProgress > newProgressInMs && Math.abs(newProgress - currentProgress) > REWIND_THRESHOLD; 97 | } 98 | 99 | function addTrackEvent (eventName, trackOnce, canBeAdded) { 100 | if (trackingEvents[eventName] && canBeAdded) { 101 | events.push({ 102 | name: eventName, 103 | trackOnce: !!trackOnce 104 | }); 105 | } 106 | } 107 | 108 | function addQuartileEvents (progress) { 109 | var quartiles = that.quartiles; 110 | var firstQuartile = that.quartiles.firstQuartile; 111 | var midpoint = that.quartiles.midpoint; 112 | var thirdQuartile = that.quartiles.thirdQuartile; 113 | 114 | if (!firstQuartile.tracked) { 115 | trackQuartile('firstQuartile', progress); 116 | } else if (!midpoint.tracked) { 117 | trackQuartile('midpoint', progress); 118 | } else if (!thirdQuartile.tracked) { 119 | trackQuartile('thirdQuartile', progress); 120 | } 121 | 122 | // **** Local Function **** // 123 | function trackQuartile (quartileName, progress) { 124 | var quartile = quartiles[quartileName]; 125 | if (canBeTracked(quartile, progress)) { 126 | quartile.tracked = true; 127 | addTrackEvent(quartileName, ONCE, true); 128 | } 129 | } 130 | } 131 | 132 | function canBeTracked (quartile, progress) { 133 | var quartileTime = quartile.time; 134 | // We only fire the quartile event if the progress is bigger than the quartile time by 5 seconds at most. 135 | return progress >= quartileTime && progress <= (quartileTime + 5000); 136 | } 137 | 138 | function trackProgressEvents (progress) { 139 | if (!utilities.isArray(trackingEvents.progress)) { 140 | return; // Nothing to track 141 | } 142 | 143 | var pendingProgressEvts = []; 144 | 145 | trackingEvents.progress.forEach(function (evt) { 146 | if (evt.offset <= progress) { 147 | that.trackURLs([evt.uri]); 148 | } else { 149 | pendingProgressEvts.push(evt); 150 | } 151 | }); 152 | trackingEvents.progress = pendingProgressEvts; 153 | } 154 | 155 | function trackEvents () { 156 | events.forEach(function (event) { 157 | that.trackEvent(event.name, event.trackOnce); 158 | }); 159 | } 160 | }; 161 | 162 | [ 163 | 'rewind', 164 | 'fullscreen', 165 | 'exitFullscreen', 166 | 'pause', 167 | 'resume', 168 | 'mute', 169 | 'unmute', 170 | 'acceptInvitation', 171 | 'acceptInvitationLinear', 172 | 'collapse', 173 | 'expand' 174 | ].forEach(function (eventName) { 175 | VASTTracker.prototype['track' + utilities.capitalize(eventName)] = function () { 176 | this.trackEvent(eventName); 177 | }; 178 | }); 179 | 180 | [ 181 | 'start', 182 | 'skip', 183 | 'close', 184 | 'closeLinear' 185 | ].forEach(function (eventName) { 186 | VASTTracker.prototype['track' + utilities.capitalize(eventName)] = function () { 187 | this.trackEvent(eventName, true); 188 | }; 189 | }); 190 | 191 | [ 192 | 'firstQuartile', 193 | 'midpoint', 194 | 'thirdQuartile' 195 | ].forEach(function (quartile) { 196 | VASTTracker.prototype['track' + utilities.capitalize(quartile)] = function () { 197 | this.quartiles[quartile].tracked = true; 198 | this.trackEvent(quartile, true); 199 | }; 200 | }); 201 | 202 | VASTTracker.prototype.trackComplete = function () { 203 | if (this.quartiles.thirdQuartile.tracked) { 204 | this.trackEvent('complete', true); 205 | } 206 | }; 207 | 208 | VASTTracker.prototype.trackErrorWithCode = function trackErrorWithCode (errorcode) { 209 | if (utilities.isNumber(errorcode)) { 210 | this.trackURLs(this.response.errorURLMacros, {ERRORCODE: errorcode}); 211 | } 212 | }; 213 | 214 | VASTTracker.prototype.trackImpressions = function trackImpressions () { 215 | this.trackURLs(this.response.impressions); 216 | }; 217 | 218 | VASTTracker.prototype.trackCreativeView = function trackCreativeView () { 219 | this.trackEvent('creativeView'); 220 | }; 221 | 222 | VASTTracker.prototype.trackClick = function trackClick () { 223 | if (window._molSettings.viewabilityTracking) { 224 | window._molSettings.viewabilityTracking.invokeEvent('ad-click'); 225 | } 226 | this.trackURLs(this.response.clickTrackings); 227 | }; 228 | 229 | module.exports = VASTTracker; 230 | -------------------------------------------------------------------------------- /src/scripts/ads/vast/Verification.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var xml = require('../../utils/xml'); 4 | var vastUtil = require('./vastUtil'); 5 | var utilities = require('../../utils/utilityFunctions'); 6 | 7 | function Verification (verificationJTree) { 8 | if (!(this instanceof Verification)) { 9 | return new Verification(verificationJTree); 10 | } 11 | 12 | this.vendor = verificationJTree.attr('vendor'); 13 | if (verificationJTree.javaScriptResource) { 14 | this.javaScriptResources = parseResources(verificationJTree.javaScriptResource); 15 | } 16 | if (vastUtil.isFlashSupported() && verificationJTree.flashResource) { 17 | this.javaScriptResources = parseResources(verificationJTree.javaScriptResource); 18 | } 19 | if (verificationJTree.viewableImpression) { 20 | this.viewableImpression = {id: verificationJTree.viewableImpression.attr('id'), uri: xml.keyValue(verificationJTree.viewableImpression)}; 21 | } 22 | 23 | function parseResources (resources) { 24 | var arr = []; 25 | var arrData; 26 | if (resources) { 27 | arrData = utilities.isArray(resources) ? resources : [resources]; 28 | arrData.forEach(function (elem) { 29 | arr.push({apiFramework: elem.attr('apiFramework'), uri: xml.keyValue(elem)}); 30 | }); 31 | } 32 | return arr; 33 | } 34 | } 35 | 36 | Verification.parseAdVerifications = function parseAdVerifications (verificationsJTree) { 37 | var verifications = []; 38 | var verificationsData; 39 | if (utilities.isDefined(verificationsJTree) && utilities.isDefined(verificationsJTree.verification)) { 40 | verificationsData = utilities.isArray(verificationsJTree.verification) ? verificationsJTree.verification : [verificationsJTree.verification]; 41 | verificationsData.forEach(function (verification) { 42 | verifications.push(new Verification(verification)); 43 | }); 44 | } 45 | return verifications; 46 | }; 47 | 48 | module.exports = Verification; 49 | -------------------------------------------------------------------------------- /src/scripts/ads/vast/VideoClicks.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var utilities = require('../../utils/utilityFunctions'); 4 | var xml = require('../../utils/xml'); 5 | 6 | function VideoClicks (videoClickJTree) { 7 | if (!(this instanceof VideoClicks)) { 8 | return new VideoClicks(videoClickJTree); 9 | } 10 | 11 | this.clickThrough = xml.keyValue(videoClickJTree.clickThrough); 12 | this.clickTrackings = parseClickTrackings(videoClickJTree.clickTracking); 13 | this.customClicks = parseClickTrackings(videoClickJTree.customClick); 14 | 15 | // **** Local Functions **** // 16 | function parseClickTrackings (trackingData) { 17 | var clickTrackings = []; 18 | if (trackingData) { 19 | trackingData = utilities.isArray(trackingData) ? trackingData : [trackingData]; 20 | trackingData.forEach(function (clickTrackingData) { 21 | clickTrackings.push(xml.keyValue(clickTrackingData)); 22 | }); 23 | } 24 | return clickTrackings; 25 | } 26 | } 27 | 28 | module.exports = VideoClicks; 29 | -------------------------------------------------------------------------------- /src/scripts/ads/vast/ViewableImpression.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var xml = require('../../utils/xml'); 4 | var utilities = require('../../utils/utilityFunctions'); 5 | 6 | function ViewableImpression (viewableImpressionJTree) { 7 | if (!(this instanceof ViewableImpression)) { 8 | return new ViewableImpression(viewableImpressionJTree); 9 | } 10 | 11 | // Optional Fields 12 | this.id = viewableImpressionJTree.attr('id'); 13 | if (viewableImpressionJTree.viewable) { 14 | this.viewables = parseUriArray(viewableImpressionJTree.viewable); 15 | } 16 | if (viewableImpressionJTree.notViewable) { 17 | this.notViewables = parseUriArray(viewableImpressionJTree.notViewable); 18 | } 19 | if (viewableImpressionJTree.viewUndetermined) { 20 | this.viewUndetermineds = parseUriArray(viewableImpressionJTree.viewUndetermined); 21 | } 22 | 23 | function parseUriArray (arrJTree) { 24 | var arr = []; 25 | var arrData; 26 | if (arrJTree) { 27 | arrData = utilities.isArray(arrJTree) ? arrJTree : [arrJTree]; 28 | arrData.forEach(function (elem) { 29 | arr.push(xml.keyValue(elem)); 30 | }); 31 | } 32 | return arr; 33 | } 34 | } 35 | 36 | module.exports = ViewableImpression; 37 | -------------------------------------------------------------------------------- /src/scripts/ads/vast/Wrapper.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var vastUtil = require('./vastUtil'); 4 | var Creative = require('./Creative'); 5 | var ViewableImpression = require('./ViewableImpression'); 6 | var Verification = require('./Verification'); 7 | 8 | var utilities = require('../../utils/utilityFunctions'); 9 | var xml = require('../../utils/xml'); 10 | 11 | function Wrapper (wrapperJTree) { 12 | if (!(this instanceof Wrapper)) { 13 | return new Wrapper(wrapperJTree); 14 | } 15 | 16 | // Required elements 17 | this.adSystem = xml.keyValue(wrapperJTree.adSystem); 18 | this.impressions = vastUtil.parseImpressions(wrapperJTree.impression); 19 | this.VASTAdTagURI = xml.keyValue(wrapperJTree.vASTAdTagURI); 20 | 21 | // Optional elements 22 | this.creatives = Creative.parseCreatives(wrapperJTree.creatives); 23 | // this.error = xml.keyValue(wrapperJTree.error); 24 | this.errors = vastUtil.parseErrors(wrapperJTree.error); 25 | this.extensions = wrapperJTree.extensions; 26 | 27 | // Optional attrs 28 | this.followAdditionalWrappers = utilities.isDefined(xml.attr(wrapperJTree, 'followAdditionalWrappers')) ? xml.attr(wrapperJTree, 'followAdditionalWrappers') : true; 29 | this.allowMultipleAds = xml.attr(wrapperJTree, 'allowMultipleAds'); 30 | this.fallbackOnNoAd = xml.attr(wrapperJTree, 'fallbackOnNoAd'); 31 | 32 | if (window.mol_vastVersion === 4) { 33 | if (wrapperJTree.viewableImpression) { 34 | this.viewableImpression = new ViewableImpression(wrapperJTree.viewableImpression); 35 | } 36 | if (wrapperJTree.adVerifications) { 37 | this.adVerifications = Verification.parseAdVerifications(wrapperJTree.adVerifications); 38 | } 39 | } 40 | } 41 | 42 | module.exports = Wrapper; 43 | -------------------------------------------------------------------------------- /src/scripts/ads/vast/parsers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var utilities = require('../../utils/utilityFunctions'); 4 | 5 | var durationRegex = /(\d\d):(\d\d):(\d\d)(\.(\d\d\d))?/; 6 | 7 | var parsers = { 8 | 9 | duration: function parseDuration (durationStr) { 10 | 11 | var match, durationInMs; 12 | 13 | if (utilities.isString(durationStr)) { 14 | match = durationStr.match(durationRegex); 15 | if (match) { 16 | durationInMs = parseHoursToMs(match[1]) + parseMinToMs(match[2]) + parseSecToMs(match[3]) + parseInt(match[5] || 0); 17 | } 18 | } 19 | 20 | return isNaN(durationInMs) ? null : durationInMs; 21 | 22 | // **** Local Functions **** // 23 | function parseHoursToMs (hourStr) { 24 | return parseInt(hourStr, 10) * 60 * 60 * 1000; 25 | } 26 | 27 | function parseMinToMs (minStr) { 28 | return parseInt(minStr, 10) * 60 * 1000; 29 | } 30 | 31 | function parseSecToMs (secStr) { 32 | return parseInt(secStr, 10) * 1000; 33 | } 34 | }, 35 | 36 | offset: function parseOffset (offset, duration) { 37 | if (isPercentage(offset)) { 38 | return calculatePercentage(offset, duration); 39 | } 40 | return parsers.duration(offset); 41 | 42 | // **** Local Functions **** // 43 | function isPercentage (offset) { 44 | var percentageRegex = /^\d+(\.\d+)?%$/g; 45 | return percentageRegex.test(offset); 46 | } 47 | 48 | function calculatePercentage (percentStr, duration) { 49 | if (duration) { 50 | return calcPercent(duration, parseFloat(percentStr.replace('%', ''))); 51 | } 52 | return null; 53 | } 54 | 55 | function calcPercent (quantity, percent) { 56 | return quantity * percent / 100; 57 | } 58 | } 59 | 60 | }; 61 | 62 | 63 | module.exports = parsers; 64 | -------------------------------------------------------------------------------- /src/scripts/ads/vast/vastUtil.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var utilities = require('../../utils/utilityFunctions'); 4 | var VPAIDHTML5Tech = require('../vpaid/VPAIDHTML5Tech'); 5 | 6 | var vastUtil = { 7 | 8 | track: function track (URLMacros, variables) { 9 | var sources = vastUtil.parseURLMacros(URLMacros, variables); 10 | var trackImgs = []; 11 | sources.forEach(function (src) { 12 | var img = new Image(); 13 | img.src = src; 14 | trackImgs.push(img); 15 | }); 16 | return trackImgs; 17 | }, 18 | 19 | parseURLMacros: function parseMacros (URLMacros, variables) { 20 | var parsedURLs = []; 21 | 22 | variables = variables || {}; 23 | 24 | if (!(variables['CACHEBUSTING'])) { 25 | variables['CACHEBUSTING'] = Math.round(Math.random() * 1.0e+10); 26 | } 27 | 28 | URLMacros.forEach(function (URLMacro) { 29 | parsedURLs.push(vastUtil._parseURLMacro(URLMacro, variables)); 30 | }); 31 | 32 | return parsedURLs; 33 | }, 34 | 35 | parseURLMacro: function parseMacro (URLMacro, variables) { 36 | variables = variables || {}; 37 | 38 | if (!(variables['CACHEBUSTING'])) { 39 | variables['CACHEBUSTING'] = Math.round(Math.random() * 1.0e+10); 40 | } 41 | 42 | return vastUtil._parseURLMacro(URLMacro, variables); 43 | }, 44 | 45 | _parseURLMacro: function parseMacro (URLMacro, variables) { 46 | variables = variables || {}; 47 | 48 | utilities.forEach(variables, function (value, key) { 49 | URLMacro = URLMacro.replace(new RegExp('\\[' + key + '\\\]', 'gm'), value); 50 | }); 51 | 52 | return URLMacro; 53 | }, 54 | 55 | parseDuration: function parseDuration (durationStr) { 56 | var durationRegex = /(\d\d):(\d\d):(\d\d)(\.(\d\d\d))?/; 57 | var match, durationInMs; 58 | 59 | if (utilities.isString(durationStr)) { 60 | match = durationStr.match(durationRegex); 61 | if (match) { 62 | durationInMs = parseHoursToMs(match[1]) + parseMinToMs(match[2]) + parseSecToMs(match[3]) + parseInt(match[5] || 0); 63 | } 64 | } 65 | 66 | return isNaN(durationInMs) ? null : durationInMs; 67 | 68 | // **** Local Functions **** // 69 | function parseHoursToMs (hourStr) { 70 | return parseInt(hourStr, 10) * 60 * 60 * 1000; 71 | } 72 | 73 | function parseMinToMs (minStr) { 74 | return parseInt(minStr, 10) * 60 * 1000; 75 | } 76 | 77 | function parseSecToMs (secStr) { 78 | return parseInt(secStr, 10) * 1000; 79 | } 80 | }, 81 | 82 | parseImpressions: function parseImpressions (impressions) { 83 | if (impressions) { 84 | impressions = utilities.isArray(impressions) ? impressions : [impressions]; 85 | return utilities.transformArray(impressions, function (impression) { 86 | if (utilities.isNotEmptyString(impression.keyValue)) { 87 | return impression.keyValue; 88 | } 89 | return undefined; 90 | }); 91 | } 92 | return []; 93 | }, 94 | 95 | parseErrors: function parseErrors (errors) { 96 | if (errors) { 97 | errors = utilities.isArray(errors) ? errors : [errors]; 98 | return utilities.transformArray(errors, function (error) { 99 | if (utilities.isNotEmptyString(error.keyValue)) { 100 | return error.keyValue; 101 | } 102 | return undefined; 103 | }); 104 | } 105 | return []; 106 | }, 107 | 108 | // We assume that the progress is going to arrive in milliseconds 109 | formatProgress: function formatProgress (progress) { 110 | var hours, minutes, seconds, milliseconds; 111 | hours = progress / (60 * 60 * 1000); 112 | hours = Math.floor(hours); 113 | minutes = (progress / (60 * 1000)) % 60; 114 | minutes = Math.floor(minutes); 115 | seconds = (progress / 1000) % 60; 116 | seconds = Math.floor(seconds); 117 | milliseconds = progress % 1000; 118 | return utilities.toFixedDigits(hours, 2) + ':' + utilities.toFixedDigits(minutes, 2) + ':' + utilities.toFixedDigits(seconds, 2) + '.' + utilities.toFixedDigits(milliseconds, 3); 119 | }, 120 | 121 | parseOffset: function parseOffset (offset, duration) { 122 | if (isPercentage(offset)) { 123 | return calculatePercentage(offset, duration); 124 | } 125 | return vastUtil.parseDuration(offset); 126 | 127 | // **** Local Function **** // 128 | function isPercentage (offset) { 129 | var percentageRegex = /^\d+(\.\d+)?%$/g; 130 | return percentageRegex.test(offset); 131 | } 132 | 133 | function calculatePercentage (percentStr, duration) { 134 | if (duration) { 135 | return calcPercent(duration, parseFloat(percentStr.replace('%', ''))); 136 | } 137 | return null; 138 | } 139 | 140 | function calcPercent (quantity, percent) { 141 | return quantity * percent / 100; 142 | } 143 | }, 144 | 145 | 146 | // List of supported VPAID technologies 147 | VPAID_techs: [ 148 | VPAIDHTML5Tech 149 | ], 150 | 151 | isVPAID: function isVPAIDMediaFile (mediaFile) { 152 | return !!mediaFile && mediaFile.apiFramework === 'VPAID'; 153 | }, 154 | 155 | findSupportedVPAIDTech: function findSupportedVPAIDTech (mimeType) { 156 | var i, len, VPAIDTech; 157 | 158 | for (i = 0, len = this.VPAID_techs.length; i < len; i += 1) { 159 | VPAIDTech = this.VPAID_techs[i]; 160 | if (VPAIDTech.supports(mimeType)) { 161 | return VPAIDTech; 162 | } 163 | } 164 | return null; 165 | }, 166 | 167 | isFlashSupported: function isFlashSupported () { 168 | return false; 169 | }, 170 | 171 | }; 172 | 173 | module.exports = vastUtil; 174 | -------------------------------------------------------------------------------- /src/scripts/ads/vpaid/VPAIDAdUnitWrapper.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var VASTError = require('../vast/VASTError'); 4 | 5 | var utilities = require('../../utils/utilityFunctions'); 6 | var logger = require('../../utils/consoleLogger'); 7 | 8 | function VPAIDAdUnitWrapper (vpaidAdUnit, opts) { 9 | if (!(this instanceof VPAIDAdUnitWrapper)) { 10 | return new VPAIDAdUnitWrapper(vpaidAdUnit, opts); 11 | } 12 | sanityCheck(vpaidAdUnit, opts); 13 | 14 | this.options = utilities.extend({}, opts); 15 | this.adStarted = false; 16 | 17 | this._adUnit = vpaidAdUnit; 18 | 19 | // **** Local Functions **** // 20 | function sanityCheck (adUnit, opts) { 21 | if (!adUnit || !VPAIDAdUnitWrapper.checkVPAIDInterface(adUnit)) { 22 | throw new VASTError('on VPAIDAdUnitWrapper, the passed VPAID adUnit does not fully implement the VPAID interface'); 23 | } 24 | 25 | if (!utilities.isObject(opts)) { 26 | throw new VASTError('on VPAIDAdUnitWrapper, expected options hash but got "' + opts + '"'); 27 | } 28 | 29 | if (!('responseTimeout' in opts) || !utilities.isNumber(opts.responseTimeout)) { 30 | throw new VASTError('on VPAIDAdUnitWrapper, expected responseTimeout in options'); 31 | } 32 | } 33 | } 34 | 35 | VPAIDAdUnitWrapper.checkVPAIDInterface = function checkVPAIDInterface (VPAIDAdUnit) { 36 | // NOTE: skipAd is not part of the method list because it only appears in VPAID 2.0 and we support VPAID 1.0 37 | var VPAIDInterfaceMethods = [ 38 | 'handshakeVersion', 'initAd', 'startAd', 'stopAd', 'resizeAd', 'pauseAd', 'expandAd', 'collapseAd' 39 | ]; 40 | 41 | for (var i = 0, len = VPAIDInterfaceMethods.length; i < len; i++) { 42 | if (!VPAIDAdUnit || !utilities.isFunction(VPAIDAdUnit[VPAIDInterfaceMethods[i]])) { 43 | return false; 44 | } 45 | } 46 | 47 | 48 | return canSubscribeToEvents(VPAIDAdUnit) && canUnsubscribeFromEvents(VPAIDAdUnit); 49 | 50 | // **** Local Functions **** // 51 | 52 | function canSubscribeToEvents (adUnit) { 53 | return utilities.isFunction(adUnit.subscribe) || utilities.isFunction(adUnit.addEventListener) || utilities.isFunction(adUnit.on); 54 | } 55 | 56 | function canUnsubscribeFromEvents (adUnit) { 57 | return utilities.isFunction(adUnit.unsubscribe) || utilities.isFunction(adUnit.removeEventListener) || utilities.isFunction(adUnit.off); 58 | 59 | } 60 | }; 61 | 62 | VPAIDAdUnitWrapper.prototype.adUnitAsyncCall = function () { 63 | var args = utilities.arrayLikeObjToArray(arguments); 64 | var method = args.shift(); 65 | var cb = args.pop(); 66 | var timeoutId; 67 | 68 | sanityCheck(method, cb, this._adUnit); 69 | args.push(wrapCallback()); 70 | 71 | this._adUnit[method].apply(this._adUnit, args); 72 | timeoutId = setTimeout(function () { 73 | timeoutId = null; 74 | cb(new VASTError('on VPAIDAdUnitWrapper, timeout while waiting for a response on call "' + method + '"')); 75 | cb = utilities.noop; 76 | }, this.options.responseTimeout); 77 | 78 | // **** Local Functions **** // 79 | function sanityCheck (method, cb, adUnit) { 80 | if (!utilities.isString(method) || !utilities.isFunction(adUnit[method])) { 81 | throw new VASTError('on VPAIDAdUnitWrapper.adUnitAsyncCall, invalid method name'); 82 | } 83 | 84 | if (!utilities.isFunction(cb)) { 85 | throw new VASTError('on VPAIDAdUnitWrapper.adUnitAsyncCall, missing callback'); 86 | } 87 | } 88 | 89 | function wrapCallback () { 90 | return function () { 91 | if (timeoutId) { 92 | clearTimeout(timeoutId); 93 | } 94 | cb.apply(this, arguments); 95 | }; 96 | } 97 | }; 98 | 99 | VPAIDAdUnitWrapper.prototype.on = function (evtName, handler) { 100 | var addEventListener = this._adUnit.addEventListener || this._adUnit.subscribe || this._adUnit.on; 101 | addEventListener.call(this._adUnit, evtName, handler); 102 | }; 103 | 104 | VPAIDAdUnitWrapper.prototype.off = function (evtName, handler) { 105 | var removeEventListener = this._adUnit.removeEventListener || this._adUnit.unsubscribe || this._adUnit.off; 106 | removeEventListener.call(this._adUnit, evtName, handler); 107 | }; 108 | 109 | VPAIDAdUnitWrapper.prototype.waitForEvent = function (evtName, cb, context) { 110 | var that = this; 111 | var timeoutId; 112 | sanityCheck(evtName, cb); 113 | context = context || null; 114 | 115 | this.on(evtName, responseListener); 116 | 117 | timeoutId = setTimeout(function () { 118 | that.off(evtName, responseListener); 119 | // ignore events if ad is finished 120 | if (window._timeoutIds.indexOf(timeoutId) >= 0) { 121 | if (that.options && that.options.player) { 122 | that.options.player.trigger({type: 'trace.message', data: {message: 'Timeout while waiting for event ' + evtName}}); 123 | } 124 | cb(new VASTError('on VPAIDAdUnitWrapper.waitForEvent, timeout while waiting for event "' + evtName + '". You may need to increase adStartTimeout.')); 125 | } 126 | timeoutId = null; 127 | cb = utilities.noop; 128 | }, this.options.responseTimeout); 129 | 130 | // EN 131 | window._timeoutIds.push(timeoutId); 132 | 133 | // **** Local Functions **** // 134 | function sanityCheck (evtName, cb) { 135 | if (!utilities.isString(evtName)) { 136 | throw new VASTError('on VPAIDAdUnitWrapper.waitForEvent, missing evt name'); 137 | } 138 | 139 | if (!utilities.isFunction(cb)) { 140 | throw new VASTError('on VPAIDAdUnitWrapper.waitForEvent, missing callback'); 141 | } 142 | } 143 | 144 | function responseListener () { 145 | if (that.options && that.options.player) { 146 | that.options.player.trigger({type: 'trace.event', data: {event: evtName}}); 147 | } 148 | that.off(evtName, responseListener); 149 | var args = utilities.arrayLikeObjToArray(arguments); 150 | 151 | if (timeoutId) { 152 | clearTimeout(timeoutId); 153 | timeoutId = null; 154 | } 155 | 156 | args.unshift(null); 157 | cb.apply(context, args); 158 | } 159 | }; 160 | 161 | // VPAID METHODS 162 | VPAIDAdUnitWrapper.prototype.handshakeVersion = function (version, cb) { 163 | this.adUnitAsyncCall('handshakeVersion', version, cb); 164 | }; 165 | 166 | /* jshint maxparams:6 */ 167 | VPAIDAdUnitWrapper.prototype.initAd = function (width, height, viewMode, desiredBitrate, adUnitData, cb) { 168 | logger.log('VPAIDAdUnitWrapper->Calling initAd on creative'); 169 | this.waitForEvent('AdLoaded', cb); 170 | this._adUnit.initAd(width, height, viewMode, desiredBitrate, adUnitData); 171 | }; 172 | 173 | VPAIDAdUnitWrapper.prototype.resizeAd = function (width, height, viewMode, cb) { 174 | // NOTE: AdSizeChange event is only supported on VPAID 2.0 so for the moment we are not going to use it 175 | // and will assume that everything is fine after the async call 176 | logger.log('VPAIDAdUnitWrapper->Calling resizeAd on creative'); 177 | this.adUnitAsyncCall('resizeAd', width, height, viewMode, cb); 178 | if (window.MoatApiReference) { 179 | window.MoatApiReference.dispatchEvent({type: 'AdSizeChange', adVolume: this.options.player.volume()}); 180 | } 181 | }; 182 | 183 | VPAIDAdUnitWrapper.prototype.startAd = function (cb) { 184 | logger.log('VPAIDAdUnitWrapper->Calling startAd on creative'); 185 | if (!this.adStarted) { 186 | this.waitForEvent('AdStarted', cb); 187 | } 188 | this._adUnit.startAd(); 189 | if (this.adStarted) { 190 | cb(null); 191 | } 192 | }; 193 | 194 | VPAIDAdUnitWrapper.prototype.stopAd = function (cb) { 195 | logger.log('VPAIDAdUnitWrapper->Calling stopAd on creative'); 196 | this.waitForEvent('AdStopped', cb); 197 | this._adUnit.stopAd(); 198 | }; 199 | 200 | VPAIDAdUnitWrapper.prototype.pauseAd = function (cb) { 201 | logger.log('VPAIDAdUnitWrapper->Calling pauseAd on creative'); 202 | this.waitForEvent('AdPaused', cb); 203 | this._adUnit.pauseAd(); 204 | }; 205 | 206 | VPAIDAdUnitWrapper.prototype.resumeAd = function (cb) { 207 | logger.log('VPAIDAdUnitWrapper->Calling resumeAd on creative'); 208 | this.waitForEvent('AdPlaying', cb); 209 | this._adUnit.resumeAd(); 210 | }; 211 | 212 | VPAIDAdUnitWrapper.prototype.expandAd = function (cb) { 213 | logger.log('VPAIDAdUnitWrapper->Calling expandAd on creative'); 214 | this.waitForEvent('AdExpandedChange', cb); 215 | this._adUnit.expandAd(); 216 | }; 217 | 218 | VPAIDAdUnitWrapper.prototype.collapseAd = function (cb) { 219 | logger.log('VPAIDAdUnitWrapper->Calling collapseAd on creative'); 220 | this.waitForEvent('AdExpandedChange', cb); 221 | this._adUnit.collapseAd(); 222 | }; 223 | 224 | VPAIDAdUnitWrapper.prototype.skipAd = function (cb) { 225 | logger.log('VPAIDAdUnitWrapper->Calling skipAd on creative'); 226 | this.waitForEvent('AdSkipped', cb); 227 | this._adUnit.skipAd(); 228 | }; 229 | 230 | // VPAID property getters 231 | [ 232 | 'adLinear', 233 | 'adWidth', 234 | 'adHeight', 235 | 'adExpanded', 236 | 'adSkippableState', 237 | 'adRemainingTime', 238 | 'adDuration', 239 | 'adVolume', 240 | 'adCompanions', 241 | 'adIcons' 242 | ].forEach(function (property) { 243 | var getterName = 'get' + utilities.capitalize(property); 244 | 245 | VPAIDAdUnitWrapper.prototype[getterName] = function (cb) { 246 | this.adUnitAsyncCall(getterName, cb); 247 | }; 248 | }); 249 | 250 | // VPAID property setters 251 | VPAIDAdUnitWrapper.prototype.setAdVolume = function (volume, cb) { 252 | this.adUnitAsyncCall('setAdVolume', volume, cb); 253 | }; 254 | 255 | module.exports = VPAIDAdUnitWrapper; 256 | -------------------------------------------------------------------------------- /src/scripts/ads/vpaid/VPAIDHTML5Tech.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var MimeTypes = require('../../utils/mimetypes'); 4 | 5 | var VASTError = require('../vast/VASTError'); 6 | 7 | var VPAIDHTML5Client = require('vpaid-html5-client'); 8 | 9 | var utilities = require('../../utils/utilityFunctions'); 10 | var dom = require('../../utils/dom'); 11 | 12 | var logger = require('../../utils/consoleLogger'); 13 | 14 | function VPAIDHTML5Tech (mediaFile) { 15 | 16 | if (!(this instanceof VPAIDHTML5Tech)) { 17 | return new VPAIDHTML5Tech(mediaFile); 18 | } 19 | 20 | sanityCheck(mediaFile); 21 | 22 | this.name = 'vpaid-html5'; 23 | this.containerEl = null; 24 | this.videoEl = null; 25 | this.vpaidHTMLClient = null; 26 | 27 | this.mediaFile = mediaFile; 28 | 29 | function sanityCheck (mediaFile) { 30 | if (!mediaFile || !utilities.isString(mediaFile.src)) { 31 | throw new VASTError(VPAIDHTML5Tech.INVALID_MEDIA_FILE); 32 | } 33 | } 34 | } 35 | 36 | VPAIDHTML5Tech.VPAIDHTML5Client = VPAIDHTML5Client; 37 | 38 | VPAIDHTML5Tech.supports = function (type) { 39 | return !utilities.isOldIE() && MimeTypes.html5.indexOf(type) > -1; 40 | }; 41 | 42 | VPAIDHTML5Tech.prototype.loadAdUnit = function loadAdUnit (containerEl, videoEl, callback) { 43 | sanityCheck(containerEl, videoEl, callback); 44 | 45 | this.containerEl = containerEl; 46 | this.videoEl = videoEl; 47 | this.vpaidHTMLClient = new VPAIDHTML5Tech.VPAIDHTML5Client(containerEl, videoEl, {}); 48 | // make unique client id for when multiple VPAIDs are running in the same html document at the same time. 49 | this.vpaidHTMLClient._id = 'vpaidIframe_' + Date.now(); 50 | this.vpaidHTMLClient.loadAdUnit(this.mediaFile.src, callback); 51 | 52 | function sanityCheck (container, video, cb) { 53 | if (!dom.isDomElement(container)) { 54 | throw new VASTError(VPAIDHTML5Tech.INVALID_DOM_CONTAINER_EL); 55 | } 56 | 57 | if (!dom.isDomElement(video) || video.tagName.toLowerCase() !== 'video') { 58 | throw new VASTError(VPAIDHTML5Tech.INVALID_DOM_CONTAINER_EL); 59 | } 60 | 61 | if (!utilities.isFunction(cb)) { 62 | throw new VASTError(VPAIDHTML5Tech.MISSING_CALLBACK); 63 | } 64 | } 65 | }; 66 | 67 | VPAIDHTML5Tech.prototype.unloadAdUnit = function unloadAdUnit () { 68 | if (this.vpaidHTMLClient) { 69 | try { 70 | this.vpaidHTMLClient.destroy(); 71 | } catch (e) { 72 | logger.error('VAST ERROR: trying to unload the VPAID adunit'); 73 | } 74 | 75 | this.vpaidHTMLClient = null; 76 | } 77 | 78 | if (this.containerEl) { 79 | dom.remove(this.containerEl); 80 | this.containerEl = null; 81 | } 82 | }; 83 | 84 | var PREFIX = 'on VPAIDHTML5Tech'; 85 | VPAIDHTML5Tech.INVALID_MEDIA_FILE = PREFIX + ', invalid MediaFile'; 86 | VPAIDHTML5Tech.INVALID_DOM_CONTAINER_EL = PREFIX + ', invalid container HtmlElement'; 87 | VPAIDHTML5Tech.INVALID_DOM_VIDEO_EL = PREFIX + ', invalid HTMLVideoElement'; 88 | VPAIDHTML5Tech.MISSING_CALLBACK = PREFIX + ', missing valid callback'; 89 | 90 | module.exports = VPAIDHTML5Tech; 91 | -------------------------------------------------------------------------------- /src/scripts/plugin/components/black-poster.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * The component that shows a black screen until the ads plugin has decided if it can or it can not play the ad. 5 | * 6 | * Note: In case you wonder why instead of this black poster we don't just show the spinner loader. 7 | * IOS devices do not work well with animations and the browser chrashes from time to time That is why we chose to 8 | * have a secondary black poster. 9 | * 10 | * It also makes it much more easier for the users of the plugin since it does not change the default behaviour of the 11 | * spinner and the player works the same way with and without the plugin. 12 | * 13 | * @param {vjs.Player|Object} player 14 | * @param {Object=} options 15 | * @constructor 16 | */ 17 | var element = document.createElement('div'); 18 | 19 | var BlackPosterFactory = function (baseComponent) { 20 | return { 21 | /** @constructor */ 22 | init: function init (player, options) { 23 | options.el = element; 24 | element.className = 'vjs-black-poster'; 25 | baseComponent.call(this, player, options); 26 | 27 | var posterImg = player.getChild('posterImage'); 28 | 29 | // We need to do it asynchronously to be sure that the black poster el is on the dom. 30 | setTimeout(function () { 31 | if (posterImg && player && player.el()) { 32 | player.el().insertBefore(element, posterImg.el()); 33 | } 34 | }, 0); 35 | }, 36 | el: function getElement () { 37 | return element; 38 | } 39 | }; 40 | }; 41 | 42 | module.exports = BlackPosterFactory; 43 | -------------------------------------------------------------------------------- /src/scripts/plugin/components/black-poster_4.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var baseVideoJsComponent = videojs.Component; 4 | 5 | var BlackPoster = require('./black-poster')(baseVideoJsComponent); 6 | 7 | videojs.BlackPoster = videojs.Component.extend(BlackPoster); 8 | -------------------------------------------------------------------------------- /src/scripts/plugin/components/black-poster_5.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var baseVideoJsComponent = videojs.getComponent('Component'); 4 | 5 | var BlackPoster = require('./black-poster')(baseVideoJsComponent); 6 | var utilities = require('../../utils/utilityFunctions'); 7 | 8 | // VIDLA-4391: Hack to prevent uncaught exception when loading Mail Online plugin into child iframe of video.js window 9 | // VIDLA-4563: Hack for Edge when Brightcove player embed in not friendly iframe 10 | if (parent && window !== parent && utilities.scriptLoadedInIframe()) { 11 | BlackPoster.constructor = parent.Object.prototype.constructor; 12 | } 13 | 14 | videojs.registerComponent('BlackPoster', videojs.extend(baseVideoJsComponent, BlackPoster)); 15 | -------------------------------------------------------------------------------- /src/scripts/utils/anVideoViewability.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Viewability Wrapper 3 | */ 4 | 5 | // var ANVideoViewabilityTracker = require('an-video-viewability');//viewability library 6 | var ANVideoViewabilityTracker = null; 7 | var logger = require('./consoleLogger'); 8 | var debug = function (message) { 9 | logger.debug(message); 10 | }; 11 | var info = function (message) { 12 | logger.info(message); 13 | }; 14 | 15 | var html5VideoViewability = function (player_) { 16 | 17 | var player = player_; 18 | var tracker = null; 19 | var mappedActions = { 20 | 'start': 'start', 21 | 'expand': 'expand', 22 | 'collapse': 'collapse', 23 | 'unmute': 'sound_on', 24 | 'mute': 'sound_off', 25 | 'pause': 'pause', 26 | 'resume': 'resume', 27 | 'ad-click': 'click', 28 | 'skip': 'stop', 29 | 'complete': 'stop', 30 | 'fullscreen': 'fullscreen', 31 | 'exitFullscreen': 'exitFullscreen' 32 | }; 33 | 34 | var fireOnceEvents = { 35 | 'start': false, 36 | 'skip': false, 37 | 'complete': false 38 | }; 39 | 40 | var getConfig = function () { 41 | return window._molSettings.viewability && window._molSettings.viewability.config; 42 | }; 43 | 44 | var getPlayerDOMElement = function () { 45 | return player.el_; 46 | }; 47 | 48 | var getVideoInfo = function (duration, width, height) { 49 | return {duration: duration, w: width, h: height}; 50 | }; 51 | 52 | // Using callback for logs helps getting a right logger in each app 53 | var loggerCallback = function (_level, _message) { 54 | if (_level && _level === 'debug') { 55 | debug(_message); 56 | } else { 57 | info(_message); 58 | } 59 | }; 60 | 61 | this.init = function (contextId, duration, width, height) { 62 | if (!ANVideoViewabilityTracker) { 63 | return; 64 | } 65 | info('initialize with duration: ' + duration + ', width: ' + width + ', height: ' + height); 66 | try { 67 | tracker = new ANVideoViewabilityTracker( 68 | getConfig(), 69 | getPlayerDOMElement(), 70 | getVideoInfo(duration, width, height), // The duration of the video ad 71 | contextId, 72 | loggerCallback 73 | ); 74 | this.isReady = true; 75 | } catch (ex) { 76 | debug('error on viewability library: '); 77 | debug(ex); 78 | } 79 | 80 | }; 81 | 82 | this.invokeEvent = function (event_) { 83 | if (!ANVideoViewabilityTracker) { 84 | return; 85 | } 86 | if (this.isReady && event_ && mappedActions[event_]) { 87 | try { 88 | if (fireOnceEvents.hasOwnProperty(event_)) { 89 | if (fireOnceEvents[event_]) { 90 | info('supressing fireOnceEvents event as it is already fired once by viewability library: ' + mappedActions[event_]); 91 | return; 92 | } else { 93 | fireOnceEvents[event_] = true; // mark that the event has been fired. 94 | } 95 | } 96 | info('event invoked by viewability library: ' + mappedActions[event_]); 97 | tracker.notifyEvent(mappedActions[event_]); 98 | } catch (ex) { 99 | debug('error on viewability library: '); 100 | debug(ex); 101 | } 102 | } 103 | }; 104 | 105 | this.isReady = false; 106 | 107 | 108 | }; 109 | 110 | 111 | module.exports = html5VideoViewability; 112 | -------------------------------------------------------------------------------- /src/scripts/utils/async.js: -------------------------------------------------------------------------------- 1 | // Small subset of async 2 | 3 | var utilities = require('./utilityFunctions'); 4 | 5 | var async = {}; 6 | 7 | async.setImmediate = function (fn) { 8 | setTimeout(fn, 0); 9 | }; 10 | 11 | async.iterator = function (tasks) { 12 | var makeCallback = function (index) { 13 | var fn = function () { 14 | if (tasks.length) { 15 | tasks[index].apply(null, arguments); 16 | } 17 | return fn.next(); 18 | }; 19 | fn.next = function () { 20 | return (index < tasks.length - 1) ? makeCallback(index + 1) : null; 21 | }; 22 | return fn; 23 | }; 24 | return makeCallback(0); 25 | }; 26 | 27 | 28 | async.waterfall = function (tasks, callback) { 29 | callback = callback || function () { }; 30 | if (!utilities.isArray(tasks)) { 31 | var err = new Error('First argument to waterfall must be an array of functions'); 32 | return callback(err); 33 | } 34 | if (!tasks.length) { 35 | return callback(); 36 | } 37 | var wrapIterator = function (iterator) { 38 | return function (err) { 39 | if (err) { 40 | callback.apply(null, arguments); 41 | callback = function () { 42 | }; 43 | } 44 | else { 45 | var args = Array.prototype.slice.call(arguments, 1); 46 | var next = iterator.next(); 47 | if (next) { 48 | args.push(wrapIterator(next)); 49 | } 50 | else { 51 | args.push(callback); 52 | } 53 | async.setImmediate(function () { 54 | iterator.apply(null, args); 55 | }); 56 | } 57 | }; 58 | }; 59 | wrapIterator(async.iterator(tasks))(); 60 | }; 61 | 62 | async.when = function (condition, callback) { 63 | if (!utilities.isFunction(callback)) { 64 | throw new Error('async.when error: missing callback argument'); 65 | } 66 | 67 | var isAllowed = utilities.isFunction(condition) ? condition : function () { 68 | return !!condition; 69 | }; 70 | 71 | return function () { 72 | var args = utilities.arrayLikeObjToArray(arguments); 73 | var next = args.pop(); 74 | 75 | if (isAllowed.apply(null, args)) { 76 | return callback.apply(this, arguments); 77 | } 78 | 79 | args.unshift(null); 80 | return next.apply(null, args); 81 | }; 82 | }; 83 | 84 | module.exports = async; 85 | 86 | -------------------------------------------------------------------------------- /src/scripts/utils/consoleLogger.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _verbosity = 0; 4 | var _prefix = '[MOL-Plugin'; 5 | var _playerId = ''; 6 | var _astDebug; 7 | 8 | function setPlayerId (id) 9 | { 10 | _playerId = id; 11 | } 12 | 13 | // determine the maximum debug level from the page URL 14 | function setDebugLevelFromPage () { 15 | try { 16 | // keep track of the new level 17 | _astDebug = (getParameterByName('ast_debug').toLowerCase() == 'true') ? 4 : 0; 18 | 19 | // the highest (least restrictive debug level) always wins 20 | _verbosity = Math.max(_verbosity, _astDebug); 21 | } catch (e) {} 22 | } 23 | 24 | // get a named parameter from the querystring 25 | function getParameterByName (name) { 26 | // accesing window might fail at the browser level, we can't really test for it, 27 | // so there are a few nested try/catch blocks here 28 | try { 29 | var urlToSearch = ''; 30 | // try checking the topmost window, and if not, use current window 31 | try { 32 | urlToSearch = window.top.location.search; 33 | } catch (e) { 34 | try { 35 | urlToSearch = window.location.search; 36 | } catch (e) {} 37 | } 38 | 39 | var regexS = '[\\?&]' + name + '=([^&#]*)'; 40 | var regex = new RegExp(regexS); 41 | var results = regex.exec(urlToSearch); 42 | if (results === null) { 43 | return ''; 44 | } 45 | return decodeURIComponent(results[1].replace(/\+/g, ' ')); 46 | } catch (e) { 47 | return ''; 48 | } 49 | } 50 | 51 | function setVerbosity (v) 52 | { 53 | // the highest (least restrictive debug level) always wins 54 | _verbosity = Math.max(v, _verbosity); 55 | } 56 | 57 | function getCurrentTimeString () { 58 | var dateToReturn = ''; 59 | try { 60 | var curDate = new Date(); 61 | dateToReturn = curDate.getHours() + ':' + curDate.getMinutes() + ':' + curDate.getSeconds() + '.' + curDate.getMilliseconds(); 62 | } catch (e) {} 63 | return dateToReturn; 64 | } 65 | 66 | function handleMsg (method, args) 67 | { 68 | var messagePrefix = _prefix; 69 | if ((args.length) > 0 && (typeof args[0] === 'string')) 70 | { 71 | if (_playerId && _playerId.length > 0) { 72 | messagePrefix += ('-' + _playerId); 73 | } 74 | messagePrefix += ']'; 75 | messagePrefix += '[' + getCurrentTimeString() + ']'; 76 | 77 | args[0] = messagePrefix + args[0]; 78 | } 79 | 80 | if (method.apply) 81 | { 82 | method.apply(console, Array.prototype.slice.call(args)); 83 | } 84 | else 85 | { 86 | method(Array.prototype.slice.call(args)); 87 | } 88 | } 89 | 90 | function debug () 91 | { 92 | if (_verbosity < 4) 93 | { 94 | return; 95 | } 96 | 97 | if (typeof console.debug === 'undefined') 98 | { 99 | // IE 10 doesn't have a console.debug() function 100 | handleMsg(console.log, arguments); 101 | } 102 | else 103 | { 104 | handleMsg(console.debug, arguments); 105 | } 106 | } 107 | 108 | function log () 109 | { 110 | if (_verbosity < 3) 111 | { 112 | return; 113 | } 114 | 115 | handleMsg(console.log, arguments); 116 | } 117 | 118 | function info () 119 | { 120 | if (_verbosity < 2) 121 | { 122 | return; 123 | } 124 | 125 | handleMsg(console.info, arguments); 126 | } 127 | 128 | 129 | function warn () 130 | { 131 | if (_verbosity < 1) 132 | { 133 | return; 134 | } 135 | 136 | handleMsg(console.warn, arguments); 137 | } 138 | 139 | function error () 140 | { 141 | handleMsg(console.error, arguments); 142 | } 143 | 144 | var consoleLogger = { 145 | setPlayerId: setPlayerId, 146 | setVerbosity: setVerbosity, 147 | debug: debug, 148 | log: log, 149 | info: info, 150 | warn: warn, 151 | error: error 152 | }; 153 | 154 | if ((typeof (console) === 'undefined') || !console.log) 155 | { 156 | // no console available; make functions no-op 157 | consoleLogger.debug = function () {}; 158 | consoleLogger.log = function () {}; 159 | consoleLogger.info = function () {}; 160 | consoleLogger.warn = function () {}; 161 | consoleLogger.error = function () {}; 162 | } 163 | 164 | module.exports = consoleLogger; 165 | 166 | // look in the query string for debug level 167 | setDebugLevelFromPage(); 168 | -------------------------------------------------------------------------------- /src/scripts/utils/dom.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var utilities = require('./utilityFunctions'); 4 | 5 | var dom = {}; 6 | 7 | dom.isVisible = function isVisible (el) { 8 | var style = window.getComputedStyle(el); 9 | return style.visibility !== 'hidden'; 10 | }; 11 | 12 | dom.isHidden = function isHidden (el) { 13 | var style = window.getComputedStyle(el); 14 | return style.display === 'none'; 15 | }; 16 | 17 | dom.isShown = function isShown (el) { 18 | return !dom.isHidden(el); 19 | }; 20 | 21 | dom.hide = function hide (el) { 22 | el.__prev_style_display_ = el.style.display; 23 | el.style.display = 'none'; 24 | }; 25 | 26 | dom.show = function show (el) { 27 | if (dom.isHidden(el)) { 28 | el.style.display = el.__prev_style_display_; 29 | } 30 | el.__prev_style_display_ = undefined; 31 | }; 32 | 33 | dom.hasClass = function hasClass (el, cssClass) { 34 | var classes, i, len; 35 | 36 | if (utilities.isNotEmptyString(cssClass)) { 37 | if (el.classList) { 38 | return el.classList.contains(cssClass); 39 | } 40 | 41 | classes = utilities.isString(el.getAttribute('class')) ? el.getAttribute('class').split(/\s+/) : []; 42 | cssClass = (cssClass || ''); 43 | 44 | for (i = 0, len = classes.length; i < len; i += 1) { 45 | if (classes[i] === cssClass) { 46 | return true; 47 | } 48 | } 49 | } 50 | return false; 51 | }; 52 | 53 | dom.addClass = function (el, cssClass) { 54 | var classes; 55 | 56 | if (utilities.isNotEmptyString(cssClass)) { 57 | if (el.classList) { 58 | return el.classList.add(cssClass); 59 | } 60 | 61 | classes = utilities.isString(el.getAttribute('class')) ? el.getAttribute('class').split(/\s+/) : []; 62 | if (utilities.isString(cssClass) && utilities.isNotEmptyString(cssClass.replace(/\s+/, ''))) { 63 | classes.push(cssClass); 64 | el.setAttribute('class', classes.join(' ')); 65 | } 66 | } 67 | }; 68 | 69 | dom.removeClass = function (el, cssClass) { 70 | var classes; 71 | 72 | if (utilities.isNotEmptyString(cssClass)) { 73 | if (el.classList) { 74 | return el.classList.remove(cssClass); 75 | } 76 | 77 | classes = utilities.isString(el.getAttribute('class')) ? el.getAttribute('class').split(/\s+/) : []; 78 | var newClasses = []; 79 | var i, len; 80 | if (utilities.isString(cssClass) && utilities.isNotEmptyString(cssClass.replace(/\s+/, ''))) { 81 | 82 | for (i = 0, len = classes.length; i < len; i += 1) { 83 | if (cssClass !== classes[i]) { 84 | newClasses.push(classes[i]); 85 | } 86 | } 87 | el.setAttribute('class', newClasses.join(' ')); 88 | } 89 | } 90 | }; 91 | 92 | dom.addEventListener = function addEventListener (el, type, handler) { 93 | if (utilities.isArray(el)) { 94 | utilities.forEach(el, function (e) { 95 | dom.addEventListener(e, type, handler); 96 | }); 97 | return; 98 | } 99 | 100 | if (utilities.isArray(type)) { 101 | utilities.forEach(type, function (t) { 102 | dom.addEventListener(el, t, handler); 103 | }); 104 | return; 105 | } 106 | 107 | if (el.addEventListener) { 108 | el.addEventListener(type, handler, false); 109 | } else if (el.attachEvent) { 110 | // WARNING!!! this is a very naive implementation ! 111 | // the event object that should be passed to the handler 112 | // would not be there for IE8 113 | // we should use "window.event" and then "event.srcElement" 114 | // instead of "event.target" 115 | el.attachEvent('on' + type, handler); 116 | } 117 | }; 118 | 119 | dom.removeEventListener = function removeEventListener (el, type, handler) { 120 | if (utilities.isArray(el)) { 121 | utilities.forEach(el, function (e) { 122 | dom.removeEventListener(e, type, handler); 123 | }); 124 | return; 125 | } 126 | 127 | if (utilities.isArray(type)) { 128 | utilities.forEach(type, function (t) { 129 | dom.removeEventListener(el, t, handler); 130 | }); 131 | return; 132 | } 133 | 134 | if (el.removeEventListener) { 135 | el.removeEventListener(type, handler, false); 136 | } else if (el.detachEvent) { 137 | el.detachEvent('on' + type, handler); 138 | } else { 139 | el['on' + type] = null; 140 | } 141 | }; 142 | 143 | dom.dispatchEvent = function dispatchEvent (el, event) { 144 | if (el.dispatchEvent) { 145 | el.dispatchEvent(event); 146 | } else { 147 | el.fireEvent('on' + event.eventType, event); 148 | } 149 | }; 150 | 151 | dom.isDescendant = function isDescendant (parent, child) { 152 | var node = child.parentNode; 153 | while (node !== null) { 154 | if (node === parent) { 155 | return true; 156 | } 157 | node = node.parentNode; 158 | } 159 | return false; 160 | }; 161 | 162 | dom.getTextContent = function getTextContent (el) { 163 | return el.textContent || el.text; 164 | }; 165 | 166 | dom.prependChild = function prependChild (parent, child) { 167 | if (child.parentNode) { 168 | child.parentNode.removeChild(child); 169 | } 170 | return parent.insertBefore(child, parent.firstChild); 171 | }; 172 | 173 | dom.remove = function removeNode (node) { 174 | if (node && node.parentNode) { 175 | node.parentNode.removeChild(node); 176 | } 177 | }; 178 | 179 | dom.isDomElement = function isDomElement (o) { 180 | return o instanceof Element || (parent && parent.Element && o instanceof parent.Element); // If MOL is loaded in an iFrame, a DOM Element may be passed in from the parent document - check for that, too 181 | }; 182 | 183 | dom.click = function (el, handler) { 184 | dom.addEventListener(el, 'click', handler); 185 | }; 186 | 187 | dom.once = function (el, type, handler) { 188 | function handlerWrap () { 189 | handler.apply(null, arguments); 190 | dom.removeEventListener(el, type, handlerWrap); 191 | } 192 | 193 | dom.addEventListener(el, type, handlerWrap); 194 | }; 195 | 196 | // Note: there is no getBoundingClientRect on iPad so we need a fallback 197 | dom.getDimension = function getDimension (element) { 198 | var rect; 199 | 200 | // On IE9 and below getBoundingClientRect does not work consistently 201 | if (!utilities.isOldIE() && element.getBoundingClientRect) { 202 | rect = element.getBoundingClientRect(); 203 | return { 204 | width: rect.width, 205 | height: rect.height 206 | }; 207 | } 208 | 209 | return { 210 | width: element.offsetWidth, 211 | height: element.offsetHeight 212 | }; 213 | }; 214 | 215 | module.exports = dom; 216 | -------------------------------------------------------------------------------- /src/scripts/utils/http.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var urlUtils = require('./urlUtils'); 4 | var utilities = require('./utilityFunctions'); 5 | 6 | function HttpRequestError (message) { 7 | this.message = 'HttpRequest Error: ' + (message || ''); 8 | } 9 | HttpRequestError.prototype = new Error(); 10 | HttpRequestError.prototype.name = 'HttpRequest Error'; 11 | 12 | function HttpRequest (createXhr) { 13 | if (!utilities.isFunction(createXhr)) { 14 | throw new HttpRequestError('Missing XMLHttpRequest factory method'); 15 | } 16 | 17 | this.createXhr = createXhr; 18 | } 19 | 20 | HttpRequest.prototype.run = function (method, url, callback, options) { 21 | sanityCheck(url, callback, options); 22 | var timeout, timeoutId; 23 | var xhr = this.createXhr(); 24 | options = options || {}; 25 | timeout = utilities.isNumber(options.timeout) ? options.timeout : 0; 26 | 27 | xhr.open(method, urlUtils.urlParts(url).href, true); 28 | 29 | if (options.headers) { 30 | setHeaders(xhr, options.headers); 31 | } 32 | 33 | if (options.withCredentials) { 34 | xhr.withCredentials = true; 35 | } 36 | 37 | xhr.onload = function () { 38 | var statusText, response, status; 39 | 40 | /** 41 | * The only way to do a secure request on IE8 and IE9 is with the XDomainRequest object. Unfortunately, microsoft is 42 | * so nice that decided that the status property and the 'getAllResponseHeaders' method where not needed so we have to 43 | * fake them. If the request gets done with an XDomainRequest instance, we will assume that there are no headers and 44 | * the status will always be 200. If you don't like it, DO NOT USE ANCIENT BROWSERS!!! 45 | * 46 | * For mor info go to: https://msdn.microsoft.com/en-us/library/cc288060(v=vs.85).aspx 47 | */ 48 | if (!xhr.getAllResponseHeaders) { 49 | xhr.getAllResponseHeaders = function () { 50 | return null; 51 | }; 52 | } 53 | 54 | if (!xhr.status) { 55 | xhr.status = 200; 56 | } 57 | 58 | if (utilities.isDefined(timeoutId)) { 59 | clearTimeout(timeoutId); 60 | timeoutId = undefined; 61 | } 62 | 63 | statusText = xhr.statusText || ''; 64 | 65 | // responseText is the old-school way of retrieving response (supported by IE8 & 9) 66 | // response/responseType properties were introduced in XHR Level2 spec (supported by IE10) 67 | response = ('response' in xhr) ? xhr.response : xhr.responseText; 68 | 69 | // normalize IE9 bug (http://bugs.jquery.com/ticket/1450) 70 | status = xhr.status === 1223 ? 204 : xhr.status; 71 | 72 | callback( 73 | status, 74 | response, 75 | xhr.getAllResponseHeaders(), 76 | statusText); 77 | }; 78 | 79 | xhr.onerror = requestError; 80 | xhr.onabort = requestError; 81 | 82 | xhr.send(); 83 | 84 | if (timeout > 0) { 85 | timeoutId = setTimeout(function () { 86 | xhr && xhr.abort(); 87 | }, timeout); 88 | } 89 | 90 | function sanityCheck (url, callback, options) { 91 | if (!utilities.isString(url) || utilities.isEmptyString(url)) { 92 | throw new HttpRequestError("Invalid url '" + url + "'"); 93 | } 94 | 95 | if (!utilities.isFunction(callback)) { 96 | throw new HttpRequestError("Invalid handler '" + callback + "' for the http request"); 97 | } 98 | 99 | if (utilities.isDefined(options) && !utilities.isObject(options)) { 100 | throw new HttpRequestError("Invalid options map '" + options + "'"); 101 | } 102 | } 103 | 104 | function setHeaders (xhr, headers) { 105 | utilities.forEach(headers, function (value, key) { 106 | if (utilities.isDefined(value)) { 107 | xhr.setRequestHeader(key, value); 108 | } 109 | }); 110 | } 111 | 112 | function requestError () { 113 | callback(-1, null, null, ''); 114 | } 115 | }; 116 | 117 | HttpRequest.prototype.get = function (url, callback, options) { 118 | this.run('GET', url, processResponse, options); 119 | 120 | function processResponse (status, response, headersString, statusText) { 121 | if (isSuccess(status)) { 122 | callback(null, response, status, headersString, statusText); 123 | } else { 124 | callback(new HttpRequestError(statusText), response, status, headersString, statusText); 125 | } 126 | } 127 | 128 | function isSuccess (status) { 129 | return 200 <= status && status < 300; // eslint-disable-line yoda 130 | } 131 | }; 132 | 133 | function createXhr () { 134 | var xhr = new XMLHttpRequest(); 135 | if (!('withCredentials' in xhr)) { 136 | // XDomainRequest for IE. 137 | xhr = new XDomainRequest(); 138 | } 139 | return xhr; 140 | } 141 | 142 | var http = new HttpRequest(createXhr); 143 | 144 | module.exports = { 145 | http: http, 146 | HttpRequest: HttpRequest, 147 | HttpRequestError: HttpRequestError, 148 | createXhr: createXhr 149 | }; 150 | -------------------------------------------------------------------------------- /src/scripts/utils/mimetypes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | html5: [ 5 | 'text/javascript', 6 | 'text/javascript1.0', 7 | 'text/javascript1.2', 8 | 'text/javascript1.4', 9 | 'text/jscript', 10 | 'application/javascript', 11 | 'application/x-javascript', 12 | 'text/ecmascript', 13 | 'text/ecmascript1.0', 14 | 'text/ecmascript1.2', 15 | 'text/ecmascript1.4', 16 | 'text/livescript', 17 | 'application/ecmascript', 18 | 'application/x-ecmascript', 19 | ], 20 | 21 | flash: [ 22 | 'application/x-shockwave-flash' 23 | ], 24 | }; 25 | -------------------------------------------------------------------------------- /src/scripts/utils/urlUtils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var utilities = require('./utilityFunctions'); 4 | 5 | /** 6 | * 7 | * IMPORTANT NOTE: This function comes from angularJs and was originally called urlResolve 8 | * you can take a look at the original code here https://github.com/angular/angular.js/blob/master/src/ng/urlUtils.js 9 | * 10 | * Implementation Notes for non-IE browsers 11 | * ---------------------------------------- 12 | * Assigning a URL to the href property of an anchor DOM node, even one attached to the DOM, 13 | * results both in the normalizing and parsing of the URL. Normalizing means that a relative 14 | * URL will be resolved into an absolute URL in the context of the application document. 15 | * Parsing means that the anchor node's host, hostname, protocol, port, pathname and related 16 | * properties are all populated to reflect the normalized URL. This approach has wide 17 | * compatibility - Safari 1+, Mozilla 1+, Opera 7+,e etc. See 18 | * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html 19 | * 20 | * Implementation Notes for IE 21 | * --------------------------- 22 | * IE >= 8 and <= 10 normalizes the URL when assigned to the anchor node similar to the other 23 | * browsers. However, the parsed components will not be set if the URL assigned did not specify 24 | * them. (e.g. if you assign a.href = "foo", then a.protocol, a.host, etc. will be empty.) We 25 | * work around that by performing the parsing in a 2nd step by taking a previously normalized 26 | * URL (e.g. by assigning to a.href) and assigning it a.href again. This correctly populates the 27 | * properties such as protocol, hostname, port, etc. 28 | * 29 | * IE7 does not normalize the URL when assigned to an anchor node. (Apparently, it does, if one 30 | * uses the inner HTML approach to assign the URL as part of an HTML snippet - 31 | * http://stackoverflow.com/a/472729) However, setting img[src] does normalize the URL. 32 | * Unfortunately, setting img[src] to something like "javascript:foo" on IE throws an exception. 33 | * Since the primary usage for normalizing URLs is to sanitize such URLs, we can't use that 34 | * method and IE < 8 is unsupported. 35 | * 36 | * References: 37 | * http://developer.mozilla.org/en-US/docs/Web/API/HTMLAnchorElement 38 | * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html 39 | * http://url.spec.whatwg.org/#urlutils 40 | * https://github.com/angular/angular.js/pull/2902 41 | * http://james.padolsey.com/javascript/parsing-urls-with-the-dom/ 42 | * 43 | * @kind function 44 | * @param {string} url The URL to be parsed. 45 | * @description Normalizes and parses a URL. 46 | * @returns {object} Returns the normalized URL as a dictionary. 47 | * 48 | * | member name | Description | 49 | * |---------------|----------------| 50 | * | href | A normalized version of the provided URL if it was not an absolute URL | 51 | * | protocol | The protocol including the trailing colon | 52 | * | host | The host and port (if the port is non-default) of the normalizedUrl | 53 | * | search | The search params, minus the question mark | 54 | * | hash | The hash string, minus the hash symbol 55 | * | hostname | The hostname 56 | * | port | The port, without ":" 57 | * | pathname | The pathname, beginning with "/" 58 | * 59 | */ 60 | 61 | var urlParsingNode = document.createElement('a'); 62 | /** 63 | * documentMode is an IE-only property 64 | * http://msdn.microsoft.com/en-us/library/ie/cc196988(v=vs.85).aspx 65 | */ 66 | var msie = document.documentMode; 67 | 68 | function urlParts (url) { 69 | var href = url; 70 | 71 | if (msie) { 72 | // Normalize before parse. Refer Implementation Notes on why this is 73 | // done in two steps on IE. 74 | urlParsingNode.setAttribute('href', href); 75 | href = urlParsingNode.href; 76 | } 77 | 78 | urlParsingNode.setAttribute('href', href); 79 | 80 | // urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils 81 | return { 82 | href: urlParsingNode.href, 83 | protocol: urlParsingNode.protocol ? urlParsingNode.protocol.replace(/:$/, '') : '', 84 | host: urlParsingNode.host, 85 | search: urlParsingNode.search ? urlParsingNode.search.replace(/^\?/, '') : '', 86 | hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '', 87 | hostname: urlParsingNode.hostname, 88 | port: utilities.isNotEmptyString(urlParsingNode.port) ? urlParsingNode.port : 80, 89 | pathname: (urlParsingNode.pathname.charAt(0) === '/') 90 | ? urlParsingNode.pathname 91 | : '/' + urlParsingNode.pathname 92 | }; 93 | } 94 | 95 | 96 | /** 97 | * This function accepts a query string (search part of a url) and returns a dictionary with 98 | * the different key value pairs 99 | * @param {string} qs queryString 100 | */ 101 | function queryStringToObj (qs, cond) { 102 | var pairs, qsObj; 103 | 104 | cond = utilities.isFunction(cond) ? cond : function () { 105 | return true; 106 | }; 107 | 108 | qs = qs.trim().replace(/^\?/, ''); 109 | pairs = qs.split('&'); 110 | qsObj = {}; 111 | 112 | utilities.forEach(pairs, function (pair) { 113 | var keyValue, key, value; 114 | if (pair !== '') { 115 | keyValue = pair.split('='); 116 | key = keyValue[0]; 117 | value = keyValue[1]; 118 | if (cond(key, value)) { 119 | qsObj[key] = value; 120 | } 121 | } 122 | }); 123 | 124 | return qsObj; 125 | } 126 | 127 | /** 128 | * This function accepts an object and serializes it into a query string without the leading '?' 129 | * @param obj 130 | * @returns {string} 131 | */ 132 | function objToQueryString (obj) { 133 | var pairs = []; 134 | utilities.forEach(obj, function (value, key) { 135 | pairs.push(key + '=' + value); 136 | }); 137 | return pairs.join('&'); 138 | } 139 | 140 | module.exports = { 141 | urlParts: urlParts, 142 | queryStringToObj: queryStringToObj, 143 | objToQueryString: objToQueryString 144 | }; 145 | -------------------------------------------------------------------------------- /src/scripts/utils/xml.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var utilities = require('./utilityFunctions'); 4 | 5 | var xml = {}; 6 | 7 | xml.strToXMLDoc = function strToXMLDoc (stringContainingXMLSource) { 8 | // IE 8 9 | if (typeof window.DOMParser === 'undefined') { 10 | var xmlDocument = new ActiveXObject('Microsoft.XMLDOM'); 11 | xmlDocument.async = false; 12 | xmlDocument.loadXML(stringContainingXMLSource); 13 | return xmlDocument; 14 | } 15 | 16 | return parseString(stringContainingXMLSource); 17 | 18 | function parseString (stringContainingXMLSource) { 19 | var parser = new DOMParser(); 20 | var parsedDocument; 21 | 22 | // Note: This try catch is to deal with the fact that on IE parser.parseFromString does throw an error but the rest of the browsers don't. 23 | try { 24 | parsedDocument = parser.parseFromString(stringContainingXMLSource, 'application/xml'); 25 | 26 | if (isParseError(parsedDocument) || utilities.isEmptyString(stringContainingXMLSource)) { 27 | throw new Error(); 28 | } 29 | } catch (e) { 30 | throw new Error('xml.strToXMLDOC: Error parsing the string: "' + stringContainingXMLSource + '"'); 31 | } 32 | 33 | return parsedDocument; 34 | } 35 | 36 | function isParseError (parsedDocument) { 37 | try { // parser and parsererrorNS could be cached on startup for efficiency 38 | var parser = new DOMParser(), 39 | erroneousParse = parser.parseFromString('INVALID', 'text/xml'), 40 | parsererrorNS = erroneousParse.getElementsByTagName('parsererror')[0].namespaceURI; 41 | 42 | if (parsererrorNS === 'http://www.w3.org/1999/xhtml') { 43 | // In PhantomJS the parseerror element doesn't seem to have a special namespace, so we are just guessing here :( 44 | return parsedDocument.getElementsByTagName('parsererror').length > 0; 45 | } 46 | 47 | return parsedDocument.getElementsByTagNameNS(parsererrorNS, 'parsererror').length > 0; 48 | } catch (e) { 49 | // Note on IE parseString throws an error by itself and it will never reach this code. Because it will have failed before 50 | } 51 | } 52 | }; 53 | 54 | xml.parseText = function parseText (sValue) { 55 | if (/^\s*$/.test(sValue)) { return null; } 56 | if (/^(?:true|false)$/i.test(sValue)) { return sValue.toLowerCase() === 'true'; } 57 | if (isFinite(sValue)) { return parseFloat(sValue); } 58 | if (utilities.isISO8601(sValue)) { return new Date(sValue); } 59 | return sValue.trim(); 60 | }; 61 | 62 | xml.JXONTree = function JXONTree (oXMLParent) { 63 | var parseText = xml.parseText; 64 | 65 | // The document object is an especial object that it may miss some functions or attrs depending on the browser. 66 | // To prevent this problem with create the JXONTree using the root childNode which is a fully fleshed node on all supported 67 | // browsers. 68 | if (oXMLParent.documentElement) { 69 | return new xml.JXONTree(oXMLParent.documentElement); 70 | } 71 | 72 | if (oXMLParent.hasChildNodes()) { 73 | var sCollectedTxt = ''; 74 | for (var oNode, sProp, vContent, nItem = 0; nItem < oXMLParent.childNodes.length; nItem++) { 75 | oNode = oXMLParent.childNodes.item(nItem); 76 | if ((oNode.nodeType - 1 | 1) === 3) { sCollectedTxt += oNode.nodeType === 3 ? oNode.nodeValue.trim() : oNode.nodeValue; } 77 | else if (oNode.nodeType === 1 && !oNode.prefix) { 78 | sProp = utilities.decapitalize(oNode.nodeName); 79 | vContent = new xml.JXONTree(oNode); 80 | if (this.hasOwnProperty(sProp)) { 81 | if (this[sProp].constructor !== Array) { this[sProp] = [this[sProp]]; } 82 | this[sProp].push(vContent); 83 | } else { this[sProp] = vContent; } 84 | } 85 | } 86 | if (sCollectedTxt) { this.keyValue = parseText(sCollectedTxt); } 87 | } 88 | 89 | // IE8 Stupid fix 90 | var hasAttr = typeof oXMLParent.hasAttributes === 'undefined' ? oXMLParent.attributes.length > 0 : oXMLParent.hasAttributes(); 91 | if (hasAttr) { 92 | var oAttrib; 93 | for (var nAttrib = 0; nAttrib < oXMLParent.attributes.length; nAttrib++) { 94 | oAttrib = oXMLParent.attributes.item(nAttrib); 95 | this['@' + utilities.decapitalize(oAttrib.name)] = parseText(oAttrib.value.trim()); 96 | } 97 | } 98 | }; 99 | 100 | xml.JXONTree.prototype.attr = function (attr) { 101 | return this['@' + utilities.decapitalize(attr)]; 102 | }; 103 | 104 | xml.toJXONTree = function toJXONTree (xmlString) { 105 | var xmlDoc = xml.strToXMLDoc(xmlString); 106 | return new xml.JXONTree(xmlDoc); 107 | }; 108 | 109 | /** 110 | * Helper function to extract the keyvalue of a JXONTree obj 111 | * 112 | * @param xmlObj {JXONTree} 113 | * return the key value or undefined; 114 | */ 115 | xml.keyValue = function getKeyValue (xmlObj) { 116 | if (xmlObj) { 117 | return xmlObj.keyValue; 118 | } 119 | return undefined; 120 | }; 121 | 122 | xml.attr = function getAttrValue (xmlObj, attr) { 123 | if (xmlObj) { 124 | return xmlObj['@' + utilities.decapitalize(attr)]; 125 | } 126 | return undefined; 127 | }; 128 | 129 | xml.encode = function encodeXML (str) { 130 | if (!utilities.isString(str)) return undefined; 131 | 132 | return str.replace(/&/g, '&') 133 | .replace(//g, '>') 135 | .replace(/"/g, '"') 136 | .replace(/'/g, '''); 137 | }; 138 | 139 | xml.decode = function decodeXML (str) { 140 | if (!utilities.isString(str)) return undefined; 141 | 142 | return str.replace(/'/g, "'") 143 | .replace(/"/g, '"') 144 | .replace(/>/g, '>') 145 | .replace(/</g, '<') 146 | .replace(/&/g, '&'); 147 | }; 148 | 149 | module.exports = xml; 150 | -------------------------------------------------------------------------------- /src/scripts/videojs_5.vast.vpaid.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* PROD-EXCLUDE-START */ 4 | var videojs = window.videojs || { 5 | getComponent: function () {}, 6 | extend: function () {}, 7 | registerComponent: function () {}, 8 | registerPlugin: function () {} 9 | }; 10 | /* PROD-EXCLUDE-END */ 11 | 12 | window._molSettings = null; 13 | 14 | require('./plugin/components/black-poster_5'); 15 | var logger = require('./utils/consoleLogger'); 16 | 17 | logger.log('Prebid MailOnline plugin version 1.3.18'); 18 | 19 | var videoJsVAST = require('./plugin/videojs.vast.vpaid'); 20 | 21 | if (videojs.registerPlugin) { 22 | if (!videojs.getPlugins().vastClient) { 23 | videojs.registerPlugin('vastClient', videoJsVAST); 24 | } 25 | } else if (videojs.Player) { 26 | if (!videojs.Player.prototype.vastClient) { 27 | videojs.plugin('vastClient', videoJsVAST); 28 | } 29 | } else if (videojs.plugin) { 30 | try { 31 | videojs.plugin('vastClient', videoJsVAST); 32 | } catch (e) { 33 | logger.log('*** ERROR: Unable to register MailOnline plugin with VideoJS!'); 34 | } 35 | } 36 | 37 | // VIDLA-4391 - Add support for multiple players on the same page, each with a unique MOL plugin loaded from an iFrames 38 | if (parent && window !== parent) { 39 | window.bc_vastClientFunc = videoJsVAST; 40 | } 41 | -------------------------------------------------------------------------------- /src/styles/ads-label.scss: -------------------------------------------------------------------------------- 1 | /* Appnexus MailOnline plugin CSS Version 1.0.3 */ 2 | .vjs-label-hidden { 3 | display: none!important; 4 | } 5 | 6 | .vjs-default-skin div.vjs-ads-label { 7 | font-size: 13px; 8 | line-height: 30px; 9 | font-weight: normal; 10 | text-align: center; 11 | color: white; 12 | display: none; 13 | width: auto; 14 | padding-left: 10px; 15 | } 16 | 17 | .vjs-ad-playing .vjs-control.vjs-ads-label { 18 | display: block; 19 | } 20 | -------------------------------------------------------------------------------- /src/styles/black-poster.scss: -------------------------------------------------------------------------------- 1 | .vjs-black-poster { 2 | position: absolute; 3 | top: 0; 4 | right: 0; 5 | bottom: 0; 6 | left: 0; 7 | margin: 0; 8 | padding: 0; 9 | background-repeat: no-repeat; 10 | background-position: 50% 50%; 11 | background-size: contain; 12 | cursor: pointer; 13 | background-color: #000; 14 | } 15 | 16 | .vjs-has-started .vjs-black-poster.vjs-hidden { 17 | display: none; 18 | } 19 | 20 | // if we use native controls we don't need black poster 21 | .vjs-using-native-controls .vjs-black-poster { 22 | display: none; 23 | } 24 | -------------------------------------------------------------------------------- /src/styles/videojs.vast.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * videojs.ads.css 3 | */ 4 | 5 | /* Ad playback */ 6 | .vjs-ad-playing .vjs-progress-control { 7 | pointer-events: none; 8 | display: none; 9 | } 10 | 11 | .vjs-ad-playing .vjs-play-control.vjs-paused, 12 | .vjs-ad-playing .vjs-volume-level, 13 | .vjs-ad-playing .vjs-play-progress { 14 | background-color: #ffe400 !important; 15 | } 16 | 17 | div.vast-skip-button { 18 | display: block; 19 | position: absolute; 20 | bottom: 20%; 21 | right: 0; 22 | background-color: #000; 23 | color: white; 24 | font-size: 15px; 25 | font-weight: bold; 26 | width: auto; 27 | padding: 8px; 28 | z-index: 2; 29 | border: 1px solid white; 30 | border-right: none; 31 | } 32 | 33 | p.vast-skip-button-text { 34 | display: block; 35 | color: white; 36 | font-size: 13px; 37 | font-weight: 100; 38 | width: 80px; 39 | margin: 0; 40 | padding: 0; } 41 | 42 | .vast-skip-button.enabled { 43 | cursor: pointer; 44 | color: #fff; 45 | } 46 | 47 | .vast-skip-button.enabled:hover { 48 | cursor: pointer; 49 | background: #333; 50 | } 51 | 52 | .vast-blocker { 53 | display: block; 54 | position: absolute; 55 | margin: 0; 56 | padding: 0; 57 | height: 100%; 58 | width: 100%; 59 | top: 0; 60 | left: 0; 61 | right: 0; 62 | bottom: 0; 63 | } 64 | 65 | .vast-skip-button.enabled:after { 66 | content: ">>"; 67 | position: relative; 68 | top: 1px; 69 | margin-left: 8px; 70 | } 71 | /* Ad loading */ 72 | .vjs-ad-playing.vjs-vast-ad-loading .vjs-loading-spinner { 73 | display: block; 74 | z-index: 10; 75 | 76 | /* only animate when showing because it can be processor heavy */ 77 | /* animation */ 78 | -webkit-animation: spin 1.5s infinite linear; 79 | -moz-animation: spin 1.5s infinite linear; 80 | -o-animation: spin 1.5s infinite linear; 81 | animation: spin 1.5s infinite linear; 82 | } 83 | 84 | .vjs-vast-ad-loading div.vjs-big-play-button { 85 | display: none!important; 86 | } 87 | 88 | .vjs-ad-playing .vjs-slider-handle:before{ 89 | display: none; 90 | } 91 | 92 | .vjs-ad-playing .vjs-live-controls{ 93 | display: none; 94 | } 95 | 96 | div.vast-back-button { 97 | display: block; 98 | position: absolute; 99 | top: 5%; 100 | left: 0; 101 | background-color: #000; 102 | color: white; 103 | font-size: 15px; 104 | font-weight: bold; 105 | width: auto; 106 | padding: 8px; 107 | z-index: 2; 108 | border: 1px solid white; 109 | border-left: none; 110 | } 111 | 112 | .vast-back-button.enabled { 113 | cursor: pointer; 114 | color: #fff; 115 | } 116 | 117 | .vast-back-button.enabled:after { 118 | content: "<<"; 119 | position: relative; 120 | top: 1px; 121 | margin-left: 8px; 122 | } 123 | -------------------------------------------------------------------------------- /src/styles/videojs.vast.vpaid.scss: -------------------------------------------------------------------------------- 1 | @import "ads-label"; 2 | @import "black-poster"; 3 | @import "videojs.vpaid"; 4 | @import "videojs.vast"; -------------------------------------------------------------------------------- /src/styles/videojs.vpaid.scss: -------------------------------------------------------------------------------- 1 | div.VPAID-container{ 2 | position: absolute; 3 | top: 0; 4 | left: 0; 5 | width: 100%; 6 | height: 100%; 7 | } 8 | 9 | /* EN - hide progress-bar when ad playing */ 10 | div.vjs-vpaid-ad div.vjs-progress-control { 11 | display: none; } 12 | 13 | /* EN 14 | div.vjs-vpaid-ad div.vjs-progress-control, 15 | div.vjs-vpaid-ad div.vjs-time-controls, 16 | div.vjs-vpaid-ad div.vjs-time-divider { 17 | display: none; } 18 | */ 19 | 20 | div.vjs-vpaid-ad.vjs-vpaid-flash-ad div.VPAID-container { 21 | background-color: #000000; 22 | } 23 | 24 | div.vjs-vpaid-ad .vjs-tech { 25 | z-index: 0; 26 | } 27 | 28 | /* EN - hide progress-bar when ad playing */ 29 | div.vjs-ad-playing div.vjs-progress-control { 30 | display: none; } 31 | -------------------------------------------------------------------------------- /test/ads/icon/IconIntegrator.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe("IconIntegrator", function () { 4 | 5 | var VASTError = require('ads/vast/VASTError'); 6 | var IconIntegrator = require('ads/icon/IconIntegrator'); 7 | var Icon = require('ads/vast/Icon'); 8 | 9 | var xml = require('utils/xml'); 10 | var testUtils = require('../../test-utils'); 11 | 12 | function assertError(callback, msg, code) { 13 | var error = testUtils.firstArg(callback); 14 | assert.instanceOf(error, VASTError); 15 | assert.equal(error.message, "VAST Error: " + msg); 16 | if (code) { 17 | assert.equal(error.code, code); 18 | } 19 | } 20 | 21 | function createIcons() { 22 | var xmlStr1 = ` 23 | 24 | http://fake.net/images/Fallback_1.jpeg 25 | 26 | http://fake.net/trackers/IconClickTracker_1.png 27 | http://www.wikipedia.com 28 | 29 | http://fake.net/trackers/IconViewTracker_1.png 30 | `; 31 | var xmlStr2 = ` 32 | 33 | http://fake.net/images/Fallback_2.jpeg 34 | 35 | http://fake.net/trackers/IconClickTracker_2.png 36 | http://www.google.com 37 | 38 | http://fake.net/trackers/IconViewTracker_1.png 39 | `; 40 | var arr = []; 41 | arr.push(new Icon(xml.toJXONTree(xmlStr1))); 42 | arr.push(new Icon(xml.toJXONTree(xmlStr2))); 43 | return arr; 44 | } 45 | 46 | describe("instance", function () { 47 | var iconIntegrator, player, callback; 48 | 49 | beforeEach(function () { 50 | player = videojs(document.createElement('video'), {}); 51 | iconIntegrator = new IconIntegrator(player); 52 | callback = sinon.spy(); 53 | }); 54 | 55 | afterEach(function () { 56 | }); 57 | 58 | describe("renderIcons", function () { 59 | beforeEach(function () { 60 | this.clock = sinon.useFakeTimers(); 61 | }); 62 | 63 | afterEach(function () { 64 | this.clock.restore(); 65 | }); 66 | 67 | it("must call the callback with an error if you don't pass a valid icon array", function () { 68 | iconIntegrator.renderIcons(null, callback); 69 | assertError(callback, 'On IconIntegrator, missing required icon array'); 70 | }); 71 | 72 | it("must render the icons", function () { 73 | iconIntegrator.renderIcons(createIcons(), callback); 74 | this.clock.tick(); 75 | assert.equal(iconIntegrator.icons[0].div.id, 'adicon_static1'); 76 | assert.equal(iconIntegrator.icons[1].div.id, 'adicon_static2'); 77 | player.trigger('vast.adEnd'); 78 | assert.equal(iconIntegrator.icons.length, 0); 79 | }); 80 | }); 81 | 82 | describe("_createIcons", function () { 83 | var icons; 84 | 85 | beforeEach(function () { 86 | icons = createIcons(); 87 | }); 88 | 89 | it("must create one content for icons with same program", function (done) { 90 | icons[1].program = icons[0].program; 91 | iconIntegrator._createIcons(icons, function(error) { 92 | assert.equal(iconIntegrator.icons.length, 1); 93 | assert.isNull(error); 94 | done(); 95 | }); 96 | }); 97 | }); 98 | 99 | describe("_renderIcons", function () { 100 | var icons; 101 | 102 | beforeEach(function () { 103 | icons = createIcons(); 104 | iconIntegrator._createIcons(icons, function() {}); 105 | }); 106 | 107 | it("must begin to render two icons and remove them by icon durations", function (done) { 108 | this.timeout(15000); 109 | iconIntegrator._renderIcons(callback); 110 | assert.equal(iconIntegrator.icons.length, 2); 111 | assert.equal(iconIntegrator.icons[0].div.style.display, 'none'); 112 | assert.equal(iconIntegrator.icons[1].div.style.display, 'none'); 113 | setTimeout(function() { 114 | assert.equal(iconIntegrator.icons[0].div.style.display, 'block'); 115 | assert.equal(iconIntegrator.icons[1].div.style.display, 'block'); 116 | setTimeout(function() { 117 | assert.equal(iconIntegrator.icons.length, 0); 118 | done(); 119 | }, 6000); 120 | }, 3000); 121 | }); 122 | }); 123 | }); 124 | }); 125 | -------------------------------------------------------------------------------- /test/ads/vast/Ad.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe("Ad", function () { 4 | 5 | var Ad = require('ads/vast/Ad'); 6 | var InLine = require('ads/vast/InLine'); 7 | var Wrapper = require('ads/vast/Wrapper'); 8 | 9 | var xml = require('utils/xml'); 10 | 11 | it("must be a constructor function", function () { 12 | assert.isFunction(Ad); 13 | }); 14 | 15 | it("must return an instance of Ad", function () { 16 | assert.instanceOf(new Ad(xml.toJXONTree('')), Ad); 17 | }); 18 | 19 | it("must set the id of the ad", function () { 20 | var ad = new Ad(xml.toJXONTree('')); 21 | assert.equal(ad.id, '107195552'); 22 | }); 23 | 24 | it("must not set the id if not defined in the xml", function () { 25 | var ad = new Ad(xml.toJXONTree('')); 26 | assert.isUndefined(ad.id); 27 | }); 28 | 29 | it("must not set the sequence number if not defined", function () { 30 | var ad = new Ad(xml.toJXONTree('')); 31 | assert.isUndefined(ad.sequence); 32 | }); 33 | 34 | it("must set the sequence number if defined as attr", function () { 35 | var ad = new Ad(xml.toJXONTree('')); 36 | assert.strictEqual(ad.sequence, 1); 37 | }); 38 | 39 | it("must set the inline as an InLine instance object if set", function () { 40 | var adXML = ''; 41 | var ad = new Ad(xml.toJXONTree(adXML)); 42 | assert.instanceOf(ad.inLine, InLine); 43 | }); 44 | 45 | it("must set the wrapper as a Wrapper instance object if set", function(){ 46 | var adXML = ''; 47 | var ad = new Ad(xml.toJXONTree(adXML)); 48 | assert.instanceOf(ad.wrapper, Wrapper); 49 | }); 50 | 51 | }); -------------------------------------------------------------------------------- /test/ads/vast/Companion.spec.js: -------------------------------------------------------------------------------- 1 | var Companion = require('ads/vast/Companion'); 2 | var TrackingEvent = require('ads/vast/TrackingEvent'); 3 | 4 | var xml = require('utils/xml'); 5 | 6 | describe("Companion", function(){ 7 | it("must return an instance of Companion", function(){ 8 | var companion = new Companion(xml.toJXONTree('')); 9 | assert.instanceOf(companion, Companion); 10 | }); 11 | 12 | //Required Elements 13 | describe("staticResource", function(){ 14 | it("must contain the static resource and mime type", function(){ 15 | var companionXML = ''; 16 | var companion = new Companion(xml.toJXONTree(companionXML)); 17 | assert.equal(companion.staticResource, "http://www.example.com/image.jpg"); 18 | assert.equal(companion.creativeType, "image/jpeg"); 19 | }); 20 | }); 21 | 22 | var companionXML = '' + 23 | '' + 24 | '' + 25 | '' + 26 | ''; 27 | 28 | //Optional Elements 29 | describe("trackingEvents", function(){ 30 | /*jshint maxlen: 500 */ 31 | it("must be filled with all the tracking events that the xml provides", function(){ 32 | var companion = new Companion(xml.toJXONTree(companionXML)); 33 | var trackingEvents = companion.trackingEvents; 34 | 35 | //First tracking event 36 | var tracking = trackingEvents[0]; 37 | assert.instanceOf(tracking, TrackingEvent); 38 | assert.equal(tracking.name, 'creativeView'); 39 | }); 40 | }); 41 | 42 | describe("dimensions", function(){ 43 | it("must contain creative dimensions", function(){ 44 | var companion = new Companion(xml.toJXONTree(companionXML)); 45 | assert.equal(companion.width, 300); 46 | assert.equal(companion.height, 250); 47 | }); 48 | }); 49 | 50 | describe("clickthrough", function(){ 51 | it("must provide companion impression tracker", function(){ 52 | var companionXML = '' + 53 | ''+ 54 | ''+ 55 | ''; 56 | var companion = new Companion(xml.toJXONTree(companionXML)); 57 | assert.equal(companion.companionClickThrough, "http://www.example.com/tracker.html"); 58 | }); 59 | }); 60 | }); -------------------------------------------------------------------------------- /test/ads/vast/Creative.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var testUtils = require('../../test-utils'); 3 | 4 | describe("Creative", function(){ 5 | 6 | var Creative = require('ads/vast/Creative'); 7 | var Linear = require('ads/vast/Linear'); 8 | 9 | var xml = require('utils/xml'); 10 | 11 | it("must return an instance of Creative", function(){ 12 | assert.instanceOf(new Creative(xml.toJXONTree('')), Creative); 13 | }); 14 | 15 | it("must set the id if passed", function(){ 16 | var creativeXML = ''; 17 | var creative = new Creative(xml.toJXONTree(creativeXML)); 18 | assert.equal(creative.id, '8455'); 19 | }); 20 | 21 | it("must set the sequence if set", function(){ 22 | var creativeXML = ''; 23 | var creative = new Creative(xml.toJXONTree(creativeXML)); 24 | assert.equal(creative.sequence, 1); 25 | }); 26 | 27 | it("must set the the ad id if set", function(){ 28 | var creativeXML = ''; 29 | var creative = new Creative(xml.toJXONTree(creativeXML)); 30 | assert.equal(creative.adId, 8455); 31 | }); 32 | 33 | it("must set the the apiFramework if set", function(){ 34 | var creativeXML = ''; 35 | var creative = new Creative(xml.toJXONTree(creativeXML)); 36 | assert.equal(creative.apiFramework, "fooFramework"); 37 | }); 38 | 39 | it("must set the linear if passed as part of the jxonTreeData", function(){ 40 | var creativeXML = '' + 41 | '' + 42 | '00:00:58' + 43 | '' + 44 | ''+ 45 | ''; 46 | var creative = new Creative(xml.toJXONTree(creativeXML)); 47 | assert.instanceOf(creative.linear, Linear); 48 | }); 49 | 50 | describe("companionAds", function() { 51 | var creativeXML = '' + 52 | '' + 53 | '' + 54 | ''; 55 | 56 | it('must handle when no companionAds', function() { 57 | function newCreative() { 58 | return new Creative(xml.toJXONTree(creativeXML)); 59 | } 60 | assert.doesNotThrow(newCreative); 61 | }); 62 | }); 63 | 64 | describe("isSupported", function(){ 65 | var creative; 66 | 67 | beforeEach(function(){ 68 | var creativeXML = '' + 69 | '' + 70 | '00:00:58' + 71 | '' + 72 | ''+ 73 | ''; 74 | creative = new Creative(xml.toJXONTree(creativeXML)); 75 | }); 76 | 77 | it("must return true if the creative does not contain a linear", function(){ 78 | delete creative.linear; 79 | assert.isTrue(creative.isSupported()); 80 | }); 81 | 82 | it("must returns false if it contains a non supported linear", function(){ 83 | creative.linear = { 84 | isSupported: function(){ 85 | return false; 86 | } 87 | }; 88 | assert.isFalse(creative.isSupported()); 89 | }); 90 | 91 | it("must returns true if it contains a supported linear", function(){ 92 | creative.linear = { 93 | isSupported: function(){ 94 | return true; 95 | } 96 | }; 97 | assert.isTrue(creative.isSupported()); 98 | }); 99 | 100 | 101 | }); 102 | 103 | describe("parseCreatives", function () { 104 | var parseCreatives; 105 | 106 | beforeEach(function () { 107 | parseCreatives = Creative.parseCreatives; 108 | }); 109 | 110 | it("must return an empty array if you pass no creativesJTree", function () { 111 | testUtils.assertEmptyArray(parseCreatives()); 112 | }); 113 | 114 | it("must return an empty array if there is no real creatives", function () { 115 | var inlineXML = ''; 116 | testUtils.assertEmptyArray(parseCreatives(xml.toJXONTree(inlineXML).creatives)); 117 | }); 118 | 119 | it("must be an array or creatives", function () { 120 | var inlineXML = '' + 121 | '' + 122 | '' + 123 | '' + 124 | '' + 125 | ''; 126 | var creativesJTree = xml.toJXONTree(inlineXML).creatives; 127 | var creatives = parseCreatives(creativesJTree); 128 | assert.isArray(creatives); 129 | assert.instanceOf(creatives[0], Creative); 130 | assert.equal(creatives[0].id, 8454); 131 | assert.instanceOf(creatives[1], Creative); 132 | assert.equal(creatives[1].id, 8455); 133 | }); 134 | }); 135 | }); 136 | -------------------------------------------------------------------------------- /test/ads/vast/Icon.spec.js: -------------------------------------------------------------------------------- 1 | var Icon = require('ads/vast/Icon'); 2 | 3 | var xml = require('utils/xml'); 4 | 5 | describe("Icon", function(){ 6 | it("must return an instance of Icon", function(){ 7 | var icon = new Icon(xml.toJXONTree('')); 8 | assert.instanceOf(icon, Icon); 9 | }); 10 | 11 | //Required Elements 12 | describe("staticResource", function(){ 13 | it("must contain the static resource and mime type", function(){ 14 | var iconXML = ''; 15 | var icon = new Icon(xml.toJXONTree(iconXML)); 16 | assert.equal(icon.staticResource, "http://www.example.com/image.jpg"); 17 | assert.equal(icon.creativeType, "image/jpeg"); 18 | }); 19 | }); 20 | 21 | var iconXML = '' + 22 | '' + 23 | '' + 24 | '' + 25 | '' + 26 | ''; 27 | 28 | //Optional Elements 29 | describe("iconClicks", function(){ 30 | it("must be filled with all the click data that the xml provides", function(){ 31 | var icon = new Icon(xml.toJXONTree(iconXML)); 32 | assert.equal(icon.iconClickThrough, 'http://www.example.com/whatever'); 33 | var tracking = icon.iconClickTrackings[0]; 34 | assert.equal(tracking, 'http://www.example.com/tracking'); 35 | tracking = icon.iconViewTrackings[0]; 36 | assert.equal(tracking, 'http://www.example.com/viewTracking'); 37 | }); 38 | }); 39 | 40 | describe("dimensions", function(){ 41 | it("must contain creative dimensions", function(){ 42 | var icon = new Icon(xml.toJXONTree(iconXML)); 43 | assert.equal(icon.width, 300); 44 | assert.equal(icon.height, 250); 45 | }); 46 | }); 47 | }); -------------------------------------------------------------------------------- /test/ads/vast/Linear.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe("Linear", function () { 4 | 5 | var Linear = require('ads/vast/Linear'); 6 | var MediaFile = require('ads/vast/MediaFile'); 7 | var TrackingEvent = require('ads/vast/TrackingEvent'); 8 | var VideoClicks = require('ads/vast/VideoClicks'); 9 | 10 | var xml = require('utils/xml'); 11 | 12 | beforeEach(function () { 13 | window._molSettings = {}; 14 | }); 15 | 16 | it("must return an instance of Linear", function () { 17 | var linear = new Linear(xml.toJXONTree('')); 18 | assert.instanceOf(linear, Linear); 19 | }); 20 | 21 | //Required Elements 22 | describe("duration", function () { 23 | it("must contain the duration specified on the xml in ms", function () { 24 | var linearXML = '00:00:58'; 25 | var linear = new Linear(xml.toJXONTree(linearXML)); 26 | assert.equal(linear.duration, 58000); 27 | }); 28 | }); 29 | 30 | describe("mediaFiles", function () { 31 | it("must be an array", function () { 32 | var linearXML = ''; 33 | var linear = new Linear(xml.toJXONTree(linearXML)); 34 | assert.isArray(linear.mediaFiles); 35 | }); 36 | 37 | it("must contain all the specified mediaFiles", function () { 38 | var linearXML = '' + 39 | '' + 40 | '00:00:58' + 41 | '' + 42 | '' + 43 | '' + 44 | '' + 45 | '' + 46 | '' + 47 | '' + 48 | '' + 49 | ''; 50 | var linear = new Linear(xml.toJXONTree(linearXML)); 51 | 52 | assert.equal(linear.mediaFiles.length, 2); 53 | assert.instanceOf(linear.mediaFiles[0], MediaFile); 54 | assert.equal(1, linear.mediaFiles[0].id); 55 | assert.instanceOf(linear.mediaFiles[1], MediaFile); 56 | assert.equal(2, linear.mediaFiles[1].id); 57 | }); 58 | }); 59 | 60 | //Optional Elements 61 | describe("videoClicks", function () { 62 | it("must be an instance of videoClicks", function () { 63 | var linearXML = ''; 64 | var linear = new Linear(xml.toJXONTree(linearXML)); 65 | assert.instanceOf(linear.videoClicks, VideoClicks); 66 | }); 67 | }); 68 | 69 | describe("trackingEvents", function () { 70 | /*jshint maxlen: 500 */ 71 | it("must be filled with all the tracking events that the xml provides", function () { 72 | var linearXML = '' + 73 | '' + 74 | '' + 75 | '' + 76 | '' + 77 | '' + 78 | '' + 79 | '' + 80 | '' + 81 | ''; 82 | var linear = new Linear(xml.toJXONTree(linearXML)); 83 | var trackingEvents = linear.trackingEvents; 84 | 85 | //First tracking event 86 | var tracking = trackingEvents[0]; 87 | assert.instanceOf(tracking, TrackingEvent); 88 | assert.equal(tracking.name, 'firstQuartile'); 89 | 90 | //Second tracking event 91 | tracking = trackingEvents[1]; 92 | assert.instanceOf(tracking, TrackingEvent); 93 | assert.equal(tracking.name, 'midQuartile'); 94 | }); 95 | 96 | it("must properly set progress events", function () { 97 | var linearXML = '' + 98 | '' + 99 | '' + 100 | '' + 101 | '' + 102 | '00:00:00.100' + 103 | ''; 104 | var linear = new Linear(xml.toJXONTree(linearXML)); 105 | var trackingEvents = linear.trackingEvents; 106 | 107 | assert.isArray(trackingEvents); 108 | assert.equal(trackingEvents.length, 2); 109 | assert.equal(trackingEvents[0].name, 'progress'); 110 | assert.equal(trackingEvents[0].uri, 'track.url.com'); 111 | assert.equal(trackingEvents[0].offset, 10); 112 | assert.equal(trackingEvents[1].name, 'progress'); 113 | assert.equal(trackingEvents[1].uri, 'track.url.com'); 114 | assert.equal(trackingEvents[1].offset, 60000); 115 | }); 116 | }); 117 | 118 | describe("skipoffset", function () { 119 | it("must contain whatever is set on the xml but parsed into ms", function () { 120 | var linearXML = ''; 121 | var linear = new Linear(xml.toJXONTree(linearXML)); 122 | assert.equal(linear.skipoffset, 5000); 123 | }); 124 | 125 | it("must be possible to set the skipoffset as a percentage", function () { 126 | var linearXML = '00:00:01'; 127 | var linear = new Linear(xml.toJXONTree(linearXML)); 128 | assert.equal(linear.skipoffset, 100); 129 | }); 130 | 131 | it("must be possible to set the skipoffset as a percentage with decimals", function () { 132 | var linearXML = '00:00:01'; 133 | var linear = new Linear(xml.toJXONTree(linearXML)); 134 | assert.equal(linear.skipoffset, 105); 135 | }); 136 | 137 | it("as percentage with no linear.duration must be null", function () { 138 | var linearXML = ''; 139 | var linear = new Linear(xml.toJXONTree(linearXML)); 140 | assert.isNull(linear.skipoffset); 141 | }); 142 | }); 143 | 144 | describe("AdParameters", function () { 145 | it("must handle when no parameters is passed", function () { 146 | 147 | [true, false].map(function(xmlEncoded) { 148 | return ''; 149 | }).forEach(function (adParametersString) { 150 | 151 | var linearXML = '' + adParametersString + ''; 152 | assert.doesNotThrow(function () { 153 | return new Linear(xml.toJXONTree(linearXML)); 154 | }); 155 | }); 156 | }); 157 | 158 | it("must be added to the linear", function () { 159 | var encodedAdParameters = xml.encode('data'); 160 | 161 | var linearXML = ''; 162 | var linear = new Linear(xml.toJXONTree(linearXML)); 163 | assert.equal(linear.adParameters, encodedAdParameters); 164 | }); 165 | 166 | it("with xmlEncoded to true, must decode the ad params before adding them to the linear", function () { 167 | var encodedAdParameters = xml.encode('data'); 168 | 169 | var linearXML = ''; 170 | var linear = new Linear(xml.toJXONTree(linearXML)); 171 | assert.equal(linear.adParameters, 'data'); 172 | }); 173 | 174 | it("with xmlEncoded to false, must NOT decode the ad params before adding them to the linear", function () { 175 | var encodedAdParameters = xml.encode('data'); 176 | 177 | var linearXML = ''; 178 | var linear = new Linear(xml.toJXONTree(linearXML)); 179 | assert.equal(linear.adParameters, encodedAdParameters); 180 | }); 181 | }); 182 | 183 | describe("isSupported", function () { 184 | var linear; 185 | 186 | beforeEach(function () { 187 | var linearXML = ''; 188 | linear = new Linear(xml.toJXONTree(linearXML)); 189 | }); 190 | 191 | it("must return true if at least one of the mediaFiles is supported", function () { 192 | linear.mediaFiles = [ 193 | { 194 | isSupported: function () { 195 | return false; 196 | } 197 | }, 198 | { 199 | isSupported: function () { 200 | return true; 201 | } 202 | } 203 | ]; 204 | 205 | assert.isTrue(linear.isSupported()); 206 | }); 207 | 208 | it("must return false if none of the mediaFiles are supported", function () { 209 | linear.mediaFiles = [ 210 | { 211 | isSupported: function () { 212 | return false; 213 | } 214 | }, 215 | { 216 | isSupported: function () { 217 | return false; 218 | } 219 | } 220 | ]; 221 | 222 | assert.isFalse(linear.isSupported()); 223 | }); 224 | }); 225 | }); 226 | -------------------------------------------------------------------------------- /test/ads/vast/MediaFile.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe("MediaFile", function () { 4 | 5 | var MediaFile = require('ads/vast/MediaFile'); 6 | 7 | var xml = require('utils/xml'); 8 | var vastUtil = require('ads/vast/vastUtil'); 9 | 10 | var mediaFileXML; 11 | 12 | beforeEach(function () { 13 | mediaFileXML = '' + 14 | '' + 15 | '' + 16 | ''; 17 | }); 18 | 19 | it("must return an instance of MediaFile", function () { 20 | assert.instanceOf(new MediaFile(xml.toJXONTree(mediaFileXML)), MediaFile); 21 | }); 22 | 23 | describe("instance", function () { 24 | var mediaFile; 25 | 26 | beforeEach(function () { 27 | var mediaFileXML = '' + 28 | '' + 29 | '' + 30 | ''; 31 | mediaFile = new MediaFile(xml.toJXONTree(mediaFileXML)); 32 | }); 33 | 34 | it("must set the src", function(){ 35 | assert.equal('http://gcdn.2mdn.net/MotifFiles/html/2215309/PID_914438_1235753019000_dcrmvideo.flv', mediaFile.src); 36 | }); 37 | 38 | it("must set the delivery", function () { 39 | assert.equal(mediaFile.delivery, "progressive"); 40 | }); 41 | 42 | it("must set the type", function () { 43 | assert.equal(mediaFile.type, 'video/x-flv'); 44 | }); 45 | 46 | it("must contain the width", function () { 47 | assert.equal(mediaFile.width, 300); 48 | }); 49 | 50 | it("must set the height", function () { 51 | assert.equal(mediaFile.height, 225); 52 | }); 53 | 54 | it("must set the codec if passed", function () { 55 | assert.equal(mediaFile.codec, "video/mpeg-generic"); 56 | }); 57 | 58 | it("must set the id for the media file if set in the xml", function () { 59 | assert.equal(mediaFile.id, 1); 60 | }); 61 | 62 | it("must set the bitrate if set in the xml", function () { 63 | assert.equal(mediaFile.bitrate, 457); 64 | }); 65 | 66 | it("must set the minBitrate if set in the xml in the xml", function () { 67 | mediaFile = new MediaFile(xml.toJXONTree('')); 68 | assert.equal(mediaFile.minBitrate, 457); 69 | }); 70 | 71 | it("must set the maxBitrate if set in the xml", function () { 72 | mediaFile = new MediaFile(xml.toJXONTree('')); 73 | assert.equal(mediaFile.maxBitrate, 457); 74 | }); 75 | 76 | it("must set the scalable attr if set in the xml", function () { 77 | mediaFile = new MediaFile(xml.toJXONTree('')); 78 | assert.isTrue(mediaFile.scalable); 79 | }); 80 | 81 | it("must set the maintainAspectRatio attr if set in the xml", function () { 82 | mediaFile = new MediaFile(xml.toJXONTree('')); 83 | assert.isTrue(mediaFile.maintainAspectRatio); 84 | }); 85 | 86 | it("must set the apiFramework if set in the xml", function () { 87 | mediaFile = new MediaFile(xml.toJXONTree('')); 88 | assert.equal(mediaFile.apiFramework, 'someApiFramework'); 89 | }); 90 | 91 | describe("isSupported", function(){ 92 | var mediaFile; 93 | 94 | beforeEach(function () { 95 | mediaFile = new MediaFile(xml.toJXONTree('')); 96 | }); 97 | 98 | it("must be a function", function(){ 99 | assert.isFunction(mediaFile.isSupported); 100 | }); 101 | 102 | describe("VPAID mediafile", function() { 103 | 104 | beforeEach(function() { 105 | mediaFile.apiFramework = 'VPAID'; 106 | }); 107 | 108 | it("must return true if there is a supported VPAIDtech", function () { 109 | mediaFile.type = 'application/javascript'; // type of VPAIDHTML5Tech 110 | assert.isTrue(mediaFile.isSupported()); 111 | }); 112 | 113 | it("must return false if there is a supported VPAIDtech", function () { 114 | mediaFile.type = 'application/nonSupported'; 115 | assert.isFalse(mediaFile.isSupported()); 116 | }); 117 | }); 118 | 119 | describe("VAST mediafile", function() { 120 | 121 | beforeEach(function() { 122 | sinon.stub(vastUtil, 'isFlashSupported'); 123 | }); 124 | 125 | afterEach(function () { 126 | vastUtil.isFlashSupported.restore(); 127 | }); 128 | 129 | it("must return true if mime type is video/x-flv and flash is supported", function () { 130 | mediaFile.type = 'video/x-flv'; 131 | vastUtil.isFlashSupported.returns(true); 132 | assert.isFalse(mediaFile.isSupported()); 133 | }); 134 | 135 | it("must return false if mime type is video/x-flv and flash is not supported", function () { 136 | mediaFile.type = 'video/x-flv'; 137 | vastUtil.isFlashSupported.returns(false); 138 | assert.isFalse(mediaFile.isSupported()); 139 | }); 140 | 141 | it("must return true if mime type is not video/x-flv", function () { 142 | mediaFile.type = 'video/volkswagen'; 143 | vastUtil.isFlashSupported.returns(false); 144 | assert.isTrue(mediaFile.isSupported()); 145 | }); 146 | }); 147 | }); 148 | }); 149 | 150 | }); 151 | -------------------------------------------------------------------------------- /test/ads/vast/TrackingEvent.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe("TrackingEvent", function () { 4 | 5 | var TrackingEvent = require('ads/vast/TrackingEvent'); 6 | 7 | var xml = require('utils/xml'); 8 | 9 | var tracking, trackingXML; 10 | /*jshint maxlen: 500 */ 11 | beforeEach(function(){ 12 | trackingXML = '' + 13 | '' + 14 | '' + 15 | ''; 16 | tracking = new TrackingEvent(xml.toJXONTree(trackingXML)); 17 | }); 18 | 19 | it("must return an instance of Tracking", function () { 20 | assert.instanceOf(new TrackingEvent(xml.toJXONTree(trackingXML)), TrackingEvent); 21 | }); 22 | 23 | describe("name", function () { 24 | it("must contain the event set on the html", function () { 25 | assert.equal(tracking.name, 'firstQuartile'); 26 | }); 27 | }); 28 | 29 | describe("uri", function(){ 30 | it("must contain the tracking uri", function(){ 31 | /*jshint maxlen: 700 */ 32 | assert.equal(tracking.uri, 'http://t4.liverail.com/?metric=view25&pos=0&coid=135&pid=1331&nid=1331&oid=229&olid=2291331&cid=331&tpcid=&vid=&amid=&cc=default&pp=&vi=0&vv=&sg=&tsg=&pmu=0&pau=0&psz=0&ctx=&tctx=&coty=7&adt=0&did=&buid=&scen=&mca=&mma=&mct=0&url=http%3A%2F%2Fwww.iab.net%2Fguidelines%2F508676%2Fdigitalvideo%2Fvsuite%2Fvast%2Fvast_copy%2Fvast_xml_samples&trid=54afb53ccfddf1.12174006&bidf=0.10000&bids=0.00000&bidt=1&bidh=0&bidlaf=0&sdk=0&cb=8279.195.234.241.9.0&ver=1&w=&wy=&x=&y=&xy='); 33 | }); 34 | }); 35 | 36 | describe("offset", function(){ 37 | it("must not be added if the event is not progress", function(){ 38 | trackingXML = ''; 39 | tracking = new TrackingEvent(xml.toJXONTree(trackingXML)); 40 | assert.isUndefined(tracking.offset); 41 | }); 42 | 43 | it("must be added if the event progress and the attr is present and the duration is passed to the constructor", function(){ 44 | trackingXML = ''; 45 | tracking = new TrackingEvent(xml.toJXONTree(trackingXML), 100); 46 | assert.equal(tracking.offset, 10); 47 | }); 48 | }); 49 | }); -------------------------------------------------------------------------------- /test/ads/vast/VideoClicks.spec.js: -------------------------------------------------------------------------------- 1 | var VideoClicks = require('ads/vast/VideoClicks'); 2 | 3 | var xml = require('utils/xml'); 4 | 5 | describe("VideoClicks", function () { 6 | var videoClicks, videoClicksXML; 7 | 8 | beforeEach(function () { 9 | videoClicksXML = '' + 10 | '' + 11 | ''; 12 | videoClicks = new VideoClicks(xml.toJXONTree(videoClicksXML)); 13 | }); 14 | 15 | it("must return an instance of VideoClicks", function () { 16 | assert.instanceOf(new VideoClicks(videoClicksXML), VideoClicks); 17 | }); 18 | 19 | it("must set the clickThrough", function () { 20 | assert.equal(videoClicks.clickThrough, "http://www.target.com"); 21 | }); 22 | 23 | //Optional Elements 24 | describe("clickTrackings", function () { 25 | it("must be an array", function () { 26 | assert.isArray(videoClicks.clickTrackings); 27 | }); 28 | 29 | it("must be optional to have clickTrackings", function () { 30 | assert.equal(videoClicks.clickTrackings.length, 0); 31 | }); 32 | 33 | it("must contain all the clicktracking uris set on the xml", function () { 34 | var videoClicksXML = '' + 35 | '' + 36 | '' + 37 | '' + 38 | ''; 39 | 40 | videoClicks = new VideoClicks(xml.toJXONTree(videoClicksXML)); 41 | assert.equal(videoClicks.clickTrackings.length, 2); 42 | assert.equal(videoClicks.clickTrackings[0], 'http://www.tracking1.com'); 43 | assert.equal(videoClicks.clickTrackings[1], 'http://www.tracking2.com'); 44 | }); 45 | }); 46 | 47 | describe("customClicks", function () { 48 | it("must be an array", function () { 49 | assert.isArray(videoClicks.customClicks); 50 | }); 51 | 52 | it("must be optional", function () { 53 | assert.equal(videoClicks.customClicks.length, 0); 54 | }); 55 | 56 | it("must contain all the customClicks uris set on the xml", function () { 57 | var videoClicksXML = '' + 58 | '' + 59 | '' + 60 | '' + 61 | ''; 62 | 63 | videoClicks = new VideoClicks(xml.toJXONTree(videoClicksXML)); 64 | assert.equal(videoClicks.customClicks.length, 2); 65 | assert.equal(videoClicks.customClicks[0], 'http://www.tracking1.com'); 66 | assert.equal(videoClicks.customClicks[1], 'http://www.tracking2.com'); 67 | }); 68 | }); 69 | }); -------------------------------------------------------------------------------- /test/ads/vast/Wrapper.spec.js: -------------------------------------------------------------------------------- 1 | var Wrapper = require('ads/vast/Wrapper'); 2 | var Creative = require('ads/vast/Creative'); 3 | 4 | var xml = require('utils/xml'); 5 | 6 | describe("Wrapper", function () { 7 | it("must return an instance of Wrapper", function () { 8 | assert.instanceOf(new Wrapper(xml.toJXONTree('')), Wrapper); 9 | }); 10 | 11 | it("must set the adSystem", function () { 12 | var wrapperXML = 'GDFP'; 13 | var wrapper = new Wrapper(xml.toJXONTree(wrapperXML)); 14 | assert.equal('GDFP', wrapper.adSystem); 15 | }); 16 | 17 | describe("impressions", function () { 18 | it("must be an array", function () { 19 | var wrapper = new Wrapper(xml.toJXONTree('')); 20 | assert.isArray(wrapper.impressions); 21 | }); 22 | 23 | it("must be empty if there is no real impression", function () { 24 | var wrapperXML = ''; 25 | var wrapper = new Wrapper(xml.toJXONTree(wrapperXML)); 26 | assert.equal(wrapper.impressions.length, 0); 27 | }); 28 | 29 | it("must contain the defined impression", function () { 30 | var wrapperXML = '' + 31 | '' + 32 | '' + 33 | '' + 34 | ''; 35 | var wrapper = new Wrapper(xml.toJXONTree(wrapperXML)); 36 | 37 | assert.deepEqual(wrapper.impressions, [ 38 | 'http://ad.doubleclick.net/imp;v7;x;223626102;0-0;0;47414672;0/0;30477563/30495440/1;;~aopt=0/0/ff/0;~cs=j%3fhttp://s0.2mdn.net/dot.gif' 39 | ]); 40 | }); 41 | 42 | it("must be possible to contain more than one impression", function () { 43 | var wrapperXML = '' + 44 | '' + 45 | '' + 46 | '' + 47 | '' + 48 | '' + 49 | '' + 50 | '' + 51 | '' + 52 | '' + 53 | ''; 54 | var wrapper = new Wrapper(xml.toJXONTree(wrapperXML)); 55 | 56 | assert.deepEqual(wrapper.impressions, [ 57 | 'http://ad.doubleclick.net/imp;v7;x;223626102;0-0;0;47414672;0/0;30477563/30495440/1;;~aopt=0/0/ff/0;~cs=j%3fhttp://s0.2mdn.net/dot.gif', 58 | 'http://ad.doubleclick.net/ad/N270.Process_Other/B3473145;sz=1x1;ord=6212269?' 59 | ]); 60 | }); 61 | }); 62 | 63 | it("must set the VASTAdTagURI", function () { 64 | var wrapperXML = '' + 65 | '' + 66 | ''; 67 | var wrapper = new Wrapper(xml.toJXONTree(wrapperXML)); 68 | assert.equal('http://demo.tremormedia.com/proddev/vast/vast_inline_linear.xml', wrapper.VASTAdTagURI); 69 | }); 70 | 71 | it("must set the tracking error uri", function () { 72 | var wrapperXML = '' + 73 | '' + 74 | ''; 75 | var wrapper = new Wrapper(xml.toJXONTree(wrapperXML)); 76 | assert.equal(wrapper.errors[0], 'http://pubads.g.doubleclick.net/pagead/conversion/[ERRORCODE]'); 77 | }); 78 | 79 | describe("extensions", function () { 80 | it("must be undefined if not set", function () { 81 | var wrapper = new Wrapper(xml.toJXONTree('')); 82 | assert.isUndefined(wrapper.extensions); 83 | }); 84 | 85 | it("must be contain the JXONTree of the extension element if set", function () { 86 | var wrapperXML = 'price'; 87 | var wrapper = new Wrapper(xml.toJXONTree(wrapperXML)); 88 | 89 | assert.isDefined(wrapper.extensions); 90 | assert.deepEqual(wrapper.extensions, xml.toJXONTree(wrapperXML).extensions); 91 | }); 92 | }); 93 | 94 | describe("creatives", function () { 95 | it("must be an array or creatives", function () { 96 | var wrapperXML = '' + 97 | '' + 98 | '' + 99 | '' + 100 | '' + 101 | ''; 102 | var wrapper = new Wrapper(xml.toJXONTree(wrapperXML)); 103 | 104 | assert.isArray(wrapper.creatives); 105 | assert.instanceOf(wrapper.creatives[0], Creative); 106 | assert.equal(wrapper.creatives[0].id, 8454); 107 | assert.instanceOf(wrapper.creatives[1], Creative); 108 | assert.equal(wrapper.creatives[1].id, 8455); 109 | }); 110 | }); 111 | 112 | describe("followAdditionalWrappers", function () { 113 | it("must be true by default", function () { 114 | var wrapper = new Wrapper(xml.toJXONTree('')); 115 | assert.isTrue(wrapper.followAdditionalWrappers); 116 | }); 117 | 118 | it("must contain whatever the followAdditionalWrappers attr from the wrapper tag contain on the xml", function () { 119 | var wrapperXML = ''; 120 | var wrapper = new Wrapper(xml.toJXONTree(wrapperXML)); 121 | assert.isFalse(wrapper.followAdditionalWrappers); 122 | }); 123 | }); 124 | 125 | describe("allowMultipleAds", function () { 126 | it("must be set if the attr is present on the wrapper tag", function () { 127 | var wrapperXML = ''; 128 | var wrapper = new Wrapper(xml.toJXONTree(wrapperXML)); 129 | assert.isFalse(wrapper.allowMultipleAds); 130 | }); 131 | }); 132 | 133 | describe("fallbackOnNoAd", function () { 134 | it("must be set if the attr is present on the wrapper tag", function () { 135 | var wrapperXML = ''; 136 | var wrapper = new Wrapper(xml.toJXONTree(wrapperXML)); 137 | assert.isFalse(wrapper.fallbackOnNoAd); 138 | }); 139 | }); 140 | }); -------------------------------------------------------------------------------- /test/ads/vast/vastUtil.spec.js: -------------------------------------------------------------------------------- 1 | var vastUtil = require('ads/vast/vastUtil'); 2 | var VPAIDHTML5Tech = require('ads/vpaid/VPAIDHTML5Tech'); 3 | 4 | var xml = require('utils/xml'); 5 | 6 | var testUtils = require('../../test-utils'); 7 | 8 | describe("vastUtil", function () { 9 | it("must be an object", function () { 10 | assert.isObject(vastUtil); 11 | }); 12 | 13 | describe("_parseURLMacro", function () { 14 | var _parseURLMacro; 15 | 16 | beforeEach(function () { 17 | _parseURLMacro = vastUtil._parseURLMacro; 18 | }); 19 | 20 | it("must parse the passed macro using the passed variables", function () { 21 | assert.equal('http://foo.bar/BLA', _parseURLMacro('http://foo.bar/[CODE]', {CODE: 'BLA'})); 22 | assert.equal('http://foo.bar/BLA/123', _parseURLMacro('http://foo.bar/[CODE]/[END]', {CODE: 'BLA', END: 123})); 23 | }); 24 | }); 25 | 26 | describe("parseURLMacro", function () { 27 | var parseURLMacro; 28 | 29 | beforeEach(function () { 30 | parseURLMacro = vastUtil.parseURLMacro; 31 | }); 32 | 33 | it("must parse the passed macro and return the parsed url", function () { 34 | assert.equal( 35 | parseURLMacro('http://foo.bar/[CODE]/[END]', {CODE: 'BLA', END: 123}), 36 | 'http://foo.bar/BLA/123' 37 | ); 38 | }); 39 | 40 | it("must auto generate CACHEBUSTING variable if not passed ", function () { 41 | assert.match(parseURLMacro('http://foo.bar/[CACHEBUSTING]'), /http:\/\/foo\.bar\/\d+/); 42 | assert.equal(parseURLMacro('http://foo.bar/[CACHEBUSTING]/[CACHEBUSTING]', {CACHEBUSTING: 123}), 'http://foo.bar/123/123'); 43 | }); 44 | 45 | }); 46 | 47 | describe("parseURLMacros", function () { 48 | var parseURLMacros; 49 | 50 | beforeEach(function () { 51 | parseURLMacros = vastUtil.parseURLMacros; 52 | }); 53 | 54 | it("must be function", function () { 55 | assert.isFunction(vastUtil.parseURLMacros); 56 | }); 57 | 58 | it("must parse an array of macros and return an array with the parsed urls", function () { 59 | var macros = [ 60 | 'http://foo.bar/[CODE]', 61 | 'http://foo.bar/[CODE]/[END]' 62 | ]; 63 | 64 | assert.deepEqual(parseURLMacros(macros, {CODE: 'BLA', END: 123}), [ 65 | 'http://foo.bar/BLA', 66 | 'http://foo.bar/BLA/123' 67 | ]); 68 | }); 69 | 70 | it("must auto generate CACHEBUSTING variable if not passed ", function () { 71 | var macros = ['http://foo.bar/[CACHEBUSTING]']; 72 | assert.match(parseURLMacros(macros), /http:\/\/foo\.bar\/\d+/); 73 | assert.deepEqual(parseURLMacros(macros, {CACHEBUSTING: 123}), ['http://foo.bar/123']); 74 | }); 75 | }); 76 | 77 | describe("track", function () { 78 | var track; 79 | 80 | beforeEach(function () { 81 | track = vastUtil.track; 82 | }); 83 | 84 | it("must return an array with the created track images", function () { 85 | var macros = [ 86 | 'http://foo.bar/[CODE]', 87 | 'http://foo.bar/[CODE]/[END]' 88 | ]; 89 | var trackImgs = track(macros, {CODE: 'BLA', END: 123}); 90 | 91 | assert.equal(trackImgs.length, 2); 92 | assert.instanceOf(trackImgs[0], Image); 93 | assert.equal(trackImgs[0].src, "http://foo.bar/BLA"); 94 | assert.instanceOf(trackImgs[1], Image); 95 | assert.equal(trackImgs[1].src, "http://foo.bar/BLA/123"); 96 | }); 97 | }); 98 | 99 | describe("parseDuration", function () { 100 | var parseDuration; 101 | 102 | beforeEach(function () { 103 | parseDuration = vastUtil.parseDuration; 104 | }); 105 | 106 | it("must return null if the duration is not an string with the format HH:MM:SS[.mmm]", function () { 107 | assert.isNull(parseDuration()); 108 | assert.isNull(parseDuration(123)); 109 | assert.isNull(parseDuration('23:444:23')); 110 | assert.isNull(parseDuration('foo')); 111 | }); 112 | 113 | it("must return the duration in milliseconds", function () { 114 | assert.equal(parseDuration('00:00:00.001'), 1); 115 | assert.equal(parseDuration('00:00:01'), 1000); 116 | assert.equal(parseDuration('00:01:00'), 60000); 117 | assert.equal(parseDuration('01:00:00'), 3600000); 118 | assert.equal(parseDuration('01:11:09:456'), 4269000); 119 | }); 120 | }); 121 | 122 | describe("parseImpressions", function () { 123 | var parseImpressions; 124 | 125 | beforeEach(function () { 126 | parseImpressions = vastUtil.parseImpressions; 127 | }); 128 | 129 | it("must return an empty array if you pass no impressions", function () { 130 | testUtils.assertEmptyArray(parseImpressions()); 131 | }); 132 | 133 | it("must return an empty array if there is no real impression", function () { 134 | var inlineXML = '' + 135 | '' + 136 | ''; 137 | testUtils.assertEmptyArray(parseImpressions(xml.toJXONTree(inlineXML).impression)); 138 | }); 139 | 140 | it("must return an array with the passed impressions formatted", function () { 141 | var inlineXML = '' + 142 | '' + 143 | '' + 144 | '' + 145 | ''; 146 | var impressionJTree = xml.toJXONTree(inlineXML).impression; 147 | 148 | assert.deepEqual(parseImpressions(impressionJTree), [ 149 | 'http://ad.doubleclick.net/imp;v7;x;223626102;0-0;0;47414672;0/0;30477563/30495440/1;;~aopt=0/0/ff/0;~cs=j%3fhttp://s0.2mdn.net/dot.gif' 150 | ]); 151 | }); 152 | 153 | it("must add all the passed impressions to the returned array", function () { 154 | var inlineXML = '' + 155 | '' + 156 | '' + 157 | '' + 158 | '' + 159 | '' + 160 | ''; 161 | var impressionJTree = xml.toJXONTree(inlineXML).impression; 162 | 163 | assert.deepEqual(parseImpressions(impressionJTree), [ 164 | 'http://ad.doubleclick.net/imp;v7;x;223626102;0-0;0;47414672;0/0;30477563/30495440/1;;~aopt=0/0/ff/0;~cs=j%3fhttp://s0.2mdn.net/dot.gif', 165 | 'http://ad.doubleclick.net/ad/N270.Process_Other/B3473145;sz=1x1;ord=6212269?' 166 | ]); 167 | }); 168 | }); 169 | 170 | describe("formatProgress", function () { 171 | it("must return the formatted progress", function () { 172 | assert.equal(vastUtil.formatProgress(12345000), "03:25:45.000"); 173 | assert.equal(vastUtil.formatProgress(123000), "00:02:03.000"); 174 | assert.equal(vastUtil.formatProgress(123545978), "34:19:05.978"); 175 | }); 176 | }); 177 | 178 | describe("parseOffset", function () { 179 | var parseOffset; 180 | 181 | beforeEach(function () { 182 | parseOffset = vastUtil.parseOffset; 183 | }); 184 | 185 | it("must return the passed offset string in ms", function () { 186 | assert.equal(parseOffset('00:00:05.000'), 5000); 187 | }); 188 | 189 | it("must be possible pass the offset as a percentage", function () { 190 | assert.equal(parseOffset('10%', 1000), 100); 191 | assert.equal(parseOffset('10.5%', 1000), 105); 192 | }); 193 | 194 | it("with a percentage offset and no duration must return null", function () { 195 | assert.isNull(parseOffset('10.5%')); 196 | }); 197 | 198 | it("must return null if you don't pass an offset", function () { 199 | assert.isNull(parseOffset()); 200 | assert.isNull(parseOffset(undefined, 123)); 201 | }); 202 | }); 203 | 204 | describe("isVPAID", function () { 205 | it("must return true if the passed mediaFile apiFramework attr is VPAID and false otherwiser", function () { 206 | assert.isFunction(vastUtil.isVPAID); 207 | [undefined, false, '', {}, []].forEach(function (wrongMediaFile) { 208 | assert.isFalse(vastUtil.isVPAID(wrongMediaFile)); 209 | }); 210 | 211 | assert.isFalse(vastUtil.isVPAID({apiFramework: 'JS'})); 212 | assert.isTrue(vastUtil.isVPAID({apiFramework: 'VPAID'})); 213 | }); 214 | }); 215 | 216 | describe("findSupportedVPAIDTech", function () { 217 | var HTML5_APP_MIME = 'application/javascript'; 218 | 219 | beforeEach(function () { 220 | sinon.stub(VPAIDHTML5Tech, 'supports'); 221 | }); 222 | 223 | afterEach(function () { 224 | VPAIDHTML5Tech.supports.restore(); 225 | }); 226 | 227 | it("must return HTML tech if it supports the passed mime type", function () { 228 | VPAIDHTML5Tech.supports.returns(true); 229 | assert.equal(vastUtil.findSupportedVPAIDTech(HTML5_APP_MIME), VPAIDHTML5Tech); 230 | }); 231 | 232 | it("must return null if no supported tech is found", function () { 233 | VPAIDHTML5Tech.supports.returns(false); 234 | assert.isNull(vastUtil.findSupportedVPAIDTech(HTML5_APP_MIME)); 235 | }); 236 | }); 237 | }); 238 | -------------------------------------------------------------------------------- /test/ads/vpaid/VPAIDHTML5Tech.spec.js: -------------------------------------------------------------------------------- 1 | var VPAIDHTML5Tech = require('ads/vpaid/VPAIDHTML5Tech'); 2 | var VASTError = require('ads/vast/VASTError'); 3 | 4 | var dom = require('utils/dom'); 5 | var utilities = require('utils/utilityFunctions'); 6 | 7 | var VAST_ERROR_PREFIX = 'VAST Error: '; 8 | 9 | describe("VPAIDHTML5Tech", function() { 10 | it("must return an instance of itself", function() { 11 | assert.instanceOf(new VPAIDHTML5Tech({src: 'fakeSource'}), VPAIDHTML5Tech); 12 | }); 13 | 14 | it("must implement supports", function () { 15 | sinon.stub(utilities, 'isOldIE').returns(false); 16 | assert.isFunction(VPAIDHTML5Tech.supports); 17 | assert(!VPAIDHTML5Tech.supports('application/x-shockwave-flash')); 18 | assert(VPAIDHTML5Tech.supports('application/javascript')); 19 | 20 | //Must return false for old IE (IE9 and below) 21 | utilities.isOldIE.returns(9); 22 | assert(!VPAIDHTML5Tech.supports('application/javascript')); 23 | 24 | utilities.isOldIE.restore(); 25 | }); 26 | 27 | it("must complain if you don't pass a valid media file", function () { 28 | [undefined, null, {}, []].forEach(function(invalidMediaFile) { 29 | assert.throws(function () { 30 | /*jshint unused:false*/ 31 | var vpaidTech = new VPAIDHTML5Tech(invalidMediaFile); 32 | }, VASTError, VAST_ERROR_PREFIX + VPAIDHTML5Tech.INVALID_MEDIA_FILE); 33 | }); 34 | }); 35 | 36 | describe("instance", function() { 37 | var vpaidTech, testDiv, testVideo; 38 | 39 | beforeEach(function () { 40 | vpaidTech = new VPAIDHTML5Tech({src: 'http://fake.mediaFile.url'}); 41 | testDiv = document.createElement("div"); 42 | testVideo = document.createElement("video"); 43 | document.body.appendChild(testDiv); 44 | }); 45 | 46 | afterEach(function () { 47 | dom.remove(testDiv); 48 | dom.remove(testVideo); 49 | }); 50 | 51 | 52 | it("must publish the name of the tech", function(){ 53 | assert.equal(vpaidTech.name, 'vpaid-html5'); 54 | }); 55 | 56 | describe("loadAdUnit", function() { 57 | it("must throw a VASTError if you don't pass a valid dom Element to contain the ad", function() { 58 | [undefined, null, {}, [], 123].forEach(function (invalidDOMElement) { 59 | assert.throws(function (){ 60 | vpaidTech.loadAdUnit(invalidDOMElement); 61 | }, VASTError, VAST_ERROR_PREFIX + VPAIDHTML5Tech.INVALID_DOM_CONTAINER_EL); 62 | }); 63 | }); 64 | 65 | it("must throw a VASTError if you don't pass a valid vide Element to contain the video ad", function() { 66 | [undefined, null, {}, [], 123].forEach(function (invalidDOMElement) { 67 | assert.throws(function (){ 68 | vpaidTech.loadAdUnit(testDiv, invalidDOMElement); 69 | }, VASTError, VAST_ERROR_PREFIX + VPAIDHTML5Tech.INVALID_DOM_CONTAINER_EL); 70 | }); 71 | }); 72 | 73 | it("must throw a VASTError if you don't pass a callback to call once the ad have been loaded", function () { 74 | [undefined, null, {}, 123].forEach(function(invalidCallback) { 75 | assert.throws(function () { 76 | vpaidTech.loadAdUnit(testDiv, testVideo, invalidCallback); 77 | }, VASTError, VAST_ERROR_PREFIX + VPAIDHTML5Tech.MISSING_CALLBACK); 78 | }); 79 | }); 80 | 81 | it("must not throw an error if pass valid arguments", function(){ 82 | assert.doesNotThrow(function () { 83 | vpaidTech.loadAdUnit(testDiv, testVideo, utilities.noop); 84 | }); 85 | }); 86 | 87 | it("must set properties into vpaidTech", function () { 88 | assert.isNull(vpaidTech.containerEl); 89 | assert.isNull(vpaidTech.vpaidHTMLClient); 90 | 91 | vpaidTech.loadAdUnit(testDiv, testVideo, utilities.noop); 92 | 93 | assert.equal(vpaidTech.containerEl, testDiv); 94 | assert.equal(vpaidTech.videoEl, testVideo); 95 | assert.instanceOf(vpaidTech.vpaidHTMLClient, VPAIDHTML5Tech.VPAIDHTML5Client); 96 | }); 97 | 98 | }); 99 | 100 | describe("unloadAdUnit", function() { 101 | it("must do nothing if there is no loaded adUnit", function() { 102 | assert.doesNotThrow(function() { 103 | vpaidTech.unloadAdUnit(); 104 | }); 105 | }); 106 | 107 | it("must unload the adUnit", function() { 108 | vpaidTech.loadAdUnit(testDiv, testVideo, utilities.noop); 109 | 110 | var vpaidClient = vpaidTech.vpaidHTMLClient; 111 | vpaidClient.destroy = sinon.spy(); 112 | 113 | vpaidTech.unloadAdUnit(); 114 | 115 | assert(vpaidClient.destroy.calledOnce); 116 | }); 117 | 118 | it("must remove the containerEl", function() { 119 | sinon.stub(dom, 'remove'); 120 | vpaidTech.loadAdUnit(testDiv, testVideo, utilities.noop); 121 | 122 | vpaidTech.vpaidHTMLClient.destroy = utilities.noop; 123 | vpaidTech.unloadAdUnit(); 124 | 125 | assert(dom.remove.calledWithExactly(testDiv)); 126 | dom.remove.restore(); 127 | }); 128 | 129 | it("must set instance properties: to null", function() { 130 | vpaidTech.loadAdUnit(testDiv, testVideo, utilities.noop); 131 | 132 | vpaidTech.unloadAdUnit(); 133 | 134 | assert.isNull(vpaidTech.vpaidHTMLClient); 135 | assert.isNull(vpaidTech.containerEl); 136 | }); 137 | }); 138 | }); 139 | }); 140 | 141 | -------------------------------------------------------------------------------- /test/test-utils.css: -------------------------------------------------------------------------------- 1 | .relative-pos { 2 | position: relative; 3 | top: 10px; 4 | left: 10px; 5 | } 6 | 7 | .absolute-pos { 8 | position: absolute; 9 | top: 10px; 10 | left: 10px; 11 | } 12 | 13 | .ten-px-square { 14 | width: 10px; 15 | height: 10px; 16 | padding: 5px; 17 | margin: 5px; 18 | border: 5px solid black; 19 | } 20 | 21 | div.vjs-test-player { 22 | width: 720px !important; 23 | height: 480px !important; 24 | display: block; 25 | } -------------------------------------------------------------------------------- /test/test-utils.js: -------------------------------------------------------------------------------- 1 | var utilities = require('utils/utilityFunctions'); 2 | 3 | function createMouseEvent(type) { 4 | var event = document.createEvent('MouseEvents'); 5 | event.initEvent(type, true, false); 6 | return event; 7 | } 8 | 9 | function click(element) { 10 | if(element.click) { 11 | return element.click(); 12 | } 13 | return element.onclick(createMouseEvent('click')); 14 | } 15 | 16 | function assertEmptyArray(array) { 17 | assert.isArray(array); 18 | assert.equal(0, array.length, "The passed array should be empty"); 19 | } 20 | 21 | function stubAsyncStep(context, method, clock) { 22 | var stub = sinon.stub(context, method); 23 | function tick(millis) { 24 | if(clock) { 25 | clock.tick(millis || 1); 26 | } 27 | } 28 | return { 29 | flush: function() { 30 | var args = utilities.arrayLikeObjToArray(arguments); 31 | var cb = lastArg(stub); 32 | cb.apply(null, args); 33 | tick(); 34 | }, 35 | stub: function() { 36 | return stub; 37 | } 38 | }; 39 | 40 | } 41 | 42 | function isChrome() { 43 | return !!navigator.userAgent.match(/chrome/i); 44 | } 45 | function isFF() { 46 | return !!navigator.userAgent.match(/firefox/i); 47 | } 48 | 49 | function firstArg(spy) { 50 | return spy.lastCall.args[0]; 51 | } 52 | 53 | function secondArg(spy) { 54 | return spy.lastCall.args[1]; 55 | } 56 | 57 | function thirdArg(spy) { 58 | return spy.lastCall.args[2]; 59 | } 60 | 61 | function fourthArg(spy) { 62 | return spy.lastCall.args[3]; 63 | } 64 | 65 | function lastArg(spy) { 66 | return spy.lastCall.args[spy.lastCall.args.length - 1]; 67 | } 68 | 69 | function namespace(namespaceString) { 70 | var parts = namespaceString.split('.'), 71 | parent = window, 72 | currentPart = ''; 73 | 74 | for (var i = 0, length = parts.length; i < length; i++) { 75 | currentPart = parts[i]; 76 | parent[currentPart] = parent[currentPart] || {}; 77 | parent = parent[currentPart]; 78 | } 79 | 80 | return parent; 81 | } 82 | 83 | function spyOn(namespaceString, method) { 84 | var parent = namespace(namespaceString); 85 | if (parent[method]) { 86 | sinon.spy(parent, method); 87 | } else { 88 | parent[method] = sinon.spy(); 89 | } 90 | return parent[method]; 91 | } 92 | 93 | //testDiv.querySelector('#videoEl1') 94 | function queryById(el, id) { 95 | return el.querySelector('#' + id); 96 | } 97 | 98 | 99 | function getCompByName(comp, name) { 100 | return utilities.treeSearch(comp, function (comp) { 101 | return comp.children(); 102 | }, function (comp) { 103 | return comp.name() === name; 104 | }); 105 | } 106 | 107 | function getCompByFactory(comp, factory) { 108 | return utilities.treeSearch(comp, function (comp) { 109 | return comp.children(); 110 | }, function (comp) { 111 | return comp instanceof factory; 112 | }); 113 | } 114 | 115 | function isCompVisible(comp) { 116 | return window.getComputedStyle(comp.el()).display !== "none"; 117 | } 118 | 119 | function muteVideoJSErrorLogs() { 120 | var patterns = muteVideoJSErrorLogs.IGNORED_PATTERNS; 121 | 122 | ['log', 'error', 'warn'].forEach(function (logFnName) { 123 | var log = console && console[logFnName]; 124 | if (log) { 125 | console[logFnName] = function (msg) { 126 | if (canBeLogged(msg)) { 127 | log.apply(console, arguments); 128 | } 129 | }; 130 | } 131 | }); 132 | 133 | /*** local functions ***/ 134 | function canBeLogged(msg) { 135 | var i, len, pattern; 136 | for (i = 0, len = patterns.length; i < len; i++) { 137 | pattern = patterns[i]; 138 | 139 | if (pattern instanceof RegExp && pattern.test(msg)) { 140 | return false; 141 | } else if (utilities.isString(pattern) && pattern === msg) { 142 | return false; 143 | } 144 | } 145 | 146 | return true; 147 | } 148 | } 149 | 150 | muteVideoJSErrorLogs.IGNORED_PATTERNS = [ 151 | 'VIDEOJS:', 152 | 'AD ERROR:', 153 | /^Video Player Setup Error/gm 154 | ]; 155 | 156 | muteVideoJSErrorLogs(); 157 | 158 | module.exports = { 159 | createMouseEvent: createMouseEvent, 160 | click: click, 161 | assertEmptyArray: assertEmptyArray, 162 | stubAsyncStep: stubAsyncStep, 163 | isChrome: isChrome, 164 | isFF: isFF, 165 | firstArg: firstArg, 166 | secondArg: secondArg, 167 | thirdArg: thirdArg, 168 | fourthArg: fourthArg, 169 | lastArg: lastArg, 170 | namespace: namespace, 171 | spyOn: spyOn, 172 | queryById: queryById, 173 | getCompByName: getCompByName, 174 | getCompByFactory: getCompByFactory, 175 | isCompVisible: isCompVisible, 176 | muteVideoJSErrorLogs: muteVideoJSErrorLogs 177 | }; -------------------------------------------------------------------------------- /test/utils/async.spec.js: -------------------------------------------------------------------------------- 1 | var async = require('utils/async'); 2 | var utilities = require('utils/utilityFunctions'); 3 | 4 | describe("async", function () { 5 | it("must be an object", function () { 6 | assert.isObject(async); 7 | }); 8 | 9 | describe("setImmediate", function () { 10 | beforeEach(function () { 11 | this.clock = sinon.useFakeTimers(); 12 | }); 13 | 14 | afterEach(function () { 15 | this.clock.restore(); 16 | }); 17 | 18 | it("must call the passed function asynchronously", function () { 19 | var spy = sinon.spy(); 20 | async.setImmediate(spy); 21 | sinon.assert.notCalled(spy); 22 | this.clock.tick(); 23 | sinon.assert.calledOnce(spy); 24 | }); 25 | }); 26 | 27 | describe("iterator", function () { 28 | var task1, task2, task3; 29 | 30 | beforeEach(function () { 31 | task1 = sinon.spy(); 32 | task2 = sinon.spy(); 33 | task3 = sinon.spy(); 34 | }); 35 | 36 | it("must return an iterator that iterates over the array of tasks", function () { 37 | var next = async.iterator([task1, task2, task3]); 38 | while (next !== null) { 39 | next = next(); 40 | } 41 | sinon.assert.callOrder(task1, task2, task3); 42 | }); 43 | 44 | it("must be possible to pass args to the tasks", function () { 45 | var tasks = [task1, task2, task3]; 46 | var next = async.iterator(tasks); 47 | while (next !== null) { 48 | next = next('foo', 'bar'); 49 | } 50 | tasks.forEach(function (task) { 51 | sinon.assert.calledWithExactly(task, 'foo', 'bar'); 52 | }); 53 | }); 54 | }); 55 | 56 | describe("waterfall", function () { 57 | it("must pass an error to the callback if you don't pass an array for tasks", function () { 58 | var callback = sinon.spy(); 59 | async.waterfall(null, callback); 60 | var error = callback.lastCall.args[0]; 61 | assert.instanceOf(error, Error); 62 | assert.equal('First argument to waterfall must be an array of functions', error.message); 63 | }); 64 | 65 | it("must call the callback with no args if the task array is empty", function () { 66 | var callback = sinon.spy(); 67 | async.waterfall([], callback); 68 | assert.equal(callback.lastCall.args.length, 0); 69 | }); 70 | 71 | describe("given an array of tasks", function () { 72 | var task1, task2, task3, callback; 73 | 74 | beforeEach(function () { 75 | this.clock = sinon.useFakeTimers(); 76 | task1 = sinon.spy(); 77 | task2 = sinon.spy(); 78 | task3 = sinon.spy(); 79 | callback = sinon.spy(); 80 | }); 81 | 82 | afterEach(function(){ 83 | this.clock.restore(); 84 | }); 85 | 86 | it("must call the all the tasks passing the arguments of the previous one to the next task", function () { 87 | var spy = sinon.spy(); 88 | 89 | function startCounter(next) { 90 | next(null, 0); 91 | } 92 | 93 | function increaseCounter(counter, next) { 94 | next(null, counter + 1); 95 | } 96 | 97 | async.waterfall([ 98 | startCounter, 99 | increaseCounter, 100 | increaseCounter, 101 | increaseCounter 102 | ], function (error, counter) { 103 | assert.isNull(error); 104 | assert.equal(3, counter); 105 | spy(); 106 | }); 107 | 108 | this.clock.tick(5); 109 | sinon.assert.calledOnce(spy); 110 | }); 111 | 112 | it("must call the callback if an error occur on one of the task", function () { 113 | var spy = sinon.spy(); 114 | 115 | function startCounter(next) { 116 | next(null, 0); 117 | } 118 | 119 | function increaseCounter(counter, next) { 120 | next(null, counter + 1); 121 | } 122 | 123 | function throwError(counter, next) { 124 | next(new Error(), counter); 125 | } 126 | 127 | async.waterfall([ 128 | startCounter, 129 | increaseCounter, 130 | increaseCounter, 131 | throwError, 132 | increaseCounter 133 | ], function (error, counter) { 134 | assert.instanceOf(error, Error); 135 | assert.equal(2, counter); 136 | spy(); 137 | }); 138 | 139 | this.clock.tick(5); 140 | sinon.assert.calledOnce(spy); 141 | }); 142 | }); 143 | }); 144 | 145 | describe("when", function () { 146 | it("must return a function", function () { 147 | assert.isFunction(async.when(false, utilities.noop)); 148 | }); 149 | 150 | it("must throw an exception if the second argument is not a callback", function () { 151 | assert.throws(function () { 152 | async.when(); 153 | }, Error, 'async.when error: missing callback argument'); 154 | }); 155 | 156 | describe("with truthy condition", function () { 157 | it("must call the callback with the same arguments that where passed to the return if function", function () { 158 | var spy = sinon.spy(); 159 | var ifFn = async.when(true, spy); 160 | 161 | ifFn("1", 2, null, utilities.noop); 162 | sinon.assert.calledWithExactly(spy, "1", 2, null, utilities.noop); 163 | }); 164 | }); 165 | 166 | describe("with falsy condition,", function () { 167 | it("must not call the callback and call the next callback passed passing null (for no error) and the rest of the args", function () { 168 | var callback = sinon.spy(); 169 | var next = sinon.spy(); 170 | var ifFn = async.when(false, callback); 171 | 172 | ifFn("1", 2, null, next); 173 | sinon.assert.notCalled(callback); 174 | sinon.assert.calledWithExactly(next, null, "1", 2, null); 175 | }); 176 | }); 177 | 178 | describe("with conditional function,", function () { 179 | it("must use whatever the condition return to decide weather to execute the condition or not.", function () { 180 | var callback = sinon.spy(); 181 | var next = sinon.spy(); 182 | var ifFn = async.when(function () { 183 | return false; 184 | }, callback); 185 | 186 | ifFn("1", 2, null, next); 187 | sinon.assert.notCalled(callback); 188 | sinon.assert.calledWithExactly(next, null, "1", 2, null); 189 | }); 190 | 191 | it("must pass the arguments to the conditional function", function(){ 192 | var callback = sinon.spy(); 193 | var next = sinon.spy(); 194 | var condition = sinon.stub(); 195 | var ifFn = async.when(condition, callback); 196 | condition.returns(true); 197 | 198 | ifFn("1", 2, null, next); 199 | 200 | sinon.assert.notCalled(next); 201 | sinon.assert.calledWithExactly(condition, "1", 2, null); 202 | sinon.assert.calledWithExactly(callback, "1", 2, null, next); 203 | }); 204 | }); 205 | }); 206 | }); -------------------------------------------------------------------------------- /test/utils/urlUtils.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var urlUtils = require('utils/urlUtils'); 4 | 5 | describe('urlUtils.urlParts', function () { 6 | it('must normalize a relative url', function () { 7 | assert.match(urlUtils.urlParts("foo").href, /^https?:\/\/[^/]+\/foo$/); 8 | assert.match(urlUtils.urlParts("foo?name=carlos").href, /^https?:\/\/[^/]+\/foo\?name=carlos$/); 9 | }); 10 | 11 | it("must not do anything on a normalized url", function () { 12 | assert.equal(urlUtils.urlParts("http://www.google.com").href, 'http://www.google.com/'); 13 | }); 14 | 15 | it('must parse relative URL into component pieces', function () { 16 | var parsed = urlUtils.urlParts("foo"); 17 | assert.match(parsed.href, /https?:\/\//); 18 | assert.match(parsed.protocol, /^https?/); 19 | assert.notEqual(parsed.host, ""); 20 | assert.notEqual(parsed.hostname, ""); 21 | assert.notEqual(parsed.pathname, ""); 22 | }); 23 | 24 | describe("dictionary property", function () { 25 | describe("protocol", function () { 26 | it("must contain the protocol of the normalized url", function () { 27 | assert.match(urlUtils.urlParts("http://foo").protocol, /^http/); 28 | assert.match(urlUtils.urlParts("https://foo.com").protocol, /^https/); 29 | }); 30 | }); 31 | 32 | describe("host", function () { 33 | it("must contain the host", function () { 34 | assert.equal(urlUtils.urlParts('http://www.google.com:8080').host, 'www.google.com:8080'); 35 | assert.match(urlUtils.urlParts('http://gmail.com').host, /gmail\.com(\:80)?/g); 36 | }); 37 | }); 38 | 39 | describe("search", function () { 40 | it("must contain the search part of the url without the ?", function () { 41 | assert.equal(urlUtils.urlParts('http://www.google.com').search, ''); 42 | assert.equal(urlUtils.urlParts('http://www.google.com?name=Carlos').search, 'name=Carlos'); 43 | assert.equal(urlUtils.urlParts('http://www.google.com?name=Carlos&surname=Serrano').search, 'name=Carlos&surname=Serrano'); 44 | }); 45 | }); 46 | 47 | describe("hash", function () { 48 | it("must return the hash part of a url without the #", function () { 49 | assert.equal(urlUtils.urlParts('http://www.google.com').hash, ''); 50 | assert.equal(urlUtils.urlParts('http://google.com#nose').hash, 'nose'); 51 | }); 52 | }); 53 | 54 | describe("hostName", function () { 55 | it("must return the hostname of the url", function () { 56 | assert.equal(urlUtils.urlParts('http://www.google.com:8080').hostname, 'www.google.com'); 57 | assert.equal(urlUtils.urlParts('http://gmail.com').hostname, 'gmail.com'); 58 | }); 59 | }); 60 | 61 | describe("port", function () { 62 | it("must return the port of the url if specified", function () { 63 | assert.equal(urlUtils.urlParts('http://www.google.com').port, '80'); 64 | assert.equal(urlUtils.urlParts('http://www.google.com:8080').port, '8080'); 65 | }); 66 | }); 67 | 68 | describe("pathName", function () { 69 | it("must return the pathname of the url", function () { 70 | assert.equal(urlUtils.urlParts('http://www.google.com/example/demo.html').pathname, '/example/demo.html'); 71 | assert.equal(urlUtils.urlParts('http://www.google.com').pathname, '/'); 72 | assert.equal(urlUtils.urlParts('/').pathname, '/'); 73 | assert.equal(urlUtils.urlParts('http://www.google.com/index.html').pathname, '/index.html'); 74 | }); 75 | }); 76 | }); 77 | }); 78 | 79 | describe("urlUtils.queryStringToObj", function () { 80 | it("must transform the passed query string into a dictionary/map", function () { 81 | assert.deepEqual(urlUtils.queryStringToObj('?name=carlos&surname=serrano'), {name: 'carlos', surname: 'serrano'}); 82 | assert.deepEqual(urlUtils.queryStringToObj('name=carlos&surname=serrano'), {name: 'carlos', surname: 'serrano'}); 83 | assert.deepEqual(urlUtils.queryStringToObj(' ?name=carlos '), {name: 'carlos'}); 84 | assert.deepEqual(urlUtils.queryStringToObj(' ?name='), {name: ''}); 85 | assert.deepEqual(urlUtils.queryStringToObj(''), {}); 86 | }); 87 | 88 | it("must be possible to conditionally decide if a key value pair gets added to the resulting object", function(){ 89 | function isNotSerrano(key, value) { 90 | return !(key === 'surname' && value === 'serrano'); 91 | } 92 | assert.deepEqual(urlUtils.queryStringToObj('name=carlos&surname=serrano', isNotSerrano), { name: 'carlos'}); 93 | }); 94 | }); 95 | 96 | describe("urlUtils.objToQueryString", function () { 97 | it("must transform the passed object into a query string ", function () { 98 | assert.deepEqual(urlUtils.objToQueryString({name: 'carlos', surname: 'serrano'}), 'name=carlos&surname=serrano'); 99 | assert.deepEqual(urlUtils.objToQueryString({name: 'carlos'}), 'name=carlos'); 100 | assert.deepEqual(urlUtils.objToQueryString({}), ''); 101 | }); 102 | }); -------------------------------------------------------------------------------- /test/utils/xml/xml.spec.js: -------------------------------------------------------------------------------- 1 | var xml = require('utils/xml'); 2 | 3 | describe("xml", function () { 4 | it("must be an object", function () { 5 | assert.isObject(xml); 6 | }); 7 | 8 | describe("strToXMLDoc", function(){ 9 | it("must throw an exception if it encounters a problem parsing the xml", function(){ 10 | assert.throws(function () { 11 | xml.strToXMLDoc(''); 12 | }, Error, "Error parsing the string: \"\""); 13 | 14 | assert.throws(function () { 15 | xml.strToXMLDoc(); 16 | }, Error, "Error parsing the string: \"undefined\""); 17 | 18 | assert.throws(function () { 19 | xml.strToXMLDoc({}); 20 | }, Error, "Error parsing the string: \"[object Object]\""); 21 | }); 22 | 23 | it("must, given an xml string, return an equivalent XML document object", function(){ 24 | var doc = xml.strToXMLDoc('John Smith'); 25 | var employeeNode; 26 | assert.isTrue(doc.hasChildNodes()); 27 | assert.equal(doc.childNodes.length, 1); 28 | 29 | employeeNode = doc.childNodes.item(0); 30 | assert.equal(employeeNode.nodeName.toLowerCase(), 'employee'); 31 | assert.isTrue(employeeNode.hasAttributes()); 32 | assert.equal(employeeNode.attributes.length, 1); 33 | assert.equal(employeeNode.attributes.item(0).name,'type'); 34 | assert.equal(employeeNode.attributes.item(0).value,'usher'); 35 | assert.isTrue(employeeNode.hasChildNodes()); 36 | assert.equal(employeeNode.childNodes.length, 1); 37 | assert.isFalse(employeeNode.childNodes.item(0).hasChildNodes()); 38 | assert.equal(employeeNode.childNodes.item(0).data, 'John Smith'); 39 | }); 40 | }); 41 | 42 | describe("parseText", function () { 43 | it("must return null if you pass an empty string or a string full of spaces", function () { 44 | assert.equal(xml.parseText(''), null); 45 | assert.equal(xml.parseText(' '), null); 46 | }); 47 | 48 | it("must return true if you pass 'true' or 'TRUE'", function () { 49 | assert.equal(xml.parseText('true'), true); 50 | assert.equal(xml.parseText('TRUE'), true); 51 | }); 52 | 53 | it("must return false if you pass 'false' or 'FALSE'", function () { 54 | assert.equal(xml.parseText('false'), false); 55 | assert.equal(xml.parseText('FALSE'), false); 56 | }); 57 | 58 | it("must return a number if you pass a number string", function () { 59 | assert.equal(xml.parseText('123'), 123); 60 | assert.equal(xml.parseText('123.123'), 123.123); 61 | }); 62 | 63 | it("must return a date if you pass an ISO 8601 date", function () { 64 | var now = new Date(); 65 | assert.equal(xml.parseText(now.toISOString()).getTime(), now.getTime()); 66 | }); 67 | 68 | //Regression text 69 | it("must return a string if you pass a percentage", function(){ 70 | assert.equal(xml.parseText('10%'), '10%'); 71 | }); 72 | }); 73 | 74 | describe("JXONTree", function(){ 75 | 76 | it("must be a function constructor function", function(){ 77 | var xmlDoc = xml.strToXMLDoc(''); 78 | assert.isFunction(xml.JXONTree); 79 | assert.instanceOf(new xml.JXONTree(xmlDoc), xml.JXONTree); 80 | }); 81 | 82 | describe("on text node type", function(){ 83 | var jxonTree; 84 | 85 | beforeEach(function(){ 86 | jxonTree = xml.toJXONTree(''); 87 | }); 88 | 89 | it("must publish its value in the 'keyValue' field", function(){ 90 | assert.equal(jxonTree.keyValue, 'QWZ5671'); 91 | }); 92 | 93 | it("must publish its attributs with using '@' as a prefix", function(){ 94 | assert.equal(jxonTree['@type'], 'string'); 95 | assert.equal(jxonTree['@custom_attr'], 'foo'); 96 | }); 97 | 98 | it("must extract the value from the ", function(){ 99 | jxonTree = xml.toJXONTree(''); 100 | assert.equal(jxonTree.keyValue, 'QWZ5671'); 101 | }); 102 | 103 | it("must return undefined if the element is empty", function(){ 104 | jxonTree = xml.toJXONTree(''); 105 | assert.isUndefined(jxonTree.keyValue); 106 | 107 | jxonTree = xml.toJXONTree(''); 108 | assert.isUndefined(jxonTree.keyValue); 109 | }); 110 | 111 | it("must convert number elements", function(){ 112 | jxonTree = xml.toJXONTree('4321'); 113 | assert.strictEqual(jxonTree.keyValue, 4321); 114 | }); 115 | 116 | it("must convert ISO8601 Date strings into actual date objects", function(){ 117 | var now = new Date(); 118 | jxonTree = xml.toJXONTree(''+now.toISOString()+''); 119 | assert.equal(jxonTree.keyValue.getTime(), now.getTime()); 120 | }); 121 | 122 | it("must convert boolean values", function(){ 123 | assert.isTrue(xml.toJXONTree('true').keyValue); 124 | assert.isFalse(xml.toJXONTree('false').keyValue); 125 | }); 126 | 127 | it("must work with single tag elems", function(){ 128 | jxonTree = xml.toJXONTree(''); 129 | assert.equal(jxonTree.attr('type'), 'waterfall'); 130 | assert.equal(jxonTree.attr('fallback_index'), 0); 131 | }); 132 | 133 | it("must set empty attributes as null", function(){ 134 | assert.equal(xml.toJXONTree('true')["@id"], null); 135 | }); 136 | }); 137 | 138 | describe("on element node type", function(){ 139 | var jxonTree; 140 | 141 | beforeEach(function(){ 142 | var sampleXml = '' + 143 | '' + 144 | '' + 145 | '1234'+ 146 | ''; 147 | var xmlDoc = xml.strToXMLDoc(sampleXml); 148 | jxonTree = new xml.JXONTree(xmlDoc); 149 | }); 150 | 151 | it("must have a field for each tag element type", function(){ 152 | assert.isDefined(jxonTree.item_number); 153 | assert.isDefined(jxonTree.price); 154 | }); 155 | 156 | it("must publish all the child of the same type (tagname) on an array ", function(){ 157 | assert.isArray(jxonTree.item_number); 158 | assert.equal(jxonTree.item_number.length, 2); 159 | 160 | assert.equal(jxonTree.item_number[0].keyValue, "QWZ5671"); 161 | assert.equal(jxonTree.item_number[0].attr('type'), "string"); 162 | 163 | assert.equal(jxonTree.item_number[1].keyValue, "QWZ5672"); 164 | assert.equal(jxonTree.item_number[1].attr('type'), "string"); 165 | }); 166 | 167 | it("must publish single childs as the object itself", function(){ 168 | assert.equal(jxonTree.price.keyValue, 1234); 169 | assert.equal(jxonTree.price.attr('currency'), 'dollar'); 170 | }); 171 | }); 172 | 173 | describe("attr", function(){ 174 | it("must return the value of the atrr", function(){ 175 | var jxonTree = xml.toJXONTree(''); 176 | assert.equal('string', jxonTree.attr('type')); 177 | assert.equal('foo', jxonTree.attr('custom_attr')); 178 | }); 179 | }); 180 | }); 181 | 182 | describe("toJXONTree", function(){ 183 | it("must given a xml string, return a JXONTree object with the xml content", function(){ 184 | var jxonTree = xml.toJXONTree(''); 185 | assert.equal(jxonTree.keyValue, 'QWZ5671'); 186 | assert.equal(jxonTree['@type'], 'string'); 187 | assert.equal(jxonTree['@custom_attr'], 'fooo'); 188 | }); 189 | 190 | //Regression test 191 | it("must properly parse pecentage strings", function(){ 192 | var jxonTree = xml.toJXONTree('90%'); 193 | assert.equal(jxonTree.keyValue, '90%'); 194 | assert.equal(jxonTree['@custom_attr'], '10%'); 195 | }); 196 | }); 197 | 198 | describe("keyValue", function(){ 199 | it("must return the key value of the passed JXONTree obj", function(){ 200 | var jxonTree = xml.toJXONTree(''); 201 | assert.equal('QWZ5671', xml.keyValue(jxonTree)); 202 | }); 203 | 204 | it("must return undefined if the passed obje does not have a keyvalue", function(){ 205 | assert.isUndefined(xml.keyValue()); 206 | assert.isUndefined(xml.keyValue({})); 207 | }); 208 | }); 209 | 210 | describe("attr", function(){ 211 | it("must return the value of the attr on the passed obj", function(){ 212 | var jxonTree = xml.toJXONTree(''); 213 | assert.equal('string', xml.attr(jxonTree, 'type')); 214 | assert.equal('foo', xml.attr(jxonTree, 'custom_attr')); 215 | }); 216 | }); 217 | 218 | describe("encode", function(){ 219 | it("must return undefined when is not passed a string", function() { 220 | assert.isUndefined(xml.encode()); 221 | assert.isUndefined(xml.encode({})); 222 | }); 223 | 224 | it("must encode &, \", ', < and >", function(){ 225 | assert.equal(xml.encode("
\"'"), '<br/> "'' ); 226 | }); 227 | }); 228 | 229 | describe("decode", function(){ 230 | it("must return undefined when is not passed a string", function() { 231 | assert.isUndefined(xml.decode()); 232 | assert.isUndefined(xml.decode({})); 233 | }); 234 | 235 | it("must edcode a previously encoded xml", function(){ 236 | assert.equal(xml.decode('<br/> "''), "
\"'"); 237 | }); 238 | }); 239 | }); 240 | -------------------------------------------------------------------------------- /webpack.common.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var path = require('path'); 3 | var StringReplacePlugin = require('string-replace-webpack-plugin'); 4 | 5 | var buildProps = require('./webpack.properties.js'); 6 | 7 | var bannerOptions = { 8 | banner: buildProps.plugin.bannerText, 9 | entryOnly: true, 10 | raw: false 11 | } 12 | 13 | 14 | module.exports = function (mode) { 15 | 16 | console.log('Exporting Common Config > mode: ' + mode); 17 | 18 | var config = { 19 | module: { 20 | rules: [ 21 | { 22 | test: /\.js$/, 23 | enforce: 'pre', 24 | include: [ 25 | path.resolve(__dirname, 'src') 26 | ], 27 | loader: 'eslint-loader', 28 | options: { 29 | emitError: true, 30 | failOnError: true, 31 | } 32 | }, 33 | { 34 | test: /\.js$/, 35 | enforce: 'pre', 36 | include: [ 37 | path.resolve(__dirname, 'src') 38 | ], 39 | loader: StringReplacePlugin.replace({ 40 | replacements: [ 41 | { 42 | /* Any '' (closing stript tags within string literals) 43 | * that exist in the code must be escaped - or they may be 44 | * misinterpreted by browsers as closing the parent script tag 45 | * that this code is embedded within on the parent html page. */ 46 | pattern: /<\/script>/gi, 47 | replacement: function (match, p1, offset, string) { 48 | console.warn('*******************************************************************************************'); 49 | console.warn('*** WARNING: "<\/script>" string found in code - THIS SHOULD BE ESCAPED to: "<\\/script>" ***'); 50 | console.warn('*******************************************************************************************'); 51 | return '<\\/script>'; 52 | } 53 | }, 54 | { 55 | /* Remove any debugger statements for a production build */ 56 | pattern: /debugger;|debugger/g, 57 | replacement: function (match, p1, offset, string) { 58 | if (mode === buildProps.MODE_PRODUCTION) { 59 | return ''; 60 | } else { 61 | console.warn('********************************************************************************'); 62 | console.warn('*** DEV WARNING: debugger; statement found in code - DO NOT COMMIT THIS CODE ***'); 63 | console.warn('********************************************************************************'); 64 | return match; // Return the same string back - just throw a big warning 65 | } 66 | } 67 | } 68 | ] 69 | }) 70 | } 71 | ] 72 | }, 73 | plugins: [ 74 | new StringReplacePlugin(), 75 | new webpack.BannerPlugin(bannerOptions) 76 | ] 77 | }; 78 | 79 | return config; 80 | }; 81 | -------------------------------------------------------------------------------- /webpack.dev.js: -------------------------------------------------------------------------------- 1 | var merge = require('webpack-merge'); 2 | var CleanWebpackPlugin = require('clean-webpack-plugin'); 3 | 4 | var buildProps = require('./webpack.properties.js'); 5 | 6 | var WEBPACK_MODE = buildProps.MODE_DEVELOPMENT; 7 | 8 | var commonConfig = require('./webpack.common.js')(WEBPACK_MODE); 9 | 10 | module.exports = function (env, argv) { 11 | 12 | var cleanConfig = { 13 | plugins: [ 14 | new CleanWebpackPlugin() 15 | ] 16 | }; 17 | 18 | var pluginConfig = { 19 | mode: WEBPACK_MODE, 20 | entry: buildProps.plugin.entry_file, 21 | devtool: buildProps.devTool[WEBPACK_MODE], 22 | output: { 23 | path: buildProps.output.path, 24 | filename: buildProps.plugin.output_file[WEBPACK_MODE], 25 | libraryTarget: buildProps.plugin.libraryTarget, 26 | library: buildProps.plugin.var_name 27 | } 28 | }; 29 | 30 | pluginConfig = merge(pluginConfig, commonConfig, cleanConfig); 31 | 32 | return [pluginConfig]; 33 | }; 34 | -------------------------------------------------------------------------------- /webpack.prod.js: -------------------------------------------------------------------------------- 1 | var merge = require('webpack-merge'); 2 | 3 | var buildProps = require('./webpack.properties.js'); 4 | 5 | var WEBPACK_MODE = buildProps.MODE_PRODUCTION; 6 | 7 | var devConfig = require('./webpack.dev.js'); 8 | var commonConfig = require('./webpack.common.js')(WEBPACK_MODE); 9 | 10 | module.exports = function (env, argv) { 11 | 12 | devConfig = devConfig(env, argv); 13 | 14 | var pluginConfig = { 15 | mode: WEBPACK_MODE, 16 | entry: buildProps.plugin.entry_file, 17 | devtool: buildProps.devTool[WEBPACK_MODE], 18 | output: { 19 | path: buildProps.output.path, 20 | filename: buildProps.plugin.output_file[WEBPACK_MODE], 21 | libraryTarget: buildProps.plugin.libraryTarget, 22 | library: buildProps.plugin.var_name 23 | }, 24 | module: { 25 | rules: [{ 26 | test: /\.js$/, 27 | enforce: 'pre', 28 | exclude: /(node_modules|test)/, 29 | use: [{ 30 | loader: 'webpack-strip-block', 31 | options: { 32 | start: 'PROD-EXCLUDE-START', // Format: /* BUILD-EXCLUDE-START */ -- Beginning of code block to exclude from the build 33 | end: 'PROD-EXCLUDE-END' // Format: /* BUILD-EXCLUDE-END */ -- End of code block to exclude from the build 34 | } 35 | }] 36 | }] 37 | } 38 | }; 39 | 40 | pluginConfig = merge(pluginConfig, commonConfig); 41 | 42 | return devConfig.concat([pluginConfig]); 43 | }; 44 | -------------------------------------------------------------------------------- /webpack.properties.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | 4 | // For build comments at top of file 5 | var pkg = require('./package.json'); 6 | 7 | var versionText = 'v' + pkg.version; 8 | 9 | var licenseHeaders = fs.readFileSync('license-bc-mol.txt'); 10 | 11 | var curDateObj = new Date(); 12 | 13 | var copyrightText = '(c)' + curDateObj.getUTCFullYear() + ' PREBID.ORG, INC'; 14 | 15 | var pluginBannerText = copyrightText + ' ' + versionText + '\n' + licenseHeaders; 16 | 17 | 18 | 19 | var PROPS = { 20 | 21 | MODE_DEVELOPMENT: 'development', 22 | MODE_PRODUCTION: 'production', 23 | 24 | /* SHARED PROPERTIES */ 25 | 26 | output: { 27 | path: path.join(__dirname, 'dist') 28 | }, 29 | 30 | // 'devTool' sets the type of source maps used - see docs here: https://webpack.js.org/configuration/devtool 31 | devTool: { 32 | development: 'eval-source-map', 33 | production: 'none', 34 | }, 35 | 36 | /* PLUGIN PROPERTIES */ 37 | 38 | plugin: { 39 | entry_file: './src/scripts/videojs_5.vast.vpaid.js', 40 | output_file: { 41 | development: 'videojs_5.vast.vpaid.js', 42 | production: 'videojs_5.vast.vpaid.min.js', 43 | }, 44 | bannerText: pluginBannerText 45 | }, 46 | 47 | /* UTIL FUNCTIONS */ 48 | 49 | util: { 50 | traceObj: function traceObj (obj, depth) { 51 | depth = depth || 0; 52 | var space = new Array(depth + 2).join('==') + '> '; 53 | for (var k in obj) { 54 | console.log(space + k + ' --> ' + obj[k]); 55 | if (typeof obj[k] === 'object') { 56 | traceObj(obj[k], depth + 1); 57 | } 58 | } 59 | } 60 | } 61 | }; 62 | 63 | module.exports = PROPS; 64 | --------------------------------------------------------------------------------